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..4fbc28c 100644 --- a/README.md +++ b/README.md @@ -160,3 +160,17 @@ 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.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. + +#### fs.lstat(path, callback) + +Asynchronous lstat(2). Callback gets `(error, stats)`, See `fs.stat`. 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 21ef81a..3c94ede 100644 --- a/src/fs.js +++ b/src/fs.js @@ -28,17 +28,20 @@ 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; 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; 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; @@ -119,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) { @@ -138,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 { @@ -152,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 { @@ -300,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); @@ -321,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) { @@ -361,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) { @@ -375,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')); @@ -407,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 { @@ -420,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); @@ -577,6 +650,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); @@ -660,8 +778,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; @@ -752,6 +870,106 @@ 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); + 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; @@ -1166,17 +1384,51 @@ 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(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) { }; - 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) { @@ -1458,6 +1710,42 @@ 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); + }; + 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); + }; + 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 2cdc912..f0d2772 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() { @@ -201,6 +240,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(); @@ -343,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() { @@ -424,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; @@ -859,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() { @@ -1054,7 +1288,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() { @@ -1115,6 +1387,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() { @@ -1172,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() { @@ -1350,3 +1713,506 @@ 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(); + 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(); + }); + }); + + it('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('/'); + }); + }); +}); + +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(); + }); + }); +});