diff --git a/AUTHORS b/AUTHORS index e218b72..eed1c65 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,4 @@ Alan K (blog.modeswitch.org) David Humphrey (@humphd) Abir Viqar +Barry Tulchinsky (@btulchinsky) diff --git a/README.md b/README.md index 86c81e4..994d41e 100644 --- a/README.md +++ b/README.md @@ -336,3 +336,11 @@ Asynchronous truncate(2). Callback gets no additional arguments. #### fs.ftruncate(fd, length, callback) Asynchronous ftruncate(2). Callback gets no additional arguments. + +#### fs.utimes(path, atime, mtime, callback) + +Asynchronous utimes(3). Callback gets no additional arguments. + +#### fs.futimes(fd, atime, mtime, callback) + +Asynchronous futimes(3). Callback gets no additional arguments. diff --git a/src/fs.js b/src/fs.js index 087181b..d5ad38d 100644 --- a/src/fs.js +++ b/src/fs.js @@ -1069,6 +1069,55 @@ define(function(require) { } } + function utimes_file(context, path, atime, mtime, callback) { + path = normalize(path); + + function update_times (error, node) { + if (error) { + callback(error); + } + else { + node.atime = atime; + node.mtime = mtime; + context.put(node.id, node, callback); + } + } + + if (typeof atime != 'number' || typeof mtime != 'number') { + callback(new EInvalid('atime and mtime must be number')); + } + else if (atime < 0 || mtime < 0) { + callback(new EInvalid('atime and mtime must be positive integers')); + } + else { + find_node(context, path, update_times); + } + } + + function futimes_file(context, ofd, atime, mtime, callback) { + + function update_times (error, node) { + if (error) { + callback(error); + } + else { + node.atime = atime; + node.mtime = mtime; + context.put(node.id, node, callback); + } + } + + if (typeof atime != 'number' || typeof mtime != 'number') { + callback(new EInvalid('atime and mtime must be a number')); + } + else if (atime < 0 || mtime < 0) { + callback(new EInvalid('atime and mtime must be positive integers')); + } + else { + context.get(ofd.id, update_times); + } + } + function validate_flags(flags) { if(!_(O_FLAGS).has(flags)) { return null; @@ -1555,9 +1604,47 @@ define(function(require) { read_directory(context, path, check_result); } - function _utimes(path, atime, mtime, callback) { - // TODO - // if(!nullCheck(path, callback)) return; + function _utimes(context, path, atime, mtime, callback) { + if(!nullCheck(path, callback)) return; + + var currentTime = Date.now(); + atime = (atime) ? atime : currentTime; + mtime = (mtime) ? mtime : currentTime; + + function check_result(error) { + if (error) { + callback(error); + } + else { + callback(null); + } + } + utimes_file(context, path, atime, mtime, check_result) + } + + function _futimes(fs, context, fd, atime, mtime, callback) { + function check_result(error) { + if (error) { + callback(error); + } + else { + callback(null); + } + } + + var currentTime = Date.now() + atime = (atime) ? atime : currentTime; + mtime = (mtime) ? mtime : currentTime; + + var ofd = fs.openFiles[fd]; + + if(!ofd) { + callback(new EBadFileDescriptor('invalid file descriptor')); + } else if(!_(ofd.flags).contains(O_WRITE)) { + callback(new EBadFileDescriptor('descriptor does not permit writing')); + } else { + futimes_file(context, ofd, atime, mtime, check_result); + } } function _rename(context, oldpath, newpath, callback) { @@ -1895,6 +1982,26 @@ define(function(require) { ); if(error) callback(error); }; + FileSystem.prototype.utimes = function(path, atime, mtime, callback) { + callback = maybeCallback(callback); + var fs = this; + var error = fs.queueOrRun( + function () { + var context = fs.provider.getReadWriteContext(); + _utimes(context, path, atime, mtime, callback); + } + ); + }; + FileSystem.prototype.futimes = function(fd, atime, mtime, callback) { + callback = maybeCallback(callback); + var fs = this; + var error = fs.queueOrRun( + function () { + var context = fs.provider.getReadWriteContext(); + _futimes(fs, context, fd, atime, mtime, callback); + } + ); + }; return FileSystem; diff --git a/tests/spec/fs.utimes.spec.js b/tests/spec/fs.utimes.spec.js new file mode 100644 index 0000000..6e085e0 --- /dev/null +++ b/tests/spec/fs.utimes.spec.js @@ -0,0 +1,317 @@ +define(["IDBFS"], function(IDBFS) { + + describe('fs.utimes', function() { + beforeEach(function() { + this.db_name = mk_db_name(); + this.fs = new IDBFS.FileSystem({ + name: this.db_name, + flags: 'FORMAT' + }); + }); + + afterEach(function() { + indexedDB.deleteDatabase(this.db_name); + delete this.fs; + }); + + it('should be a function', function() { + expect(typeof this.fs.utimes).toEqual('function'); + }); + + it('should error when atime is negative', function () { + var complete = false; + var _error; + var that = this; + + that.fs.writeFile('/testfile', '', function(error) { + if (error) throw error; + + that.fs.utimes('/testfile', -1, Date.now(), function (error) { + _error = error; + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('EInvalid'); + }); + }); + + it('should error when mtime is negative', function () { + var complete = false; + var _error; + var that = this; + + that.fs.writeFile('/testfile', '', function(error) { + if (error) throw error; + + that.fs.utimes('/testfile', Date.now(), -1, function (error) { + _error = error; + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('EInvalid'); + }); + }); + + it('should error when atime is as invalid number', function () { + var complete = false; + var _error; + var that = this; + + that.fs.writeFile('/testfile', '', function (error) { + if (error) throw error; + + that.fs.utimes('/testfile', 'invalid datetime', Date.now(), function (error) { + _error = error; + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('EInvalid'); + }); + }); + + it ('should error when path does not exist', function () { + var complete = false; + var _error; + var that = this; + + var atime = Date.parse('1 Oct 2000 15:33:22'); + var mtime = Date.parse('30 Sep 2000 06:43:54'); + + that.fs.utimes('/pathdoesnotexist', atime, mtime, function (error) { + _error = error; + complete = true; + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('ENoEntry'); + }); + }); + + it('should error when mtime is an invalid number', function () { + var complete = false; + var _error; + var that = this; + + that.fs.writeFile('/testfile', '', function (error) { + if (error) throw error; + + that.fs.utimes('/testfile', Date.now(), 'invalid datetime', function (error) { + _error = error; + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('EInvalid'); + }); + }); + + it ('should error when file descriptor is invalid', function () { + var complete = false; + var _error; + var that = this; + + var atime = Date.parse('1 Oct 2000 15:33:22'); + var mtime = Date.parse('30 Sep 2000 06:43:54'); + + that.fs.futimes(1, atime, mtime, function (error) { + _error = error; + complete = true; + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toBeDefined(); + expect(_error.name).toEqual('EBadFileDescriptor'); + }); + }); + + it('should change atime and mtime of a file path', function () { + var complete = false; + var _error; + var that = this; + + var _stat; + + var atime = Date.parse('1 Oct 2000 15:33:22'); + var mtime = Date.parse('30 Sep 2000 06:43:54'); + + that.fs.writeFile('/testfile', '', function (error) { + if (error) throw error; + + that.fs.utimes('/testfile', atime, mtime, function (error) { + _error = error; + + that.fs.stat('/testfile', function (error, stat) { + if (error) throw error; + + _stat = stat; + complete = true; + }); + }); + }); + + waitsFor(function() { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function() { + expect(_error).toEqual(null); + expect(_stat.atime).toEqual(atime); + expect(_stat.mtime).toEqual(mtime); + }); + }); + + it ('should change atime and mtime for a valid file descriptor', function (error) { + var complete = false; + var _error; + var that = this; + + var ofd; + var _stat; + + var atime = Date.parse('1 Oct 2000 15:33:22'); + var mtime = Date.parse('30 Sep 2000 06:43:54'); + + that.fs.open('/testfile', 'w', function (error, result) { + if (error) throw error; + + ofd = result; + + that.fs.futimes(ofd, atime, mtime, function (error) { + _error = error; + + that.fs.fstat(ofd, function (error, stat) { + if (error) throw error; + + _stat = stat; + complete = true; + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toEqual(null); + expect(_stat.atime).toEqual(atime); + expect(_stat.mtime).toEqual(mtime); + }); + }); + + it ('should update atime and mtime of directory path', function (error) { + var complete = false + var _error; + + //Note: required as the filesystem somehow gets removed from the Jasmine object + var fs = this.fs; + + var _stat; + + var atime = Date.parse('1 Oct 2000 15:33:22'); + var mtime = Date.parse('30 Sep 2000 06:43:54'); + + fs.mkdir('/testdir', function (error) { + if (error) throw error; + + fs.utimes('/testdir', atime, mtime, function (error) { + _error = error; + + fs.stat('/testdir', function (error, stat) { + if (error) throw error; + + _stat = stat; + complete = true; + }); + }); + }); + + waitsFor(function () { + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toEqual(null); + expect(_stat.atime).toEqual(atime); + expect(_stat.mtime).toEqual(mtime); + delete fs; + }); + }); + + it ('should update atime and mtime using current time if arguments are null', function () { + var complete = false; + var _error; + var that = this; + + var atimeEst; + var mtimeEst; + var now; + + that.fs.writeFile('/myfile', '', function (error) { + if (error) throw error; + + that.fs.utimes('/myfile', null, null, function (error) { + _error = error; + + now = Date.now(); + + that.fs.stat('/myfile', function (error, stat) { + if (error) throw error; + + atimeEst = now - stat.atime; + mtimeEst = now - stat.mtime; + complete = true; + }); + }); + }); + + waitsFor(function (){ + return complete; + }, 'test to complete', DEFAULT_TIMEOUT); + + runs(function () { + expect(_error).toEqual(null); + // Note: testing estimation as time may differ by a couple of milliseconds + // This number should be increased if tests are on slow systems + expect(atimeEst).toBeLessThan(10); + expect(mtimeEst).toBeLessThan(10); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/test-manifest.js b/tests/test-manifest.js index 72c44a9..124f36e 100644 --- a/tests/test-manifest.js +++ b/tests/test-manifest.js @@ -27,6 +27,7 @@ define([ "spec/fs.symlink.spec", "spec/fs.readlink.spec", "spec/fs.truncate.spec", + "spec/fs.utimes.spec", "spec/path-resolution.spec", // IDBFS.FileSystem.providers.*