| /** | |
| * @license | |
| * Copyright 2015 The Emscripten Authors | |
| * SPDX-License-Identifier: MIT | |
| */ | |
| var SyscallsLibrary = { | |
| $SYSCALLS__deps: [ | |
| #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM | |
| '$PATH', | |
| '$FS', | |
| #endif | |
| ], | |
| $SYSCALLS: { | |
| #if SYSCALLS_REQUIRE_FILESYSTEM | |
| currentUmask: 0o022, | |
| // global constants | |
| // shared utilities | |
| calculateAt(dirfd, path, allowEmpty = false) { | |
| if (PATH.isAbs(path)) { | |
| return path; | |
| } | |
| // relative path | |
| var dir; | |
| if (dirfd === {{{ cDefs.AT_FDCWD }}}) { | |
| dir = FS.cwd(); | |
| } else { | |
| var dirstream = SYSCALLS.getStreamFromFD(dirfd); | |
| dir = dirstream.path; | |
| } | |
| if (path.length == 0) { | |
| if (!allowEmpty) { | |
| throw new FS.ErrnoError({{{ cDefs.ENOENT }}});; | |
| } | |
| return dir; | |
| } | |
| return dir + '/' + path; | |
| }, | |
| writeStat(buf, stat) { | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_dev, 'stat.dev', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_mode, 'stat.mode', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_nlink, 'stat.nlink', SIZE_TYPE) }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_uid, 'stat.uid', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_gid, 'stat.gid', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_rdev, 'stat.rdev', 'u32') }}}; | |
| {{{ 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') }}}; | |
| var atime = stat.atime.getTime(); | |
| var mtime = stat.mtime.getTime(); | |
| var ctime = stat.ctime.getTime(); | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_sec, 'Math.floor(atime / 1000)', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_atim.tv_nsec, '(atime % 1000) * 1000 * 1000', SIZE_TYPE) }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_sec, 'Math.floor(mtime / 1000)', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_mtim.tv_nsec, '(mtime % 1000) * 1000 * 1000', SIZE_TYPE) }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_sec, 'Math.floor(ctime / 1000)', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_ctim.tv_nsec, '(ctime % 1000) * 1000 * 1000', SIZE_TYPE) }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.stat.st_ino, 'stat.ino', 'i64') }}}; | |
| return 0; | |
| }, | |
| writeStatFs(buf, stats) { | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bsize, 'stats.bsize', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_frsize, 'stats.bsize', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_blocks, 'stats.blocks', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bfree, 'stats.bfree', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_bavail, 'stats.bavail', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_files, 'stats.files', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_ffree, 'stats.ffree', 'i64') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_fsid, 'stats.fsid', 'u32') }}}; | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_flags, 'stats.flags', 'u32') }}}; // ST_NOSUID | |
| {{{ makeSetValue('buf', C_STRUCTS.statfs.f_namelen, 'stats.namelen', 'u32') }}}; | |
| }, | |
| doMsync(addr, stream, len, flags, offset) { | |
| if (!FS.isFile(stream.node.mode)) { | |
| throw new FS.ErrnoError({{{ cDefs.ENODEV }}}); | |
| } | |
| if (flags & {{{ cDefs.MAP_PRIVATE }}}) { | |
| // MAP_PRIVATE calls need not to be synced back to underlying fs | |
| return 0; | |
| } | |
| var buffer = HEAPU8.subarray(addr, addr + len); | |
| FS.msync(stream, buffer, offset, len, flags); | |
| }, | |
| // Just like `FS.getStream` but will throw EBADF if stream is undefined. | |
| getStreamFromFD(fd) { | |
| var stream = FS.getStreamChecked(fd); | |
| #if SYSCALL_DEBUG | |
| dbg(` (stream: "${stream.path}")`); | |
| #endif | |
| return stream; | |
| }, | |
| #endif // SYSCALLS_REQUIRE_FILESYSTEM | |
| varargs: undefined, | |
| getStr(ptr) { | |
| var ret = UTF8ToString(ptr); | |
| #if SYSCALL_DEBUG | |
| dbg(` (str: "${ret}")`); | |
| #endif | |
| return ret; | |
| }, | |
| }, | |
| $syscallGetVarargI__internal: true, | |
| $syscallGetVarargI: () => { | |
| #if ASSERTIONS | |
| assert(SYSCALLS.varargs != undefined); | |
| #endif | |
| // the `+` prepended here is necessary to convince the JSCompiler that varargs is indeed a number. | |
| var ret = {{{ makeGetValue('+SYSCALLS.varargs', 0, 'i32') }}}; | |
| SYSCALLS.varargs += 4; | |
| #if SYSCALL_DEBUG | |
| dbg(` (raw: "${ret}")`); | |
| #endif | |
| return ret; | |
| }, | |
| $syscallGetVarargP__internal: true, | |
| #if MEMORY64 | |
| $syscallGetVarargP: () => { | |
| #if ASSERTIONS | |
| assert(SYSCALLS.varargs != undefined); | |
| #endif | |
| var ret = {{{ makeGetValue('SYSCALLS.varargs', 0, '*') }}}; | |
| SYSCALLS.varargs += {{{ POINTER_SIZE }}}; | |
| #if SYSCALL_DEBUG | |
| dbg(` (raw: "${ret}")`); | |
| #endif | |
| return ret; | |
| }, | |
| #else | |
| $syscallGetVarargP: '$syscallGetVarargI', | |
| #endif | |
| _mmap_js__i53abi: true, | |
| _mmap_js__deps: ['$SYSCALLS', | |
| #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM | |
| '$FS', | |
| // The dependency of FS on `mmapAlloc` and `mmapAlloc` on | |
| // `emscripten_builtin_memalign` are not encoding as hard dependencies, | |
| // so we need to explicitly depend on them here to ensure a working | |
| // `FS.mmap`. | |
| // `emscripten_builtin_memalign`). | |
| '$mmapAlloc', | |
| 'emscripten_builtin_memalign', | |
| #endif | |
| ], | |
| _mmap_js: (len, prot, flags, fd, offset, allocated, addr) => { | |
| #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM | |
| #if ASSERTIONS | |
| // musl's mmap doesn't allow values over a certain limit | |
| // see OFF_MASK in mmap.c. | |
| assert(!isNaN(offset)); | |
| #endif | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| var res = FS.mmap(stream, len, offset, prot, flags); | |
| var ptr = res.ptr; | |
| {{{ makeSetValue('allocated', 0, 'res.allocated', 'i32') }}}; | |
| {{{ makeSetValue('addr', 0, 'ptr', '*') }}}; | |
| return 0; | |
| #else // no filesystem support; report lack of support | |
| return -{{{ cDefs.ENOSYS }}}; | |
| #endif | |
| }, | |
| _munmap_js__i53abi: true, | |
| _munmap_js: (addr, len, prot, flags, fd, offset) => { | |
| #if FILESYSTEM && SYSCALLS_REQUIRE_FILESYSTEM | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| if (prot & {{{ cDefs.PROT_WRITE }}}) { | |
| SYSCALLS.doMsync(addr, stream, len, flags, offset); | |
| } | |
| #endif | |
| }, | |
| __syscall_chdir: (path) => { | |
| path = SYSCALLS.getStr(path); | |
| FS.chdir(path); | |
| return 0; | |
| }, | |
| __syscall_chmod: (path, mode) => { | |
| path = SYSCALLS.getStr(path); | |
| FS.chmod(path, mode); | |
| return 0; | |
| }, | |
| __syscall_rmdir: (path) => { | |
| path = SYSCALLS.getStr(path); | |
| FS.rmdir(path); | |
| return 0; | |
| }, | |
| __syscall_dup: (fd) => { | |
| var old = SYSCALLS.getStreamFromFD(fd); | |
| return FS.dupStream(old).fd; | |
| }, | |
| __syscall_pipe2__deps: ['$PIPEFS'], | |
| __syscall_pipe2: (fdPtr, flags) => { | |
| if (fdPtr == 0) { | |
| throw new FS.ErrnoError({{{ cDefs.EFAULT }}}); | |
| } | |
| var validFlags = {{{ cDefs.O_CLOEXEC }}} | {{{ cDefs.O_NONBLOCK }}}; | |
| if (flags & ~validFlags) { | |
| throw new FS.ErrnoError({{{ cDefs.ENOTSUP }}}); | |
| } | |
| var res = PIPEFS.createPipe(); | |
| if (flags & {{{ cDefs.O_NONBLOCK }}}) { | |
| FS.getStream(res.readable_fd).flags |= {{{ cDefs.O_NONBLOCK }}}; | |
| FS.getStream(res.writable_fd).flags |= {{{ cDefs.O_NONBLOCK }}}; | |
| } | |
| {{{ makeSetValue('fdPtr', 0, 'res.readable_fd', 'i32') }}}; | |
| {{{ makeSetValue('fdPtr', 4, 'res.writable_fd', 'i32') }}}; | |
| return 0; | |
| }, | |
| #if SYSCALLS_REQUIRE_FILESYSTEM | |
| __syscall_ioctl__deps: ['$syscallGetVarargP'], | |
| #endif | |
| __syscall_ioctl: (fd, op, varargs) => { | |
| #if SYSCALLS_REQUIRE_FILESYSTEM == 0 | |
| #if SYSCALL_DEBUG | |
| dbg('no-op in ioctl syscall due to SYSCALLS_REQUIRE_FILESYSTEM=0'); | |
| #endif | |
| return 0; | |
| #else | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| switch (op) { | |
| case {{{ cDefs.TCGETA }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| #if SYSCALL_DEBUG | |
| dbg('warning: not filling tio struct'); | |
| #endif | |
| return 0; | |
| } | |
| case {{{ cDefs.TCGETS }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| if (stream.tty.ops.ioctl_tcgets) { | |
| var termios = stream.tty.ops.ioctl_tcgets(stream); | |
| var argp = syscallGetVarargP(); | |
| {{{ makeSetValue('argp', C_STRUCTS.termios.c_iflag, 'termios.c_iflag || 0', 'i32') }}}; | |
| {{{ makeSetValue('argp', C_STRUCTS.termios.c_oflag, 'termios.c_oflag || 0', 'i32') }}}; | |
| {{{ makeSetValue('argp', C_STRUCTS.termios.c_cflag, 'termios.c_cflag || 0', 'i32') }}}; | |
| {{{ makeSetValue('argp', C_STRUCTS.termios.c_lflag, 'termios.c_lflag || 0', 'i32') }}}; | |
| for (var i = 0; i < {{{ cDefs.NCCS }}}; i++) { | |
| {{{ makeSetValue('argp + i', C_STRUCTS.termios.c_cc, 'termios.c_cc[i] || 0', 'i8') }}}; | |
| } | |
| return 0; | |
| } | |
| #if SYSCALL_DEBUG | |
| dbg('warning: not filling tio struct'); | |
| #endif | |
| return 0; | |
| } | |
| case {{{ cDefs.TCSETA }}}: | |
| case {{{ cDefs.TCSETAW }}}: | |
| case {{{ cDefs.TCSETAF }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| return 0; // no-op, not actually adjusting terminal settings | |
| } | |
| case {{{ cDefs.TCSETS }}}: | |
| case {{{ cDefs.TCSETSW }}}: | |
| case {{{ cDefs.TCSETSF }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| if (stream.tty.ops.ioctl_tcsets) { | |
| var argp = syscallGetVarargP(); | |
| var c_iflag = {{{ makeGetValue('argp', C_STRUCTS.termios.c_iflag, 'i32') }}}; | |
| var c_oflag = {{{ makeGetValue('argp', C_STRUCTS.termios.c_oflag, 'i32') }}}; | |
| var c_cflag = {{{ makeGetValue('argp', C_STRUCTS.termios.c_cflag, 'i32') }}}; | |
| var c_lflag = {{{ makeGetValue('argp', C_STRUCTS.termios.c_lflag, 'i32') }}}; | |
| var c_cc = [] | |
| for (var i = 0; i < {{{ cDefs.NCCS }}}; i++) { | |
| c_cc.push({{{ makeGetValue('argp + i', C_STRUCTS.termios.c_cc, 'i8') }}}); | |
| } | |
| return stream.tty.ops.ioctl_tcsets(stream.tty, op, { c_iflag, c_oflag, c_cflag, c_lflag, c_cc }); | |
| } | |
| return 0; // no-op, not actually adjusting terminal settings | |
| } | |
| case {{{ cDefs.TIOCGPGRP }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| var argp = syscallGetVarargP(); | |
| {{{ makeSetValue('argp', 0, 0, 'i32') }}}; | |
| return 0; | |
| } | |
| case {{{ cDefs.TIOCSPGRP }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| return -{{{ cDefs.EINVAL }}}; // not supported | |
| } | |
| case {{{ cDefs.FIONBIO }}}: | |
| case {{{ cDefs.FIONREAD }}}: { | |
| var argp = syscallGetVarargP(); | |
| return FS.ioctl(stream, op, argp); | |
| } | |
| case {{{ cDefs.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 -{{{ cDefs.ENOTTY }}}; | |
| if (stream.tty.ops.ioctl_tiocgwinsz) { | |
| var winsize = stream.tty.ops.ioctl_tiocgwinsz(stream.tty); | |
| var argp = syscallGetVarargP(); | |
| {{{ makeSetValue('argp', 0, 'winsize[0]', 'i16') }}}; | |
| {{{ makeSetValue('argp', 2, 'winsize[1]', 'i16') }}}; | |
| } | |
| return 0; | |
| } | |
| case {{{ cDefs.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 -{{{ cDefs.ENOTTY }}}; | |
| return 0; | |
| } | |
| case {{{ cDefs.TCFLSH }}}: { | |
| if (!stream.tty) return -{{{ cDefs.ENOTTY }}}; | |
| return 0; | |
| } | |
| default: return -{{{ cDefs.EINVAL }}}; // not supported | |
| } | |
| #endif // SYSCALLS_REQUIRE_FILESYSTEM | |
| }, | |
| __syscall_fchmod: (fd, mode) => { | |
| FS.fchmod(fd, mode); | |
| return 0; | |
| }, | |
| // When building with PROXY_POSIX_SOCKETS the socket syscalls are implemented | |
| // natively in libsockets.a. | |
| // When building with WASMFS the socket syscalls are implemented natively in | |
| // libwasmfs.a. | |
| #if PROXY_POSIX_SOCKETS == 0 && WASMFS == 0 | |
| $getSocketFromFD__deps: ['$SOCKFS', '$FS'], | |
| $getSocketFromFD: (fd) => { | |
| var socket = SOCKFS.getSocket(fd); | |
| if (!socket) throw new FS.ErrnoError({{{ cDefs.EBADF }}}); | |
| #if SYSCALL_DEBUG | |
| dbg(` (socket: "${socket.path}")`); | |
| #endif | |
| return socket; | |
| }, | |
| $getSocketAddress__deps: ['$readSockaddr', '$FS', | |
| #if !NODERAWSOCKETS | |
| '$DNS', | |
| #endif | |
| ], | |
| $getSocketAddress: (addrp, addrlen) => { | |
| var info = readSockaddr(addrp, addrlen); | |
| if (info.errno) throw new FS.ErrnoError(info.errno); | |
| #if !NODERAWSOCKETS | |
| info.addr = DNS.lookup_addr(info.addr) || info.addr; | |
| #endif | |
| #if SYSCALL_DEBUG | |
| dbg(` (socketaddress: "${[info.addr, info.port]}")`); | |
| #endif | |
| return info; | |
| }, | |
| __syscall_socket__deps: ['$SOCKFS'], | |
| __syscall_socket: (domain, type, protocol, u1, u2, u3) => { | |
| var sock = SOCKFS.createSocket(domain, type, protocol); | |
| return sock.stream.fd; | |
| }, | |
| __syscall_getsockname__deps: ['$getSocketFromFD', '$writeSockaddr'], | |
| __syscall_getsockname: (fd, addr, len, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| #if NODERAWSOCKETS | |
| // An unbound AF_UNIX socket is unnamed (family-only address), not a | |
| // wildcard IP. | |
| var defaultAddr = sock.family == {{{ cDefs.AF_UNIX }}} ? '' : '0.0.0.0'; | |
| #else | |
| var defaultAddr = '0.0.0.0'; | |
| #endif | |
| // TODO: sock.saddr should never be undefined, see TODO in websocket_sock_ops.getname | |
| var errno = writeSockaddr(addr, sock.family, sock.saddr || defaultAddr, sock.sport, len); | |
| #if ASSERTIONS | |
| assert(!errno); | |
| #endif | |
| return 0; | |
| }, | |
| __syscall_getpeername__deps: ['$getSocketFromFD', '$writeSockaddr'], | |
| __syscall_getpeername: (fd, addr, len, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| #if NODERAWSOCKETS | |
| // '' is a valid connected-but-unnamed AF_UNIX peer (e.g. an accepted | |
| // connection's client), so only undefined means not connected there. | |
| if (sock.family == {{{ cDefs.AF_UNIX }}} ? sock.daddr === undefined : !sock.daddr) { | |
| #else | |
| if (!sock.daddr) { | |
| #endif | |
| return -{{{ cDefs.ENOTCONN }}}; // The socket is not connected. | |
| } | |
| var errno = writeSockaddr(addr, sock.family, sock.daddr, sock.dport, len); | |
| #if ASSERTIONS | |
| assert(!errno); | |
| #endif | |
| return 0; | |
| }, | |
| __syscall_connect__deps: ['$getSocketFromFD', '$getSocketAddress'], | |
| __syscall_connect: (fd, addr, len, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| var info = getSocketAddress(addr, len); | |
| sock.sock_ops.connect(sock, info.addr, info.port); | |
| return 0; | |
| }, | |
| __syscall_shutdown__deps: ['$getSocketFromFD'], | |
| __syscall_shutdown: (fd, how, u1, u2, u3, u4) => { | |
| var sock = getSocketFromFD(fd); | |
| #if NODERAWSOCKETS | |
| return sock.sock_ops.shutdown(sock, how); | |
| #else | |
| return -{{{ cDefs.ENOSYS }}}; // unsupported feature | |
| #endif | |
| }, | |
| __syscall_accept4__deps: ['$getSocketFromFD', '$writeSockaddr'], | |
| __syscall_accept4: (fd, addr, len, flags, u1, u2) => { | |
| var sock = getSocketFromFD(fd); | |
| var newsock = sock.sock_ops.accept(sock); | |
| #if NODERAWSOCKETS | |
| // Linux: the accepted fd's status flags come only from `flags`, never from | |
| // the listener. | |
| if (flags & {{{ cDefs.SOCK_NONBLOCK }}}) newsock.stream.flags |= {{{ cDefs.O_NONBLOCK }}}; | |
| #endif | |
| if (addr) { | |
| var errno = writeSockaddr(addr, newsock.family, newsock.daddr, newsock.dport, len); | |
| #if ASSERTIONS | |
| assert(!errno); | |
| #endif | |
| } | |
| return newsock.stream.fd; | |
| }, | |
| __syscall_bind__deps: ['$getSocketFromFD', '$getSocketAddress'], | |
| __syscall_bind: (fd, addr, len, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| var info = getSocketAddress(addr, len); | |
| sock.sock_ops.bind(sock, info.addr, info.port); | |
| return 0; | |
| }, | |
| __syscall_listen__deps: ['$getSocketFromFD'], | |
| __syscall_listen: (fd, backlog, u1, u2, u3, u4) => { | |
| var sock = getSocketFromFD(fd); | |
| sock.sock_ops.listen(sock, backlog); | |
| return 0; | |
| }, | |
| __syscall_recvfrom__deps: ['$getSocketFromFD', '$writeSockaddr'], | |
| __syscall_recvfrom: (fd, buf, len, flags, addr, alen) => { | |
| var sock = getSocketFromFD(fd); | |
| var msg = sock.sock_ops.recvmsg(sock, len, flags); | |
| if (!msg) return 0; // socket is closed | |
| if (addr) { | |
| var errno = writeSockaddr(addr, sock.family, msg.addr, msg.port, alen); | |
| #if ASSERTIONS | |
| assert(!errno); | |
| #endif | |
| } | |
| HEAPU8.set(msg.buffer, buf); | |
| return msg.buffer.byteLength; | |
| }, | |
| __syscall_sendto__deps: ['$getSocketFromFD', '$getSocketAddress'], | |
| __syscall_sendto: (fd, buf, len, flags, addr, alen) => { | |
| var sock = getSocketFromFD(fd); | |
| if (!addr) { | |
| // send, no address provided | |
| return FS.write(sock.stream, HEAP8, buf, len); | |
| } | |
| var dest = getSocketAddress(addr, alen); | |
| // sendto an address | |
| return sock.sock_ops.sendmsg(sock, HEAP8, buf, len, dest.addr, dest.port); | |
| }, | |
| __syscall_getsockopt__deps: ['$getSocketFromFD'], | |
| __syscall_getsockopt: (fd, level, optname, optval, optlen, unused) => { | |
| var sock = getSocketFromFD(fd); | |
| #if NODERAWSOCKETS | |
| // The node:net backend handles all socket options. | |
| return sock.sock_ops.getsockopt(sock, level, optname, optval, optlen); | |
| #else | |
| // Minimal getsockopt aimed at resolving https://github.com/emscripten-core/emscripten/issues/2211 | |
| // so only supports SOL_SOCKET with SO_ERROR. | |
| if (level === {{{ cDefs.SOL_SOCKET }}}) { | |
| if (optname === {{{ cDefs.SO_ERROR }}}) { | |
| {{{ makeSetValue('optval', 0, 'sock.error', 'i32') }}}; | |
| {{{ makeSetValue('optlen', 0, 4, 'i32') }}}; | |
| sock.error = null; // Clear the error (The SO_ERROR option obtains and then clears this field). | |
| return 0; | |
| } | |
| } | |
| return -{{{ cDefs.ENOPROTOOPT }}}; // The option is unknown at the level indicated. | |
| #endif | |
| }, | |
| // Defined in JS rather than as a weak native stub so the node:net backend can | |
| // provide it without a separate libstubs variation. Without that backend it | |
| // just reports the option as unknown. | |
| __syscall_setsockopt__deps: ['$getSocketFromFD'], | |
| __syscall_setsockopt: (fd, level, optname, optval, optlen, unused) => { | |
| #if NODERAWSOCKETS | |
| var sock = getSocketFromFD(fd); | |
| return sock.sock_ops.setsockopt(sock, level, optname, optval, optlen); | |
| #else | |
| getSocketFromFD(fd); // validate the fd (and keep this syscall's catch reachable) | |
| return -{{{ cDefs.ENOPROTOOPT }}}; // The option is unknown at the level indicated. | |
| #endif | |
| }, | |
| __syscall_sendmsg__deps: ['$getSocketFromFD', '$getSocketAddress'], | |
| __syscall_sendmsg: (fd, message, flags, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| var iov = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iov, '*') }}}; | |
| var num = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iovlen, 'i32') }}}; | |
| // read the address and port to send to | |
| var addr, port; | |
| var name = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_name, '*') }}}; | |
| var namelen = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_namelen, 'i32') }}}; | |
| if (name) { | |
| var info = getSocketAddress(name, namelen); | |
| port = info.port; | |
| addr = info.addr; | |
| } | |
| // concatenate scatter-gather arrays into one message buffer | |
| var total = 0; | |
| for (var i = 0; i < num; i++) { | |
| total += {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_len}`, 'i32') }}}; | |
| } | |
| var view = new Uint8Array(total); | |
| var offset = 0; | |
| for (var i = 0; i < num; i++) { | |
| var iovbase = {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_base}`, '*') }}}; | |
| var iovlen = {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_len}`, 'i32') }}}; | |
| for (var j = 0; j < iovlen; j++) { | |
| view[offset++] = {{{ makeGetValue('iovbase', 'j', 'i8') }}}; | |
| } | |
| } | |
| // write the buffer | |
| return sock.sock_ops.sendmsg(sock, view, 0, total, addr, port); | |
| }, | |
| __syscall_recvmsg__deps: ['$getSocketFromFD', '$writeSockaddr'], | |
| __syscall_recvmsg: (fd, message, flags, u1, u2, u3) => { | |
| var sock = getSocketFromFD(fd); | |
| var iov = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iov, '*') }}}; | |
| var num = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_iovlen, 'i32') }}}; | |
| // get the total amount of data we can read across all arrays | |
| var total = 0; | |
| for (var i = 0; i < num; i++) { | |
| total += {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_len}`, 'i32') }}}; | |
| } | |
| // try to read total data (MSG_PEEK, when set, leaves it buffered) | |
| var msg = sock.sock_ops.recvmsg(sock, total, flags); | |
| if (!msg) return 0; // socket is closed | |
| // TODO honor flags: | |
| // MSG_OOB | |
| // Requests out-of-band data. The significance and semantics of out-of-band data are protocol-specific. | |
| // MSG_WAITALL | |
| // Requests that the function block until the full amount of data requested can be returned. The function may return a smaller amount of data if a signal is caught, if the connection is terminated, if MSG_PEEK was specified, or if an error is pending for the socket. | |
| // write the source address out | |
| var name = {{{ makeGetValue('message', C_STRUCTS.msghdr.msg_name, '*') }}}; | |
| if (name) { | |
| var namelen = message + {{{ C_STRUCTS.msghdr.msg_namelen }}}; | |
| var errno = writeSockaddr(name, sock.family, msg.addr, msg.port, namelen); | |
| #if ASSERTIONS | |
| assert(!errno); | |
| #endif | |
| } | |
| // write the buffer out to the scatter-gather arrays | |
| var bytesRead = 0; | |
| var bytesRemaining = msg.buffer.byteLength; | |
| for (var i = 0; bytesRemaining > 0 && i < num; i++) { | |
| var iovbase = {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_base}`, '*') }}}; | |
| var iovlen = {{{ makeGetValue('iov', `(${C_STRUCTS.iovec.__size__} * i) + ${C_STRUCTS.iovec.iov_len}`, 'i32') }}}; | |
| if (!iovlen) { | |
| continue; | |
| } | |
| var length = Math.min(iovlen, bytesRemaining); | |
| var buf = msg.buffer.subarray(bytesRead, bytesRead + length); | |
| HEAPU8.set(buf, iovbase); | |
| bytesRead += length; | |
| bytesRemaining -= length; | |
| } | |
| {{{ makeSetValue('message', C_STRUCTS.msghdr.msg_controllen, '0', 'i32') }}}; | |
| {{{ makeSetValue('message', C_STRUCTS.msghdr.msg_flags, '0', 'i32') }}}; | |
| // TODO report truncation in msghdr.msg_flags | |
| // MSG_EOR | |
| // End of record was received (if supported by the protocol). | |
| // MSG_OOB | |
| // Out-of-band data was received. | |
| // MSG_TRUNC | |
| // Normal data was truncated. | |
| // MSG_CTRUNC | |
| return bytesRead; | |
| }, | |
| #endif // PROXY_POSIX_SOCKETS == 0 && WASMFS == 0 | |
| __syscall_fchdir: (fd) => { | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| FS.chdir(stream.path); | |
| return 0; | |
| }, | |
| _msync_js__i53abi: true, | |
| _msync_js: (addr, len, prot, flags, fd, offset) => { | |
| if (isNaN(offset)) return -{{{ cDefs.EFBIG }}}; | |
| SYSCALLS.doMsync(addr, SYSCALLS.getStreamFromFD(fd), len, flags, offset); | |
| return 0; | |
| }, | |
| __syscall_fdatasync: (fd) => { | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| return 0; // we can't do anything synchronously; the in-memory FS is already synced to | |
| }, | |
| // Derive readiness for one fd against its requested `events`: POLLNVAL for a | |
| // closed/bad fd, default readable+writable for types without a poll handler. | |
| // POLLERR/POLLHUP/POLLNVAL are output-only conditions reported regardless of | |
| // `events` (a bad fd reports POLLNVAL even if the caller didn't ask for it). | |
| $pollOne__internal: true, | |
| $pollOne__deps: ['$FS'], | |
| $pollOne: (fd, events) => { | |
| var stream = FS.getStream(fd); | |
| if (!stream) return {{{ cDefs.POLLNVAL }}}; | |
| // Streams without a poll handler (regular files, incl. NODERAWFS/NODEFS | |
| // which leave stream_ops unset) are treated as always readable+writable. | |
| var flags = stream.stream_ops?.poll?.(stream) ?? {{{ cDefs.POLLIN | cDefs.POLLOUT }}}; | |
| return flags & (events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}} | {{{ cDefs.POLLNVAL }}}); | |
| }, | |
| __syscall_poll__proxy: 'sync', | |
| __syscall_poll__async: 'auto', | |
| __syscall_poll__deps: ['$doPollSync', | |
| #if PTHREADS || ASYNCIFY | |
| '$doPollAsync', | |
| #endif | |
| ], | |
| __syscall_poll: (fds, nfds, timeout) => { | |
| #if PTHREADS || ASYNCIFY | |
| #if PTHREADS | |
| const isAsyncContext = PThread.currentProxiedOperationCallerThread; | |
| #else | |
| const isAsyncContext = true; | |
| #endif | |
| // When proxied from a worker (PTHREADS) or able to suspend (ASYNCIFY/JSPI), | |
| // block on the wait-queue. This must run for every timeout (including zero): | |
| // a proxied syscall's return is awaited by the caller thread, so it has to | |
| // be a Promise even for a probe. | |
| if (isAsyncContext) { | |
| #if RUNTIME_DEBUG | |
| dbg('async poll start'); | |
| #endif | |
| return doPollAsync(fds, nfds, timeout); | |
| } | |
| #endif | |
| var count = doPollSync(fds, nfds); | |
| #if ASSERTIONS | |
| if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout) | |
| #endif | |
| return count; | |
| }, | |
| // Synchronous poll(): derive each fd in place, writing revents and returning | |
| // the ready count. Used by the non-suspending syscall paths. | |
| $doPollSync__internal: true, | |
| $doPollSync__deps: ['$pollOne'], | |
| $doPollSync: (fds, nfds) => { | |
| var count = 0; | |
| for (var i = 0, pollfd = fds; i < nfds; i++, pollfd += {{{ C_STRUCTS.pollfd.__size__ }}}) { | |
| var revents = pollOne( | |
| {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}}, | |
| {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}}); | |
| if (revents) count++; | |
| {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'revents', 'i16') }}}; | |
| } | |
| return count; | |
| }, | |
| #if PTHREADS || ASYNCIFY | |
| // Async poll(): derive each fd in place (like doPollSync), but if nothing is | |
| // ready and `timeout` is non-zero, register one waiter per fd on its node | |
| // wait-queue and re-derive the whole set on any wake (the wake flags are just | |
| // the trigger), resolving then or, for a positive `timeout`, once it elapses. | |
| // A negative `timeout` waits forever. Returns a Promise of the ready count. | |
| $doPollAsync__internal: true, | |
| $doPollAsync__deps: ['$FS', '$pollOne'], | |
| $doPollAsync: (fds, nfds, timeout) => new Promise((resolve) => { | |
| var regs = []; | |
| var timer; | |
| var done = false; | |
| function derive() { | |
| var count = 0; | |
| for (var i = 0, pollfd = fds; i < nfds; i++, pollfd += {{{ C_STRUCTS.pollfd.__size__ }}}) { | |
| var revents = pollOne( | |
| {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}}, | |
| {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}}); | |
| if (revents) count++; | |
| {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'revents', 'i16') }}}; | |
| } | |
| return count; | |
| } | |
| function finish(count) { | |
| if (done) return; | |
| done = true; | |
| for (var r of regs) r.listeners.delete(r.entry); | |
| if (timer) clearTimeout(timer); | |
| resolve(count); | |
| } | |
| var count = derive(); | |
| if (count || !timeout) { | |
| finish(count); | |
| } else { | |
| function recheck() { | |
| if (done) return; | |
| var c = derive(); | |
| if (c) finish(c); | |
| } | |
| for (var i = 0, pollfd = fds; i < nfds; i++, pollfd += {{{ C_STRUCTS.pollfd.__size__ }}}) { | |
| var stream = FS.getStream({{{ makeGetValue('pollfd', C_STRUCTS.pollfd.fd, 'i32') }}}); | |
| if (stream) regs.push(stream.node.addListener(recheck)); | |
| } | |
| if (timeout > 0) timer = setTimeout(() => finish(0), timeout); | |
| } | |
| }), | |
| #endif | |
| // libc routes zero-timeout poll() calls here: the same synchronous | |
| // readiness derivation as __syscall_poll, but as a plain import that never | |
| // suspends, so probes stay callable from any context (under JSPI, | |
| // __syscall_poll is a suspending import and traps when called from a stack | |
| // that wasn't entered through a promising export). | |
| __syscall_poll_nonblocking__proxy: 'sync', | |
| __syscall_poll_nonblocking__deps: ['$doPollSync'], | |
| __syscall_poll_nonblocking: (fds, nfds) => { | |
| return doPollSync(fds, nfds); | |
| }, | |
| // epoll: the entry points live here (like every other syscall); the heavy | |
| // lifting is in libepoll.js, which they call after resolving the epoll stream. | |
| __syscall_epoll_create1__deps: ['$epollNewInstance'], | |
| __syscall_epoll_create1__proxy: 'sync', | |
| __syscall_epoll_create1: (flags) => { | |
| // EPOLL_CLOEXEC is accepted but a no-op (there is no exec). | |
| if (flags & ~{{{ cDefs.EPOLL_CLOEXEC }}}) return -{{{ cDefs.EINVAL }}}; | |
| return epollNewInstance().fd; | |
| }, | |
| __syscall_epoll_ctl__deps: ['$FS', '$epollCtl'], | |
| __syscall_epoll_ctl__proxy: 'sync', | |
| __syscall_epoll_ctl: (epfd, op, fd, ev) => { | |
| var ep = FS.getStream(epfd); | |
| if (!ep?.shared.epoll) return -{{{ cDefs.EBADF }}}; | |
| return epollCtl(ep.shared, op, fd, ev); | |
| }, | |
| __syscall_epoll_pwait__proxy: 'sync', | |
| __syscall_epoll_pwait__async: 'auto', | |
| __syscall_epoll_pwait__deps: ['$FS', '$epollPwait'], | |
| __syscall_epoll_pwait: (epfd, ev, maxevents, timeout, sigmask, sigsetsize) => { | |
| var ep = FS.getStream(epfd); | |
| if (!ep?.shared.epoll) return -{{{ cDefs.EBADF }}}; | |
| if (maxevents <= 0) return -{{{ cDefs.EINVAL }}}; | |
| return epollPwait(ep.shared, ev, maxevents, timeout); | |
| }, | |
| // libc routes zero-timeout epoll_wait()/epoll_pwait() calls here: a plain | |
| // import that never suspends, so probes stay callable from any context (under | |
| // JSPI, __syscall_epoll_pwait is a suspending import and traps when called | |
| // from a stack that wasn't entered through a promising export). Mirrors | |
| // __syscall_poll_nonblocking. | |
| __syscall_epoll_pwait_nonblocking__proxy: 'sync', | |
| __syscall_epoll_pwait_nonblocking__deps: ['$FS', '$doEpollWait'], | |
| __syscall_epoll_pwait_nonblocking: (epfd, ev, maxevents) => { | |
| var ep = FS.getStream(epfd); | |
| if (!ep?.shared.epoll) return -{{{ cDefs.EBADF }}}; | |
| if (maxevents <= 0) return -{{{ cDefs.EINVAL }}}; | |
| return doEpollWait(ep.shared, ev, maxevents); | |
| }, | |
| __syscall_getcwd__deps: ['$lengthBytesUTF8', '$stringToUTF8'], | |
| __syscall_getcwd: (buf, size) => { | |
| if (!size) return -{{{ cDefs.EINVAL }}}; | |
| var cwd = FS.cwd(); | |
| var cwdLengthInBytes = lengthBytesUTF8(cwd) + 1; | |
| if (size < cwdLengthInBytes) return -{{{ cDefs.ERANGE }}}; | |
| stringToUTF8(cwd, buf, size); | |
| return cwdLengthInBytes; | |
| }, | |
| __syscall_truncate64__i53abi: true, | |
| __syscall_truncate64: (path, length) => { | |
| if (isNaN(length)) return -{{{ cDefs.EFBIG }}}; | |
| path = SYSCALLS.getStr(path); | |
| FS.truncate(path, length); | |
| return 0; | |
| }, | |
| __syscall_ftruncate64__i53abi: true, | |
| __syscall_ftruncate64: (fd, length) => { | |
| if (isNaN(length)) return -{{{ cDefs.EFBIG }}}; | |
| FS.ftruncate(fd, length); | |
| return 0; | |
| }, | |
| __syscall_stat64: (path, buf) => { | |
| path = SYSCALLS.getStr(path); | |
| return SYSCALLS.writeStat(buf, FS.stat(path)); | |
| }, | |
| __syscall_lstat64: (path, buf) => { | |
| path = SYSCALLS.getStr(path); | |
| return SYSCALLS.writeStat(buf, FS.lstat(path)); | |
| }, | |
| __syscall_fstat64: (fd, buf) => { | |
| return SYSCALLS.writeStat(buf, FS.fstat(fd)); | |
| }, | |
| __syscall_fchown32: (fd, owner, group) => { | |
| FS.fchown(fd, owner, group); | |
| return 0; | |
| }, | |
| __syscall_getdents64__deps: ['$stringToUTF8'], | |
| __syscall_getdents64: (fd, dirp, count) => { | |
| var stream = SYSCALLS.getStreamFromFD(fd) | |
| stream.getdents ||= FS.readdir(stream.path); | |
| var struct_size = {{{ C_STRUCTS.dirent.__size__ }}}; | |
| var pos = 0; | |
| var off = FS.llseek(stream, 0, {{{ cDefs.SEEK_CUR }}}); | |
| var startIdx = Math.floor(off / struct_size); | |
| var endIdx = Math.min(stream.getdents.length, startIdx + Math.floor(count/struct_size)) | |
| for (var idx = startIdx; idx < endIdx; idx++) { | |
| var id; | |
| var type; | |
| var name = stream.getdents[idx]; | |
| if (name === '.') { | |
| id = stream.node.id; | |
| type = {{{ cDefs.DT_DIR }}}; | |
| } | |
| else if (name === '..') { | |
| var lookup = FS.lookupPath(stream.path, { parent: true }); | |
| id = lookup.node.id; | |
| type = {{{ cDefs.DT_DIR }}}; | |
| } | |
| else { | |
| var child; | |
| try { | |
| child = FS.lookupNode(stream.node, name); | |
| } catch (e) { | |
| // If the entry is not a directory, file, or symlink, nodefs | |
| // lookupNode will raise EINVAL. Skip these and continue. | |
| if (e?.errno === {{{ cDefs.EINVAL }}}) { | |
| continue; | |
| } | |
| throw e; | |
| } | |
| id = child.id; | |
| type = FS.isChrdev(child.mode) ? {{{ cDefs.DT_CHR }}} : // character device. | |
| FS.isDir(child.mode) ? {{{ cDefs.DT_DIR }}} : // directory | |
| FS.isLink(child.mode) ? {{{ cDefs.DT_LNK }}} : // symbolic link. | |
| {{{ cDefs.DT_REG }}}; // regular file. | |
| } | |
| #if ASSERTIONS | |
| assert(id); | |
| #endif | |
| {{{ 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; | |
| } | |
| FS.llseek(stream, idx * struct_size, {{{ cDefs.SEEK_SET }}}); | |
| return pos; | |
| }, | |
| #if SYSCALLS_REQUIRE_FILESYSTEM | |
| __syscall_fcntl64__deps: ['$syscallGetVarargP', '$syscallGetVarargI'], | |
| #endif | |
| __syscall_fcntl64: (fd, cmd, varargs) => { | |
| #if SYSCALLS_REQUIRE_FILESYSTEM == 0 | |
| #if SYSCALL_DEBUG | |
| dbg('no-op in fcntl syscall due to SYSCALLS_REQUIRE_FILESYSTEM=0'); | |
| #endif | |
| return 0; | |
| #else | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| switch (cmd) { | |
| case {{{ cDefs.F_DUPFD }}}: { | |
| var arg = syscallGetVarargI(); | |
| if (arg < 0) { | |
| return -{{{ cDefs.EINVAL }}}; | |
| } | |
| while (FS.streams[arg]) { | |
| arg++; | |
| } | |
| var newStream; | |
| newStream = FS.dupStream(stream, arg); | |
| return newStream.fd; | |
| } | |
| case {{{ cDefs.F_GETFD }}}: | |
| case {{{ cDefs.F_SETFD }}}: | |
| return 0; // FD_CLOEXEC makes no sense for a single process. | |
| case {{{ cDefs.F_GETFL }}}: | |
| return stream.flags; | |
| case {{{ cDefs.F_SETFL }}}: { | |
| var arg = syscallGetVarargI(); | |
| var mask = {{{ cDefs.O_APPEND | cDefs.O_ASYNC | cDefs.O_DIRECT | cDefs.O_NOATIME | cDefs.O_NONBLOCK }}}; | |
| stream.flags = (stream.flags & ~mask) | (arg & mask); | |
| return 0; | |
| } | |
| case {{{ cDefs.F_GETLK }}}: { | |
| var arg = syscallGetVarargP(); | |
| var offset = {{{ C_STRUCTS.flock.l_type }}}; | |
| // We're always unlocked. | |
| {{{ makeSetValue('arg', 'offset', cDefs.F_UNLCK, 'i16') }}}; | |
| return 0; | |
| } | |
| case {{{ cDefs.F_SETLK }}}: | |
| case {{{ cDefs.F_SETLKW }}}: | |
| // Pretend that the locking is successful. These are process-level locks, | |
| // and Emscripten programs are a single process. If we supported linking a | |
| // filesystem between programs, we'd need to do more here. | |
| // See https://github.com/emscripten-core/emscripten/issues/23697 | |
| return 0; | |
| #if SYSCALL_DEBUG | |
| case {{{ cDefs.F_GETOWN_EX }}}: | |
| case {{{ cDefs.F_SETOWN }}}: | |
| case {{{ cDefs.F_GETOWN }}}: | |
| return -{{{ cDefs.EINVAL }}}; | |
| default: | |
| dbg(`warning: fcntl unrecognized command ${cmd}`); | |
| #endif | |
| } | |
| return -{{{ cDefs.EINVAL }}}; | |
| #endif // SYSCALLS_REQUIRE_FILESYSTEM | |
| }, | |
| __syscall_statfs64: (path, size, buf) => { | |
| #if ASSERTIONS | |
| assert(size === {{{ C_STRUCTS.statfs.__size__ }}}); | |
| #endif | |
| SYSCALLS.writeStatFs(buf, FS.statfs(SYSCALLS.getStr(path))); | |
| return 0; | |
| }, | |
| __syscall_fstatfs64: (fd, size, buf) => { | |
| #if ASSERTIONS | |
| assert(size === {{{ C_STRUCTS.statfs.__size__ }}}); | |
| #endif | |
| var stream = SYSCALLS.getStreamFromFD(fd); | |
| SYSCALLS.writeStatFs(buf, FS.statfsStream(stream)); | |
| return 0; | |
| }, | |
| __syscall_openat__deps: ['$syscallGetVarargI'], | |
| __syscall_openat: (dirfd, path, flags, varargs) => { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| var mode = varargs ? syscallGetVarargI() : 0; | |
| if (flags & {{{ cDefs.O_CREAT }}}) { | |
| mode &= ~SYSCALLS.currentUmask; | |
| } | |
| return FS.open(path, flags, mode).fd; | |
| }, | |
| __syscall_umask: (mask) => { | |
| var old = SYSCALLS.currentUmask; | |
| SYSCALLS.currentUmask = mask; | |
| return old; | |
| }, | |
| __syscall_mkdirat: (dirfd, path, mode) => { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| mode &= ~SYSCALLS.currentUmask; | |
| FS.mkdir(path, mode); | |
| return 0; | |
| }, | |
| __syscall_mknodat: (dirfd, path, mode, dev) => { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| mode &= ~SYSCALLS.currentUmask; | |
| // we don't want this in the JS API as it uses mknod to create all nodes. | |
| switch (mode & {{{ cDefs.S_IFMT }}}) { | |
| case {{{ cDefs.S_IFREG }}}: | |
| case {{{ cDefs.S_IFCHR }}}: | |
| case {{{ cDefs.S_IFBLK }}}: | |
| case {{{ cDefs.S_IFIFO }}}: | |
| case {{{ cDefs.S_IFSOCK }}}: | |
| break; | |
| default: return -{{{ cDefs.EINVAL }}}; | |
| } | |
| FS.mknod(path, mode, dev); | |
| return 0; | |
| }, | |
| __syscall_fchownat: (dirfd, path, owner, group, flags) => { | |
| path = SYSCALLS.getStr(path); | |
| var nofollow = flags & {{{ cDefs.AT_SYMLINK_NOFOLLOW }}}; | |
| flags = flags & (~{{{ cDefs.AT_SYMLINK_NOFOLLOW }}}); | |
| #if ASSERTIONS | |
| assert(!flags); | |
| #endif | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| (nofollow ? FS.lchown : FS.chown)(path, owner, group); | |
| return 0; | |
| }, | |
| __syscall_newfstatat: (dirfd, path, buf, flags) => { | |
| path = SYSCALLS.getStr(path); | |
| var nofollow = flags & {{{ cDefs.AT_SYMLINK_NOFOLLOW }}}; | |
| var allowEmpty = flags & {{{ cDefs.AT_EMPTY_PATH }}}; | |
| flags = flags & (~{{{ cDefs.AT_SYMLINK_NOFOLLOW | cDefs.AT_EMPTY_PATH | cDefs.AT_NO_AUTOMOUNT }}}); | |
| #if ASSERTIONS | |
| assert(!flags, `unknown flags in __syscall_newfstatat: ${flags}`); | |
| #endif | |
| path = SYSCALLS.calculateAt(dirfd, path, allowEmpty); | |
| return SYSCALLS.writeStat(buf, nofollow ? FS.lstat(path) : FS.stat(path)); | |
| }, | |
| __syscall_unlinkat: (dirfd, path, flags) => { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (!flags) { | |
| FS.unlink(path); | |
| } else if (flags === {{{ cDefs.AT_REMOVEDIR }}}) { | |
| FS.rmdir(path); | |
| } else { | |
| return -{{{ cDefs.EINVAL }}}; | |
| } | |
| return 0; | |
| }, | |
| __syscall_renameat: (olddirfd, oldpath, newdirfd, newpath) => { | |
| oldpath = SYSCALLS.getStr(oldpath); | |
| newpath = SYSCALLS.getStr(newpath); | |
| oldpath = SYSCALLS.calculateAt(olddirfd, oldpath); | |
| newpath = SYSCALLS.calculateAt(newdirfd, newpath); | |
| FS.rename(oldpath, newpath); | |
| return 0; | |
| }, | |
| __syscall_symlinkat: (target, dirfd, linkpath) => { | |
| target = SYSCALLS.getStr(target); | |
| linkpath = SYSCALLS.getStr(linkpath); | |
| linkpath = SYSCALLS.calculateAt(dirfd, linkpath); | |
| FS.symlink(target, linkpath); | |
| return 0; | |
| }, | |
| __syscall_linkat: (olddirfd, oldpath, newdirfd, newpath, flags) => { | |
| oldpath = SYSCALLS.getStr(oldpath); | |
| newpath = SYSCALLS.getStr(newpath); | |
| oldpath = SYSCALLS.calculateAt(olddirfd, oldpath); | |
| newpath = SYSCALLS.calculateAt(newdirfd, newpath); | |
| FS.link(oldpath, newpath, flags); | |
| return 0; | |
| }, | |
| __syscall_readlinkat__deps: ['$lengthBytesUTF8', '$stringToUTF8'], | |
| __syscall_readlinkat: (dirfd, path, buf, bufsize) => { | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (bufsize <= 0) return -{{{ cDefs.EINVAL }}}; | |
| var ret = FS.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; | |
| }, | |
| __syscall_fchmodat2: (dirfd, path, mode, flags) => { | |
| var nofollow = flags & {{{ cDefs.AT_SYMLINK_NOFOLLOW }}}; | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| FS.chmod(path, mode, nofollow); | |
| return 0; | |
| }, | |
| __syscall_faccessat: (dirfd, path, amode, flags) => { | |
| path = SYSCALLS.getStr(path); | |
| #if ASSERTIONS | |
| assert(!flags || flags == {{{ cDefs.AT_EACCESS }}}); | |
| #endif | |
| path = SYSCALLS.calculateAt(dirfd, path); | |
| if (amode & ~{{{ cDefs.S_IRWXO }}}) { | |
| // need a valid mode | |
| return -{{{ cDefs.EINVAL }}}; | |
| } | |
| var lookup = FS.lookupPath(path, { follow: true }); | |
| var node = lookup.node; | |
| if (!node) { | |
| return -{{{ cDefs.ENOENT }}}; | |
| } | |
| var perms = ''; | |
| if (amode & {{{ cDefs.R_OK }}}) perms += 'r'; | |
| if (amode & {{{ cDefs.W_OK }}}) perms += 'w'; | |
| if (amode & {{{ cDefs.X_OK }}}) perms += 'x'; | |
| if (perms /* otherwise, they've just passed F_OK */ && FS.nodePermissions(node, perms)) { | |
| return -{{{ cDefs.EACCES }}}; | |
| } | |
| return 0; | |
| }, | |
| __syscall_utimensat__deps: ['$readI53FromI64'], | |
| __syscall_utimensat: (dirfd, path, times, flags) => { | |
| var nofollow = flags & {{{ cDefs.AT_SYMLINK_NOFOLLOW }}}; | |
| path = SYSCALLS.getStr(path); | |
| path = SYSCALLS.calculateAt(dirfd, path, true); | |
| var now = Date.now(), atime, mtime; | |
| if (!times) { | |
| atime = now; | |
| mtime = now; | |
| } else { | |
| function readTimespec(ptr) { | |
| var tv_nsec = {{{ makeGetValue('ptr', C_STRUCTS.timespec.tv_nsec, 'i32') }}}; | |
| if (tv_nsec == {{{ cDefs.UTIME_NOW }}}) { | |
| return now; | |
| } | |
| if (tv_nsec == {{{ cDefs.UTIME_OMIT }}}) { | |
| return null; | |
| } | |
| var tv_sec = {{{ makeGetValue('ptr', C_STRUCTS.timespec.tv_sec, 'i53') }}}; | |
| return (tv_sec*1000) + Math.floor(tv_nsec/(1000*1000)); | |
| } | |
| atime = readTimespec(times); | |
| mtime = readTimespec(times + {{{ C_STRUCTS.timespec.__size__ }}}); | |
| } | |
| // null here means UTIME_OMIT was passed. If both were set to UTIME_OMIT then | |
| // we can skip the call completely. | |
| if ((mtime ?? atime) !== null) { | |
| FS.utime(path, atime, mtime, nofollow); | |
| } | |
| return 0; | |
| }, | |
| __syscall_fallocate__i53abi: true, | |
| __syscall_fallocate: (fd, mode, offset, len) => { | |
| if (isNaN(offset) || isNaN(len)) return -{{{ cDefs.EFBIG }}}; | |
| if (mode != 0) { | |
| return -{{{ cDefs.ENOTSUP }}} | |
| } | |
| if (offset < 0 || len < 0) { | |
| return -{{{ cDefs.EINVAL }}} | |
| } | |
| // fallocate is only meaningful on regular files; a pipe/socket is a seek | |
| // error (ESPIPE), matching Linux. | |
| if (!SYSCALLS.getStreamFromFD(fd).seekable) { | |
| return -{{{ cDefs.ESPIPE }}}; | |
| } | |
| // We only support mode == 0, which means we can implement fallocate | |
| // in terms of ftruncate. | |
| var oldSize = FS.fstat(fd).size; | |
| var newSize = offset + len; | |
| if (newSize > oldSize) { | |
| FS.ftruncate(fd, newSize); | |
| } | |
| return 0; | |
| }, | |
| __syscall_fadvise64__i53abi: true, | |
| __syscall_fadvise64: (fd, offset, len, advice) => { | |
| // Advisory only, so a no-op, but a pipe/socket fd is still a seek error | |
| // (ESPIPE), matching Linux. | |
| if (!SYSCALLS.getStreamFromFD(fd).seekable) { | |
| return -{{{ cDefs.ESPIPE }}}; | |
| } | |
| return 0; | |
| }, | |
| __syscall_dup3: (fd, newfd, flags) => { | |
| if (fd === newfd) return -{{{ cDefs.EINVAL }}}; | |
| if (flags & ~{{{ cDefs.O_CLOEXEC }}}) return -{{{ cDefs.EINVAL }}}; | |
| var old = SYSCALLS.getStreamFromFD(fd); | |
| // Check newfd is within range of valid open file descriptors. | |
| if (newfd < 0 || newfd >= FS.MAX_OPEN_FDS) return -{{{ cDefs.EBADF }}}; | |
| var existing = FS.getStream(newfd); | |
| if (existing) FS.close(existing); | |
| var stream = FS.dupStream(old, newfd); | |
| if (flags & {{{ cDefs.O_CLOEXEC }}}) { | |
| stream.flags |= {{{ cDefs.O_CLOEXEC }}}; | |
| } | |
| return stream.fd; | |
| }, | |
| }; | |
| for (const name of Object.keys(SyscallsLibrary)) { | |
| wrapSyscallFunction(name, SyscallsLibrary, false); | |
| } | |
| addToLibrary(SyscallsLibrary); |