emscripten icon indicating copy to clipboard operation
emscripten copied to clipboard

WASMFS does not handle symlinks???

Open rickg-hcl opened this issue 7 months ago • 2 comments

Please include the following in your bug report:

Version of emscripten/emsdk: emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.6 (1ddaae4d2d6dfbb678ecc193bc988820d1fc4633) clang version 21.0.0git (https:/github.com/llvm/llvm-project 4775e6d9099467df9363e1a3cd5950cc3d2fde05) Target: wasm32-unknown-emscripten Thread model: posix

Failing command line in full:

Full link command and output with -v appended:

emcc -g -s WASMFS -pthread -s PROXY_TO_PTHREAD=1 -s EXPORTED_FUNCTIONS="_malloc,_free" -v wasmfs_links.c -o test.html
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin/clang -target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot -D__EMSCRIPTEN_SHARED_MEMORY__=1 -DEMSCRIPTEN -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat -g3 -pthread -v -c wasmfs_links.c -o /var/folders/d8/08bv2b6s2b50q04__ln3cmbh0000gp/T/emscripten_temp_61hwxnjn/wasmfs_links_0.o
clang version 21.0.0git (https:/github.com/llvm/llvm-project 4775e6d9099467df9363e1a3cd5950cc3d2fde05)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin
 (in-process)
 "/Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin/clang-21" -cc1 -triple wasm32-unknown-emscripten -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name wasmfs_links.c -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 -fvisibility=hidden -debug-info-kind=constructor -dwarf-version=4 -debugger-tuning=gdb -fdebug-compilation-dir=/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/test -v -fcoverage-compilation-dir=/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/test -resource-dir /Users/richardgillaspy/GitHubOpen/emsdk/upstream/lib/clang/21 -D __EMSCRIPTEN_SHARED_MEMORY__=1 -D EMSCRIPTEN -isysroot /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot -internal-isystem /Users/richardgillaspy/GitHubOpen/emsdk/upstream/lib/clang/21/include -internal-isystem /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten -internal-isystem /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include -ferror-limit 19 -pthread -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fignore-exceptions -fcolor-diagnostics -iwithsysroot/include/fakesdl -iwithsysroot/include/compat -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr -o /var/folders/d8/08bv2b6s2b50q04__ln3cmbh0000gp/T/emscripten_temp_61hwxnjn/wasmfs_links_0.o -x c wasmfs_links.c
clang -cc1 version 21.0.0git based upon LLVM 21.0.0git default target x86_64-apple-darwin23.6.0
ignoring nonexistent directory "/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include/wasm32-emscripten"
#include "..." search starts here:
#include <...> search starts here:
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include/fakesdl
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include/compat
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/lib/clang/21/include
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/include
End of search list.
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin/clang --version
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin/wasm-ld -o test.wasm /var/folders/d8/08bv2b6s2b50q04__ln3cmbh0000gp/T/emscripten_temp_61hwxnjn/wasmfs_links_0.o -L/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten -L/Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/src/lib /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/crtbegin.o /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/crt1_proxy_main.o -lGL-mt-getprocaddr -lal -lhtml5 -lstubs-debug -lnoexit -lc-mt-debug -ldlmalloc-mt-debug -lcompiler_rt-mt -lc++-mt-noexcept -lc++abi-debug-mt-noexcept -lsockets-mt -lwasmfs_no_fs -lwasmfs-mt-debug -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr /var/folders/d8/08bv2b6s2b50q04__ln3cmbh0000gp/T/tmpg0r0_o6vlibemscripten_js_symbols.so --import-memory --shared-memory --export=malloc --export=free --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=wasmfs_flush --export=_emscripten_stack_alloc --export=_emscripten_thread_free_data --export=_emscripten_thread_crashed --export=__wasm_call_ctors --export=_emscripten_tls_init --export=_emscripten_thread_init --export=_emscripten_stack_restore --export=emscripten_stack_set_limits --export=_emscripten_thread_exit --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 --export-if-defined=__main_argc_argv --export-if-defined=fflush --export-table -z stack-size=65536 --no-growable-memory --initial-memory=16777216 --entry=_emscripten_proxy_main --stack-first --table-base=1
 /Users/richardgillaspy/GitHubOpen/emsdk/upstream/bin/llvm-objcopy test.wasm test.wasm --remove-section=producers
emcc: warning: `main` is defined in the input files, but `_main` is not in `EXPORTED_FUNCTIONS`. Add it to this list if you want `main` to run. [-Wunused-main]
 /Users/richardgillaspy/GitHubOpen/emsdk/node/20.18.0_64bit/bin/node /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/tools/compiler.mjs -
 /Users/richardgillaspy/GitHubOpen/emsdk/node/20.18.0_64bit/bin/node /Users/richardgillaspy/GitHubOpen/emsdk/upstream/emscripten/tools/preprocessor.mjs - shell.html

I just modified the unistd/link.c test to use WASMFS and it fails.

/*
 * 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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <emscripten/wasmfs.h>
#include <emscripten/console.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

void makedir(const char *dir) {
  int rtn = mkdir(dir, 0777);
  assert(rtn == 0);
}

void makefile(const char *file, const char *content) {
  int fd = open(file, O_RDWR | O_CREAT, 0777);
  assert(fd >= 0);
  int rtn = write(fd, content, strlen(content));
  assert(rtn == strlen(content));
  close(fd);
}

void makelink(const char *link, const char *path) {
  int rtn = symlink(link, path);
  assert(rtn == 0);
}

void changedir(const char *dir) {
  int rtn = chdir(dir);
  assert(rtn == 0);
}

void setup() {
//  makedir("/working");
#if defined(__EMSCRIPTEN__) && defined(NODEFS)
  EM_ASM(FS.mount(NODEFS, { root: '.' }, '/working'));
#endif
  changedir("/working");
  makelink("../test/../there!", "link");
  makefile("file", "test");
  makedir("directory");
  makedir("directory/subdirectory");
  makefile("directory/subdirectory/file", "Subdirectory");

  makedir("relative");
  makedir("relative/subrelative");
  makefile("relative/file", "Relative");
  makefile("relative/subrelative/file", "Subrelative");
  makelink("../relative/file", "directory/relative");
  makelink("../../relative/subrelative/file", "directory/subdirectory/subrelative");
  makelink("directory/subdirectory/file", "subdirectoryrelative");

  makedir("absolute");
  makedir("absolute/subabsolute");
  makefile("absolute/file", "Absolute");
  makefile("absolute/subabsolute/file", "Subabsolute");
  makelink("/working/absolute/file", "/working/directory/absolute");
  makelink("/working/absolute/subabsolute/file", "/working/directory/subdirectory/subabsolute");
  makelink("/working/directory/subdirectory/file", "/working/subdirectoryabsolute");
}

void test_reading_existing_symlinks() {
  char* files[] = {"link", "file", "directory"};

  for (int i = 0; i < sizeof files / sizeof files[0]; i++) {
    char buffer[256] = {0};
    int rtn = readlink(files[i], buffer, 256);
    printf("readlink: '%s'\n", files[i]);
    printf("errno: %s\n\n", strerror(errno));
    if (rtn < 0) {
      continue;
    }

    assert(strcmp(buffer, "../test/../there!") == 0);
    assert(strlen(buffer) == rtn);
    errno = 0;
  }
}

void test_overwriting_symlink() {
  int rtn = symlink("new-nonexistent-path", "link");
  assert(rtn == -1);
  assert(errno == EEXIST);
  errno = 0;
}

void test_creating_symlink() {
  int rtn = symlink("new-nonexistent-path", "directory/link");
  assert(rtn == 0);
  assert(errno == 0);
  errno = 0;

  char buffer[256] = {0};
  rtn = readlink("directory/link", buffer, 256);
  assert(errno == 0);
  assert(strcmp(buffer, "new-nonexistent-path") == 0);
  assert(strlen(buffer) == rtn);
  errno = 0;
}

void test_reading_shortened_symlink() {
  char buffer[256] = {0};
  readlink("directory/link", buffer, 256);
  buffer[0] = buffer[1] = buffer[2] = buffer[3] = buffer[4] = buffer[5] = '*';
  int rtn = readlink("link", buffer, 4);
  assert(errno == 0);
  assert(rtn == 4);
  assert(strcmp(buffer, "../t**nexistent-path") == 0);
  errno = 0;
}

void test_noticing_loop_in_symlink() {
  // FS.lookupPath should notice the symlink loop and return ELOOP, not go into
  // an infinite recurse.
  //
  // This test doesn't work in wasmfs -- probably because access sees the
  // symlink and returns true without bothering to chase the symlink
  symlink("./loop-link/inside", "./loop-link");
  int rtn = access("loop-link", F_OK);
  assert(rtn == -1);
  assert(errno == ELOOP);
  errno = 0;
}


void test_relative_path_symlinks() {
  char* parents[] = {
    "/working/directory/",
    "/working/directory/subdirectory/",
    "/working/"
  };

  char* links[] = {
    "relative",
    "subrelative",
    "subdirectoryrelative",
  };

  for (int i = 0; i < sizeof links / sizeof links[0]; i++) {
    int rtn = chdir(parents[i]);
    assert(rtn == 0);
    char symlink[256] = {0};
    strcat(strcpy(symlink, parents[i]), links[i]);
    printf("symlink: '%s'\n", symlink);
    char buf[256] = {0};
    rtn = readlink(links[i], buf, 256);
    FILE *fd = fopen(buf, "r");
    assert(fd);
    char buffer[13] = {0};
    rtn = fread(buffer, 1, 13, fd);
    assert(rtn <= 13);
    printf("buffer: '%s'\n\n", buffer);
    fclose(fd);
  }
}

void test_absolute_path_symlinks() {
  char* links[] = {
    "/working/directory/absolute",
    "/working/directory/subdirectory/subabsolute",
    "/working/subdirectoryabsolute"
  };

  for (int i = 0; i < sizeof links / sizeof links[0]; i++) {
    printf("symlink: '%s'\n", links[i]);
    char buf[256] = {0};
    readlink(links[i], buf, 256);
    FILE *fd = fopen(buf, "r");
    assert(fd);
    char buffer[13] = {0};
    int rtn = fread(buffer, 1, 13, fd);
    assert(rtn <= 13);
    printf("buffer: '%s'\n\n", buffer);
    fclose(fd);
  }
}

int main() {
  backend_t opfs = wasmfs_create_opfs_backend();
  emscripten_console_log("created OPFS backend");

  int err = wasmfs_create_directory("/working", 0777, opfs);
  assert(err == 0);
  emscripten_console_log("mounted OPFS root directory");

  setup();
  test_reading_existing_symlinks();
  test_overwriting_symlink();
  test_creating_symlink();
  test_reading_shortened_symlink();
  test_noticing_loop_in_symlink();
  test_relative_path_symlinks();
  test_absolute_path_symlinks();
  return 0;
}

rickg-hcl avatar Apr 16 '25 15:04 rickg-hcl

You can run test/unistd/links.c under wasmfs by running wasmfs.test_unistd_links, and this test does pass.

However I suppose what doesn't work is symbolic links under OPFS? Is this expected behavior @tlively ?

sbc100 avatar Apr 16 '25 16:04 sbc100

Yes, this is expected behavior because the underlying API does not support any kind of symlink.

tlively avatar Apr 18 '25 20:04 tlively