Skip to content

JS file system: distinguish between atime, mtime, and ctime #22998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/library_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ FS.staticInit();
this.name = name;
this.mode = mode;
this.rdev = rdev;
this.atime = this.mtime = this.ctime = Date.now();
}
get read() {
return (this.mode & this.readMode) === this.readMode;
Expand All @@ -165,6 +166,12 @@ FS.staticInit();
get isDevice() {
return FS.isChrdev(this.mode);
}
get timestamp() {
return this.atime;
}
set timestamp(val) {
this.atime = this.mtime = this.ctime = val;
}
},

//
Expand Down Expand Up @@ -933,7 +940,7 @@ FS.staticInit();
}
node.node_ops.setattr(node, {
mode: (mode & {{{ cDefs.S_IALLUGO }}}) | (node.mode & ~{{{ cDefs.S_IALLUGO }}}),
timestamp: Date.now()
ctime: Date.now()
});
},
lchmod(path, mode) {
Expand Down Expand Up @@ -1006,7 +1013,8 @@ FS.staticInit();
var lookup = FS.lookupPath(path, { follow: true });
var node = lookup.node;
node.node_ops.setattr(node, {
timestamp: Math.max(atime, mtime)
atime: atime,
mtime: mtime
});
},
open(path, flags, mode) {
Expand Down Expand Up @@ -1609,7 +1617,7 @@ FS.staticInit();
buffer[offset+i] = result;
}
if (bytesRead) {
stream.node.timestamp = Date.now();
stream.node.atime = Date.now();
}
return bytesRead;
},
Expand All @@ -1622,7 +1630,7 @@ FS.staticInit();
}
}
if (length) {
stream.node.timestamp = Date.now();
stream.node.mtime = stream.node.ctime = Date.now();
}
return i;
}
Expand Down
17 changes: 8 additions & 9 deletions src/library_lz4.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ addToLibrary({
node.mode = mode;
node.node_ops = LZ4.node_ops;
node.stream_ops = LZ4.stream_ops;
node.timestamp = (mtime || new Date).getTime();
node.atime = node.mtime = node.ctime = (mtime || new Date).getTime();
assert(LZ4.FILE_MODE !== LZ4.DIR_MODE);
if (mode === LZ4.FILE_MODE) {
node.size = contents.end - contents.start;
Expand All @@ -95,19 +95,18 @@ addToLibrary({
gid: 0,
rdev: 0,
size: node.size,
atime: new Date(node.timestamp),
mtime: new Date(node.timestamp),
ctime: new Date(node.timestamp),
atime: new Date(node.atime),
mtime: new Date(node.mtime),
ctime: new Date(node.ctime),
blksize: 4096,
blocks: Math.ceil(node.size / 4096),
};
},
setattr(node, attr) {
if (attr.mode !== undefined) {
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
node.timestamp = attr.timestamp;
for (const key of ["mode", "atime", "mtime", "ctime"]) {
if (attr[key]) {
node[key] = attr[key];
}
}
},
lookup(parent, name) {
Expand Down
30 changes: 14 additions & 16 deletions src/library_memfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ addToLibrary({
node.node_ops = MEMFS.ops_table.chrdev.node;
node.stream_ops = MEMFS.ops_table.chrdev.stream;
}
node.timestamp = Date.now();
node.atime = node.mtime = node.ctime = Date.now();
// add the new node to the parent
if (parent) {
parent.contents[name] = node;
parent.timestamp = node.timestamp;
parent.atime = parent.mtime = parent.ctime = node.atime;
}
return node;
},
Expand Down Expand Up @@ -161,21 +161,20 @@ addToLibrary({
} else {
attr.size = 0;
}
attr.atime = new Date(node.timestamp);
attr.mtime = new Date(node.timestamp);
attr.ctime = new Date(node.timestamp);
attr.atime = new Date(node.atime);
attr.mtime = new Date(node.mtime);
attr.ctime = new Date(node.ctime);
// NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize),
// but this is not required by the standard.
attr.blksize = 4096;
attr.blocks = Math.ceil(attr.size / attr.blksize);
return attr;
},
setattr(node, attr) {
if (attr.mode !== undefined) {
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
node.timestamp = attr.timestamp;
for (const key of ["mode", "atime", "mtime", "ctime"]) {
if (attr[key]) {
node[key] = attr[key];
}
}
if (attr.size !== undefined) {
MEMFS.resizeFileStorage(node, attr.size);
Expand Down Expand Up @@ -207,22 +206,21 @@ addToLibrary({
}
// do the internal rewiring
delete old_node.parent.contents[old_node.name];
old_node.parent.timestamp = Date.now()
old_node.name = new_name;
new_dir.contents[new_name] = old_node;
new_dir.timestamp = old_node.parent.timestamp;
old_node.name = new_name;
new_dir.ctime = new_dir.mtime = old_node.parent.ctime = old_node.parent.mtime = Date.now();
},
unlink(parent, name) {
delete parent.contents[name];
parent.timestamp = Date.now();
parent.ctime = parent.mtime = Date.now();
},
rmdir(parent, name) {
var node = FS.lookupNode(parent, name);
for (var i in node.contents) {
throw new FS.ErrnoError({{{ cDefs.ENOTEMPTY }}});
}
delete parent.contents[name];
parent.timestamp = Date.now();
parent.ctime = parent.mtime = Date.now();
},
readdir(node) {
var entries = ['.', '..'];
Expand Down Expand Up @@ -282,7 +280,7 @@ addToLibrary({

if (!length) return 0;
var node = stream.node;
node.timestamp = Date.now();
node.mtime = node.ctime = Date.now();

if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array?
if (canOwn) {
Expand Down
8 changes: 5 additions & 3 deletions src/library_nodefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,11 @@ addToLibrary({
// update the common node structure mode as well
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
var date = new Date(attr.timestamp);
fs.utimesSync(path, date, date);
if (attr.atime || attr.mtime) {
var atime = attr.atime && new Date(attr.atime);
var mtime = attr.mtime && new Date(attr.mtime);
console.log("atime", atime, "mtime", mtime);
fs.utimesSync(path, atime, mtime);
}
if (attr.size !== undefined) {
fs.truncateSync(path, attr.size);
Expand Down
6 changes: 3 additions & 3 deletions src/library_noderawfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ addToLibrary({
// -1 here for atime or mtime means UTIME_OMIT was passed. Since node
// doesn't support this concept we need to first find the existing
// timestamps in order to preserve them.
if (atime == -1 || mtime == -1) {
if (atime === undefined || mtime === undefined) {
var st = fs.statSync(path);
if (atime == -1) atime = st.atimeMs;
if (mtime == -1) mtime = st.mtimeMs;
atime ||= st.atimeMs;
mtime ||= st.mtimeMs;
}
fs.utimesSync(path, atime/1000, mtime/1000);
},
Expand Down
7 changes: 4 additions & 3 deletions src/library_proxyfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ addToLibrary({
// update the common node structure mode as well
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
var date = new Date(attr.timestamp);
node.mount.opts.fs.utime(path, date, date);
if (attr.atime || attr.mtime) {
var atime = new Date(attr.atime || attr.mtime);
var mtime = new Date(attr.mtime || attr.atime);
node.mount.opts.fs.utime(path, atime, mtime);
}
if (attr.size !== undefined) {
node.mount.opts.fs.truncate(path, attr.size);
Expand Down
11 changes: 5 additions & 6 deletions src/library_syscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ var SyscallsLibrary = {
if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
atime = now;
} else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
atime = -1;
atime = undefined;
} else {
atime = (seconds*1000) + (nanoseconds/(1000*1000));
}
Expand All @@ -990,15 +990,14 @@ var SyscallsLibrary = {
if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
mtime = now;
} else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
mtime = -1;
mtime = undefined;
} else {
mtime = (seconds*1000) + (nanoseconds/(1000*1000));
}
}
// -1 here means UTIME_OMIT was passed. FS.utime tables the max of these
// two values and sets the timestamp to that single value. If both were
// set to UTIME_OMIT then we can skip the call completely.
if (mtime != -1 || atime != -1) {
// undefined here means UTIME_OMIT was passed. If both were set to UTIME_OMIT then
// we can skip the call completely.
if (mtime !== undefined || atime !== undefined) {
FS.utime(path, atime, mtime);
}
return 0;
Expand Down
4 changes: 2 additions & 2 deletions src/library_tty.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ addToLibrary({
buffer[offset+i] = result;
}
if (bytesRead) {
stream.node.timestamp = Date.now();
stream.node.atime = Date.now();
}
return bytesRead;
},
Expand All @@ -95,7 +95,7 @@ addToLibrary({
throw new FS.ErrnoError({{{ cDefs.EIO }}});
}
if (length) {
stream.node.timestamp = Date.now();
stream.node.mtime = stream.node.ctime = Date.now();
}
return i;
}
Expand Down
17 changes: 8 additions & 9 deletions src/library_workerfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ addToLibrary({
node.mode = mode;
node.node_ops = WORKERFS.node_ops;
node.stream_ops = WORKERFS.stream_ops;
node.timestamp = (mtime || new Date).getTime();
node.atime = node.mtime = node.ctime = (mtime || new Date).getTime();
assert(WORKERFS.FILE_MODE !== WORKERFS.DIR_MODE);
if (mode === WORKERFS.FILE_MODE) {
node.size = contents.size;
Expand All @@ -82,19 +82,18 @@ addToLibrary({
gid: 0,
rdev: 0,
size: node.size,
atime: new Date(node.timestamp),
mtime: new Date(node.timestamp),
ctime: new Date(node.timestamp),
atime: new Date(node.atime),
mtime: new Date(node.mtime),
ctime: new Date(node.ctime),
blksize: 4096,
blocks: Math.ceil(node.size / 4096),
};
},
setattr(node, attr) {
if (attr.mode !== undefined) {
node.mode = attr.mode;
}
if (attr.timestamp !== undefined) {
node.timestamp = attr.timestamp;
for (const key of ["mode", "atime", "mtime", "ctime"]) {
if (attr[key]) {
node[key] = attr[key];
}
}
},
lookup(parent, name) {
Expand Down
7 changes: 0 additions & 7 deletions test/fs/test_fs_js_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -439,17 +439,10 @@ void test_fs_utime() {
assert(utimeStats.st_atime == 10);
assert(utimeStats.st_atim.tv_sec == 10);

// WasmFS correctly sets both times, but the legacy API sets both times to the max of atime and mtime
// and does not correctly handle nanseconds.
#if WASMFS
assert(utimeStats.st_atim.tv_nsec == 500000000);

assert(utimeStats.st_mtime == 8);
assert(utimeStats.st_mtim.tv_sec == 8);
#else
assert(utimeStats.st_mtime == 10);
assert(utimeStats.st_mtim.tv_sec == 10);
#endif

remove("utimetest");
}
Expand Down
9 changes: 9 additions & 0 deletions test/stat/test_chmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ void cleanup() {
void test() {
int err;
int lastctime;
int lastmtime;
struct stat s;

//
Expand All @@ -57,6 +58,7 @@ void test() {
memset(&s, 0, sizeof s);
stat("file", &s);
lastctime = s.st_ctime;
lastmtime = s.st_mtime;
sleep(1);

// do the actual chmod
Expand All @@ -67,11 +69,13 @@ void test() {
stat("file", &s);
assert(s.st_mode == (0200 | S_IFREG));
assert(s.st_ctime != lastctime);
assert(s.st_mtime == lastmtime);

//
// fchmod a file
//
lastctime = s.st_ctime;
lastmtime = s.st_mtime;
sleep(1);

err = fchmod(open("file", O_WRONLY), 0100);
Expand All @@ -81,12 +85,14 @@ void test() {
stat("file", &s);
assert(s.st_mode == (0100 | S_IFREG));
assert(s.st_ctime != lastctime);
assert(s.st_mtime == lastmtime);


//
// fchmodat a file
//
lastctime = s.st_ctime;
lastmtime = s.st_mtime;
sleep(1);
err = fchmodat(AT_FDCWD, "otherfile", 0100, 0);
assert(!err);
Expand All @@ -95,6 +101,7 @@ void test() {
stat("otherfile", &s);
assert(s.st_mode == (0100 | S_IFREG));
assert(s.st_ctime != lastctime);
assert(s.st_mtime == lastmtime);

//
// chmod a folder
Expand All @@ -103,6 +110,7 @@ void test() {
memset(&s, 0, sizeof s);
stat("folder", &s);
lastctime = s.st_ctime;
lastmtime = s.st_mtime;
sleep(1);

// do the actual chmod
Expand All @@ -112,6 +120,7 @@ void test() {
stat("folder", &s);
assert(s.st_mode == (0300 | S_IFDIR));
assert(s.st_ctime != lastctime);
assert(s.st_mtime == lastmtime);

#ifndef WASMFS // TODO https://github.com/emscripten-core/emscripten/issues/15948
//
Expand Down
6 changes: 0 additions & 6 deletions test/utime/test_futimens.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ void test() {
err = futimens(fd, newtimes);
assert(!err);

#if defined(__EMSCRIPTEN__) && !defined(WASMFS) && !defined(NODERAWFS)
// The original emscripten FS (in JS) only supports a single timestamp so both
// mtime and atime will always be the same.
times[0].tv_sec = 42;
times[0].tv_nsec = 88;
#endif
times[1].tv_sec = 42;
times[1].tv_nsec = 88;
check_times(fd, times, 0);
Expand Down
Loading