From 5ceff20b1265e65e6f74ce34450b7be29a4e19f1 Mon Sep 17 00:00:00 2001 From: Abir Viqar Date: Sat, 23 Nov 2013 13:34:48 -0500 Subject: [PATCH] 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(); + }); + }); +});