From 271b2985480b13a4e2728ac57f09651f1b1d87eb Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc@chromium.org>
Date: Tue, 4 Mar 2025 10:50:39 -0800
Subject: [PATCH] [WasmFS] Create public `wasmfs_mount` function

We already had `wasmfs_unmount` so it seems logical we should have this
one too.

This is designed to replace `wasmfs_create_directory` which has a rather
misleading name.

I have left `wasmfs_create_directory` around for now but is simply
combination of `mkdir` + `mount` (which is essentially what is was
doing already).

This change ends up simplifying `doMkdir` which no longer needs to take
a backend argument.
---
 src/lib/libwasmfs.js               |  2 +-
 system/include/emscripten/wasmfs.h |  8 ++--
 system/lib/wasmfs/js_api.cpp       | 15 -------
 system/lib/wasmfs/syscalls.cpp     | 71 ++++++++++++++++++------------
 tools/link.py                      |  2 +-
 5 files changed, 50 insertions(+), 48 deletions(-)

diff --git a/src/lib/libwasmfs.js b/src/lib/libwasmfs.js
index 63a14510767bd..a665f431b56b8 100644
--- a/src/lib/libwasmfs.js
+++ b/src/lib/libwasmfs.js
@@ -348,7 +348,7 @@ addToLibrary({
       }
 #endif
       var backendPointer = type.createBackend(opts);
-      return FS.handleError(withStackSave(() => __wasmfs_mount(stringToUTF8OnStack(mountpoint), backendPointer)));
+      return FS.handleError(withStackSave(() => _wasmfs_mount(stringToUTF8OnStack(mountpoint), backendPointer)));
     },
     unmount: (mountpoint) => (
       FS.handleError(withStackSave(() => _wasmfs_unmount(stringToUTF8OnStack(mountpoint))))
diff --git a/system/include/emscripten/wasmfs.h b/system/include/emscripten/wasmfs.h
index f7025dc6eb6cb..b3839e26d1dda 100644
--- a/system/include/emscripten/wasmfs.h
+++ b/system/include/emscripten/wasmfs.h
@@ -29,11 +29,13 @@ backend_t wasmfs_get_backend_by_fd(int fd);
 // TODO: Remove this function so that only directories can be mounted.
 int wasmfs_create_file(const char* pathname __attribute__((nonnull)), mode_t mode, backend_t backend);
 
-// Creates a new directory using a specific backend.
-// Returns 0 on success like `mkdir`, or a negative value on error.
-// TODO: Add an alias with wasmfs_mount.
+// Legacy function. This function works like `mkdir` + `wasmfs_mount`.
 int wasmfs_create_directory(const char* path __attribute__((nonnull)), mode_t mode, backend_t backend);
 
+// Mount a backend at a given location in the filesystem
+// `path` must be an existing directory.
+int wasmfs_mount(const char* path __attribute__((nonnull)), backend_t backend);
+
 // Unmounts the directory (Which must be a valid mountpoint) at a specific path.
 // Returns 0 on success, or a negative value on error.
 int wasmfs_unmount(const char* path __attribute__((nonnull)));
diff --git a/system/lib/wasmfs/js_api.cpp b/system/lib/wasmfs/js_api.cpp
index bcf3be01c5c9f..08b605a86d7ab 100644
--- a/system/lib/wasmfs/js_api.cpp
+++ b/system/lib/wasmfs/js_api.cpp
@@ -277,21 +277,6 @@ int _wasmfs_lstat(const char* path, struct stat* statBuf) {
   return __syscall_lstat64((intptr_t)path, (intptr_t)statBuf);
 }
 
-// The legacy JS API requires a mountpoint to already exist, so  WasmFS will
-// attempt to remove the target directory if it exists before replacing it with
-// a mounted directory.
-int _wasmfs_mount(const char* path, ::backend_t created_backend) {
-  int err = __syscall_rmdir((intptr_t)path);
-
-  // The legacy JS API mount requires the directory to already exist, but we
-  // will also allow it to be missing.
-  if (err && err != -ENOENT) {
-    return err;
-  }
-
-  return wasmfs_create_directory(path, 0777, created_backend);
-}
-
 // Helper method that identifies what a path is:
 //   ENOENT - if nothing exists there
 //   EISDIR - if it is a directory
diff --git a/system/lib/wasmfs/syscalls.cpp b/system/lib/wasmfs/syscalls.cpp
index 64932128d19ad..c5de6fcedaceb 100644
--- a/system/lib/wasmfs/syscalls.cpp
+++ b/system/lib/wasmfs/syscalls.cpp
@@ -597,8 +597,7 @@ int __syscall_mknodat(int dirfd, intptr_t path, int mode, int dev) {
                 OpenReturnMode::Nothing);
 }
 
-static int
-doMkdir(path::ParsedParent parsed, int mode, backend_t backend = NullBackend) {
+static int doMkdir(path::ParsedParent parsed, int mode) {
   if (auto err = parsed.getError()) {
     return err;
   }
@@ -624,41 +623,57 @@ doMkdir(path::ParsedParent parsed, int mode, backend_t backend = NullBackend) {
     return -EACCES;
   }
 
-  // By default, the backend that the directory is created in is the same as
-  // the parent directory. However, if a backend is passed as a parameter,
-  // then that backend is used.
-  if (!backend) {
-    backend = parent->getBackend();
+  if (!lockedParent.insertDirectory(childName, mode)) {
+    // TODO Receive a specific error code, and report it here. For now, report
+    //      a generic error.
+    return -EIO;
   }
 
-  if (backend == parent->getBackend()) {
-    if (!lockedParent.insertDirectory(childName, mode)) {
-      // TODO Receive a specific error code, and report it here. For now, report
-      //      a generic error.
-      return -EIO;
-    }
-  } else {
-    auto created = backend->createDirectory(mode);
-    if (!created) {
-      // TODO Receive a specific error code, and report it here. For now, report
-      //      a generic error.
-      return -EIO;
-    }
-    [[maybe_unused]] bool mounted = lockedParent.mountChild(childName, created);
-    assert(mounted);
+  // TODO: Check that the insertion is successful.
+
+  return 0;
+}
+
+int wasmfs_mount(const char* path, backend_t backend) {
+  path::ParsedParent parsed = path::parseParent(path);
+  if (auto err = parsed.getError()) {
+    return err;
   }
+  auto& [parent, childNameView] = parsed.getParentChild();
+  auto lockedParent = parent->locked();
+  std::string childName(childNameView);
 
-  // TODO: Check that the insertion is successful.
+  // Child must exist and must be directory
+  auto child = lockedParent.getChild(childName);
+  if (!child) {
+    return -EEXIST;
+  }
+  if (!child->dynCast<Directory>()) {
+    return -ENOTDIR;
+  }
+
+  auto created = backend->createDirectory(0777);
+  if (!created) {
+    // TODO Receive a specific error code, and report it here. For now, report
+    //      a generic error.
+    return -EIO;
+  }
+  [[maybe_unused]] bool mounted = lockedParent.mountChild(childName, created);
+  assert(mounted);
 
   return 0;
 }
 
-// This function is exposed to users and allows users to specify a particular
-// backend that a directory should be created within.
-int wasmfs_create_directory(char* path, int mode, backend_t backend) {
-  static_assert(std::is_same_v<decltype(doMkdir(0, 0, 0)), int>,
+// Legacy function, use wasmfs_mount instead.
+int wasmfs_create_directory(const char* path, int mode, backend_t backend) {
+  static_assert(std::is_same_v<decltype(doMkdir(0, 0)), int>,
                 "unexpected conversion from result of doMkdir to int");
-  return doMkdir(path::parseParent(path), mode, backend);
+  int rtn = doMkdir(path::parseParent(path), mode);
+  if (rtn != 0) {
+    return rtn;
+  }
+
+  return wasmfs_mount(path, backend);
 }
 
 // TODO: Test this.
diff --git a/tools/link.py b/tools/link.py
index 9440e8908a858..314a3b70895cb 100644
--- a/tools/link.py
+++ b/tools/link.py
@@ -1265,7 +1265,7 @@ def phase_linker_setup(options, linker_args):  # noqa: C901, PLR0912, PLR0915
         'emscripten_builtin_memalign',
         'wasmfs_create_file',
         'wasmfs_unmount',
-        '_wasmfs_mount',
+        'wasmfs_mount',
         '_wasmfs_read_file',
         '_wasmfs_write_file',
         '_wasmfs_open',