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.
This commit is contained in:
Abir Viqar 2013-11-23 13:34:48 -05:00
parent 85a804cc0c
commit 5ceff20b12
4 changed files with 391 additions and 5 deletions

View File

@ -18,6 +18,8 @@ define(function(require) {
MODE_DIRECTORY: 'DIRECTORY', MODE_DIRECTORY: 'DIRECTORY',
MODE_SYMBOLIC_LINK: 'SYMLINK', MODE_SYMBOLIC_LINK: 'SYMLINK',
SYMLOOP_MAX: 10,
BINARY_MIME_TYPE: 'application/octet-stream', BINARY_MIME_TYPE: 'application/octet-stream',
JSON_MIME_TYPE: 'application/json', JSON_MIME_TYPE: 'application/json',

View File

@ -91,6 +91,13 @@ define(function(require) {
EIO.prototype.name = "EIO"; EIO.prototype.name = "EIO";
EIO.prototype.constructor = 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){ function EFileSystemError(message){
this.message = message || ''; this.message = message || '';
} }
@ -109,7 +116,8 @@ define(function(require) {
ENotImplemented: ENotImplemented, ENotImplemented: ENotImplemented,
ENotMounted: ENotMounted, ENotMounted: ENotMounted,
EInvalid: EInvalid, EInvalid: EInvalid,
EIO: EIO EIO: EIO,
ELoop: ELoop
}; };
}); });

View File

@ -28,6 +28,7 @@ define(function(require) {
var ENotMounted = require('src/error').ENotMounted; var ENotMounted = require('src/error').ENotMounted;
var EInvalid = require('src/error').EInvalid; var EInvalid = require('src/error').EInvalid;
var EIO = require('src/error').EIO; var EIO = require('src/error').EIO;
var ELoop = require('src/error').ELoop;
var EFileSystemError = require('src/error').EFileSystemError; var EFileSystemError = require('src/error').EFileSystemError;
var FS_FORMAT = require('src/constants').FS_FORMAT; var FS_FORMAT = require('src/constants').FS_FORMAT;
@ -40,6 +41,7 @@ define(function(require) {
var IDB_RO = require('src/constants').IDB_RO; var IDB_RO = require('src/constants').IDB_RO;
var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME; var FILE_STORE_NAME = require('src/constants').FILE_STORE_NAME;
var METADATA_STORE_NAME = require('src/constants').METADATA_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_READY = require('src/constants').FS_READY;
var FS_PENDING = require('src/constants').FS_PENDING; var FS_PENDING = require('src/constants').FS_PENDING;
var FS_ERROR = require('src/constants').FS_ERROR; var FS_ERROR = require('src/constants').FS_ERROR;
@ -120,6 +122,7 @@ define(function(require) {
} }
var name = basename(path); var name = basename(path);
var parentPath = dirname(path); var parentPath = dirname(path);
var followedCount = 0;
function check_root_directory_node(error, rootDirectoryNode) { function check_root_directory_node(error, rootDirectoryNode) {
if(error) { if(error) {
@ -139,13 +142,13 @@ define(function(require) {
} else if(parentDirectoryNode.mode !== MODE_DIRECTORY || !parentDirectoryNode.data) { } else if(parentDirectoryNode.mode !== MODE_DIRECTORY || !parentDirectoryNode.data) {
callback(new ENotDirectory('a component of the path prefix is not a directory')); callback(new ENotDirectory('a component of the path prefix is not a directory'));
} else { } 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 // in: parent directory data
// out: searched node id // out: searched node
function get_node_id_from_parent_directory_data(error, parentDirectoryData) { function get_node_from_parent_directory_data(error, parentDirectoryData) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
@ -153,11 +156,39 @@ define(function(require) {
callback(new ENoEntry('path does not exist')); callback(new ENoEntry('path does not exist'));
} else { } else {
var nodeId = parentDirectoryData[name].id; 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) { if(ROOT_DIRECTORY_NAME == name) {
read_object(objectStore, ROOT_NODE_ID, check_root_directory_node); read_object(objectStore, ROOT_NODE_ID, check_root_directory_node);
} else { } else {

View File

@ -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();
});
});
});