diff --git a/src/library_nodefs.js b/src/library_nodefs.js index d30c69815c133..68249f5f63ba1 100644 --- a/src/library_nodefs.js +++ b/src/library_nodefs.js @@ -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) { @@ -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, @@ -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; } diff --git a/src/library_noderawfs.js b/src/library_noderawfs.js index d6a112c2c34ec..55b9e97a4c716 100644 --- a/src/library_noderawfs.js +++ b/src/library_noderawfs.js @@ -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 diff --git a/src/struct_info.json b/src/struct_info.json index c6acebfa35290..8f81911b65624 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -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", @@ -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": [ diff --git a/src/struct_info_generated.json b/src/struct_info_generated.json index d1dee03540dd8..5ca6688dac3b1 100644 --- a/src/struct_info_generated.json +++ b/src/struct_info_generated.json @@ -447,6 +447,7 @@ "S_IRWXUGO": 511, "S_ISVTX": 512, "S_IWUGO": 146, + "S_IWUSR": 128, "S_IXUGO": 73, "TCFLSH": 21515, "TCGETA": 21509, diff --git a/src/struct_info_generated_wasm64.json b/src/struct_info_generated_wasm64.json index 0669db9433042..e0b21e0a9b577 100644 --- a/src/struct_info_generated_wasm64.json +++ b/src/struct_info_generated_wasm64.json @@ -447,6 +447,7 @@ "S_IRWXUGO": 511, "S_ISVTX": 512, "S_IWUGO": 146, + "S_IWUSR": 128, "S_IXUGO": 73, "TCFLSH": 21515, "TCGETA": 21509, diff --git a/test/test_core.py b/test/test_core.py index 510bfa6cd2263..144ffb4c0f40f 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -5911,8 +5911,8 @@ 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'],), @@ -5920,16 +5920,22 @@ def test_sigaction_default(self, signal, exit_code, assert_identical): }) 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'): diff --git a/test/unistd/access.c b/test/unistd/access.c index 532b14e1e6a1a..c125c919a892f 100644 --- a/test/unistd/access.c +++ b/test/unistd/access.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -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"); } @@ -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"); @@ -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( diff --git a/test/unistd/access.out b/test/unistd/access.out index 73da0c0543164..43454609d4f2c 100644 --- a/test/unistd/access.out +++ b/test/unistd/access.out @@ -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 diff --git a/test/unistd/access.win.out b/test/unistd/access.win.out new file mode 100644 index 0000000000000..6a8d3a571d841 --- /dev/null +++ b/test/unistd/access.win.out @@ -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