Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ sigs = {
_wasmfs_node_insert_directory__sig: 'ipi',
_wasmfs_node_insert_file__sig: 'ipi',
_wasmfs_node_open__sig: 'ipp',
_wasmfs_node_path_get_root__sig: 'ippi',
_wasmfs_node_read__sig: 'iipiip',
_wasmfs_node_readdir__sig: 'ipp',
_wasmfs_node_readlink__sig: 'ippi',
Expand Down
48 changes: 37 additions & 11 deletions src/lib/libwasmfs_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,28 @@
*/

addToLibrary({
$wasmfsNodeIsWindows: !!process.platform.match(/^win/),
#if !ENVIRONMENT_MAY_BE_NODE
_wasmfs_node_readdir: (path_p, vec) => {},
_wasmfs_node_get_mode: (path_p, mode_p) => {},
_wasmfs_node_stat_size: (path_p, size_p) => {},
_wasmfs_node_fstat_size: (fd, size_p) => {},
_wasmfs_node_insert_file: (path_p, mode) => {},
_wasmfs_node_insert_directory: (path_p, mode) => {},
_wasmfs_node_unlink: (path_p) => {},
_wasmfs_node_rmdir: (path_p) => {},
_wasmfs_node_truncate: (path_p, len) => {},
_wasmfs_node_ftruncate: (fd, len) => {},
_wasmfs_node_open: (path_p, mode_p) => {},
_wasmfs_node_rename: (from_path_p, to_path_p) => {},
_wasmfs_node_symlink: (target_path_p, linkpath_path_p) => {},
_wasmfs_node_readlink: (path_p, target_p, bufsize) => {},
_wasmfs_node_close: (fd) => {},
_wasmfs_node_read: (fd, buf_p, len, pos, nread_p) => {},
_wasmfs_node_write: (fd, buf_p, len, pos, nwritten_p) => {},
_wasmfs_node_path_get_root: (path_p, target_p, bufsize) => {},
#else
$wasmfsNodeIsWindows: "!!globalThis.process?.platform.match(/^win/)",
$nodePath: "ENVIRONMENT_IS_NODE ? require('node:path') : undefined",

$wasmfsNodeConvertNodeCode__deps: ['$ERRNO_CODES'],
$wasmfsNodeConvertNodeCode: (e) => {
Expand All @@ -31,7 +52,7 @@ addToLibrary({
if (wasmfsNodeIsWindows) {
// 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;
stat.mode |= (stat.mode & {{{ cDefs.S_IRUGO }}}) >> 2;
}
return stat;
},
Expand Down Expand Up @@ -157,15 +178,15 @@ addToLibrary({
},

_wasmfs_node_truncate__i53abi: true,
_wasmfs_node_truncate__deps : ['$wasmfsTry'],
_wasmfs_node_truncate : (path_p, len) => {
_wasmfs_node_truncate__deps: ['$wasmfsTry'],
_wasmfs_node_truncate: (path_p, len) => {
if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}};
return wasmfsTry(() => fs.truncateSync(UTF8ToString(path_p), len));
},

_wasmfs_node_ftruncate__i53abi: true,
_wasmfs_node_ftruncate__deps : ['$wasmfsTry'],
_wasmfs_node_ftruncate : (fd, len) => {
_wasmfs_node_ftruncate__deps: ['$wasmfsTry'],
_wasmfs_node_ftruncate: (fd, len) => {
if (isNaN(len)) return -{{{ cDefs.EOVERFLOW }}};
return wasmfsTry(() => fs.ftruncateSync(fd, len));
},
Expand Down Expand Up @@ -193,7 +214,7 @@ addToLibrary({
});
},

_wasmfs_node_close__deps: [],
_wasmfs_node_close__deps: ['$wasmfsTry'],
_wasmfs_node_close: (fd) => {
return wasmfsTry(() => {
fs.closeSync(fd);
Expand All @@ -206,21 +227,26 @@ addToLibrary({
return wasmfsTry(() => {
// TODO: Cache open file descriptors to guarantee that opened files will
// still exist when we try to access them.
let nread = fs.readSync(fd, new Int8Array(HEAPU8.buffer, buf_p, len), 0, len, pos);
let nread = fs.readSync(fd, HEAPU8, buf_p, len, pos);
{{{ makeSetValue('nread_p', 0, 'nread', 'i32') }}};
// implicitly return 0
});
},

_wasmfs_node_write__deps : ['$wasmfsTry'],
_wasmfs_node_write : (fd, buf_p, len, pos, nwritten_p) => {
_wasmfs_node_write__deps: ['$wasmfsTry'],
_wasmfs_node_write: (fd, buf_p, len, pos, nwritten_p) => {
return wasmfsTry(() => {
// TODO: Cache open file descriptors to guarantee that opened files will
// still exist when we try to access them.
let nwritten = fs.writeSync(fd, new Int8Array(HEAPU8.buffer, buf_p, len), 0, len, pos);
let nwritten = fs.writeSync(fd, HEAPU8, buf_p, len, pos);
{{{ makeSetValue('nwritten_p', 0, 'nwritten', 'i32') }}};
// implicitly return 0
});
},

_wasmfs_node_path_get_root__deps: ['$nodePath'],
_wasmfs_node_path_get_root: (path_p, target_p, bufsize) => {
return stringToUTF8(nodePath.parse(UTF8ToString(path_p)).root, target_p, bufsize);
},
#endif
});
7 changes: 6 additions & 1 deletion system/include/emscripten/wasmfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ backend_t wasmfs_create_memory_backend(void);
//
backend_t wasmfs_create_fetch_backend(const char* _Nonnull base_url, uint32_t chunk_size);

backend_t wasmfs_create_node_backend(const char* _Nonnull root);
backend_t wasmfs_create_node_backend(const char* _Nonnull mount_path);

// Note: this cannot be called on the browser main thread because it might
// deadlock while waiting for the OPFS dedicated worker thread to be spawned.
Expand Down Expand Up @@ -111,6 +111,11 @@ void wasmfs_flush(void);
// default backend is used.
backend_t wasmfs_create_root_dir(void);

// A hook users can do to create the working directory. Overriding this allows
// the user to set a particular backend as the CWD. If this is not set then the
// root_backend is used.
backend_t wasmfs_create_working_dir(backend_t root_backend);

// A hook users can do to run code during WasmFS startup. This hook happens
// before file preloading, so user code could create backends and mount them,
// which would then affect in which backend the preloaded files are loaded (the
Expand Down
4 changes: 4 additions & 0 deletions system/lib/wasmfs/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class Backend {
virtual std::shared_ptr<Directory> createDirectory(mode_t mode) = 0;
virtual std::shared_ptr<Symlink> createSymlink(std::string target) = 0;

virtual std::string getRootPath(std::string_view path) {
return path.front() == '/' ? "/" : "";
}

virtual ~Backend() = default;
};

Expand Down
17 changes: 14 additions & 3 deletions system/lib/wasmfs/backends/node_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ class NodeDirectory : public Directory {
if (_wasmfs_node_get_mode(childPath.c_str(), &mode)) {
return nullptr;
}
if (S_ISREG(mode)) {
// Allow reading from character device files too (e.g. `/dev/random`,
// `/dev/urandom`)
if (S_ISREG(mode) || S_ISCHR(mode)) {
return std::make_shared<NodeFile>(mode, getBackend(), childPath);
} else if (S_ISDIR(mode)) {
return std::make_shared<NodeDirectory>(mode, getBackend(), childPath);
Expand Down Expand Up @@ -294,14 +296,23 @@ class NodeBackend : public Backend {
std::shared_ptr<Symlink> createSymlink(std::string target) override {
WASMFS_UNREACHABLE("TODO: implement NodeBackend::createSymlink");
}

std::string getRootPath(std::string_view path) override {
char buf[PATH_MAX];
auto pathStr = std::string(path);
if (_wasmfs_node_path_get_root(pathStr.c_str(), buf, PATH_MAX) < 0) {
WASMFS_UNREACHABLE("getRootPath cannot fail");
}
return std::string(buf);
}
};

// TODO: symlink

extern "C" {

backend_t wasmfs_create_node_backend(const char* root) {
return wasmFS.addBackend(std::make_unique<NodeBackend>(root));
backend_t wasmfs_create_node_backend(const char* mount_path) {
return wasmFS.addBackend(std::make_unique<NodeBackend>(mount_path));
}

void EMSCRIPTEN_KEEPALIVE _wasmfs_node_record_dirent(
Expand Down
2 changes: 2 additions & 0 deletions system/lib/wasmfs/backends/node_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ int _wasmfs_node_read(
int _wasmfs_node_write(
int fd, const void* buf, uint32_t len, uint32_t pos, uint32_t* nwritten);

int _wasmfs_node_path_get_root(const char* path, const char *buf, int bufsize);

} // extern "C"
4 changes: 4 additions & 0 deletions system/lib/wasmfs/backends/noderawfs_root.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
#include "emscripten/wasmfs.h"

backend_t wasmfs_create_root_dir(void) {
return wasmfs_create_node_backend("");
}

backend_t wasmfs_create_working_dir(backend_t root_backend) {
return wasmfs_create_node_backend(".");
}
6 changes: 4 additions & 2 deletions system/lib/wasmfs/paths.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ ParsedParent doParseParent(std::string_view path,
return {-ENOENT};
}

auto rootPath = curr->getBackend()->getRootPath(path);

// Handle absolute paths.
if (path.front() == '/') {
if (!rootPath.empty()) {
curr = wasmFS.getRootDirectory();
path.remove_prefix(1);
path.remove_prefix(rootPath.size());
}

// Ignore trailing '/'.
Expand Down
29 changes: 26 additions & 3 deletions system/lib/wasmfs/wasmfs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ __attribute__((init_priority(100))) WasmFS wasmFS;
__attribute__((weak)) extern "C" void wasmfs_before_preload(void) {}

// Set up global data structures and preload files.
WasmFS::WasmFS() : rootDirectory(initRootDirectory()), cwd(rootDirectory) {
WasmFS::WasmFS()
: rootDirectory(initRootDirectory()), cwd(initCWD(rootDirectory)) {
wasmfs_before_preload();
preloadFiles();
}
Expand Down Expand Up @@ -87,8 +88,8 @@ WasmFS::~WasmFS() {
rootDirectory->locked().setParent(nullptr);
}

// Special backends that want to install themselves as the root use this hook.
// Otherwise, we use the default backends.
// Hooks for backends that want to install themselves as the root or CWD.
// Otherwise, we use the default memory backends.
__attribute__((weak)) extern "C" backend_t wasmfs_create_root_dir(void) {
#ifdef WASMFS_CASE_INSENSITIVE
return createIgnoreCaseBackend([]() { return createMemoryBackend(); });
Expand All @@ -97,6 +98,11 @@ __attribute__((weak)) extern "C" backend_t wasmfs_create_root_dir(void) {
#endif
}

__attribute__((weak)) extern "C" backend_t
wasmfs_create_working_dir(backend_t root_backend) {
return root_backend;
}

std::shared_ptr<Directory> WasmFS::initRootDirectory() {
auto rootBackend = wasmfs_create_root_dir();
auto rootDirectory =
Expand Down Expand Up @@ -126,6 +132,23 @@ std::shared_ptr<Directory> WasmFS::initRootDirectory() {
return rootDirectory;
}

std::shared_ptr<Directory>
WasmFS::initCWD(const std::shared_ptr<Directory>& root) {
auto rootBackend = root->getBackend();
auto cwdBackend = wasmfs_create_working_dir(rootBackend);
if (rootBackend == cwdBackend) {
return root;
}

auto cwd = cwdBackend->createDirectory(S_IRUGO | S_IXUGO | S_IWUGO);
auto lockedCwd = cwd->locked();

// The root directory is the parent of the CWD.
lockedCwd.setParent(root);

return cwd;
}

// Initialize files specified by the --preload-file option.
// Set up directories and files in wasmFS$preloadedDirs and
// wasmFS$preloadedFiles from JS. This function will be called before any file
Expand Down
3 changes: 3 additions & 0 deletions system/lib/wasmfs/wasmfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class WasmFS {
// dev/stderr. Refers to the same std streams in the open file table.
std::shared_ptr<Directory> initRootDirectory();

// Private method to initialize CWD once.
std::shared_ptr<Directory> initCWD(const std::shared_ptr<Directory>& root);

// Initialize files specified by --preload-file option.
void preloadFiles();

Expand Down
6 changes: 4 additions & 2 deletions test/fs/test_fs_dev_random.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ int main() {
int nread;

fp = fopen("/dev/random", "r");
nread = fread(&data, 1, byte_count, fp);
assert(fp != NULL);
nread = fread(data, 1, byte_count, fp);
assert(nread == byte_count);
fclose(fp);

fp = fopen("/dev/urandom", "r");
nread = fread(&data, 1, byte_count, fp);
assert(fp != NULL);
nread = fread(data, 1, byte_count, fp);
assert(nread == byte_count);
fclose(fp);

Expand Down
7 changes: 5 additions & 2 deletions test/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6149,9 +6149,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')

Expand Down
Loading
Loading