diff --git a/src/library_fs.js b/src/library_fs.js index 575bf8feaac6b..717d466a00191 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -192,8 +192,18 @@ FS.staticInit(); break; } - current = FS.lookupNode(current, parts[i]); current_path = PATH.join2(current_path, parts[i]); + try { + current = FS.lookupNode(current, parts[i]); + } catch (e) { + // if noent_okay is true, suppress a ENOENT in the last component + // and return an object with an undefined node. This is needed for + // resolving symlinks in the path when creating a file. + if ((e?.errno === {{{ cDefs.ENOENT }}}) && islast && opts.noent_okay) { + return { path: current_path }; + } + throw e; + } // jump to the mount's root node if this is a mountpoint if (FS.isMountpoint(current) && (!islast || opts.follow_mount)) { @@ -1034,14 +1044,15 @@ FS.staticInit(); node = path; } else { path = PATH.normalize(path); - try { - var lookup = FS.lookupPath(path, { - follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}) - }); - node = lookup.node; - } catch (e) { - // ignore - } + // noent_okay makes it so that if the final component of the path + // doesn't exist, lookupPath returns `node: undefined`. `path` will be + // updated to point to the target of all symlinks. + var lookup = FS.lookupPath(path, { + follow: !(flags & {{{ cDefs.O_NOFOLLOW }}}), + noent_okay: true + }); + node = lookup.node; + path = lookup.path; } // perhaps we need to create the node var created = false; diff --git a/test/test_core.py b/test/test_core.py index 7511815c00a8e..95697d1e9aaa4 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -6105,6 +6105,10 @@ def test_unistd_links(self, args, nodefs): self.do_run_in_out_file_test('unistd/links.c', emcc_args=args) + @also_with_noderawfs + def test_unistd_write_broken_link(self): + self.do_run_in_out_file_test('unistd/test_unistd_write_broken_link.c') + @no_windows('Skipping NODEFS test, since it would require administrative privileges.') @requires_node def test_unistd_symlink_on_nodefs(self): diff --git a/test/unistd/test_unistd_write_broken_link.c b/test/unistd/test_unistd_write_broken_link.c new file mode 100644 index 0000000000000..1c119a6e45a7e --- /dev/null +++ b/test/unistd/test_unistd_write_broken_link.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include +#include + + +int main() { + int res = symlink("link_target", "link_source"); + printf("link result: %d\n", res); + int src_fd = open("link_source", O_CREAT | O_WRONLY, 0777); + printf("source_fd: %d, errno: %d %s\n", src_fd, errno, strerror(errno)); + write(src_fd, "abc", 3); + close(src_fd); + { + int target_fd = open("link_target", O_RDONLY); + printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno)); + char buf[10]; + read(target_fd, buf, 10); + printf("buf: '%s'\n", buf); + close(target_fd); + } + { + int target_fd = open("link_source", O_RDONLY); + printf("target_fd: %d, errno: %d %s\n", target_fd, errno, strerror(errno)); + char buf[10]; + read(target_fd, buf, 10); + printf("buf: '%s'\n", buf); + close(target_fd); + } +} diff --git a/test/unistd/test_unistd_write_broken_link.out b/test/unistd/test_unistd_write_broken_link.out new file mode 100644 index 0000000000000..6f5a9e3ea3183 --- /dev/null +++ b/test/unistd/test_unistd_write_broken_link.out @@ -0,0 +1,6 @@ +link result: 0 +source_fd: 3, errno: 0 No error information +target_fd: 3, errno: 0 No error information +buf: 'abc' +target_fd: 3, errno: 0 No error information +buf: 'abc' diff --git a/test/wasmfs/wasmfs_open.c b/test/wasmfs/wasmfs_open.c index eb8454f1ccd0a..ac95cbd066746 100644 --- a/test/wasmfs/wasmfs_open.c +++ b/test/wasmfs/wasmfs_open.c @@ -89,11 +89,7 @@ int main() { int fd5 = open("/dev/stdout/foo", O_RDWR); printf("Errno: %s\n", strerror(errno)); // Both errors are valid, but in WasmFS, ENOTDIR is returned first. -#ifdef WASMFS assert(errno == ENOTDIR); -#else - assert(errno == ENOENT); -#endif errno = 0; // Attempt to open and write to the root directory.