From 1d5ff15a46102bb7f0611a42a028903f615acab0 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 29 Oct 2010 12:38:13 +0200 Subject: [PATCH] fs.utimes() and fs.futimes() support. --- doc/api/fs.markdown | 12 ++++ lib/fs.js | 39 +++++++++++ src/node_file.cc | 81 ++++++++++++++++++++++ test/simple/test-fs-utimes.js | 125 ++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 test/simple/test-fs-utimes.js diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown index 437ba732f8..9759db6b2c 100644 --- a/doc/api/fs.markdown +++ b/doc/api/fs.markdown @@ -216,6 +216,18 @@ or 'a+'. `mode` defaults to 0666. The callback gets two arguments `(err, fd)`. Synchronous open(2). +### fs.utimes(path, atime, mtime, callback) +### fs.utimesSync(path, atime, mtime) + +Change file timestamps. + +### fs.futimes(path, atime, mtime, callback) +### fs.futimesSync(path, atime, mtime) + +Change file timestamps with the difference that if filename refers to a +symbolic link, then the link is not dereferenced. + + ### fs.write(fd, buffer, offset, length, position, [callback]) Write `buffer` to the file specified by `fd`. diff --git a/lib/fs.js b/lib/fs.js index 3eb31a99fc..244923fb94 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -417,6 +417,45 @@ fs.chownSync = function(path, uid, gid) { return binding.chown(path, uid, gid); }; +// converts Date or number to a fractional UNIX timestamp +function toUnixTimestamp(time) { + if (typeof time == 'number') { + return time; + } + if (time instanceof Date) { + // convert to 123.456 UNIX timestamp + return time.getTime() / 1000; + } + throw new Error("Cannot parse time: " + time); +} + +// exported for unit tests, not for public consumption +fs._toUnixTimestamp = toUnixTimestamp; + +fs.utimes = function(path, atime, mtime, callback) { + atime = toUnixTimestamp(atime); + mtime = toUnixTimestamp(mtime); + binding.utimes(path, atime, mtime, callback || noop); +}; + +fs.utimesSync = function(path, atime, mtime) { + atime = toUnixTimestamp(atime); + mtime = toUnixTimestamp(mtime); + binding.utimes(path, atime, mtime); +}; + +fs.futimes = function(fd, atime, mtime, callback) { + atime = toUnixTimestamp(atime); + mtime = toUnixTimestamp(mtime); + binding.futimes(fd, atime, mtime, callback || noop); +}; + +fs.futimesSync = function(fd, atime, mtime) { + atime = toUnixTimestamp(atime); + mtime = toUnixTimestamp(mtime); + binding.futimes(fd, atime, mtime); +}; + function writeAll(fd, buffer, offset, length, callback) { // write(fd, buffer, offset, length, position, callback) fs.write(fd, buffer, offset, length, offset, function(writeErr, written) { diff --git a/src/node_file.cc b/src/node_file.cc index 40b8362ca5..9f0b8437c6 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -110,6 +111,11 @@ static int After(eio_req *req) { argc = 1; break; + case EIO_UTIME: + case EIO_FUTIME: + argc = 0; + break; + case EIO_OPEN: SetCloseOnExec(req->result); /* pass thru */ @@ -824,6 +830,78 @@ static Handle Chown(const Arguments& args) { } #endif // __POSIX__ + +// Utimes() and Futimes() helper function, converts 123.456 timestamps to timevals +static inline void ToTimevals(eio_tstamp atime, + eio_tstamp mtime, + timeval times[2]) { + times[0].tv_sec = atime; + times[0].tv_usec = 10e5 * (atime - (long) atime); + times[1].tv_sec = mtime; + times[1].tv_usec = 10e5 * (mtime - (long) mtime); +} + + +static Handle UTimes(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 3 + || !args[0]->IsString() + || !args[1]->IsNumber() + || !args[2]->IsNumber()) + { + return THROW_BAD_ARGS; + } + + const String::Utf8Value path(args[0]->ToString()); + const eio_tstamp atime = static_cast(args[1]->NumberValue()); + const eio_tstamp mtime = static_cast(args[2]->NumberValue()); + + if (args[3]->IsFunction()) { + ASYNC_CALL(utime, args[3], *path, atime, mtime); + } else { + timeval times[2]; + + ToTimevals(atime, mtime, times); + if (utimes(*path, times) == -1) { + return ThrowException(ErrnoException(errno, "utimes", "", *path)); + } + } + + return Undefined(); +} + + +static Handle FUTimes(const Arguments& args) { + HandleScope scope; + + if (args.Length() < 3 + || !args[0]->IsInt32() + || !args[1]->IsNumber() + || !args[2]->IsNumber()) + { + return THROW_BAD_ARGS; + } + + const int fd = args[0]->Int32Value(); + const eio_tstamp atime = static_cast(args[1]->NumberValue()); + const eio_tstamp mtime = static_cast(args[2]->NumberValue()); + + if (args[3]->IsFunction()) { + ASYNC_CALL(futime, args[3], fd, atime, mtime); + } else { + timeval times[2]; + + ToTimevals(atime, mtime, times); + if (futimes(fd, times) == -1) { + return ThrowException(ErrnoException(errno, "futimes", "", 0)); + } + } + + return Undefined(); +} + + void File::Initialize(Handle target) { HandleScope scope; @@ -856,6 +934,9 @@ void File::Initialize(Handle target) { NODE_SET_METHOD(target, "chown", Chown); #endif // __POSIX__ + NODE_SET_METHOD(target, "utimes", UTimes); + NODE_SET_METHOD(target, "futimes", FUTimes); + errno_symbol = NODE_PSYMBOL("errno"); encoding_symbol = NODE_PSYMBOL("node:encoding"); buf_symbol = NODE_PSYMBOL("__buf"); diff --git a/test/simple/test-fs-utimes.js b/test/simple/test-fs-utimes.js new file mode 100644 index 0000000000..3b154d8277 --- /dev/null +++ b/test/simple/test-fs-utimes.js @@ -0,0 +1,125 @@ +var common = require('../common'); +var assert = require('assert'); +var util = require('util'); +var fs = require('fs'); + +var tests_ok = 0; +var tests_run = 0; + +function stat_resource(resource) { + if (typeof resource == 'string') { + return fs.statSync(resource); + } else { + // ensure mtime has been written to disk + fs.fsyncSync(resource); + return fs.fstatSync(resource); + } +} + +function check_mtime(resource, mtime) { + var mtime = fs._toUnixTimestamp(mtime); + var stats = stat_resource(resource); + var real_mtime = fs._toUnixTimestamp(stats.mtime); + // check up to single-second precision + // sub-second precision is OS and fs dependant + return Math.floor(mtime) == Math.floor(real_mtime); +} + +function expect_errno(syscall, resource, err, errno) { + if (err && err.code == errno) { + tests_ok++; + } else { + console.log('FAILED:', arguments.callee.name, util.inspect(arguments)); + } +} + +function expect_ok(syscall, resource, err, atime, mtime) { + if (!err && check_mtime(resource, mtime)) { + tests_ok++; + } else { + console.log('FAILED:', arguments.callee.name, util.inspect(arguments)); + } +} + +// the tests assume that __filename belongs to the user running the tests +// this should be a fairly safe assumption; testing against a temp file +// would be even better though (node doesn't have such functionality yet) +function runTests(atime, mtime, callback) { + + var fd, err; + // + // test synchronized code paths, these functions throw on failure + // + function syncTests() { + fs.utimesSync(__filename, atime, mtime); + expect_ok('utimesSync', __filename, undefined, atime, mtime); + tests_run++; + + fs.futimesSync(fd, atime, mtime); + expect_ok('futimesSync', fd, undefined, atime, mtime); + tests_run++; + + err = undefined; + try { + fs.utimesSync('foobarbaz', atime, mtime); + } catch (ex) { + err = ex; + } + expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT'); + tests_run++; + + err = undefined; + try { + fs.futimesSync(-1, atime, mtime); + } catch (ex) { + err = ex; + } + expect_errno('futimesSync', -1, err, 'EBADF'); + tests_run++; + } + + // + // test async code paths + // + fs.utimes(__filename, atime, mtime, function(err) { + expect_ok('utimes', __filename, err, atime, mtime); + + fs.utimes('foobarbaz', atime, mtime, function(err) { + expect_errno('utimes', 'foobarbaz', err, 'ENOENT'); + + // don't close this fd + fd = fs.openSync(__filename, 'r'); + + fs.futimes(fd, atime, mtime, function(err) { + expect_ok('futimes', fd, err, atime, mtime); + + fs.futimes(-1, atime, mtime, function(err) { + expect_errno('futimes', -1, err, 'EBADF'); + syncTests(); + callback(); + }); + tests_run++; + }); + tests_run++; + }); + tests_run++; + }); + tests_run++; +} + +var stats = fs.statSync(__filename); + +runTests(new Date('1982-09-10 13:37'), new Date('1982-09-10 13:37'), function() { + runTests(new Date(), new Date(), function() { + runTests(1234.5678, 1234.5678, function() { + runTests(stats.mtime, stats.mtime, function() { + // done + }); + }); + }); +}); + +process.on('exit', function() { + console.log('Tests run / ok:', tests_run, '/', tests_ok); + assert.equal(tests_ok, tests_run); +});