emscripten icon indicating copy to clipboard operation
emscripten copied to clipboard

WasmFS: Current directory ignores custom backend

Open mere-human opened this issue 2 years ago • 8 comments

getcwd()returns empty string for directories that use custom backends.

Version of emscripten/emsdk:

emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.25-git (8647224628dfc10dec6e5c6f3621d625a5f16eda)
clang version 16.0.0 (https://github.com/llvm/llvm-project 2234f582c91e8511443f3e71b6e06abe3e47db4b)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/user/src/emsdk/upstream/bin

Failing code:

#include <cassert>
#include <dirent.h>
#include <emscripten/wasmfs.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>

int main() {
  const char *dir_path = "/subdir";

#ifdef WASMFS
  const char *url = "http://localhost:8080";
  printf("url: %s\n", url);
  backend_t backend = wasmfs_create_fetch_backend(url);
  assert(backend);
  int res = wasmfs_create_directory(dir_path, 0777, backend);
  if (errno) perror("wasmfs_create_directory");
  assert(errno == 0);
  assert(res == 0);
#else
  int res = mkdir(dir_path, 0777);
  if (errno) perror("mkdir");
  assert(errno == 0);
  assert(res == 0);
#endif

  char buf[100];
  assert(getcwd(buf, sizeof(buf)));
  printf("cwd: %s\n", buf);

  printf("chdir: %s\n", dir_path);
  chdir(dir_path);

  assert(getcwd(buf, sizeof(buf)));
  printf("cwd: %s\n", buf);

  return 0;
}

Full link command and output with -v appended:

EXE=common
rm -rf build
mkdir -p build
FLAG_WASMFS="-DWASMFS -sWASMFS=1"
em++ -v -g $(echo $FLAG_WASMFS) -pthread -sPTHREAD_POOL_SIZE=2 -std=c++11 main.cpp -o build/$EXE.html
ret=$?
if [ $ret -eq 0 ]; then
    echo "build ok"
    emrun build/$EXE.html --browser chrome --port 8080
fi;

Build output:

Click to expand
"/Users/user/src/emsdk/upstream/bin/clang" --version
"/Users/user/src/emsdk/upstream/bin/clang++" -target wasm32-unknown-emscripten -fignore-exceptions -fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -D__EMSCRIPTEN_SHARED_MEMORY__=1 -DEMSCRIPTEN -I/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/SDL --sysroot=/Users/user/src/emsdk/upstream/emscripten/cache/sysroot -Xclang -iwithsysroot/include/compat -v -g3 -DWASMFS -pthread -std=c++11 -matomics -mbulk-memory main.cpp -c -o /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/emscripten_temp_83imqb2s/main_0.o
clang version 16.0.0 (https://github.com/llvm/llvm-project 2234f582c91e8511443f3e71b6e06abe3e47db4b)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/user/src/emsdk/upstream/bin
(in-process)
"/Users/user/src/emsdk/upstream/bin/clang-16" -cc1 -triple wasm32-unknown-emscripten -emit-obj -mrelax-all --mrelax-relocations -disable-free -clear-ast-before-backend -main-file-name main.cpp -mrelocation-model static -mframe-pointer=none -ffp-contract=on -fno-rounding-math -mconstructor-aliases -target-feature +atomics -target-feature +bulk-memory -target-feature +mutable-globals -target-feature +sign-ext -target-cpu generic -target-feature +atomics -target-feature +bulk-memory -mllvm -treat-scalable-fixed-error-as-warning -debug-info-kind=constructor -dwarf-version=4 -debugger-tuning=gdb -v -fcoverage-compilation-dir=/Users/user/src/emdemo/common -resource-dir /Users/user/src/emsdk/upstream/lib/clang/16.0.0 -D __EMSCRIPTEN_SHARED_MEMORY__=1 -D EMSCRIPTEN -I /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/SDL -D WASMFS -isysroot /Users/user/src/emsdk/upstream/emscripten/cache/sysroot -internal-isystem /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1 -internal-isystem /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1 -internal-isystem /Users/user/src/emsdk/upstream/lib/clang/16.0.0/include -internal-isystem /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten -internal-isystem /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include -std=c++11 -fdeprecated-macro -fdebug-compilation-dir=/Users/user/src/emdemo/common -ferror-limit 19 -fvisibility=default -pthread -fgnuc-version=4.2.1 -fcxx-exceptions -fignore-exceptions -fexceptions -fcolor-diagnostics -iwithsysroot/include/compat -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/emscripten_temp_83imqb2s/main_0.o -x c++ main.cpp
clang -cc1 version 16.0.0 based upon LLVM 16.0.0git default target x86_64-apple-darwin21.6.0
ignoring nonexistent directory "/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten/c++/v1"
ignoring nonexistent directory "/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/SDL
/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/compat
/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include/c++/v1
/Users/user/src/emsdk/upstream/lib/clang/16.0.0/include
/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/include
End of search list.
"/Users/user/src/emsdk/upstream/bin/wasm-ld" -o build/common.wasm /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/emscripten_temp_83imqb2s/main_0.o -L/Users/user/src/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten /Users/user/src/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/crtbegin.o --whole-archive -lwasmfs-mt-debug --no-whole-archive -lGL-mt -lal -lhtml5 -lstubs-debug -lnoexit -lc-mt-debug -ldlmalloc-mt -lcompiler_rt-mt -lc++-mt-noexcept -lc++abi-debug-mt-noexcept -lsockets-mt -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --import-undefined --import-memory --shared-memory --export-if-defined=main --export-if-defined=_emscripten_thread_init --export-if-defined=_emscripten_thread_exit --export-if-defined=_emscripten_thread_crashed --export-if-defined=_emscripten_tls_init --export-if-defined=pthread_self --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-if-defined=__main_argc_argv --export-if-defined=fflush --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_init --export=_wasmfs_read_file --export=stackSave --export=stackRestore --export=stackAlloc --export=__wasm_call_ctors --export=__errno_location --export=emscripten_dispatch_to_thread_ --export=_emscripten_thread_free_data --export=emscripten_main_browser_thread_id --export=emscripten_main_thread_process_queued_calls --export=emscripten_run_in_main_runtime_thread_js --export=emscripten_stack_set_limits --export=emscripten_proxy_finish --export=__get_temp_ret --export=__set_temp_ret --export=malloc --export=free --export-table -z stack-size=5242880 --initial-memory=16777216 --no-entry --max-memory=16777216 --global-base=1024
"/Users/user/src/emsdk/upstream/bin/wasm-emscripten-finalize" -g --dyncalls-i64 --pass-arg=legalize-js-interface-exported-helpers --dwarf build/common.wasm -o build/common.wasm --detect-features
"/Users/user/src/emsdk/node/14.18.2_64bit/bin/node" /Users/user/src/emsdk/upstream/emscripten/src/compiler.js /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/tmpwpzuw4xu.json
"/Users/user/src/emsdk/upstream/bin/llvm-objcopy" build/common.wasm build/common.wasm --remove-section=producers
"/Users/user/src/emsdk/node/14.18.2_64bit/bin/node" /Users/user/src/emsdk/upstream/emscripten/tools/preprocessor.js /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/emscripten_temp_83imqb2s/settings.js worker.js --expandMacros
"/Users/user/src/emsdk/node/14.18.2_64bit/bin/node" /Users/user/src/emsdk/upstream/emscripten/tools/preprocessor.js /var/folders/c_/szdjrjm12g5_9vh8ybcndhmm0000gp/T/emscripten_temp_83imqb2s/settings.js shell.html
build ok
objc[19348]: Class WebSwapCGLLayer is implemented in both /System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/Frameworks/libANGLE-shared.dylib (0x7ffb53be4ec8) and /Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/106.0.5249.119/Libraries/libGLESv2.dylib (0x116d8a668). One of the two will be used. Which one is undefined.

Program output:

url: http://localhost:8080
cwd: /
chdir: /subdir
cwd: /

Expected output: (when building without WASMFS):

cwd: /
chdir: /subdir
cwd: /subdir

mere-human avatar Oct 28 '22 16:10 mere-human

Details: When calling wasmfs_create_directory() it in turn calls mountChild() but not the insertDirectory(). https://github.com/emscripten-core/emscripten/blob/8647224628dfc10dec6e5c6f3621d625a5f16eda/system/lib/wasmfs/syscalls.cpp#L637 Which leads to a child entry being added to the dcache. https://github.com/emscripten-core/emscripten/blob/8647224628dfc10dec6e5c6f3621d625a5f16eda/system/lib/wasmfs/file.cpp#L49 Then this entry is not found via getcwd() because MemoryDirectory::maintainsFileIdentity() returns true which skips the dcache. https://github.com/emscripten-core/emscripten/blob/8647224628dfc10dec6e5c6f3621d625a5f16eda/system/lib/wasmfs/file.cpp#L215

mere-human avatar Oct 28 '22 16:10 mere-human

👋 I would like to kindly bring attention to this issue as it is blocking wasm-git to persist to opfs. I downloaded emscripten and was able to build it but I don't know what to do from there 😂😅

fredericrous avatar Apr 20 '24 17:04 fredericrous

Hmm, the reference to MemoryDirectory::maintainsFileIdentity() above suggests that this would only be an issue when using the case-insensitive virtual backend, i.e. wasmfs_create_icase_backend. @fredericrous, are you using that virtual backend?

tlively avatar Apr 22 '24 17:04 tlively

few months ago when I tried to test and I read emscripten code, I was under the impression that even with the correct compilation parameters, the opfs backend was never actually called. The virtual backend was always selected. @petersalomonsen would you mind helping with your reproduction workflow? 🙏

fredericrous avatar Apr 23 '24 16:04 fredericrous

few months ago when I tried to test and I read emscripten code, I was under the impression that even with the correct compilation parameters, the opfs backend was never actually called. The virtual backend was always selected. @petersalomonsen would you mind helping with your reproduction workflow? 🙏

I'm not sure if I'm using the API the correct way here. With the FS.mount, I'm not able to write the file, and if removing the FS.mount I'm able to write it, and list the directory contents, but not read the file.

I'm compiling with -sWASMFS and -lopfs.js.

FS.mkdir(`/test`);
FS.mount(lg.OPFS, {}, `/test`);
FS.writeFile('/test/test.txt', "Hello");
console.log(FS.readdir('/test'));
console.log(FS.readFile('/test'));

So not getting very basic things to work, but maybe I'm using it the wrong way?

petersalomonsen avatar Apr 24 '24 16:04 petersalomonsen

One problem is that you're passing a directory to the FS.readFile call there. Another is that you shouldn't need the lg. before OPFS, although perhaps you're putting the OPFS object there yourself.

This sample program works for me:

#include <emscripten/wasmfs.h>
#include <emscripten/emscripten.h>

int main() {
  EM_ASM({
      FS.mkdir('/test');
      FS.mount(OPFS, {}, '/test');
      FS.writeFile('/test/test.txt', "Hello");
      console.log(FS.readdir('/test'));
      console.log(new TextDecoder().decode(FS.readFile('/test/test.txt')));
    });
}

built with em++ test.cpp -o test.html -pthread -sWASMFS -lopfs.js -sFORCE_FILESYSTEM -sPROXY_TO_PTHREAD

tlively avatar Apr 24 '24 19:04 tlively

Thanks, and sorry, the readFile was my mistake. Is it so that threads (and then SharedArrayBuffer) are required for WASMFS to work?

petersalomonsen avatar Apr 25 '24 15:04 petersalomonsen

Yes, the OPFS backend in particular requires either threads or ASYNCIFY=2 (i.e. JSPI, which is currently under an origin trial in Chrome) because it needs to provide a synchronous interface to the underlying asynchronous APIs for opening files and doing other operations.

tlively avatar Apr 25 '24 16:04 tlively