brintos

brintos / linux public Read only

0
0

wasm32 kernel: rename/renameat2, link, symlink, fsync all return ENOSYS — no atomic-rename, hardlinks, symlinks, or fsync #5

Closed vlad opened this issue · 2 comments
vlad vlad commented

The wasm32 (browser) kernel returns ENOSYS for the filesystem-mutation syscalls rename/renameat/renameat2, link/linkat, symlink, and legacy unlink, while unlinkat (and open/write/mkdir/read) work. Net effect: the machine has no atomic rename, no hardlinks, no symlinks, and no fsync.

Reproduction

A standalone C probe (compiled with wasmcc-glibc, dynamic-main) run in the live linux-6.12-glibc-bash-coreutils machine:

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
int main(void){
  int fd=open("/tmp/a",O_CREAT|O_WRONLY,0644); write(fd,"x",1);
  printf("fsync     %d\n", fsync(fd)); close(fd);
  printf("rename    %d\n", rename("/tmp/a","/tmp/b"));
  printf("renameat  %d\n", renameat(AT_FDCWD,"/tmp/b",AT_FDCWD,"/tmp/c"));
  printf("renameat2 %d\n", renameat2(AT_FDCWD,"/tmp/c",AT_FDCWD,"/tmp/d",0));
  printf("link      %d\n", link("/tmp/d","/tmp/e"));
  printf("linkat    %d\n", linkat(AT_FDCWD,"/tmp/d",AT_FDCWD,"/tmp/f",0));
  printf("symlink   %d\n", symlink("/tmp/d","/tmp/sl"));
  printf("unlink    %d\n", unlink("/tmp/zzz"));
  printf("unlinkat  %d\n", unlinkat(AT_FDCWD,"/tmp/zzz2",0));
  return 0;
}

Observed (errno 38 = ENOSYS "Function not implemented"):

fsync     -1  ENOSYS
rename    -1  ENOSYS
renameat  -1  ENOSYS
renameat2 -1  ENOSYS
link      -1  ENOSYS
linkat    -1  ENOSYS
symlink   -1  ENOSYS
unlink    -1  ENOSYS
unlinkat  -1  ENOENT   <- works (correct: path does not exist)

Impact

Any program using the standard "write to a temp file, then rename() over the target" atomic-update idiom cannot commit. This surfaced porting dpkg: its whole install model is write-.dpkg-new-then-rename() for the status database and for every unpacked file, so rename() ENOSYS aborts dpkg -i/-r at commit. mv appears to work only because gnulib silently falls back to copy-then-delete when rename fails — it is not exercising rename at all. Editors, databases, compilers writing output, and package managers are all affected.

Likely layer

ENOSYS at the syscall boundary points to the wasm32 kernel syscall table (sys_ni_syscall) rather than the virtiofs/FUSE backend (which would more likely return EOPNOTSUPP/EPERM). Worth triaging kernel (arch/wasm syscall wiring) vs the hardwarejs FS backend.

Suggested direction

Wire up (in priority order): renameat2 (load-bearing — unblocks all atomic writers), link/linkat, symlink/symlinkat, and make fsync a success no-op (return 0) on the browser-backed FS rather than ENOSYS. Also worth routing the legacy unlink/rmdir to their *at forms (which work).

Filed while porting dpkg (BRINTOS-31); happy to re-run the probe or test a fix.

vlad vlad commented

Follow-up — the gap is broader than the first probe showed. Extending the probe to directory / metadata syscalls (same live glibc machine):

mkdir      ENOSYS      chown      ENOSYS
mkdirat    ENOSYS      fchownat   ENOSYS
chmod      ENOSYS      mknod      ENOSYS
fchmodat   ENOSYS      utimensat  works (ENOENT on a nonexistent path)

So mkdir/mkdirat (both), chmod/fchmodat, chown/fchownat, and mknod are ALSO ENOSYS. Combined with the first probe, the browser FS effectively supports only create / write / read / unlink a file inside an already-existing directory — at runtime there is no way to create a directory, set a file's mode/owner, make a node, rename, or link.

Impact: this blocks not just atomic writers but any installer/unpacker — a package manager cannot create the directory tree its files live in or set their permissions. dpkg gets as far as reading the archive and its database, then fails at dpkg-deb's "create directory" step (mkdir ENOSYS); install/remove cannot work on the machine until the VFS mutation syscalls are wired.

Priority to unblock a package manager: mkdirat + chmod/chown (file install) alongside renameat2 (atomic commit). Happy to re-run the probe against a fix.

jdbrinton jdbrinton commented

Fixed on joel-dev — two layers, both landed and pushed tonight:

Kernel 790070a9c31e (arch/wasm32/kernel/syscall_table.c): wired the remaining NULL entries — link/linkat (86/265), symlink/symlinkat (88/266), fchmod (91), chown/fchown/lchown/fchownat (92/93/94/260), mknod/mknodat (133/259). Note: much of your original list (rename*/mkdir*/chmod/fsync/unlink) had already been wired on joel-dev by the #34/#71 lineage after you filed — the live machines run an older kernel, which is why your probe still showed ENOSYS.

hardwarejs 0d86fea: the worker syscall-routing table now routes those nrs (they previously died at the SAB-RPC fallback before vmlinux was consulted); the FUSE server gained FUSE_SYMLINK/FUSE_LINK/FUSE_MKNOD dispatch and now honors FATTR_UID/GID/MTIME in SETATTR (utimensat's mtime was previously a silent drop).

Semantics: link/symlink/mknod(S_IFREG) are real on virtio-fs (hardlinks share one guest inode via host dev:ino aliasing); mknod non-regular → EPERM (browser store can't host FIFOs/device nodes; real on tmpfs); chown-family is kernel-real with the documented accept-and-succeed no-op on virtio-fs uid/gid; fsync was already real via #71. Legacy utime(132)/utimes(235)/futimesat(261) deliberately not wired — both libcs convert to utimensat(280), which works. renameat2 flags≠0 on virtio-fs answers EINVAL (upstream fuse no_rename2 contract); flags=0 — the atomic-commit path dpkg needs — is real.

Verified with a 16-leg boot gate (all green on the rebuilt kernel, incl. your probe shape rc=0 on both stores) plus two flipping RED arms (pre-fix kernel reproduces your exact Function not implemented outputs; HWJS_RED_58_VFSMUT_UNROUTED=1 flips on final artifacts); all 8 existing virtio-fs suites (32 tests) still pass. Reaches the live machines at the next deploy — please re-run your probe then; reopen if anything still refuses. dpkg's mkdir/chmod/rename path should now be unblocked. — agent for joel, 2026-07-14

jdbrinton closed this as completed