From a7630d8e5e25e9ea9732f2e38c7867cbc07544d4 Mon Sep 17 00:00:00 2001 From: Richard Stotz Date: Fri, 6 Aug 2021 15:16:18 +0200 Subject: [PATCH 1/3] Initial commit --- .gitignore | 6 + pthreadfs/README.md | 106 + pthreadfs/examples/Makefile | 126 + .../examples/emscripten-tests/access.cpp | 58 + .../examples/emscripten-tests/access.out | 58 + pthreadfs/examples/emscripten-tests/close.cpp | 48 + pthreadfs/examples/emscripten-tests/close.out | 8 + .../examples/emscripten-tests/curdir.cpp | 114 + .../examples/emscripten-tests/curdir.out | 32 + pthreadfs/examples/emscripten-tests/dup.cpp | 52 + pthreadfs/examples/emscripten-tests/dup.out | 20 + pthreadfs/examples/emscripten-tests/fsafs.cpp | 47 + pthreadfs/examples/emscripten-tests/io.cpp | 219 + pthreadfs/examples/emscripten-tests/io.out | 85 + .../examples/emscripten-tests/isatty.cpp | 37 + pthreadfs/examples/emscripten-tests/links.cpp | 59 + pthreadfs/examples/emscripten-tests/links.out | 31 + pthreadfs/examples/emscripten-tests/misc.cpp | 213 + pthreadfs/examples/emscripten-tests/misc.out | 52 + .../examples/emscripten-tests/truncate.cpp | 96 + .../examples/emscripten-tests/truncate.out | 25 + .../sqlite-speedtest-prejs.js | 6 + pthreadfs/library_pthreadfs.js | 3538 +++++++++++++++++ pthreadfs/pthreadfs.cpp | 366 ++ pthreadfs/pthreadfs.h | 265 ++ pthreadfs/src/Makefile | 14 + pthreadfs/src/js/library_asyncfs.js | 1582 ++++++++ pthreadfs/src/js/library_fsafs.js | 292 ++ pthreadfs/src/js/library_memfs_async.js | 389 ++ pthreadfs/src/js/library_sfafs_async.js | 424 ++ pthreadfs/src/js/library_syscall_async.js | 935 +++++ pthreadfs/src/js/library_tty_async.js | 160 + pthreadfs/src/js/pthreadfs.js | 184 + pthreadfs/src/pthreadfs.cpp | 366 ++ pthreadfs/src/pthreadfs.h | 265 ++ 35 files changed, 10278 insertions(+) create mode 100644 pthreadfs/README.md create mode 100644 pthreadfs/examples/Makefile create mode 100644 pthreadfs/examples/emscripten-tests/access.cpp create mode 100644 pthreadfs/examples/emscripten-tests/access.out create mode 100644 pthreadfs/examples/emscripten-tests/close.cpp create mode 100644 pthreadfs/examples/emscripten-tests/close.out create mode 100644 pthreadfs/examples/emscripten-tests/curdir.cpp create mode 100644 pthreadfs/examples/emscripten-tests/curdir.out create mode 100644 pthreadfs/examples/emscripten-tests/dup.cpp create mode 100644 pthreadfs/examples/emscripten-tests/dup.out create mode 100644 pthreadfs/examples/emscripten-tests/fsafs.cpp create mode 100644 pthreadfs/examples/emscripten-tests/io.cpp create mode 100644 pthreadfs/examples/emscripten-tests/io.out create mode 100644 pthreadfs/examples/emscripten-tests/isatty.cpp create mode 100644 pthreadfs/examples/emscripten-tests/links.cpp create mode 100644 pthreadfs/examples/emscripten-tests/links.out create mode 100644 pthreadfs/examples/emscripten-tests/misc.cpp create mode 100644 pthreadfs/examples/emscripten-tests/misc.out create mode 100644 pthreadfs/examples/emscripten-tests/truncate.cpp create mode 100644 pthreadfs/examples/emscripten-tests/truncate.out create mode 100644 pthreadfs/examples/sqlite-speedtest/sqlite-speedtest-prejs.js create mode 100644 pthreadfs/library_pthreadfs.js create mode 100644 pthreadfs/pthreadfs.cpp create mode 100644 pthreadfs/pthreadfs.h create mode 100644 pthreadfs/src/Makefile create mode 100644 pthreadfs/src/js/library_asyncfs.js create mode 100644 pthreadfs/src/js/library_fsafs.js create mode 100644 pthreadfs/src/js/library_memfs_async.js create mode 100644 pthreadfs/src/js/library_sfafs_async.js create mode 100644 pthreadfs/src/js/library_syscall_async.js create mode 100644 pthreadfs/src/js/library_tty_async.js create mode 100644 pthreadfs/src/js/pthreadfs.js create mode 100644 pthreadfs/src/pthreadfs.cpp create mode 100644 pthreadfs/src/pthreadfs.h diff --git a/.gitignore b/.gitignore index 0f0da2a9a54c..5005132edc13 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,9 @@ htmlcov/ coverage.xml .DS_Store + +# PThreadfs +pthreadfs/examples/cache +pthreadfs/examples/dist +pthreadfs/examples/out +pthreadfs/examples/sqlite-src diff --git a/pthreadfs/README.md b/pthreadfs/README.md new file mode 100644 index 000000000000..ef786fcc1779 --- /dev/null +++ b/pthreadfs/README.md @@ -0,0 +1,106 @@ +# PThreadFS + +The Emscripten Pthread File System (PThreadFS) unlocks using (partly) asynchronous storage APIs such as [OPFS Access Handles](https://docs.google.com/document/d/121OZpRk7bKSF7qU3kQLqAEUVSNxqREnE98malHYwWec/edit#heading=h.gj2fudnvy982) through the Emscripten File System API. This enables C++ applications compiled through Emscripten to use persistent storage without using [Asyncify](https://emscripten.org/docs/porting/asyncify.html). PThreadFS requires only minimal modifications to the C++ code and nearly achieves feature parity with the Emscripten's classic File System API. + +PThreadFS is based on Emscripten’s [SyncToAsync prototype](https://github.com/emscripten-core/emscripten/pull/13298). All file system operations are proxied through a dedicated worker thread. Although the underlying storage API is asynchronous, PThreadFS makes it appear synchronous to the C++ application. + +The code is still prototype quality and **should not be used in a production environment**. + +## Enable and detect OPFS in Chrome + +TODO + +## Getting the code + +PthreadFS is available on Github in the [emscripten-pthreadfs](https://github.com/rstz/emscripten-pthreadfs) repository. All code resides in the `pthreadfs` folder. It should be usable with any up-to-date Emscripten version. + +There is **no need** to use a fork of Emscripten itself since all code operates in user-space. + +## Using PThreadFS in a project + +In order to use the code in a new project, you only need the three files in the `pthreadfs` folder: `pthreadfs_library.js`, `pthreadfs.cpp` and `pthreadfs.h`. The files are included as follows: + +### Code changes + +- Include `pthreadfs.h` in the C++ file containing `main()`. +- Call `emscripten_init_pthreadfs();` at the top of `main()` (or before any file system syscalls). +- Files that persist between sessions must be stored in `/filesystemaccess/` or its subfolders. + +### Build process changes + +- Compile `pthreadfs.h` and `pthreadfs.cpp` and link the resulting object to your application. +- Include the PThreadFS Javascript code by adding the following in the linking step: +``` +--js-library=library_pthreadsfs.js +``` +- Enable `PROXY_TO_PTHREAD` by adding the following to the linking step: +``` +-s PROXY_TO_PTHREAD +``` + +### Advanced Usage + +If you want to modify the PThreadFS file system directly, you may use the macro `EM_PTHREADFS_ASM()` defined in `pthreadfs.h`. The macro allows you to run asynchrononous Javascript on the Pthread hosting the PThreadFS file system. For example, you may create a folder in the virtual file system by calling +``` +EM_PTHREADFS_ASM( + await PThreadFS.mkdir('mydirectory'); +); +``` +See `pthreadfs/examples/emscripten-tests/` for exemplary usage. + + +## Known Limitations + +- The code is still prototype quality and **should not be used in a production environment**. +- PThreadFS requires PROXY_TO_PTHREAD to be active. + + In particular, no system calls interacting with the file system should be called from the main thread. +- Some functionality of the Emscripten File System API is missing, such as sockets, IndexedDB integration and support for XHRequests. +- PThreadFS depends on C++ libraries. `EM_PTRHEADFS_ASM()` cannot be used within C files (although initializing through `emscripten_init_pthreadfs()` is possible, see the `pthreadfs/examples/sqlite-speedtest` for an example). +- Only in-memory storage (MEMFS) and OPFS Access Handles (FSAFS) are available as backends for PThreadFS. + + In particualar, there is no support (yet) for IDBFS. Limited support is available for the Storage Foundation API. +- Performance is good if and only if full optimizations (compiler option `-O3`) are enabled and DevTools are closed. +- Using stdout from C++ only prints to the Javascript console, not the Emscripten-generated html file. + + +## Examples + +The examples are provided to show how projects can be transformed to use PThreadFS. To build them, navigate to the `pthreadfs/examples/` folder and run `make all`. You need to have the [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) activated for the build process to succeed. + +### SQLite Speedtest + +This example shows how to compile and run the [speedtest1](https://www.sqlite.org/cpu.html) from the SQLite project in the browser. + +The Makefile downloads the source of the speedtest and sqlite3 directly from . + +To compile, navigate to the `pthreadfs/examples/` directory and run + +```shell +make sqlite-speedtest +cd dist/sqlite-speedtest +python3 -m http.server 8888 +``` +Then open the following link in a Chrome instance with the +"File System Access Access Handles" enabled (see above): +[localhost:8888/sqlite-speedtest](http://localhost:8888/sqlite-speedtest). The results of the speedtest can be found in the DevTools console. + +### Other tests + +The folder `pthreadfs/examples/emscripten-tests` contains a number of other file system tests taken from Emscripten's standard test suite. + +To compile, navigate to the `pthreadfs/examples/` directory and run + +```shell +make emscripten-tests +cd dist/emscripten-tests +python3 -m http.server 8888 +``` +Then open the following link in a Chrome instance with the +"File System Access Access Handles" enabled (see above): +[localhost:8888/emscripten-tests](http://localhost:8888/emscripten-tests) and choose a test. The results of the test can be found in the DevTools console. + +## Authors +- Richard Stotz () + +This is not an official Google product. \ No newline at end of file diff --git a/pthreadfs/examples/Makefile b/pthreadfs/examples/Makefile new file mode 100644 index 000000000000..0b8a9a89f939 --- /dev/null +++ b/pthreadfs/examples/Makefile @@ -0,0 +1,126 @@ +# Emscripten +EMCC = emcc + +# Define some folders +EMTESTS = emscripten-tests +SQLITETESTS = sqlite-speedtest +OBJ = out/bc + +PTHREADFS_JS = ../library_pthreadfs.js +PTHREADFS_H = ../pthreadfs.h +PTHREADFS_CPP = ../pthreadfs.cpp + +# I got this handy makefile syntax from : https://github.com/mandel59/sqlite-wasm (MIT License) Credited in LICENSE +# To use another version of Sqlite, visit https://www.sqlite.org/download.html and copy the appropriate values here: +SQLITE_AMALGAMATION = sqlite-amalgamation-3350000 +SQLITE_AMALGAMATION_ZIP_URL = https://www.sqlite.org/2021/sqlite-amalgamation-3350000.zip +SQLITE_AMALGAMATION_ZIP_SHA1 = ba64bad885c9f51df765a9624700747e7bf21b79 +SQLITE_H = sqlite3.h +SQLITE_SPEEDTEST = speedtest1.c +SQLITE_SPEEDTEST_URL = https://sqlite.org/src/raw/5e5b805f24cc939656058f6a498f5a2160f9142e4815c54faf758ec798d4cdad?at=speedtest1.c +SQLITE_SPEEDTEST_SHA1 = f0edde2ad68f090e4676ac30042e0f6b765a8528 + +## cache +cache/$(SQLITE_AMALGAMATION).zip: + mkdir -p cache + curl -LsSf '$(SQLITE_AMALGAMATION_ZIP_URL)' -o $@ + +cache/$(SQLITE_SPEEDTEST): + mkdir -p cache + curl -LsSf '$(SQLITE_SPEEDTEST_URL)' -o $@ + + +## sqlite-src +.PHONY: sqlite-src +sqlite-src: sqlite-src/$(SQLITE_AMALGAMATION) sqlite-src/$(SQLITE_SPEEDTEST) + +sqlite-src/$(SQLITE_AMALGAMATION): cache/$(SQLITE_AMALGAMATION).zip sqlite-src/$(SQLITE_AMALGAMATION)/$(EXTENSION_FUNCTIONS) + mkdir -p sqlite-src/$(SQLITE_AMALGAMATION) + echo '$(SQLITE_AMALGAMATION_ZIP_SHA1) ./cache/$(SQLITE_AMALGAMATION).zip' > cache/check.txt + shasum -c cache/check.txt + # We don't delete the sqlite_amalgamation folder. That's a job for clean + # Also, the extension functions get copied here, and if we get the order of these steps wrong, + # this step could remove the extension functions, and that's not what we want + unzip -u 'cache/$(SQLITE_AMALGAMATION).zip' -d sqlite-src/ + touch $@ + +sqlite-src/$(SQLITE_AMALGAMATION)/$(SQLITE_H): sqlite-src/$(SQLITE_AMALGAMATION) + +sqlite-src/$(SQLITE_SPEEDTEST): cache/$(SQLITE_SPEEDTEST) + mkdir -p sqlite-src/$(SQLITE_AMALGAMATION) + echo '$(SQLITE_SPEEDTEST_SHA1) ./cache/$(SQLITE_SPEEDTEST)' > cache/check.txt + shasum -c cache/check.txt + cp 'cache/$(SQLITE_SPEEDTEST)' $@ + echo 'extern void emscripten_init_pthreadfs();' > includeline.tmp + mv $@ input.tmp + cat includeline.tmp input.tmp > $@ + rm input.tmp includeline.tmp + sed -i '' -e '/int main(/a\'$$'\n''emscripten_init_pthreadfs();' $@ + + +# build options +OPTIMIZATION_LEVEL = O3 + +CFLAGS = \ + -$(OPTIMIZATION_LEVEL) \ + -Wall \ + -pthread \ + -I.. + +CFLAGS_SQLITE = \ + $(CFLAGS) \ + -DSQLITE_ENABLE_MEMSYS5 \ + -D_HAVE_SQLITE_CONFIG_H \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_DISABLE_LFS \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_ENABLE_NORMALIZE \ + -Isqlite-src/$(SQLITE_AMALGAMATION)/ + +LINK_FLAGS = \ + -pthread \ + -s PROXY_TO_PTHREAD \ + -s INITIAL_MEMORY=134217728 + +.PHONY: all +all: sqlite-speedtest emscripten-tests + +.PHONY: clean +clean: + rm -rf dist/* + rm -rf out/ + rm -f cache/* + rm -rf sqlite-src/ + +$(OBJ)/sqlite3.o: sqlite-src/$(SQLITE_AMALGAMATION) + mkdir -p $(OBJ) + $(EMCC) $(CFLAGS) -c sqlite-src/$(SQLITE_AMALGAMATION)/sqlite3.c -o $@ + +$(OBJ)/speedtest1.o: sqlite-src/$(SQLITE_SPEEDTEST) sqlite-src/$(SQLITE_AMALGAMATION) + mkdir -p $(OBJ) + $(EMCC) $(CFLAGS) -Isqlite-src/$(SQLITE_AMALGAMATION)/ -c $< -o $@ + +$(OBJ)/pthreadfs.o : $(PTHREADFS_CPP) + mkdir -p $(OBJ) + $(EMCC) -c $(CFLAGS) $< -o $@ + +$(OBJ)/%.o : $(EMTESTS)/%.cpp + mkdir -p $(OBJ) + $(EMCC) -c $(CFLAGS) $< -o $@ + +# Don't delete my precious object files +.PRECIOUS: $(OBJ)/%.out + +.PHONY: sqlite-speedtest +sqlite-speedtest: dist/sqlite-speedtest/index.html +dist/sqlite-speedtest/index.html: $(OBJ)/speedtest1.o $(OBJ)/sqlite3.o $(OBJ)/pthreadfs.o $(PTHREADFS_JS) $(SQLITETESTS)/sqlite-speedtest-prejs.js + mkdir -p dist/sqlite-speedtest + $(EMCC) $(LINK_FLAGS) --pre-js=$(SQLITETESTS)/sqlite-speedtest-prejs.js --js-library=$(PTHREADFS_JS) $< $(word 2,$^) $(word 3,$^) -o $@ + +.PHONY: emscripten-tests +emscripten-tests: $(addprefix dist/, $(addsuffix .html, $(basename $(wildcard $(EMTESTS)/*.cpp)))) + @echo 'building emscripten-tests' $? + +dist/$(EMTESTS)/%.html : $(OBJ)/%.o $(OBJ)/pthreadfs.o $(PTHREADFS_JS) + mkdir -p dist/$(EMTESTS) + $(EMCC) $(LINK_FLAGS) --js-library=$(PTHREADFS_JS) $< $(word 2,$^) -o $@ \ No newline at end of file diff --git a/pthreadfs/examples/emscripten-tests/access.cpp b/pthreadfs/examples/emscripten-tests/access.cpp new file mode 100644 index 000000000000..cad6dd9b75bb --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/access.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + EM_PTHREADFS_ASM( + await PThreadFS.mkdir('working'); + await PThreadFS.chdir('working'); + await PThreadFS.writeFile('forbidden', ""); await PThreadFS.chmod('forbidden', 0o000); + await PThreadFS.writeFile('readable', ""); await PThreadFS.chmod('readable', 0o444); + await PThreadFS.writeFile('writeable', ""); await PThreadFS.chmod('writeable', 0o222); + await PThreadFS.writeFile('allaccess', ""); await PThreadFS.chmod('allaccess', 0o777); + ); + // Empty path checks #9136 fix + 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("\n"); + } + + EM_PTHREADFS_ASM( + await PThreadFS.writeFile('filetorename', 'renametest'); + ); + + rename("filetorename", "renamedfile"); + + 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); + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/access.out b/pthreadfs/examples/emscripten-tests/access.out new file mode 100644 index 000000000000..c5f0c24b9be4 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/access.out @@ -0,0 +1,58 @@ +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(writeable): 0 +errno: 0 +R_OK(writeable): -1 +errno: 2 +X_OK(writeable): -1 +errno: 2 +W_OK(writeable): 0 +errno: 0 + +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(forbidden): 0 +errno: 0 +R_OK(forbidden): -1 +errno: 2 +X_OK(forbidden): -1 +errno: 2 +W_OK(forbidden): -1 +errno: 2 + +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(): -1 +errno: 44 +R_OK(): -1 +errno: 44 +X_OK(): -1 +errno: 44 +W_OK(): -1 +errno: 44 + +F_OK(filetorename): -1 +errno: 44 +F_OK(renamedfile): 0 +errno: 0 diff --git a/pthreadfs/examples/emscripten-tests/close.cpp b/pthreadfs/examples/emscripten-tests/close.cpp new file mode 100644 index 000000000000..0c252ae0a8d3 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/close.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + int f = open(".", O_RDONLY); + + int ret = fsync(f); + printf("fsync(opened): %d\n", ret); + printf("errno: %d\n", errno); + assert(ret == 0); + assert(errno == 0); + errno = 0; + + ret = close(f); + printf("close(opened): %d\n", ret); + printf("errno: %d\n", errno); + assert(ret == 0); + assert(errno == 0); + errno = 0; + + ret = fsync(f); + printf("fsync(closed): %d\n", ret); + printf("errno: %d\n", errno); + assert(ret == -1); + assert(errno == EBADF); + errno = 0; + + ret = close(f); + printf("close(closed): %d\n", ret); + printf("errno: %d\n", errno); + assert(ret == -1); + assert(errno == EBADF); + errno = 0; + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/close.out b/pthreadfs/examples/emscripten-tests/close.out new file mode 100644 index 000000000000..5d7634b44e5b --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/close.out @@ -0,0 +1,8 @@ +fsync(opened): 0 +errno: 0 +close(opened): 0 +errno: 0 +fsync(closed): -1 +errno: 8 +close(closed): -1 +errno: 8 diff --git a/pthreadfs/examples/emscripten-tests/curdir.cpp b/pthreadfs/examples/emscripten-tests/curdir.cpp new file mode 100644 index 000000000000..bbdb6db2b643 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/curdir.cpp @@ -0,0 +1,114 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + EM_PTHREADFS_ASM( + var dummy_device = await PThreadFS.makedev(64, 0); + await PThreadFS.registerDevice(dummy_device, {}); + await PThreadFS.mkdev('/device', dummy_device); + + await PThreadFS.mkdir('/folder'); + await PThreadFS.symlink('/folder', '/link'); + await PThreadFS.writeFile('/file', "", { mode: 0o777 }); + ); + + char buffer[256]; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + errno = 0; + printf("\n"); + + printf("chdir(file): %d\n", chdir("/file")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 0; + printf("\n"); + + printf("chdir(dir): %d\n", chdir("/dir")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 2; + printf("\n"); + + printf("chdir(\"\"): %d\n", chdir("")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 2; + printf("\n"); + + printf("chdir(device): %d\n", chdir("/device")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 0; + printf("\n"); + + printf("chdir(folder): %d\n", chdir("/folder")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 0; + printf("\n"); + + printf("chdir(nonexistent): %d\n", chdir("/nonexistent")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 0; + printf("\n"); + + printf("chdir(link): %d\n", chdir("/link")); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + } + errno = 0; + printf("\n"); + + errno = 0; + printf("fchdir(/): %d\n", fchdir(open("/", O_RDONLY, 0777))); + printf("errno: %d\n", errno); + if (!errno) { + errno = 0; + printf("getcwd: %s\n", getcwd(buffer, 256)); + printf("errno: %d\n", errno); + errno = 0; + } + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/curdir.out b/pthreadfs/examples/emscripten-tests/curdir.out new file mode 100644 index 000000000000..d099284e0549 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/curdir.out @@ -0,0 +1,32 @@ +getcwd: / +errno: 0 + +chdir(file): -1 +errno: 54 + +chdir(dir): -1 +errno: 44 + +chdir(""): -1 +errno: 44 + +chdir(device): -1 +errno: 54 + +chdir(folder): 0 +errno: 0 +getcwd: /folder +errno: 0 + +chdir(nonexistent): -1 +errno: 44 + +chdir(link): 0 +errno: 0 +getcwd: /folder +errno: 0 + +fchdir(/): 0 +errno: 0 +getcwd: / +errno: 0 diff --git a/pthreadfs/examples/emscripten-tests/dup.cpp b/pthreadfs/examples/emscripten-tests/dup.cpp new file mode 100644 index 000000000000..0e877882a139 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/dup.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + int f, f2, f3; + + printf("DUP\n"); + f = open("/", O_RDONLY); + f2 = open("/", O_RDONLY); + f3 = dup(f); + printf("errno: %d\n", errno); + printf("f: %d\n", f != f2 && f != f3); + printf("f2,f3: %d\n", f2 != f3); + printf("close(f1): %d\n", close(f)); + printf("close(f2): %d\n", close(f2)); + printf("close(f3): %d\n", close(f3)); + printf("\n"); + errno = 0; + + printf("DUP2\n"); + f = open("/", O_RDONLY); + f2 = open("/", O_RDONLY); + f3 = dup2(f, f2); + printf("errno: %d\n", errno); + printf("f: %d\n", f != f2 && f != f3); + printf("f2,f3: %d\n", f2 == f3); + printf("close(f1): %d\n", close(f)); + printf("close(f2): %d\n", close(f2)); + printf("close(f3): %d\n", close(f3)); + printf("\n"); + errno = 0; + + printf("DUP2 err\n"); + f = dup2(-2, -2); + printf("f: %d\n", f == -1); + printf("errno: %d\n", errno); + printf("close(f): %d\n", close(f)); + errno = 0; + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/dup.out b/pthreadfs/examples/emscripten-tests/dup.out new file mode 100644 index 000000000000..d35779f4b4fc --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/dup.out @@ -0,0 +1,20 @@ +DUP +errno: 0 +f: 1 +f2,f3: 1 +close(f1): 0 +close(f2): 0 +close(f3): 0 + +DUP2 +errno: 0 +f: 1 +f2,f3: 1 +close(f1): 0 +close(f2): 0 +close(f3): -1 + +DUP2 err +f: 1 +errno: 8 +close(f): -1 diff --git a/pthreadfs/examples/emscripten-tests/fsafs.cpp b/pthreadfs/examples/emscripten-tests/fsafs.cpp new file mode 100644 index 000000000000..b1c5cc281456 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/fsafs.cpp @@ -0,0 +1,47 @@ +// basic file operations +#include +#include +#include +#include +#include "pthreadfs.h" + +int main () { + emscripten_init_pthreadfs(); + std::cout << "Proof that stdout works fine.\n"; + std::ofstream myfile; + myfile.open ("filesystemaccess/example"); + myfile << "Writing a few characters.\n"; + myfile.close(); + + std::string line; + std::ifstream myfile_read ("filesystemaccess/example"); + + if (myfile_read.is_open()) { + std::getline(myfile_read, line); + EM_ASM({console.log("Read line" + UTF8ToString($0)); + }, line.c_str()); + myfile_read.close(); + } + + std::ofstream stream1 ("filesystemaccess/multistreamexample"); + std::ofstream stream2 ("filesystemaccess/multistreamexample"); + stream1 << "Write a line through stream1.\n"; + stream2 << "Write a line through stream2.\n"; + stream1.close(); + stream2.close(); + + std::remove("filesystemaccess/multistreamexample"); + bool can_open_deleted_file = (bool) std::ifstream("filesystemaccess/multistreamexample"); + if(!can_open_deleted_file) { + std::cout << "Opening deleted file failed, as expected.\n"; + } + + EM_ASM(console.log('after close');); + EM_PTHREADFS_ASM( function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } + await timeout(3000); + console.log("3 seconds after close");) + EM_PTHREADFS_ASM( function timeout(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } + await timeout(1000); + console.log("4 seconds after close");) + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/io.cpp b/pthreadfs/examples/emscripten-tests/io.cpp new file mode 100644 index 000000000000..b940440c545b --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/io.cpp @@ -0,0 +1,219 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); +// EIO is used by Emscripten, but is also defined (somewhere) as a C macro +#undef EIO + EM_PTHREADFS_ASM( + await PThreadFS.mkdir('/working'); + + var major = 80; + + var device = await PThreadFS.makedev(major++, 0); + await PThreadFS.registerDevice(device, { + open: function(stream) { + stream.payload = [65, 66, 67, 68]; + }, + read: function(stream, buffer, offset, length, pos) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + if (stream.payload.length) { + bytesRead++; + buffer[offset+i] = stream.payload.shift(); + } else { + break; + } + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + out('TO DEVICE: ' + buffer[offset+i]); + } + return i; + } + }); + await PThreadFS.mkdev('/device', device); + + var broken_device = await PThreadFS.makedev(major++, 0); + await PThreadFS.registerDevice(broken_device, { + read: function(stream, buffer, offset, length, pos) { + throw new PThreadFS.ErrnoError(ERRNO_CODES.EIO); + }, + write: function(stream, buffer, offset, length, pos) { + throw new PThreadFS.ErrnoError(ERRNO_CODES.EIO); + } + }); + await PThreadFS.mkdev('/broken-device', broken_device); + + // NB: These are meant to test FS.createDevice specifically, + // and as such do not use registerDevice/mkdev + await PThreadFS.createDevice('/', 'createDevice-read-only', function() {}); + await PThreadFS.createDevice('/', 'createDevice-write-only', null, function() {}); + + await PThreadFS.mkdir('/working/folder'); + await PThreadFS.writeFile('/working/file', '1234567890'); + ); +#define EIO 5 + + char readBuffer[256] = {0}; + char writeBuffer[] = "writeme"; + + int fl = open("/working/folder", O_RDWR); + printf("read from folder: %zd\n", read(fl, readBuffer, sizeof readBuffer)); + printf("errno: %d\n", errno); + errno = 0; + printf("write to folder: %zd\n", write(fl, writeBuffer, sizeof writeBuffer)); + printf("errno: %d\n\n", errno); + errno = 0; + + int bd = open("/broken-device", O_RDWR); + printf("read from broken device: %zd\n", read(bd, readBuffer, sizeof readBuffer)); + printf("errno: %d\n", errno); + errno = 0; + printf("write to broken device: %zd\n", write(bd, writeBuffer, sizeof writeBuffer)); + printf("errno: %d\n\n", errno); + errno = 0; + + int d = open("/device", O_RDWR); + printf("read from device: %zd\n", read(d, readBuffer, sizeof readBuffer)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n", errno); + errno = 0; + printf("write to device: %zd\n", write(d, writeBuffer, sizeof writeBuffer)); + printf("errno: %d\n\n", errno); + errno = 0; + + int cd_ro_r = open("/createDevice-read-only", O_RDONLY); + printf("open read-only device from createDevice for read, errno: %d\n", errno); + errno = 0; + int cd_ro_w = open("/createDevice-read-only", O_WRONLY); + printf("open read-only device from createDevice for write, errno: %d\n", errno); + errno = 0; + int cd_wo_r = open("/createDevice-write-only", O_RDONLY); + printf("open write-only device from createDevice for read, errno: %d\n", errno); + errno = 0; + int cd_wo_w = open("/createDevice-write-only", O_WRONLY); + printf("open write-only device from createDevice for write, errno: %d\n\n", errno); + errno = 0; + + int f = open("/working/file", O_RDWR); + printf("read from file: %zd\n", read(f, readBuffer, sizeof readBuffer)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("pread past end of file: %zd\n", pread(f, readBuffer, sizeof readBuffer, 999999999)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, 3, SEEK_SET)); + printf("errno: %d\n\n", errno); + printf("partial read from file: %zd\n", read(f, readBuffer, 3)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, -2, SEEK_END)); + printf("errno: %d\n", errno); + errno = 0; + printf("partial read from end of file: %zd\n", read(f, readBuffer, 3)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, -15, SEEK_CUR)); + printf("errno: %d\n", errno); + errno = 0; + printf("partial read from before start of file: %zd\n", read(f, readBuffer, 3)); + printf("data: %s\n", readBuffer); + memset(readBuffer, 0, sizeof readBuffer); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, 0, SEEK_SET)); + printf("write to start of file: %zd\n", write(f, writeBuffer, 3)); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, 0, SEEK_END)); + printf("write to end of file: %zd\n", write(f, writeBuffer, 3)); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("seek: %lld\n", lseek(f, 10, SEEK_END)); + printf("write after end of file: %zd\n", write(f, writeBuffer, sizeof writeBuffer)); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("pwrite to the middle of file: %zd\n", pwrite(f, writeBuffer + 2, 3, 17)); + printf("errno: %d\n", errno); + printf("seek: %lld\n\n", lseek(f, 0, SEEK_CUR)); + errno = 0; + + printf("pwrite past end of file: %zd\n", pwrite(f, writeBuffer, 5, 32)); + printf("errno: %d\n", errno); + printf("seek: %lld\n\n", lseek(f, 0, SEEK_CUR)); + errno = 0; + + ssize_t bytesRead; + printf("seek: %lld\n", lseek(f, 0, SEEK_SET)); + printf("read after write: %zd\n", bytesRead = read(f, readBuffer, sizeof readBuffer)); + printf("errno: %d\n", errno); + errno = 0; + printf("final: "); + for (ssize_t i = 0; i < bytesRead; i++) { + if (readBuffer[i] == 0) { + printf("\\0"); + } else { + printf("%c", readBuffer[i]); + } + } + printf("\n"); + + // readv + printf("\n"); + memset(readBuffer, 0, sizeof readBuffer); + struct iovec iov; + iov.iov_base = readBuffer; + iov.iov_len = sizeof readBuffer; + printf("seek: %lld\n", lseek(f, 0, SEEK_SET)); + printf("read after write: %zd\n", bytesRead = readv(f, &iov, 1)); + printf("errno: %d\n", errno); + errno = 0; + printf("final: "); + for (ssize_t i = 0; i < bytesRead; i++) { + if (readBuffer[i] == 0) { + printf("\\0"); + } else { + printf("%c", readBuffer[i]); + } + } + printf("\n"); + +#ifdef REPORT_RESULT + REPORT_RESULT(0); +#endif + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/io.out b/pthreadfs/examples/emscripten-tests/io.out new file mode 100644 index 000000000000..7e45f650d1b7 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/io.out @@ -0,0 +1,85 @@ +read from folder: -1 +errno: 8 +write to folder: -1 +errno: 8 + +read from broken device: -1 +errno: 29 +write to broken device: -1 +errno: 29 + +read from device: 4 +data: ABCD +errno: 0 +TO DEVICE: 119 +TO DEVICE: 114 +TO DEVICE: 105 +TO DEVICE: 116 +TO DEVICE: 101 +TO DEVICE: 109 +TO DEVICE: 101 +TO DEVICE: 0 +write to device: 8 +errno: 0 + +open read-only device from createDevice for read, errno: 0 +open read-only device from createDevice for write, errno: 2 +open write-only device from createDevice for read, errno: 2 +open write-only device from createDevice for write, errno: 0 + +read from file: 10 +data: 1234567890 +errno: 0 + +pread past end of file: 0 +data: +errno: 0 + +seek: 3 +errno: 0 + +partial read from file: 3 +data: 456 +errno: 0 + +seek: 8 +errno: 0 +partial read from end of file: 2 +data: 90 +errno: 0 + +seek: -1 +errno: 28 +partial read from before start of file: 0 +data: +errno: 0 + +seek: 0 +write to start of file: 3 +errno: 0 + +seek: 10 +write to end of file: 3 +errno: 0 + +seek: 23 +write after end of file: 8 +errno: 0 + +pwrite to the middle of file: 3 +errno: 0 +seek: 31 + +pwrite past end of file: 5 +errno: 0 +seek: 31 + +seek: 0 +read after write: 37 +errno: 0 +final: wri4567890wri\0\0\0\0ite\0\0\0writeme\0\0write + +seek: 0 +read after write: 37 +errno: 0 +final: wri4567890wri\0\0\0\0ite\0\0\0writeme\0\0write diff --git a/pthreadfs/examples/emscripten-tests/isatty.cpp b/pthreadfs/examples/emscripten-tests/isatty.cpp new file mode 100644 index 000000000000..92300d7cfcd2 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/isatty.cpp @@ -0,0 +1,37 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + int err; + + err = isatty(-1); + assert(!err); + assert(errno == EBADF); + + err = isatty(open("/dev/stdin", O_RDONLY)); + assert(err == 1); + + err = isatty(open("/dev/null", O_RDONLY)); + assert(!err); + + err = isatty(open("/dev", O_RDONLY)); + assert(!err); + + puts("success"); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/pthreadfs/examples/emscripten-tests/links.cpp b/pthreadfs/examples/emscripten-tests/links.cpp new file mode 100644 index 000000000000..94ae1095af14 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/links.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include "pthreadfs.h" + +int main () { + emscripten_init_pthreadfs(); + EM_PTHREADFS_ASM( + await PThreadFS.mkdir('working'); + await PThreadFS.chdir('working'); + await PThreadFS.symlink('../test/../there!', 'link'); + await PThreadFS.writeFile('file', 'test'); + await PThreadFS.mkdir('folder'); + ); + + char* files[] = {"link", "file", "folder"}; + char buffer[256] = {0}; + + for (int i = 0; i < sizeof files / sizeof files[0]; i++) { + printf("readlink(%s)\n", files[i]); + printf("ret: %zd\n", readlink(files[i], buffer, 256)); + printf("errno: %d\n", errno); + printf("result: %s\n\n", buffer); + errno = 0; + } + + printf("symlink/overwrite\n"); + printf("ret: %d\n", symlink("new-nonexistent-path", "link")); + printf("errno: %d\n\n", errno); + errno = 0; + + printf("symlink/normal\n"); + printf("ret: %d\n", symlink("new-nonexistent-path", "folder/link")); + printf("errno: %d\n", errno); + errno = 0; + + printf("readlink(created link)\n"); + printf("ret: %zd\n", readlink("folder/link", buffer, 256)); + printf("errno: %d\n", errno); + printf("result: %s\n\n", buffer); + errno = 0; + + buffer[0] = buffer[1] = buffer[2] = buffer[3] = buffer[4] = buffer[5] = '*'; + printf("readlink(short buffer)\n"); + printf("ret: %zd\n", readlink("link", buffer, 4)); + printf("errno: %d\n", errno); + printf("result: %s\n", buffer); + errno = 0; + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/links.out b/pthreadfs/examples/emscripten-tests/links.out new file mode 100644 index 000000000000..1b5915b0b499 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/links.out @@ -0,0 +1,31 @@ +readlink(link) +ret: 7 +errno: 0 +result: /there! + +readlink(file) +ret: -1 +errno: 28 +result: /there! + +readlink(folder) +ret: -1 +errno: 28 +result: /there! + +symlink/overwrite +ret: -1 +errno: 20 + +symlink/normal +ret: 0 +errno: 0 +readlink(created link) +ret: 36 +errno: 0 +result: /working/folder/new-nonexistent-path + +readlink(short buffer) +ret: 4 +errno: 0 +result: /the**ng/folder/new-nonexistent-path diff --git a/pthreadfs/examples/emscripten-tests/misc.cpp b/pthreadfs/examples/emscripten-tests/misc.cpp new file mode 100644 index 000000000000..1842d13f7986 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/misc.cpp @@ -0,0 +1,213 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +int main() { + emscripten_init_pthreadfs(); + EM_PTHREADFS_ASM( + await PThreadFS.mkdir('working'); + ); + + int f = open("working", O_RDONLY); + assert(f); + int t = open("/dev/stdin", O_RDONLY); + assert(t); + + sync(); + + printf("fsync(good): %d", fsync(f)); + printf(", errno: %d\n", errno); + errno = 0; + printf("fsync(bad): %d", fsync(42)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("fdatasync(good): %d", fdatasync(f)); + printf(", errno: %d\n", errno); + errno = 0; + printf("fdatasync(bad): %d", fdatasync(42)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("tcgetpgrp(good): %d", tcgetpgrp(t)); + printf(", errno: %d\n", errno); + errno = 0; + printf("tcgetpgrp(bad): %d", tcgetpgrp(42)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("tcsetpgrp(good): %d", tcsetpgrp(t, 123)); + printf(", errno: %d\n", errno); + errno = 0; + printf("tcsetpgrp(bad): %d", tcsetpgrp(42, 123)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("link: %d", link("working/here", "working/there")); + printf(", errno: %d\n", errno); + errno = 0; + + printf("lockf(good): %d", lockf(f, F_LOCK, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("lockf(bad): %d", lockf(42, F_LOCK, 456)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("nice: %d", nice(42)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("pause: %d", pause()); + printf(", errno: %d\n", errno); + errno = 0; + + int pipe_arg[2]; + printf("pipe(good): %d", pipe(pipe_arg)); + printf(", errno: %d\n", errno); + errno = 0; + printf("pipe(bad): %d", pipe(0)); + printf(", errno: %d\n", errno); + errno = 0; + + char* exec_argv[] = {"arg", 0}; + char* exec_env[] = {"a=b", 0}; + printf("execl: %d", execl("working/program", "arg", 0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("execle: %d", execle("working/program", "arg", 0, exec_env)); + printf(", errno: %d\n", errno); + errno = 0; + printf("execlp: %d", execlp("program", "arg", 0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("execv: %d", execv("program", exec_argv)); + printf(", errno: %d\n", errno); + errno = 0; + printf("execve: %d", execve("program", exec_argv, exec_env)); + printf(", errno: %d\n", errno); + errno = 0; + printf("execvp: %d", execvp("program", exec_argv)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("chown(good): %d", chown("working", 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("chown(bad): %d", chown("working/noexist", 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("lchown(good): %d", lchown("working", 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("lchown(bad): %d", lchown("working/noexist", 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("fchown(good): %d", fchown(f, 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("fchown(bad): %d", fchown(42, 123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + + printf("fork: %d", fork()); + printf(", errno: %d\n", errno); + errno = 0; + printf("vfork: %d", vfork()); + printf(", errno: %d\n", errno); + errno = 0; + + printf("crypt: %s", crypt("foo", "bar")); + printf(", errno: %d\n", errno); + errno = 0; + char encrypt_block[64] = {0}; + printf("encrypt"); + encrypt(encrypt_block, 42); + printf(", errno: %d\n", errno); + errno = 0; + + printf("getgid: %d", getgid()); + printf(", errno: %d\n", errno); + errno = 0; + printf("getegid: %d", getegid()); + printf(", errno: %d\n", errno); + errno = 0; + printf("getuid: %d", getuid()); + printf(", errno: %d\n", errno); + errno = 0; + printf("geteuid: %d", geteuid()); + printf(", errno: %d\n", errno); + errno = 0; + + printf("getpgrp: %d", getpgrp()); + printf(", errno: %d\n", errno); + errno = 0; + pid_t mypid = getpid(); + printf("getpid: %d", mypid); + printf(", errno: %d\n", errno); + errno = 0; + printf("getppid: %d", getppid()); + printf(", errno: %d\n", errno); + errno = 0; + printf("getpgid: %d", getpgid(mypid)); + printf(", errno: %d\n", errno); + errno = 0; + printf("getsid: %d", getsid(mypid)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setgid: %d", setgid(0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setegid: %d", setegid(0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setuid: %d", setuid(0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("seteuid: %d", seteuid(0)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setpgrp: %d", setpgrp()); + printf(", errno: %d\n", errno); + errno = 0; + printf("setsid: %d", setsid()); + printf(", errno: %d\n", errno); + errno = 0; + printf("setpgid: %d", setpgid(mypid, mypid)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setpgid: %d", setpgid(123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setregid: %d", setregid(123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + printf("setreuid: %d", setreuid(123, 456)); + printf(", errno: %d\n", errno); + errno = 0; + + gid_t groups[10] = {42}; + printf("getgroups: %d", getgroups(10, groups)); + printf(", result: %d", groups[0]); + printf(", errno: %d\n", errno); + errno = 0; + + gid_t groups2[1] = {0}; + printf("setgroups: %d", setgroups(1, groups2)); + printf(", errno: %d\n", errno); + errno = 0; + + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/misc.out b/pthreadfs/examples/emscripten-tests/misc.out new file mode 100644 index 000000000000..f25cf73eb9f9 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/misc.out @@ -0,0 +1,52 @@ +fsync(good): 0, errno: 0 +fsync(bad): -1, errno: 8 +fdatasync(good): 0, errno: 0 +fdatasync(bad): -1, errno: 8 +tcgetpgrp(good): 0, errno: 0 +tcgetpgrp(bad): -1, errno: 8 +tcsetpgrp(good): -1, errno: 28 +tcsetpgrp(bad): -1, errno: 8 +link: -1, errno: 34 +lockf(good): 0, errno: 0 +lockf(bad): -1, errno: 8 +nice: -1, errno: 63 +pause: -1, errno: 27 +pipe(good): 0, errno: 0 +pipe(bad): -1, errno: 21 +execl: -1, errno: 45 +execle: -1, errno: 45 +execlp: -1, errno: 45 +execv: -1, errno: 45 +execve: -1, errno: 45 +execvp: -1, errno: 45 +chown(good): 0, errno: 0 +chown(bad): -1, errno: 44 +lchown(good): 0, errno: 0 +lchown(bad): -1, errno: 44 +fchown(good): 0, errno: 0 +fchown(bad): -1, errno: 8 +fork: -1, errno: 52 +vfork: -1, errno: 52 +crypt: ba4TuD1iozTxw, errno: 0 +encrypt, errno: 0 +getgid: 0, errno: 0 +getegid: 0, errno: 0 +getuid: 0, errno: 0 +geteuid: 0, errno: 0 +getpgrp: 42, errno: 0 +getpid: 42, errno: 0 +getppid: 1, errno: 0 +getpgid: 42, errno: 0 +getsid: 42, errno: 0 +setgid: -1, errno: 63 +setegid: -1, errno: 63 +setuid: -1, errno: 63 +seteuid: -1, errno: 63 +setpgrp: 0, errno: 0 +setsid: 0, errno: 0 +setpgid: 0, errno: 0 +setpgid: -1, errno: 71 +setregid: -1, errno: 63 +setreuid: -1, errno: 63 +getgroups: 1, result: 0, errno: 0 +setgroups: -1, errno: 63 diff --git a/pthreadfs/examples/emscripten-tests/truncate.cpp b/pthreadfs/examples/emscripten-tests/truncate.cpp new file mode 100644 index 000000000000..e054601f7605 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/truncate.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2011 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "pthreadfs.h" + +void setup() { + EM_PTHREADFS_ASM( + await PThreadFS.mkdir('working'); + await PThreadFS.chdir('working'); + await PThreadFS.writeFile('towrite', 'abcdef'); + await PThreadFS.writeFile('toread', 'abcdef'); + await PThreadFS.chmod('toread', 0o444); + ); +} + +int main() { + emscripten_init_pthreadfs(); + setup(); + + struct stat s; + int f = open("towrite", O_WRONLY); + int f2 = open("toread", O_RDONLY); + printf("f2: %d\n", f2); + + fstat(f, &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("ftruncate(10): %d\n", ftruncate(f, 10)); + printf("errno: %s\n", strerror(errno)); + fstat(f, &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("ftruncate(4): %d\n", ftruncate(f, 4)); + printf("errno: %s\n", strerror(errno)); + fstat(f, &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("ftruncate(-1): %d\n", ftruncate(f, -1)); + printf("errno: %s\n", strerror(errno)); + fstat(f, &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("truncate(2): %d\n", truncate("towrite", 2)); + printf("errno: %s\n", strerror(errno)); + stat("towrite", &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("truncate(readonly, 2): %d\n", truncate("toread", 2)); + printf("errno: %s\n", strerror(errno)); + stat("toread", &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + printf("\n"); + + printf("ftruncate(readonly, 4): %d\n", ftruncate(f2, 4)); + printf("errno: %s\n", strerror(errno)); + fstat(f2, &s); + printf("st_size: %lld\n", s.st_size); + memset(&s, 0, sizeof s); + errno = 0; + + // Restore full permissions on all created files so that python test runner rmtree + // won't have problems on deleting the files. On Windows, calling shutil.rmtree() + // will fail if any of the files are read-only. + EM_PTHREADFS_ASM( + await PThreadFS.chmod('toread', 0o777); + ); + return 0; +} diff --git a/pthreadfs/examples/emscripten-tests/truncate.out b/pthreadfs/examples/emscripten-tests/truncate.out new file mode 100644 index 000000000000..c410cf655f25 --- /dev/null +++ b/pthreadfs/examples/emscripten-tests/truncate.out @@ -0,0 +1,25 @@ +st_size: 6 + +ftruncate(10): 0 +errno: No error information +st_size: 10 + +ftruncate(4): 0 +errno: No error information +st_size: 4 + +ftruncate(-1): -1 +errno: Invalid argument +st_size: 4 + +truncate(2): 0 +errno: No error information +st_size: 2 + +truncate(readonly, 2): -1 +errno: Permission denied +st_size: 6 + +ftruncate(readonly, 4): -1 +errno: Invalid argument +st_size: 6 diff --git a/pthreadfs/examples/sqlite-speedtest/sqlite-speedtest-prejs.js b/pthreadfs/examples/sqlite-speedtest/sqlite-speedtest-prejs.js new file mode 100644 index 000000000000..e9a6cfd11aa1 --- /dev/null +++ b/pthreadfs/examples/sqlite-speedtest/sqlite-speedtest-prejs.js @@ -0,0 +1,6 @@ + +var Module = Module || {}; +Module['arguments'] = Module['arguments'] || []; +Module['arguments'].push('--size'); +Module['arguments'].push('40'); +Module['arguments'].push(`/filesystemaccess/db${Math.random()}`); \ No newline at end of file diff --git a/pthreadfs/library_pthreadfs.js b/pthreadfs/library_pthreadfs.js new file mode 100644 index 000000000000..d840fba54201 --- /dev/null +++ b/pthreadfs/library_pthreadfs.js @@ -0,0 +1,3538 @@ +/** + * @license + * Copyright 2021 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var SyscallWrappers = {} + +let SyscallsFunctions = [ + {'name': 'open', 'args': ['path', 'flags', 'varargs']}, + {'name': 'unlink', 'args': ['path']}, + {'name': 'chdir', 'args': ['path']}, + {'name': 'mknod', 'args': ['path', 'mode', 'dev']}, + {'name': 'chmod', 'args': ['path', 'mode']}, + {'name': 'access', 'args': ['path', 'amode']}, + {'name': 'rename', 'args': ['old_path', 'new_path']}, + {'name': 'mkdir', 'args': ['path', 'mode']}, + {'name': 'rmdir', 'args': ['path']}, + {'name': 'dup', 'args': ['fd']}, + {'name': 'ioctl', 'args': ['fd', 'request', 'varargs']}, + {'name': 'dup2', 'args': ['oldfd', 'newfd']}, + {'name': 'symlink', 'args': ['target', 'linkpath']}, + {'name': 'readlink', 'args': ['path', 'buf', 'bufsize']}, + {'name': 'munmap', 'args': ['addr', 'len']}, + {'name': 'fchmod', 'args': ['fd', 'mode']}, + {'name': 'fchdir', 'args': ['fd']}, + {'name': '_newselect', 'args': ['nfds', 'readfds', 'writefds', 'exceptfds', 'timeout']}, + {'name': 'msync', 'args': ['addr', 'len', 'flags']}, + {'name': 'fdatasync', 'args': ['fd']}, + {'name': 'poll', 'args': ['fds', 'nfds', 'timeout']}, + {'name': 'getcwd', 'args': ['buf', 'size']}, + {'name': 'mmap2', 'args': ['addr', 'len', 'prot', 'flags', 'fd', 'off']}, + {'name': 'truncate64', 'args': ['path', 'zero', 'low', 'high']}, + {'name': 'ftruncate64', 'args': ['fd', 'zero', 'low', 'high']}, + {'name': 'stat64', 'args': ['path', 'buf']}, + {'name': 'lstat64', 'args': ['path', 'buf']}, + {'name': 'fstat64', 'args': ['fd', 'buf']}, + {'name': 'lchown32', 'args': ['path', 'owner', 'group']}, + {'name': 'fchown32', 'args': ['fd', 'owner', 'group']}, + {'name': 'chown32', 'args': ['path', 'owner', 'group']}, + {'name': 'getdents64', 'args': ['fd', 'dirp', 'count']}, + {'name': 'fcntl64', 'args': ['fd', 'cmd', 'varargs']}, + {'name': 'statfs64', 'args': ['path', 'size', 'buf']}, + {'name': 'fstatfs64', 'args': ['fd', 'size', 'buf']}, + {'name': 'openat', 'args': ['dirfd', 'path', 'flags', 'varargs']}, + {'name': 'mkdirat', 'args': ['dirfd', 'path', 'mode']}, + {'name': 'mknodat', 'args': ['dirfd', 'path', 'mode', 'dev']}, + {'name': 'fchownat', 'args': ['dirfd', 'path', 'owner', 'group', 'flags']}, + {'name': 'fstatat64', 'args': ['dirfd', 'path', 'buf', 'flags']}, + {'name': 'unlinkat', 'args': ['dirfd', 'path', 'flags']}, + {'name': 'renameat', 'args': ['olddirfd', 'oldpath', 'newdirfd', 'newpath']}, + {'name': 'symlinkat', 'args': ['target', 'newdirfd', 'linkpath']}, + {'name': 'readlinkat', 'args': ['dirfd', 'path', 'bug', 'bufsize']}, + {'name': 'fchmodat', 'args': ['dirfd', 'path', 'mode', 'varargs']}, + {'name': 'faccessat', 'args': ['dirfd', 'path', 'amode', 'flags']}, + {'name': 'utimensat', 'args': ['dirfd', 'path', 'times', 'flags']}, + {'name': 'fallocate', 'args': ['fd', 'mode', 'off_low', 'off_high', 'len_low', 'len_high']}, + {'name': 'dup3', 'args': ['fd', 'suggestfd', 'flags']}, +] + +let WasiFunctions = [ + {'name': 'write', 'args': ['iovs', 'iovs_len', 'nwritten']}, + {'name': 'read', 'args': ['iovs', 'iovs_len', 'nread']}, + {'name': 'close', 'args': []}, + {'name': 'pwrite', 'args': ['iov', 'iovcnt', "{{{ defineI64Param('offset') }}}", 'pnum']}, + {'name': 'pread', 'args': ['iov', 'iovcnt', "{{{ defineI64Param('offset') }}}", 'pnum']}, + {'name': 'seek', 'args': ["{{{ defineI64Param('offset') }}}", 'whence', 'newOffset']}, + {'name': 'fdstat_get', 'args': ['pbuf']}, + {'name': 'sync', 'args': []}, +] + +function createWasiWrapper(name, args, wrappers) { + let full_args = 'fd'; + if (args.length > 0) { + full_args = full_args + ',' + args.join(','); + } + let full_args_with_resume = full_args + ',resume'; + let wrapper = `function(${full_args_with_resume}) {`; + wrapper += `_fd_${name}_async(${full_args}).then((res) => {`; + wrapper += 'wasmTable.get(resume)(res);});}' + wrappers[`__fd_${name}_async`] = eval('(' + wrapper + ')'); + wrappers[`__fd_${name}_async__deps`] = [`fd_${name}_async`, '$ASYNCSYSCALLS', '$FSAFS']; +} + +function createSyscallWrapper(name, args, wrappers) { + let full_args = ''; + let full_args_with_resume = 'resume'; + if (args.length > 0) { + full_args = args.join(','); + full_args_with_resume = full_args + ', resume'; + } + let wrapper = `function(${full_args_with_resume}) {`; + wrapper += `_${name}_async(${full_args}).then((res) => {`; + wrapper += 'wasmTable.get(resume)(res);});}' + wrappers[`__sys_${name}_async`] = eval('(' + wrapper + ')'); + wrappers[`__sys_${name}_async__deps`] = [`${name}_async`, '$ASYNCSYSCALLS', '$FSAFS']; +} + +for (x of WasiFunctions) { + createWasiWrapper(x.name, x.args, SyscallWrappers); +} +for (x of SyscallsFunctions) { + createSyscallWrapper(x.name, x.args, SyscallWrappers); +} + +SyscallWrappers['init_pthreadfs'] = function (resume) { + var FSNode = /** @constructor */ function(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; + this.id = PThreadFS.nextInode++; + this.name = name; + this.mode = mode; + this.node_ops = {}; + this.stream_ops = {}; + this.rdev = rdev; + }; + var readMode = 292/*{{{ cDefine("S_IRUGO") }}}*/ | 73/*{{{ cDefine("S_IXUGO") }}}*/; + var writeMode = 146/*{{{ cDefine("S_IWUGO") }}}*/; + Object.defineProperties(FSNode.prototype, { + read: { + get: /** @this{FSNode} */function() { + return (this.mode & readMode) === readMode; + }, + set: /** @this{FSNode} */function(val) { + val ? this.mode |= readMode : this.mode &= ~readMode; + } + }, + write: { + get: /** @this{FSNode} */function() { + return (this.mode & writeMode) === writeMode; + }, + set: /** @this{FSNode} */function(val) { + val ? this.mode |= writeMode : this.mode &= ~writeMode; + } + }, + isFolder: { + get: /** @this{FSNode} */function() { + return PThreadFS.isDir(this.mode); + } + }, + isDevice: { + get: /** @this{FSNode} */function() { + return PThreadFS.isChrdev(this.mode); + } + } + }); + PThreadFS.FSNode = FSNode; + + PThreadFS.staticInit().then(async ()=> { + await PThreadFS.init(); + PThreadFS.ignorePermissions = false; + wasmTable.get(resume)(); + }); +} + +SyscallWrappers['init_fsafs'] = function(resume) { + PThreadFS.mkdir('/filesystemaccess').then(async () => { + await PThreadFS.mount(FSAFS, { root: '.' }, '/filesystemaccess'); + wasmTable.get(resume)(); + }); +} + +SyscallWrappers['init_sfafs'] = function(resume) { + PThreadFS.mkdir('/sfa').then(async () => { + await PThreadFS.mount(SFAFS, { root: '.' }, '/sfa'); + + // Storage Foundation requires explicit capacity allocations. + if (storageFoundation.requestCapacity) { + await storageFoundation.requestCapacity(1024*1024*100); + } + // Delete all old files. + let files = await storageFoundation.getAll(); + for (file of files) { + await storageFoundation.delete(file); + } + wasmTable.get(resume)(); + }); +} + +mergeInto(LibraryManager.library, SyscallWrappers);/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +/* +List of implemented syscalls: + {'name': 'open', 'args': ['path', 'flags', 'varargs']}, + {'name': 'unlink', 'args': ['path']}, + {'name': 'chdir', 'args': ['path']}, + {'name': 'mknod', 'args': ['path', 'mode', 'dev']}, + {'name': 'chmod', 'args': ['path', 'mode']}, + {'name': 'access', 'args': ['path', 'amode']}, + {'name': 'rename', 'args': ['old_path', 'new_path']}, + {'name': 'mkdir', 'args': ['path', 'mode']}, + {'name': 'rmdir', 'args': ['path']}, + {'name': 'dup', 'args': ['fd']}, + {'name': 'ioctl', 'args': ['fd', 'request', 'varargs']}, + {'name': 'dup2', 'args': ['oldfd', 'newfd']}, + {'name': 'symlink', 'args': ['target', 'linkpath']}, + {'name': 'readlink', 'args': ['path', 'buf', 'bufsize']}, + {'name': 'munmap', 'args': ['addr', 'len']}, + {'name': 'fchmod', 'args': ['fd', 'mode']}, + {'name': 'fchdir', 'args': ['fd']}, + {'name': '_newselect', 'args': ['nfds', 'readfds', 'writefds', 'exceptfds', 'timeout']}, + {'name': 'msync', 'args': ['addr', 'len', 'flags']}, + {'name': 'fdatasync', 'args': ['fd']}, + {'name': 'poll', 'args': ['fds', 'nfds', 'timeout']}, + {'name': 'getcwd', 'args': ['buf', 'size']}, + {'name': 'mmap2', 'args': ['addr', 'len', 'prot', 'flags', 'fd', 'off']}, + {'name': 'truncate64', 'args': ['path', 'zero', 'low', 'high']}, + {'name': 'ftruncate64', 'args': ['fd', 'zero', 'low', 'high']}, + {'name': 'stat64', 'args': ['path', 'buf']}, + {'name': 'lstat64', 'args': ['path', 'buf']}, + {'name': 'fstat64', 'args': ['fd', 'buf']}, + {'name': 'lchown32', 'args': ['path', 'owner', 'group']}, + {'name': 'fchown32', 'args': ['fd', 'owner', 'group']}, + {'name': 'chown32', 'args': ['path', 'owner', 'group']}, + {'name': 'getdents64', 'args': ['fd', 'dirp', 'count']}, + {'name': 'fcntl64', 'args': ['fd', 'cmd', 'varargs']}, + {'name': 'statfs64', 'args': ['path', 'size', 'buf']}, + {'name': 'fstatfs64', 'args': ['fd', 'size', 'buf']}, + {'name': 'openat', 'args': ['dirfd', 'path', 'flags', 'varargs']}, + {'name': 'mkdirat', 'args': ['dirfd', 'path', 'mode']}, + {'name': 'mknodat', 'args': ['dirfd', 'path', 'mode', 'dev']}, + {'name': 'fchownat', 'args': ['dirfd', 'path', 'owner', 'group', 'flags']}, + {'name': 'fstatat64', 'args': ['dirfd', 'path', 'buf', 'flags']}, + {'name': 'unlinkat', 'args': ['dirfd', 'path', 'flags']}, + {'name': 'renameat', 'args': ['olddirfd', 'oldpath', 'newdirfd', 'newpath']}, + {'name': 'symlinkat', 'args': ['target', 'newdirfd', 'linkpath']}, + {'name': 'readlinkat', 'args': ['dirfd', 'path', 'bug', 'bufsize']}, + {'name': 'fchmodat', 'args': ['dirfd', 'path', 'mode', 'varargs']}, + {'name': 'faccessat', 'args': ['dirfd', 'path', 'amode', 'flags']}, + {'name': 'utimensat', 'args': ['dirfd', 'path', 'times', 'flags']}, + {'name': 'fallocate', 'args': ['fd', 'mode', 'off_low', 'off_high', 'len_low', 'len_high']}, + {'name': 'dup3', 'args': ['fd', 'suggestfd', 'flags']}, +*/ + +var SyscallsLibrary = { + $ASYNCSYSCALLS__deps: ['$PThreadFS'], + $ASYNCSYSCALLS: { + mappings: {}, + // global constants + DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}}, + + // global state + umask: 0x1FF, // S_IRWXU | S_IRWXG | S_IRWXO + + // shared utilities + calculateAt: async function(dirfd, path, allowEmpty) { + if (path[0] === '/') { + return path; + } + // relative path + var dir; + if (dirfd === {{{ cDefine('AT_FDCWD') }}}) { + dir = await PThreadFS.cwd(); + } else { + var dirstream = await PThreadFS.getStream(dirfd); + if (!dirstream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}});; + } + return dir; + } + return PATH.join2(dir, path); + }, + + doStat: async function(func, path, buf) { + try { + var stat = await func(path); + } catch (e) { + if (e && e.node && PATH.normalize(path) !== PATH.normalize(PThreadFS.getPath(e.node))) { + // an error occurred while trying to look up the path; we should just report ENOTDIR + return -{{{ cDefine('ENOTDIR') }}}; + } + throw e; + } + {{{ makeSetValue('buf', C_STRUCTS.stat.st_dev, 'stat.dev', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_dev_padding, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_ino_truncated, 'stat.ino', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mode, 'stat.mode', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_nlink, 'stat.nlink', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_uid, 'stat.uid', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_gid, 'stat.gid', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_rdev, 'stat.rdev', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_rdev_padding, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_size, 'stat.size', 'i64') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_blksize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_blocks, 'stat.blocks', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_sec, '(stat.atime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_sec, '(stat.mtime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_sec, '(stat.ctime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}}; + return 0; + }, + doMsync: async function(addr, stream, len, flags, offset) { + var buffer = HEAPU8.slice(addr, addr + len); + await PThreadFS.msync(stream, buffer, offset, len, flags); + }, + doMkdir: async function(path, mode) { + // remove a trailing slash, if one - /a/b/ has basename of '', but + // we want to create b in the context of this function + path = PATH.normalize(path); + if (path[path.length-1] === '/') path = path.substr(0, path.length-1); + await PThreadFS.mkdir(path, mode, 0); + return 0; + }, + doMknod: async function(path, mode, dev) { + // we don't want this in the JS API as it uses mknod to create all nodes. + switch (mode & {{{ cDefine('S_IFMT') }}}) { + case {{{ cDefine('S_IFREG') }}}: + case {{{ cDefine('S_IFCHR') }}}: + case {{{ cDefine('S_IFBLK') }}}: + case {{{ cDefine('S_IFIFO') }}}: + case {{{ cDefine('S_IFSOCK') }}}: + break; + default: return -{{{ cDefine('EINVAL') }}}; + } + await PThreadFS.mknod(path, mode, dev); + return 0; + }, + doReadlink: async function(path, buf, bufsize) { + if (bufsize <= 0) return -{{{ cDefine('EINVAL') }}}; + var ret = await PThreadFS.readlink(path); + + var len = Math.min(bufsize, lengthBytesUTF8(ret)); + var endChar = HEAP8[buf+len]; + stringToUTF8(ret, buf, bufsize+1); + // readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!) + // stringToUTF8() always appends a null byte, so restore the character under the null byte after the write. + HEAP8[buf+len] = endChar; + + return len; + }, + doAccess: async function(path, amode) { + if (amode & ~{{{ cDefine('S_IRWXO') }}}) { + // need a valid mode + return -{{{ cDefine('EINVAL') }}}; + } + var node; + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + node = lookup.node; + if (!node) { + return -{{{ cDefine('ENOENT') }}}; + } + var perms = ''; + if (amode & {{{ cDefine('R_OK') }}}) perms += 'r'; + if (amode & {{{ cDefine('W_OK') }}}) perms += 'w'; + if (amode & {{{ cDefine('X_OK') }}}) perms += 'x'; + let nodepermissions = await PThreadFS.nodePermissions(node, perms); + if (perms /* otherwise, they've just passed F_OK */ && nodepermissions) { + return -{{{ cDefine('EACCES') }}}; + } + return 0; + }, + doDup: async function(path, flags, suggestFD) { + var suggest = await PThreadFS.getStream(suggestFD); + if (suggest) await PThreadFS.close(suggest); + let stream = await PThreadFS.open(path, flags, 0, suggestFD, suggestFD); + return stream.fd; + }, + doReadv: async function(stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = {{{ makeGetValue('iov', 'i*8', 'i32') }}}; + var len = {{{ makeGetValue('iov', 'i*8 + 4', 'i32') }}}; + var curr = await PThreadFS.read(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; // nothing more to read + } + return ret; + }, + doWritev: async function(stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = {{{ makeGetValue('iov', 'i*8', 'i32') }}}; + var len = {{{ makeGetValue('iov', 'i*8 + 4', 'i32') }}}; + var curr = await PThreadFS.write(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset); + if (curr < 0) return -1; + ret += curr; + } + return ret; + }, + + // arguments handling + + varargs: undefined, + + get: function() { +#if ASSERTIONS + assert(ASYNCSYSCALLS.varargs != undefined); +#endif + ASYNCSYSCALLS.varargs += 4; + var ret = {{{ makeGetValue('ASYNCSYSCALLS.varargs', '-4', 'i32') }}}; +#if SYSCALL_DEBUG + err(' (raw: "' + ret + '")'); +#endif + return ret; + }, + getStr: function(ptr) { + var ret = UTF8ToString(ptr); +#if SYSCALL_DEBUG + err(' (str: "' + ret + '")'); +#endif + return ret; + }, + getStreamFromFD: async function(fd) { + var stream = await PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); +#if SYSCALL_DEBUG + err(' (stream: "' + stream.path + '")'); +#endif + return stream; + }, + get64: function(low, high) { +#if ASSERTIONS + if (low >= 0) assert(high === 0); + else assert(high === -1); +#endif + return low; + }, + + $syscallMmap2_async__deps: ['$ASYNCSYSCALLS', '$zeroMemory', '$mmapAlloc', '$PThreadFS', + ], + $syscallMmap2_async: async function(addr, len, prot, flags, fd, off) { + off <<= 12; // undo pgoffset + var ptr; + var allocated = false; + + // addr argument must be page aligned if MAP_FIXED flag is set. + if ((flags & {{{ cDefine('MAP_FIXED') }}}) !== 0 && (addr % {{{ WASM_PAGE_SIZE }}}) !== 0) { + return -{{{ cDefine('EINVAL') }}}; + } + + // MAP_ANONYMOUS (aka MAP_ANON) isn't actually defined by POSIX spec, + // but it is widely used way to allocate memory pages on Linux, BSD and Mac. + // In this case fd argument is ignored. + if ((flags & {{{ cDefine('MAP_ANONYMOUS') }}}) !== 0) { + ptr = mmapAlloc(len); + if (!ptr) return -{{{ cDefine('ENOMEM') }}}; + allocated = true; + } else { + var info = await PThreadFS.getStream(fd); + if (!info) return -{{{ cDefine('EBADF') }}}; + var res = await PThreadFS.mmap(info, addr, len, off, prot, flags); + ptr = res.ptr; + allocated = res.allocated; + } + #if CAN_ADDRESS_2GB + ptr >>>= 0; + #endif + ASYNCSYSCALLS.mappings[ptr] = { malloc: ptr, len: len, allocated: allocated, fd: fd, prot: prot, flags: flags, offset: off }; + return ptr; + }, + }, + + $syscallMunmap_async__deps: ['$ASYNCSYSCALLS', '$PThreadFS'], + $syscallMunmap_async: async function(addr, len) { + #if CAN_ADDRESS_2GB + addr >>>= 0; + #endif + // TODO: support unmmap'ing parts of allocations + var info = ASYNCSYSCALLS.mappings[addr]; + if (len === 0 || !info) { + return -{{{ cDefine('EINVAL') }}}; + } + if (len === info.len) { + var stream = await PThreadFS.getStream(info.fd); + if (stream) { + if (info.prot & {{{ cDefine('PROT_WRITE') }}}) { + await ASYNCSYSCALLS.doMsync(addr, stream, len, info.flags, info.offset); + } + await PThreadFS.munmap(stream); + } + ASYNCSYSCALLS.mappings[addr] = null; + if (info.allocated) { + _free(info.malloc); + } + } + return 0; + }, + + // WASI + fd_write_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var num = await ASYNCSYSCALLS.doWritev(stream, iov, iovcnt); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_read_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var num = await ASYNCSYSCALLS.doReadv(stream, iov, iovcnt); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_close_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + await PThreadFS.close(stream); + return 0; + }, + fd_pwrite_async: async function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var num = await ASYNCSYSCALLS.doWritev(stream, iov, iovcnt, offset_low); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_pread_async: async function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var num = await ASYNCSYSCALLS.doReadv(stream, iov, iovcnt, offset_low); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_seek_async: async function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var HIGH_OFFSET = 0x100000000; // 2^32 + // use an unsigned operator on low and shift high by 32-bits + var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0); + + var DOUBLE_LIMIT = 0x20000000000000; // 2^53 + // we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT + if (offset <= -DOUBLE_LIMIT || offset >= DOUBLE_LIMIT) { + return -{{{ cDefine('EOVERFLOW') }}}; + } + + await PThreadFS.llseek(stream, offset, whence); + {{{ makeSetValue('newOffset', '0', 'stream.position', 'i64') }}}; + if (stream.getdents && offset === 0 && whence === {{{ cDefine('SEEK_SET') }}}) stream.getdents = null; // reset readdir state + return 0; + }, + fd_fdstat_get_async: async function(fd, pbuf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + // All character devices are terminals (other things a Linux system would + // assume is a character device, like the mouse, we have special APIs for). + var type = stream.tty ? {{{ cDefine('__WASI_FILETYPE_CHARACTER_DEVICE') }}} : + PThreadFS.isDir(stream.mode) ? {{{ cDefine('__WASI_FILETYPE_DIRECTORY') }}} : + PThreadFS.isLink(stream.mode) ? {{{ cDefine('__WASI_FILETYPE_SYMBOLIC_LINK') }}} : + {{{ cDefine('__WASI_FILETYPE_REGULAR_FILE') }}}; + {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_filetype, 'type', 'i8') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_flags, '?', 'i16') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_base, '?', 'i64') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_inheriting, '?', 'i64') }}}; + return 0; + }, + fd_sync_async: async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + if (stream.stream_ops && stream.stream_ops.fsync) { + let res = await stream.stream_ops.fsync(stream); + return await -res; + } + return 0; // TODO(rstz): Consider adding functionality here! + }, + + // Syscalls + open_async: async function(path, flags, mode) { + var pathname = ASYNCSYSCALLS.getStr(path); + var stream = await PThreadFS.open(pathname, flags, mode); + return stream.fd; + }, + unlink_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.unlink(path); + return 0; + }, + chdir_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chdir(path); + return 0; + }, + mknod_async : async function(path, mode, dev) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doMknod(path, mode, dev); + }, + chmod_async : async function(path, mode) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chmod(path, mode); + return 0; + }, + access_async : async function(path, amode) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doAccess(path, amode); + }, + rename_async : async function(old_path, new_path) { + old_path = ASYNCSYSCALLS.getStr(old_path); + new_path = ASYNCSYSCALLS.getStr(new_path); + await PThreadFS.rename(old_path, new_path); + return 0; + }, + mkdir_async : async function(path, mode) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doMkdir(path, mode); + }, + rmdir_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.rmdir(path); + return 0; + }, + dup_async : async function(fd) { + let old = await ASYNCSYSCALLS.getStreamFromFD(fd); + let stream = await PThreadFS.open(old.path, old.flags, 0); + return stream.fd; + }, + ioctl_async: async function(fd, op, varargs) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + switch (op) { + case {{{ cDefine('TCGETA') }}}: + case {{{ cDefine('TCGETS') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + case {{{ cDefine('TCSETA') }}}: + case {{{ cDefine('TCSETAW') }}}: + case {{{ cDefine('TCSETAF') }}}: + case {{{ cDefine('TCSETS') }}}: + case {{{ cDefine('TCSETSW') }}}: + case {{{ cDefine('TCSETSF') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; // no-op, not actually adjusting terminal settings + } + case {{{ cDefine('TIOCGPGRP') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + var argp = ASYNCSYSCALLS.get(); + {{{ makeSetValue('argp', 0, 0, 'i32') }}}; + return 0; + } + case {{{ cDefine('TIOCSPGRP') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return -{{{ cDefine('EINVAL') }}}; // not supported + } + case {{{ cDefine('FIONREAD') }}}: { + var argp = ASYNCSYSCALLS.get(); + return await PThreadFS.ioctl(stream, op, argp); + } + case {{{ cDefine('TIOCGWINSZ') }}}: { + // TODO: in theory we should write to the winsize struct that gets + // passed in, but for now musl doesn't read anything on it + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + case {{{ cDefine('TIOCSWINSZ') }}}: { + // TODO: technically, this ioctl call should change the window size. + // but, since emscripten doesn't have any concept of a terminal window + // yet, we'll just silently throw it away as we do TIOCGWINSZ + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + default: abort('bad ioctl syscall ' + op); + } + }, + dup2_async : async function(oldfd, suggestFD) { + var old = await ASYNCSYSCALLS.getStreamFromFD(oldfd); + if (old.fd === suggestFD) return suggestFD; + return await ASYNCSYSCALLS.doDup(old.path, old.flags, suggestFD); + }, + symlink_async : async function(target, linkpath) { + target = ASYNCSYSCALLS.getStr(target); + linkpath = ASYNCSYSCALLS.getStr(linkpath); + await PThreadFS.symlink(target, linkpath); + return 0; + }, + readlink_async : async function(path, buf, bufsize) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doReadlink(path, buf, bufsize); + }, + munmap_async__deps: ['$syscallMunmap_async'], + munmap_async : async function(addr, len) { + return await syscallMunmap_async(addr, len); + }, + fchmod_async : async function(fd, mode) { + await PThreadFS.fchmod(fd, mode); + return 0; + }, + fchdir_async : async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + await PThreadFS.chdir(stream.path); + return 0; + }, + _newselect_async : async function(nfds, readfds, writefds, exceptfds, timeout) { + // readfds are supported, + // writefds checks socket open status + // exceptfds not supported + // timeout is always 0 - fully async + #if ASSERTIONS + assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits // TODO: this could be 1024 based on current musl headers + assert(!exceptfds, 'exceptfds not supported'); + #endif + + var total = 0; + + var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0), + srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0); + var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0), + srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0); + var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0), + srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var dstReadLow = 0, + dstReadHigh = 0; + var dstWriteLow = 0, + dstWriteHigh = 0; + var dstExceptLow = 0, + dstExceptHigh = 0; + + var allLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0); + var allHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var check = function(fd, low, high, val) { + return (fd < 32 ? (low & val) : (high & val)); + }; + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32); + if (!(check(fd, allLow, allHigh, mask))) { + continue; // index isn't in the set + } + + var stream = await PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + + var flags = ASYNCSYSCALLS.DEFAULT_POLLMASK; + + if (stream.stream_ops.poll) { + flags = await stream.stream_ops.poll(stream); + } + + if ((flags & {{{ cDefine('POLLIN') }}}) && check(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLOUT') }}}) && check(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLPRI') }}}) && check(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } + + if (readfds) { + {{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}}; + {{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}}; + } + if (writefds) { + {{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}}; + {{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}}; + } + if (exceptfds) { + {{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}}; + {{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}}; + } + + return total; + }, + msync_async: async function(addr, len, flags) { + #if CAN_ADDRESS_2GB + addr >>>= 0; + #endif + var info = ASYNCSYSCALLS.mappings[addr]; + if (!info) return 0; + await ASYNCSYSCALLS.doMsync(addr, await PThreadFS.getStream(info.fd), len, info.flags, 0); + return 0; + }, + fdatasync_async : async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + //TODO(rstz): Consider implementing this, since Storage Foundation supports flush(). + return 0; + }, + poll_async : async function(fds, nfds, timeout) { + var nonzero = 0; + for (var i = 0; i < nfds; i++) { + var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i; + var fd = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}}; + var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}}; + var mask = {{{ cDefine('POLLNVAL') }}}; + var stream = await PThreadFS.getStream(fd); + if (stream) { + mask = ASYNCSYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops.poll) { + mask = stream.stream_ops.poll(stream); + } + } + mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) nonzero++; + {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'mask', 'i16') }}}; + } + return nonzero; + }, + getcwd_async : async function(buf, size) { + if (size === 0) return -{{{ cDefine('EINVAL') }}}; + var cwd = await PThreadFS.cwd(); + var cwdLengthInBytes = lengthBytesUTF8(cwd); + if (size < cwdLengthInBytes + 1) return -{{{ cDefine('ERANGE') }}}; + stringToUTF8(cwd, buf, size); + return buf; + }, + mmap2_async__deps: ['$syscallMmap2'], + mmap2_async: async function(addr, len, prot, flags, fd, off) { + return await syscallMmap2(addr, len, prot, flags, fd, off); + }, + truncate64_async: async function(path, zero, low, high) { + path = ASYNCSYSCALLS.getStr(path); + var length = ASYNCSYSCALLS.get64(low, high); + await PThreadFS.truncate(path, length); + return 0; + }, + ftruncate64_async : async function(fd, zero, low, high) { + var length = ASYNCSYSCALLS.get64(low, high); + await PThreadFS.ftruncate(fd, length); + return 0; + }, + stat64_async : async function(path, buf) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doStat(PThreadFS.stat, path, buf); + }, + lstat64_async : async function(path, buf) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doStat(PThreadFS.lstat, path, buf); + }, + fstat64_async : async function(fd, buf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + return await ASYNCSYSCALLS.doStat(PThreadFS.stat, stream.path, buf); + }, + lchown32_async : async function(path, owner, group) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chown(path, owner, group); // XXX we ignore the 'l' aspect, and do the same as chown + return 0; + }, + fchown32_async: async function(fd, owner, group) { + await PThreadFS.fchown(fd, owner, group); + return 0; + }, + chown32_async: async function(path, owner, group) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chown(path, owner, group); + return 0; + }, + getdents64_async: async function(fd, dirp, count) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + if (!stream.getdents) { + stream.getdents = await PThreadFS.readdir(stream.path); + } + + var struct_size = {{{ C_STRUCTS.dirent.__size__ }}}; + var pos = 0; + var off = await PThreadFS.llseek(stream, 0, {{{ cDefine('SEEK_CUR') }}}); + + var idx = Math.floor(off / struct_size); + + while (idx < stream.getdents.length && pos + struct_size <= count) { + var id; + var type; + var name = stream.getdents[idx]; + if (name[0] === '.') { + id = 1; + type = 4; // DT_DIR + } else { + var child = await PThreadFS.lookupNode(stream.node, name); + id = child.id; + type = PThreadFS.isChrdev(child.mode) ? 2 : // DT_CHR, character device. + PThreadFS.isDir(child.mode) ? 4 : // DT_DIR, directory. + PThreadFS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link. + 8; // DT_REG, regular file. + } + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_ino, 'id', 'i64') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_off, '(idx + 1) * struct_size', 'i64') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_reclen, C_STRUCTS.dirent.__size__, 'i16') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_type, 'type', 'i8') }}}; + stringToUTF8(name, dirp + pos + {{{ C_STRUCTS.dirent.d_name }}}, 256); + pos += struct_size; + idx += 1; + } + await PThreadFS.llseek(stream, idx * struct_size, {{{ cDefine('SEEK_SET') }}}); + return pos; + }, + fcntl64_async__deps: ['$setErrNo'], + fcntl64_async: async function(fd, cmd, varargs) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + switch (cmd) { + case {{{ cDefine('F_DUPFD') }}}: { + var arg = ASYNCSYSCALLS.get(); + if (arg < 0) { + return -{{{ cDefine('EINVAL') }}}; + } + var newStream; + newStream = await PThreadFS.open(stream.path, stream.flags, 0, arg); + return newStream.fd; + } + case {{{ cDefine('F_GETFD') }}}: + case {{{ cDefine('F_SETFD') }}}: + return 0; // FD_CLOEXEC makes no sense for a single process. + case {{{ cDefine('F_GETFL') }}}: + return stream.flags; + case {{{ cDefine('F_SETFL') }}}: { + var arg = ASYNCSYSCALLS.get(); + stream.flags |= arg; + return 0; + } + case {{{ cDefine('F_GETLK') }}}: + /* case {{{ cDefine('F_GETLK64') }}}: Currently in musl F_GETLK64 has same value as F_GETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ { + {{{ assert(cDefine('F_GETLK') === cDefine('F_GETLK64')), '' }}} + var arg = ASYNCSYSCALLS.get(); + var offset = {{{ C_STRUCTS.flock.l_type }}}; + // We're always unlocked. + {{{ makeSetValue('arg', 'offset', cDefine('F_UNLCK'), 'i16') }}}; + return 0; + } + case {{{ cDefine('F_SETLK') }}}: + case {{{ cDefine('F_SETLKW') }}}: + /* case {{{ cDefine('F_SETLK64') }}}: Currently in musl F_SETLK64 has same value as F_SETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ + /* case {{{ cDefine('F_SETLKW64') }}}: Currently in musl F_SETLKW64 has same value as F_SETLKW, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ + {{{ assert(cDefine('F_SETLK64') === cDefine('F_SETLK')), '' }}} + {{{ assert(cDefine('F_SETLKW64') === cDefine('F_SETLKW')), '' }}} + return 0; // Pretend that the locking is successful. + case {{{ cDefine('F_GETOWN_EX') }}}: + case {{{ cDefine('F_SETOWN') }}}: + return -{{{ cDefine('EINVAL') }}}; // These are for sockets. We don't have them fully implemented yet. + case {{{ cDefine('F_GETOWN') }}}: + // musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fnctl() returns that, and we set errno ourselves. + setErrNo({{{ cDefine('EINVAL') }}}); + return -1; + default: { + return -{{{ cDefine('EINVAL') }}}; + } + } + }, + statfs64_async: function(path, size, buf) { + path = ASYNCSYSCALLS.getStr(path); + // NOTE: None of the constants here are true. We're just returning safe and + // sane values. + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, '1000000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, '500000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, '500000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'PThreadFS.nextInode', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, '1000000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, '42', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, '2', 'i32') }}}; // ST_NOSUID + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, '255', 'i32') }}}; + return 0; + }, + fstatfs64_async: async function(fd, size, buf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + return _statfs64(0, size, buf); + }, + openat_async: async function(dirfd, path, flags, varargs) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + var mode = varargs ? ASYNCSYSCALLS.get() : 0; + let stream = await PThreadFS.open(path, flags, mode); + return stream.fd; + }, + mkdirat_async: async function(dirfd, path, mode) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return ASYNCSYSCALLS.doMkdir(path, mode); + }, + mknodat_async: async function(dirfd, path, mode, dev) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return ASYNCSYSCALLS.doMknod(path, mode, dev); + }, + fchownat_async: async function(dirfd, path, owner, group, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + await PThreadFS.chown(path, owner, group); + return 0; + }, + fstatat64_async: async function(dirfd, path, buf, flags) { + path = ASYNCSYSCALLS.getStr(path); + var nofollow = flags & {{{ cDefine('AT_SYMLINK_NOFOLLOW') }}}; + var allowEmpty = flags & {{{ cDefine('AT_EMPTY_PATH') }}}; + flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') | cDefine('AT_EMPTY_PATH') }}}); + path = ASYNCSYSCALLS.calculateAt(dirfd, path, allowEmpty); + return await ASYNCSYSCALLS.doStat(nofollow ? PThreadFS.lstat : PThreadFS.stat, path, buf); + }, + unlinkat_async: async function(dirfd, path, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + if (flags === 0) { + await PThreadFS.unlink(path); + } else if (flags === {{{ cDefine('AT_REMOVEDIR') }}}) { + await PThreadFS.rmdir(path); + } else { + abort('Invalid flags passed to unlinkat'); + } + return 0; + }, + renameat_async: async function(olddirfd, oldpath, newdirfd, newpath) { + oldpath = ASYNCSYSCALLS.getStr(oldpath); + newpath = ASYNCSYSCALLS.getStr(newpath); + oldpath = ASYNCSYSCALLS.calculateAt(olddirfd, oldpath); + newpath = ASYNCSYSCALLS.calculateAt(newdirfd, newpath); + await PThreadFS.rename(oldpath, newpath); + return 0; + }, + symlinkat_async: async function(target, newdirfd, linkpath) { + linkpath = ASYNCSYSCALLS.calculateAt(newdirfd, linkpath); + await PThreadFS.symlink(target, linkpath); + return 0; + }, + readlinkat_async: async function(dirfd, path, buf, bufsize) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return await ASYNCSYSCALLS.doReadlink(path, buf, bufsize); + }, + fchmodat_async: async function(dirfd, path, mode, varargs) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + await PThreadFS.chmod(path, mode); + return 0; + }, + faccessat_async: async function(dirfd, path, amode, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return await ASYNCSYSCALLS.doAccess(path, amode); + }, + utimensat_async: async function(dirfd, path, times, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path, true); + var seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i32') }}}; + var nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; + var atime = (seconds*1000) + (nanoseconds/(1000*1000)); + times += {{{ C_STRUCTS.timespec.__size__ }}}; + seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i32') }}}; + nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; + var mtime = (seconds*1000) + (nanoseconds/(1000*1000)); + await PThreadFS.utime(path, atime, mtime); + return 0; + }, + fallocate_async: async function(fd, mode, off_low, off_high, len_low, len_high) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var offset = ASYNCSYSCALLS.get64(off_low, off_high); + var len = ASYNCSYSCALLS.get64(len_low, len_high); + await PThreadFS.allocate(stream, offset, len); + return 0; + }, + dup3_async: async function(fd, suggestFD, flags) { + var old = await ASYNCSYSCALLS.getStreamFromFD(fd); + if (old.fd === suggestFD) return -{{{ cDefine('EINVAL') }}}; + return await ASYNCSYSCALLS.doDup(old.path, old.flags, suggestFD); + }, +}; + +function wrapSyscallFunction(x, library, isWasi) { + if (x[0] === '$' || isJsLibraryConfigIdentifier(x)) { + return; + } + + var t = library[x]; + if (typeof t === 'string') return; + t = t.toString(); + + var isVariadic = !isWasi && t.includes(', varargs'); + var canThrow = library[x + '__nothrow'] !== true; + + var pre = '', post = ''; + if (isVariadic) { + pre += 'ASYNCSYSCALLS.varargs = varargs;\n'; + } + + delete library[x + '__nothrow']; + var handler = ''; + if (canThrow) { + pre += 'try {\n'; + handler += + "} catch (e) {\n" + + " if (typeof PThreadFS === 'undefined' || !(e instanceof PThreadFS.ErrnoError)) abort(e);\n"; + + // Musl syscalls are negated. + if (isWasi) { + handler += " return e.errno;\n"; + } else { + // Musl syscalls are negated. + handler += " return -e.errno;\n"; + } + handler += "}\n"; + } + post = handler + post; + + if (pre) { + var bodyStart = t.indexOf('{') + 1; + t = t.substring(0, bodyStart) + pre + t.substring(bodyStart); + } + if (post) { + var bodyEnd = t.lastIndexOf('}'); + t = t.substring(0, bodyEnd) + post + t.substring(bodyEnd); + } + library[x] = eval('(' + t + ')'); + if (!library[x + '__deps']) library[x + '__deps'] = []; + library[x + '__deps'].push('$ASYNCSYSCALLS'); +} + +for (var x in SyscallsLibrary) { + let isWasi = x.startsWith('fd_'); + wrapSyscallFunction(x, SyscallsLibrary, isWasi); +} + +mergeInto(LibraryManager.library, SyscallsLibrary);/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + + mergeInto(LibraryManager.library, { + $PThreadFS__deps: ['$getRandomDevice', '$PATH', '$PATH_FS', '$TTY_ASYNC', '$MEMFS_ASYNC', +#if ASSERTIONS + '$ERRNO_MESSAGES', '$ERRNO_CODES', +#endif + ], + $PThreadFS: { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + // Whether we are currently ignoring permissions. Useful when preparing the + // filesystem and creating files inside read-only folders. + // This is set to false during `preInit`, allowing you to modify the + // filesystem freely up until that point (e.g. during `preRun`). + ignorePermissions: true, + trackingDelegate: {}, + tracking: { + openFlags: { + READ: 1 << 0, + WRITE: 1 << 1 + } + }, + ErrnoError: null, // set during init + genericErrors: {}, + filesystems: null, + syncFSRequests: 0, // we warn if there are multiple in flight at once + + // + // paths + // + lookupPath: async function(path, opts) { + path = PATH_FS.resolve(PThreadFS.cwd(), path); + opts = opts || {}; + + if (!path) return { path: '', node: null }; + + var defaults = { + follow_mount: true, + recurse_count: 0 + }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } + + if (opts.recurse_count > 8) { // max recursive lookup of 8 + throw new PThreadFS.ErrnoError({{{ cDefine('ELOOP') }}}); + } + + // split the path + var parts = PATH.normalizeArray(path.split('/').filter(function(p) { + return !!p; + }), false); + + // start at the root + var current = PThreadFS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = (i === parts.length-1); + if (islast && opts.parent) { + // stop resolving + break; + } + + current = await PThreadFS.lookupNode(current, parts[i]); + current_path = PATH.join2(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (PThreadFS.isMountpoint(current)) { + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } + } + + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (!islast || opts.follow) { + var count = 0; + while (PThreadFS.isLink(current.mode)) { + var link = await PThreadFS.readlink(current_path); + current_path = PATH_FS.resolve(PATH.dirname(current_path), link); + + var lookup = await PThreadFS.lookupPath(current_path, { recurse_count: opts.recurse_count }); + current = lookup.node; + + if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + throw new PThreadFS.ErrnoError({{{ cDefine('ELOOP') }}}); + } + } + } + } + + return { path: current_path, node: current }; + }, + getPath: function(node) { + var path; + while (true) { + if (PThreadFS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length-1] !== '/' ? mount + '/' + path : mount + path; + } + path = path ? node.name + '/' + path : node.name; + node = node.parent; + } + }, + + // + // nodes + // + hashName: function(parentid, name) { + var hash = 0; + +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % PThreadFS.nameTable.length; + }, + hashAddNode: function(node) { + var hash = PThreadFS.hashName(node.parent.id, node.name); + node.name_next = PThreadFS.nameTable[hash]; + PThreadFS.nameTable[hash] = node; + }, + hashRemoveNode: function(node) { + var hash = PThreadFS.hashName(node.parent.id, node.name); + if (PThreadFS.nameTable[hash] === node) { + PThreadFS.nameTable[hash] = node.name_next; + } else { + var current = PThreadFS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode: async function(parent, name) { + var errCode = PThreadFS.mayLookup(parent); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode, parent); + } + var hash = PThreadFS.hashName(parent.id, name); +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + for (var node = PThreadFS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; +#if CASE_INSENSITIVE_FS + nodeName = nodeName.toLowerCase(); +#endif + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return await PThreadFS.lookup(parent, name); + }, + createNode: function(parent, name, mode, rdev) { +#if ASSERTIONS + assert(typeof parent === 'object') +#endif + var node = new PThreadFS.FSNode(parent, name, mode, rdev); + + PThreadFS.hashAddNode(node); + + return node; + }, + destroyNode: function(node) { + PThreadFS.hashRemoveNode(node); + }, + isRoot: function(node) { + return node === node.parent; + }, + isMountpoint: function(node) { + return !!node.mounted; + }, + isFile: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}}; + }, + isDir: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFDIR') }}}; + }, + isLink: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFLNK') }}}; + }, + isChrdev: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFCHR') }}}; + }, + isBlkdev: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFBLK') }}}; + }, + isFIFO: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; + }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, + + // + // permissions + // + flagModes: { + // Extra quotes used here on the keys to this object otherwise jsifier will + // erase them in the process of reading and then writing the JS library + // code. + '"r"': {{{ cDefine('O_RDONLY') }}}, + '"r+"': {{{ cDefine('O_RDWR') }}}, + '"w"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"w+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + '"a"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"a+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + }, + // convert the 'r', 'r+', etc. to it's corresponding set of O_* flags + modeStringToFlags: function(str) { + var flags = PThreadFS.flagModes[str]; + if (typeof flags === 'undefined') { + throw new Error('Unknown file open mode: ' + str); + } + return flags; + }, + // convert O_* bitmask to a string for nodePermissions + flagsToPermissionString: function(flag) { + var perms = ['r', 'w', 'rw'][flag & 3]; + if ((flag & {{{ cDefine('O_TRUNC') }}})) { + perms += 'w'; + } + return perms; + }, + nodePermissions: function(node, perms) { + if (PThreadFS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.includes('r') && !(node.mode & {{{ cDefine('S_IRUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } else if (perms.includes('w') && !(node.mode & {{{ cDefine('S_IWUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } else if (perms.includes('x') && !(node.mode & {{{ cDefine('S_IXUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } + return 0; + }, + mayLookup: function(dir) { + var errCode = PThreadFS.nodePermissions(dir, 'x'); + if (errCode) return errCode; + if (!dir.node_ops.lookup) return {{{ cDefine('EACCES') }}}; + return 0; + }, + mayCreate: async function(dir, name) { + try { + var node = await PThreadFS.lookupNode(dir, name); + return {{{ cDefine('EEXIST') }}}; + } catch (e) { + } + return PThreadFS.nodePermissions(dir, 'wx'); + }, + mayDelete: async function(dir, name, isdir) { + var node; + try { + node = await PThreadFS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var errCode = PThreadFS.nodePermissions(dir, 'wx'); + if (errCode) { + return errCode; + } + if (isdir) { + if (!PThreadFS.isDir(node.mode)) { + return {{{ cDefine('ENOTDIR') }}}; + } + if (PThreadFS.isRoot(node) || PThreadFS.getPath(node) === PThreadFS.cwd()) { + return {{{ cDefine('EBUSY') }}}; + } + } else { + if (PThreadFS.isDir(node.mode)) { + return {{{ cDefine('EISDIR') }}}; + } + } + return 0; + }, + mayOpen: function(node, flags) { + if (!node) { + return {{{ cDefine('ENOENT') }}}; + } + if (PThreadFS.isLink(node.mode)) { + return {{{ cDefine('ELOOP') }}}; + } else if (PThreadFS.isDir(node.mode)) { + if (PThreadFS.flagsToPermissionString(flags) !== 'r' || // opening for write + (flags & {{{ cDefine('O_TRUNC') }}})) { // TODO: check for O_SEARCH? (== search for dir only) + return {{{ cDefine('EISDIR') }}}; + } + } + return PThreadFS.nodePermissions(node, PThreadFS.flagsToPermissionString(flags)); + }, + + // + // streams + // + MAX_OPEN_FDS: 4096, + nextfd: function(fd_start, fd_end) { + fd_start = fd_start || 0; + fd_end = fd_end || PThreadFS.MAX_OPEN_FDS; + for (var fd = fd_start; fd <= fd_end; fd++) { + if (!PThreadFS.streams[fd]) { + return fd; + } + } + throw new PThreadFS.ErrnoError({{{ cDefine('EMFILE') }}}); + }, + getStream: function(fd) { + return PThreadFS.streams[fd]; + }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. + createStream: function(stream, fd_start, fd_end) { + if (!PThreadFS.FSStream) { + PThreadFS.FSStream = /** @constructor */ function(){}; + PThreadFS.FSStream.prototype = { + object: { + get: function() { return this.node; }, + set: function(val) { this.node = val; } + }, + isRead: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; } + }, + isWrite: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; } + }, + isAppend: { + get: function() { return (this.flags & {{{ cDefine('O_APPEND') }}}); } + } + }; + } + // clone it, so we can return an instance of FSStream + var newStream = new PThreadFS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; + } + stream = newStream; + var fd = PThreadFS.nextfd(fd_start, fd_end); + stream.fd = fd; + PThreadFS.streams[fd] = stream; + return stream; + }, + closeStream: function(fd) { + PThreadFS.streams[fd] = null; + }, + + // + // devices + // + // each character device consists of a device id + stream operations. + // when a character device node is created (e.g. /dev/stdin) it is + // assigned a device id that lets us map back to the actual device. + // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. + // however, once opened, the stream's operations are overridden with + // the operations of the device its underlying node maps back to. + chrdev_stream_ops: { + open: function(stream) { + var device = PThreadFS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + }, + llseek: function() { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + }, + major: function(dev) { + return ((dev) >> 8); + }, + minor: function(dev) { + return ((dev) & 0xff); + }, + makedev: function(ma, mi) { + return ((ma) << 8 | (mi)); + }, + registerDevice: function(dev, ops) { + PThreadFS.devices[dev] = { stream_ops: ops }; + }, + getDevice: function(dev) { + return PThreadFS.devices[dev]; + }, + + // + // core + // + getMounts: function(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push.apply(check, m.mounts); + } + + return mounts; + }, + syncfs: function(populate, callback) { + if (typeof(populate) === 'function') { + callback = populate; + populate = false; + } + + PThreadFS.syncFSRequests++; + + if (PThreadFS.syncFSRequests > 1) { + err('warning: ' + PThreadFS.syncFSRequests + ' PThreadFS.syncfs operations in flight at once, probably just doing extra work'); + } + + var mounts = PThreadFS.getMounts(PThreadFS.root.mount); + var completed = 0; + + function doCallback(errCode) { +#if ASSERTIONS + assert(PThreadFS.syncFSRequests > 0); +#endif + PThreadFS.syncFSRequests--; + return callback(errCode); + } + + function done(errCode) { + if (errCode) { + if (!done.errored) { + done.errored = true; + return doCallback(errCode); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + }; + + // sync all mounts + mounts.forEach(function (mount) { + if (!mount.type.syncfs) { + return done(null); + } + mount.type.syncfs(mount, populate, done); + }); + }, + mount: async function(type, opts, mountpoint) { +#if ASSERTIONS + if (typeof type === 'string') { + // The filesystem was not included, and instead we have an error + // message stored in the variable. + throw type; + } +#endif + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && PThreadFS.root) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } else if (!root && !pseudo) { + var lookup = await PThreadFS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + + if (!PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + } + + var mount = { + type: type, + opts: opts, + mountpoint: mountpoint, + mounts: [] + }; + + // create a root node for the fs + var mountRoot = await type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + PThreadFS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + + return mountRoot; + }, + unmount: async function (mountpoint) { + var lookup = await PThreadFS.lookupPath(mountpoint, { follow_mount: false }); + + if (!PThreadFS.isMountpoint(lookup.node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = PThreadFS.getMounts(mount); + + Object.keys(PThreadFS.nameTable).forEach(function (hash) { + var current = PThreadFS.nameTable[hash]; + + while (current) { + var next = current.name_next; + + if (mounts.includes(current.mount)) { + PThreadFS.destroyNode(current); + } + + current = next; + } + }); + + // no longer a mountpoint + node.mounted = null; + + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); +#if ASSERTIONS + assert(idx !== -1); +#endif + node.mount.mounts.splice(idx, 1); + }, + lookup: async function(parent, name) { + return await parent.node_ops.lookup(parent, name); + }, + // generic function for all node creation + mknod: async function(path, mode, dev) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name || name === '.' || name === '..') { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var errCode = await PThreadFS.mayCreate(parent, name); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.mknod) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return await parent.node_ops.mknod(parent, name, mode, dev); + }, + // helpers to create specific types of nodes + create: async function(path, mode) { + mode = mode !== undefined ? mode : 438 /* 0666 */; + mode &= {{{ cDefine('S_IALLUGO') }}}; + mode |= {{{ cDefine('S_IFREG') }}}; + return await PThreadFS.mknod(path, mode, 0); + }, + mkdir: async function(path, mode) { + mode = mode !== undefined ? mode : 511 /* 0777 */; + mode &= {{{ cDefine('S_IRWXUGO') }}} | {{{ cDefine('S_ISVTX') }}}; + mode |= {{{ cDefine('S_IFDIR') }}}; + return await PThreadFS.mknod(path, mode, 0); + }, + // Creates a whole directory tree chain if it doesn't yet exist + mkdirTree: async function(path, mode) { + var dirs = path.split('/'); + var d = ''; + for (var i = 0; i < dirs.length; ++i) { + if (!dirs[i]) continue; + d += '/' + dirs[i]; + try { + await PThreadFS.mkdir(d, mode); + } catch(e) { + if (e.errno != {{{ cDefine('EEXIST') }}}) throw e; + } + } + }, + mkdev: async function(path, mode, dev) { + if (typeof(dev) === 'undefined') { + dev = mode; + mode = 438 /* 0666 */; + } + mode |= {{{ cDefine('S_IFCHR') }}}; + return await PThreadFS.mknod(path, mode, dev); + }, + symlink: async function(oldpath, newpath) { + if (!PATH_FS.resolve(oldpath)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + var lookup = await PThreadFS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + var newname = PATH.basename(newpath); + var errCode = await PThreadFS.mayCreate(parent, newname); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.symlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename: async function(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + // parents must exist + var lookup, old_dir, new_dir; + + // let the errors from non existant directories percolate up + lookup = await PThreadFS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = await PThreadFS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + + if (!old_dir || !new_dir) throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + // need to be part of the same mount + if (old_dir.mount !== new_dir.mount) { + throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}}); + } + // source must exist + var old_node = await PThreadFS.lookupNode(old_dir, old_name); + // old path should not be an ancestor of the new path + var relative = PATH_FS.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + // new path should not be an ancestor of the old path + relative = PATH_FS.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + // see if the new path already exists + var new_node; + try { + new_node = await PThreadFS.lookupNode(new_dir, new_name); + } catch (e) { + // not fatal + } + // early out if nothing needs to change + if (old_node === new_node) { + return; + } + // we'll need to delete the old entry + var isdir = PThreadFS.isDir(old_node.mode); + var errCode = await PThreadFS.mayDelete(old_dir, old_name, isdir); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + // need delete permissions if we'll be overwriting. + // need create permissions if new doesn't already exist. + errCode = new_node ? + await PThreadFS.mayDelete(new_dir, new_name, isdir) : + await PThreadFS.mayCreate(new_dir, new_name); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!old_dir.node_ops.rename) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(old_node) || (new_node && PThreadFS.isMountpoint(new_node))) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + // if we are going to change the parent, check write permissions + if (new_dir !== old_dir) { + errCode = PThreadFS.nodePermissions(old_dir, 'w'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + } + try { + if (PThreadFS.trackingDelegate['willMovePath']) { + PThreadFS.trackingDelegate['willMovePath'](old_path, new_path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } + // remove the node from the lookup hash + PThreadFS.hashRemoveNode(old_node); + // do the underlying fs rename + try { + await old_dir.node_ops.rename(old_node, new_dir, new_name); + } catch (e) { + throw e; + } finally { + // add the node back to the hash (in case node_ops.rename + // changed its name) + PThreadFS.hashAddNode(old_node); + } + try { + if (PThreadFS.trackingDelegate['onMovePath']) PThreadFS.trackingDelegate['onMovePath'](old_path, new_path); + } catch(e) { + err("PThreadFS.trackingDelegate['onMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } + }, + rmdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = await PThreadFS.lookupNode(parent, name); + var errCode = await PThreadFS.mayDelete(parent, name, true); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.rmdir) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + try { + if (PThreadFS.trackingDelegate['willDeletePath']) { + PThreadFS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } + await parent.node_ops.rmdir(parent, name); + PThreadFS.destroyNode(node); + try { + if (PThreadFS.trackingDelegate['onDeletePath']) PThreadFS.trackingDelegate['onDeletePath'](path); + } catch(e) { + err("PThreadFS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } + }, + readdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node.node_ops.readdir) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + return await node.node_ops.readdir(node); + }, + unlink: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = await PThreadFS.lookupNode(parent, name); + var errCode = await PThreadFS.mayDelete(parent, name, false); + if (errCode) { + // According to POSIX, we should map EISDIR to EPERM, but + // we instead do what Linux does (and we must, as we use + // the musl linux libc). + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.unlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + try { + if (PThreadFS.trackingDelegate['willDeletePath']) { + PThreadFS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } + await parent.node_ops.unlink(parent, name); + PThreadFS.destroyNode(node); + try { + if (PThreadFS.trackingDelegate['onDeletePath']) PThreadFS.trackingDelegate['onDeletePath'](path); + } catch(e) { + err("PThreadFS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } + }, + readlink: async function(path) { + var lookup = await PThreadFS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!link.node_ops.readlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return PATH_FS.resolve(PThreadFS.getPath(link.parent), link.node_ops.readlink(link)); + }, + stat: async function(path, dontFollow) { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + if (!node) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!node.node_ops.getattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return await node.node_ops.getattr(node); + }, + lstat: async function(path) { + return await PThreadFS.stat(path, true); + }, + chmod: async function(path, mode, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + await node.node_ops.setattr(node, { + mode: (mode & {{{ cDefine('S_IALLUGO') }}}) | (node.mode & ~{{{ cDefine('S_IALLUGO') }}}), + timestamp: Date.now() + }); + }, + lchmod: async function(path, mode) { + await PThreadFS.chmod(path, mode, true); + }, + fchmod: async function(fd, mode) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await PThreadFS.chmod(stream.node, mode); + }, + chown: async function(path, uid, gid, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + await node.node_ops.setattr(node, { + timestamp: Date.now() + // we ignore the uid / gid for now + }); + }, + lchown: async function(path, uid, gid) { + await PThreadFS.chown(path, uid, gid, true); + }, + fchown: async function(fd, uid, gid) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await PThreadFS.chown(stream.node, uid, gid); + }, + truncate: async function(path, len) { + if (len < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!PThreadFS.isFile(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var errCode = PThreadFS.nodePermissions(node, 'w'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + await node.node_ops.setattr(node, { + size: len, + timestamp: Date.now() + }); + }, + ftruncate: async function(fd, len) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + await PThreadFS.truncate(stream.node, len); + }, + utime: async function(path, atime, mtime) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + var node = lookup.node; + await node.node_ops.setattr(node, { + timestamp: Math.max(atime, mtime) + }); + }, + open: async function(path, flags, mode, fd_start, fd_end) { + if (path === "") { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + flags = typeof flags === 'string' ? PThreadFS.modeStringToFlags(flags) : flags; + mode = typeof mode === 'undefined' ? 438 /* 0666 */ : mode; + if ((flags & {{{ cDefine('O_CREAT') }}})) { + mode = (mode & {{{ cDefine('S_IALLUGO') }}}) | {{{ cDefine('S_IFREG') }}}; + } else { + mode = 0; + } + var node; + if (typeof path === 'object') { + node = path; + } else { + path = PATH.normalize(path); + try { + var lookup = await PThreadFS.lookupPath(path, { + follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}}) + }); + node = lookup.node; + } catch (e) { + // ignore + } + } + // perhaps we need to create the node + var created = false; + if ((flags & {{{ cDefine('O_CREAT') }}})) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if ((flags & {{{ cDefine('O_EXCL') }}})) { + throw new PThreadFS.ErrnoError({{{ cDefine('EEXIST') }}}); + } + } else { + // node doesn't exist, try to create it + node = await PThreadFS.mknod(path, mode, 0); + created = true; + } + } + if (!node) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + // can't truncate a device + if (PThreadFS.isChrdev(node.mode)) { + flags &= ~{{{ cDefine('O_TRUNC') }}}; + } + // if asked only for a directory, then this must be one + if ((flags & {{{ cDefine('O_DIRECTORY') }}}) && !PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + // check permissions, if this is not a file we just created now (it is ok to + // create and write to a file with read-only permissions; it is read-only + // for later use) + if (!created) { + var errCode = PThreadFS.mayOpen(node, flags); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + } + // do truncation if necessary + if ((flags & {{{ cDefine('O_TRUNC')}}})) { + await PThreadFS.truncate(node, 0); + } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~({{{ cDefine('O_EXCL') }}} | {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_NOFOLLOW') }}}); + + // register the stream with the filesystem + var stream = PThreadFS.createStream({ + node: node, + path: PThreadFS.getPath(node), // we want the absolute path to the node + flags: flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false + }, fd_start, fd_end); + // call the new stream's open function + if (stream.stream_ops.open) { + await stream.stream_ops.open(stream); + } + if (Module['logReadFiles'] && !(flags & {{{ cDefine('O_WRONLY')}}})) { + if (!PThreadFS.readFiles) PThreadFS.readFiles = {}; + if (!(path in PThreadFS.readFiles)) { + PThreadFS.readFiles[path] = 1; + err("PThreadFS.trackingDelegate error on read file: " + path); + } + } + try { + if (PThreadFS.trackingDelegate['onOpenFile']) { + var trackingFlags = 0; + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}) { + trackingFlags |= PThreadFS.tracking.openFlags.READ; + } + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}) { + trackingFlags |= PThreadFS.tracking.openFlags.WRITE; + } + PThreadFS.trackingDelegate['onOpenFile'](path, trackingFlags); + } + } catch(e) { + err("PThreadFS.trackingDelegate['onOpenFile']('"+path+"', flags) threw an exception: " + e.message); + } + return stream; + }, + close: async function(stream) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (stream.getdents) stream.getdents = null; // free readdir state + try { + if (stream.stream_ops.close) { + await stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + PThreadFS.closeStream(stream.fd); + } + stream.fd = null; + }, + isClosed: function(stream) { + return stream.fd === null; + }, + llseek: async function(stream, offset, whence) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + if (whence != {{{ cDefine('SEEK_SET') }}} && whence != {{{ cDefine('SEEK_CUR') }}} && whence != {{{ cDefine('SEEK_END') }}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + stream.position = await stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read: async function(stream, buffer, offset, length, position) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (length < 0 || position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!stream.stream_ops.read) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var seeking = typeof position !== 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + var bytesRead = await stream.stream_ops.read(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write: async function(stream, buffer, offset, length, position, canOwn) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (length < 0 || position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!stream.stream_ops.write) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (stream.seekable && stream.flags & {{{ cDefine('O_APPEND') }}}) { + // seek to the end before writing in append mode + await PThreadFS.llseek(stream, 0, {{{ cDefine('SEEK_END') }}}); + } + var seeking = typeof position !== 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + var bytesWritten = await stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); + if (!seeking) stream.position += bytesWritten; + try { + if (stream.path && PThreadFS.trackingDelegate['onWriteToFile']) PThreadFS.trackingDelegate['onWriteToFile'](stream.path); + } catch(e) { + err("PThreadFS.trackingDelegate['onWriteToFile']('"+stream.path+"') threw an exception: " + e.message); + } + return bytesWritten; + }, + allocate: async function(stream, offset, length) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (offset < 0 || length <= 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (!PThreadFS.isFile(stream.node.mode) && !PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + if (!stream.stream_ops.allocate) { + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + } + await stream.stream_ops.allocate(stream, offset, length); + }, + mmap: async function(stream, address, length, position, prot, flags) { +#if CAN_ADDRESS_2GB + address >>>= 0; +#endif + // User requests writing to file (prot & PROT_WRITE != 0). + // Checking if we have permissions to write to the file unless + // MAP_PRIVATE flag is set. According to POSIX spec it is possible + // to write to file opened in read-only mode with MAP_PRIVATE flag, + // as all modifications will be visible only in the memory of + // the current process. + if ((prot & {{{ cDefine('PROT_WRITE') }}}) !== 0 + && (flags & {{{ cDefine('MAP_PRIVATE')}}}) === 0 + && (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDWR')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EACCES') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EACCES') }}}); + } + if (!stream.stream_ops.mmap) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + return await stream.stream_ops.mmap(stream, address, length, position, prot, flags); + }, + msync: async function(stream, buffer, offset, length, mmapFlags) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (!stream || !stream.stream_ops.msync) { + return 0; + } + return await stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); + }, + munmap: function(stream) { + return 0; + }, + ioctl: async function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTTY') }}}); + } + return await stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile: async function(path, opts) { + opts = opts || {}; + opts.flags = opts.flags || {{{ cDefine('O_RDONLY') }}}; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + var ret; + var stream = await PThreadFS.open(path, opts.flags); + var stat = await PThreadFS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + await PThreadFS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + ret = UTF8ArrayToString(buf, 0); + } else if (opts.encoding === 'binary') { + ret = buf; + } + await PThreadFS.close(stream); + return ret; + }, + writeFile: async function(path, data, opts) { + opts = opts || {}; + opts.flags = opts.flags || {{{ cDefine('O_TRUNC') | cDefine('O_CREAT') | cDefine('O_WRONLY') }}}; + var stream = await PThreadFS.open(path, opts.flags, opts.mode); + if (typeof data === 'string') { + var buf = new Uint8Array(lengthBytesUTF8(data)+1); + var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); + await PThreadFS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn); + } else if (ArrayBuffer.isView(data)) { + await PThreadFS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); + } else { + throw new Error('Unsupported data type'); + } + await PThreadFS.close(stream); + }, + + // + // module-level FS code + // + cwd: function() { + return PThreadFS.currentPath; + }, + chdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + if (lookup.node === null) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!PThreadFS.isDir(lookup.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + var errCode = PThreadFS.nodePermissions(lookup.node, 'x'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + PThreadFS.currentPath = lookup.path; + }, + createDefaultDirectories: async function() { + await PThreadFS.mkdir('/tmp'); + await PThreadFS.mkdir('/home'); + await PThreadFS.mkdir('/home/web_user'); + }, + createDefaultDevices: async function() { + // create /dev + await PThreadFS.mkdir('/dev'); + // setup /dev/null + PThreadFS.registerDevice(PThreadFS.makedev(1, 3), { + read: function() { return 0; }, + write: function(stream, buffer, offset, length, pos) { return length; } + }); + await PThreadFS.mkdev('/dev/null', PThreadFS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using err() rather than out() + // so we register a second tty just for it. + TTY_ASYNC.register(PThreadFS.makedev(5, 0), TTY_ASYNC.default_tty_ops); + TTY_ASYNC.register(PThreadFS.makedev(6, 0), TTY_ASYNC.default_tty1_ops); + await PThreadFS.mkdev('/dev/tty', PThreadFS.makedev(5, 0)); + await PThreadFS.mkdev('/dev/tty1', PThreadFS.makedev(6, 0)); + // setup /dev/[u]random + var random_device = getRandomDevice(); + await PThreadFS.createDevice('/dev', 'random', random_device); + await PThreadFS.createDevice('/dev', 'urandom', random_device); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + await PThreadFS.mkdir('/dev/shm'); + await PThreadFS.mkdir('/dev/shm/tmp'); + }, + createSpecialDirectories: async function() { + // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the + // name of the stream for fd 6 (see test_unistd_ttyname) + await PThreadFS.mkdir('/proc'); + var proc_self = await PThreadFS.mkdir('/proc/self'); + await PThreadFS.mkdir('/proc/self/fd'); + await PThreadFS.mount({ + mount: function() { + var node = PThreadFS.createNode(proc_self, 'fd', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, {{{ cDefine('S_IXUGO') }}}); + node.node_ops = { + lookup: function(parent, name) { + var fd = +name; + var stream = PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + var ret = { + parent: null, + mount: { mountpoint: 'fake' }, + node_ops: { readlink: function() { return stream.path } } + }; + ret.parent = ret; // make it look like a simple root node + return ret; + } + }; + return node; + } + }, {}, '/proc/self/fd'); + }, + createStandardStreams: async function() { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes PThreadFS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (Module['stdin']) { + await PThreadFS.createDevice('/dev', 'stdin', Module['stdin']); + } else { + await PThreadFS.symlink('/dev/tty', '/dev/stdin'); + } + if (Module['stdout']) { + await PThreadFS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + await PThreadFS.symlink('/dev/tty', '/dev/stdout'); + } + if (Module['stderr']) { + await PThreadFS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + await PThreadFS.symlink('/dev/tty1', '/dev/stderr'); + } + + // open default streams for the stdin, stdout and stderr devices + var stdin = await PThreadFS.open('/dev/stdin', {{{ cDefine('O_RDONLY') }}}); + var stdout = await PThreadFS.open('/dev/stdout', {{{ cDefine('O_WRONLY') }}}); + var stderr = await PThreadFS.open('/dev/stderr', {{{ cDefine('O_WRONLY') }}}); +#if ASSERTIONS + assert(stdin.fd === 0, 'invalid handle for stdin (' + stdin.fd + ')'); + assert(stdout.fd === 1, 'invalid handle for stdout (' + stdout.fd + ')'); + assert(stderr.fd === 2, 'invalid handle for stderr (' + stderr.fd + ')'); +#endif + }, + ensureErrnoError: function() { + if (PThreadFS.ErrnoError) return; + PThreadFS.ErrnoError = /** @this{Object} */ function ErrnoError(errno, node) { + this.node = node; + this.setErrno = /** @this{Object} */ function(errno) { + this.errno = errno; +#if ASSERTIONS + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } +#endif + }; + this.setErrno(errno); +#if ASSERTIONS + this.message = ERRNO_MESSAGES[errno]; +#else + this.message = 'PThreadFS error'; +#endif + +#if ASSERTIONS && !MINIMAL_RUNTIME + // Try to get a maximally helpful stack trace. On Node.js, getting Error.stack + // now ensures it shows what we want. + if (this.stack) { + // Define the stack property for Node.js 4, which otherwise errors on the next line. + Object.defineProperty(this, "stack", { value: (new Error).stack, writable: true }); + this.stack = demangleAll(this.stack); + } +#endif // ASSERTIONS + }; + PThreadFS.ErrnoError.prototype = new Error(); + PThreadFS.ErrnoError.prototype.constructor = PThreadFS.ErrnoError; + // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info) + [{{{ cDefine('ENOENT') }}}].forEach(function(code) { + PThreadFS.genericErrors[code] = new PThreadFS.ErrnoError(code); + PThreadFS.genericErrors[code].stack = ''; + }); + }, + staticInit: async function() { + PThreadFS.ensureErrnoError(); + + PThreadFS.nameTable = new Array(4096); + + await PThreadFS.mount(MEMFS_ASYNC, {}, '/'); + + await PThreadFS.createDefaultDirectories(); + await PThreadFS.createDefaultDevices(); + await PThreadFS.createSpecialDirectories(); + + PThreadFS.filesystems = { + 'MEMFS_ASYNC': MEMFS_ASYNC, + }; + }, + init: async function(input, output, error) { +#if ASSERTIONS + assert(!PThreadFS.init.initialized, 'PThreadFS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); +#endif + PThreadFS.init.initialized = true; + + PThreadFS.ensureErrnoError(); + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + Module['stdin'] = input || Module['stdin']; + Module['stdout'] = output || Module['stdout']; + Module['stderr'] = error || Module['stderr']; + + await PThreadFS.createStandardStreams(); + }, + quit: async function() { + // TODO(rstz): This function is never called. + PThreadFS.init.initialized = false; + // force-flush all streams, so we get musl std streams printed out + var fflush = Module['_fflush']; + if (fflush) fflush(0); + // close all of our streams + for (var i = 0; i < PThreadFS.streams.length; i++) { + var stream = PThreadFS.streams[i]; + if (!stream) { + continue; + } + await PThreadFS.close(stream); + } + }, + + // + // old v1 compatibility functions + // + getMode: function(canRead, canWrite) { + var mode = 0; + if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; + if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}}; + return mode; + }, + findObject: async function(path, dontResolveLastLink) { + var ret = await PThreadFS.analyzePath(path, dontResolveLastLink); + if (ret.exists) { + return ret.object; + } else { + return null; + } + }, + analyzePath: async function(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) { + } + var ret = { + isRoot: false, exists: false, error: 0, name: null, path: null, object: null, + parentExists: false, parentPath: null, parentObject: null + }; + try { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = await PThreadFS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + }; + return ret; + }, + createPath: async function(parent, path, canRead, canWrite) { + parent = typeof parent === 'string' ? parent : PThreadFS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + await PThreadFS.mkdir(current); + } catch (e) { + // ignore EEXIST + } + parent = current; + } + return current; + }, + createFile: async function(parent, name, properties, canRead, canWrite) { + var path = PATH.join2(typeof parent === 'string' ? parent : await PThreadFS.getPath(parent), name); + var mode = await PThreadFS.getMode(canRead, canWrite); + return await PThreadFS.create(path, mode); + }, + createDataFile: async function(parent, name, data, canRead, canWrite, canOwn) { + var path = name ? PATH.join2(typeof parent === 'string' ? parent : await PThreadFS.getPath(parent), name) : parent; + var mode = await PThreadFS.getMode(canRead, canWrite); + var node = await PThreadFS.create(path, mode); + if (data) { + if (typeof data === 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + await PThreadFS.chmod(node, mode | {{{ cDefine('S_IWUGO') }}}); + var stream = await PThreadFS.open(node, {{{ cDefine('O_TRUNC') | cDefine('O_CREAT') | cDefine('O_WRONLY') }}}); + await PThreadFS.write(stream, data, 0, data.length, 0, canOwn); + await PThreadFS.close(stream); + await PThreadFS.chmod(node, mode); + } + return node; + }, + createDevice: async function(parent, name, input, output) { + var path = PATH.join2(typeof parent === 'string' ? parent : PThreadFS.getPath(parent), name); + var mode = PThreadFS.getMode(!!input, !!output); + if (!PThreadFS.createDevice.major) PThreadFS.createDevice.major = 64; + var dev = PThreadFS.makedev(PThreadFS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + PThreadFS.registerDevice(dev, { + open: function(stream) { + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + if (output && output.buffer && output.buffer.length) { + output({{{ charCode('\n') }}}); + } + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (result === undefined && bytesRead === 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EAGAIN') }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset+i]); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }); + return await PThreadFS.mkdev(path, mode, dev); + }, + // Makes sure a file's contents are loaded. Returns whether the file has + // been loaded successfully. No-op for files that have been loaded already. + forceLoadFile: function(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + if (typeof XMLHttpRequest !== 'undefined') { + throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + } else if (read_) { + // Command-line. + try { + // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as + // read() will try to parse UTF8. + obj.contents = intArrayFromString(read_(obj.url), true); + obj.usedBytes = obj.contents.length; + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + } else { + throw new Error('Cannot load without read() or XMLHttpRequest.'); + } + }, + }, +});/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $MEMFS_ASYNC__deps: ['$PThreadFS', '$mmapAlloc'], + $MEMFS_ASYNC: { + ops_table: null, + mount: function(mount) { + return MEMFS_ASYNC.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); + }, + createNode: function(parent, name, mode, dev) { + if (PThreadFS.isBlkdev(mode) || PThreadFS.isFIFO(mode)) { + // no supported + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (!MEMFS_ASYNC.ops_table) { + MEMFS_ASYNC.ops_table = { + dir: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr, + lookup: MEMFS_ASYNC.node_ops.lookup, + mknod: MEMFS_ASYNC.node_ops.mknod, + rename: MEMFS_ASYNC.node_ops.rename, + unlink: MEMFS_ASYNC.node_ops.unlink, + rmdir: MEMFS_ASYNC.node_ops.rmdir, + readdir: MEMFS_ASYNC.node_ops.readdir, + symlink: MEMFS_ASYNC.node_ops.symlink + }, + stream: { + llseek: MEMFS_ASYNC.stream_ops.llseek + } + }, + file: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr + }, + stream: { + llseek: MEMFS_ASYNC.stream_ops.llseek, + read: MEMFS_ASYNC.stream_ops.read, + write: MEMFS_ASYNC.stream_ops.write, + allocate: MEMFS_ASYNC.stream_ops.allocate, + mmap: MEMFS_ASYNC.stream_ops.mmap, + msync: MEMFS_ASYNC.stream_ops.msync + } + }, + link: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr, + readlink: MEMFS_ASYNC.node_ops.readlink + }, + stream: {} + }, + chrdev: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr + }, + stream: PThreadFS.chrdev_stream_ops + } + }; + } + var node = PThreadFS.createNode(parent, name, mode, dev); + if (PThreadFS.isDir(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.dir.node; + node.stream_ops = MEMFS_ASYNC.ops_table.dir.stream; + node.contents = {}; + } else if (PThreadFS.isFile(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.file.node; + node.stream_ops = MEMFS_ASYNC.ops_table.file.stream; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; + } else if (PThreadFS.isLink(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.link.node; + node.stream_ops = MEMFS_ASYNC.ops_table.link.stream; + } else if (PThreadFS.isChrdev(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.chrdev.node; + node.stream_ops = MEMFS_ASYNC.ops_table.chrdev.stream; + } + node.timestamp = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + parent.timestamp = node.timestamp; + } + return node; + }, + + // Given a file node, returns its file data converted to a typed array. + getFileDataAsTypedArray: function(node) { + if (!node.contents) return new Uint8Array(0); + if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, + + // Allocates a new backing store for the given node so that it can fit at least newSize amount of bytes. + // May allocate more, to provide automatic geometric increase and amortized linear performance appending writes. + // Never shrinks the storage. + expandFileStorage: function(node, newCapacity) { +#if CAN_ADDRESS_2GB + newCapacity >>>= 0; +#endif + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + }, + + // Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated. + resizeFileStorage: function(node, newSize) { +#if CAN_ADDRESS_2GB + newSize >>>= 0; +#endif + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + } else { + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); // Allocate new storage. + if (oldContents) { + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. + } + node.usedBytes = newSize; + } + }, + + node_ops: { + getattr: function(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = PThreadFS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (PThreadFS.isDir(node.mode)) { + attr.size = 4096; + } else if (PThreadFS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (PThreadFS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr: function(node, attr) { + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + MEMFS_ASYNC.resizeFileStorage(node, attr.size); + } + }, + lookup: function(parent, name) { + throw PThreadFS.genericErrors[{{{ cDefine('ENOENT') }}}]; + }, + mknod: function(parent, name, mode, dev) { + return MEMFS_ASYNC.createNode(parent, name, mode, dev); + }, + rename: async function(old_node, new_dir, new_name) { + // if we're overwriting a directory at new_name, make sure it's empty. + if (PThreadFS.isDir(old_node.mode)) { + var new_node; + try { + new_node = await PThreadFS.lookupNode(new_dir, new_name); + } catch (e) { + } + if (new_node) { + for (var i in new_node.contents) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + } + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + old_node.parent.timestamp = Date.now() + old_node.name = new_name; + new_dir.contents[new_name] = old_node; + new_dir.timestamp = old_node.parent.timestamp; + old_node.parent = new_dir; + }, + unlink: function(parent, name) { + delete parent.contents[name]; + parent.timestamp = Date.now(); + }, + rmdir: async function(parent, name) { + var node = await PThreadFS.lookupNode(parent, name); + for (var i in node.contents) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + delete parent.contents[name]; + parent.timestamp = Date.now(); + }, + readdir: function(node) { + var entries = ['.', '..']; + for (var key in node.contents) { + if (!node.contents.hasOwnProperty(key)) { + continue; + } + entries.push(key); + } + return entries; + }, + symlink: function(parent, newname, oldpath) { + var node = MEMFS_ASYNC.createNode(parent, newname, 511 /* 0777 */ | {{{ cDefine('S_IFLNK') }}}, 0); + node.link = oldpath; + return node; + }, + readlink: function(node) { + if (!PThreadFS.isLink(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return node.link; + }, + }, + stream_ops: { + read: function(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); +#if ASSERTIONS + assert(size >= 0); +#endif + if (size > 8 && contents.subarray) { // non-trivial, and typed array + buffer.set(contents.subarray(position, position + size), offset); + } else { + for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; + } + return size; + }, + + // Writes the byte range (buffer[offset], buffer[offset+length]) to offset 'position' into the file pointed by 'stream' + // canOwn: A boolean that tells if this function can take ownership of the passed in buffer from the subbuffer portion + // that the typed array view 'buffer' points to. The underlying ArrayBuffer can be larger than that, but + // canOwn=true will not take ownership of the portion outside the bytes addressed by the view. This means that + // with canOwn=true, creating a copy of the bytes is avoided, but the caller shouldn't touch the passed in range + // of bytes anymore since their contents now represent file data inside the filesystem. + write: function(stream, buffer, offset, length, position, canOwn) { +#if ASSERTIONS + // The data buffer should be a typed array view + assert(!(buffer instanceof ArrayBuffer)); +#endif +#if ALLOW_MEMORY_GROWTH + // If the buffer is located in main memory (HEAP), and if + // memory can grow, we can't hold on to references of the + // memory buffer, as they may get invalidated. That means we + // need to do copy its contents. + if (buffer.buffer === HEAP8.buffer) { + canOwn = false; + } +#endif // ALLOW_MEMORY_GROWTH + + if (!length) return 0; + var node = stream.node; + node.timestamp = Date.now(); + + if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? + if (canOwn) { +#if ASSERTIONS + assert(position === 0, 'canOwn must imply no weird position inside the file'); +#endif + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = buffer.slice(offset, offset + length); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; + } + } + + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS_ASYNC.expandFileStorage(node, position+length); + if (node.contents.subarray && buffer.subarray) { + // Use typed array write which is available. + node.contents.set(buffer.subarray(offset, offset + length), position); + } else { + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. + } + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + + llseek: function(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefine('SEEK_CUR') }}}) { + position += stream.position; + } else if (whence === {{{ cDefine('SEEK_END') }}}) { + if (PThreadFS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return position; + }, + allocate: function(stream, offset, length) { + MEMFS_ASYNC.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); + }, + mmap: function(stream, address, length, position, prot, flags) { + if (address !== 0) { + // We don't currently support location hints for the address of the mapping + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (!PThreadFS.isFile(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if (!(flags & {{{ cDefine('MAP_PRIVATE') }}}) && contents.buffer === buffer) { + // We can't emulate MAP_SHARED when the file is not backed by the buffer + // we're mapping to (e.g. the HEAP buffer). + allocated = false; + ptr = contents.byteOffset; + } else { + // Try to avoid unnecessary slices. + if (position > 0 || position + length < contents.length) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call(contents, position, position + length); + } + } + allocated = true; + ptr = mmapAlloc(length); + if (!ptr) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOMEM') }}}); + } +#if CAN_ADDRESS_2GB + ptr >>>= 0; +#endif + HEAP8.set(contents, ptr); + } + return { ptr: ptr, allocated: allocated }; + }, + msync: function(stream, buffer, offset, length, mmapFlags) { + if (!PThreadFS.isFile(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + if (mmapFlags & {{{ cDefine('MAP_PRIVATE') }}}) { + // MAP_PRIVATE calls need not to be synced back to underlying fs + return 0; + } + + var bytesWritten = MEMFS_ASYNC.stream_ops.write(stream, buffer, 0, length, offset, false); + // should we check if bytesWritten and length are the same? + return 0; + } + } + } +}); +/** + * @license + * Copyright 2021 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $FSAFS__deps: ['$PThreadFS'], + $FSAFS: { + + /* Debugging */ + + debug: function(...args) { + // Uncomment to print debug information. + // + // console.log(args); + }, + + /* Filesystem implementation (public interface) */ + + createNode: function (parent, name, mode, dev) { + FSAFS.debug('createNode', arguments); + if (!PThreadFS.isDir(mode) && !PThreadFS.isFile(mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = FSAFS.node_ops; + node.stream_ops = FSAFS.stream_ops; + if (PThreadFS.isDir(mode)) { + node.contents = {}; + } + node.timestamp = Date.now(); + return node; + }, + + mount: async function (mount) { + FSAFS.debug('mount', arguments); + let node = FSAFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); + FSAFS.root = await navigator.storage.getDirectory(); + node.localReference = FSAFS.root; + return node; + }, + + /* Operations on the nodes of the filesystem tree */ + + node_ops: { + getattr: async function(node) { + FSAFS.debug('getattr', arguments); + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = PThreadFS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (PThreadFS.isDir(node.mode)) { + attr.size = 4096; + } else if (PThreadFS.isFile(node.mode)) { + if (node.handle) { + attr.size = await node.handle.getSize(); + } + else { + let fileHandle = await node.localReference.createSyncAccessHandle(); + attr.size = await fileHandle.getSize(); + await fileHandle.close(); + } + } else if (PThreadFS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + + setattr: async function(node, attr) { + FSAFS.debug('setattr', arguments); + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + let useOpen = false; + let fileHandle = node.handle; + try { + if (!fileHandle) { + // Open a handle that is closed later. + useOpen = true; + fileHandle = await node.localReference.createSyncAccessHandle(); + } + await fileHandle.truncate(attr.size); + + } catch (e) { + if (!('code' in e)) throw e; + throw new PThreadFS.ErrnoError(-e.errno); + } finally { + if (useOpen) { + await fileHandle.close(); + } + } + } + }, + + lookup: async function (parent, name) { + FSAFS.debug('lookup', arguments); + let childLocalReference = null; + let mode = null; + try { + childLocalReference = await parent.localReference.getDirectoryHandle(name, {create: false}); + mode = {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */ + } catch (e) { + try { + childLocalReference = await parent.localReference.getFileHandle(name, {create: false}); + mode = {{{ cDefine('S_IFREG') }}} | 511 /* 0777 */ + } catch (e) { + throw PThreadFS.genericErrors[{{{ cDefine('ENOENT') }}}]; + } + } + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = FSAFS.node_ops; + node.stream_ops = FSAFS.stream_ops; + node.localReference = childLocalReference; + return node; + }, + + mknod: async function (parent, name, mode, dev) { + FSAFS.debug('mknod', arguments); + let node = FSAFS.createNode(parent, name, mode, dev); + try { + if (PThreadFS.isDir(mode)) { + node.localReference = await parent.localReference.getDirectoryHandle(name, {create: true}); + } else if (PThreadFS.isFile(mode)) { + node.localReference = await parent.localReference.getFileHandle(name, {create: true}); + } + } catch (e) { + if (!('code' in e)) throw e; + throw new PThreadFS.ErrnoError(-e.errno); + } + + node.handle = null; + node.refcount = 0; + return node; + }, + + rename: function (oldNode, newParentNode, newName) { + FSAFS.debug('rename', arguments); + console.log('FSAFS error: rename is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + unlink: async function(parent, name) { + FSAFS.debug('unlink', arguments); + delete parent.contents[name]; + return await parent.localReference.removeEntry(name); + }, + + rmdir: async function(parent, name) { + FSAFS.debug('rmdir', arguments); + for (var i in node.contents) { + throw new FS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + delete parent.contents[name]; + return await parent.localReference.removeEntry(name); + }, + + readdir: async function(node) { + FSAFS.debug('readdir', arguments); + let entries = ['.', '..']; + for await (let [name, handle] of node.localReference) { + entries.push(name); + } + return entries; + }, + + symlink: function(parent, newName, oldPath) { + console.log('FSAFS error: symlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + readlink: function(node) { + console.log('FSAFS error: readlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + }, + + /* Operations on file streams (i.e., file handles) */ + + stream_ops: { + open: async function (stream) { + FSAFS.debug('open', arguments); + if (PThreadFS.isDir(stream.node.mode)) { + console.log('FSAFS error: open for directories is not fully implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('FSAFS error: open is only implemented for files') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + if (stream.node.handle) { + stream.handle = stream.node.handle; + ++stream.node.refcount; + } else { + stream.handle = await stream.node.localReference.createSyncAccessHandle(); + stream.node.handle = stream.handle; + stream.node.refcount = 1; + } + }, + + close: async function (stream) { + FSAFS.debug('close', arguments); + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('FSAFS error: close is only implemented for files'); + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + stream.handle = null; + --stream.node.refcount; + if (stream.node.refcount <= 0) { + await stream.node.handle.close(); + stream.node.handle = null; + } + }, + + fsync: async function(stream) { + FSAFS.debug('fsync', arguments); + if (stream.handle == null) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await stream.handle.flush(); + return 0; + }, + + read: async function (stream, buffer, offset, length, position) { + FSAFS.debug('read', arguments); + let data = buffer.subarray(offset, offset+length); + let readBytes = await stream.handle.read(data, {at: position}); + return readBytes; + }, + + write: async function (stream, buffer, offset, length, position) { + FSAFS.debug('write', arguments); + stream.node.timestamp = Date.now(); + let data = buffer.subarray(offset, offset+length); + let writtenBytes = await stream.handle.write(data, {at: position}); + return writtenBytes; + }, + + llseek: async function (stream, offset, whence) { + FSAFS.debug('llseek', arguments); + let position = offset; + if (whence === {{{ cDefine('SEEK_CUR') }}}) { + position += stream.position; + } else if (whence === {{{ cDefine('SEEK_END') }}}) { + if (PThreadFS.isFile(stream.node.mode)) { + position += await stream.handle.getSize(); + } + } + + if (position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return position; + }, + + mmap: function(stream, buffer, offset, length, position, prot, flags) { + FSAFS.debug('mmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + msync: function(stream, buffer, offset, length, mmapFlags) { + FSAFS.debug('msync', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + munmap: function(stream) { + FSAFS.debug('munmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + } + } +});/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + + mergeInto(LibraryManager.library, { + $TTY_ASYNC__deps: ['$PThreadFS'], +#if !MINIMAL_RUNTIME + $TTY_ASYNC__postset: function() { + addAtInit('TTY_ASYNC.init();'); + addAtExit('TTY_ASYNC.shutdown();'); + }, +#endif + $TTY_ASYNC: { + ttys: [], + init: function () { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // currently, PThreadFS.init does not distinguish if process.stdin is a file or TTY_ASYNC + // // device, it always assumes it's a TTY_ASYNC device. because of this, we're forcing + // // process.stdin to UTF8 encoding to at least make stdin reading compatible + // // with text files until PThreadFS.init can be refactored. + // process['stdin']['setEncoding']('utf8'); + // } + }, + shutdown: function() { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? + // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation + // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? + // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle + // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call + // process['stdin']['pause'](); + // } + }, + register: function(dev, ops) { + TTY_ASYNC.ttys[dev] = { input: [], output: [], ops: ops }; + PThreadFS.registerDevice(dev, TTY_ASYNC.stream_ops); + }, + stream_ops: { + open: function(stream) { + var tty = TTY_ASYNC.ttys[stream.node.rdev]; + if (!tty) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + stream.tty = tty; + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + stream.tty.ops.flush(stream.tty); + }, + flush: function(stream) { + stream.tty.ops.flush(stream.tty); + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENXIO') }}}); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (result === undefined && bytesRead === 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EAGAIN') }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENXIO') }}}); + } + try { + for (var i = 0; i < length; i++) { + stream.tty.ops.put_char(stream.tty, buffer[offset+i]); + } + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }, + default_tty_ops: { + // get_char has 3 particular return values: + // a.) the next character represented as an integer + // b.) undefined to signal that no data is currently available + // c.) null to signal an EOF + get_char: function(tty) { + if (!tty.input.length) { + var result = null; + if (typeof window != 'undefined' && + typeof window.prompt == 'function') { + // Browser. + result = window.prompt('Input: '); // returns null on cancel + if (result !== null) { + result += '\n'; + } + } else if (typeof readline == 'function') { + // Command line. + result = readline(); + if (result !== null) { + result += '\n'; + } + } + if (!result) { + return null; + } + tty.input = intArrayFromString(result, true); + } + return tty.input.shift(); + }, + put_char: function(tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + out(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle. + } + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + out(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + } + }, + default_tty1_ops: { + put_char: function(tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + err(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + err(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + } + } + } +}); \ No newline at end of file diff --git a/pthreadfs/pthreadfs.cpp b/pthreadfs/pthreadfs.cpp new file mode 100644 index 000000000000..0b313b28b754 --- /dev/null +++ b/pthreadfs/pthreadfs.cpp @@ -0,0 +1,366 @@ +#include "pthreadfs.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +SyncToAsync::SyncToAsync() : thread(threadMain, this), childLock(mutex) { + // The child lock is associated with the mutex, which takes the lock, and + // we free it here. Only the child will lock/unlock it from now on. + childLock.unlock(); +} + +SyncToAsync::~SyncToAsync() { + quit = true; + + shutdown(); + + thread.join(); +} + +void SyncToAsync::shutdown() { + readyToWork = true; + condition.notify_one(); +} + +void SyncToAsync::doWork(std::function newWork) { + // Send the work over. + { + std::lock_guard lock(mutex); + work = newWork; + finishedWork = false; + readyToWork = true; + } + condition.notify_one(); + + // Wait for it to be complete. + std::unique_lock lock(mutex); + condition.wait(lock, [&]() { + return finishedWork; + }); +} + +void* SyncToAsync::threadMain(void* arg) { + // Prevent the pthread from shutting down too early. + EM_ASM(runtimeKeepalivePush();); + auto* parent = (SyncToAsync*)arg; + emscripten_async_call(threadIter, arg, 0); + return 0; +} + +void SyncToAsync::threadIter(void* arg) { + auto* parent = (SyncToAsync*)arg; + // Wait until we get something to do. + parent->childLock.lock(); + parent->condition.wait(parent->childLock, [&]() { + return parent->readyToWork; + }); + if (parent->quit) { + EM_ASM(runtimeKeepalivePop();); + return; + } + auto work = parent->work; + parent->readyToWork = false; + // Do the work. + work([parent, arg]() { + // We are called, so the work was finished. Notify the caller. + parent->finishedWork = true; + parent->childLock.unlock(); + parent->condition.notify_one(); + threadIter(arg); + }); +} + +// Define global variables to be populated by resume; +SyncToAsync::Callback g_resumeFct; +SyncToAsync g_synctoasync_helper; + +// Static functions calling resumFct and setting the return value. +void resumeWrapper_v() +{ + g_resumeFct(); +} +// return value long +long resume_result_long = 0; +void resumeWrapper_l(long retVal) +{ + resume_result_long = retVal; + g_resumeFct(); +} +// return value __wasi_errno_t +__wasi_errno_t resume_result_wasi = 0; +void resumeWrapper_wasi(__wasi_errno_t retVal) +{ + resume_result_wasi = retVal; + g_resumeFct(); +} + +// Wasi definitions +WASI_CAPI_DEF(write, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_size_t *nwritten) { + WASI_SYNCTOASYNC(write, iovs, iovs_len, nwritten); +} +WASI_CAPI_DEF(read, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_size_t *nread) { + WASI_SYNCTOASYNC(read, iovs, iovs_len, nread); +} +WASI_CAPI_DEF(pwrite, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nwritten) { + WASI_SYNCTOASYNC(pwrite, iovs, iovs_len, offset, nwritten); +} +WASI_CAPI_DEF(pread, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nread) { + WASI_SYNCTOASYNC(pread, iovs, iovs_len, offset, nread); +} +WASI_CAPI_DEF(seek, __wasi_filedelta_t offset, __wasi_whence_t whence, __wasi_filesize_t *newoffset) { + WASI_SYNCTOASYNC(seek, offset, whence, newoffset); +} +WASI_CAPI_DEF(fdstat_get, __wasi_fdstat_t *stat) { + WASI_SYNCTOASYNC(fdstat_get, stat); +} +WASI_CAPI_NOARGS_DEF(close) { + WASI_SYNCTOASYNC_NOARGS(close); +} +WASI_CAPI_NOARGS_DEF(sync) { + WASI_SYNCTOASYNC_NOARGS(sync); +} + +// Syscall definitions +SYS_CAPI_DEF(open, 5, long path, long flags, ...) { + va_list vl; + va_start(vl, flags); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(open, path, flags, varargs); +} + +SYS_CAPI_DEF(unlink, 10, long path) { + SYS_SYNCTOASYNC(unlink, path); +} + +SYS_CAPI_DEF(chdir, 12, long path) { + SYS_SYNCTOASYNC(chdir, path); +} + +SYS_CAPI_DEF(mknod, 14, long path, long mode, long dev) { + SYS_SYNCTOASYNC(mknod, path, mode, dev); +} + +SYS_CAPI_DEF(chmod, 15, long path, long mode) { + SYS_SYNCTOASYNC(chmod, path, mode); +} + +SYS_CAPI_DEF(access, 33, long path, long amode) { + SYS_SYNCTOASYNC(access, path, amode); +} + +SYS_CAPI_DEF(rename, 38, long old_path, long new_path) { + SYS_SYNCTOASYNC(rename, old_path, new_path); +} + +SYS_CAPI_DEF(mkdir, 39, long path, long mode) { + SYS_SYNCTOASYNC(mkdir, path, mode); +} + +SYS_CAPI_DEF(rmdir, 40, long path) { + SYS_SYNCTOASYNC(rmdir, path); +} + +SYS_CAPI_DEF(dup, 41, long fd) { + SYS_SYNCTOASYNC(dup, fd); +} + +SYS_CAPI_DEF(ioctl, 54, long fd, long request, ...) { + void *arg; + va_list ap; + va_start(ap, request); + arg = va_arg(ap, void *); + va_end(ap); + + SYS_SYNCTOASYNC(ioctl, fd, request, arg); +} + +SYS_CAPI_DEF(dup2, 63, long oldfd, long newfd) { + SYS_SYNCTOASYNC(dup2, oldfd, newfd); +} + +SYS_CAPI_DEF(symlink, 83, long target, long linkpath) { + SYS_SYNCTOASYNC(symlink, target, linkpath); +} + +SYS_CAPI_DEF(readlink, 85, long path, long buf, long bufsize) { + SYS_SYNCTOASYNC(readlink, path, buf, bufsize); +} + +SYS_CAPI_DEF(munmap, 91, long addr, long len) { + SYS_SYNCTOASYNC(munmap, addr, len); +} + +SYS_CAPI_DEF(fchmod, 94, long fd, long mode) { + SYS_SYNCTOASYNC(fchmod, fd, mode); +} + +SYS_CAPI_DEF(fchdir, 133, long fd) { + SYS_SYNCTOASYNC(fchdir, fd); +} + +SYS_CAPI_DEF(_newselect, 142, long nfds, long readfds, long writefds, long exceptfds, long timeout) { + SYS_SYNCTOASYNC(_newselect, nfds, readfds, writefds, exceptfds, timeout); +} + +SYS_CAPI_DEF(msync, 144, long addr, long len, long flags) { + SYS_SYNCTOASYNC(msync, addr, len, flags); +} + +SYS_CAPI_DEF(fdatasync, 148, long fd) { + SYS_SYNCTOASYNC(fdatasync, fd); +} + +SYS_CAPI_DEF(poll, 168, long fds, long nfds, long timeout) { + SYS_SYNCTOASYNC(poll, fds, nfds, timeout); +} + +SYS_CAPI_DEF(getcwd, 183, long buf, long size) { + SYS_SYNCTOASYNC(getcwd, buf, size); +} + +SYS_CAPI_DEF(mmap2, 192, long addr, long len, long prot, long flags, long fd, long off) { + SYS_SYNCTOASYNC(mmap2, addr, len, prot, flags, fd, off); +} + +SYS_CAPI_DEF(truncate64, 193, long path, long zero, long low, long high) { + SYS_SYNCTOASYNC(truncate64, path, zero, low, high); +} + +SYS_CAPI_DEF(ftruncate64, 194, long fd, long zero, long low, long high) { + SYS_SYNCTOASYNC(ftruncate64, fd, zero, low, high); +} + +SYS_CAPI_DEF(stat64, 195, long path, long buf) { + SYS_SYNCTOASYNC(stat64, path, buf); +} + +SYS_CAPI_DEF(lstat64, 196, long path, long buf) { + SYS_SYNCTOASYNC(lstat64, path, buf); +} + +SYS_CAPI_DEF(fstat64, 197, long fd, long buf) { + SYS_SYNCTOASYNC(fstat64, fd, buf); +} + +SYS_CAPI_DEF(lchown32, 198, long path, long owner, long group) { + SYS_SYNCTOASYNC(lchown32, path, owner, group); +} + +SYS_CAPI_DEF(fchown32, 207, long fd, long owner, long group) { + SYS_SYNCTOASYNC(fchown32, fd, owner, group); +} + +SYS_CAPI_DEF(chown32, 212, long path, long owner, long group) { + SYS_SYNCTOASYNC(chown32, path, owner, group); +} + +SYS_CAPI_DEF(getdents64, 220, long fd, long dirp, long count) { + SYS_SYNCTOASYNC(getdents64, fd, dirp, count); +} + +SYS_CAPI_DEF(fcntl64, 221, long fd, long cmd, ...) { +va_list vl; + va_start(vl, cmd); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(fcntl64, fd, cmd, varargs); +} + +SYS_CAPI_DEF(statfs64, 268, long path, long size, long buf) { + SYS_SYNCTOASYNC(statfs64, path, size, buf); +} + +SYS_CAPI_DEF(fstatfs64, 269, long fd, long size, long buf) { + SYS_SYNCTOASYNC(fstatfs64, fd, size, buf); +} + +SYS_CAPI_DEF(openat, 295, long dirfd, long path, long flags, ...) { +va_list vl; + va_start(vl, flags); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(openat, dirfd, path, flags, varargs); +} + +SYS_CAPI_DEF(mkdirat, 296, long dirfd, long path, long mode) { + SYS_SYNCTOASYNC(mkdirat, dirfd, path, mode); +} + +SYS_CAPI_DEF(mknodat, 297, long dirfd, long path, long mode, long dev) { + SYS_SYNCTOASYNC(mknodat, dirfd, path, mode, dev); +} + +SYS_CAPI_DEF(fchownat, 298, long dirfd, long path, long owner, long group, long flags) { + SYS_SYNCTOASYNC(fchownat, dirfd, path, owner, group, flags); +} + +SYS_CAPI_DEF(fstatat64, 300, long dirfd, long path, long buf, long flags) { + SYS_SYNCTOASYNC(fstatat64, dirfd, path, buf, flags); +} + +SYS_CAPI_DEF(unlinkat, 301, long dirfd, long path, long flags) { + SYS_SYNCTOASYNC(unlinkat, dirfd, path, flags); +} + +SYS_CAPI_DEF(renameat, 302, long olddirfd, long oldpath, long newdirfd, long newpath) { + SYS_SYNCTOASYNC(renameat, olddirfd, oldpath, newdirfd, newpath); +} + +SYS_CAPI_DEF(symlinkat, 304, long target, long newdirfd, long linkpath) { + SYS_SYNCTOASYNC(symlinkat, target, newdirfd, linkpath); +} + +SYS_CAPI_DEF(readlinkat, 305, long dirfd, long path, long bug, long bufsize) { + SYS_SYNCTOASYNC(readlinkat, dirfd, path, bug, bufsize); +} + +SYS_CAPI_DEF(fchmodat, 306, long dirfd, long path, long mode, ...) { +va_list vl; + va_start(vl, mode); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(fchmodat, dirfd, path, mode, varargs); +} + +SYS_CAPI_DEF(faccessat, 307, long dirfd, long path, long amode, long flags) { + SYS_SYNCTOASYNC(faccessat, dirfd, path, amode, flags); +} + +SYS_CAPI_DEF(utimensat, 320, long dirfd, long path, long times, long flags) { + SYS_SYNCTOASYNC(utimensat, dirfd, path, times, flags); +} + +SYS_CAPI_DEF(fallocate, 324, long fd, long mode, long off_low, long off_high, long len_low, long len_high) { + SYS_SYNCTOASYNC(fallocate, fd, mode, off_low, off_high, len_low, len_high); +} + +SYS_CAPI_DEF(dup3, 330, long fd, long suggestfd, long flags) { + SYS_SYNCTOASYNC(dup3, fd, suggestfd, flags); +} + +// Other helper code + +void emscripten_init_pthreadfs() { + g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { + g_resumeFct = [resume]() { resume(); }; + init_pthreadfs(&resumeWrapper_v); + }); + g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { + g_resumeFct = [resume]() { resume(); }; + init_fsafs(&resumeWrapper_v); + }); + return; +} \ No newline at end of file diff --git a/pthreadfs/pthreadfs.h b/pthreadfs/pthreadfs.h new file mode 100644 index 000000000000..04d0b57d5d65 --- /dev/null +++ b/pthreadfs/pthreadfs.h @@ -0,0 +1,265 @@ +#ifndef PTHREADFS_H +#define PTHREADFS_H + +#include +#include + +#define EM_PTHREADFS_ASM(code) g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + EM_ASM({(async () => {code wasmTable.get($0)(); \ + })();}, &resumeWrapper_v); \ + }); + +#define WASI_JSAPI_DEF(name, ...) extern void __fd_##name##_async(__wasi_fd_t fd, __VA_ARGS__, void (*fun)(__wasi_errno_t)); +#define WASI_JSAPI_NOARGS_DEF(name) extern void __fd_##name##_async(__wasi_fd_t fd, void (*fun)(__wasi_errno_t)); + +#define WASI_CAPI_DEF(name, ...) __wasi_errno_t __wasi_fd_##name(__wasi_fd_t fd, __VA_ARGS__) +#define WASI_CAPI_NOARGS_DEF(name) __wasi_errno_t __wasi_fd_##name(__wasi_fd_t fd) + +#define WASI_JSAPI(name, ...) __fd_##name##_async(__VA_ARGS__) +#define WASI_SYNCTOASYNC(name, ...) g_synctoasync_helper.doWork([fd, __VA_ARGS__](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + WASI_JSAPI(name, fd, __VA_ARGS__, &resumeWrapper_wasi); \ + }); \ + return resume_result_wasi; +#define WASI_SYNCTOASYNC_NOARGS(name) g_synctoasync_helper.doWork([fd](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + WASI_JSAPI(name, fd, &resumeWrapper_wasi); \ + }); \ + return resume_result_wasi; + +// Classic Syscalls + +#define SYS_JSAPI_DEF(name, ...) extern void __sys_##name##_async(__VA_ARGS__, void (*fun)(long)); +#define SYS_JSAPI_NOARGS_DEF(name) extern void __sys_##name##_async(void (*fun)(long)); + +#define SYS_CAPI_DEF(name, number, ...) long __syscall##number(__VA_ARGS__) + +#define SYS_DEF(name, number, ...) SYS_CAPI_DEF(name, number, __VA_ARGS__); SYS_JSAPI_DEF(name, __VA_ARGS__) + +#define SYS_JSAPI(name, ...) __sys_##name##_async(__VA_ARGS__) +#define SYS_SYNCTOASYNC(name, ...) g_synctoasync_helper.doWork([__VA_ARGS__](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + SYS_JSAPI(name, __VA_ARGS__, &resumeWrapper_l); \ + }); \ + return resume_result_long; +#define SYS_SYNCTOASYNC_NOARGS(name) g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + SYS_JSAPI(name, &resumeWrapper_l); \ + }); \ + return resume_result_long; + +extern "C" { + // Helpers + extern void init_pthreadfs(void (*fun)(void)); + extern void init_sfafs(void (*fun)(void)); + extern void init_fsafs(void (*fun)(void)); + void emscripten_init_pthreadfs(); + + // WASI + WASI_JSAPI_DEF(write, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_size_t *nwritten) + WASI_JSAPI_DEF(read, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_size_t *nread) + WASI_JSAPI_DEF(pwrite, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nwritten) + WASI_JSAPI_DEF(pread, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nread) + WASI_JSAPI_DEF(seek, __wasi_filedelta_t offset, __wasi_whence_t whence, __wasi_filesize_t *newoffset) + WASI_JSAPI_DEF(fdstat_get, __wasi_fdstat_t *stat) + WASI_JSAPI_NOARGS_DEF(close) + WASI_JSAPI_NOARGS_DEF(sync) + + // Syscalls + // see https://github.com/emscripten-core/emscripten/blob/main/system/lib/libc/musl/arch/emscripten/syscall_arch.h +SYS_CAPI_DEF(open, 5, long path, long flags, ...); +SYS_JSAPI_DEF(open, long path, long flags, int varargs) + +SYS_CAPI_DEF(unlink, 10, long path); +SYS_JSAPI_DEF(unlink, long path) + +SYS_CAPI_DEF(chdir, 12, long path); +SYS_JSAPI_DEF(chdir, long path) + +SYS_CAPI_DEF(mknod, 14, long path, long mode, long dev); +SYS_JSAPI_DEF(mknod, long path, long mode, long dev) + +SYS_CAPI_DEF(chmod, 15, long path, long mode); +SYS_JSAPI_DEF(chmod, long path, long mode) + +SYS_CAPI_DEF(access, 33, long path, long amode); +SYS_JSAPI_DEF(access, long path, long amode) + +SYS_CAPI_DEF(rename, 38, long old_path, long new_path); +SYS_JSAPI_DEF(rename, long old_path, long new_path) + +SYS_CAPI_DEF(mkdir, 39, long path, long mode); +SYS_JSAPI_DEF(mkdir, long path, long mode) + +SYS_CAPI_DEF(rmdir, 40, long path); +SYS_JSAPI_DEF(rmdir, long path) + +SYS_CAPI_DEF(dup, 41, long fd); +SYS_JSAPI_DEF(dup, long fd) + +SYS_CAPI_DEF(ioctl, 54, long fd, long request, ...); +SYS_JSAPI_DEF(ioctl, long fd, long request, void *const varargs) + +SYS_CAPI_DEF(dup2, 63, long oldfd, long newfd); +SYS_JSAPI_DEF(dup2, long oldfd, long newfd) + +SYS_CAPI_DEF(symlink, 83, long target, long linkpath); +SYS_JSAPI_DEF(symlink, long target, long linkpath) + +SYS_CAPI_DEF(readlink, 85, long path, long buf, long bufsize); +SYS_JSAPI_DEF(readlink, long path, long buf, long bufsize) + +SYS_CAPI_DEF(munmap, 91, long addr, long len); +SYS_JSAPI_DEF(munmap, long addr, long len) + +SYS_CAPI_DEF(fchmod, 94, long fd, long mode); +SYS_JSAPI_DEF(fchmod, long fd, long mode) + +SYS_CAPI_DEF(fchdir, 133, long fd); +SYS_JSAPI_DEF(fchdir, long fd) + +SYS_CAPI_DEF(_newselect, 142, long nfds, long readfds, long writefds, long exceptfds, long timeout); +SYS_JSAPI_DEF(_newselect, long nfds, long readfds, long writefds, long exceptfds, long timeout) + +SYS_CAPI_DEF(msync, 144, long addr, long len, long flags); +SYS_JSAPI_DEF(msync, long addr, long len, long flags) + +SYS_CAPI_DEF(fdatasync, 148, long fd); +SYS_JSAPI_DEF(fdatasync, long fd) + +SYS_CAPI_DEF(poll, 168, long fds, long nfds, long timeout); +SYS_JSAPI_DEF(poll, long fds, long nfds, long timeout) + +SYS_CAPI_DEF(getcwd, 183, long buf, long size); +SYS_JSAPI_DEF(getcwd, long buf, long size) + +SYS_CAPI_DEF(mmap2, 192, long addr, long len, long prot, long flags, long fd, long off); +SYS_JSAPI_DEF(mmap2, long addr, long len, long prot, long flags, long fd, long off) + +SYS_CAPI_DEF(truncate64, 193, long path, long zero, long low, long high); +SYS_JSAPI_DEF(truncate64, long path, long zero, long low, long high) + +SYS_CAPI_DEF(ftruncate64, 194, long fd, long zero, long low, long high); +SYS_JSAPI_DEF(ftruncate64, long fd, long zero, long low, long high) + +SYS_CAPI_DEF(stat64, 195, long path, long buf); +SYS_JSAPI_DEF(stat64, long path, long buf) + +SYS_CAPI_DEF(lstat64, 196, long path, long buf); +SYS_JSAPI_DEF(lstat64, long path, long buf) + +SYS_CAPI_DEF(fstat64, 197, long fd, long buf); +SYS_JSAPI_DEF(fstat64, long fd, long buf) + +SYS_CAPI_DEF(lchown32, 198, long path, long owner, long group); +SYS_JSAPI_DEF(lchown32, long path, long owner, long group) + +SYS_CAPI_DEF(fchown32, 207, long fd, long owner, long group); +SYS_JSAPI_DEF(fchown32, long fd, long owner, long group) + +SYS_CAPI_DEF(chown32, 212, long path, long owner, long group); +SYS_JSAPI_DEF(chown32, long path, long owner, long group) + +SYS_CAPI_DEF(getdents64, 220, long fd, long dirp, long count); +SYS_JSAPI_DEF(getdents64, long fd, long dirp, long count) + +SYS_CAPI_DEF(fcntl64, 221, long fd, long cmd, ...); +SYS_JSAPI_DEF(fcntl64, long fd, long cmd, int varargs) + +SYS_CAPI_DEF(statfs64, 268, long path, long size, long buf); +SYS_JSAPI_DEF(statfs64, long path, long size, long buf) + +SYS_CAPI_DEF(fstatfs64, 269, long fd, long size, long buf); +SYS_JSAPI_DEF(fstatfs64, long fd, long size, long buf) + +SYS_CAPI_DEF(openat, 295, long dirfd, long path, long flags, ...); +SYS_JSAPI_DEF(openat, long dirfd, long path, long flags, int varargs) + +SYS_CAPI_DEF(mkdirat, 296, long dirfd, long path, long mode); +SYS_JSAPI_DEF(mkdirat, long dirfd, long path, long mode) + +SYS_CAPI_DEF(mknodat, 297, long dirfd, long path, long mode, long dev); +SYS_JSAPI_DEF(mknodat, long dirfd, long path, long mode, long dev) + +SYS_CAPI_DEF(fchownat, 298, long dirfd, long path, long owner, long group, long flags); +SYS_JSAPI_DEF(fchownat, long dirfd, long path, long owner, long group, long flags) + +SYS_CAPI_DEF(fstatat64, 300, long dirfd, long path, long buf, long flags); +SYS_JSAPI_DEF(fstatat64, long dirfd, long path, long buf, long flags) + +SYS_CAPI_DEF(unlinkat, 301, long dirfd, long path, long flags); +SYS_JSAPI_DEF(unlinkat, long dirfd, long path, long flags) + +SYS_CAPI_DEF(renameat, 302, long olddirfd, long oldpath, long newdirfd, long newpath); +SYS_JSAPI_DEF(renameat, long olddirfd, long oldpath, long newdirfd, long newpath) + +SYS_CAPI_DEF(symlinkat, 304, long target, long newdirfd, long linkpath); +SYS_JSAPI_DEF(symlinkat, long target, long newdirfd, long linkpath) + +SYS_CAPI_DEF(readlinkat, 305, long dirfd, long path, long bug, long bufsize); +SYS_JSAPI_DEF(readlinkat, long dirfd, long path, long bug, long bufsize) + +SYS_CAPI_DEF(fchmodat, 306, long dirfd, long path, long mode, ...); +SYS_JSAPI_DEF(fchmodat, long dirfd, long path, long mode, int varargs) + +SYS_CAPI_DEF(faccessat, 307, long dirfd, long path, long amode, long flags); +SYS_JSAPI_DEF(faccessat, long dirfd, long path, long amode, long flags) + +SYS_CAPI_DEF(utimensat, 320, long dirfd, long path, long times, long flags); +SYS_JSAPI_DEF(utimensat, long dirfd, long path, long times, long flags) + +SYS_CAPI_DEF(fallocate, 324, long fd, long mode, long off_low, long off_high, long len_low, long len_high); +SYS_JSAPI_DEF(fallocate, long fd, long mode, long off_low, long off_high, long len_low, long len_high) + +SYS_CAPI_DEF(dup3, 330, long fd, long suggestfd, long flags); +SYS_JSAPI_DEF(dup3, long fd, long suggestfd, long flags) +} + +class SyncToAsync { +public: + using Callback = std::function; + + SyncToAsync(); + + ~SyncToAsync(); + + void shutdown(); + + // Run some work on thread. This is a synchronous call, but the thread can do + // async work for us. To allow us to know when the async work finishes, the + // worker is given a function to call at that time. + void doWork(std::function newWork); + +private: + std::thread thread; + std::mutex mutex; + std::condition_variable condition; + std::function work; + bool readyToWork = false; + bool finishedWork; + bool quit = false; + + // The child will be asynchronous, and therefore we cannot rely on RAII to + // unlock for us, we must do it manually. + std::unique_lock childLock; + + static void* threadMain(void* arg); + + static void threadIter(void* arg); +}; + +// Declare global variables to be populated by resume; +extern SyncToAsync::Callback g_resumeFct; +extern SyncToAsync g_synctoasync_helper; + +// Static functions calling resumFct and setting corresponding the return value. +void resumeWrapper_v(); + +void resumeWrapper_l(long retVal); + +void resumeWrapper_wasi(__wasi_errno_t retVal); + +void emscripten_init_pthreadfs(); + +#endif // PTHREADFS_H \ No newline at end of file diff --git a/pthreadfs/src/Makefile b/pthreadfs/src/Makefile new file mode 100644 index 000000000000..094cf2970b27 --- /dev/null +++ b/pthreadfs/src/Makefile @@ -0,0 +1,14 @@ +# Define some folders +JSLIB_SRC = js + +LIB_WRAPPER = $(JSLIB_SRC)/pthreadfs.js +LIB_SYS = $(JSLIB_SRC)/library_syscall_async.js +LIB_FS = $(JSLIB_SRC)/library_asyncfs.js +LIB_MEMFS = $(JSLIB_SRC)/library_memfs_async.js +LIB_SFAFS = $(JSLIB_SRC)/library_sfafs_async.js +LIB_FSAFS = $(JSLIB_SRC)/library_fsafs.js +LIB_TTY = $(JSLIB_SRC)/library_tty_async.js + +library_pthreadfs.js: $(LIB_WRAPPER) $(LIB_SYS) $(LIB_FS) $(LIB_MEMFS) $(LIB_FSAFS) $(LIB_TTY) + rm -f $@ + cat $? > ../$@ diff --git a/pthreadfs/src/js/library_asyncfs.js b/pthreadfs/src/js/library_asyncfs.js new file mode 100644 index 000000000000..94c0dda85fff --- /dev/null +++ b/pthreadfs/src/js/library_asyncfs.js @@ -0,0 +1,1582 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + + mergeInto(LibraryManager.library, { + $PThreadFS__deps: ['$getRandomDevice', '$PATH', '$PATH_FS', '$TTY_ASYNC', '$MEMFS_ASYNC', +#if ASSERTIONS + '$ERRNO_MESSAGES', '$ERRNO_CODES', +#endif + ], + $PThreadFS: { + root: null, + mounts: [], + devices: {}, + streams: [], + nextInode: 1, + nameTable: null, + currentPath: '/', + initialized: false, + // Whether we are currently ignoring permissions. Useful when preparing the + // filesystem and creating files inside read-only folders. + // This is set to false during `preInit`, allowing you to modify the + // filesystem freely up until that point (e.g. during `preRun`). + ignorePermissions: true, + trackingDelegate: {}, + tracking: { + openFlags: { + READ: 1 << 0, + WRITE: 1 << 1 + } + }, + ErrnoError: null, // set during init + genericErrors: {}, + filesystems: null, + syncFSRequests: 0, // we warn if there are multiple in flight at once + + // + // paths + // + lookupPath: async function(path, opts) { + path = PATH_FS.resolve(PThreadFS.cwd(), path); + opts = opts || {}; + + if (!path) return { path: '', node: null }; + + var defaults = { + follow_mount: true, + recurse_count: 0 + }; + for (var key in defaults) { + if (opts[key] === undefined) { + opts[key] = defaults[key]; + } + } + + if (opts.recurse_count > 8) { // max recursive lookup of 8 + throw new PThreadFS.ErrnoError({{{ cDefine('ELOOP') }}}); + } + + // split the path + var parts = PATH.normalizeArray(path.split('/').filter(function(p) { + return !!p; + }), false); + + // start at the root + var current = PThreadFS.root; + var current_path = '/'; + + for (var i = 0; i < parts.length; i++) { + var islast = (i === parts.length-1); + if (islast && opts.parent) { + // stop resolving + break; + } + + current = await PThreadFS.lookupNode(current, parts[i]); + current_path = PATH.join2(current_path, parts[i]); + + // jump to the mount's root node if this is a mountpoint + if (PThreadFS.isMountpoint(current)) { + if (!islast || (islast && opts.follow_mount)) { + current = current.mounted.root; + } + } + + // by default, lookupPath will not follow a symlink if it is the final path component. + // setting opts.follow = true will override this behavior. + if (!islast || opts.follow) { + var count = 0; + while (PThreadFS.isLink(current.mode)) { + var link = await PThreadFS.readlink(current_path); + current_path = PATH_FS.resolve(PATH.dirname(current_path), link); + + var lookup = await PThreadFS.lookupPath(current_path, { recurse_count: opts.recurse_count }); + current = lookup.node; + + if (count++ > 40) { // limit max consecutive symlinks to 40 (SYMLOOP_MAX). + throw new PThreadFS.ErrnoError({{{ cDefine('ELOOP') }}}); + } + } + } + } + + return { path: current_path, node: current }; + }, + getPath: function(node) { + var path; + while (true) { + if (PThreadFS.isRoot(node)) { + var mount = node.mount.mountpoint; + if (!path) return mount; + return mount[mount.length-1] !== '/' ? mount + '/' + path : mount + path; + } + path = path ? node.name + '/' + path : node.name; + node = node.parent; + } + }, + + // + // nodes + // + hashName: function(parentid, name) { + var hash = 0; + +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + + for (var i = 0; i < name.length; i++) { + hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0; + } + return ((parentid + hash) >>> 0) % PThreadFS.nameTable.length; + }, + hashAddNode: function(node) { + var hash = PThreadFS.hashName(node.parent.id, node.name); + node.name_next = PThreadFS.nameTable[hash]; + PThreadFS.nameTable[hash] = node; + }, + hashRemoveNode: function(node) { + var hash = PThreadFS.hashName(node.parent.id, node.name); + if (PThreadFS.nameTable[hash] === node) { + PThreadFS.nameTable[hash] = node.name_next; + } else { + var current = PThreadFS.nameTable[hash]; + while (current) { + if (current.name_next === node) { + current.name_next = node.name_next; + break; + } + current = current.name_next; + } + } + }, + lookupNode: async function(parent, name) { + var errCode = PThreadFS.mayLookup(parent); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode, parent); + } + var hash = PThreadFS.hashName(parent.id, name); +#if CASE_INSENSITIVE_FS + name = name.toLowerCase(); +#endif + for (var node = PThreadFS.nameTable[hash]; node; node = node.name_next) { + var nodeName = node.name; +#if CASE_INSENSITIVE_FS + nodeName = nodeName.toLowerCase(); +#endif + if (node.parent.id === parent.id && nodeName === name) { + return node; + } + } + // if we failed to find it in the cache, call into the VFS + return await PThreadFS.lookup(parent, name); + }, + createNode: function(parent, name, mode, rdev) { +#if ASSERTIONS + assert(typeof parent === 'object') +#endif + var node = new PThreadFS.FSNode(parent, name, mode, rdev); + + PThreadFS.hashAddNode(node); + + return node; + }, + destroyNode: function(node) { + PThreadFS.hashRemoveNode(node); + }, + isRoot: function(node) { + return node === node.parent; + }, + isMountpoint: function(node) { + return !!node.mounted; + }, + isFile: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFREG') }}}; + }, + isDir: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFDIR') }}}; + }, + isLink: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFLNK') }}}; + }, + isChrdev: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFCHR') }}}; + }, + isBlkdev: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFBLK') }}}; + }, + isFIFO: function(mode) { + return (mode & {{{ cDefine('S_IFMT') }}}) === {{{ cDefine('S_IFIFO') }}}; + }, + isSocket: function(mode) { + return (mode & {{{ cDefine('S_IFSOCK') }}}) === {{{ cDefine('S_IFSOCK') }}}; + }, + + // + // permissions + // + flagModes: { + // Extra quotes used here on the keys to this object otherwise jsifier will + // erase them in the process of reading and then writing the JS library + // code. + '"r"': {{{ cDefine('O_RDONLY') }}}, + '"r+"': {{{ cDefine('O_RDWR') }}}, + '"w"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"w+"': {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + '"a"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_WRONLY') }}}, + '"a+"': {{{ cDefine('O_APPEND') }}} | {{{ cDefine('O_CREAT') }}} | {{{ cDefine('O_RDWR') }}}, + }, + // convert the 'r', 'r+', etc. to it's corresponding set of O_* flags + modeStringToFlags: function(str) { + var flags = PThreadFS.flagModes[str]; + if (typeof flags === 'undefined') { + throw new Error('Unknown file open mode: ' + str); + } + return flags; + }, + // convert O_* bitmask to a string for nodePermissions + flagsToPermissionString: function(flag) { + var perms = ['r', 'w', 'rw'][flag & 3]; + if ((flag & {{{ cDefine('O_TRUNC') }}})) { + perms += 'w'; + } + return perms; + }, + nodePermissions: function(node, perms) { + if (PThreadFS.ignorePermissions) { + return 0; + } + // return 0 if any user, group or owner bits are set. + if (perms.includes('r') && !(node.mode & {{{ cDefine('S_IRUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } else if (perms.includes('w') && !(node.mode & {{{ cDefine('S_IWUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } else if (perms.includes('x') && !(node.mode & {{{ cDefine('S_IXUGO') }}})) { + return {{{ cDefine('EACCES') }}}; + } + return 0; + }, + mayLookup: function(dir) { + var errCode = PThreadFS.nodePermissions(dir, 'x'); + if (errCode) return errCode; + if (!dir.node_ops.lookup) return {{{ cDefine('EACCES') }}}; + return 0; + }, + mayCreate: async function(dir, name) { + try { + var node = await PThreadFS.lookupNode(dir, name); + return {{{ cDefine('EEXIST') }}}; + } catch (e) { + } + return PThreadFS.nodePermissions(dir, 'wx'); + }, + mayDelete: async function(dir, name, isdir) { + var node; + try { + node = await PThreadFS.lookupNode(dir, name); + } catch (e) { + return e.errno; + } + var errCode = PThreadFS.nodePermissions(dir, 'wx'); + if (errCode) { + return errCode; + } + if (isdir) { + if (!PThreadFS.isDir(node.mode)) { + return {{{ cDefine('ENOTDIR') }}}; + } + if (PThreadFS.isRoot(node) || PThreadFS.getPath(node) === PThreadFS.cwd()) { + return {{{ cDefine('EBUSY') }}}; + } + } else { + if (PThreadFS.isDir(node.mode)) { + return {{{ cDefine('EISDIR') }}}; + } + } + return 0; + }, + mayOpen: function(node, flags) { + if (!node) { + return {{{ cDefine('ENOENT') }}}; + } + if (PThreadFS.isLink(node.mode)) { + return {{{ cDefine('ELOOP') }}}; + } else if (PThreadFS.isDir(node.mode)) { + if (PThreadFS.flagsToPermissionString(flags) !== 'r' || // opening for write + (flags & {{{ cDefine('O_TRUNC') }}})) { // TODO: check for O_SEARCH? (== search for dir only) + return {{{ cDefine('EISDIR') }}}; + } + } + return PThreadFS.nodePermissions(node, PThreadFS.flagsToPermissionString(flags)); + }, + + // + // streams + // + MAX_OPEN_FDS: 4096, + nextfd: function(fd_start, fd_end) { + fd_start = fd_start || 0; + fd_end = fd_end || PThreadFS.MAX_OPEN_FDS; + for (var fd = fd_start; fd <= fd_end; fd++) { + if (!PThreadFS.streams[fd]) { + return fd; + } + } + throw new PThreadFS.ErrnoError({{{ cDefine('EMFILE') }}}); + }, + getStream: function(fd) { + return PThreadFS.streams[fd]; + }, + // TODO parameterize this function such that a stream + // object isn't directly passed in. not possible until + // SOCKFS is completed. + createStream: function(stream, fd_start, fd_end) { + if (!PThreadFS.FSStream) { + PThreadFS.FSStream = /** @constructor */ function(){}; + PThreadFS.FSStream.prototype = { + object: { + get: function() { return this.node; }, + set: function(val) { this.node = val; } + }, + isRead: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}; } + }, + isWrite: { + get: function() { return (this.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}; } + }, + isAppend: { + get: function() { return (this.flags & {{{ cDefine('O_APPEND') }}}); } + } + }; + } + // clone it, so we can return an instance of FSStream + var newStream = new PThreadFS.FSStream(); + for (var p in stream) { + newStream[p] = stream[p]; + } + stream = newStream; + var fd = PThreadFS.nextfd(fd_start, fd_end); + stream.fd = fd; + PThreadFS.streams[fd] = stream; + return stream; + }, + closeStream: function(fd) { + PThreadFS.streams[fd] = null; + }, + + // + // devices + // + // each character device consists of a device id + stream operations. + // when a character device node is created (e.g. /dev/stdin) it is + // assigned a device id that lets us map back to the actual device. + // by default, each character device stream (e.g. _stdin) uses chrdev_stream_ops. + // however, once opened, the stream's operations are overridden with + // the operations of the device its underlying node maps back to. + chrdev_stream_ops: { + open: function(stream) { + var device = PThreadFS.getDevice(stream.node.rdev); + // override node's stream ops with the device's + stream.stream_ops = device.stream_ops; + // forward the open call + if (stream.stream_ops.open) { + stream.stream_ops.open(stream); + } + }, + llseek: function() { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + }, + major: function(dev) { + return ((dev) >> 8); + }, + minor: function(dev) { + return ((dev) & 0xff); + }, + makedev: function(ma, mi) { + return ((ma) << 8 | (mi)); + }, + registerDevice: function(dev, ops) { + PThreadFS.devices[dev] = { stream_ops: ops }; + }, + getDevice: function(dev) { + return PThreadFS.devices[dev]; + }, + + // + // core + // + getMounts: function(mount) { + var mounts = []; + var check = [mount]; + + while (check.length) { + var m = check.pop(); + + mounts.push(m); + + check.push.apply(check, m.mounts); + } + + return mounts; + }, + syncfs: function(populate, callback) { + if (typeof(populate) === 'function') { + callback = populate; + populate = false; + } + + PThreadFS.syncFSRequests++; + + if (PThreadFS.syncFSRequests > 1) { + err('warning: ' + PThreadFS.syncFSRequests + ' PThreadFS.syncfs operations in flight at once, probably just doing extra work'); + } + + var mounts = PThreadFS.getMounts(PThreadFS.root.mount); + var completed = 0; + + function doCallback(errCode) { +#if ASSERTIONS + assert(PThreadFS.syncFSRequests > 0); +#endif + PThreadFS.syncFSRequests--; + return callback(errCode); + } + + function done(errCode) { + if (errCode) { + if (!done.errored) { + done.errored = true; + return doCallback(errCode); + } + return; + } + if (++completed >= mounts.length) { + doCallback(null); + } + }; + + // sync all mounts + mounts.forEach(function (mount) { + if (!mount.type.syncfs) { + return done(null); + } + mount.type.syncfs(mount, populate, done); + }); + }, + mount: async function(type, opts, mountpoint) { +#if ASSERTIONS + if (typeof type === 'string') { + // The filesystem was not included, and instead we have an error + // message stored in the variable. + throw type; + } +#endif + var root = mountpoint === '/'; + var pseudo = !mountpoint; + var node; + + if (root && PThreadFS.root) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } else if (!root && !pseudo) { + var lookup = await PThreadFS.lookupPath(mountpoint, { follow_mount: false }); + + mountpoint = lookup.path; // use the absolute path + node = lookup.node; + + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + + if (!PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + } + + var mount = { + type: type, + opts: opts, + mountpoint: mountpoint, + mounts: [] + }; + + // create a root node for the fs + var mountRoot = await type.mount(mount); + mountRoot.mount = mount; + mount.root = mountRoot; + + if (root) { + PThreadFS.root = mountRoot; + } else if (node) { + // set as a mountpoint + node.mounted = mount; + + // add the new mount to the current mount's children + if (node.mount) { + node.mount.mounts.push(mount); + } + } + + return mountRoot; + }, + unmount: async function (mountpoint) { + var lookup = await PThreadFS.lookupPath(mountpoint, { follow_mount: false }); + + if (!PThreadFS.isMountpoint(lookup.node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + + // destroy the nodes for this mount, and all its child mounts + var node = lookup.node; + var mount = node.mounted; + var mounts = PThreadFS.getMounts(mount); + + Object.keys(PThreadFS.nameTable).forEach(function (hash) { + var current = PThreadFS.nameTable[hash]; + + while (current) { + var next = current.name_next; + + if (mounts.includes(current.mount)) { + PThreadFS.destroyNode(current); + } + + current = next; + } + }); + + // no longer a mountpoint + node.mounted = null; + + // remove this mount from the child mounts + var idx = node.mount.mounts.indexOf(mount); +#if ASSERTIONS + assert(idx !== -1); +#endif + node.mount.mounts.splice(idx, 1); + }, + lookup: async function(parent, name) { + return await parent.node_ops.lookup(parent, name); + }, + // generic function for all node creation + mknod: async function(path, mode, dev) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + if (!name || name === '.' || name === '..') { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var errCode = await PThreadFS.mayCreate(parent, name); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.mknod) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return await parent.node_ops.mknod(parent, name, mode, dev); + }, + // helpers to create specific types of nodes + create: async function(path, mode) { + mode = mode !== undefined ? mode : 438 /* 0666 */; + mode &= {{{ cDefine('S_IALLUGO') }}}; + mode |= {{{ cDefine('S_IFREG') }}}; + return await PThreadFS.mknod(path, mode, 0); + }, + mkdir: async function(path, mode) { + mode = mode !== undefined ? mode : 511 /* 0777 */; + mode &= {{{ cDefine('S_IRWXUGO') }}} | {{{ cDefine('S_ISVTX') }}}; + mode |= {{{ cDefine('S_IFDIR') }}}; + return await PThreadFS.mknod(path, mode, 0); + }, + // Creates a whole directory tree chain if it doesn't yet exist + mkdirTree: async function(path, mode) { + var dirs = path.split('/'); + var d = ''; + for (var i = 0; i < dirs.length; ++i) { + if (!dirs[i]) continue; + d += '/' + dirs[i]; + try { + await PThreadFS.mkdir(d, mode); + } catch(e) { + if (e.errno != {{{ cDefine('EEXIST') }}}) throw e; + } + } + }, + mkdev: async function(path, mode, dev) { + if (typeof(dev) === 'undefined') { + dev = mode; + mode = 438 /* 0666 */; + } + mode |= {{{ cDefine('S_IFCHR') }}}; + return await PThreadFS.mknod(path, mode, dev); + }, + symlink: async function(oldpath, newpath) { + if (!PATH_FS.resolve(oldpath)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + var lookup = await PThreadFS.lookupPath(newpath, { parent: true }); + var parent = lookup.node; + if (!parent) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + var newname = PATH.basename(newpath); + var errCode = await PThreadFS.mayCreate(parent, newname); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.symlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return parent.node_ops.symlink(parent, newname, oldpath); + }, + rename: async function(old_path, new_path) { + var old_dirname = PATH.dirname(old_path); + var new_dirname = PATH.dirname(new_path); + var old_name = PATH.basename(old_path); + var new_name = PATH.basename(new_path); + // parents must exist + var lookup, old_dir, new_dir; + + // let the errors from non existant directories percolate up + lookup = await PThreadFS.lookupPath(old_path, { parent: true }); + old_dir = lookup.node; + lookup = await PThreadFS.lookupPath(new_path, { parent: true }); + new_dir = lookup.node; + + if (!old_dir || !new_dir) throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + // need to be part of the same mount + if (old_dir.mount !== new_dir.mount) { + throw new PThreadFS.ErrnoError({{{ cDefine('EXDEV') }}}); + } + // source must exist + var old_node = await PThreadFS.lookupNode(old_dir, old_name); + // old path should not be an ancestor of the new path + var relative = PATH_FS.relative(old_path, new_dirname); + if (relative.charAt(0) !== '.') { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + // new path should not be an ancestor of the old path + relative = PATH_FS.relative(new_path, old_dirname); + if (relative.charAt(0) !== '.') { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + // see if the new path already exists + var new_node; + try { + new_node = await PThreadFS.lookupNode(new_dir, new_name); + } catch (e) { + // not fatal + } + // early out if nothing needs to change + if (old_node === new_node) { + return; + } + // we'll need to delete the old entry + var isdir = PThreadFS.isDir(old_node.mode); + var errCode = await PThreadFS.mayDelete(old_dir, old_name, isdir); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + // need delete permissions if we'll be overwriting. + // need create permissions if new doesn't already exist. + errCode = new_node ? + await PThreadFS.mayDelete(new_dir, new_name, isdir) : + await PThreadFS.mayCreate(new_dir, new_name); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!old_dir.node_ops.rename) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(old_node) || (new_node && PThreadFS.isMountpoint(new_node))) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + // if we are going to change the parent, check write permissions + if (new_dir !== old_dir) { + errCode = PThreadFS.nodePermissions(old_dir, 'w'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + } + try { + if (PThreadFS.trackingDelegate['willMovePath']) { + PThreadFS.trackingDelegate['willMovePath'](old_path, new_path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } + // remove the node from the lookup hash + PThreadFS.hashRemoveNode(old_node); + // do the underlying fs rename + try { + await old_dir.node_ops.rename(old_node, new_dir, new_name); + } catch (e) { + throw e; + } finally { + // add the node back to the hash (in case node_ops.rename + // changed its name) + PThreadFS.hashAddNode(old_node); + } + try { + if (PThreadFS.trackingDelegate['onMovePath']) PThreadFS.trackingDelegate['onMovePath'](old_path, new_path); + } catch(e) { + err("PThreadFS.trackingDelegate['onMovePath']('"+old_path+"', '"+new_path+"') threw an exception: " + e.message); + } + }, + rmdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = await PThreadFS.lookupNode(parent, name); + var errCode = await PThreadFS.mayDelete(parent, name, true); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.rmdir) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + try { + if (PThreadFS.trackingDelegate['willDeletePath']) { + PThreadFS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } + await parent.node_ops.rmdir(parent, name); + PThreadFS.destroyNode(node); + try { + if (PThreadFS.trackingDelegate['onDeletePath']) PThreadFS.trackingDelegate['onDeletePath'](path); + } catch(e) { + err("PThreadFS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } + }, + readdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + var node = lookup.node; + if (!node.node_ops.readdir) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + return await node.node_ops.readdir(node); + }, + unlink: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + var parent = lookup.node; + var name = PATH.basename(path); + var node = await PThreadFS.lookupNode(parent, name); + var errCode = await PThreadFS.mayDelete(parent, name, false); + if (errCode) { + // According to POSIX, we should map EISDIR to EPERM, but + // we instead do what Linux does (and we must, as we use + // the musl linux libc). + throw new PThreadFS.ErrnoError(errCode); + } + if (!parent.node_ops.unlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isMountpoint(node)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBUSY') }}}); + } + try { + if (PThreadFS.trackingDelegate['willDeletePath']) { + PThreadFS.trackingDelegate['willDeletePath'](path); + } + } catch(e) { + err("PThreadFS.trackingDelegate['willDeletePath']('"+path+"') threw an exception: " + e.message); + } + await parent.node_ops.unlink(parent, name); + PThreadFS.destroyNode(node); + try { + if (PThreadFS.trackingDelegate['onDeletePath']) PThreadFS.trackingDelegate['onDeletePath'](path); + } catch(e) { + err("PThreadFS.trackingDelegate['onDeletePath']('"+path+"') threw an exception: " + e.message); + } + }, + readlink: async function(path) { + var lookup = await PThreadFS.lookupPath(path); + var link = lookup.node; + if (!link) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!link.node_ops.readlink) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return PATH_FS.resolve(PThreadFS.getPath(link.parent), link.node_ops.readlink(link)); + }, + stat: async function(path, dontFollow) { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + var node = lookup.node; + if (!node) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!node.node_ops.getattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + return await node.node_ops.getattr(node); + }, + lstat: async function(path) { + return await PThreadFS.stat(path, true); + }, + chmod: async function(path, mode, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + await node.node_ops.setattr(node, { + mode: (mode & {{{ cDefine('S_IALLUGO') }}}) | (node.mode & ~{{{ cDefine('S_IALLUGO') }}}), + timestamp: Date.now() + }); + }, + lchmod: async function(path, mode) { + await PThreadFS.chmod(path, mode, true); + }, + fchmod: async function(fd, mode) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await PThreadFS.chmod(stream.node, mode); + }, + chown: async function(path, uid, gid, dontFollow) { + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontFollow }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + await node.node_ops.setattr(node, { + timestamp: Date.now() + // we ignore the uid / gid for now + }); + }, + lchown: async function(path, uid, gid) { + await PThreadFS.chown(path, uid, gid, true); + }, + fchown: async function(fd, uid, gid) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await PThreadFS.chown(stream.node, uid, gid); + }, + truncate: async function(path, len) { + if (len < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var node; + if (typeof path === 'string') { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + node = lookup.node; + } else { + node = path; + } + if (!node.node_ops.setattr) { + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!PThreadFS.isFile(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var errCode = PThreadFS.nodePermissions(node, 'w'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + await node.node_ops.setattr(node, { + size: len, + timestamp: Date.now() + }); + }, + ftruncate: async function(fd, len) { + var stream = PThreadFS.getStream(fd); + if (!stream) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + await PThreadFS.truncate(stream.node, len); + }, + utime: async function(path, atime, mtime) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + var node = lookup.node; + await node.node_ops.setattr(node, { + timestamp: Math.max(atime, mtime) + }); + }, + open: async function(path, flags, mode, fd_start, fd_end) { + if (path === "") { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + flags = typeof flags === 'string' ? PThreadFS.modeStringToFlags(flags) : flags; + mode = typeof mode === 'undefined' ? 438 /* 0666 */ : mode; + if ((flags & {{{ cDefine('O_CREAT') }}})) { + mode = (mode & {{{ cDefine('S_IALLUGO') }}}) | {{{ cDefine('S_IFREG') }}}; + } else { + mode = 0; + } + var node; + if (typeof path === 'object') { + node = path; + } else { + path = PATH.normalize(path); + try { + var lookup = await PThreadFS.lookupPath(path, { + follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}}) + }); + node = lookup.node; + } catch (e) { + // ignore + } + } + // perhaps we need to create the node + var created = false; + if ((flags & {{{ cDefine('O_CREAT') }}})) { + if (node) { + // if O_CREAT and O_EXCL are set, error out if the node already exists + if ((flags & {{{ cDefine('O_EXCL') }}})) { + throw new PThreadFS.ErrnoError({{{ cDefine('EEXIST') }}}); + } + } else { + // node doesn't exist, try to create it + node = await PThreadFS.mknod(path, mode, 0); + created = true; + } + } + if (!node) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + // can't truncate a device + if (PThreadFS.isChrdev(node.mode)) { + flags &= ~{{{ cDefine('O_TRUNC') }}}; + } + // if asked only for a directory, then this must be one + if ((flags & {{{ cDefine('O_DIRECTORY') }}}) && !PThreadFS.isDir(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + // check permissions, if this is not a file we just created now (it is ok to + // create and write to a file with read-only permissions; it is read-only + // for later use) + if (!created) { + var errCode = PThreadFS.mayOpen(node, flags); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + } + // do truncation if necessary + if ((flags & {{{ cDefine('O_TRUNC')}}})) { + await PThreadFS.truncate(node, 0); + } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~({{{ cDefine('O_EXCL') }}} | {{{ cDefine('O_TRUNC') }}} | {{{ cDefine('O_NOFOLLOW') }}}); + + // register the stream with the filesystem + var stream = PThreadFS.createStream({ + node: node, + path: PThreadFS.getPath(node), // we want the absolute path to the node + flags: flags, + seekable: true, + position: 0, + stream_ops: node.stream_ops, + // used by the file family libc calls (fopen, fwrite, ferror, etc.) + ungotten: [], + error: false + }, fd_start, fd_end); + // call the new stream's open function + if (stream.stream_ops.open) { + await stream.stream_ops.open(stream); + } + if (Module['logReadFiles'] && !(flags & {{{ cDefine('O_WRONLY')}}})) { + if (!PThreadFS.readFiles) PThreadFS.readFiles = {}; + if (!(path in PThreadFS.readFiles)) { + PThreadFS.readFiles[path] = 1; + err("PThreadFS.trackingDelegate error on read file: " + path); + } + } + try { + if (PThreadFS.trackingDelegate['onOpenFile']) { + var trackingFlags = 0; + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_WRONLY') }}}) { + trackingFlags |= PThreadFS.tracking.openFlags.READ; + } + if ((flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDONLY') }}}) { + trackingFlags |= PThreadFS.tracking.openFlags.WRITE; + } + PThreadFS.trackingDelegate['onOpenFile'](path, trackingFlags); + } + } catch(e) { + err("PThreadFS.trackingDelegate['onOpenFile']('"+path+"', flags) threw an exception: " + e.message); + } + return stream; + }, + close: async function(stream) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (stream.getdents) stream.getdents = null; // free readdir state + try { + if (stream.stream_ops.close) { + await stream.stream_ops.close(stream); + } + } catch (e) { + throw e; + } finally { + PThreadFS.closeStream(stream.fd); + } + stream.fd = null; + }, + isClosed: function(stream) { + return stream.fd === null; + }, + llseek: async function(stream, offset, whence) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (!stream.seekable || !stream.stream_ops.llseek) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + if (whence != {{{ cDefine('SEEK_SET') }}} && whence != {{{ cDefine('SEEK_CUR') }}} && whence != {{{ cDefine('SEEK_END') }}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + stream.position = await stream.stream_ops.llseek(stream, offset, whence); + stream.ungotten = []; + return stream.position; + }, + read: async function(stream, buffer, offset, length, position) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (length < 0 || position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!stream.stream_ops.read) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var seeking = typeof position !== 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + var bytesRead = await stream.stream_ops.read(stream, buffer, offset, length, position); + if (!seeking) stream.position += bytesRead; + return bytesRead; + }, + write: async function(stream, buffer, offset, length, position, canOwn) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (length < 0 || position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!stream.stream_ops.write) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (stream.seekable && stream.flags & {{{ cDefine('O_APPEND') }}}) { + // seek to the end before writing in append mode + await PThreadFS.llseek(stream, 0, {{{ cDefine('SEEK_END') }}}); + } + var seeking = typeof position !== 'undefined'; + if (!seeking) { + position = stream.position; + } else if (!stream.seekable) { + throw new PThreadFS.ErrnoError({{{ cDefine('ESPIPE') }}}); + } + var bytesWritten = await stream.stream_ops.write(stream, buffer, offset, length, position, canOwn); + if (!seeking) stream.position += bytesWritten; + try { + if (stream.path && PThreadFS.trackingDelegate['onWriteToFile']) PThreadFS.trackingDelegate['onWriteToFile'](stream.path); + } catch(e) { + err("PThreadFS.trackingDelegate['onWriteToFile']('"+stream.path+"') threw an exception: " + e.message); + } + return bytesWritten; + }, + allocate: async function(stream, offset, length) { + if (PThreadFS.isClosed(stream)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (offset < 0 || length <= 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_RDONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + if (!PThreadFS.isFile(stream.node.mode) && !PThreadFS.isDir(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + if (!stream.stream_ops.allocate) { + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + } + await stream.stream_ops.allocate(stream, offset, length); + }, + mmap: async function(stream, address, length, position, prot, flags) { +#if CAN_ADDRESS_2GB + address >>>= 0; +#endif + // User requests writing to file (prot & PROT_WRITE != 0). + // Checking if we have permissions to write to the file unless + // MAP_PRIVATE flag is set. According to POSIX spec it is possible + // to write to file opened in read-only mode with MAP_PRIVATE flag, + // as all modifications will be visible only in the memory of + // the current process. + if ((prot & {{{ cDefine('PROT_WRITE') }}}) !== 0 + && (flags & {{{ cDefine('MAP_PRIVATE')}}}) === 0 + && (stream.flags & {{{ cDefine('O_ACCMODE') }}}) !== {{{ cDefine('O_RDWR')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EACCES') }}}); + } + if ((stream.flags & {{{ cDefine('O_ACCMODE') }}}) === {{{ cDefine('O_WRONLY')}}}) { + throw new PThreadFS.ErrnoError({{{ cDefine('EACCES') }}}); + } + if (!stream.stream_ops.mmap) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + return await stream.stream_ops.mmap(stream, address, length, position, prot, flags); + }, + msync: async function(stream, buffer, offset, length, mmapFlags) { +#if CAN_ADDRESS_2GB + offset >>>= 0; +#endif + if (!stream || !stream.stream_ops.msync) { + return 0; + } + return await stream.stream_ops.msync(stream, buffer, offset, length, mmapFlags); + }, + munmap: function(stream) { + return 0; + }, + ioctl: async function(stream, cmd, arg) { + if (!stream.stream_ops.ioctl) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTTY') }}}); + } + return await stream.stream_ops.ioctl(stream, cmd, arg); + }, + readFile: async function(path, opts) { + opts = opts || {}; + opts.flags = opts.flags || {{{ cDefine('O_RDONLY') }}}; + opts.encoding = opts.encoding || 'binary'; + if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') { + throw new Error('Invalid encoding type "' + opts.encoding + '"'); + } + var ret; + var stream = await PThreadFS.open(path, opts.flags); + var stat = await PThreadFS.stat(path); + var length = stat.size; + var buf = new Uint8Array(length); + await PThreadFS.read(stream, buf, 0, length, 0); + if (opts.encoding === 'utf8') { + ret = UTF8ArrayToString(buf, 0); + } else if (opts.encoding === 'binary') { + ret = buf; + } + await PThreadFS.close(stream); + return ret; + }, + writeFile: async function(path, data, opts) { + opts = opts || {}; + opts.flags = opts.flags || {{{ cDefine('O_TRUNC') | cDefine('O_CREAT') | cDefine('O_WRONLY') }}}; + var stream = await PThreadFS.open(path, opts.flags, opts.mode); + if (typeof data === 'string') { + var buf = new Uint8Array(lengthBytesUTF8(data)+1); + var actualNumBytes = stringToUTF8Array(data, buf, 0, buf.length); + await PThreadFS.write(stream, buf, 0, actualNumBytes, undefined, opts.canOwn); + } else if (ArrayBuffer.isView(data)) { + await PThreadFS.write(stream, data, 0, data.byteLength, undefined, opts.canOwn); + } else { + throw new Error('Unsupported data type'); + } + await PThreadFS.close(stream); + }, + + // + // module-level FS code + // + cwd: function() { + return PThreadFS.currentPath; + }, + chdir: async function(path) { + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + if (lookup.node === null) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}}); + } + if (!PThreadFS.isDir(lookup.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTDIR') }}}); + } + var errCode = PThreadFS.nodePermissions(lookup.node, 'x'); + if (errCode) { + throw new PThreadFS.ErrnoError(errCode); + } + PThreadFS.currentPath = lookup.path; + }, + createDefaultDirectories: async function() { + await PThreadFS.mkdir('/tmp'); + await PThreadFS.mkdir('/home'); + await PThreadFS.mkdir('/home/web_user'); + }, + createDefaultDevices: async function() { + // create /dev + await PThreadFS.mkdir('/dev'); + // setup /dev/null + PThreadFS.registerDevice(PThreadFS.makedev(1, 3), { + read: function() { return 0; }, + write: function(stream, buffer, offset, length, pos) { return length; } + }); + await PThreadFS.mkdev('/dev/null', PThreadFS.makedev(1, 3)); + // setup /dev/tty and /dev/tty1 + // stderr needs to print output using err() rather than out() + // so we register a second tty just for it. + TTY_ASYNC.register(PThreadFS.makedev(5, 0), TTY_ASYNC.default_tty_ops); + TTY_ASYNC.register(PThreadFS.makedev(6, 0), TTY_ASYNC.default_tty1_ops); + await PThreadFS.mkdev('/dev/tty', PThreadFS.makedev(5, 0)); + await PThreadFS.mkdev('/dev/tty1', PThreadFS.makedev(6, 0)); + // setup /dev/[u]random + var random_device = getRandomDevice(); + await PThreadFS.createDevice('/dev', 'random', random_device); + await PThreadFS.createDevice('/dev', 'urandom', random_device); + // we're not going to emulate the actual shm device, + // just create the tmp dirs that reside in it commonly + await PThreadFS.mkdir('/dev/shm'); + await PThreadFS.mkdir('/dev/shm/tmp'); + }, + createSpecialDirectories: async function() { + // create /proc/self/fd which allows /proc/self/fd/6 => readlink gives the + // name of the stream for fd 6 (see test_unistd_ttyname) + await PThreadFS.mkdir('/proc'); + var proc_self = await PThreadFS.mkdir('/proc/self'); + await PThreadFS.mkdir('/proc/self/fd'); + await PThreadFS.mount({ + mount: function() { + var node = PThreadFS.createNode(proc_self, 'fd', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, {{{ cDefine('S_IXUGO') }}}); + node.node_ops = { + lookup: function(parent, name) { + var fd = +name; + var stream = PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + var ret = { + parent: null, + mount: { mountpoint: 'fake' }, + node_ops: { readlink: function() { return stream.path } } + }; + ret.parent = ret; // make it look like a simple root node + return ret; + } + }; + return node; + } + }, {}, '/proc/self/fd'); + }, + createStandardStreams: async function() { + // TODO deprecate the old functionality of a single + // input / output callback and that utilizes PThreadFS.createDevice + // and instead require a unique set of stream ops + + // by default, we symlink the standard streams to the + // default tty devices. however, if the standard streams + // have been overwritten we create a unique device for + // them instead. + if (Module['stdin']) { + await PThreadFS.createDevice('/dev', 'stdin', Module['stdin']); + } else { + await PThreadFS.symlink('/dev/tty', '/dev/stdin'); + } + if (Module['stdout']) { + await PThreadFS.createDevice('/dev', 'stdout', null, Module['stdout']); + } else { + await PThreadFS.symlink('/dev/tty', '/dev/stdout'); + } + if (Module['stderr']) { + await PThreadFS.createDevice('/dev', 'stderr', null, Module['stderr']); + } else { + await PThreadFS.symlink('/dev/tty1', '/dev/stderr'); + } + + // open default streams for the stdin, stdout and stderr devices + var stdin = await PThreadFS.open('/dev/stdin', {{{ cDefine('O_RDONLY') }}}); + var stdout = await PThreadFS.open('/dev/stdout', {{{ cDefine('O_WRONLY') }}}); + var stderr = await PThreadFS.open('/dev/stderr', {{{ cDefine('O_WRONLY') }}}); +#if ASSERTIONS + assert(stdin.fd === 0, 'invalid handle for stdin (' + stdin.fd + ')'); + assert(stdout.fd === 1, 'invalid handle for stdout (' + stdout.fd + ')'); + assert(stderr.fd === 2, 'invalid handle for stderr (' + stderr.fd + ')'); +#endif + }, + ensureErrnoError: function() { + if (PThreadFS.ErrnoError) return; + PThreadFS.ErrnoError = /** @this{Object} */ function ErrnoError(errno, node) { + this.node = node; + this.setErrno = /** @this{Object} */ function(errno) { + this.errno = errno; +#if ASSERTIONS + for (var key in ERRNO_CODES) { + if (ERRNO_CODES[key] === errno) { + this.code = key; + break; + } + } +#endif + }; + this.setErrno(errno); +#if ASSERTIONS + this.message = ERRNO_MESSAGES[errno]; +#else + this.message = 'PThreadFS error'; +#endif + +#if ASSERTIONS && !MINIMAL_RUNTIME + // Try to get a maximally helpful stack trace. On Node.js, getting Error.stack + // now ensures it shows what we want. + if (this.stack) { + // Define the stack property for Node.js 4, which otherwise errors on the next line. + Object.defineProperty(this, "stack", { value: (new Error).stack, writable: true }); + this.stack = demangleAll(this.stack); + } +#endif // ASSERTIONS + }; + PThreadFS.ErrnoError.prototype = new Error(); + PThreadFS.ErrnoError.prototype.constructor = PThreadFS.ErrnoError; + // Some errors may happen quite a bit, to avoid overhead we reuse them (and suffer a lack of stack info) + [{{{ cDefine('ENOENT') }}}].forEach(function(code) { + PThreadFS.genericErrors[code] = new PThreadFS.ErrnoError(code); + PThreadFS.genericErrors[code].stack = ''; + }); + }, + staticInit: async function() { + PThreadFS.ensureErrnoError(); + + PThreadFS.nameTable = new Array(4096); + + await PThreadFS.mount(MEMFS_ASYNC, {}, '/'); + + await PThreadFS.createDefaultDirectories(); + await PThreadFS.createDefaultDevices(); + await PThreadFS.createSpecialDirectories(); + + PThreadFS.filesystems = { + 'MEMFS_ASYNC': MEMFS_ASYNC, + }; + }, + init: async function(input, output, error) { +#if ASSERTIONS + assert(!PThreadFS.init.initialized, 'PThreadFS.init was previously called. If you want to initialize later with custom parameters, remove any earlier calls (note that one is automatically added to the generated code)'); +#endif + PThreadFS.init.initialized = true; + + PThreadFS.ensureErrnoError(); + + // Allow Module.stdin etc. to provide defaults, if none explicitly passed to us here + Module['stdin'] = input || Module['stdin']; + Module['stdout'] = output || Module['stdout']; + Module['stderr'] = error || Module['stderr']; + + await PThreadFS.createStandardStreams(); + }, + quit: async function() { + // TODO(rstz): This function is never called. + PThreadFS.init.initialized = false; + // force-flush all streams, so we get musl std streams printed out + var fflush = Module['_fflush']; + if (fflush) fflush(0); + // close all of our streams + for (var i = 0; i < PThreadFS.streams.length; i++) { + var stream = PThreadFS.streams[i]; + if (!stream) { + continue; + } + await PThreadFS.close(stream); + } + }, + + // + // old v1 compatibility functions + // + getMode: function(canRead, canWrite) { + var mode = 0; + if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}}; + if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}}; + return mode; + }, + findObject: async function(path, dontResolveLastLink) { + var ret = await PThreadFS.analyzePath(path, dontResolveLastLink); + if (ret.exists) { + return ret.object; + } else { + return null; + } + }, + analyzePath: async function(path, dontResolveLastLink) { + // operate from within the context of the symlink's target + try { + var lookup = await PThreadFS.lookupPath(path, { follow: !dontResolveLastLink }); + path = lookup.path; + } catch (e) { + } + var ret = { + isRoot: false, exists: false, error: 0, name: null, path: null, object: null, + parentExists: false, parentPath: null, parentObject: null + }; + try { + var lookup = await PThreadFS.lookupPath(path, { parent: true }); + ret.parentExists = true; + ret.parentPath = lookup.path; + ret.parentObject = lookup.node; + ret.name = PATH.basename(path); + lookup = await PThreadFS.lookupPath(path, { follow: !dontResolveLastLink }); + ret.exists = true; + ret.path = lookup.path; + ret.object = lookup.node; + ret.name = lookup.node.name; + ret.isRoot = lookup.path === '/'; + } catch (e) { + ret.error = e.errno; + }; + return ret; + }, + createPath: async function(parent, path, canRead, canWrite) { + parent = typeof parent === 'string' ? parent : PThreadFS.getPath(parent); + var parts = path.split('/').reverse(); + while (parts.length) { + var part = parts.pop(); + if (!part) continue; + var current = PATH.join2(parent, part); + try { + await PThreadFS.mkdir(current); + } catch (e) { + // ignore EEXIST + } + parent = current; + } + return current; + }, + createFile: async function(parent, name, properties, canRead, canWrite) { + var path = PATH.join2(typeof parent === 'string' ? parent : await PThreadFS.getPath(parent), name); + var mode = await PThreadFS.getMode(canRead, canWrite); + return await PThreadFS.create(path, mode); + }, + createDataFile: async function(parent, name, data, canRead, canWrite, canOwn) { + var path = name ? PATH.join2(typeof parent === 'string' ? parent : await PThreadFS.getPath(parent), name) : parent; + var mode = await PThreadFS.getMode(canRead, canWrite); + var node = await PThreadFS.create(path, mode); + if (data) { + if (typeof data === 'string') { + var arr = new Array(data.length); + for (var i = 0, len = data.length; i < len; ++i) arr[i] = data.charCodeAt(i); + data = arr; + } + // make sure we can write to the file + await PThreadFS.chmod(node, mode | {{{ cDefine('S_IWUGO') }}}); + var stream = await PThreadFS.open(node, {{{ cDefine('O_TRUNC') | cDefine('O_CREAT') | cDefine('O_WRONLY') }}}); + await PThreadFS.write(stream, data, 0, data.length, 0, canOwn); + await PThreadFS.close(stream); + await PThreadFS.chmod(node, mode); + } + return node; + }, + createDevice: async function(parent, name, input, output) { + var path = PATH.join2(typeof parent === 'string' ? parent : PThreadFS.getPath(parent), name); + var mode = PThreadFS.getMode(!!input, !!output); + if (!PThreadFS.createDevice.major) PThreadFS.createDevice.major = 64; + var dev = PThreadFS.makedev(PThreadFS.createDevice.major++, 0); + // Create a fake device that a set of stream ops to emulate + // the old behavior. + PThreadFS.registerDevice(dev, { + open: function(stream) { + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + if (output && output.buffer && output.buffer.length) { + output({{{ charCode('\n') }}}); + } + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = input(); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (result === undefined && bytesRead === 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EAGAIN') }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + for (var i = 0; i < length; i++) { + try { + output(buffer[offset+i]); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }); + return await PThreadFS.mkdev(path, mode, dev); + }, + // Makes sure a file's contents are loaded. Returns whether the file has + // been loaded successfully. No-op for files that have been loaded already. + forceLoadFile: function(obj) { + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; + if (typeof XMLHttpRequest !== 'undefined') { + throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread."); + } else if (read_) { + // Command-line. + try { + // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as + // read() will try to parse UTF8. + obj.contents = intArrayFromString(read_(obj.url), true); + obj.usedBytes = obj.contents.length; + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + } else { + throw new Error('Cannot load without read() or XMLHttpRequest.'); + } + }, + }, +}); \ No newline at end of file diff --git a/pthreadfs/src/js/library_fsafs.js b/pthreadfs/src/js/library_fsafs.js new file mode 100644 index 000000000000..a531a2f97054 --- /dev/null +++ b/pthreadfs/src/js/library_fsafs.js @@ -0,0 +1,292 @@ +/** + * @license + * Copyright 2021 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $FSAFS__deps: ['$PThreadFS'], + $FSAFS: { + + /* Debugging */ + + debug: function(...args) { + // Uncomment to print debug information. + // + // console.log(args); + }, + + /* Filesystem implementation (public interface) */ + + createNode: function (parent, name, mode, dev) { + FSAFS.debug('createNode', arguments); + if (!PThreadFS.isDir(mode) && !PThreadFS.isFile(mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = FSAFS.node_ops; + node.stream_ops = FSAFS.stream_ops; + if (PThreadFS.isDir(mode)) { + node.contents = {}; + } + node.timestamp = Date.now(); + return node; + }, + + mount: async function (mount) { + FSAFS.debug('mount', arguments); + let node = FSAFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); + FSAFS.root = await navigator.storage.getDirectory(); + node.localReference = FSAFS.root; + return node; + }, + + /* Operations on the nodes of the filesystem tree */ + + node_ops: { + getattr: async function(node) { + FSAFS.debug('getattr', arguments); + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = PThreadFS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (PThreadFS.isDir(node.mode)) { + attr.size = 4096; + } else if (PThreadFS.isFile(node.mode)) { + if (node.handle) { + attr.size = await node.handle.getSize(); + } + else { + let fileHandle = await node.localReference.createSyncAccessHandle(); + attr.size = await fileHandle.getSize(); + await fileHandle.close(); + } + } else if (PThreadFS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + + setattr: async function(node, attr) { + FSAFS.debug('setattr', arguments); + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + let useOpen = false; + let fileHandle = node.handle; + try { + if (!fileHandle) { + // Open a handle that is closed later. + useOpen = true; + fileHandle = await node.localReference.createSyncAccessHandle(); + } + await fileHandle.truncate(attr.size); + + } catch (e) { + if (!('code' in e)) throw e; + throw new PThreadFS.ErrnoError(-e.errno); + } finally { + if (useOpen) { + await fileHandle.close(); + } + } + } + }, + + lookup: async function (parent, name) { + FSAFS.debug('lookup', arguments); + let childLocalReference = null; + let mode = null; + try { + childLocalReference = await parent.localReference.getDirectoryHandle(name, {create: false}); + mode = {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */ + } catch (e) { + try { + childLocalReference = await parent.localReference.getFileHandle(name, {create: false}); + mode = {{{ cDefine('S_IFREG') }}} | 511 /* 0777 */ + } catch (e) { + throw PThreadFS.genericErrors[{{{ cDefine('ENOENT') }}}]; + } + } + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = FSAFS.node_ops; + node.stream_ops = FSAFS.stream_ops; + node.localReference = childLocalReference; + return node; + }, + + mknod: async function (parent, name, mode, dev) { + FSAFS.debug('mknod', arguments); + let node = FSAFS.createNode(parent, name, mode, dev); + try { + if (PThreadFS.isDir(mode)) { + node.localReference = await parent.localReference.getDirectoryHandle(name, {create: true}); + } else if (PThreadFS.isFile(mode)) { + node.localReference = await parent.localReference.getFileHandle(name, {create: true}); + } + } catch (e) { + if (!('code' in e)) throw e; + throw new PThreadFS.ErrnoError(-e.errno); + } + + node.handle = null; + node.refcount = 0; + return node; + }, + + rename: function (oldNode, newParentNode, newName) { + FSAFS.debug('rename', arguments); + console.log('FSAFS error: rename is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + unlink: async function(parent, name) { + FSAFS.debug('unlink', arguments); + delete parent.contents[name]; + return await parent.localReference.removeEntry(name); + }, + + rmdir: async function(parent, name) { + FSAFS.debug('rmdir', arguments); + for (var i in node.contents) { + throw new FS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + delete parent.contents[name]; + return await parent.localReference.removeEntry(name); + }, + + readdir: async function(node) { + FSAFS.debug('readdir', arguments); + let entries = ['.', '..']; + for await (let [name, handle] of node.localReference) { + entries.push(name); + } + return entries; + }, + + symlink: function(parent, newName, oldPath) { + console.log('FSAFS error: symlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + readlink: function(node) { + console.log('FSAFS error: readlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + }, + + /* Operations on file streams (i.e., file handles) */ + + stream_ops: { + open: async function (stream) { + FSAFS.debug('open', arguments); + if (PThreadFS.isDir(stream.node.mode)) { + console.log('FSAFS error: open for directories is not fully implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('EISDIR') }}}); + } + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('FSAFS error: open is only implemented for files') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + if (stream.node.handle) { + stream.handle = stream.node.handle; + ++stream.node.refcount; + } else { + stream.handle = await stream.node.localReference.createSyncAccessHandle(); + stream.node.handle = stream.handle; + stream.node.refcount = 1; + } + }, + + close: async function (stream) { + FSAFS.debug('close', arguments); + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('FSAFS error: close is only implemented for files'); + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + stream.handle = null; + --stream.node.refcount; + if (stream.node.refcount <= 0) { + await stream.node.handle.close(); + stream.node.handle = null; + } + }, + + fsync: async function(stream) { + FSAFS.debug('fsync', arguments); + if (stream.handle == null) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await stream.handle.flush(); + return 0; + }, + + read: async function (stream, buffer, offset, length, position) { + FSAFS.debug('read', arguments); + let data = buffer.subarray(offset, offset+length); + let readBytes = await stream.handle.read(data, {at: position}); + return readBytes; + }, + + write: async function (stream, buffer, offset, length, position) { + FSAFS.debug('write', arguments); + stream.node.timestamp = Date.now(); + let data = buffer.subarray(offset, offset+length); + let writtenBytes = await stream.handle.write(data, {at: position}); + return writtenBytes; + }, + + llseek: async function (stream, offset, whence) { + FSAFS.debug('llseek', arguments); + let position = offset; + if (whence === {{{ cDefine('SEEK_CUR') }}}) { + position += stream.position; + } else if (whence === {{{ cDefine('SEEK_END') }}}) { + if (PThreadFS.isFile(stream.node.mode)) { + position += await stream.handle.getSize(); + } + } + + if (position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return position; + }, + + mmap: function(stream, buffer, offset, length, position, prot, flags) { + FSAFS.debug('mmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + msync: function(stream, buffer, offset, length, mmapFlags) { + FSAFS.debug('msync', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + munmap: function(stream) { + FSAFS.debug('munmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + } + } +}); \ No newline at end of file diff --git a/pthreadfs/src/js/library_memfs_async.js b/pthreadfs/src/js/library_memfs_async.js new file mode 100644 index 000000000000..f55fdc4bd775 --- /dev/null +++ b/pthreadfs/src/js/library_memfs_async.js @@ -0,0 +1,389 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $MEMFS_ASYNC__deps: ['$PThreadFS', '$mmapAlloc'], + $MEMFS_ASYNC: { + ops_table: null, + mount: function(mount) { + return MEMFS_ASYNC.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); + }, + createNode: function(parent, name, mode, dev) { + if (PThreadFS.isBlkdev(mode) || PThreadFS.isFIFO(mode)) { + // no supported + throw new PThreadFS.ErrnoError({{{ cDefine('EPERM') }}}); + } + if (!MEMFS_ASYNC.ops_table) { + MEMFS_ASYNC.ops_table = { + dir: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr, + lookup: MEMFS_ASYNC.node_ops.lookup, + mknod: MEMFS_ASYNC.node_ops.mknod, + rename: MEMFS_ASYNC.node_ops.rename, + unlink: MEMFS_ASYNC.node_ops.unlink, + rmdir: MEMFS_ASYNC.node_ops.rmdir, + readdir: MEMFS_ASYNC.node_ops.readdir, + symlink: MEMFS_ASYNC.node_ops.symlink + }, + stream: { + llseek: MEMFS_ASYNC.stream_ops.llseek + } + }, + file: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr + }, + stream: { + llseek: MEMFS_ASYNC.stream_ops.llseek, + read: MEMFS_ASYNC.stream_ops.read, + write: MEMFS_ASYNC.stream_ops.write, + allocate: MEMFS_ASYNC.stream_ops.allocate, + mmap: MEMFS_ASYNC.stream_ops.mmap, + msync: MEMFS_ASYNC.stream_ops.msync + } + }, + link: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr, + readlink: MEMFS_ASYNC.node_ops.readlink + }, + stream: {} + }, + chrdev: { + node: { + getattr: MEMFS_ASYNC.node_ops.getattr, + setattr: MEMFS_ASYNC.node_ops.setattr + }, + stream: PThreadFS.chrdev_stream_ops + } + }; + } + var node = PThreadFS.createNode(parent, name, mode, dev); + if (PThreadFS.isDir(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.dir.node; + node.stream_ops = MEMFS_ASYNC.ops_table.dir.stream; + node.contents = {}; + } else if (PThreadFS.isFile(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.file.node; + node.stream_ops = MEMFS_ASYNC.ops_table.file.stream; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.length which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; + } else if (PThreadFS.isLink(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.link.node; + node.stream_ops = MEMFS_ASYNC.ops_table.link.stream; + } else if (PThreadFS.isChrdev(node.mode)) { + node.node_ops = MEMFS_ASYNC.ops_table.chrdev.node; + node.stream_ops = MEMFS_ASYNC.ops_table.chrdev.stream; + } + node.timestamp = Date.now(); + // add the new node to the parent + if (parent) { + parent.contents[name] = node; + parent.timestamp = node.timestamp; + } + return node; + }, + + // Given a file node, returns its file data converted to a typed array. + getFileDataAsTypedArray: function(node) { + if (!node.contents) return new Uint8Array(0); + if (node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, + + // Allocates a new backing store for the given node so that it can fit at least newSize amount of bytes. + // May allocate more, to provide automatic geometric increase and amortized linear performance appending writes. + // Never shrinks the storage. + expandFileStorage: function(node, newCapacity) { +#if CAN_ADDRESS_2GB + newCapacity >>>= 0; +#endif + var prevCapacity = node.contents ? node.contents.length : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) >>> 0); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + }, + + // Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated. + resizeFileStorage: function(node, newSize) { +#if CAN_ADDRESS_2GB + newSize >>>= 0; +#endif + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + } else { + var oldContents = node.contents; + node.contents = new Uint8Array(newSize); // Allocate new storage. + if (oldContents) { + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. + } + node.usedBytes = newSize; + } + }, + + node_ops: { + getattr: function(node) { + var attr = {}; + // device numbers reuse inode numbers. + attr.dev = PThreadFS.isChrdev(node.mode) ? node.id : 1; + attr.ino = node.id; + attr.mode = node.mode; + attr.nlink = 1; + attr.uid = 0; + attr.gid = 0; + attr.rdev = node.rdev; + if (PThreadFS.isDir(node.mode)) { + attr.size = 4096; + } else if (PThreadFS.isFile(node.mode)) { + attr.size = node.usedBytes; + } else if (PThreadFS.isLink(node.mode)) { + attr.size = node.link.length; + } else { + attr.size = 0; + } + attr.atime = new Date(node.timestamp); + attr.mtime = new Date(node.timestamp); + attr.ctime = new Date(node.timestamp); + // NOTE: In our implementation, st_blocks = Math.ceil(st_size/st_blksize), + // but this is not required by the standard. + attr.blksize = 4096; + attr.blocks = Math.ceil(attr.size / attr.blksize); + return attr; + }, + setattr: function(node, attr) { + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + MEMFS_ASYNC.resizeFileStorage(node, attr.size); + } + }, + lookup: function(parent, name) { + throw PThreadFS.genericErrors[{{{ cDefine('ENOENT') }}}]; + }, + mknod: function(parent, name, mode, dev) { + return MEMFS_ASYNC.createNode(parent, name, mode, dev); + }, + rename: async function(old_node, new_dir, new_name) { + // if we're overwriting a directory at new_name, make sure it's empty. + if (PThreadFS.isDir(old_node.mode)) { + var new_node; + try { + new_node = await PThreadFS.lookupNode(new_dir, new_name); + } catch (e) { + } + if (new_node) { + for (var i in new_node.contents) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + } + } + // do the internal rewiring + delete old_node.parent.contents[old_node.name]; + old_node.parent.timestamp = Date.now() + old_node.name = new_name; + new_dir.contents[new_name] = old_node; + new_dir.timestamp = old_node.parent.timestamp; + old_node.parent = new_dir; + }, + unlink: function(parent, name) { + delete parent.contents[name]; + parent.timestamp = Date.now(); + }, + rmdir: async function(parent, name) { + var node = await PThreadFS.lookupNode(parent, name); + for (var i in node.contents) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTEMPTY') }}}); + } + delete parent.contents[name]; + parent.timestamp = Date.now(); + }, + readdir: function(node) { + var entries = ['.', '..']; + for (var key in node.contents) { + if (!node.contents.hasOwnProperty(key)) { + continue; + } + entries.push(key); + } + return entries; + }, + symlink: function(parent, newname, oldpath) { + var node = MEMFS_ASYNC.createNode(parent, newname, 511 /* 0777 */ | {{{ cDefine('S_IFLNK') }}}, 0); + node.link = oldpath; + return node; + }, + readlink: function(node) { + if (!PThreadFS.isLink(node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return node.link; + }, + }, + stream_ops: { + read: function(stream, buffer, offset, length, position) { + var contents = stream.node.contents; + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); +#if ASSERTIONS + assert(size >= 0); +#endif + if (size > 8 && contents.subarray) { // non-trivial, and typed array + buffer.set(contents.subarray(position, position + size), offset); + } else { + for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; + } + return size; + }, + + // Writes the byte range (buffer[offset], buffer[offset+length]) to offset 'position' into the file pointed by 'stream' + // canOwn: A boolean that tells if this function can take ownership of the passed in buffer from the subbuffer portion + // that the typed array view 'buffer' points to. The underlying ArrayBuffer can be larger than that, but + // canOwn=true will not take ownership of the portion outside the bytes addressed by the view. This means that + // with canOwn=true, creating a copy of the bytes is avoided, but the caller shouldn't touch the passed in range + // of bytes anymore since their contents now represent file data inside the filesystem. + write: function(stream, buffer, offset, length, position, canOwn) { +#if ASSERTIONS + // The data buffer should be a typed array view + assert(!(buffer instanceof ArrayBuffer)); +#endif +#if ALLOW_MEMORY_GROWTH + // If the buffer is located in main memory (HEAP), and if + // memory can grow, we can't hold on to references of the + // memory buffer, as they may get invalidated. That means we + // need to do copy its contents. + if (buffer.buffer === HEAP8.buffer) { + canOwn = false; + } +#endif // ALLOW_MEMORY_GROWTH + + if (!length) return 0; + var node = stream.node; + node.timestamp = Date.now(); + + if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? + if (canOwn) { +#if ASSERTIONS + assert(position === 0, 'canOwn must imply no weird position inside the file'); +#endif + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0 && position === 0) { // If this is a simple first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = buffer.slice(offset, offset + length); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; + } + } + + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS_ASYNC.expandFileStorage(node, position+length); + if (node.contents.subarray && buffer.subarray) { + // Use typed array write which is available. + node.contents.set(buffer.subarray(offset, offset + length), position); + } else { + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. + } + } + node.usedBytes = Math.max(node.usedBytes, position + length); + return length; + }, + + llseek: function(stream, offset, whence) { + var position = offset; + if (whence === {{{ cDefine('SEEK_CUR') }}}) { + position += stream.position; + } else if (whence === {{{ cDefine('SEEK_END') }}}) { + if (PThreadFS.isFile(stream.node.mode)) { + position += stream.node.usedBytes; + } + } + if (position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + return position; + }, + allocate: function(stream, offset, length) { + MEMFS_ASYNC.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); + }, + mmap: function(stream, address, length, position, prot, flags) { + if (address !== 0) { + // We don't currently support location hints for the address of the mapping + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + if (!PThreadFS.isFile(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + var ptr; + var allocated; + var contents = stream.node.contents; + // Only make a new copy when MAP_PRIVATE is specified. + if (!(flags & {{{ cDefine('MAP_PRIVATE') }}}) && contents.buffer === buffer) { + // We can't emulate MAP_SHARED when the file is not backed by the buffer + // we're mapping to (e.g. the HEAP buffer). + allocated = false; + ptr = contents.byteOffset; + } else { + // Try to avoid unnecessary slices. + if (position > 0 || position + length < contents.length) { + if (contents.subarray) { + contents = contents.subarray(position, position + length); + } else { + contents = Array.prototype.slice.call(contents, position, position + length); + } + } + allocated = true; + ptr = mmapAlloc(length); + if (!ptr) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOMEM') }}}); + } +#if CAN_ADDRESS_2GB + ptr >>>= 0; +#endif + HEAP8.set(contents, ptr); + } + return { ptr: ptr, allocated: allocated }; + }, + msync: function(stream, buffer, offset, length, mmapFlags) { + if (!PThreadFS.isFile(stream.node.mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + if (mmapFlags & {{{ cDefine('MAP_PRIVATE') }}}) { + // MAP_PRIVATE calls need not to be synced back to underlying fs + return 0; + } + + var bytesWritten = MEMFS_ASYNC.stream_ops.write(stream, buffer, 0, length, offset, false); + // should we check if bytesWritten and length are the same? + return 0; + } + } + } +}); diff --git a/pthreadfs/src/js/library_sfafs_async.js b/pthreadfs/src/js/library_sfafs_async.js new file mode 100644 index 000000000000..ab6306efa634 --- /dev/null +++ b/pthreadfs/src/js/library_sfafs_async.js @@ -0,0 +1,424 @@ +/** + * @license + * Copyright 2021 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +mergeInto(LibraryManager.library, { + $SFAFS__deps: ['$PThreadFS'], + $SFAFS: { + + benchmark: function(name, fct) { + if('benchmark_results' in Module) { + let time_pre = performance.now(); + let result = fct(); + let time_needed = performance.now() - time_pre; + + Module.benchmark_results[`${name}_time`] = (Module.benchmark_results[`${name}_time`] || 0) + time_needed; + Module.benchmark_results[`${name}_num`] = (Module.benchmark_results[`${name}_num`] || 0) + 1; + return result; + } + return fct(); + }, + + /* Debugging */ + + debug: function(...args) { + // Uncomment to print debug information. + // + // console.log('SFAFS', arguments); + }, + + /* Helper functions */ + + realPath: function(node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + if (!parts.length) { + return '_'; + } + parts.push(''); + parts.reverse(); + return parts.join('_'); + }, + + encodedPath: function(node) { + return SFAFS.encodePath(SFAFS.realPath(node)); + }, + + joinPaths: function(path1, path2) { + if (path1.endsWith('_')) { + if (path2.startsWith('_')) { + return path1.slice(0, -1) + path2; + } + return path1 + path2; + } else { + if (path2.startsWith('_')) { + return path1 + path2; + } + return path1 + '_' + path2; + } + }, + + // directoryPath ensures path ends with a path delimiter ('_'). + // + // Example: + // * directoryPath('_dir') = '_dir_' + // * directoryPath('_dir_') = '_dir_' + directoryPath: function(path) { + if (path.length && path.slice(-1) == '_') { + return path; + } + return path + '_'; + }, + + // extractFilename strips the parent path and drops suffixes after '_'. + // + // Example: + // * extractFilename('_dir', '_dir_myfile') = 'myfile' + // * extractFilename('_dir', '_dir_mydir_myfile') = 'mydir' + extractFilename: function(parent, path) { + parent = SFAFS.directoryPath(parent); + path = path.substr(parent.length); + var index = path.indexOf('_'); + if (index == -1) { + return path; + } + return path.substr(0, index); + }, + + encodePath: function(path) { + //TODO: this is a random hex encoding decide and document on reasonable + //scheme + var s = unescape(encodeURIComponent(path)) + var h = '' + for (var i = 0; i < s.length; i++) { + h += s.charCodeAt(i).toString(16) + } + return h + }, + + decodePath: function(hex) { + var s = '' + for (var i = 0; i < hex.length; i+=2) { + s += String.fromCharCode(parseInt(hex.substr(i, 2), 16)) + } + return decodeURIComponent(escape(s)) + }, + + + listByPrefix: async function(prefix) { + entries = await storageFoundation.getAll(); + return entries.filter(name => name.startsWith(prefix)) + }, + + // Caches open file handles to simulate opening a file multiple times. + openFileHandles: {}, + + /* Filesystem implementation (public interface) */ + + createNode: function (parent, name, mode, dev) { + SFAFS.debug('createNode', arguments); + if (!PThreadFS.isDir(mode) && !PThreadFS.isFile(mode)) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = SFAFS.node_ops; + node.stream_ops = SFAFS.stream_ops; + if (PThreadFS.isDir(mode)) { + node.contents = {}; + } + node.timestamp = Date.now(); + return node; + }, + + mount: function (mount) { + SFAFS.debug('mount', arguments); + return SFAFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); + }, + + cwd: function() { return process.cwd(); }, + + chdir: function() { process.chdir.apply(void 0, arguments); }, + + allocate: function() { + SFAFS.debug('allocate', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + ioctl: function() { + SFAFS.debug('ioctl', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('ENOTTY') }}}); + }, + + /* Operations on the nodes of the filesystem tree */ + + node_ops: { + getattr: async function(node) { + SFAFS.debug('getattr', arguments); + let length; + if (node.handle) { + length = await node.handle.getLength(); + } + else { + // TODO: this double caching of opened files is probably redundant. + // Clean up after publishing a clean design for the PThreadFS. + var path = SFAFS.realPath(node); + if(path in SFAFS.openFileHandles) { + let fileHandle = SFAFS.openFileHandles[path] + length = await fileHandle.getLength(); + } + else { + let fileHandle = await storageFoundation.open(SFAFS.encodePath(path)); + length = await fileHandle.getLength(); + await fileHandle.close(); + } + } + + let modificationTime = new Date(node.timestamp); + return { + dev: null, + ino: null, + mode: node.mode, + nlink: 1, + uid: 0, + gid: 0, + rdev: null, + size: length, + atime: modificationTime, + mtime: modificationTime, + ctime: modificationTime, + blksize: 4096, + blocks: Math.ceil(length / 4096), + }; + }, + + setattr: async function(node, attr) { + SFAFS.debug('setattr', arguments); + if (attr.mode !== undefined) { + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + node.timestamp = attr.timestamp; + } + if (attr.size !== undefined) { + let useOpen = false; + let fileHandle = node.handle; + try { + if (!fileHandle) { + // Open a handle that is closed later. + useOpen = true; + fileHandle = await storageFoundation.open(SFAFS.encodedPath(node)); + } + await fileHandle.setLength(attr.size); + + } catch (e) { + if (!('code' in e)) throw e; + throw new PThreadFS.ErrnoError(-e.errno); + } finally { + if (useOpen) { + await fileHandle.close(); + } + } + } + }, + + lookup: async function (parent, name) { + SFAFS.debug('lookup', arguments); + var parentPath = SFAFS.directoryPath(SFAFS.realPath(parent)); + + var children = await SFAFS.listByPrefix(parentPath); + + var exists = false; + var mode = 511 /* 0777 */ + for (var i = 0; i < children.length; ++i) { + var path = children[i].substr(parentPath.length); + if (path == name) { + exists = true; + mode |= {{{ cDefine('S_IFREG') }}}; + break; + } + + subdirName = SFAFS.directoryPath(name); + if (path.startsWith(subdirName)) { + exists = true; + mode |= {{{ cDefine('S_IFDIR') }}}; + break; + } + } + + if (!exists) { + throw PThreadFS.genericErrors[{{{ cDefine('ENOENT') }}}]; + } + + var node = PThreadFS.createNode(parent, name, mode); + node.node_ops = SFAFS.node_ops; + node.stream_ops = SFAFS.stream_ops; + return node; + }, + + mknod: function (parent, name, mode, dev) { + SFAFS.debug('mknod', arguments); + var node = SFAFS.createNode(parent, name, mode, dev); + if (!PThreadFS.isFile) { + console.log('SFAFS error: mknod is only implemented for files') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + node.handle = null; + node.refcount = 0; + return node; + }, + + rename: function (oldNode, newParentNode, newName) { + // TODO(rstz): Use storageFoundation.rename() to implement this. + SFAFS.debug('rename', arguments); + console.log('SFAFS error: rename is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + unlink: async function(parent, name) { + SFAFS.debug('unlink', arguments); + var path = SFAFS.joinPaths(SFAFS.realPath(parent), name); + return await storageFoundation.delete(SFAFS.encodePath(path)); + }, + + rmdir: function(parent, name) { + SFAFS.debug('rmdir', arguments); + console.log('SFAFS error: rmdir is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + readdir: async function(node) { + SFAFS.debug('readdir', arguments); + var parentPath = SFAFS.realPath(node); + var children = await SFAFS.listByPrefix(SFAFS.encodePath(parentPath)); + children = children.map(child => SFAFS.extractFilename(parentPath, child)); + // Remove duplicates. + return Array.from(new Set(children)); + }, + + symlink: function(parent, newName, oldPath) { + console.log('SFAFS error: symlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + + readlink: function(node) { + console.log('SFAFS error: readlink is not implemented') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + }, + }, + + /* Operations on file streams (i.e., file handles) */ + + stream_ops: { + open: async function (stream) { + SFAFS.debug('open', arguments); + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('SFAFS error: open is only implemented for files') + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + if (stream.node.handle) { + //TODO: check when this code path is actually executed, it seems to + //duplicate some of the caching behavior below. + stream.handle = stream.node.handle; + ++stream.node.refcount; + } else { + var path = SFAFS.realPath(stream.node); + + // Open existing file. + if(!(path in SFAFS.openFileHandles)) { + SFAFS.openFileHandles[path] = await storageFoundation.open(SFAFS.encodePath(path)); + } + stream.handle = SFAFS.openFileHandles[path]; + stream.node.handle = stream.handle; + stream.node.refcount = 1; + } + SFAFS.debug('end open'); + }, + + close: async function (stream) { + SFAFS.debug('close', arguments); + if (!PThreadFS.isFile(stream.node.mode)) { + console.log('SFAFS error: close is only implemented for files'); + throw new PThreadFS.ErrnoError({{{ cDefine('ENOSYS') }}}); + } + + stream.handle = null; + --stream.node.refcount; + if (stream.node.refcount <= 0) { + await stream.node.handle.close(); + stream.node.handle = null; + delete SFAFS.openFileHandles[SFAFS.realPath(stream.node)]; + } + SFAFS.debug('end close'); + }, + + fsync: async function(stream) { + SFAFS.debug('fsync', arguments); + if (stream.handle == null) { + throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + } + await stream.handle.flush(); + SFAFS.debug('end fsync'); + return 0; + }, + + read: async function (stream, buffer, offset, length, position) { + SFAFS.debug('read', arguments); + let data = new Uint8Array(length); + let result = await stream.handle.read(data, position); + buffer.set(result.buffer, offset); + SFAFS.debug('end read'); + return result.readBytes; + }, + + write: async function (stream, buffer, offset, length, position) { + SFAFS.debug('write', arguments); + stream.node.timestamp = Date.now(); + let data = new Uint8Array(buffer.slice(offset, offset+length)); + let result = await stream.handle.write(data, position); + SFAFS.debug('end write'); + return result.writtenBytes; + }, + + llseek: async function (stream, offset, whence) { + SFAFS.debug('llseek', arguments); + var position = offset; + if (whence === 1) { // SEEK_CUR. + position += stream.position; + } else if (whence === 2) { // SEEK_END. + position += await stream.handle.getLength(); + } else if (whence !== 0) { // SEEK_SET. + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + + if (position < 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EINVAL') }}}); + } + stream.position = position; + SFAFS.debug('end llseek'); + return position; + }, + + mmap: function(stream, buffer, offset, length, position, prot, flags) { + SFAFS.debug('mmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + msync: function(stream, buffer, offset, length, mmapFlags) { + SFAFS.debug('msync', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + + munmap: function(stream) { + SFAFS.debug('munmap', arguments); + throw new PThreadFS.ErrnoError({{{ cDefine('EOPNOTSUPP') }}}); + }, + } + } +}); \ No newline at end of file diff --git a/pthreadfs/src/js/library_syscall_async.js b/pthreadfs/src/js/library_syscall_async.js new file mode 100644 index 000000000000..a3185572efb1 --- /dev/null +++ b/pthreadfs/src/js/library_syscall_async.js @@ -0,0 +1,935 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +/* +List of implemented syscalls: + {'name': 'open', 'args': ['path', 'flags', 'varargs']}, + {'name': 'unlink', 'args': ['path']}, + {'name': 'chdir', 'args': ['path']}, + {'name': 'mknod', 'args': ['path', 'mode', 'dev']}, + {'name': 'chmod', 'args': ['path', 'mode']}, + {'name': 'access', 'args': ['path', 'amode']}, + {'name': 'rename', 'args': ['old_path', 'new_path']}, + {'name': 'mkdir', 'args': ['path', 'mode']}, + {'name': 'rmdir', 'args': ['path']}, + {'name': 'dup', 'args': ['fd']}, + {'name': 'ioctl', 'args': ['fd', 'request', 'varargs']}, + {'name': 'dup2', 'args': ['oldfd', 'newfd']}, + {'name': 'symlink', 'args': ['target', 'linkpath']}, + {'name': 'readlink', 'args': ['path', 'buf', 'bufsize']}, + {'name': 'munmap', 'args': ['addr', 'len']}, + {'name': 'fchmod', 'args': ['fd', 'mode']}, + {'name': 'fchdir', 'args': ['fd']}, + {'name': '_newselect', 'args': ['nfds', 'readfds', 'writefds', 'exceptfds', 'timeout']}, + {'name': 'msync', 'args': ['addr', 'len', 'flags']}, + {'name': 'fdatasync', 'args': ['fd']}, + {'name': 'poll', 'args': ['fds', 'nfds', 'timeout']}, + {'name': 'getcwd', 'args': ['buf', 'size']}, + {'name': 'mmap2', 'args': ['addr', 'len', 'prot', 'flags', 'fd', 'off']}, + {'name': 'truncate64', 'args': ['path', 'zero', 'low', 'high']}, + {'name': 'ftruncate64', 'args': ['fd', 'zero', 'low', 'high']}, + {'name': 'stat64', 'args': ['path', 'buf']}, + {'name': 'lstat64', 'args': ['path', 'buf']}, + {'name': 'fstat64', 'args': ['fd', 'buf']}, + {'name': 'lchown32', 'args': ['path', 'owner', 'group']}, + {'name': 'fchown32', 'args': ['fd', 'owner', 'group']}, + {'name': 'chown32', 'args': ['path', 'owner', 'group']}, + {'name': 'getdents64', 'args': ['fd', 'dirp', 'count']}, + {'name': 'fcntl64', 'args': ['fd', 'cmd', 'varargs']}, + {'name': 'statfs64', 'args': ['path', 'size', 'buf']}, + {'name': 'fstatfs64', 'args': ['fd', 'size', 'buf']}, + {'name': 'openat', 'args': ['dirfd', 'path', 'flags', 'varargs']}, + {'name': 'mkdirat', 'args': ['dirfd', 'path', 'mode']}, + {'name': 'mknodat', 'args': ['dirfd', 'path', 'mode', 'dev']}, + {'name': 'fchownat', 'args': ['dirfd', 'path', 'owner', 'group', 'flags']}, + {'name': 'fstatat64', 'args': ['dirfd', 'path', 'buf', 'flags']}, + {'name': 'unlinkat', 'args': ['dirfd', 'path', 'flags']}, + {'name': 'renameat', 'args': ['olddirfd', 'oldpath', 'newdirfd', 'newpath']}, + {'name': 'symlinkat', 'args': ['target', 'newdirfd', 'linkpath']}, + {'name': 'readlinkat', 'args': ['dirfd', 'path', 'bug', 'bufsize']}, + {'name': 'fchmodat', 'args': ['dirfd', 'path', 'mode', 'varargs']}, + {'name': 'faccessat', 'args': ['dirfd', 'path', 'amode', 'flags']}, + {'name': 'utimensat', 'args': ['dirfd', 'path', 'times', 'flags']}, + {'name': 'fallocate', 'args': ['fd', 'mode', 'off_low', 'off_high', 'len_low', 'len_high']}, + {'name': 'dup3', 'args': ['fd', 'suggestfd', 'flags']}, +*/ + +var SyscallsLibrary = { + $ASYNCSYSCALLS__deps: ['$PThreadFS'], + $ASYNCSYSCALLS: { + mappings: {}, + // global constants + DEFAULT_POLLMASK: {{{ cDefine('POLLIN') }}} | {{{ cDefine('POLLOUT') }}}, + + // global state + umask: 0x1FF, // S_IRWXU | S_IRWXG | S_IRWXO + + // shared utilities + calculateAt: async function(dirfd, path, allowEmpty) { + if (path[0] === '/') { + return path; + } + // relative path + var dir; + if (dirfd === {{{ cDefine('AT_FDCWD') }}}) { + dir = await PThreadFS.cwd(); + } else { + var dirstream = await PThreadFS.getStream(dirfd); + if (!dirstream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + dir = dirstream.path; + } + if (path.length == 0) { + if (!allowEmpty) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENOENT') }}});; + } + return dir; + } + return PATH.join2(dir, path); + }, + + doStat: async function(func, path, buf) { + try { + var stat = await func(path); + } catch (e) { + if (e && e.node && PATH.normalize(path) !== PATH.normalize(PThreadFS.getPath(e.node))) { + // an error occurred while trying to look up the path; we should just report ENOTDIR + return -{{{ cDefine('ENOTDIR') }}}; + } + throw e; + } + {{{ makeSetValue('buf', C_STRUCTS.stat.st_dev, 'stat.dev', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_dev_padding, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_ino_truncated, 'stat.ino', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mode, 'stat.mode', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_nlink, 'stat.nlink', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_uid, 'stat.uid', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_gid, 'stat.gid', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_rdev, 'stat.rdev', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.__st_rdev_padding, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_size, 'stat.size', 'i64') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_blksize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_blocks, 'stat.blocks', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_sec, '(stat.atime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_sec, '(stat.mtime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_sec, '(stat.ctime.getTime() / 1000)|0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '0', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}}; + return 0; + }, + doMsync: async function(addr, stream, len, flags, offset) { + var buffer = HEAPU8.slice(addr, addr + len); + await PThreadFS.msync(stream, buffer, offset, len, flags); + }, + doMkdir: async function(path, mode) { + // remove a trailing slash, if one - /a/b/ has basename of '', but + // we want to create b in the context of this function + path = PATH.normalize(path); + if (path[path.length-1] === '/') path = path.substr(0, path.length-1); + await PThreadFS.mkdir(path, mode, 0); + return 0; + }, + doMknod: async function(path, mode, dev) { + // we don't want this in the JS API as it uses mknod to create all nodes. + switch (mode & {{{ cDefine('S_IFMT') }}}) { + case {{{ cDefine('S_IFREG') }}}: + case {{{ cDefine('S_IFCHR') }}}: + case {{{ cDefine('S_IFBLK') }}}: + case {{{ cDefine('S_IFIFO') }}}: + case {{{ cDefine('S_IFSOCK') }}}: + break; + default: return -{{{ cDefine('EINVAL') }}}; + } + await PThreadFS.mknod(path, mode, dev); + return 0; + }, + doReadlink: async function(path, buf, bufsize) { + if (bufsize <= 0) return -{{{ cDefine('EINVAL') }}}; + var ret = await PThreadFS.readlink(path); + + var len = Math.min(bufsize, lengthBytesUTF8(ret)); + var endChar = HEAP8[buf+len]; + stringToUTF8(ret, buf, bufsize+1); + // readlink is one of the rare functions that write out a C string, but does never append a null to the output buffer(!) + // stringToUTF8() always appends a null byte, so restore the character under the null byte after the write. + HEAP8[buf+len] = endChar; + + return len; + }, + doAccess: async function(path, amode) { + if (amode & ~{{{ cDefine('S_IRWXO') }}}) { + // need a valid mode + return -{{{ cDefine('EINVAL') }}}; + } + var node; + var lookup = await PThreadFS.lookupPath(path, { follow: true }); + node = lookup.node; + if (!node) { + return -{{{ cDefine('ENOENT') }}}; + } + var perms = ''; + if (amode & {{{ cDefine('R_OK') }}}) perms += 'r'; + if (amode & {{{ cDefine('W_OK') }}}) perms += 'w'; + if (amode & {{{ cDefine('X_OK') }}}) perms += 'x'; + let nodepermissions = await PThreadFS.nodePermissions(node, perms); + if (perms /* otherwise, they've just passed F_OK */ && nodepermissions) { + return -{{{ cDefine('EACCES') }}}; + } + return 0; + }, + doDup: async function(path, flags, suggestFD) { + var suggest = await PThreadFS.getStream(suggestFD); + if (suggest) await PThreadFS.close(suggest); + let stream = await PThreadFS.open(path, flags, 0, suggestFD, suggestFD); + return stream.fd; + }, + doReadv: async function(stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = {{{ makeGetValue('iov', 'i*8', 'i32') }}}; + var len = {{{ makeGetValue('iov', 'i*8 + 4', 'i32') }}}; + var curr = await PThreadFS.read(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset); + if (curr < 0) return -1; + ret += curr; + if (curr < len) break; // nothing more to read + } + return ret; + }, + doWritev: async function(stream, iov, iovcnt, offset) { + var ret = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = {{{ makeGetValue('iov', 'i*8', 'i32') }}}; + var len = {{{ makeGetValue('iov', 'i*8 + 4', 'i32') }}}; + var curr = await PThreadFS.write(stream, {{{ heapAndOffset('HEAP8', 'ptr') }}}, len, offset); + if (curr < 0) return -1; + ret += curr; + } + return ret; + }, + + // arguments handling + + varargs: undefined, + + get: function() { +#if ASSERTIONS + assert(ASYNCSYSCALLS.varargs != undefined); +#endif + ASYNCSYSCALLS.varargs += 4; + var ret = {{{ makeGetValue('ASYNCSYSCALLS.varargs', '-4', 'i32') }}}; +#if SYSCALL_DEBUG + err(' (raw: "' + ret + '")'); +#endif + return ret; + }, + getStr: function(ptr) { + var ret = UTF8ToString(ptr); +#if SYSCALL_DEBUG + err(' (str: "' + ret + '")'); +#endif + return ret; + }, + getStreamFromFD: async function(fd) { + var stream = await PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); +#if SYSCALL_DEBUG + err(' (stream: "' + stream.path + '")'); +#endif + return stream; + }, + get64: function(low, high) { +#if ASSERTIONS + if (low >= 0) assert(high === 0); + else assert(high === -1); +#endif + return low; + }, + + $syscallMmap2_async__deps: ['$ASYNCSYSCALLS', '$zeroMemory', '$mmapAlloc', '$PThreadFS', + ], + $syscallMmap2_async: async function(addr, len, prot, flags, fd, off) { + off <<= 12; // undo pgoffset + var ptr; + var allocated = false; + + // addr argument must be page aligned if MAP_FIXED flag is set. + if ((flags & {{{ cDefine('MAP_FIXED') }}}) !== 0 && (addr % {{{ WASM_PAGE_SIZE }}}) !== 0) { + return -{{{ cDefine('EINVAL') }}}; + } + + // MAP_ANONYMOUS (aka MAP_ANON) isn't actually defined by POSIX spec, + // but it is widely used way to allocate memory pages on Linux, BSD and Mac. + // In this case fd argument is ignored. + if ((flags & {{{ cDefine('MAP_ANONYMOUS') }}}) !== 0) { + ptr = mmapAlloc(len); + if (!ptr) return -{{{ cDefine('ENOMEM') }}}; + allocated = true; + } else { + var info = await PThreadFS.getStream(fd); + if (!info) return -{{{ cDefine('EBADF') }}}; + var res = await PThreadFS.mmap(info, addr, len, off, prot, flags); + ptr = res.ptr; + allocated = res.allocated; + } + #if CAN_ADDRESS_2GB + ptr >>>= 0; + #endif + ASYNCSYSCALLS.mappings[ptr] = { malloc: ptr, len: len, allocated: allocated, fd: fd, prot: prot, flags: flags, offset: off }; + return ptr; + }, + }, + + $syscallMunmap_async__deps: ['$ASYNCSYSCALLS', '$PThreadFS'], + $syscallMunmap_async: async function(addr, len) { + #if CAN_ADDRESS_2GB + addr >>>= 0; + #endif + // TODO: support unmmap'ing parts of allocations + var info = ASYNCSYSCALLS.mappings[addr]; + if (len === 0 || !info) { + return -{{{ cDefine('EINVAL') }}}; + } + if (len === info.len) { + var stream = await PThreadFS.getStream(info.fd); + if (stream) { + if (info.prot & {{{ cDefine('PROT_WRITE') }}}) { + await ASYNCSYSCALLS.doMsync(addr, stream, len, info.flags, info.offset); + } + await PThreadFS.munmap(stream); + } + ASYNCSYSCALLS.mappings[addr] = null; + if (info.allocated) { + _free(info.malloc); + } + } + return 0; + }, + + // WASI + fd_write_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var num = await ASYNCSYSCALLS.doWritev(stream, iov, iovcnt); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_read_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var num = await ASYNCSYSCALLS.doReadv(stream, iov, iovcnt); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_close_async: async function(fd, iov, iovcnt, pnum) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + await PThreadFS.close(stream); + return 0; + }, + fd_pwrite_async: async function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var num = await ASYNCSYSCALLS.doWritev(stream, iov, iovcnt, offset_low); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_pread_async: async function(fd, iov, iovcnt, {{{ defineI64Param('offset') }}}, pnum) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var num = await ASYNCSYSCALLS.doReadv(stream, iov, iovcnt, offset_low); + {{{ makeSetValue('pnum', 0, 'num', 'i32') }}} + return 0; + }, + fd_seek_async: async function(fd, {{{ defineI64Param('offset') }}}, whence, newOffset) { + {{{ receiveI64ParamAsI32s('offset') }}} + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + var HIGH_OFFSET = 0x100000000; // 2^32 + // use an unsigned operator on low and shift high by 32-bits + var offset = offset_high * HIGH_OFFSET + (offset_low >>> 0); + + var DOUBLE_LIMIT = 0x20000000000000; // 2^53 + // we also check for equality since DOUBLE_LIMIT + 1 == DOUBLE_LIMIT + if (offset <= -DOUBLE_LIMIT || offset >= DOUBLE_LIMIT) { + return -{{{ cDefine('EOVERFLOW') }}}; + } + + await PThreadFS.llseek(stream, offset, whence); + {{{ makeSetValue('newOffset', '0', 'stream.position', 'i64') }}}; + if (stream.getdents && offset === 0 && whence === {{{ cDefine('SEEK_SET') }}}) stream.getdents = null; // reset readdir state + return 0; + }, + fd_fdstat_get_async: async function(fd, pbuf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + // All character devices are terminals (other things a Linux system would + // assume is a character device, like the mouse, we have special APIs for). + var type = stream.tty ? {{{ cDefine('__WASI_FILETYPE_CHARACTER_DEVICE') }}} : + PThreadFS.isDir(stream.mode) ? {{{ cDefine('__WASI_FILETYPE_DIRECTORY') }}} : + PThreadFS.isLink(stream.mode) ? {{{ cDefine('__WASI_FILETYPE_SYMBOLIC_LINK') }}} : + {{{ cDefine('__WASI_FILETYPE_REGULAR_FILE') }}}; + {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_filetype, 'type', 'i8') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_flags, '?', 'i16') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_base, '?', 'i64') }}}; + // TODO {{{ makeSetValue('pbuf', C_STRUCTS.__wasi_fdstat_t.fs_rights_inheriting, '?', 'i64') }}}; + return 0; + }, + fd_sync_async: async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + if (stream.stream_ops && stream.stream_ops.fsync) { + let res = await stream.stream_ops.fsync(stream); + return await -res; + } + return 0; // TODO(rstz): Consider adding functionality here! + }, + + // Syscalls + open_async: async function(path, flags, mode) { + var pathname = ASYNCSYSCALLS.getStr(path); + var stream = await PThreadFS.open(pathname, flags, mode); + return stream.fd; + }, + unlink_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.unlink(path); + return 0; + }, + chdir_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chdir(path); + return 0; + }, + mknod_async : async function(path, mode, dev) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doMknod(path, mode, dev); + }, + chmod_async : async function(path, mode) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chmod(path, mode); + return 0; + }, + access_async : async function(path, amode) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doAccess(path, amode); + }, + rename_async : async function(old_path, new_path) { + old_path = ASYNCSYSCALLS.getStr(old_path); + new_path = ASYNCSYSCALLS.getStr(new_path); + await PThreadFS.rename(old_path, new_path); + return 0; + }, + mkdir_async : async function(path, mode) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doMkdir(path, mode); + }, + rmdir_async : async function(path) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.rmdir(path); + return 0; + }, + dup_async : async function(fd) { + let old = await ASYNCSYSCALLS.getStreamFromFD(fd); + let stream = await PThreadFS.open(old.path, old.flags, 0); + return stream.fd; + }, + ioctl_async: async function(fd, op, varargs) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + switch (op) { + case {{{ cDefine('TCGETA') }}}: + case {{{ cDefine('TCGETS') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + case {{{ cDefine('TCSETA') }}}: + case {{{ cDefine('TCSETAW') }}}: + case {{{ cDefine('TCSETAF') }}}: + case {{{ cDefine('TCSETS') }}}: + case {{{ cDefine('TCSETSW') }}}: + case {{{ cDefine('TCSETSF') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; // no-op, not actually adjusting terminal settings + } + case {{{ cDefine('TIOCGPGRP') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + var argp = ASYNCSYSCALLS.get(); + {{{ makeSetValue('argp', 0, 0, 'i32') }}}; + return 0; + } + case {{{ cDefine('TIOCSPGRP') }}}: { + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return -{{{ cDefine('EINVAL') }}}; // not supported + } + case {{{ cDefine('FIONREAD') }}}: { + var argp = ASYNCSYSCALLS.get(); + return await PThreadFS.ioctl(stream, op, argp); + } + case {{{ cDefine('TIOCGWINSZ') }}}: { + // TODO: in theory we should write to the winsize struct that gets + // passed in, but for now musl doesn't read anything on it + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + case {{{ cDefine('TIOCSWINSZ') }}}: { + // TODO: technically, this ioctl call should change the window size. + // but, since emscripten doesn't have any concept of a terminal window + // yet, we'll just silently throw it away as we do TIOCGWINSZ + if (!stream.tty) return -{{{ cDefine('ENOTTY') }}}; + return 0; + } + default: abort('bad ioctl syscall ' + op); + } + }, + dup2_async : async function(oldfd, suggestFD) { + var old = await ASYNCSYSCALLS.getStreamFromFD(oldfd); + if (old.fd === suggestFD) return suggestFD; + return await ASYNCSYSCALLS.doDup(old.path, old.flags, suggestFD); + }, + symlink_async : async function(target, linkpath) { + target = ASYNCSYSCALLS.getStr(target); + linkpath = ASYNCSYSCALLS.getStr(linkpath); + await PThreadFS.symlink(target, linkpath); + return 0; + }, + readlink_async : async function(path, buf, bufsize) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doReadlink(path, buf, bufsize); + }, + munmap_async__deps: ['$syscallMunmap_async'], + munmap_async : async function(addr, len) { + return await syscallMunmap_async(addr, len); + }, + fchmod_async : async function(fd, mode) { + await PThreadFS.fchmod(fd, mode); + return 0; + }, + fchdir_async : async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + await PThreadFS.chdir(stream.path); + return 0; + }, + _newselect_async : async function(nfds, readfds, writefds, exceptfds, timeout) { + // readfds are supported, + // writefds checks socket open status + // exceptfds not supported + // timeout is always 0 - fully async + #if ASSERTIONS + assert(nfds <= 64, 'nfds must be less than or equal to 64'); // fd sets have 64 bits // TODO: this could be 1024 based on current musl headers + assert(!exceptfds, 'exceptfds not supported'); + #endif + + var total = 0; + + var srcReadLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0), + srcReadHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0); + var srcWriteLow = (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0), + srcWriteHigh = (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0); + var srcExceptLow = (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0), + srcExceptHigh = (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var dstReadLow = 0, + dstReadHigh = 0; + var dstWriteLow = 0, + dstWriteHigh = 0; + var dstExceptLow = 0, + dstExceptHigh = 0; + + var allLow = (readfds ? {{{ makeGetValue('readfds', 0, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 0, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 0, 'i32') }}} : 0); + var allHigh = (readfds ? {{{ makeGetValue('readfds', 4, 'i32') }}} : 0) | + (writefds ? {{{ makeGetValue('writefds', 4, 'i32') }}} : 0) | + (exceptfds ? {{{ makeGetValue('exceptfds', 4, 'i32') }}} : 0); + + var check = function(fd, low, high, val) { + return (fd < 32 ? (low & val) : (high & val)); + }; + + for (var fd = 0; fd < nfds; fd++) { + var mask = 1 << (fd % 32); + if (!(check(fd, allLow, allHigh, mask))) { + continue; // index isn't in the set + } + + var stream = await PThreadFS.getStream(fd); + if (!stream) throw new PThreadFS.ErrnoError({{{ cDefine('EBADF') }}}); + + var flags = ASYNCSYSCALLS.DEFAULT_POLLMASK; + + if (stream.stream_ops.poll) { + flags = await stream.stream_ops.poll(stream); + } + + if ((flags & {{{ cDefine('POLLIN') }}}) && check(fd, srcReadLow, srcReadHigh, mask)) { + fd < 32 ? (dstReadLow = dstReadLow | mask) : (dstReadHigh = dstReadHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLOUT') }}}) && check(fd, srcWriteLow, srcWriteHigh, mask)) { + fd < 32 ? (dstWriteLow = dstWriteLow | mask) : (dstWriteHigh = dstWriteHigh | mask); + total++; + } + if ((flags & {{{ cDefine('POLLPRI') }}}) && check(fd, srcExceptLow, srcExceptHigh, mask)) { + fd < 32 ? (dstExceptLow = dstExceptLow | mask) : (dstExceptHigh = dstExceptHigh | mask); + total++; + } + } + + if (readfds) { + {{{ makeSetValue('readfds', '0', 'dstReadLow', 'i32') }}}; + {{{ makeSetValue('readfds', '4', 'dstReadHigh', 'i32') }}}; + } + if (writefds) { + {{{ makeSetValue('writefds', '0', 'dstWriteLow', 'i32') }}}; + {{{ makeSetValue('writefds', '4', 'dstWriteHigh', 'i32') }}}; + } + if (exceptfds) { + {{{ makeSetValue('exceptfds', '0', 'dstExceptLow', 'i32') }}}; + {{{ makeSetValue('exceptfds', '4', 'dstExceptHigh', 'i32') }}}; + } + + return total; + }, + msync_async: async function(addr, len, flags) { + #if CAN_ADDRESS_2GB + addr >>>= 0; + #endif + var info = ASYNCSYSCALLS.mappings[addr]; + if (!info) return 0; + await ASYNCSYSCALLS.doMsync(addr, await PThreadFS.getStream(info.fd), len, info.flags, 0); + return 0; + }, + fdatasync_async : async function(fd) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + //TODO(rstz): Consider implementing this, since Storage Foundation supports flush(). + return 0; + }, + poll_async : async function(fds, nfds, timeout) { + var nonzero = 0; + for (var i = 0; i < nfds; i++) { + var pollfd = fds + {{{ C_STRUCTS.pollfd.__size__ }}} * i; + var fd = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}}; + var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}}; + var mask = {{{ cDefine('POLLNVAL') }}}; + var stream = await PThreadFS.getStream(fd); + if (stream) { + mask = ASYNCSYSCALLS.DEFAULT_POLLMASK; + if (stream.stream_ops.poll) { + mask = stream.stream_ops.poll(stream); + } + } + mask &= events | {{{ cDefine('POLLERR') }}} | {{{ cDefine('POLLHUP') }}}; + if (mask) nonzero++; + {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'mask', 'i16') }}}; + } + return nonzero; + }, + getcwd_async : async function(buf, size) { + if (size === 0) return -{{{ cDefine('EINVAL') }}}; + var cwd = await PThreadFS.cwd(); + var cwdLengthInBytes = lengthBytesUTF8(cwd); + if (size < cwdLengthInBytes + 1) return -{{{ cDefine('ERANGE') }}}; + stringToUTF8(cwd, buf, size); + return buf; + }, + mmap2_async__deps: ['$syscallMmap2'], + mmap2_async: async function(addr, len, prot, flags, fd, off) { + return await syscallMmap2(addr, len, prot, flags, fd, off); + }, + truncate64_async: async function(path, zero, low, high) { + path = ASYNCSYSCALLS.getStr(path); + var length = ASYNCSYSCALLS.get64(low, high); + await PThreadFS.truncate(path, length); + return 0; + }, + ftruncate64_async : async function(fd, zero, low, high) { + var length = ASYNCSYSCALLS.get64(low, high); + await PThreadFS.ftruncate(fd, length); + return 0; + }, + stat64_async : async function(path, buf) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doStat(PThreadFS.stat, path, buf); + }, + lstat64_async : async function(path, buf) { + path = ASYNCSYSCALLS.getStr(path); + return await ASYNCSYSCALLS.doStat(PThreadFS.lstat, path, buf); + }, + fstat64_async : async function(fd, buf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + return await ASYNCSYSCALLS.doStat(PThreadFS.stat, stream.path, buf); + }, + lchown32_async : async function(path, owner, group) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chown(path, owner, group); // XXX we ignore the 'l' aspect, and do the same as chown + return 0; + }, + fchown32_async: async function(fd, owner, group) { + await PThreadFS.fchown(fd, owner, group); + return 0; + }, + chown32_async: async function(path, owner, group) { + path = ASYNCSYSCALLS.getStr(path); + await PThreadFS.chown(path, owner, group); + return 0; + }, + getdents64_async: async function(fd, dirp, count) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + if (!stream.getdents) { + stream.getdents = await PThreadFS.readdir(stream.path); + } + + var struct_size = {{{ C_STRUCTS.dirent.__size__ }}}; + var pos = 0; + var off = await PThreadFS.llseek(stream, 0, {{{ cDefine('SEEK_CUR') }}}); + + var idx = Math.floor(off / struct_size); + + while (idx < stream.getdents.length && pos + struct_size <= count) { + var id; + var type; + var name = stream.getdents[idx]; + if (name[0] === '.') { + id = 1; + type = 4; // DT_DIR + } else { + var child = await PThreadFS.lookupNode(stream.node, name); + id = child.id; + type = PThreadFS.isChrdev(child.mode) ? 2 : // DT_CHR, character device. + PThreadFS.isDir(child.mode) ? 4 : // DT_DIR, directory. + PThreadFS.isLink(child.mode) ? 10 : // DT_LNK, symbolic link. + 8; // DT_REG, regular file. + } + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_ino, 'id', 'i64') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_off, '(idx + 1) * struct_size', 'i64') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_reclen, C_STRUCTS.dirent.__size__, 'i16') }}}; + {{{ makeSetValue('dirp + pos', C_STRUCTS.dirent.d_type, 'type', 'i8') }}}; + stringToUTF8(name, dirp + pos + {{{ C_STRUCTS.dirent.d_name }}}, 256); + pos += struct_size; + idx += 1; + } + await PThreadFS.llseek(stream, idx * struct_size, {{{ cDefine('SEEK_SET') }}}); + return pos; + }, + fcntl64_async__deps: ['$setErrNo'], + fcntl64_async: async function(fd, cmd, varargs) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + switch (cmd) { + case {{{ cDefine('F_DUPFD') }}}: { + var arg = ASYNCSYSCALLS.get(); + if (arg < 0) { + return -{{{ cDefine('EINVAL') }}}; + } + var newStream; + newStream = await PThreadFS.open(stream.path, stream.flags, 0, arg); + return newStream.fd; + } + case {{{ cDefine('F_GETFD') }}}: + case {{{ cDefine('F_SETFD') }}}: + return 0; // FD_CLOEXEC makes no sense for a single process. + case {{{ cDefine('F_GETFL') }}}: + return stream.flags; + case {{{ cDefine('F_SETFL') }}}: { + var arg = ASYNCSYSCALLS.get(); + stream.flags |= arg; + return 0; + } + case {{{ cDefine('F_GETLK') }}}: + /* case {{{ cDefine('F_GETLK64') }}}: Currently in musl F_GETLK64 has same value as F_GETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ { + {{{ assert(cDefine('F_GETLK') === cDefine('F_GETLK64')), '' }}} + var arg = ASYNCSYSCALLS.get(); + var offset = {{{ C_STRUCTS.flock.l_type }}}; + // We're always unlocked. + {{{ makeSetValue('arg', 'offset', cDefine('F_UNLCK'), 'i16') }}}; + return 0; + } + case {{{ cDefine('F_SETLK') }}}: + case {{{ cDefine('F_SETLKW') }}}: + /* case {{{ cDefine('F_SETLK64') }}}: Currently in musl F_SETLK64 has same value as F_SETLK, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ + /* case {{{ cDefine('F_SETLKW64') }}}: Currently in musl F_SETLKW64 has same value as F_SETLKW, so omitted to avoid duplicate case blocks. If that changes, uncomment this */ + {{{ assert(cDefine('F_SETLK64') === cDefine('F_SETLK')), '' }}} + {{{ assert(cDefine('F_SETLKW64') === cDefine('F_SETLKW')), '' }}} + return 0; // Pretend that the locking is successful. + case {{{ cDefine('F_GETOWN_EX') }}}: + case {{{ cDefine('F_SETOWN') }}}: + return -{{{ cDefine('EINVAL') }}}; // These are for sockets. We don't have them fully implemented yet. + case {{{ cDefine('F_GETOWN') }}}: + // musl trusts getown return values, due to a bug where they must be, as they overlap with errors. just return -1 here, so fnctl() returns that, and we set errno ourselves. + setErrNo({{{ cDefine('EINVAL') }}}); + return -1; + default: { + return -{{{ cDefine('EINVAL') }}}; + } + } + }, + statfs64_async: function(path, size, buf) { + path = ASYNCSYSCALLS.getStr(path); + // NOTE: None of the constants here are true. We're just returning safe and + // sane values. + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, '4096', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, '1000000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, '500000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, '500000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'PThreadFS.nextInode', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, '1000000', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, '42', 'i32') }}}; + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, '2', 'i32') }}}; // ST_NOSUID + {{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, '255', 'i32') }}}; + return 0; + }, + fstatfs64_async: async function(fd, size, buf) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd); + return _statfs64(0, size, buf); + }, + openat_async: async function(dirfd, path, flags, varargs) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + var mode = varargs ? ASYNCSYSCALLS.get() : 0; + let stream = await PThreadFS.open(path, flags, mode); + return stream.fd; + }, + mkdirat_async: async function(dirfd, path, mode) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return ASYNCSYSCALLS.doMkdir(path, mode); + }, + mknodat_async: async function(dirfd, path, mode, dev) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return ASYNCSYSCALLS.doMknod(path, mode, dev); + }, + fchownat_async: async function(dirfd, path, owner, group, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + await PThreadFS.chown(path, owner, group); + return 0; + }, + fstatat64_async: async function(dirfd, path, buf, flags) { + path = ASYNCSYSCALLS.getStr(path); + var nofollow = flags & {{{ cDefine('AT_SYMLINK_NOFOLLOW') }}}; + var allowEmpty = flags & {{{ cDefine('AT_EMPTY_PATH') }}}; + flags = flags & (~{{{ cDefine('AT_SYMLINK_NOFOLLOW') | cDefine('AT_EMPTY_PATH') }}}); + path = ASYNCSYSCALLS.calculateAt(dirfd, path, allowEmpty); + return await ASYNCSYSCALLS.doStat(nofollow ? PThreadFS.lstat : PThreadFS.stat, path, buf); + }, + unlinkat_async: async function(dirfd, path, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + if (flags === 0) { + await PThreadFS.unlink(path); + } else if (flags === {{{ cDefine('AT_REMOVEDIR') }}}) { + await PThreadFS.rmdir(path); + } else { + abort('Invalid flags passed to unlinkat'); + } + return 0; + }, + renameat_async: async function(olddirfd, oldpath, newdirfd, newpath) { + oldpath = ASYNCSYSCALLS.getStr(oldpath); + newpath = ASYNCSYSCALLS.getStr(newpath); + oldpath = ASYNCSYSCALLS.calculateAt(olddirfd, oldpath); + newpath = ASYNCSYSCALLS.calculateAt(newdirfd, newpath); + await PThreadFS.rename(oldpath, newpath); + return 0; + }, + symlinkat_async: async function(target, newdirfd, linkpath) { + linkpath = ASYNCSYSCALLS.calculateAt(newdirfd, linkpath); + await PThreadFS.symlink(target, linkpath); + return 0; + }, + readlinkat_async: async function(dirfd, path, buf, bufsize) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return await ASYNCSYSCALLS.doReadlink(path, buf, bufsize); + }, + fchmodat_async: async function(dirfd, path, mode, varargs) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + await PThreadFS.chmod(path, mode); + return 0; + }, + faccessat_async: async function(dirfd, path, amode, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path); + return await ASYNCSYSCALLS.doAccess(path, amode); + }, + utimensat_async: async function(dirfd, path, times, flags) { + path = ASYNCSYSCALLS.getStr(path); + path = ASYNCSYSCALLS.calculateAt(dirfd, path, true); + var seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i32') }}}; + var nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; + var atime = (seconds*1000) + (nanoseconds/(1000*1000)); + times += {{{ C_STRUCTS.timespec.__size__ }}}; + seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i32') }}}; + nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; + var mtime = (seconds*1000) + (nanoseconds/(1000*1000)); + await PThreadFS.utime(path, atime, mtime); + return 0; + }, + fallocate_async: async function(fd, mode, off_low, off_high, len_low, len_high) { + var stream = await ASYNCSYSCALLS.getStreamFromFD(fd) + var offset = ASYNCSYSCALLS.get64(off_low, off_high); + var len = ASYNCSYSCALLS.get64(len_low, len_high); + await PThreadFS.allocate(stream, offset, len); + return 0; + }, + dup3_async: async function(fd, suggestFD, flags) { + var old = await ASYNCSYSCALLS.getStreamFromFD(fd); + if (old.fd === suggestFD) return -{{{ cDefine('EINVAL') }}}; + return await ASYNCSYSCALLS.doDup(old.path, old.flags, suggestFD); + }, +}; + +function wrapSyscallFunction(x, library, isWasi) { + if (x[0] === '$' || isJsLibraryConfigIdentifier(x)) { + return; + } + + var t = library[x]; + if (typeof t === 'string') return; + t = t.toString(); + + var isVariadic = !isWasi && t.includes(', varargs'); + var canThrow = library[x + '__nothrow'] !== true; + + var pre = '', post = ''; + if (isVariadic) { + pre += 'ASYNCSYSCALLS.varargs = varargs;\n'; + } + + delete library[x + '__nothrow']; + var handler = ''; + if (canThrow) { + pre += 'try {\n'; + handler += + "} catch (e) {\n" + + " if (typeof PThreadFS === 'undefined' || !(e instanceof PThreadFS.ErrnoError)) abort(e);\n"; + + // Musl syscalls are negated. + if (isWasi) { + handler += " return e.errno;\n"; + } else { + // Musl syscalls are negated. + handler += " return -e.errno;\n"; + } + handler += "}\n"; + } + post = handler + post; + + if (pre) { + var bodyStart = t.indexOf('{') + 1; + t = t.substring(0, bodyStart) + pre + t.substring(bodyStart); + } + if (post) { + var bodyEnd = t.lastIndexOf('}'); + t = t.substring(0, bodyEnd) + post + t.substring(bodyEnd); + } + library[x] = eval('(' + t + ')'); + if (!library[x + '__deps']) library[x + '__deps'] = []; + library[x + '__deps'].push('$ASYNCSYSCALLS'); +} + +for (var x in SyscallsLibrary) { + let isWasi = x.startsWith('fd_'); + wrapSyscallFunction(x, SyscallsLibrary, isWasi); +} + +mergeInto(LibraryManager.library, SyscallsLibrary); \ No newline at end of file diff --git a/pthreadfs/src/js/library_tty_async.js b/pthreadfs/src/js/library_tty_async.js new file mode 100644 index 000000000000..bdbd25145042 --- /dev/null +++ b/pthreadfs/src/js/library_tty_async.js @@ -0,0 +1,160 @@ +/** + * @license + * Copyright 2013 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + + mergeInto(LibraryManager.library, { + $TTY_ASYNC__deps: ['$PThreadFS'], +#if !MINIMAL_RUNTIME + $TTY_ASYNC__postset: function() { + addAtInit('TTY_ASYNC.init();'); + addAtExit('TTY_ASYNC.shutdown();'); + }, +#endif + $TTY_ASYNC: { + ttys: [], + init: function () { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // currently, PThreadFS.init does not distinguish if process.stdin is a file or TTY_ASYNC + // // device, it always assumes it's a TTY_ASYNC device. because of this, we're forcing + // // process.stdin to UTF8 encoding to at least make stdin reading compatible + // // with text files until PThreadFS.init can be refactored. + // process['stdin']['setEncoding']('utf8'); + // } + }, + shutdown: function() { + // https://github.com/emscripten-core/emscripten/pull/1555 + // if (ENVIRONMENT_IS_NODE) { + // // inolen: any idea as to why node -e 'process.stdin.read()' wouldn't exit immediately (with process.stdin being a tty)? + // // isaacs: because now it's reading from the stream, you've expressed interest in it, so that read() kicks off a _read() which creates a ReadReq operation + // // inolen: I thought read() in that case was a synchronous operation that just grabbed some amount of buffered data if it exists? + // // isaacs: it is. but it also triggers a _read() call, which calls readStart() on the handle + // // isaacs: do process.stdin.pause() and i'd think it'd probably close the pending call + // process['stdin']['pause'](); + // } + }, + register: function(dev, ops) { + TTY_ASYNC.ttys[dev] = { input: [], output: [], ops: ops }; + PThreadFS.registerDevice(dev, TTY_ASYNC.stream_ops); + }, + stream_ops: { + open: function(stream) { + var tty = TTY_ASYNC.ttys[stream.node.rdev]; + if (!tty) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENODEV') }}}); + } + stream.tty = tty; + stream.seekable = false; + }, + close: function(stream) { + // flush any pending line data + stream.tty.ops.flush(stream.tty); + }, + flush: function(stream) { + stream.tty.ops.flush(stream.tty); + }, + read: function(stream, buffer, offset, length, pos /* ignored */) { + if (!stream.tty || !stream.tty.ops.get_char) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENXIO') }}}); + } + var bytesRead = 0; + for (var i = 0; i < length; i++) { + var result; + try { + result = stream.tty.ops.get_char(stream.tty); + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (result === undefined && bytesRead === 0) { + throw new PThreadFS.ErrnoError({{{ cDefine('EAGAIN') }}}); + } + if (result === null || result === undefined) break; + bytesRead++; + buffer[offset+i] = result; + } + if (bytesRead) { + stream.node.timestamp = Date.now(); + } + return bytesRead; + }, + write: function(stream, buffer, offset, length, pos) { + if (!stream.tty || !stream.tty.ops.put_char) { + throw new PThreadFS.ErrnoError({{{ cDefine('ENXIO') }}}); + } + try { + for (var i = 0; i < length; i++) { + stream.tty.ops.put_char(stream.tty, buffer[offset+i]); + } + } catch (e) { + throw new PThreadFS.ErrnoError({{{ cDefine('EIO') }}}); + } + if (length) { + stream.node.timestamp = Date.now(); + } + return i; + } + }, + default_tty_ops: { + // get_char has 3 particular return values: + // a.) the next character represented as an integer + // b.) undefined to signal that no data is currently available + // c.) null to signal an EOF + get_char: function(tty) { + if (!tty.input.length) { + var result = null; + if (typeof window != 'undefined' && + typeof window.prompt == 'function') { + // Browser. + result = window.prompt('Input: '); // returns null on cancel + if (result !== null) { + result += '\n'; + } + } else if (typeof readline == 'function') { + // Command line. + result = readline(); + if (result !== null) { + result += '\n'; + } + } + if (!result) { + return null; + } + tty.input = intArrayFromString(result, true); + } + return tty.input.shift(); + }, + put_char: function(tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + out(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); // val == 0 would cut text output off in the middle. + } + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + out(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + } + }, + default_tty1_ops: { + put_char: function(tty, val) { + if (val === null || val === {{{ charCode('\n') }}}) { + err(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } else { + if (val != 0) tty.output.push(val); + } + }, + flush: function(tty) { + if (tty.output && tty.output.length > 0) { + err(UTF8ArrayToString(tty.output, 0)); + tty.output = []; + } + } + } + } +}); \ No newline at end of file diff --git a/pthreadfs/src/js/pthreadfs.js b/pthreadfs/src/js/pthreadfs.js new file mode 100644 index 000000000000..f730279e637c --- /dev/null +++ b/pthreadfs/src/js/pthreadfs.js @@ -0,0 +1,184 @@ +/** + * @license + * Copyright 2021 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +var SyscallWrappers = {} + +let SyscallsFunctions = [ + {'name': 'open', 'args': ['path', 'flags', 'varargs']}, + {'name': 'unlink', 'args': ['path']}, + {'name': 'chdir', 'args': ['path']}, + {'name': 'mknod', 'args': ['path', 'mode', 'dev']}, + {'name': 'chmod', 'args': ['path', 'mode']}, + {'name': 'access', 'args': ['path', 'amode']}, + {'name': 'rename', 'args': ['old_path', 'new_path']}, + {'name': 'mkdir', 'args': ['path', 'mode']}, + {'name': 'rmdir', 'args': ['path']}, + {'name': 'dup', 'args': ['fd']}, + {'name': 'ioctl', 'args': ['fd', 'request', 'varargs']}, + {'name': 'dup2', 'args': ['oldfd', 'newfd']}, + {'name': 'symlink', 'args': ['target', 'linkpath']}, + {'name': 'readlink', 'args': ['path', 'buf', 'bufsize']}, + {'name': 'munmap', 'args': ['addr', 'len']}, + {'name': 'fchmod', 'args': ['fd', 'mode']}, + {'name': 'fchdir', 'args': ['fd']}, + {'name': '_newselect', 'args': ['nfds', 'readfds', 'writefds', 'exceptfds', 'timeout']}, + {'name': 'msync', 'args': ['addr', 'len', 'flags']}, + {'name': 'fdatasync', 'args': ['fd']}, + {'name': 'poll', 'args': ['fds', 'nfds', 'timeout']}, + {'name': 'getcwd', 'args': ['buf', 'size']}, + {'name': 'mmap2', 'args': ['addr', 'len', 'prot', 'flags', 'fd', 'off']}, + {'name': 'truncate64', 'args': ['path', 'zero', 'low', 'high']}, + {'name': 'ftruncate64', 'args': ['fd', 'zero', 'low', 'high']}, + {'name': 'stat64', 'args': ['path', 'buf']}, + {'name': 'lstat64', 'args': ['path', 'buf']}, + {'name': 'fstat64', 'args': ['fd', 'buf']}, + {'name': 'lchown32', 'args': ['path', 'owner', 'group']}, + {'name': 'fchown32', 'args': ['fd', 'owner', 'group']}, + {'name': 'chown32', 'args': ['path', 'owner', 'group']}, + {'name': 'getdents64', 'args': ['fd', 'dirp', 'count']}, + {'name': 'fcntl64', 'args': ['fd', 'cmd', 'varargs']}, + {'name': 'statfs64', 'args': ['path', 'size', 'buf']}, + {'name': 'fstatfs64', 'args': ['fd', 'size', 'buf']}, + {'name': 'openat', 'args': ['dirfd', 'path', 'flags', 'varargs']}, + {'name': 'mkdirat', 'args': ['dirfd', 'path', 'mode']}, + {'name': 'mknodat', 'args': ['dirfd', 'path', 'mode', 'dev']}, + {'name': 'fchownat', 'args': ['dirfd', 'path', 'owner', 'group', 'flags']}, + {'name': 'fstatat64', 'args': ['dirfd', 'path', 'buf', 'flags']}, + {'name': 'unlinkat', 'args': ['dirfd', 'path', 'flags']}, + {'name': 'renameat', 'args': ['olddirfd', 'oldpath', 'newdirfd', 'newpath']}, + {'name': 'symlinkat', 'args': ['target', 'newdirfd', 'linkpath']}, + {'name': 'readlinkat', 'args': ['dirfd', 'path', 'bug', 'bufsize']}, + {'name': 'fchmodat', 'args': ['dirfd', 'path', 'mode', 'varargs']}, + {'name': 'faccessat', 'args': ['dirfd', 'path', 'amode', 'flags']}, + {'name': 'utimensat', 'args': ['dirfd', 'path', 'times', 'flags']}, + {'name': 'fallocate', 'args': ['fd', 'mode', 'off_low', 'off_high', 'len_low', 'len_high']}, + {'name': 'dup3', 'args': ['fd', 'suggestfd', 'flags']}, +] + +let WasiFunctions = [ + {'name': 'write', 'args': ['iovs', 'iovs_len', 'nwritten']}, + {'name': 'read', 'args': ['iovs', 'iovs_len', 'nread']}, + {'name': 'close', 'args': []}, + {'name': 'pwrite', 'args': ['iov', 'iovcnt', "{{{ defineI64Param('offset') }}}", 'pnum']}, + {'name': 'pread', 'args': ['iov', 'iovcnt', "{{{ defineI64Param('offset') }}}", 'pnum']}, + {'name': 'seek', 'args': ["{{{ defineI64Param('offset') }}}", 'whence', 'newOffset']}, + {'name': 'fdstat_get', 'args': ['pbuf']}, + {'name': 'sync', 'args': []}, +] + +function createWasiWrapper(name, args, wrappers) { + let full_args = 'fd'; + if (args.length > 0) { + full_args = full_args + ',' + args.join(','); + } + let full_args_with_resume = full_args + ',resume'; + let wrapper = `function(${full_args_with_resume}) {`; + wrapper += `_fd_${name}_async(${full_args}).then((res) => {`; + wrapper += 'wasmTable.get(resume)(res);});}' + wrappers[`__fd_${name}_async`] = eval('(' + wrapper + ')'); + wrappers[`__fd_${name}_async__deps`] = [`fd_${name}_async`, '$ASYNCSYSCALLS', '$FSAFS']; +} + +function createSyscallWrapper(name, args, wrappers) { + let full_args = ''; + let full_args_with_resume = 'resume'; + if (args.length > 0) { + full_args = args.join(','); + full_args_with_resume = full_args + ', resume'; + } + let wrapper = `function(${full_args_with_resume}) {`; + wrapper += `_${name}_async(${full_args}).then((res) => {`; + wrapper += 'wasmTable.get(resume)(res);});}' + wrappers[`__sys_${name}_async`] = eval('(' + wrapper + ')'); + wrappers[`__sys_${name}_async__deps`] = [`${name}_async`, '$ASYNCSYSCALLS', '$FSAFS']; +} + +for (x of WasiFunctions) { + createWasiWrapper(x.name, x.args, SyscallWrappers); +} +for (x of SyscallsFunctions) { + createSyscallWrapper(x.name, x.args, SyscallWrappers); +} + +SyscallWrappers['init_pthreadfs'] = function (resume) { + var FSNode = /** @constructor */ function(parent, name, mode, rdev) { + if (!parent) { + parent = this; // root node sets parent to itself + } + this.parent = parent; + this.mount = parent.mount; + this.mounted = null; + this.id = PThreadFS.nextInode++; + this.name = name; + this.mode = mode; + this.node_ops = {}; + this.stream_ops = {}; + this.rdev = rdev; + }; + var readMode = 292/*{{{ cDefine("S_IRUGO") }}}*/ | 73/*{{{ cDefine("S_IXUGO") }}}*/; + var writeMode = 146/*{{{ cDefine("S_IWUGO") }}}*/; + Object.defineProperties(FSNode.prototype, { + read: { + get: /** @this{FSNode} */function() { + return (this.mode & readMode) === readMode; + }, + set: /** @this{FSNode} */function(val) { + val ? this.mode |= readMode : this.mode &= ~readMode; + } + }, + write: { + get: /** @this{FSNode} */function() { + return (this.mode & writeMode) === writeMode; + }, + set: /** @this{FSNode} */function(val) { + val ? this.mode |= writeMode : this.mode &= ~writeMode; + } + }, + isFolder: { + get: /** @this{FSNode} */function() { + return PThreadFS.isDir(this.mode); + } + }, + isDevice: { + get: /** @this{FSNode} */function() { + return PThreadFS.isChrdev(this.mode); + } + } + }); + PThreadFS.FSNode = FSNode; + + PThreadFS.staticInit().then(async ()=> { + await PThreadFS.init(); + PThreadFS.ignorePermissions = false; + wasmTable.get(resume)(); + }); +} + +SyscallWrappers['init_fsafs'] = function(resume) { + PThreadFS.mkdir('/filesystemaccess').then(async () => { + await PThreadFS.mount(FSAFS, { root: '.' }, '/filesystemaccess'); + wasmTable.get(resume)(); + }); +} + +SyscallWrappers['init_sfafs'] = function(resume) { + PThreadFS.mkdir('/sfa').then(async () => { + await PThreadFS.mount(SFAFS, { root: '.' }, '/sfa'); + + // Storage Foundation requires explicit capacity allocations. + if (storageFoundation.requestCapacity) { + await storageFoundation.requestCapacity(1024*1024*100); + } + // Delete all old files. + let files = await storageFoundation.getAll(); + for (file of files) { + await storageFoundation.delete(file); + } + wasmTable.get(resume)(); + }); +} + +mergeInto(LibraryManager.library, SyscallWrappers); \ No newline at end of file diff --git a/pthreadfs/src/pthreadfs.cpp b/pthreadfs/src/pthreadfs.cpp new file mode 100644 index 000000000000..0b313b28b754 --- /dev/null +++ b/pthreadfs/src/pthreadfs.cpp @@ -0,0 +1,366 @@ +#include "pthreadfs.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +SyncToAsync::SyncToAsync() : thread(threadMain, this), childLock(mutex) { + // The child lock is associated with the mutex, which takes the lock, and + // we free it here. Only the child will lock/unlock it from now on. + childLock.unlock(); +} + +SyncToAsync::~SyncToAsync() { + quit = true; + + shutdown(); + + thread.join(); +} + +void SyncToAsync::shutdown() { + readyToWork = true; + condition.notify_one(); +} + +void SyncToAsync::doWork(std::function newWork) { + // Send the work over. + { + std::lock_guard lock(mutex); + work = newWork; + finishedWork = false; + readyToWork = true; + } + condition.notify_one(); + + // Wait for it to be complete. + std::unique_lock lock(mutex); + condition.wait(lock, [&]() { + return finishedWork; + }); +} + +void* SyncToAsync::threadMain(void* arg) { + // Prevent the pthread from shutting down too early. + EM_ASM(runtimeKeepalivePush();); + auto* parent = (SyncToAsync*)arg; + emscripten_async_call(threadIter, arg, 0); + return 0; +} + +void SyncToAsync::threadIter(void* arg) { + auto* parent = (SyncToAsync*)arg; + // Wait until we get something to do. + parent->childLock.lock(); + parent->condition.wait(parent->childLock, [&]() { + return parent->readyToWork; + }); + if (parent->quit) { + EM_ASM(runtimeKeepalivePop();); + return; + } + auto work = parent->work; + parent->readyToWork = false; + // Do the work. + work([parent, arg]() { + // We are called, so the work was finished. Notify the caller. + parent->finishedWork = true; + parent->childLock.unlock(); + parent->condition.notify_one(); + threadIter(arg); + }); +} + +// Define global variables to be populated by resume; +SyncToAsync::Callback g_resumeFct; +SyncToAsync g_synctoasync_helper; + +// Static functions calling resumFct and setting the return value. +void resumeWrapper_v() +{ + g_resumeFct(); +} +// return value long +long resume_result_long = 0; +void resumeWrapper_l(long retVal) +{ + resume_result_long = retVal; + g_resumeFct(); +} +// return value __wasi_errno_t +__wasi_errno_t resume_result_wasi = 0; +void resumeWrapper_wasi(__wasi_errno_t retVal) +{ + resume_result_wasi = retVal; + g_resumeFct(); +} + +// Wasi definitions +WASI_CAPI_DEF(write, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_size_t *nwritten) { + WASI_SYNCTOASYNC(write, iovs, iovs_len, nwritten); +} +WASI_CAPI_DEF(read, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_size_t *nread) { + WASI_SYNCTOASYNC(read, iovs, iovs_len, nread); +} +WASI_CAPI_DEF(pwrite, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nwritten) { + WASI_SYNCTOASYNC(pwrite, iovs, iovs_len, offset, nwritten); +} +WASI_CAPI_DEF(pread, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nread) { + WASI_SYNCTOASYNC(pread, iovs, iovs_len, offset, nread); +} +WASI_CAPI_DEF(seek, __wasi_filedelta_t offset, __wasi_whence_t whence, __wasi_filesize_t *newoffset) { + WASI_SYNCTOASYNC(seek, offset, whence, newoffset); +} +WASI_CAPI_DEF(fdstat_get, __wasi_fdstat_t *stat) { + WASI_SYNCTOASYNC(fdstat_get, stat); +} +WASI_CAPI_NOARGS_DEF(close) { + WASI_SYNCTOASYNC_NOARGS(close); +} +WASI_CAPI_NOARGS_DEF(sync) { + WASI_SYNCTOASYNC_NOARGS(sync); +} + +// Syscall definitions +SYS_CAPI_DEF(open, 5, long path, long flags, ...) { + va_list vl; + va_start(vl, flags); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(open, path, flags, varargs); +} + +SYS_CAPI_DEF(unlink, 10, long path) { + SYS_SYNCTOASYNC(unlink, path); +} + +SYS_CAPI_DEF(chdir, 12, long path) { + SYS_SYNCTOASYNC(chdir, path); +} + +SYS_CAPI_DEF(mknod, 14, long path, long mode, long dev) { + SYS_SYNCTOASYNC(mknod, path, mode, dev); +} + +SYS_CAPI_DEF(chmod, 15, long path, long mode) { + SYS_SYNCTOASYNC(chmod, path, mode); +} + +SYS_CAPI_DEF(access, 33, long path, long amode) { + SYS_SYNCTOASYNC(access, path, amode); +} + +SYS_CAPI_DEF(rename, 38, long old_path, long new_path) { + SYS_SYNCTOASYNC(rename, old_path, new_path); +} + +SYS_CAPI_DEF(mkdir, 39, long path, long mode) { + SYS_SYNCTOASYNC(mkdir, path, mode); +} + +SYS_CAPI_DEF(rmdir, 40, long path) { + SYS_SYNCTOASYNC(rmdir, path); +} + +SYS_CAPI_DEF(dup, 41, long fd) { + SYS_SYNCTOASYNC(dup, fd); +} + +SYS_CAPI_DEF(ioctl, 54, long fd, long request, ...) { + void *arg; + va_list ap; + va_start(ap, request); + arg = va_arg(ap, void *); + va_end(ap); + + SYS_SYNCTOASYNC(ioctl, fd, request, arg); +} + +SYS_CAPI_DEF(dup2, 63, long oldfd, long newfd) { + SYS_SYNCTOASYNC(dup2, oldfd, newfd); +} + +SYS_CAPI_DEF(symlink, 83, long target, long linkpath) { + SYS_SYNCTOASYNC(symlink, target, linkpath); +} + +SYS_CAPI_DEF(readlink, 85, long path, long buf, long bufsize) { + SYS_SYNCTOASYNC(readlink, path, buf, bufsize); +} + +SYS_CAPI_DEF(munmap, 91, long addr, long len) { + SYS_SYNCTOASYNC(munmap, addr, len); +} + +SYS_CAPI_DEF(fchmod, 94, long fd, long mode) { + SYS_SYNCTOASYNC(fchmod, fd, mode); +} + +SYS_CAPI_DEF(fchdir, 133, long fd) { + SYS_SYNCTOASYNC(fchdir, fd); +} + +SYS_CAPI_DEF(_newselect, 142, long nfds, long readfds, long writefds, long exceptfds, long timeout) { + SYS_SYNCTOASYNC(_newselect, nfds, readfds, writefds, exceptfds, timeout); +} + +SYS_CAPI_DEF(msync, 144, long addr, long len, long flags) { + SYS_SYNCTOASYNC(msync, addr, len, flags); +} + +SYS_CAPI_DEF(fdatasync, 148, long fd) { + SYS_SYNCTOASYNC(fdatasync, fd); +} + +SYS_CAPI_DEF(poll, 168, long fds, long nfds, long timeout) { + SYS_SYNCTOASYNC(poll, fds, nfds, timeout); +} + +SYS_CAPI_DEF(getcwd, 183, long buf, long size) { + SYS_SYNCTOASYNC(getcwd, buf, size); +} + +SYS_CAPI_DEF(mmap2, 192, long addr, long len, long prot, long flags, long fd, long off) { + SYS_SYNCTOASYNC(mmap2, addr, len, prot, flags, fd, off); +} + +SYS_CAPI_DEF(truncate64, 193, long path, long zero, long low, long high) { + SYS_SYNCTOASYNC(truncate64, path, zero, low, high); +} + +SYS_CAPI_DEF(ftruncate64, 194, long fd, long zero, long low, long high) { + SYS_SYNCTOASYNC(ftruncate64, fd, zero, low, high); +} + +SYS_CAPI_DEF(stat64, 195, long path, long buf) { + SYS_SYNCTOASYNC(stat64, path, buf); +} + +SYS_CAPI_DEF(lstat64, 196, long path, long buf) { + SYS_SYNCTOASYNC(lstat64, path, buf); +} + +SYS_CAPI_DEF(fstat64, 197, long fd, long buf) { + SYS_SYNCTOASYNC(fstat64, fd, buf); +} + +SYS_CAPI_DEF(lchown32, 198, long path, long owner, long group) { + SYS_SYNCTOASYNC(lchown32, path, owner, group); +} + +SYS_CAPI_DEF(fchown32, 207, long fd, long owner, long group) { + SYS_SYNCTOASYNC(fchown32, fd, owner, group); +} + +SYS_CAPI_DEF(chown32, 212, long path, long owner, long group) { + SYS_SYNCTOASYNC(chown32, path, owner, group); +} + +SYS_CAPI_DEF(getdents64, 220, long fd, long dirp, long count) { + SYS_SYNCTOASYNC(getdents64, fd, dirp, count); +} + +SYS_CAPI_DEF(fcntl64, 221, long fd, long cmd, ...) { +va_list vl; + va_start(vl, cmd); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(fcntl64, fd, cmd, varargs); +} + +SYS_CAPI_DEF(statfs64, 268, long path, long size, long buf) { + SYS_SYNCTOASYNC(statfs64, path, size, buf); +} + +SYS_CAPI_DEF(fstatfs64, 269, long fd, long size, long buf) { + SYS_SYNCTOASYNC(fstatfs64, fd, size, buf); +} + +SYS_CAPI_DEF(openat, 295, long dirfd, long path, long flags, ...) { +va_list vl; + va_start(vl, flags); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(openat, dirfd, path, flags, varargs); +} + +SYS_CAPI_DEF(mkdirat, 296, long dirfd, long path, long mode) { + SYS_SYNCTOASYNC(mkdirat, dirfd, path, mode); +} + +SYS_CAPI_DEF(mknodat, 297, long dirfd, long path, long mode, long dev) { + SYS_SYNCTOASYNC(mknodat, dirfd, path, mode, dev); +} + +SYS_CAPI_DEF(fchownat, 298, long dirfd, long path, long owner, long group, long flags) { + SYS_SYNCTOASYNC(fchownat, dirfd, path, owner, group, flags); +} + +SYS_CAPI_DEF(fstatat64, 300, long dirfd, long path, long buf, long flags) { + SYS_SYNCTOASYNC(fstatat64, dirfd, path, buf, flags); +} + +SYS_CAPI_DEF(unlinkat, 301, long dirfd, long path, long flags) { + SYS_SYNCTOASYNC(unlinkat, dirfd, path, flags); +} + +SYS_CAPI_DEF(renameat, 302, long olddirfd, long oldpath, long newdirfd, long newpath) { + SYS_SYNCTOASYNC(renameat, olddirfd, oldpath, newdirfd, newpath); +} + +SYS_CAPI_DEF(symlinkat, 304, long target, long newdirfd, long linkpath) { + SYS_SYNCTOASYNC(symlinkat, target, newdirfd, linkpath); +} + +SYS_CAPI_DEF(readlinkat, 305, long dirfd, long path, long bug, long bufsize) { + SYS_SYNCTOASYNC(readlinkat, dirfd, path, bug, bufsize); +} + +SYS_CAPI_DEF(fchmodat, 306, long dirfd, long path, long mode, ...) { +va_list vl; + va_start(vl, mode); + int varargs = va_arg(vl, int); + va_end(vl); + + SYS_SYNCTOASYNC(fchmodat, dirfd, path, mode, varargs); +} + +SYS_CAPI_DEF(faccessat, 307, long dirfd, long path, long amode, long flags) { + SYS_SYNCTOASYNC(faccessat, dirfd, path, amode, flags); +} + +SYS_CAPI_DEF(utimensat, 320, long dirfd, long path, long times, long flags) { + SYS_SYNCTOASYNC(utimensat, dirfd, path, times, flags); +} + +SYS_CAPI_DEF(fallocate, 324, long fd, long mode, long off_low, long off_high, long len_low, long len_high) { + SYS_SYNCTOASYNC(fallocate, fd, mode, off_low, off_high, len_low, len_high); +} + +SYS_CAPI_DEF(dup3, 330, long fd, long suggestfd, long flags) { + SYS_SYNCTOASYNC(dup3, fd, suggestfd, flags); +} + +// Other helper code + +void emscripten_init_pthreadfs() { + g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { + g_resumeFct = [resume]() { resume(); }; + init_pthreadfs(&resumeWrapper_v); + }); + g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { + g_resumeFct = [resume]() { resume(); }; + init_fsafs(&resumeWrapper_v); + }); + return; +} \ No newline at end of file diff --git a/pthreadfs/src/pthreadfs.h b/pthreadfs/src/pthreadfs.h new file mode 100644 index 000000000000..04d0b57d5d65 --- /dev/null +++ b/pthreadfs/src/pthreadfs.h @@ -0,0 +1,265 @@ +#ifndef PTHREADFS_H +#define PTHREADFS_H + +#include +#include + +#define EM_PTHREADFS_ASM(code) g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + EM_ASM({(async () => {code wasmTable.get($0)(); \ + })();}, &resumeWrapper_v); \ + }); + +#define WASI_JSAPI_DEF(name, ...) extern void __fd_##name##_async(__wasi_fd_t fd, __VA_ARGS__, void (*fun)(__wasi_errno_t)); +#define WASI_JSAPI_NOARGS_DEF(name) extern void __fd_##name##_async(__wasi_fd_t fd, void (*fun)(__wasi_errno_t)); + +#define WASI_CAPI_DEF(name, ...) __wasi_errno_t __wasi_fd_##name(__wasi_fd_t fd, __VA_ARGS__) +#define WASI_CAPI_NOARGS_DEF(name) __wasi_errno_t __wasi_fd_##name(__wasi_fd_t fd) + +#define WASI_JSAPI(name, ...) __fd_##name##_async(__VA_ARGS__) +#define WASI_SYNCTOASYNC(name, ...) g_synctoasync_helper.doWork([fd, __VA_ARGS__](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + WASI_JSAPI(name, fd, __VA_ARGS__, &resumeWrapper_wasi); \ + }); \ + return resume_result_wasi; +#define WASI_SYNCTOASYNC_NOARGS(name) g_synctoasync_helper.doWork([fd](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + WASI_JSAPI(name, fd, &resumeWrapper_wasi); \ + }); \ + return resume_result_wasi; + +// Classic Syscalls + +#define SYS_JSAPI_DEF(name, ...) extern void __sys_##name##_async(__VA_ARGS__, void (*fun)(long)); +#define SYS_JSAPI_NOARGS_DEF(name) extern void __sys_##name##_async(void (*fun)(long)); + +#define SYS_CAPI_DEF(name, number, ...) long __syscall##number(__VA_ARGS__) + +#define SYS_DEF(name, number, ...) SYS_CAPI_DEF(name, number, __VA_ARGS__); SYS_JSAPI_DEF(name, __VA_ARGS__) + +#define SYS_JSAPI(name, ...) __sys_##name##_async(__VA_ARGS__) +#define SYS_SYNCTOASYNC(name, ...) g_synctoasync_helper.doWork([__VA_ARGS__](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + SYS_JSAPI(name, __VA_ARGS__, &resumeWrapper_l); \ + }); \ + return resume_result_long; +#define SYS_SYNCTOASYNC_NOARGS(name) g_synctoasync_helper.doWork([](SyncToAsync::Callback resume) { \ + g_resumeFct = [resume]() { resume(); }; \ + SYS_JSAPI(name, &resumeWrapper_l); \ + }); \ + return resume_result_long; + +extern "C" { + // Helpers + extern void init_pthreadfs(void (*fun)(void)); + extern void init_sfafs(void (*fun)(void)); + extern void init_fsafs(void (*fun)(void)); + void emscripten_init_pthreadfs(); + + // WASI + WASI_JSAPI_DEF(write, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_size_t *nwritten) + WASI_JSAPI_DEF(read, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_size_t *nread) + WASI_JSAPI_DEF(pwrite, const __wasi_ciovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nwritten) + WASI_JSAPI_DEF(pread, const __wasi_iovec_t *iovs, size_t iovs_len, __wasi_filesize_t offset, __wasi_size_t *nread) + WASI_JSAPI_DEF(seek, __wasi_filedelta_t offset, __wasi_whence_t whence, __wasi_filesize_t *newoffset) + WASI_JSAPI_DEF(fdstat_get, __wasi_fdstat_t *stat) + WASI_JSAPI_NOARGS_DEF(close) + WASI_JSAPI_NOARGS_DEF(sync) + + // Syscalls + // see https://github.com/emscripten-core/emscripten/blob/main/system/lib/libc/musl/arch/emscripten/syscall_arch.h +SYS_CAPI_DEF(open, 5, long path, long flags, ...); +SYS_JSAPI_DEF(open, long path, long flags, int varargs) + +SYS_CAPI_DEF(unlink, 10, long path); +SYS_JSAPI_DEF(unlink, long path) + +SYS_CAPI_DEF(chdir, 12, long path); +SYS_JSAPI_DEF(chdir, long path) + +SYS_CAPI_DEF(mknod, 14, long path, long mode, long dev); +SYS_JSAPI_DEF(mknod, long path, long mode, long dev) + +SYS_CAPI_DEF(chmod, 15, long path, long mode); +SYS_JSAPI_DEF(chmod, long path, long mode) + +SYS_CAPI_DEF(access, 33, long path, long amode); +SYS_JSAPI_DEF(access, long path, long amode) + +SYS_CAPI_DEF(rename, 38, long old_path, long new_path); +SYS_JSAPI_DEF(rename, long old_path, long new_path) + +SYS_CAPI_DEF(mkdir, 39, long path, long mode); +SYS_JSAPI_DEF(mkdir, long path, long mode) + +SYS_CAPI_DEF(rmdir, 40, long path); +SYS_JSAPI_DEF(rmdir, long path) + +SYS_CAPI_DEF(dup, 41, long fd); +SYS_JSAPI_DEF(dup, long fd) + +SYS_CAPI_DEF(ioctl, 54, long fd, long request, ...); +SYS_JSAPI_DEF(ioctl, long fd, long request, void *const varargs) + +SYS_CAPI_DEF(dup2, 63, long oldfd, long newfd); +SYS_JSAPI_DEF(dup2, long oldfd, long newfd) + +SYS_CAPI_DEF(symlink, 83, long target, long linkpath); +SYS_JSAPI_DEF(symlink, long target, long linkpath) + +SYS_CAPI_DEF(readlink, 85, long path, long buf, long bufsize); +SYS_JSAPI_DEF(readlink, long path, long buf, long bufsize) + +SYS_CAPI_DEF(munmap, 91, long addr, long len); +SYS_JSAPI_DEF(munmap, long addr, long len) + +SYS_CAPI_DEF(fchmod, 94, long fd, long mode); +SYS_JSAPI_DEF(fchmod, long fd, long mode) + +SYS_CAPI_DEF(fchdir, 133, long fd); +SYS_JSAPI_DEF(fchdir, long fd) + +SYS_CAPI_DEF(_newselect, 142, long nfds, long readfds, long writefds, long exceptfds, long timeout); +SYS_JSAPI_DEF(_newselect, long nfds, long readfds, long writefds, long exceptfds, long timeout) + +SYS_CAPI_DEF(msync, 144, long addr, long len, long flags); +SYS_JSAPI_DEF(msync, long addr, long len, long flags) + +SYS_CAPI_DEF(fdatasync, 148, long fd); +SYS_JSAPI_DEF(fdatasync, long fd) + +SYS_CAPI_DEF(poll, 168, long fds, long nfds, long timeout); +SYS_JSAPI_DEF(poll, long fds, long nfds, long timeout) + +SYS_CAPI_DEF(getcwd, 183, long buf, long size); +SYS_JSAPI_DEF(getcwd, long buf, long size) + +SYS_CAPI_DEF(mmap2, 192, long addr, long len, long prot, long flags, long fd, long off); +SYS_JSAPI_DEF(mmap2, long addr, long len, long prot, long flags, long fd, long off) + +SYS_CAPI_DEF(truncate64, 193, long path, long zero, long low, long high); +SYS_JSAPI_DEF(truncate64, long path, long zero, long low, long high) + +SYS_CAPI_DEF(ftruncate64, 194, long fd, long zero, long low, long high); +SYS_JSAPI_DEF(ftruncate64, long fd, long zero, long low, long high) + +SYS_CAPI_DEF(stat64, 195, long path, long buf); +SYS_JSAPI_DEF(stat64, long path, long buf) + +SYS_CAPI_DEF(lstat64, 196, long path, long buf); +SYS_JSAPI_DEF(lstat64, long path, long buf) + +SYS_CAPI_DEF(fstat64, 197, long fd, long buf); +SYS_JSAPI_DEF(fstat64, long fd, long buf) + +SYS_CAPI_DEF(lchown32, 198, long path, long owner, long group); +SYS_JSAPI_DEF(lchown32, long path, long owner, long group) + +SYS_CAPI_DEF(fchown32, 207, long fd, long owner, long group); +SYS_JSAPI_DEF(fchown32, long fd, long owner, long group) + +SYS_CAPI_DEF(chown32, 212, long path, long owner, long group); +SYS_JSAPI_DEF(chown32, long path, long owner, long group) + +SYS_CAPI_DEF(getdents64, 220, long fd, long dirp, long count); +SYS_JSAPI_DEF(getdents64, long fd, long dirp, long count) + +SYS_CAPI_DEF(fcntl64, 221, long fd, long cmd, ...); +SYS_JSAPI_DEF(fcntl64, long fd, long cmd, int varargs) + +SYS_CAPI_DEF(statfs64, 268, long path, long size, long buf); +SYS_JSAPI_DEF(statfs64, long path, long size, long buf) + +SYS_CAPI_DEF(fstatfs64, 269, long fd, long size, long buf); +SYS_JSAPI_DEF(fstatfs64, long fd, long size, long buf) + +SYS_CAPI_DEF(openat, 295, long dirfd, long path, long flags, ...); +SYS_JSAPI_DEF(openat, long dirfd, long path, long flags, int varargs) + +SYS_CAPI_DEF(mkdirat, 296, long dirfd, long path, long mode); +SYS_JSAPI_DEF(mkdirat, long dirfd, long path, long mode) + +SYS_CAPI_DEF(mknodat, 297, long dirfd, long path, long mode, long dev); +SYS_JSAPI_DEF(mknodat, long dirfd, long path, long mode, long dev) + +SYS_CAPI_DEF(fchownat, 298, long dirfd, long path, long owner, long group, long flags); +SYS_JSAPI_DEF(fchownat, long dirfd, long path, long owner, long group, long flags) + +SYS_CAPI_DEF(fstatat64, 300, long dirfd, long path, long buf, long flags); +SYS_JSAPI_DEF(fstatat64, long dirfd, long path, long buf, long flags) + +SYS_CAPI_DEF(unlinkat, 301, long dirfd, long path, long flags); +SYS_JSAPI_DEF(unlinkat, long dirfd, long path, long flags) + +SYS_CAPI_DEF(renameat, 302, long olddirfd, long oldpath, long newdirfd, long newpath); +SYS_JSAPI_DEF(renameat, long olddirfd, long oldpath, long newdirfd, long newpath) + +SYS_CAPI_DEF(symlinkat, 304, long target, long newdirfd, long linkpath); +SYS_JSAPI_DEF(symlinkat, long target, long newdirfd, long linkpath) + +SYS_CAPI_DEF(readlinkat, 305, long dirfd, long path, long bug, long bufsize); +SYS_JSAPI_DEF(readlinkat, long dirfd, long path, long bug, long bufsize) + +SYS_CAPI_DEF(fchmodat, 306, long dirfd, long path, long mode, ...); +SYS_JSAPI_DEF(fchmodat, long dirfd, long path, long mode, int varargs) + +SYS_CAPI_DEF(faccessat, 307, long dirfd, long path, long amode, long flags); +SYS_JSAPI_DEF(faccessat, long dirfd, long path, long amode, long flags) + +SYS_CAPI_DEF(utimensat, 320, long dirfd, long path, long times, long flags); +SYS_JSAPI_DEF(utimensat, long dirfd, long path, long times, long flags) + +SYS_CAPI_DEF(fallocate, 324, long fd, long mode, long off_low, long off_high, long len_low, long len_high); +SYS_JSAPI_DEF(fallocate, long fd, long mode, long off_low, long off_high, long len_low, long len_high) + +SYS_CAPI_DEF(dup3, 330, long fd, long suggestfd, long flags); +SYS_JSAPI_DEF(dup3, long fd, long suggestfd, long flags) +} + +class SyncToAsync { +public: + using Callback = std::function; + + SyncToAsync(); + + ~SyncToAsync(); + + void shutdown(); + + // Run some work on thread. This is a synchronous call, but the thread can do + // async work for us. To allow us to know when the async work finishes, the + // worker is given a function to call at that time. + void doWork(std::function newWork); + +private: + std::thread thread; + std::mutex mutex; + std::condition_variable condition; + std::function work; + bool readyToWork = false; + bool finishedWork; + bool quit = false; + + // The child will be asynchronous, and therefore we cannot rely on RAII to + // unlock for us, we must do it manually. + std::unique_lock childLock; + + static void* threadMain(void* arg); + + static void threadIter(void* arg); +}; + +// Declare global variables to be populated by resume; +extern SyncToAsync::Callback g_resumeFct; +extern SyncToAsync g_synctoasync_helper; + +// Static functions calling resumFct and setting corresponding the return value. +void resumeWrapper_v(); + +void resumeWrapper_l(long retVal); + +void resumeWrapper_wasi(__wasi_errno_t retVal); + +void emscripten_init_pthreadfs(); + +#endif // PTHREADFS_H \ No newline at end of file From 1d1f3161ba7f51d9abfcb1b7764f23203087786f Mon Sep 17 00:00:00 2001 From: Richard Stotz Date: Fri, 6 Aug 2021 18:54:02 +0200 Subject: [PATCH 2/3] Fixed markdown, added newlines at end of files --- pthreadfs/README.md | 42 ++++++++++++++--------- pthreadfs/examples/Makefile | 3 +- pthreadfs/library_pthreadfs.js | 14 +++++--- pthreadfs/pthreadfs.cpp | 2 +- pthreadfs/pthreadfs.h | 2 +- pthreadfs/src/js/library_asyncfs.js | 2 +- pthreadfs/src/js/library_fsafs.js | 2 +- pthreadfs/src/js/library_sfafs_async.js | 2 +- pthreadfs/src/js/library_syscall_async.js | 2 +- pthreadfs/src/js/library_tty_async.js | 2 +- pthreadfs/src/js/pthreadfs.js | 2 +- pthreadfs/src/pthreadfs.cpp | 2 +- pthreadfs/src/pthreadfs.h | 2 +- 13 files changed, 47 insertions(+), 32 deletions(-) diff --git a/pthreadfs/README.md b/pthreadfs/README.md index ef786fcc1779..ac5ecbbeb83f 100644 --- a/pthreadfs/README.md +++ b/pthreadfs/README.md @@ -2,9 +2,10 @@ The Emscripten Pthread File System (PThreadFS) unlocks using (partly) asynchronous storage APIs such as [OPFS Access Handles](https://docs.google.com/document/d/121OZpRk7bKSF7qU3kQLqAEUVSNxqREnE98malHYwWec/edit#heading=h.gj2fudnvy982) through the Emscripten File System API. This enables C++ applications compiled through Emscripten to use persistent storage without using [Asyncify](https://emscripten.org/docs/porting/asyncify.html). PThreadFS requires only minimal modifications to the C++ code and nearly achieves feature parity with the Emscripten's classic File System API. -PThreadFS is based on Emscripten’s [SyncToAsync prototype](https://github.com/emscripten-core/emscripten/pull/13298). All file system operations are proxied through a dedicated worker thread. Although the underlying storage API is asynchronous, PThreadFS makes it appear synchronous to the C++ application. +PThreadFS works by replacing Emscripten's file system API with a new API that proxies all file system operations to a dedicated pthread. This dedicated thread maintains a virtual file system that can use different APIs as backend (very similar to the way Emscripten's VFS is designed). In particular, PThreadFS comes with built-in support for asynchronous backends such as [OPFS Access Handles](https://docs.google.com/document/d/121OZpRk7bKSF7qU3kQLqAEUVSNxqREnE98malHYwWec/edit#heading=h.gj2fudnvy982). +Although the underlying storage API is asynchronous, PThreadFS makes it appear synchronous to the C++ application. -The code is still prototype quality and **should not be used in a production environment**. +The code is still prototype quality and **should not be used in a production environment** for the time being. ## Enable and detect OPFS in Chrome @@ -22,20 +23,29 @@ In order to use the code in a new project, you only need the three files in the ### Code changes -- Include `pthreadfs.h` in the C++ file containing `main()`. +- Include `pthreadfs.h` in the C++ file containing `main()`: +``` +#include "pthreadfs.h" +``` - Call `emscripten_init_pthreadfs();` at the top of `main()` (or before any file system syscalls). -- Files that persist between sessions must be stored in `/filesystemaccess/` or its subfolders. +- PThreadFS maintains a virtual file system. The OPFS backend is mounted at `/filesystemaccess/`. Only files in this folder are persisted between sessions. All other files will be stored in-memory through MEMFS. ### Build process changes -- Compile `pthreadfs.h` and `pthreadfs.cpp` and link the resulting object to your application. -- Include the PThreadFS Javascript code by adding the following in the linking step: +There are two changes required to build a project with PThreadFS: +- Compile `pthreadfs.h` and `pthreadfs.cpp` and link the resulting object to your application. Add `-pthread` to the compiler flag to include support for pthreads. +- Add the following options to the linking step: ``` ---js-library=library_pthreadsfs.js +-pthread -O3 -s PROXY_TO_PTHREAD --js-library=library_pthreadsfs.js ``` -- Enable `PROXY_TO_PTHREAD` by adding the following to the linking step: +**Example** +If your build process was +```shell +emcc myproject.cpp -o myproject.html ``` --s PROXY_TO_PTHREAD +Your new build step should be +```shell +emcc -pthread -s PROXY_TO_PTHREAD -O3 --js-library=library_pthreadfs.js myproject.cpp pthreadfs.cpp -o myproject.html ``` ### Advanced Usage @@ -51,15 +61,13 @@ See `pthreadfs/examples/emscripten-tests/` for exemplary usage. ## Known Limitations -- The code is still prototype quality and **should not be used in a production environment**. -- PThreadFS requires PROXY_TO_PTHREAD to be active. - - In particular, no system calls interacting with the file system should be called from the main thread. +- The code is still prototype quality and **should not be used in a production environment** yet. It is possible that the use of PThreadFS might lead to subtle bugs in other libraries. +- PThreadFS requires PROXY_TO_PTHREAD to be active. In particular, no system calls interacting with the file system should be called from the main thread. - Some functionality of the Emscripten File System API is missing, such as sockets, IndexedDB integration and support for XHRequests. - PThreadFS depends on C++ libraries. `EM_PTRHEADFS_ASM()` cannot be used within C files (although initializing through `emscripten_init_pthreadfs()` is possible, see the `pthreadfs/examples/sqlite-speedtest` for an example). - Only in-memory storage (MEMFS) and OPFS Access Handles (FSAFS) are available as backends for PThreadFS. - In particualar, there is no support (yet) for IDBFS. Limited support is available for the Storage Foundation API. + There is no support (yet) for persisting data into IndexedDB (the way IDBFS works). Limited support is available for the Storage Foundation API as backend. - Performance is good if and only if full optimizations (compiler option `-O3`) are enabled and DevTools are closed. - Using stdout from C++ only prints to the Javascript console, not the Emscripten-generated html file. @@ -82,7 +90,8 @@ cd dist/sqlite-speedtest python3 -m http.server 8888 ``` Then open the following link in a Chrome instance with the -"File System Access Access Handles" enabled (see above): +_OPFS Access Handles_ [enabled](#enable-and-detect-opfs-in-chrome): + [localhost:8888/sqlite-speedtest](http://localhost:8888/sqlite-speedtest). The results of the speedtest can be found in the DevTools console. ### Other tests @@ -97,7 +106,8 @@ cd dist/emscripten-tests python3 -m http.server 8888 ``` Then open the following link in a Chrome instance with the -"File System Access Access Handles" enabled (see above): +_OPFS Access Handles_ [enabled](#enable-and-detect-opfs-in-chrome): + [localhost:8888/emscripten-tests](http://localhost:8888/emscripten-tests) and choose a test. The results of the test can be found in the DevTools console. ## Authors diff --git a/pthreadfs/examples/Makefile b/pthreadfs/examples/Makefile index 0b8a9a89f939..5cc77fa83653 100644 --- a/pthreadfs/examples/Makefile +++ b/pthreadfs/examples/Makefile @@ -123,4 +123,5 @@ emscripten-tests: $(addprefix dist/, $(addsuffix .html, $(basename $(wildcard $( dist/$(EMTESTS)/%.html : $(OBJ)/%.o $(OBJ)/pthreadfs.o $(PTHREADFS_JS) mkdir -p dist/$(EMTESTS) - $(EMCC) $(LINK_FLAGS) --js-library=$(PTHREADFS_JS) $< $(word 2,$^) -o $@ \ No newline at end of file + $(EMCC) $(LINK_FLAGS) --js-library=$(PTHREADFS_JS) $< $(word 2,$^) -o $@ + \ No newline at end of file diff --git a/pthreadfs/library_pthreadfs.js b/pthreadfs/library_pthreadfs.js index d840fba54201..96b1afd195ef 100644 --- a/pthreadfs/library_pthreadfs.js +++ b/pthreadfs/library_pthreadfs.js @@ -181,7 +181,8 @@ SyscallWrappers['init_sfafs'] = function(resume) { }); } -mergeInto(LibraryManager.library, SyscallWrappers);/** +mergeInto(LibraryManager.library, SyscallWrappers); +/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT @@ -1115,7 +1116,8 @@ for (var x in SyscallsLibrary) { wrapSyscallFunction(x, SyscallsLibrary, isWasi); } -mergeInto(LibraryManager.library, SyscallsLibrary);/** +mergeInto(LibraryManager.library, SyscallsLibrary); +/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT @@ -2696,7 +2698,8 @@ mergeInto(LibraryManager.library, SyscallsLibrary);/** } }, }, -});/** +}); +/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT @@ -3376,7 +3379,8 @@ mergeInto(LibraryManager.library, { }, } } -});/** +}); +/** * @license * Copyright 2013 The Emscripten Authors * SPDX-License-Identifier: MIT @@ -3535,4 +3539,4 @@ mergeInto(LibraryManager.library, { } } } -}); \ No newline at end of file +}); diff --git a/pthreadfs/pthreadfs.cpp b/pthreadfs/pthreadfs.cpp index 0b313b28b754..514c8540bc13 100644 --- a/pthreadfs/pthreadfs.cpp +++ b/pthreadfs/pthreadfs.cpp @@ -363,4 +363,4 @@ void emscripten_init_pthreadfs() { init_fsafs(&resumeWrapper_v); }); return; -} \ No newline at end of file +} diff --git a/pthreadfs/pthreadfs.h b/pthreadfs/pthreadfs.h index 04d0b57d5d65..f1220e644d27 100644 --- a/pthreadfs/pthreadfs.h +++ b/pthreadfs/pthreadfs.h @@ -262,4 +262,4 @@ void resumeWrapper_wasi(__wasi_errno_t retVal); void emscripten_init_pthreadfs(); -#endif // PTHREADFS_H \ No newline at end of file +#endif // PTHREADFS_H diff --git a/pthreadfs/src/js/library_asyncfs.js b/pthreadfs/src/js/library_asyncfs.js index 94c0dda85fff..e00ef32fbb93 100644 --- a/pthreadfs/src/js/library_asyncfs.js +++ b/pthreadfs/src/js/library_asyncfs.js @@ -1579,4 +1579,4 @@ } }, }, -}); \ No newline at end of file +}); diff --git a/pthreadfs/src/js/library_fsafs.js b/pthreadfs/src/js/library_fsafs.js index a531a2f97054..8a92058502e7 100644 --- a/pthreadfs/src/js/library_fsafs.js +++ b/pthreadfs/src/js/library_fsafs.js @@ -289,4 +289,4 @@ mergeInto(LibraryManager.library, { }, } } -}); \ No newline at end of file +}); diff --git a/pthreadfs/src/js/library_sfafs_async.js b/pthreadfs/src/js/library_sfafs_async.js index ab6306efa634..fa71a0454101 100644 --- a/pthreadfs/src/js/library_sfafs_async.js +++ b/pthreadfs/src/js/library_sfafs_async.js @@ -421,4 +421,4 @@ mergeInto(LibraryManager.library, { }, } } -}); \ No newline at end of file +}); diff --git a/pthreadfs/src/js/library_syscall_async.js b/pthreadfs/src/js/library_syscall_async.js index a3185572efb1..73603f1f64c2 100644 --- a/pthreadfs/src/js/library_syscall_async.js +++ b/pthreadfs/src/js/library_syscall_async.js @@ -932,4 +932,4 @@ for (var x in SyscallsLibrary) { wrapSyscallFunction(x, SyscallsLibrary, isWasi); } -mergeInto(LibraryManager.library, SyscallsLibrary); \ No newline at end of file +mergeInto(LibraryManager.library, SyscallsLibrary); diff --git a/pthreadfs/src/js/library_tty_async.js b/pthreadfs/src/js/library_tty_async.js index bdbd25145042..1f2636003924 100644 --- a/pthreadfs/src/js/library_tty_async.js +++ b/pthreadfs/src/js/library_tty_async.js @@ -157,4 +157,4 @@ } } } -}); \ No newline at end of file +}); diff --git a/pthreadfs/src/js/pthreadfs.js b/pthreadfs/src/js/pthreadfs.js index f730279e637c..4e7d994e5e9d 100644 --- a/pthreadfs/src/js/pthreadfs.js +++ b/pthreadfs/src/js/pthreadfs.js @@ -181,4 +181,4 @@ SyscallWrappers['init_sfafs'] = function(resume) { }); } -mergeInto(LibraryManager.library, SyscallWrappers); \ No newline at end of file +mergeInto(LibraryManager.library, SyscallWrappers); diff --git a/pthreadfs/src/pthreadfs.cpp b/pthreadfs/src/pthreadfs.cpp index 0b313b28b754..514c8540bc13 100644 --- a/pthreadfs/src/pthreadfs.cpp +++ b/pthreadfs/src/pthreadfs.cpp @@ -363,4 +363,4 @@ void emscripten_init_pthreadfs() { init_fsafs(&resumeWrapper_v); }); return; -} \ No newline at end of file +} diff --git a/pthreadfs/src/pthreadfs.h b/pthreadfs/src/pthreadfs.h index 04d0b57d5d65..f1220e644d27 100644 --- a/pthreadfs/src/pthreadfs.h +++ b/pthreadfs/src/pthreadfs.h @@ -262,4 +262,4 @@ void resumeWrapper_wasi(__wasi_errno_t retVal); void emscripten_init_pthreadfs(); -#endif // PTHREADFS_H \ No newline at end of file +#endif // PTHREADFS_H From 6009917c832cb2e155f2f5e19b181bdd4e8c5a14 Mon Sep 17 00:00:00 2001 From: Richard Stotz Date: Fri, 6 Aug 2021 19:32:21 +0200 Subject: [PATCH 3/3] Added Chrome version required --- pthreadfs/README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pthreadfs/README.md b/pthreadfs/README.md index ac5ecbbeb83f..e727f58280bd 100644 --- a/pthreadfs/README.md +++ b/pthreadfs/README.md @@ -9,7 +9,48 @@ The code is still prototype quality and **should not be used in a production env ## Enable and detect OPFS in Chrome -TODO +OPFS Access Handles require very recent versions of Google Chrome Canary. PThreadFS has been successfully tested with Version 94.0.4597.0. + +To enable the API, the " --enable-features=FileSystemAccessAccessHandle" flag must be set when starting Chrome from the console. On MacOS, this can be done through +``` +open -a /Applications/Google\ Chrome\ Canary.app --args --enable-features=FileSystemAccessAccessHandle +``` + +Support for AccesHandles in OPFS can be detected through +``` +async function detectAccessHandleWorker() { + const root = await navigator.storage.getDirectory(); + const file = await root.getFileHandle('access-handle-detect', { create: true }); + const present = file.createSyncAccessHandle != undefined; + await root.removeEntry('access-handle-detect'); + return present; +} + +async function detectAccessHandleMainThread() { + const detectAccessHandleAndPostMessage = async function(){ + const root = await navigator.storage.getDirectory(); + const file = await root.getFileHandle('access-handle-detect', { create: true }); + const present = file.createSyncAccessHandle != undefined; + await root.removeEntry('access-handle-detect'); + postMessage(present); + }; + + return new Promise((resolve, reject) => { + const detectBlob = new Blob(['('+detectAccessHandleAndPostMessage.toString()+')()'], {type: 'text/javascript'}) + const detectWorker = new Worker(window.URL.createObjectURL(detectBlob)); + + detectWorker.onmessage = result => { + resolve(result.data); + detectWorker.terminate(); + }; + + detectWorker.onerror = error => { + reject(error); + detectWorker.terminate(); + }; + }); +} +``` ## Getting the code