Skip to content

Commit 1d5ff15

Browse files
bnoordhuisry
authored andcommitted
fs.utimes() and fs.futimes() support.
1 parent 8838e14 commit 1d5ff15

4 files changed

Lines changed: 257 additions & 0 deletions

File tree

doc/api/fs.markdown

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ or 'a+'. `mode` defaults to 0666. The callback gets two arguments `(err, fd)`.
216216

217217
Synchronous open(2).
218218

219+
### fs.utimes(path, atime, mtime, callback)
220+
### fs.utimesSync(path, atime, mtime)
221+
222+
Change file timestamps.
223+
224+
### fs.futimes(path, atime, mtime, callback)
225+
### fs.futimesSync(path, atime, mtime)
226+
227+
Change file timestamps with the difference that if filename refers to a
228+
symbolic link, then the link is not dereferenced.
229+
230+
219231
### fs.write(fd, buffer, offset, length, position, [callback])
220232

221233
Write `buffer` to the file specified by `fd`.

lib/fs.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,45 @@ fs.chownSync = function(path, uid, gid) {
417417
return binding.chown(path, uid, gid);
418418
};
419419

420+
// converts Date or number to a fractional UNIX timestamp
421+
function toUnixTimestamp(time) {
422+
if (typeof time == 'number') {
423+
return time;
424+
}
425+
if (time instanceof Date) {
426+
// convert to 123.456 UNIX timestamp
427+
return time.getTime() / 1000;
428+
}
429+
throw new Error("Cannot parse time: " + time);
430+
}
431+
432+
// exported for unit tests, not for public consumption
433+
fs._toUnixTimestamp = toUnixTimestamp;
434+
435+
fs.utimes = function(path, atime, mtime, callback) {
436+
atime = toUnixTimestamp(atime);
437+
mtime = toUnixTimestamp(mtime);
438+
binding.utimes(path, atime, mtime, callback || noop);
439+
};
440+
441+
fs.utimesSync = function(path, atime, mtime) {
442+
atime = toUnixTimestamp(atime);
443+
mtime = toUnixTimestamp(mtime);
444+
binding.utimes(path, atime, mtime);
445+
};
446+
447+
fs.futimes = function(fd, atime, mtime, callback) {
448+
atime = toUnixTimestamp(atime);
449+
mtime = toUnixTimestamp(mtime);
450+
binding.futimes(fd, atime, mtime, callback || noop);
451+
};
452+
453+
fs.futimesSync = function(fd, atime, mtime) {
454+
atime = toUnixTimestamp(atime);
455+
mtime = toUnixTimestamp(mtime);
456+
binding.futimes(fd, atime, mtime);
457+
};
458+
420459
function writeAll(fd, buffer, offset, length, callback) {
421460
// write(fd, buffer, offset, length, position, callback)
422461
fs.write(fd, buffer, offset, length, offset, function(writeErr, written) {

src/node_file.cc

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <sys/types.h>
88
#include <sys/stat.h>
9+
#include <sys/time.h>
910
#include <dirent.h>
1011
#include <fcntl.h>
1112
#include <stdlib.h>
@@ -110,6 +111,11 @@ static int After(eio_req *req) {
110111
argc = 1;
111112
break;
112113

114+
case EIO_UTIME:
115+
case EIO_FUTIME:
116+
argc = 0;
117+
break;
118+
113119
case EIO_OPEN:
114120
SetCloseOnExec(req->result);
115121
/* pass thru */
@@ -824,6 +830,78 @@ static Handle<Value> Chown(const Arguments& args) {
824830
}
825831
#endif // __POSIX__
826832

833+
834+
// Utimes() and Futimes() helper function, converts 123.456 timestamps to timevals
835+
static inline void ToTimevals(eio_tstamp atime,
836+
eio_tstamp mtime,
837+
timeval times[2]) {
838+
times[0].tv_sec = atime;
839+
times[0].tv_usec = 10e5 * (atime - (long) atime);
840+
times[1].tv_sec = mtime;
841+
times[1].tv_usec = 10e5 * (mtime - (long) mtime);
842+
}
843+
844+
845+
static Handle<Value> UTimes(const Arguments& args) {
846+
HandleScope scope;
847+
848+
if (args.Length() < 3
849+
|| !args[0]->IsString()
850+
|| !args[1]->IsNumber()
851+
|| !args[2]->IsNumber())
852+
{
853+
return THROW_BAD_ARGS;
854+
}
855+
856+
const String::Utf8Value path(args[0]->ToString());
857+
const eio_tstamp atime = static_cast<eio_tstamp>(args[1]->NumberValue());
858+
const eio_tstamp mtime = static_cast<eio_tstamp>(args[2]->NumberValue());
859+
860+
if (args[3]->IsFunction()) {
861+
ASYNC_CALL(utime, args[3], *path, atime, mtime);
862+
} else {
863+
timeval times[2];
864+
865+
ToTimevals(atime, mtime, times);
866+
if (utimes(*path, times) == -1) {
867+
return ThrowException(ErrnoException(errno, "utimes", "", *path));
868+
}
869+
}
870+
871+
return Undefined();
872+
}
873+
874+
875+
static Handle<Value> FUTimes(const Arguments& args) {
876+
HandleScope scope;
877+
878+
if (args.Length() < 3
879+
|| !args[0]->IsInt32()
880+
|| !args[1]->IsNumber()
881+
|| !args[2]->IsNumber())
882+
{
883+
return THROW_BAD_ARGS;
884+
}
885+
886+
const int fd = args[0]->Int32Value();
887+
const eio_tstamp atime = static_cast<eio_tstamp>(args[1]->NumberValue());
888+
const eio_tstamp mtime = static_cast<eio_tstamp>(args[2]->NumberValue());
889+
890+
if (args[3]->IsFunction()) {
891+
ASYNC_CALL(futime, args[3], fd, atime, mtime);
892+
} else {
893+
timeval times[2];
894+
895+
ToTimevals(atime, mtime, times);
896+
if (futimes(fd, times) == -1) {
897+
return ThrowException(ErrnoException(errno, "futimes", "", 0));
898+
}
899+
}
900+
901+
return Undefined();
902+
}
903+
904+
827905
void File::Initialize(Handle<Object> target) {
828906
HandleScope scope;
829907

@@ -856,6 +934,9 @@ void File::Initialize(Handle<Object> target) {
856934
NODE_SET_METHOD(target, "chown", Chown);
857935
#endif // __POSIX__
858936

937+
NODE_SET_METHOD(target, "utimes", UTimes);
938+
NODE_SET_METHOD(target, "futimes", FUTimes);
939+
859940
errno_symbol = NODE_PSYMBOL("errno");
860941
encoding_symbol = NODE_PSYMBOL("node:encoding");
861942
buf_symbol = NODE_PSYMBOL("__buf");

test/simple/test-fs-utimes.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
var common = require('../common');
2+
var assert = require('assert');
3+
var util = require('util');
4+
var fs = require('fs');
5+
6+
var tests_ok = 0;
7+
var tests_run = 0;
8+
9+
function stat_resource(resource) {
10+
if (typeof resource == 'string') {
11+
return fs.statSync(resource);
12+
} else {
13+
// ensure mtime has been written to disk
14+
fs.fsyncSync(resource);
15+
return fs.fstatSync(resource);
16+
}
17+
}
18+
19+
function check_mtime(resource, mtime) {
20+
var mtime = fs._toUnixTimestamp(mtime);
21+
var stats = stat_resource(resource);
22+
var real_mtime = fs._toUnixTimestamp(stats.mtime);
23+
// check up to single-second precision
24+
// sub-second precision is OS and fs dependant
25+
return Math.floor(mtime) == Math.floor(real_mtime);
26+
}
27+
28+
function expect_errno(syscall, resource, err, errno) {
29+
if (err && err.code == errno) {
30+
tests_ok++;
31+
} else {
32+
console.log('FAILED:', arguments.callee.name, util.inspect(arguments));
33+
}
34+
}
35+
36+
function expect_ok(syscall, resource, err, atime, mtime) {
37+
if (!err && check_mtime(resource, mtime)) {
38+
tests_ok++;
39+
} else {
40+
console.log('FAILED:', arguments.callee.name, util.inspect(arguments));
41+
}
42+
}
43+
44+
// the tests assume that __filename belongs to the user running the tests
45+
// this should be a fairly safe assumption; testing against a temp file
46+
// would be even better though (node doesn't have such functionality yet)
47+
function runTests(atime, mtime, callback) {
48+
49+
var fd, err;
50+
//
51+
// test synchronized code paths, these functions throw on failure
52+
//
53+
function syncTests() {
54+
fs.utimesSync(__filename, atime, mtime);
55+
expect_ok('utimesSync', __filename, undefined, atime, mtime);
56+
tests_run++;
57+
58+
fs.futimesSync(fd, atime, mtime);
59+
expect_ok('futimesSync', fd, undefined, atime, mtime);
60+
tests_run++;
61+
62+
err = undefined;
63+
try {
64+
fs.utimesSync('foobarbaz', atime, mtime);
65+
} catch (ex) {
66+
err = ex;
67+
}
68+
expect_errno('utimesSync', 'foobarbaz', err, 'ENOENT');
69+
tests_run++;
70+
71+
err = undefined;
72+
try {
73+
fs.futimesSync(-1, atime, mtime);
74+
} catch (ex) {
75+
err = ex;
76+
}
77+
expect_errno('futimesSync', -1, err, 'EBADF');
78+
tests_run++;
79+
}
80+
81+
//
82+
// test async code paths
83+
//
84+
fs.utimes(__filename, atime, mtime, function(err) {
85+
expect_ok('utimes', __filename, err, atime, mtime);
86+
87+
fs.utimes('foobarbaz', atime, mtime, function(err) {
88+
expect_errno('utimes', 'foobarbaz', err, 'ENOENT');
89+
90+
// don't close this fd
91+
fd = fs.openSync(__filename, 'r');
92+
93+
fs.futimes(fd, atime, mtime, function(err) {
94+
expect_ok('futimes', fd, err, atime, mtime);
95+
96+
fs.futimes(-1, atime, mtime, function(err) {
97+
expect_errno('futimes', -1, err, 'EBADF');
98+
syncTests();
99+
callback();
100+
});
101+
tests_run++;
102+
});
103+
tests_run++;
104+
});
105+
tests_run++;
106+
});
107+
tests_run++;
108+
}
109+
110+
var stats = fs.statSync(__filename);
111+
112+
runTests(new Date('1982-09-10 13:37'), new Date('1982-09-10 13:37'), function() {
113+
runTests(new Date(), new Date(), function() {
114+
runTests(1234.5678, 1234.5678, function() {
115+
runTests(stats.mtime, stats.mtime, function() {
116+
// done
117+
});
118+
});
119+
});
120+
});
121+
122+
process.on('exit', function() {
123+
console.log('Tests run / ok:', tests_run, '/', tests_ok);
124+
assert.equal(tests_ok, tests_run);
125+
});

0 commit comments

Comments
 (0)