From a78f1ee3ff1e21ae0fbf4fd72dd4f4364c50d7b7 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:34:32 -0500 Subject: [PATCH 01/12] add readlink Disable the test on a symbolic link until symlink is added. Also add myself to AUTHORS --- AUTHORS | 1 + README.md | 5 +++ src/fs.js | 71 ++++++++++++++++++++++++++++++++++- tests/spec/idbfs.spec.js | 81 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 9eea44b..e218b72 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,3 @@ Alan K (blog.modeswitch.org) David Humphrey (@humphd) +Abir Viqar diff --git a/README.md b/README.md index 2f1c8db..20232fb 100644 --- a/README.md +++ b/README.md @@ -160,3 +160,8 @@ Asynchronous lseek(2), where `whence` can be `SET`, `CUR`, or `END`. Callback ge #### fs.readdir(path, callback) Asynchronous readdir(3). Reads the contents of a directory. Callback gets `(error, files)`, where `files` is an array containing the names of each file in the directory, excluding `.` and `..`. + +#### fs.readlink(path, callback) + +Asynchronous readlink(2). Callback gets `(error, linkContents)`, where `linkContents` is a string containing the path to which the symbolic link links to. + diff --git a/src/fs.js b/src/fs.js index 21ef81a..94c9c93 100644 --- a/src/fs.js +++ b/src/fs.js @@ -33,6 +33,7 @@ define(function(require) { var FS_FORMAT = require('src/constants').FS_FORMAT; var MODE_FILE = require('src/constants').MODE_FILE; var MODE_DIRECTORY = require('src/constants').MODE_DIRECTORY; + var MODE_SYMBOLIC_LINK = require('src/constants').MODE_SYMBOLIC_LINK; var ROOT_DIRECTORY_NAME = require('src/constants').ROOT_DIRECTORY_NAME; var ROOT_NODE_ID = require('src/constants').ROOT_NODE_ID; var IDB_RW = require('src/constants').IDB_RW; @@ -752,6 +753,51 @@ define(function(require) { } } + function read_link(objectStore, path, callback) { + path = normalize(path); + var name = basename(path); + var parentPath = dirname(path); + + var directoryNode; + var directoryData; + + find_node(objectStore, parentPath, read_directory_data); + + function read_directory_data(error, result) { + if(error) { + callback(error); + } else { + directoryNode = result; + read_object(objectStore, directoryNode.data, check_if_file_exists); + } + } + + function check_if_file_exists(error, result) { + if(error) { + callback(error); + } else { + directoryData = result; + if(!_(directoryData).has(name)) { + callback(new ENoEntry('a component of the path does not name an existing file')); + } else { + read_object(objectStore, directoryData[name].id, check_if_symbolic); + } + } + } + + function check_if_symbolic(error, result) { + if(error) { + callback(error); + } else { + if(result.mode != MODE_SYMBOLIC_LINK) { + callback(new EInvalid("path not a symbolic link")); + } else { + callback(undefined, result.data); + } + } + } + } + function validate_flags(flags) { if(!_(O_FLAGS).has(flags)) { return null; @@ -1169,8 +1215,19 @@ define(function(require) { FileSystem.prototype._symlink = function _symlink(fd, length, callback) { }; - FileSystem.prototype._readlink = function _readlink(fd, length, callback) { + FileSystem.prototype._readlink = function _readlink(context, path, callback) { + var that = this; + function check_result(error, result) { + if(error) { + // if(transaction.error) transaction.abort(); + callback(error); + } else { + callback(undefined, result); + } + } + + read_link(context, path, check_result); }; FileSystem.prototype._realpath = function _realpath(fd, length, callback) { @@ -1458,6 +1515,18 @@ define(function(require) { ); if(error) callback(error); }; + IndexedDBFileSystem.prototype.readlink = function readlink(path, callback) { + var fs = this; + var error = this._queueOrRun( + function() { + var transaction = fs.db.transaction([FILE_STORE_NAME], IDB_RW); + var files = transaction.objectStore(FILE_STORE_NAME); + var context = new IndexedDBContext(files); + fs._readlink(context, path, callback); + } + ); + if(error) callback(error); + }; // FIXME: WebSQL stuff, this needs implementation function WebSQLContext(transaction) { diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 2cdc912..8f1a74d 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1350,3 +1350,84 @@ describe('fs.lseek', function() { }); }); }); + +describe('fs.readlink', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem(this.db_name, 'FORMAT'); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should be a function', function() { + expect(typeof this.fs.readlink).toEqual('function'); + }); + + it('should return an error if part of the parent destination path does not exist', function() { + var complete = false; + var _error; + var that = this; + + that.fs.readlink('/tmp/mydir', function(error) { + _error = error; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should return an error if the path is not a symbolic link', function() { + var complete = false; + var _error; + var that = this; + + that.fs.readlink('/', function(error) { + _error = error; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + xit('should return the contents of a symbolic link', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.symlink('/', '/myfile', function(error) { + if(error) throw error; + + that.fs.readlink('/myfile', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).not.toBeDefined(); + expect(_result).toEqual('/'); + }); + }); +}); From 85a804cc0c9ed5b80880a564c698a731c0ac6fa2 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:34:40 -0500 Subject: [PATCH 02/12] add symlink Also enable readlink's symbolic link test. --- README.md | 7 +++- src/fs.js | 80 +++++++++++++++++++++++++++++++++++++++- tests/spec/idbfs.spec.js | 79 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20232fb..b0e9a49 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,12 @@ Asynchronous lseek(2), where `whence` can be `SET`, `CUR`, or `END`. Callback ge Asynchronous readdir(3). Reads the contents of a directory. Callback gets `(error, files)`, where `files` is an array containing the names of each file in the directory, excluding `.` and `..`. +#### fs.symlink(srcPath, dstPath, callback) + +Asynchronous symlink(2). Callback gets no additional arguments. + +Unlike node.js, IDBFS does not accept the optional `type` parameter. + #### fs.readlink(path, callback) Asynchronous readlink(2). Callback gets `(error, linkContents)`, where `linkContents` is a string containing the path to which the symbolic link links to. - diff --git a/src/fs.js b/src/fs.js index 94c9c93..081e6da 100644 --- a/src/fs.js +++ b/src/fs.js @@ -753,6 +753,61 @@ define(function(require) { } } + function make_symbolic_link(objectStore, srcpath, dstpath, callback) { + dstpath = normalize(dstpath); + var name = basename(dstpath); + var parentPath = dirname(dstpath); + + var directoryNode; + var directoryData; + var fileNode; + + if(ROOT_DIRECTORY_NAME == name) { + callback(new EExists('the destination path already exists')); + } else { + find_node(objectStore, parentPath, read_directory_data); + } + + function read_directory_data(error, result) { + if(error) { + callback(error); + } else { + directoryNode = result; + read_object(objectStore, directoryNode.data, check_if_file_exists); + } + } + + function check_if_file_exists(error, result) { + if(error) { + callback(error); + } else { + directoryData = result; + if(_(directoryData).has(name)) { + callback(new EExists('the destination path already exists')); + } else { + write_file_node(); + } + } + } + + function write_file_node() { + fileNode = new Node(undefined, MODE_SYMBOLIC_LINK); + fileNode.nlinks += 1; + fileNode.size = srcpath.length; + fileNode.data = srcpath; + write_object(objectStore, fileNode, fileNode.id, update_directory_data); + } + + function update_directory_data(error) { + if(error) { + callback(error); + } else { + directoryData[name] = new DirectoryEntry(fileNode.id, MODE_SYMBOLIC_LINK); + write_object(objectStore, directoryData, directoryNode.data, callback); + } + } + } + function read_link(objectStore, path, callback) { path = normalize(path); var name = basename(path); @@ -1212,8 +1267,19 @@ define(function(require) { FileSystem.prototype._ftruncate = function _ftruncate(fd, length, callback) { }; - FileSystem.prototype._symlink = function _symlink(fd, length, callback) { + FileSystem.prototype._symlink = function _symlink(context, srcpath, dstpath, callback) { + var that = this; + function check_result(error) { + if(error) { + // if(transaction.error) transaction.abort(); + callback(error); + } else { + callback(undefined); + } + } + + make_symbolic_link(context, srcpath, dstpath, check_result); }; FileSystem.prototype._readlink = function _readlink(context, path, callback) { var that = this; @@ -1527,6 +1593,18 @@ define(function(require) { ); if(error) callback(error); }; + IndexedDBFileSystem.prototype.symlink = function symlink(srcpath, dstpath, callback) { + var fs = this; + var error = this._queueOrRun( + function() { + var transaction = fs.db.transaction([FILE_STORE_NAME], IDB_RW); + var files = transaction.objectStore(FILE_STORE_NAME); + var context = new IndexedDBContext(files); + fs._symlink(context, srcpath, dstpath, callback); + } + ); + if(error) callback(error); + }; // FIXME: WebSQL stuff, this needs implementation function WebSQLContext(transaction) { diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 8f1a74d..8f0ca41 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1351,6 +1351,83 @@ describe('fs.lseek', function() { }); }); +describe('fs.symlink', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem(this.db_name, 'FORMAT'); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should be a function', function() { + expect(typeof this.fs.symlink).toEqual('function'); + }); + + it('should return an error if part of the parent destination path does not exist', function() { + var complete = false; + var _error; + var that = this; + + that.fs.symlink('/', '/tmp/mydir', function(error) { + _error = error; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should return an error if the destination path already exists', function() { + var complete = false; + var _error; + var that = this; + + that.fs.symlink('/tmp', '/', function(error) { + _error = error; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should create a symlink', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.symlink('/', '/myfile', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).not.toBeDefined(); + expect(_result).not.toBeDefined(); + }); + }); +}); + describe('fs.readlink', function() { beforeEach(function() { this.db_name = mk_db_name(); @@ -1406,7 +1483,7 @@ describe('fs.readlink', function() { }); }); - xit('should return the contents of a symbolic link', function() { + it('should return the contents of a symbolic link', function() { var complete = false; var _error, _result; var that = this; From 5ceff20b1265e65e6f74ce34450b7be29a4e19f1 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:34:48 -0500 Subject: [PATCH 03/12] make find_node symbolic link aware This adds a new constant SYMLOOP_MAX, which corresponds to the POSIX variable of the number of symbolic links that may be followed. A new error Eloop was added to signal when SYMLOOP_MAX is exceeded. SYMLOOP_MAX has been arbitrarily set to 10 while on linux it is set to 40 and the POSIX minimum is 8. find_node when encountering a symbolic link anywhere in the given path will attempt to follow it. Note that SYMLOOP_MAX is only the limit of symbolic links to follow per symbolic link. There is currently no attempt to limit the total number of symbolic links followed when resolving a path. This adds tests for path resolution of symbolic links as well. --- src/constants.js | 2 + src/error.js | 10 +- src/fs.js | 39 ++++- tests/spec/idbfs.spec.js | 345 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 391 insertions(+), 5 deletions(-) diff --git a/src/constants.js b/src/constants.js index 4a5f859..5fe34eb 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,6 +18,8 @@ define(function(require) { MODE_DIRECTORY: 'DIRECTORY', MODE_SYMBOLIC_LINK: 'SYMLINK', + SYMLOOP_MAX: 10, + BINARY_MIME_TYPE: 'application/octet-stream', JSON_MIME_TYPE: 'application/json', diff --git a/src/error.js b/src/error.js index e9b0ac9..c9ab92a 100644 --- a/src/error.js +++ b/src/error.js @@ -91,6 +91,13 @@ define(function(require) { EIO.prototype.name = "EIO"; EIO.prototype.constructor = EIO; + function ELoop(message){ + this.message = message || ''; + } + ELoop.prototype = new Error(); + ELoop.prototype.name = "ELoop"; + ELoop.prototype.constructor = ELoop; + function EFileSystemError(message){ this.message = message || ''; } @@ -109,7 +116,8 @@ define(function(require) { ENotImplemented: ENotImplemented, ENotMounted: ENotMounted, EInvalid: EInvalid, - EIO: EIO + EIO: EIO, + ELoop: ELoop }; }); diff --git a/src/fs.js b/src/fs.js index 081e6da..665ecfd 100644 --- a/src/fs.js +++ b/src/fs.js @@ -28,6 +28,7 @@ define(function(require) { var ENotMounted = require('src/error').ENotMounted; var EInvalid = require('src/error').EInvalid; var EIO = require('src/error').EIO; + var ELoop = require('src/error').ELoop; var EFileSystemError = require('src/error').EFileSystemError; var FS_FORMAT = require('src/constants').FS_FORMAT; @@ -40,6 +41,7 @@ define(function(require) { var IDB_RO = require('src/constants').IDB_RO; var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME; var METADATA_STORE_NAME = require('src/constants').METADATA_STORE_NAME; + var SYMLOOP_MAX = require('src/constants').SYMLOOP_MAX; var FS_READY = require('src/constants').FS_READY; var FS_PENDING = require('src/constants').FS_PENDING; var FS_ERROR = require('src/constants').FS_ERROR; @@ -120,6 +122,7 @@ define(function(require) { } var name = basename(path); var parentPath = dirname(path); + var followedCount = 0; function check_root_directory_node(error, rootDirectoryNode) { if(error) { @@ -139,13 +142,13 @@ define(function(require) { } else if(parentDirectoryNode.mode !== MODE_DIRECTORY || !parentDirectoryNode.data) { callback(new ENotDirectory('a component of the path prefix is not a directory')); } else { - read_object(objectStore, parentDirectoryNode.data, get_node_id_from_parent_directory_data); + read_object(objectStore, parentDirectoryNode.data, get_node_from_parent_directory_data); } } // in: parent directory data - // out: searched node id - function get_node_id_from_parent_directory_data(error, parentDirectoryData) { + // out: searched node + function get_node_from_parent_directory_data(error, parentDirectoryData) { if(error) { callback(error); } else { @@ -153,11 +156,39 @@ define(function(require) { callback(new ENoEntry('path does not exist')); } else { var nodeId = parentDirectoryData[name].id; - read_object(objectStore, nodeId, callback); + read_object(objectStore, nodeId, is_symbolic_link); } } } + function is_symbolic_link(error, node) { + if(error) { + callback(error); + } else { + if(node.mode == MODE_SYMBOLIC_LINK) { + followedCount++; + if(followedCount > SYMLOOP_MAX){ + callback(new ELoop('too many symbolic links were encountered')); + } else { + follow_symbolic_link(node.data); + } + } else { + callback(undefined, node); + } + } + } + + function follow_symbolic_link(data) { + data = normalize(data); + parentPath = dirname(data); + name = basename(data); + if(ROOT_DIRECTORY_NAME == name) { + read_object(objectStore, ROOT_NODE_ID, check_root_directory_node); + } else { + find_node(objectStore, parentPath, read_parent_directory_data); + } + } + if(ROOT_DIRECTORY_NAME == name) { read_object(objectStore, ROOT_NODE_ID, check_root_directory_node); } else { diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 8f0ca41..70634f6 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1508,3 +1508,348 @@ describe('fs.readlink', function() { }); }); }); + +describe('path resolution', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem(this.db_name, 'FORMAT'); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should follow a symbolic link to the root directory', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.symlink('/', '/mydirectorylink', function(error) { + if(error) throw error; + + that.fs.stat('/', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.stat('/mydirectorylink', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); + + it('should follow a symbolic link to a directory', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.mkdir('/mydir', function(error) { + that.fs.symlink('/mydir', '/mydirectorylink', function(error) { + if(error) throw error; + + that.fs.stat('/mydir', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.stat('/mydirectorylink', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); + + it('should follow a symbolic link to a file', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.symlink('/myfile', '/myfilelink', function(error) { + if(error) throw error; + + that.fs.stat('/myfilelink', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); + + it('should follow multiple symbolic links to a file', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.symlink('/myfile', '/myfilelink1', function(error) { + if(error) throw error; + that.fs.symlink('/myfilelink1', '/myfilelink2', function(error) { + if(error) throw error; + + that.fs.stat('/myfilelink2', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); + + it('should error if symbolic link leads to itself', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.symlink('/mylink1', '/mylink2', function(error) { + if(error) throw error; + + that.fs.symlink('/mylink2', '/mylink1', function(error) { + if(error) throw error; + + that.fs.stat('/myfilelink1', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + expect(_result).not.toBeDefined(); + }); + }); + + it('should error if it follows more than 10 symbolic links', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + that.fs.symlink('/myfile', '/myfilelink1', function(error) { + if(error) throw error; + that.fs.symlink('/myfilelink1', '/myfilelink2', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink2', '/myfilelink3', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink3', '/myfilelink4', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink4', '/myfilelink5', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink5', '/myfilelink6', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink6', '/myfilelink7', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink7', '/myfilelink8', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink8', '/myfilelink9', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink9', '/myfilelink10', function(error) { + if(error) throw error; + + that.fs.symlink('/myfilelink10', '/myfilelink11', function(error) { + if(error) throw error; + + that.fs.stat('/myfilelink11', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).not.toBeDefined(); + expect(_error).toBeDefined(); + }); + }); + + it('should follow a symbolic link in the path to a file', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.symlink('/', '/mydirlink', function(error) { + if(error) throw error; + + that.fs.stat('/mydirlink/myfile', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); + + it('should error if a symbolic link in the path to a file is itself a file', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + that.fs.open('/myfile2', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.symlink('/myfile2', '/mynotdirlink', function(error) { + if(error) throw error; + + that.fs.stat('/mynotdirlink/myfile', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + expect(_result).not.toBeDefined(); + }); + }); +}); From 5cbebd135cb770f1159f6f99c04873c636148327 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:34:57 -0500 Subject: [PATCH 04/12] add lstat --- README.md | 4 ++ src/fs.js | 71 ++++++++++++++++++++++++++++++++- tests/spec/idbfs.spec.js | 86 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0e9a49..4fbc28c 100644 --- a/README.md +++ b/README.md @@ -170,3 +170,7 @@ Unlike node.js, IDBFS does not accept the optional `type` parameter. #### fs.readlink(path, callback) Asynchronous readlink(2). Callback gets `(error, linkContents)`, where `linkContents` is a string containing the path to which the symbolic link links to. + +#### fs.lstat(path, callback) + +Asynchronous lstat(2). Callback gets `(error, stats)`, See `fs.stat`. diff --git a/src/fs.js b/src/fs.js index 665ecfd..817866a 100644 --- a/src/fs.js +++ b/src/fs.js @@ -609,6 +609,51 @@ define(function(require) { } } + function lstat_file(objectStore, path, callback) { + path = normalize(path); + var name = basename(path); + var parentPath = dirname(path); + + var directoryNode; + var directoryData; + + if(ROOT_DIRECTORY_NAME == name) { + read_object(objectStore, ROOT_NODE_ID, check_file); + } else { + find_node(objectStore, parentPath, read_directory_data); + } + + function read_directory_data(error, result) { + if(error) { + callback(error); + } else { + directoryNode = result; + read_object(objectStore, directoryNode.data, check_if_file_exists); + } + } + + function check_if_file_exists(error, result) { + if(error) { + callback(error); + } else { + directoryData = result; + if(!_(directoryData).has(name)) { + callback(new ENoEntry('a component of the path does not name an existing file')); + } else { + read_object(objectStore, directoryData[name].id, check_file); + } + } + } + + function check_file(error, result) { + if(error) { + callback(error); + } else { + callback(undefined, result); + } + } + } + function link_node(objectStore, oldpath, newpath, callback) { oldpath = normalize(oldpath); var oldname = basename(oldpath); @@ -1329,8 +1374,20 @@ define(function(require) { FileSystem.prototype._realpath = function _realpath(fd, length, callback) { }; - FileSystem.prototype._lstat = function _lstat(fd, length, callback) { + FileSystem.prototype._lstat = function _lstat(context, path, callback) { + var that = this; + function check_result(error, result) { + if(error) { + // if(transaction.error) transaction.abort(); + callback(error); + } else { + var stats = new Stats(result, that.name); + callback(undefined, stats); + } + } + + lstat_file(context, path, check_result); }; function IndexedDBContext(objectStore) { @@ -1636,6 +1693,18 @@ define(function(require) { ); if(error) callback(error); }; + IndexedDBFileSystem.prototype.lstat = function lstat(path, callback) { + var fs = this; + var error = this._queueOrRun( + function() { + var transaction = fs.db.transaction([FILE_STORE_NAME], IDB_RW); + var files = transaction.objectStore(FILE_STORE_NAME); + var context = new IndexedDBContext(files); + fs._lstat(context, path, callback); + } + ); + if(error) callback(error); + }; // FIXME: WebSQL stuff, this needs implementation function WebSQLContext(transaction) { diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 70634f6..e2d7257 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -201,6 +201,92 @@ describe('fs.fstat', function() { }); }); +describe('fs.lstat', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem(this.db_name, 'FORMAT'); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should be a function', function() { + expect(typeof this.fs.lstat).toEqual('function'); + }); + + it('should return an error if path does not exist', function() { + var complete = false; + var _error, _result; + + this.fs.lstat('/tmp', function(error, result) { + _error = error; + _result = result; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + expect(_result).not.toBeDefined(); + }); + }); + + it('should return a stat object if path is not a symbolic link', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.lstat('/', function(error, result) { + _error = error; + _result = result; + + complete = true; + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).not.toBeDefined(); + expect(_result).toBeDefined(); + }); + }); + + + it('should return a stat object if path is a symbolic link', function() { + var complete = false; + var _error, _result; + var that = this; + + that.fs.symlink('/', '/mylink', function(error) { + if(error) throw error; + + that.fs.lstat('/mylink', function(error, result) { + _error = error; + _result = result; + + complete = true; + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).not.toBeDefined(); + expect(_result).toBeDefined(); + }); + }); +}); + describe('fs.mkdir', function() { beforeEach(function() { this.db_name = mk_db_name(); From e8df3e2e6bbcc646caa92e12d6d3c820811a71ba Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:01 -0500 Subject: [PATCH 05/12] test stat's symbolic link support --- tests/spec/idbfs.spec.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index e2d7257..f53ca02 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -148,6 +148,45 @@ describe('fs.stat', function() { expect(_result['type']).toBeDefined(); }); }); + + it('should follow symbolic links and return a stat object for the resulting path', function() { + var complete = false; + var _error, _node, _result; + var that = this; + + that.fs.open('/myfile', 'w', function(error, result) { + if(error) throw error; + var fd = result; + that.fs.close(fd, function(error) { + if(error) throw error; + that.fs.stat('/myfile', function(error, result) { + if(error) throw error; + + _node = result['node']; + that.fs.symlink('/myfile', '/myfilelink', function(error) { + if(error) throw error; + + that.fs.stat('/myfilelink', function(error, result) { + _error = error; + _result = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toBeDefined(); + expect(_node).toBeDefined(); + expect(_error).not.toBeDefined(); + expect(_result['node']).toEqual(_node); + }); + }); }); describe('fs.fstat', function() { From 635dd2e89906902ea58b9bb6fbe45315159c8502 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:05 -0500 Subject: [PATCH 06/12] test readdir's symbolic link support --- tests/spec/idbfs.spec.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index f53ca02..aac1838 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -468,6 +468,35 @@ describe('fs.readdir', function() { expect(_files[0]).toEqual('tmp'); }); }); + + it('should follow symbolic links', function() { + var complete = false; + var _error, _files; + var that = this; + + that.fs.mkdir('/tmp', function(error) { + if(error) throw error; + that.fs.symlink('/', '/tmp/dirLink', function(error) { + if(error) throw error; + that.fs.readdir('/tmp/dirLink', function(error, result) { + _error = error; + _files = result; + + complete = true; + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).not.toBeDefined(); + expect(_files.length).toEqual(1); + expect(_files[0]).toEqual('tmp'); + }); + }); }); describe('fs.rmdir', function() { From 656dfd242bab788743bbb7522e7b5593942eae4d Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:10 -0500 Subject: [PATCH 07/12] rmdir should error on non-directories --- src/fs.js | 49 ++++++++++++++++++++++----------------- tests/spec/idbfs.spec.js | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/src/fs.js b/src/fs.js index 817866a..7536dac 100644 --- a/src/fs.js +++ b/src/fs.js @@ -332,13 +332,34 @@ define(function(require) { var parentDirectoryNode; var parentDirectoryData; - function check_if_directory_exists(error, result) { + function read_parent_directory_data(error, result) { + if(error) { + callback(error); + } else { + parentDirectoryNode = result; + read_object(objectStore, parentDirectoryNode.data, check_if_node_exists); + } + } + + function check_if_node_exists(error, result) { if(error) { callback(error); } else if(ROOT_DIRECTORY_NAME == name) { callback(new EBusy()); - } else if(!result) { + } else if(!_(result).has(name)) { callback(new ENoEntry()); + } else { + parentDirectoryData = result; + directoryNode = parentDirectoryData[name].id; + read_object(objectStore, directoryNode, check_if_node_is_directory); + } + } + + function check_if_node_is_directory(error, result) { + if(error) { + callback(error); + } else if(result.mode != MODE_DIRECTORY) { + callback(new ENotDirectory()); } else { directoryNode = result; read_object(objectStore, directoryNode.data, check_if_directory_is_empty); @@ -353,28 +374,14 @@ define(function(require) { if(_(directoryData).size() > 0) { callback(new ENotEmpty()); } else { - find_node(objectStore, parentPath, read_parent_directory_data); + remove_directory_entry_from_parent_directory_node(); } } } - function read_parent_directory_data(error, result) { - if(error) { - callback(error); - } else { - parentDirectoryNode = result; - read_object(objectStore, parentDirectoryNode.data, remove_directory_entry_from_parent_directory_node); - } - } - - function remove_directory_entry_from_parent_directory_node(error, result) { - if(error) { - callback(error); - } else { - parentDirectoryData = result; - delete parentDirectoryData[name]; - write_object(objectStore, parentDirectoryData, parentDirectoryNode.data, remove_directory_node); - } + function remove_directory_entry_from_parent_directory_node() { + delete parentDirectoryData[name]; + write_object(objectStore, parentDirectoryData, parentDirectoryNode.data, remove_directory_node); } function remove_directory_node(error) { @@ -393,7 +400,7 @@ define(function(require) { } } - find_node(objectStore, path, check_if_directory_exists); + find_node(objectStore, parentPath, read_parent_directory_data); } function open_file(fs, objectStore, path, flags, callback) { diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index aac1838..ff55dc5 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -578,6 +578,56 @@ describe('fs.rmdir', function() { }); }); + it('should return an error if the path is not a directory', function() { + var complete = false; + var _error; + var that = this; + + that.fs.mkdir('/tmp', function(error) { + that.fs.open('/tmp/myfile', 'w', function(error, fd) { + that.fs.close(fd, function(error) { + that.fs.rmdir('/tmp/myfile', function(error) { + _error = error; + + complete = true; + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toBeDefined(); + }); + }); + + it('should return an error if the path is a symbolic link', function () { + var complete = false; + var _error; + var that = this; + + that.fs.mkdir('/tmp', function (error) { + that.fs.symlink('/tmp', '/tmp/myfile', function (error) { + that.fs.rmdir('/tmp/myfile', function (error) { + _error = error; + + complete = true; + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + }); + }); + it('should remove an existing directory', function() { var complete = false; var _error, _stat; From f3b9a8334364c86a43ff34e998cf87b8ca4a3f65 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:14 -0500 Subject: [PATCH 08/12] test link should not follow symbolic links --- tests/spec/idbfs.spec.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index ff55dc5..887d154 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1258,7 +1258,45 @@ describe('fs.link', function() { expect(_newstats).toEqual(_oldstats); }); }); + + it('should not follow symbolic links', function () { + var complete = false; + var _error, _oldstats, _linkstats, _newstats; + var that = this; + + that.fs.stat('/', function (error, result) { + if (error) throw error; + _oldstats = result; + that.fs.symlink('/', '/myfileLink', function (error) { + if (error) throw error; + that.fs.link('/myfileLink', '/myotherfile', function (error) { + if (error) throw error; + that.fs.lstat('/myfileLink', function (error, result) { + if (error) throw error; + _linkstats = result; + that.fs.lstat('/myotherfile', function (error, result) { + if (error) throw error; + _newstats = result; + complete = true; + }); + }); + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).not.toBeDefined(); + expect(_newstats.node).toEqual(_linkstats.node); + expect(_newstats.node).toNotEqual(_oldstats.node); + expect(_newstats.nlinks).toEqual(2); + expect(_newstats).toEqual(_linkstats); + }); }); + }); describe('fs.unlink', function() { beforeEach(function() { From 1b3ef1a449c3fc6330c536f699e2fcff8c743cad Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:18 -0500 Subject: [PATCH 09/12] unlink should not follow symbolic links --- tests/spec/idbfs.spec.js | 43 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 887d154..64142c6 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1357,6 +1357,49 @@ describe('fs.unlink', function() { expect(_stats.nlinks).toEqual(1); }); }); + + it('should not follow symbolic links', function () { + var complete = false; + var _error, _stats1, _stats2; + var that = this; + + that.fs.symlink('/', '/myFileLink', function (error) { + if (error) throw error; + + that.fs.link('/myFileLink', '/myotherfile', function (error) { + if (error) throw error; + + that.fs.unlink('/myFileLink', function (error) { + if (error) throw error; + + that.fs.lstat('/myFileLink', function (error, result) { + _error = error; + + that.fs.lstat('/myotherfile', function (error, result) { + if (error) throw error; + _stats1 = result; + + that.fs.stat('/', function (error, result) { + if (error) throw error; + _stats2 = result; + complete = true; + }); + }); + }); + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_stats1.nlinks).toEqual(1); + expect(_stats2.nlinks).toEqual(1); + }); + }); }); describe('fs.rename', function() { From a95bf43069cfd69a149af39d42409c022f464d41 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:31 -0500 Subject: [PATCH 10/12] unlink_node - add missing var --- src/fs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fs.js b/src/fs.js index 7536dac..5d0e905 100644 --- a/src/fs.js +++ b/src/fs.js @@ -744,8 +744,8 @@ define(function(require) { function unlink_node(objectStore, path, callback) { path = normalize(path); - name = basename(path); - parentPath = dirname(path); + var name = basename(path); + var parentPath = dirname(path); var directoryNode; var directoryData; From 702dd1a3a303ac82c5717ff5e847130298dab5d5 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:23 -0500 Subject: [PATCH 11/12] open should follow symbolic links This functionality is tested implicitly through tests in writefile, readfile --- src/fs.js | 36 +++++++++++++++++++++++++++++++++++- tests/spec/idbfs.spec.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/fs.js b/src/fs.js index 5d0e905..3c94ede 100644 --- a/src/fs.js +++ b/src/fs.js @@ -414,6 +414,8 @@ define(function(require) { var fileNode; var fileData; + var followedCount = 0; + if(ROOT_DIRECTORY_NAME == name) { if(_(flags).contains(O_WRITE)) { callback(new EIsDirectory('the named file is a directory and O_WRITE is set')); @@ -446,7 +448,7 @@ define(function(require) { if(directoryEntry.type == MODE_DIRECTORY && _(flags).contains(O_WRITE)) { callback(new EIsDirectory('the named file is a directory and O_WRITE is set')); } else { - read_object(objectStore, directoryEntry.id, set_file_node); + read_object(objectStore, directoryEntry.id, check_if_symbolic_link); } } } else { @@ -459,6 +461,38 @@ define(function(require) { } } + function check_if_symbolic_link(error, result) { + if(error) { + callback(error); + } else { + var node = result; + if(node.mode == MODE_SYMBOLIC_LINK) { + followedCount++; + if(followedCount > SYMLOOP_MAX){ + callback(new ELoop('too many symbolic links were encountered')); + } else { + follow_symbolic_link(node.data); + } + } else { + set_file_node(undefined, node); + } + } + } + + function follow_symbolic_link(data) { + data = normalize(data); + parentPath = dirname(data); + name = basename(data); + if(ROOT_DIRECTORY_NAME == name) { + if(_(flags).contains(O_WRITE)) { + callback(new EIsDirectory('the named file is a directory and O_WRITE is set')); + } else { + find_node(objectStore, path, set_file_node); + } + } + find_node(objectStore, parentPath, read_directory_data); + } + function set_file_node(error, result) { if(error) { callback(error); diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 64142c6..9163700 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1063,6 +1063,36 @@ describe('fs.writeFile, fs.readFile', function() { }); }); + it('should follow symbolic links', function () { + var complete = false; + var _result; + var that = this; + + var contents = "This is a file."; + + that.fs.writeFile('/myfile', '', { encoding: 'utf8' }, function(error) { + if(error) throw error; + that.fs.symlink('/myfile', '/myFileLink', function (error) { + if (error) throw error; + that.fs.writeFile('/myFileLink', contents, 'utf8', function (error) { + if (error) throw error; + that.fs.readFile('/myFileLink', 'utf8', function(error, data) { + if(error) throw error; + _result = data; + complete = true; + }); + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_result).toEqual(contents); + }); + }); }); describe('fs.read', function() { From ac689fc8b4ef22d8406d3b6395f3ee2f28715597 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:35:27 -0500 Subject: [PATCH 12/12] test rename does not follow symbolic links --- tests/spec/idbfs.spec.js | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/spec/idbfs.spec.js b/tests/spec/idbfs.spec.js index 9163700..f0d2772 100644 --- a/tests/spec/idbfs.spec.js +++ b/tests/spec/idbfs.spec.js @@ -1487,6 +1487,54 @@ describe('fs.rename', function() { expect(_stats.nlinks).toEqual(1); }); }); + + it('should not follow symbolic links', function () { + var complete = false; + var _error, _stats; + var that = this; + + that.fs.open('/myfile', 'w', function (error, result) { + if (error) throw error; + + var fd = result; + that.fs.close(fd, function (error) { + if (error) throw error; + + that.fs.symlink('/myfile', '/myFileLink', function (error) { + if (error) throw error; + + that.fs.rename('/myFileLink', '/myOtherFileLink', function (error) { + if (error) throw error; + + that.fs.stat('/myfile', function (error, result) { + _error1 = error; + + that.fs.lstat('/myFileLink', function (error, result) { + _error2 = error; + + that.fs.stat('/myOtherFileLink', function (error, result) { + if (error) throw error; + + _stats = result; + complete = true; + }); + }); + }); + }); + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error1).not.toBeDefined(); + expect(_error2).toBeDefined(); + expect(_stats.nlinks).toEqual(1); + }); + }); }); describe('fs.lseek', function() {