Skip to content

Commit e668097

Browse files
authored
Support UTIME_OMIT/UTIME_NOW in futimens (#22459)
Fixes: #22348
1 parent 5a1578a commit e668097

File tree

7 files changed

+130
-23
lines changed

7 files changed

+130
-23
lines changed

src/library_syscall.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ var SyscallsLibrary = {
5656
var mtime = stat.mtime.getTime();
5757
var ctime = stat.ctime.getTime();
5858
{{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_sec, 'Math.floor(atime / 1000)', 'i64') }}};
59-
{{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '(atime % 1000) * 1000', SIZE_TYPE) }}};
59+
{{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '(atime % 1000) * 1000 * 1000', SIZE_TYPE) }}};
6060
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_sec, 'Math.floor(mtime / 1000)', 'i64') }}};
61-
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '(mtime % 1000) * 1000', SIZE_TYPE) }}};
61+
{{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '(mtime % 1000) * 1000 * 1000', SIZE_TYPE) }}};
6262
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_sec, 'Math.floor(ctime / 1000)', 'i64') }}};
63-
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '(ctime % 1000) * 1000', SIZE_TYPE) }}};
63+
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '(ctime % 1000) * 1000 * 1000', SIZE_TYPE) }}};
6464
{{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}};
6565
return 0;
6666
},
@@ -978,19 +978,37 @@ var SyscallsLibrary = {
978978
assert(flags === 0);
979979
#endif
980980
path = SYSCALLS.calculateAt(dirfd, path, true);
981+
var now = Date.now(), atime, mtime;
981982
if (!times) {
982-
var atime = Date.now();
983-
var mtime = atime;
983+
atime = now;
984+
mtime = now;
984985
} else {
985986
var seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
986987
var nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
987-
atime = (seconds*1000) + (nanoseconds/(1000*1000));
988+
if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
989+
atime = now;
990+
} else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
991+
atime = -1;
992+
} else {
993+
atime = (seconds*1000) + (nanoseconds/(1000*1000));
994+
}
988995
times += {{{ C_STRUCTS.timespec.__size__ }}};
989996
seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
990997
nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
991-
mtime = (seconds*1000) + (nanoseconds/(1000*1000));
998+
if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
999+
mtime = now;
1000+
} else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
1001+
mtime = -1;
1002+
} else {
1003+
mtime = (seconds*1000) + (nanoseconds/(1000*1000));
1004+
}
1005+
}
1006+
// -1 here means UTIME_OMIT was passed. FS.utime tables the max of these
1007+
// two values and sets the timestamp to that single value. If both were
1008+
// set to UTIME_OMIT then we can skip the call completely.
1009+
if (mtime != -1 || atime != -1) {
1010+
FS.utime(path, atime, mtime);
9921011
}
993-
FS.utime(path, atime, mtime);
9941012
return 0;
9951013
},
9961014
__syscall_fallocate__i53abi: true,

src/struct_info.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
"S_IFCHR",
3434
"S_IRUSR",
3535
"S_IRGRP",
36-
"S_IROTH"
36+
"S_IROTH",
37+
"UTIME_OMIT",
38+
"UTIME_NOW"
3739
],
3840
"structs": {
3941
"stat": [

src/struct_info_generated.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@
415415
"TIOCSPGRP": 21520,
416416
"TIOCSWINSZ": 21524,
417417
"TZNAME_MAX": 16,
418+
"UTIME_NOW": 1073741823,
419+
"UTIME_OMIT": 1073741822,
418420
"UUID_TYPE_DCE_RANDOM": 4,
419421
"UUID_VARIANT_DCE": 1,
420422
"W_OK": 2,

src/struct_info_generated_wasm64.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,8 @@
415415
"TIOCSPGRP": 21520,
416416
"TIOCSWINSZ": 21524,
417417
"TZNAME_MAX": 16,
418+
"UTIME_NOW": 1073741823,
419+
"UTIME_OMIT": 1073741822,
418420
"UUID_TYPE_DCE_RANDOM": 4,
419421
"UUID_VARIANT_DCE": 1,
420422
"W_OK": 2,

system/lib/wasmfs/syscalls.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,6 @@ static timespec ms_to_timespec(double ms) {
353353
return ts;
354354
}
355355

356-
static double timespec_to_ms(timespec ts) {
357-
return double(ts.tv_sec) * 1000 + double(ts.tv_nsec) / (1000 * 1000);
358-
}
359-
360356
int __syscall_newfstatat(int dirfd, intptr_t path, intptr_t buf, int flags) {
361357
// Only accept valid flags.
362358
if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_SYMLINK_NOFOLLOW)) {
@@ -1123,6 +1119,16 @@ int __syscall_readlinkat(int dirfd,
11231119
return bytes;
11241120
}
11251121

1122+
static double timespec_to_ms(timespec ts) {
1123+
if (ts.tv_nsec == UTIME_OMIT) {
1124+
return INFINITY;
1125+
}
1126+
if (ts.tv_nsec == UTIME_NOW) {
1127+
return emscripten_date_now();
1128+
}
1129+
return double(ts.tv_sec) * 1000 + double(ts.tv_nsec) / (1000 * 1000);
1130+
}
1131+
11261132
// TODO: Test this with non-AT_FDCWD values.
11271133
int __syscall_utimensat(int dirFD, intptr_t path_, intptr_t times_, int flags) {
11281134
const char* path = (const char*)path_;
@@ -1148,16 +1154,20 @@ int __syscall_utimensat(int dirFD, intptr_t path_, intptr_t times_, int flags) {
11481154
// TODO: Check for write access to the file (see man page for specifics).
11491155
double aTime, mTime;
11501156

1151-
if (times == NULL) {
1157+
if (times == nullptr) {
11521158
aTime = mTime = emscripten_date_now();
11531159
} else {
11541160
aTime = timespec_to_ms(times[0]);
11551161
mTime = timespec_to_ms(times[1]);
11561162
}
11571163

11581164
auto locked = parsed.getFile()->locked();
1159-
locked.setATime(aTime);
1160-
locked.setMTime(mTime);
1165+
if (aTime != INFINITY) {
1166+
locked.setATime(aTime);
1167+
}
1168+
if (mTime != INFINITY) {
1169+
locked.setMTime(mTime);
1170+
}
11611171

11621172
return 0;
11631173
}

test/test_core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5654,6 +5654,7 @@ def test_libgen(self):
56545654
def test_utime(self):
56555655
self.do_runf('utime/test_utime.c', 'success')
56565656

5657+
@also_with_noderawfs
56575658
def test_futimens(self):
56585659
self.do_runf('utime/test_futimens.c', 'success')
56595660

test/utime/test_futimens.c

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <stdlib.h>
1313
#include <string.h>
1414
#include <unistd.h>
15+
#include <time.h>
1516
#include <sys/stat.h>
1617

1718

@@ -31,6 +32,23 @@ void setup() {
3132
symlink("file", "folder/file-link");
3233
}
3334

35+
void check_times(int fd, struct timespec* expected, int tolerance) {
36+
struct stat s;
37+
int rtn = fstatat(fd, "", &s, AT_EMPTY_PATH);
38+
assert(rtn == 0);
39+
printf("atime: tv_sec=%lld tv_nsec=%ld\n", s.st_atim.tv_sec, s.st_atim.tv_nsec);
40+
printf("mtime: tv_sec=%lld tv_nsec=%ld\n", s.st_mtim.tv_sec, s.st_mtim.tv_nsec);
41+
printf("expected atime: tv_sec=%lld tv_nsec=%ld\n", expected[0].tv_sec, expected[0].tv_nsec);
42+
printf("expected mtime: tv_sec=%lld tv_nsec=%ld\n", expected[1].tv_sec, expected[1].tv_nsec);
43+
if (tolerance) {
44+
assert(llabs(expected[0].tv_sec - s.st_atim.tv_sec) <= tolerance);
45+
assert(llabs(expected[1].tv_sec - s.st_mtim.tv_sec) <= tolerance);
46+
} else {
47+
assert(expected[0].tv_sec == s.st_atim.tv_sec);
48+
assert(expected[1].tv_sec == s.st_mtim.tv_sec);
49+
}
50+
}
51+
3452
void test() {
3553
int err;
3654
struct stat s;
@@ -49,18 +67,72 @@ void test() {
4967
assert(s.st_rdev == 0);
5068
assert(s.st_size == 8);
5169
assert(s.st_ctime);
52-
#ifdef __EMSCRIPTEN__
70+
#if defined(__EMSCRIPTEN__) && !defined(NODERAWFS)
5371
assert(s.st_blksize == 4096);
5472
assert(s.st_blocks == 1);
5573
#endif
5674

57-
struct timespec origTimes[2];
58-
origTimes[0].tv_sec = (time_t)s.st_atime;
59-
origTimes[0].tv_nsec = origTimes[0].tv_sec * 1000;
60-
origTimes[1].tv_sec = (time_t)s.st_mtime;
61-
origTimes[1].tv_nsec = origTimes[1].tv_sec * 1000;
62-
err = futimens(fd, origTimes);
75+
struct timespec times[2];
76+
times[0].tv_sec = s.st_atim.tv_sec;
77+
times[0].tv_nsec = s.st_atim.tv_nsec;
78+
times[1].tv_sec = s.st_mtim.tv_sec;
79+
times[1].tv_nsec = s.st_mtim.tv_nsec;
80+
81+
// set the timestampe to the current value
82+
err = futimens(fd, times);
83+
assert(!err);
84+
check_times(fd, times, 0);
85+
86+
// UTIME_OMIT means that the timeval is ignored, so
87+
// this call should do nothing.
88+
printf("check double UTIME_OMIT...\n");
89+
struct timespec newtimes[2];
90+
newtimes[0].tv_sec = 42;
91+
newtimes[0].tv_nsec = UTIME_OMIT;
92+
newtimes[1].tv_sec = 42;
93+
newtimes[1].tv_nsec = UTIME_OMIT;
94+
err = futimens(fd, newtimes);
95+
assert(!err);
96+
check_times(fd, times, 0);
97+
98+
// Setting just one of the two times to UTIME_OMIT means
99+
// the other should be honored.
100+
printf("check single UTIME_OMIT...\n");
101+
newtimes[0].tv_sec = 41;
102+
newtimes[0].tv_nsec = UTIME_OMIT;
103+
newtimes[1].tv_sec = 42;
104+
newtimes[1].tv_nsec = 88;
105+
err = futimens(fd, newtimes);
106+
assert(!err);
107+
108+
#if defined(__EMSCRIPTEN__) && !defined(WASMFS) && !defined(NODERAWFS)
109+
// The original emscripten FS (in JS) only supports a single timestamp so both
110+
// mtime and atime will always be the same.
111+
times[0].tv_sec = 42;
112+
times[0].tv_nsec = 88;
113+
#endif
114+
times[1].tv_sec = 42;
115+
times[1].tv_nsec = 88;
116+
check_times(fd, times, 0);
117+
118+
// UTIME_NOW means use the current date and ignore the seconds value
119+
printf("check single UTIME_NOW...\n");
120+
newtimes[0].tv_sec = 99;
121+
newtimes[0].tv_nsec = UTIME_NOW;
122+
newtimes[1].tv_sec = 99;
123+
newtimes[1].tv_nsec = UTIME_NOW;
124+
err = futimens(fd, newtimes);
125+
assert(!err);
126+
127+
struct timespec now;
128+
err = clock_gettime(CLOCK_REALTIME, &now);
129+
printf("now: %lld %ld\n", now.tv_sec, now.tv_nsec);
63130
assert(!err);
131+
times[0].tv_sec = now.tv_sec;
132+
times[0].tv_nsec = now.tv_nsec;
133+
times[1].tv_sec = now.tv_sec;
134+
times[1].tv_nsec = now.tv_nsec;
135+
check_times(fd, times, 1);
64136

65137
close(fd);
66138

0 commit comments

Comments
 (0)