Skip to content

Commit f1dfaf3

Browse files
authored
Fix stat and chmod on windows under NODERAWFS (#23013)
1 parent 48b6742 commit f1dfaf3

File tree

9 files changed

+131
-114
lines changed

9 files changed

+131
-114
lines changed

src/library_nodefs.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,14 @@ addToLibrary({
7676
return node;
7777
},
7878
getMode(path) {
79-
var stat;
8079
return NODEFS.tryFSOperation(() => {
81-
stat = fs.lstatSync(path);
80+
var mode = fs.lstatSync(path).mode;
8281
if (NODEFS.isWindows) {
83-
// Node.js on Windows never represents permission bit 'x', so
84-
// propagate read bits to execute bits
85-
stat.mode |= (stat.mode & {{{ cDefs.S_IRUSR | cDefs.S_IRGRP | cDefs.S_IROTH }}}) >> 2;
82+
// Windows does not report the 'x' permission bit, so propagate read
83+
// bits to execute bits.
84+
mode |= (mode & {{{ cDefs.S_IRUGO }}}) >> 2;
8685
}
87-
return stat.mode;
86+
return mode;
8887
});
8988
},
9089
realPath(node) {
@@ -133,9 +132,9 @@ addToLibrary({
133132
if (!stat.blocks) {
134133
stat.blocks = (stat.size+stat.blksize-1)/stat.blksize|0;
135134
}
136-
// Node.js on Windows never represents permission bit 'x', so
137-
// propagate read bits to execute bits.
138-
stat.mode |= (stat.mode & {{{ cDefs.S_IRUSR | cDefs.S_IRGRP | cDefs.S_IROTH }}}) >> 2;
135+
// Windows does not report the 'x' permission bit, so propagate read
136+
// bits to execute bits.
137+
stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2;
139138
}
140139
return {
141140
dev: stat.dev,
@@ -157,7 +156,13 @@ addToLibrary({
157156
var path = NODEFS.realPath(node);
158157
NODEFS.tryFSOperation(() => {
159158
if (attr.mode !== undefined) {
160-
fs.chmodSync(path, attr.mode);
159+
var mode = attr.mode;
160+
if (NODEFS.isWindows) {
161+
// Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR)
162+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod
163+
mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}};
164+
}
165+
fs.chmodSync(path, mode);
161166
// update the common node structure mode as well
162167
node.mode = attr.mode;
163168
}

src/library_noderawfs.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,22 @@ addToLibrary({
7070
readdir(...args) { return ['.', '..'].concat(fs.readdirSync(...args)); },
7171
unlink(...args) { fs.unlinkSync(...args); },
7272
readlink(...args) { return fs.readlinkSync(...args); },
73-
stat(...args) { return fs.statSync(...args); },
74-
lstat(...args) { return fs.lstatSync(...args); },
73+
stat(path, dontFollow) {
74+
var stat = dontFollow ? fs.lstatSync(path) : fs.statSync(path);
75+
if (NODEFS.isWindows) {
76+
// Windows does not report the 'x' permission bit, so propagate read
77+
// bits to execute bits.
78+
stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2;
79+
}
80+
return stat;
81+
},
7582
chmod(path, mode, dontFollow) {
83+
mode &= {{{ cDefs.S_IALLUGO }}};
84+
if (NODEFS.isWindows) {
85+
// Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR)
86+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod
87+
mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}};
88+
}
7689
if (dontFollow && fs.lstatSync(path).isSymbolicLink()) {
7790
// Node (and indeed linux) does not support chmod on symlinks
7891
// https://nodejs.org/api/fs.html#fslchmodsyncpath-mode

src/struct_info.json

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
{
2424
"file": "sys/stat.h",
2525
"defines": [
26+
"S_IALLUGO",
27+
"S_IWUSR",
28+
"S_IWUGO",
29+
"S_IRUGO",
30+
"S_IRWXUGO",
31+
"S_IXUGO",
2632
"S_IFDIR",
2733
"S_IFREG",
2834
"S_IFMT",
@@ -470,16 +476,6 @@
470476
"SEEK_SET"
471477
]
472478
},
473-
{
474-
"file": "sys/stat.h",
475-
"defines": [
476-
"S_IALLUGO",
477-
"S_IWUGO",
478-
"S_IRUGO",
479-
"S_IRWXUGO",
480-
"S_IXUGO"
481-
]
482-
},
483479
{
484480
"file": "sys/mman.h",
485481
"defines": [

src/struct_info_generated.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@
447447
"S_IRWXUGO": 511,
448448
"S_ISVTX": 512,
449449
"S_IWUGO": 146,
450+
"S_IWUSR": 128,
450451
"S_IXUGO": 73,
451452
"TCFLSH": 21515,
452453
"TCGETA": 21509,

src/struct_info_generated_wasm64.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@
447447
"S_IRWXUGO": 511,
448448
"S_ISVTX": 512,
449449
"S_IWUGO": 146,
450+
"S_IWUSR": 128,
450451
"S_IXUGO": 73,
451452
"TCFLSH": 21515,
452453
"TCGETA": 21509,

test/test_core.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5911,25 +5911,31 @@ def test_sigaction_default(self, signal, exit_code, assert_identical):
59115911
assert_returncode=exit_code
59125912
)
59135913

5914-
@no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
59155914
@requires_node
5915+
@crossplatform
59165916
@parameterized({
59175917
'': (['-DMEMFS'],),
59185918
'nodefs': (['-DNODEFS', '-lnodefs.js'],),
59195919
'noderawfs': (['-DNODERAWFS', '-sNODERAWFS'],)
59205920
})
59215921
def test_unistd_access(self, args):
59225922
self.emcc_args += args
5923+
nodefs = '-DNODEFS' in args or '-DNODERAWFS' in args
59235924
if self.get_setting('WASMFS'):
5924-
if '-DNODEFS' in args or '-DNODERAWFS' in args:
5925+
if nodefs:
59255926
self.skipTest('NODEFS in WasmFS')
59265927
self.emcc_args += ['-sFORCE_FILESYSTEM']
5927-
5928-
# Node.js fs.chmod is nearly no-op on Windows
5929-
if '-DNODERAWFS' in args and WINDOWS:
5930-
self.skipTest('NODERAWFS on windows')
5931-
5932-
self.do_run_in_out_file_test('unistd/access.c')
5928+
# On windows we have slighly different output because we the same
5929+
# level of permissions are not available. For example, on windows
5930+
# its not possible have a file that is not readable, but writable.
5931+
# We also report all files as executable since there is no x bit
5932+
# recorded there.
5933+
# See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod?view=msvc-170#remarks
5934+
if WINDOWS and '-DNODERAWFS' in args:
5935+
out_suffix = '.win'
5936+
else:
5937+
out_suffix = ''
5938+
self.do_run_in_out_file_test('unistd/access.c', out_suffix=out_suffix)
59335939

59345940
def test_unistd_curdir(self):
59355941
if self.get_setting('WASMFS'):

test/unistd/access.c

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <errno.h>
1111
#include <fcntl.h>
1212
#include <stdio.h>
13+
#include <string.h>
1314
#include <sys/stat.h>
1415
#include <unistd.h>
1516

@@ -31,18 +32,10 @@ int main() {
3132
char* files[] = {"readable", "writeable",
3233
"allaccess", "forbidden", "nonexistent", ""};
3334
for (int i = 0; i < sizeof files / sizeof files[0]; i++) {
34-
printf("F_OK(%s): %d\n", files[i], access(files[i], F_OK));
35-
printf("errno: %d\n", errno);
36-
errno = 0;
37-
printf("R_OK(%s): %d\n", files[i], access(files[i], R_OK));
38-
printf("errno: %d\n", errno);
39-
errno = 0;
40-
printf("X_OK(%s): %d\n", files[i], access(files[i], X_OK));
41-
printf("errno: %d\n", errno);
42-
errno = 0;
43-
printf("W_OK(%s): %d\n", files[i], access(files[i], W_OK));
44-
printf("errno: %d\n", errno);
45-
errno = 0;
35+
printf("F_OK('%s'): %s\n", files[i], access(files[i], F_OK) < 0 ? strerror(errno) : "OK");
36+
printf("R_OK('%s'): %s\n", files[i], access(files[i], R_OK) < 0 ? strerror(errno) : "OK");
37+
printf("X_OK('%s'): %s\n", files[i], access(files[i], X_OK) < 0 ? strerror(errno) : "OK");
38+
printf("W_OK('%s'): %s\n", files[i], access(files[i], W_OK) < 0 ? strerror(errno) : "OK");
4639
printf("\n");
4740
}
4841

@@ -51,25 +44,20 @@ int main() {
5144
int rename_ret = rename("filetorename", "renamedfile");
5245
assert(rename_ret == 0);
5346

54-
errno = 0;
55-
printf("F_OK(%s): %d\n", "filetorename", access("filetorename", F_OK));
56-
printf("errno: %d\n", errno);
57-
errno = 0;
58-
printf("F_OK(%s): %d\n", "renamedfile", access("renamedfile", F_OK));
59-
printf("errno: %d\n", errno);
47+
printf("F_OK('%s'): %d\n", "filetorename", access("filetorename", F_OK));
48+
printf("F_OK('%s'): %d\n", "renamedfile", access("renamedfile", F_OK));
6049

6150
// Same againt with faccessat
62-
errno = 0;
63-
printf("F_OK(%s): %d\n", "filetorename", faccessat(AT_FDCWD, "filetorename", F_OK, 0));
64-
printf("errno: %d\n", errno);
65-
errno = 0;
66-
printf("F_OK(%s): %d\n", "renamedfile", faccessat(AT_FDCWD, "renamedfile", F_OK, 0));
67-
printf("errno: %d\n", errno);
68-
69-
chmod("fchmodtest", 0666);
51+
printf("F_OK('%s'): %d\n", "filetorename", faccessat(AT_FDCWD, "filetorename", F_OK, 0));
52+
printf("F_OK('%s'): %d\n", "renamedfile", faccessat(AT_FDCWD, "renamedfile", F_OK, 0));
53+
54+
chmod("fchmodtest", S_IRUGO | S_IWUGO);
7055
struct stat fileStats;
7156
stat("fchmodtest", &fileStats);
72-
assert((fileStats.st_mode & 0777) == 0666);
57+
int mode = fileStats.st_mode & 0777;
58+
// Allow S_IXUGO in addtion to S_IWUGO because on windows
59+
// we always report the execute bit.
60+
assert(mode == (S_IRUGO | S_IWUGO) || mode == (S_IRUGO | S_IWUGO | S_IXUGO));
7361

7462
EM_ASM(
7563
var fchmodstream = FS.open("fchmodtest", "r");
@@ -92,7 +80,8 @@ int main() {
9280
assert((symlinkStats.st_mode & 0777) == 0777);
9381

9482
stat("writeable", &fileStats);
95-
assert((fileStats.st_mode & 0777) == 0222);
83+
mode = fileStats.st_mode & 0777;
84+
assert(mode == S_IWUGO || mode == (S_IWUGO | S_IXUGO));
9685
#endif
9786

9887
EM_ASM(

test/unistd/access.out

Lines changed: 28 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,34 @@
1-
F_OK(readable): 0
2-
errno: 0
3-
R_OK(readable): 0
4-
errno: 0
5-
X_OK(readable): -1
6-
errno: 2
7-
W_OK(readable): -1
8-
errno: 2
1+
F_OK('readable'): OK
2+
R_OK('readable'): OK
3+
X_OK('readable'): Permission denied
4+
W_OK('readable'): Permission denied
95

10-
F_OK(writeable): 0
11-
errno: 0
12-
R_OK(writeable): -1
13-
errno: 2
14-
X_OK(writeable): -1
15-
errno: 2
16-
W_OK(writeable): 0
17-
errno: 0
6+
F_OK('writeable'): OK
7+
R_OK('writeable'): Permission denied
8+
X_OK('writeable'): Permission denied
9+
W_OK('writeable'): OK
1810

19-
F_OK(allaccess): 0
20-
errno: 0
21-
R_OK(allaccess): 0
22-
errno: 0
23-
X_OK(allaccess): 0
24-
errno: 0
25-
W_OK(allaccess): 0
26-
errno: 0
11+
F_OK('allaccess'): OK
12+
R_OK('allaccess'): OK
13+
X_OK('allaccess'): OK
14+
W_OK('allaccess'): OK
2715

28-
F_OK(forbidden): 0
29-
errno: 0
30-
R_OK(forbidden): -1
31-
errno: 2
32-
X_OK(forbidden): -1
33-
errno: 2
34-
W_OK(forbidden): -1
35-
errno: 2
16+
F_OK('forbidden'): OK
17+
R_OK('forbidden'): Permission denied
18+
X_OK('forbidden'): Permission denied
19+
W_OK('forbidden'): Permission denied
3620

37-
F_OK(nonexistent): -1
38-
errno: 44
39-
R_OK(nonexistent): -1
40-
errno: 44
41-
X_OK(nonexistent): -1
42-
errno: 44
43-
W_OK(nonexistent): -1
44-
errno: 44
21+
F_OK('nonexistent'): No such file or directory
22+
R_OK('nonexistent'): No such file or directory
23+
X_OK('nonexistent'): No such file or directory
24+
W_OK('nonexistent'): No such file or directory
4525

46-
F_OK(): -1
47-
errno: 44
48-
R_OK(): -1
49-
errno: 44
50-
X_OK(): -1
51-
errno: 44
52-
W_OK(): -1
53-
errno: 44
26+
F_OK(''): No such file or directory
27+
R_OK(''): No such file or directory
28+
X_OK(''): No such file or directory
29+
W_OK(''): No such file or directory
5430

55-
F_OK(filetorename): -1
56-
errno: 44
57-
F_OK(renamedfile): 0
58-
errno: 0
59-
F_OK(filetorename): -1
60-
errno: 44
61-
F_OK(renamedfile): 0
62-
errno: 0
31+
F_OK('filetorename'): -1
32+
F_OK('renamedfile'): 0
33+
F_OK('filetorename'): -1
34+
F_OK('renamedfile'): 0

test/unistd/access.win.out

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
F_OK('readable'): OK
2+
R_OK('readable'): OK
3+
X_OK('readable'): OK
4+
W_OK('readable'): Permission denied
5+
6+
F_OK('writeable'): OK
7+
R_OK('writeable'): OK
8+
X_OK('writeable'): OK
9+
W_OK('writeable'): OK
10+
11+
F_OK('allaccess'): OK
12+
R_OK('allaccess'): OK
13+
X_OK('allaccess'): OK
14+
W_OK('allaccess'): OK
15+
16+
F_OK('forbidden'): OK
17+
R_OK('forbidden'): OK
18+
X_OK('forbidden'): OK
19+
W_OK('forbidden'): Permission denied
20+
21+
F_OK('nonexistent'): No such file or directory
22+
R_OK('nonexistent'): No such file or directory
23+
X_OK('nonexistent'): No such file or directory
24+
W_OK('nonexistent'): No such file or directory
25+
26+
F_OK(''): No such file or directory
27+
R_OK(''): No such file or directory
28+
X_OK(''): No such file or directory
29+
W_OK(''): No such file or directory
30+
31+
F_OK('filetorename'): -1
32+
F_OK('renamedfile'): 0
33+
F_OK('filetorename'): -1
34+
F_OK('renamedfile'): 0

0 commit comments

Comments
 (0)