diff --git a/src/errors.js b/src/errors.js index fbb5e96..d7ec802 100644 --- a/src/errors.js +++ b/src/errors.js @@ -73,18 +73,25 @@ var errors = {}; '1002:ENOATTR:attribute does not exist' ].forEach(function(e) { e = e.split(':'); - var errno = e[0], + var errno = e[0]|0, err = e[1], message = e[2]; - function FilerError(m) { + function FilerError(msg, path) { this.name = err; this.code = err; this.errno = errno; - this.message = m || message; + this.message = msg || message; + if(path) { + this.path = path; + } } FilerError.prototype = Object.create(Error.prototype); FilerError.prototype.constructor = FilerError; + FilerError.prototype.toString = function() { + var pathInfo = this.path ? (', \'' + this.path + '\'') : ''; + return this.name + ': ' + message + pathInfo; + }; // We expose the error as both Errors.EINVAL and Errors[18] errors[err] = errors[errno] = FilerError; diff --git a/src/filesystem/implementation.js b/src/filesystem/implementation.js index 7b406ce..b8dd308 100644 --- a/src/filesystem/implementation.js +++ b/src/filesystem/implementation.js @@ -111,7 +111,7 @@ function update_node_times(context, path, node, times, callback) { // out: new node representing file/directory function make_node(context, path, mode, callback) { if(mode !== MODE_DIRECTORY && mode !== MODE_FILE) { - return callback(new Errors.EINVAL('mode must be a directory or file')); + return callback(new Errors.EINVAL('mode must be a directory or file', path)); } path = normalize(path); @@ -127,7 +127,7 @@ function make_node(context, path, mode, callback) { if(error) { callback(error); } else if(parentDirectoryNode.mode !== MODE_DIRECTORY) { - callback(new Errors.ENOTDIR('a component of the path prefix is not a directory')); + callback(new Errors.ENOTDIR('a component of the path prefix is not a directory', path)); } else { parentNode = parentDirectoryNode; find_node(context, path, check_if_node_exists); @@ -137,7 +137,7 @@ function make_node(context, path, mode, callback) { // Check if the node to be created already exists function check_if_node_exists(error, result) { if(!error && result) { - callback(new Errors.EEXIST('path name already exists')); + callback(new Errors.EEXIST('path name already exists', path)); } else if(error && !(error instanceof Errors.ENOENT)) { callback(error); } else { @@ -227,7 +227,7 @@ function find_node(context, path, callback) { if(error) { callback(error); } else if(parentDirectoryNode.mode !== MODE_DIRECTORY || !parentDirectoryNode.data) { - callback(new Errors.ENOTDIR('a component of the path prefix is not a directory')); + callback(new Errors.ENOTDIR('a component of the path prefix is not a directory', path)); } else { context.getObject(parentDirectoryNode.data, get_node_from_parent_directory_data); } @@ -240,7 +240,7 @@ function find_node(context, path, callback) { callback(error); } else { if(!_(parentDirectoryData).has(name)) { - callback(new Errors.ENOENT()); + callback(new Errors.ENOENT(null, path)); } else { var nodeId = parentDirectoryData[name].id; context.getObject(nodeId, is_symbolic_link); @@ -255,7 +255,7 @@ function find_node(context, path, callback) { if(node.mode == MODE_SYMBOLIC_LINK) { followedCount++; if(followedCount > SYMLOOP_MAX){ - callback(new Errors.ELOOP()); + callback(new Errors.ELOOP(null, path)); } else { follow_symbolic_link(node.data); } @@ -305,10 +305,10 @@ function set_extended_attribute (context, path_or_fd, name, value, flag, callbac callback(error); } else if (flag === XATTR_CREATE && node.xattrs.hasOwnProperty(name)) { - callback(new Errors.EEXIST('attribute already exists')); + callback(new Errors.EEXIST('attribute already exists', path_or_fd)); } else if (flag === XATTR_REPLACE && !node.xattrs.hasOwnProperty(name)) { - callback(new Errors.ENOATTR()); + callback(new Errors.ENOATTR(null, path_or_fd)); } else { node.xattrs[name] = value; @@ -325,7 +325,7 @@ function set_extended_attribute (context, path_or_fd, name, value, flag, callbac context.getObject(path_or_fd.id, set_xattr); } else { - callback(new Errors.EINVAL('path or file descriptor of wrong type')); + callback(new Errors.EINVAL('path or file descriptor of wrong type', path_or_fd)); } } @@ -402,7 +402,7 @@ function make_directory(context, path, callback) { function check_if_directory_exists(error, result) { if(!error && result) { - callback(new Errors.EEXIST()); + callback(new Errors.EEXIST(null, path)); } else if(error && !(error instanceof Errors.ENOENT)) { callback(error); } else { @@ -492,9 +492,9 @@ function remove_directory(context, path, callback) { if(error) { callback(error); } else if(ROOT_DIRECTORY_NAME == name) { - callback(new Errors.EBUSY()); + callback(new Errors.EBUSY(null, path)); } else if(!_(result).has(name)) { - callback(new Errors.ENOENT()); + callback(new Errors.ENOENT(null, path)); } else { parentDirectoryData = result; directoryNode = parentDirectoryData[name].id; @@ -506,7 +506,7 @@ function remove_directory(context, path, callback) { if(error) { callback(error); } else if(result.mode != MODE_DIRECTORY) { - callback(new Errors.ENOTDIR()); + callback(new Errors.ENOTDIR(null, path)); } else { directoryNode = result; context.getObject(directoryNode.data, check_if_directory_is_empty); @@ -519,7 +519,7 @@ function remove_directory(context, path, callback) { } else { directoryData = result; if(_(directoryData).size() > 0) { - callback(new Errors.ENOTEMPTY()); + callback(new Errors.ENOTEMPTY(null, path)); } else { remove_directory_entry_from_parent_directory_node(); } @@ -574,7 +574,7 @@ function open_file(context, path, flags, callback) { if(ROOT_DIRECTORY_NAME == name) { if(_(flags).contains(O_WRITE)) { - callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set')); + callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path)); } else { find_node(context, path, set_file_node); } @@ -586,7 +586,7 @@ function open_file(context, path, flags, callback) { if(error) { callback(error); } else if(result.mode !== MODE_DIRECTORY) { - callback(new Errors.ENOENT()); + callback(new Errors.ENOENT(null, path)); } else { directoryNode = result; context.getObject(directoryNode.data, check_if_file_exists); @@ -600,18 +600,18 @@ function open_file(context, path, flags, callback) { directoryData = result; if(_(directoryData).has(name)) { if(_(flags).contains(O_EXCLUSIVE)) { - callback(new Errors.ENOENT('O_CREATE and O_EXCLUSIVE are set, and the named file exists')); + callback(new Errors.ENOENT('O_CREATE and O_EXCLUSIVE are set, and the named file exists', path)); } else { directoryEntry = directoryData[name]; if(directoryEntry.type == MODE_DIRECTORY && _(flags).contains(O_WRITE)) { - callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set')); + callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path)); } else { context.getObject(directoryEntry.id, check_if_symbolic_link); } } } else { if(!_(flags).contains(O_CREATE)) { - callback(new Errors.ENOENT('O_CREATE is not set and the named file does not exist')); + callback(new Errors.ENOENT('O_CREATE is not set and the named file does not exist', path)); } else { write_file_node(); } @@ -627,7 +627,7 @@ function open_file(context, path, flags, callback) { if(node.mode == MODE_SYMBOLIC_LINK) { followedCount++; if(followedCount > SYMLOOP_MAX){ - callback(new Errors.ELOOP()); + callback(new Errors.ELOOP(null, path)); } else { follow_symbolic_link(node.data); } @@ -643,7 +643,7 @@ function open_file(context, path, flags, callback) { name = basename(data); if(ROOT_DIRECTORY_NAME == name) { if(_(flags).contains(O_WRITE)) { - callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set')); + callback(new Errors.EISDIR('the named file is a directory and O_WRITE is set', path)); } else { find_node(context, path, set_file_node); } @@ -898,7 +898,7 @@ function lstat_file(context, path, callback) { } else { directoryData = result; if(!_(directoryData).has(name)) { - callback(new Errors.ENOENT('a component of the path does not name an existing file')); + callback(new Errors.ENOENT('a component of the path does not name an existing file', path)); } else { context.getObject(directoryData[name].id, standard_check_result_cb(callback)); } @@ -953,7 +953,7 @@ function link_node(context, oldpath, newpath, callback) { } else { newDirectoryData = result; if(_(newDirectoryData).has(newname)) { - callback(new Errors.EEXIST('newpath resolves to an existing file')); + callback(new Errors.EEXIST('newpath resolves to an existing file', newname)); } else { newDirectoryData[newname] = oldDirectoryData[oldname]; context.putObject(newDirectoryNode.data, newDirectoryData, read_directory_entry); @@ -976,7 +976,7 @@ function link_node(context, oldpath, newpath, callback) { } else { oldDirectoryData = result; if(!_(oldDirectoryData).has(oldname)) { - callback(new Errors.ENOENT('a component of either path prefix does not exist')); + callback(new Errors.ENOENT('a component of either path prefix does not exist', oldname)); } else { find_node(context, newParentPath, read_new_directory_data); } @@ -1046,7 +1046,7 @@ function unlink_node(context, path, callback) { } else { directoryData = result; if(!_(directoryData).has(name)) { - callback(new Errors.ENOENT('a component of the path does not name an existing file')); + callback(new Errors.ENOENT('a component of the path does not name an existing file', name)); } else { context.getObject(directoryData[name].id, update_file_node); } @@ -1104,7 +1104,7 @@ function make_symbolic_link(context, srcpath, dstpath, callback) { var fileNode; if(ROOT_DIRECTORY_NAME == name) { - callback(new Errors.EEXIST()); + callback(new Errors.EEXIST(null, name)); } else { find_node(context, parentPath, read_directory_data); } @@ -1124,7 +1124,7 @@ function make_symbolic_link(context, srcpath, dstpath, callback) { } else { directoryData = result; if(_(directoryData).has(name)) { - callback(new Errors.EEXIST()); + callback(new Errors.EEXIST(null, name)); } else { write_file_node(); } @@ -1189,7 +1189,7 @@ function read_link(context, path, callback) { } else { directoryData = result; if(!_(directoryData).has(name)) { - callback(new Errors.ENOENT('a component of the path does not name an existing file')); + callback(new Errors.ENOENT('a component of the path does not name an existing file', name)); } else { context.getObject(directoryData[name].id, check_if_symbolic); } @@ -1201,7 +1201,7 @@ function read_link(context, path, callback) { callback(error); } else { if(result.mode != MODE_SYMBOLIC_LINK) { - callback(new Errors.EINVAL("path not a symbolic link")); + callback(new Errors.EINVAL('path not a symbolic link', path)); } else { callback(null, result.data); } @@ -1218,7 +1218,7 @@ function truncate_file(context, path, length, callback) { if (error) { callback(error); } else if(node.mode == MODE_DIRECTORY ) { - callback(new Errors.EISDIR()); + callback(new Errors.EISDIR(null, path)); } else{ fileNode = node; context.getBuffer(fileNode.data, truncate_file_data); @@ -1337,10 +1337,10 @@ function utimes_file(context, path, atime, mtime, callback) { } if (typeof atime != 'number' || typeof mtime != 'number') { - callback(new Errors.EINVAL('atime and mtime must be number')); + callback(new Errors.EINVAL('atime and mtime must be number', path)); } else if (atime < 0 || mtime < 0) { - callback(new Errors.EINVAL('atime and mtime must be positive integers')); + callback(new Errors.EINVAL('atime and mtime must be positive integers', path)); } else { find_node(context, path, update_times); @@ -1372,14 +1372,14 @@ function setxattr_file(context, path, name, value, flag, callback) { path = normalize(path); if (typeof name != 'string') { - callback(new Errors.EINVAL('attribute name must be a string')); + callback(new Errors.EINVAL('attribute name must be a string', path)); } else if (!name) { - callback(new Errors.EINVAL('attribute name cannot be an empty string')); + callback(new Errors.EINVAL('attribute name cannot be an empty string', path)); } else if (flag !== null && flag !== XATTR_CREATE && flag !== XATTR_REPLACE) { - callback(new Errors.EINVAL('invalid flag, must be null, XATTR_CREATE or XATTR_REPLACE')); + callback(new Errors.EINVAL('invalid flag, must be null, XATTR_CREATE or XATTR_REPLACE', path)); } else { set_extended_attribute(context, path, name, value, flag, callback); @@ -1412,7 +1412,7 @@ function getxattr_file (context, path, name, callback) { callback (error); } else if (!node.xattrs.hasOwnProperty(name)) { - callback(new Errors.ENOATTR()); + callback(new Errors.ENOATTR(null, path)); } else { callback(null, node.xattrs[name]); @@ -1420,10 +1420,10 @@ function getxattr_file (context, path, name, callback) { } if (typeof name != 'string') { - callback(new Errors.EINVAL('attribute name must be a string')); + callback(new Errors.EINVAL('attribute name must be a string', path)); } else if (!name) { - callback(new Errors.EINVAL('attribute name cannot be an empty string')); + callback(new Errors.EINVAL('attribute name cannot be an empty string', path)); } else { find_node(context, path, get_xattr); @@ -1475,7 +1475,7 @@ function removexattr_file (context, path, name, callback) { callback(error); } else if (!xattr.hasOwnProperty(name)) { - callback(new Errors.ENOATTR()); + callback(new Errors.ENOATTR(null, path)); } else { delete node.xattrs[name]; @@ -1484,10 +1484,10 @@ function removexattr_file (context, path, name, callback) { } if (typeof name != 'string') { - callback(new Errors.EINVAL('attribute name must be a string')); + callback(new Errors.EINVAL('attribute name must be a string', path)); } else if (!name) { - callback(new Errors.EINVAL('attribute name cannot be an empty string')); + callback(new Errors.EINVAL('attribute name cannot be an empty string', path)); } else { find_node(context, path, remove_xattr); @@ -1549,9 +1549,9 @@ function validate_file_options(options, enc, fileMode){ function pathCheck(path, callback) { var err; if(isNullPath(path)) { - err = new Error('Path must be a string without null bytes.'); + err = new Errors.EINVAL('Path must be a string without null bytes.', path); } else if(!isAbsolutePath(path)) { - err = new Error('Path must be absolute.'); + err = new Errors.EINAVL('Path must be absolute.', path); } if(err) { @@ -1586,7 +1586,7 @@ function open(fs, context, path, flags, mode, callback) { flags = validate_flags(flags); if(!flags) { - callback(new Errors.EINVAL('flags is not valid')); + callback(new Errors.EINVAL('flags is not valid'), path); } open_file(context, path, flags, check_result); @@ -1691,7 +1691,7 @@ function readFile(fs, context, path, options, callback) { var flags = validate_flags(options.flag || 'r'); if(!flags) { - return callback(new Errors.EINVAL('flags is not valid')); + return callback(new Errors.EINVAL('flags is not valid', path)); } open_file(context, path, flags, function(err, fileNode) { @@ -1715,7 +1715,7 @@ function readFile(fs, context, path, options, callback) { if(stats.isDirectory()) { cleanup(); - return callback(new Errors.EISDIR('illegal operation on directory')); + return callback(new Errors.EISDIR('illegal operation on directory', path)); } var size = stats.size; @@ -1766,7 +1766,7 @@ function writeFile(fs, context, path, data, options, callback) { var flags = validate_flags(options.flag || 'w'); if(!flags) { - callback(new Errors.EINVAL('flags is not valid')); + return callback(new Errors.EINVAL('flags is not valid', path)); } data = data || ''; @@ -1803,7 +1803,7 @@ function appendFile(fs, context, path, data, options, callback) { var flags = validate_flags(options.flag || 'a'); if(!flags) { - callback(new Errors.EINVAL('flags is not valid')); + return callback(new Errors.EINVAL('flags is not valid', path)); } data = data || ''; diff --git a/src/shell/shell.js b/src/shell/shell.js index 7962993..fd9332e 100644 --- a/src/shell/shell.js +++ b/src/shell/shell.js @@ -40,14 +40,14 @@ function Shell(fs, options) { // Make sure the path actually exists, and is a dir fs.stat(path, function(err, stats) { if(err) { - callback(new Errors.ENOTDIR()); + callback(new Errors.ENOTDIR(null, path)); return; } if(stats.type === 'DIRECTORY') { cwd = path; callback(); } else { - callback(new Errors.ENOTDIR()); + callback(new Errors.ENOTDIR(null, path)); } }); }; @@ -158,7 +158,7 @@ Shell.prototype.cat = function(files, callback) { callback = callback || function(){}; if(!files) { - callback(new Errors.EINVAL("Missing files argument")); + callback(new Errors.EINVAL('Missing files argument')); return; } @@ -213,7 +213,7 @@ Shell.prototype.ls = function(dir, options, callback) { callback = callback || function(){}; if(!dir) { - callback(new Errors.EINVAL("Missing dir argument")); + callback(new Errors.EINVAL('Missing dir argument')); return; } @@ -286,7 +286,7 @@ Shell.prototype.rm = function(path, options, callback) { callback = callback || function(){}; if(!path) { - callback(new Errors.EINVAL("Missing path argument")); + callback(new Errors.EINVAL('Missing path argument')); return; } @@ -319,7 +319,7 @@ Shell.prototype.rm = function(path, options, callback) { // If not, see if we're allowed to delete recursively if(!options.recursive) { - callback(new Errors.ENOTEMPTY()); + callback(new Errors.ENOTEMPTY(null, pathname)); return; } @@ -373,7 +373,7 @@ Shell.prototype.mkdirp = function(path, callback) { callback = callback || function(){}; if(!path) { - callback(new Errors.EINVAL("Missing path argument")); + callback(new Errors.EINVAL('Missing path argument')); return; } else if (path === '/') { @@ -388,7 +388,7 @@ Shell.prototype.mkdirp = function(path, callback) { return; } else if (stat.isFile()) { - callback(new Errors.ENOTDIR()); + callback(new Errors.ENOTDIR(null, path)); return; } } @@ -445,7 +445,7 @@ Shell.prototype.wget = function(url, options, callback) { callback = callback || function(){}; if(!url) { - callback(new Errors.EINVAL('missing url argument')); + callback(new Errors.EINVAL('Missing url argument')); return; } @@ -487,7 +487,7 @@ Shell.prototype.unzip = function(zipfile, options, callback) { callback = callback || function(){}; if(!zipfile) { - callback(new Errors.EINVAL('missing zipfile argument')); + callback(new Errors.EINVAL('Missing zipfile argument')); return; } @@ -533,11 +533,11 @@ Shell.prototype.zip = function(zipfile, paths, options, callback) { callback = callback || function(){}; if(!zipfile) { - callback(new Errors.EINVAL('missing zipfile argument')); + callback(new Errors.EINVAL('Missing zipfile argument')); return; } if(!paths) { - callback(new Errors.EINVAL('missing paths argument')); + callback(new Errors.EINVAL('Missing paths argument')); return; } if(typeof paths === 'string') { @@ -591,9 +591,9 @@ Shell.prototype.zip = function(zipfile, paths, options, callback) { var zip = new JSZip(); // Make sure the zipfile doesn't already exist. - fs.stat(zipfile, function(err, stats) { - if(stats) { - return callback(new Errors.EEXIST('zipfile already exists')); + fs.exists(zipfile, function(exists) { + if(exists) { + return callback(new Errors.EEXIST('zipfile already exists', zipfile)); } async.eachSeries(paths, add, function(err) { diff --git a/tests/spec/errors.spec.js b/tests/spec/errors.spec.js index a35a088..cc9ec9c 100644 --- a/tests/spec/errors.spec.js +++ b/tests/spec/errors.spec.js @@ -133,4 +133,49 @@ describe("Filer.Errors", function() { expect(Filer.Errors[1001]).to.equal(Filer.Errors.EFILESYSTEMERROR); expect(Filer.Errors[1002]).to.equal(Filer.Errors.ENOATTR); }); + + it('should include all expected properties by default', function() { + var err = new Filer.Errors.ENOENT(); + expect(err.name).to.equal('ENOENT'); + expect(err.code).to.equal('ENOENT'); + expect(err.errno).to.equal(34); + expect(err.message).to.equal('no such file or directory'); + }); + + it('should include extra properties when provided', function() { + var err = new Filer.Errors.ENOENT('This is the message', '/this/is/the/path'); + expect(err.name).to.equal('ENOENT'); + expect(err.code).to.equal('ENOENT'); + expect(err.errno).to.equal(34); + expect(err.message).to.equal('This is the message'); + expect(err.path).to.equal('/this/is/the/path'); + }); + + it('should include default message and path info when provided', function() { + var err = new Filer.Errors.ENOENT(null, '/this/is/the/path'); + expect(err.message).to.equal('no such file or directory'); + expect(err.path).to.equal('/this/is/the/path'); + }); + + it('should include just the message when no path provided', function() { + var err = new Filer.Errors.ENOENT(); + expect(err.message).to.equal('no such file or directory'); + expect(err.path).not.to.exist; + }); + + it('should not include path in toString() when not provided', function() { + var err = new Filer.Errors.ENOENT('This is the message'); + expect(err.toString()).to.equal("ENOENT: no such file or directory"); + }); + + it('should include path in toString() when provided', function() { + var err = new Filer.Errors.ENOENT('This is the message', '/this/is/the/path'); + expect(err.toString()).to.equal("ENOENT: no such file or directory, '/this/is/the/path'"); + }); + + it('should include message and path info when provided', function() { + var err = new Filer.Errors.ENOENT('This is the message', '/this/is/the/path'); + expect(err.message).to.equal('This is the message'); + expect(err.path).to.equal('/this/is/the/path'); + }); });