Skip to content

Commit

Permalink
Fix stat and chmod on windows under NODERAWFS (emscripten-core#23013)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbc100 authored Nov 26, 2024
1 parent 48b6742 commit f1dfaf3
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 114 deletions.
25 changes: 15 additions & 10 deletions src/library_nodefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,14 @@ addToLibrary({
return node;
},
getMode(path) {
var stat;
return NODEFS.tryFSOperation(() => {
stat = fs.lstatSync(path);
var mode = fs.lstatSync(path).mode;
if (NODEFS.isWindows) {
// Node.js on Windows never represents permission bit 'x', so
// propagate read bits to execute bits
stat.mode |= (stat.mode & {{{ cDefs.S_IRUSR | cDefs.S_IRGRP | cDefs.S_IROTH }}}) >> 2;
// Windows does not report the 'x' permission bit, so propagate read
// bits to execute bits.
mode |= (mode & {{{ cDefs.S_IRUGO }}}) >> 2;
}
return stat.mode;
return mode;
});
},
realPath(node) {
Expand Down Expand Up @@ -133,9 +132,9 @@ addToLibrary({
if (!stat.blocks) {
stat.blocks = (stat.size+stat.blksize-1)/stat.blksize|0;
}
// Node.js on Windows never represents permission bit 'x', so
// propagate read bits to execute bits.
stat.mode |= (stat.mode & {{{ cDefs.S_IRUSR | cDefs.S_IRGRP | cDefs.S_IROTH }}}) >> 2;
// Windows does not report the 'x' permission bit, so propagate read
// bits to execute bits.
stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2;
}
return {
dev: stat.dev,
Expand All @@ -157,7 +156,13 @@ addToLibrary({
var path = NODEFS.realPath(node);
NODEFS.tryFSOperation(() => {
if (attr.mode !== undefined) {
fs.chmodSync(path, attr.mode);
var mode = attr.mode;
if (NODEFS.isWindows) {
// Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR)
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod
mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}};
}
fs.chmodSync(path, mode);
// update the common node structure mode as well
node.mode = attr.mode;
}
Expand Down
17 changes: 15 additions & 2 deletions src/library_noderawfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,22 @@ addToLibrary({
readdir(...args) { return ['.', '..'].concat(fs.readdirSync(...args)); },
unlink(...args) { fs.unlinkSync(...args); },
readlink(...args) { return fs.readlinkSync(...args); },
stat(...args) { return fs.statSync(...args); },
lstat(...args) { return fs.lstatSync(...args); },
stat(path, dontFollow) {
var stat = dontFollow ? fs.lstatSync(path) : fs.statSync(path);
if (NODEFS.isWindows) {
// Windows does not report the 'x' permission bit, so propagate read
// bits to execute bits.
stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2;
}
return stat;
},
chmod(path, mode, dontFollow) {
mode &= {{{ cDefs.S_IALLUGO }}};
if (NODEFS.isWindows) {
// Windows only supports S_IREAD / S_IWRITE (S_IRUSR / S_IWUSR)
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod
mode &= {{{ cDefs.S_IRUSR | cDefs.S_IWUSR }}};
}
if (dontFollow && fs.lstatSync(path).isSymbolicLink()) {
// Node (and indeed linux) does not support chmod on symlinks
// https://nodejs.org/api/fs.html#fslchmodsyncpath-mode
Expand Down
16 changes: 6 additions & 10 deletions src/struct_info.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
{
"file": "sys/stat.h",
"defines": [
"S_IALLUGO",
"S_IWUSR",
"S_IWUGO",
"S_IRUGO",
"S_IRWXUGO",
"S_IXUGO",
"S_IFDIR",
"S_IFREG",
"S_IFMT",
Expand Down Expand Up @@ -470,16 +476,6 @@
"SEEK_SET"
]
},
{
"file": "sys/stat.h",
"defines": [
"S_IALLUGO",
"S_IWUGO",
"S_IRUGO",
"S_IRWXUGO",
"S_IXUGO"
]
},
{
"file": "sys/mman.h",
"defines": [
Expand Down
1 change: 1 addition & 0 deletions src/struct_info_generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@
"S_IRWXUGO": 511,
"S_ISVTX": 512,
"S_IWUGO": 146,
"S_IWUSR": 128,
"S_IXUGO": 73,
"TCFLSH": 21515,
"TCGETA": 21509,
Expand Down
1 change: 1 addition & 0 deletions src/struct_info_generated_wasm64.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@
"S_IRWXUGO": 511,
"S_ISVTX": 512,
"S_IWUGO": 146,
"S_IWUSR": 128,
"S_IXUGO": 73,
"TCFLSH": 21515,
"TCGETA": 21509,
Expand Down
22 changes: 14 additions & 8 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5911,25 +5911,31 @@ def test_sigaction_default(self, signal, exit_code, assert_identical):
assert_returncode=exit_code
)

@no_windows('https://github.com/emscripten-core/emscripten/issues/8882')
@requires_node
@crossplatform
@parameterized({
'': (['-DMEMFS'],),
'nodefs': (['-DNODEFS', '-lnodefs.js'],),
'noderawfs': (['-DNODERAWFS', '-sNODERAWFS'],)
})
def test_unistd_access(self, args):
self.emcc_args += args
nodefs = '-DNODEFS' in args or '-DNODERAWFS' in args
if self.get_setting('WASMFS'):
if '-DNODEFS' in args or '-DNODERAWFS' in args:
if nodefs:
self.skipTest('NODEFS in WasmFS')
self.emcc_args += ['-sFORCE_FILESYSTEM']

# Node.js fs.chmod is nearly no-op on Windows
if '-DNODERAWFS' in args and WINDOWS:
self.skipTest('NODERAWFS on windows')

self.do_run_in_out_file_test('unistd/access.c')
# On windows we have slighly different output because we the same
# level of permissions are not available. For example, on windows
# its not possible have a file that is not readable, but writable.
# We also report all files as executable since there is no x bit
# recorded there.
# See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/chmod-wchmod?view=msvc-170#remarks
if WINDOWS and '-DNODERAWFS' in args:
out_suffix = '.win'
else:
out_suffix = ''
self.do_run_in_out_file_test('unistd/access.c', out_suffix=out_suffix)

def test_unistd_curdir(self):
if self.get_setting('WASMFS'):
Expand Down
45 changes: 17 additions & 28 deletions test/unistd/access.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

Expand All @@ -31,18 +32,10 @@ int main() {
char* files[] = {"readable", "writeable",
"allaccess", "forbidden", "nonexistent", ""};
for (int i = 0; i < sizeof files / sizeof files[0]; i++) {
printf("F_OK(%s): %d\n", files[i], access(files[i], F_OK));
printf("errno: %d\n", errno);
errno = 0;
printf("R_OK(%s): %d\n", files[i], access(files[i], R_OK));
printf("errno: %d\n", errno);
errno = 0;
printf("X_OK(%s): %d\n", files[i], access(files[i], X_OK));
printf("errno: %d\n", errno);
errno = 0;
printf("W_OK(%s): %d\n", files[i], access(files[i], W_OK));
printf("errno: %d\n", errno);
errno = 0;
printf("F_OK('%s'): %s\n", files[i], access(files[i], F_OK) < 0 ? strerror(errno) : "OK");
printf("R_OK('%s'): %s\n", files[i], access(files[i], R_OK) < 0 ? strerror(errno) : "OK");
printf("X_OK('%s'): %s\n", files[i], access(files[i], X_OK) < 0 ? strerror(errno) : "OK");
printf("W_OK('%s'): %s\n", files[i], access(files[i], W_OK) < 0 ? strerror(errno) : "OK");
printf("\n");
}

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

errno = 0;
printf("F_OK(%s): %d\n", "filetorename", access("filetorename", F_OK));
printf("errno: %d\n", errno);
errno = 0;
printf("F_OK(%s): %d\n", "renamedfile", access("renamedfile", F_OK));
printf("errno: %d\n", errno);
printf("F_OK('%s'): %d\n", "filetorename", access("filetorename", F_OK));
printf("F_OK('%s'): %d\n", "renamedfile", access("renamedfile", F_OK));

// Same againt with faccessat
errno = 0;
printf("F_OK(%s): %d\n", "filetorename", faccessat(AT_FDCWD, "filetorename", F_OK, 0));
printf("errno: %d\n", errno);
errno = 0;
printf("F_OK(%s): %d\n", "renamedfile", faccessat(AT_FDCWD, "renamedfile", F_OK, 0));
printf("errno: %d\n", errno);

chmod("fchmodtest", 0666);
printf("F_OK('%s'): %d\n", "filetorename", faccessat(AT_FDCWD, "filetorename", F_OK, 0));
printf("F_OK('%s'): %d\n", "renamedfile", faccessat(AT_FDCWD, "renamedfile", F_OK, 0));

chmod("fchmodtest", S_IRUGO | S_IWUGO);
struct stat fileStats;
stat("fchmodtest", &fileStats);
assert((fileStats.st_mode & 0777) == 0666);
int mode = fileStats.st_mode & 0777;
// Allow S_IXUGO in addtion to S_IWUGO because on windows
// we always report the execute bit.
assert(mode == (S_IRUGO | S_IWUGO) || mode == (S_IRUGO | S_IWUGO | S_IXUGO));

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

stat("writeable", &fileStats);
assert((fileStats.st_mode & 0777) == 0222);
mode = fileStats.st_mode & 0777;
assert(mode == S_IWUGO || mode == (S_IWUGO | S_IXUGO));
#endif

EM_ASM(
Expand Down
84 changes: 28 additions & 56 deletions test/unistd/access.out
Original file line number Diff line number Diff line change
@@ -1,62 +1,34 @@
F_OK(readable): 0
errno: 0
R_OK(readable): 0
errno: 0
X_OK(readable): -1
errno: 2
W_OK(readable): -1
errno: 2
F_OK('readable'): OK
R_OK('readable'): OK
X_OK('readable'): Permission denied
W_OK('readable'): Permission denied

F_OK(writeable): 0
errno: 0
R_OK(writeable): -1
errno: 2
X_OK(writeable): -1
errno: 2
W_OK(writeable): 0
errno: 0
F_OK('writeable'): OK
R_OK('writeable'): Permission denied
X_OK('writeable'): Permission denied
W_OK('writeable'): OK

F_OK(allaccess): 0
errno: 0
R_OK(allaccess): 0
errno: 0
X_OK(allaccess): 0
errno: 0
W_OK(allaccess): 0
errno: 0
F_OK('allaccess'): OK
R_OK('allaccess'): OK
X_OK('allaccess'): OK
W_OK('allaccess'): OK

F_OK(forbidden): 0
errno: 0
R_OK(forbidden): -1
errno: 2
X_OK(forbidden): -1
errno: 2
W_OK(forbidden): -1
errno: 2
F_OK('forbidden'): OK
R_OK('forbidden'): Permission denied
X_OK('forbidden'): Permission denied
W_OK('forbidden'): Permission denied

F_OK(nonexistent): -1
errno: 44
R_OK(nonexistent): -1
errno: 44
X_OK(nonexistent): -1
errno: 44
W_OK(nonexistent): -1
errno: 44
F_OK('nonexistent'): No such file or directory
R_OK('nonexistent'): No such file or directory
X_OK('nonexistent'): No such file or directory
W_OK('nonexistent'): No such file or directory

F_OK(): -1
errno: 44
R_OK(): -1
errno: 44
X_OK(): -1
errno: 44
W_OK(): -1
errno: 44
F_OK(''): No such file or directory
R_OK(''): No such file or directory
X_OK(''): No such file or directory
W_OK(''): No such file or directory

F_OK(filetorename): -1
errno: 44
F_OK(renamedfile): 0
errno: 0
F_OK(filetorename): -1
errno: 44
F_OK(renamedfile): 0
errno: 0
F_OK('filetorename'): -1
F_OK('renamedfile'): 0
F_OK('filetorename'): -1
F_OK('renamedfile'): 0
34 changes: 34 additions & 0 deletions test/unistd/access.win.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
F_OK('readable'): OK
R_OK('readable'): OK
X_OK('readable'): OK
W_OK('readable'): Permission denied

F_OK('writeable'): OK
R_OK('writeable'): OK
X_OK('writeable'): OK
W_OK('writeable'): OK

F_OK('allaccess'): OK
R_OK('allaccess'): OK
X_OK('allaccess'): OK
W_OK('allaccess'): OK

F_OK('forbidden'): OK
R_OK('forbidden'): OK
X_OK('forbidden'): OK
W_OK('forbidden'): Permission denied

F_OK('nonexistent'): No such file or directory
R_OK('nonexistent'): No such file or directory
X_OK('nonexistent'): No such file or directory
W_OK('nonexistent'): No such file or directory

F_OK(''): No such file or directory
R_OK(''): No such file or directory
X_OK(''): No such file or directory
W_OK(''): No such file or directory

F_OK('filetorename'): -1
F_OK('renamedfile'): 0
F_OK('filetorename'): -1
F_OK('renamedfile'): 0

0 comments on commit f1dfaf3

Please sign in to comment.