diff --git a/system/lib/wasmfs/backend.h b/system/lib/wasmfs/backend.h index d8e584c01136a..6f2ce1906eb6f 100644 --- a/system/lib/wasmfs/backend.h +++ b/system/lib/wasmfs/backend.h @@ -21,6 +21,14 @@ class Backend { virtual std::shared_ptr createDirectory(mode_t mode) = 0; virtual std::shared_ptr createSymlink(std::string target) = 0; + // Indicates whether this backend relies on WasmFS to resolve paths and + // traverse the directory hierarchy. + // - true (default): WasmFS performs path parsing, symlink resolution, + // and intermediate directory traversal for this backend. + // - false: the backend handles full paths itself (e.g. NODERAWFS), so + // WasmFS should pass paths as-is without interpreting them. + virtual bool requiresPathResolution() { return true; } + virtual ~Backend() = default; }; diff --git a/system/lib/wasmfs/backends/node_backend.cpp b/system/lib/wasmfs/backends/node_backend.cpp index b6b95eeb61b40..39e85a7be7081 100644 --- a/system/lib/wasmfs/backends/node_backend.cpp +++ b/system/lib/wasmfs/backends/node_backend.cpp @@ -23,6 +23,9 @@ class NodeState { int fd = -1; public: + // Base path in the host filesystem for this backend. + // An empty string means "no base path": paths are used as-is without + // prefixing. std::string path; NodeState(std::string path) : path(path) {} @@ -180,6 +183,11 @@ class NodeDirectory : public Directory { private: std::string getChildPath(const std::string& name) { + // If state.path is empty, this backend represents the real root and paths + // should be passed through unchanged. + if (state.path.empty()) { + return name; + } return state.path + '/' + name; } @@ -296,6 +304,13 @@ class NodeBackend : public Backend { std::shared_ptr createSymlink(std::string target) override { WASMFS_UNREACHABLE("TODO: implement NodeBackend::createSymlink"); } + + virtual bool requiresPathResolution() override { + // This backend requires WasmFS to resolve paths only if it has a non-empty + // mountPath. If mountPath is empty (e.g. NODERAWFS), the backend handles + // full paths itself and WasmFS should pass paths through as-is. + return !mountPath.empty(); + } }; // TODO: symlink diff --git a/system/lib/wasmfs/backends/noderawfs_root.cpp b/system/lib/wasmfs/backends/noderawfs_root.cpp index 6ed7af81daf56..c06c7aa0da7e4 100644 --- a/system/lib/wasmfs/backends/noderawfs_root.cpp +++ b/system/lib/wasmfs/backends/noderawfs_root.cpp @@ -6,5 +6,7 @@ #include "emscripten/wasmfs.h" backend_t wasmfs_create_root_dir(void) { - return wasmfs_create_node_backend("."); + // Use an empty string as the backend "mountPath" to indicate that paths are + // passed as-is (i.e. without the "./" prefix). + return wasmfs_create_node_backend(""); } diff --git a/system/lib/wasmfs/paths.cpp b/system/lib/wasmfs/paths.cpp index 3d1fbf4c081e8..13072c833270a 100644 --- a/system/lib/wasmfs/paths.cpp +++ b/system/lib/wasmfs/paths.cpp @@ -66,7 +66,15 @@ ParsedParent doParseParent(std::string_view path, size_t& recursions) { // Empty paths never exist. if (path.empty()) { - return {-ENOENT}; + return -ENOENT; + } + + // For backends that do not require path resolution, WasmFS must not + // interpret or traverse the path (e.g. via getChild). Once such a + // backend is reached, the remaining path is forwarded as a whole, + // and the backend is responsible for resolving it. + if (!curr->getBackend()->requiresPathResolution()) { + return {std::make_pair(std::move(curr), path)}; } // Handle absolute paths. @@ -84,7 +92,7 @@ ParsedParent doParseParent(std::string_view path, // contain a child segment for us to return. The root is its own parent, so we // can handle this by returning (root, "."). if (path.empty()) { - return {std::make_pair(std::move(curr), std::string_view("."))}; + return {std::make_pair(std::move(curr), ".")}; } while (true) { diff --git a/test/test_core.py b/test/test_core.py index c3f4e7fa1ed17..d330bbf2a5be9 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -6151,9 +6151,12 @@ def test_unistd_unlink(self): # Several differences/bugs on non-linux including https://github.com/nodejs/node/issues/18014 # TODO: NODERAWFS in WasmFS - if '-DNODERAWFS' in self.cflags and os.geteuid() == 0: + if '-DNODERAWFS' in self.cflags: # 0 if root user - self.cflags += ['-DSKIP_ACCESS_TESTS'] + if os.geteuid() == 0: + self.cflags += ['-DSKIP_ACCESS_TESTS'] + if self.get_setting('WASMFS'): + self.skipTest('https://github.com/emscripten-core/emscripten/issues/18112') self.do_runf('unistd/unlink.c', 'success') diff --git a/test/test_other.py b/test/test_other.py index f5fab92327817..08450b3a7a7ed 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -9291,12 +9291,19 @@ def test_noderawfs_disables_embedding(self): self.assert_fail(base + ['--preload-file', 'somefile'], expected) self.assert_fail(base + ['--embed-file', 'somefile'], expected) + @crossplatform + @also_with_wasmfs def test_noderawfs_access_abspath(self): create_file('foo', 'bar') create_file('access.c', r''' + #include + #include #include int main(int argc, char** argv) { - return access(argv[1], F_OK); + printf("testing access to %s\n", argv[1]); + int rtn = access(argv[1], F_OK); + assert(rtn == 0); + return 0; } ''') self.do_runf('access.c', cflags=['-sNODERAWFS'], args=[os.path.abspath('foo')]) @@ -13256,6 +13263,8 @@ def test_unistd_chown(self): @wasmfs_all_backends def test_wasmfs_getdents(self): + if self.get_setting('NODERAWFS'): + self.skipTest('test expectations assumes /dev is virtualized') # Run only in WASMFS for now. self.set_setting('FORCE_FILESYSTEM') self.do_run_in_out_file_test('wasmfs/wasmfs_getdents.c') @@ -13844,15 +13853,13 @@ def test_fs_icase(self): @crossplatform @with_all_fs def test_std_filesystem(self): - if self.get_setting('NODERAWFS') and self.get_setting('WASMFS'): - self.skipTest('https://github.com/emscripten-core/emscripten/issues/24830') + if (WINDOWS or MACOS) and self.get_setting('NODERAWFS') and self.get_setting('WASMFS'): + self.skipTest('fails with ENOTEMPTY (Directory not empty) during fs::remove_all') self.do_other_test('test_std_filesystem.cpp') @crossplatform @with_all_fs def test_std_filesystem_tempdir(self): - if self.get_setting('NODERAWFS') and self.get_setting('WASMFS'): - self.skipTest('https://github.com/emscripten-core/emscripten/issues/24830') self.do_other_test('test_std_filesystem_tempdir.cpp', cflags=['-g']) def test_strict_js_closure(self):