/**
 * @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) {
      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', '$DNS'],
  $getSocketAddress: (addrp, addrlen) => {
    var info = readSockaddr(addrp, addrlen);
    if (info.errno) throw new FS.ErrnoError(info.errno);
    info.addr = DNS.lookup_addr(info.addr) || info.addr;
#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);
#if ASSERTIONS
    assert(sock.stream.fd < 64); // XXX ? select() assumes socket fd values are in 0..63
#endif
    return sock.stream.fd;
  },
  __syscall_getsockname__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
  __syscall_getsockname: (fd, addr, len, u1, u2, u3) => {
    var sock = getSocketFromFD(fd);
    // TODO: sock.saddr should never be undefined, see TODO in websocket_sock_ops.getname
    var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(sock.saddr || '0.0.0.0'), sock.sport, len);
#if ASSERTIONS
    assert(!errno);
#endif
    return 0;
  },
  __syscall_getpeername__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
  __syscall_getpeername: (fd, addr, len, u1, u2, u3) => {
    var sock = getSocketFromFD(fd);
    if (!sock.daddr) {
      return -{{{ cDefs.ENOTCONN }}}; // The socket is not connected.
    }
    var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(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) => {
    getSocketFromFD(fd);
    return -{{{ cDefs.ENOSYS }}}; // unsupported feature
  },
  __syscall_accept4__deps: ['$getSocketFromFD', '$writeSockaddr', '$DNS'],
  __syscall_accept4: (fd, addr, len, flags, u1, u2) => {
    var sock = getSocketFromFD(fd);
    var newsock = sock.sock_ops.accept(sock);
    if (addr) {
      var errno = writeSockaddr(addr, newsock.family, DNS.lookup_name(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', '$DNS'],
  __syscall_recvfrom: (fd, buf, len, flags, addr, alen) => {
    var sock = getSocketFromFD(fd);
    var msg = sock.sock_ops.recvmsg(sock, len);
    if (!msg) return 0; // socket is closed
    if (addr) {
      var errno = writeSockaddr(addr, sock.family, DNS.lookup_name(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);
    // 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.
  },
  __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', '$DNS'],
  __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
    var msg = sock.sock_ops.recvmsg(sock, total);
    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_PEEK
    // Peeks at the incoming message.
    // 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 errno = writeSockaddr(name, sock.family, DNS.lookup_name(msg.addr), msg.port);
#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);
      bytesRead += length;
      bytesRemaining -= length;
    }

    // TODO set 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
  __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
  },
  __syscall_poll__proxy: 'sync',
  __syscall_poll__async: 'auto',
  __syscall_poll__deps: ['$doPoll'],
  __syscall_poll: (fds, nfds, timeout) => {
#if PTHREADS || ASYNCIFY
#if PTHREADS
    const isAsyncContext = PThread.currentProxiedOperationCallerThread;
#else
    const isAsyncContext = true;
#endif
    // Enable event handlers only when the poll call is proxied from a worker.
    // TODO: Could use `Promise.withResolvers` here if we know its available.
    var resolve;
    var promise = new Promise((resolve_) => { resolve = resolve_; });
    var cleanupFuncs = [];
    var notifyDone = false;
    function asyncPollComplete(count) {
      if (notifyDone) {
        return;
      }
      notifyDone = true;
#if RUNTIME_DEBUG
      dbg('asyncPollComplete', count);
#endif
      cleanupFuncs.forEach(cb => cb());
      resolve(count);
    }
    function makeNotifyCallback(stream, pollfd) {
      var cb = (flags) => {
        if (notifyDone) {
          return;
        }
#if RUNTIME_DEBUG
        dbg(`async poll notify: stream=${stream}`);
#endif
        var events = {{{ makeGetValue('pollfd', C_STRUCTS.pollfd.events, 'i16') }}};
        flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
#if ASSERTIONS
        assert(flags)
#endif
        {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
        asyncPollComplete(1);
      }
      cb.registerCleanupFunc = (f) => {
        if (f) cleanupFuncs.push(f);
      }
      return cb;
    }

    if (isAsyncContext) {
#if RUNTIME_DEBUG
      dbg('async poll start');
#endif
      if (timeout > 0) {
        var t = setTimeout(() => {
#if RUNTIME_DEBUG
          dbg('poll: timeout', timeout);
#endif
          asyncPollComplete(0);
        }, timeout);
        cleanupFuncs.push(() => clearTimeout(t));
      }
      // A zero timeout never registers notifications: the derivation alone
      // answers, matching the non-blocking probe.
      var count = doPoll(fds, nfds, timeout, makeNotifyCallback);
      if (count || !timeout) {
        asyncPollComplete(count);
      }
      return promise;
    }
#endif

    var count = doPoll(fds, nfds, 0, undefined);
#if ASSERTIONS
    if (!count && timeout != 0) warnOnce('non-zero poll() timeout not supported: ' + timeout)
#endif
    return count;
  },
  // The shared readiness derivation: one pass over the pollfds, writing
  // revents and returning the ready count. With a nonzero `timeout`, a
  // readiness notification is also registered on each stream by the same
  // `stream_ops.poll` call that derives it, so there is no window between
  // registration and derivation; a zero `timeout` means the caller will not
  // wait, so no notification is registered (the plain probe).
  $doPoll__internal: true,
  $doPoll__deps: ['$FS'],
  $doPoll: (fds, nfds, timeout, makeNotifyCallback) => {
    var count = 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 flags = {{{ cDefs.POLLNVAL }}};
      var stream = FS.getStream(fd);
      if (stream) {
        if (stream.stream_ops.poll) {
          flags = timeout
            ? stream.stream_ops.poll(stream, timeout, makeNotifyCallback(stream, pollfd))
            : stream.stream_ops.poll(stream, -1);
        } else {
          flags = {{{ cDefs.POLLIN | cDefs.POLLOUT }}};
        }
      }
      flags &= events | {{{ cDefs.POLLERR }}} | {{{ cDefs.POLLHUP }}};
      if (flags) count++;
      {{{ makeSetValue('pollfd', C_STRUCTS.pollfd.revents, 'flags', 'i16') }}};
    }
    return count;
  },
  // 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: ['$doPoll'],
  __syscall_poll_nonblocking: (fds, nfds) => {
    return doPoll(fds, nfds, 0, undefined);
  },
  __syscall_getcwd__deps: ['$lengthBytesUTF8', '$stringToUTF8'],
  __syscall_getcwd: (buf, size) => {
    if (size === 0) 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, 0);
    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_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) => {
    path = SYSCALLS.getStr(path);
#if ASSERTIONS
    assert(!flags);
#endif
    path = SYSCALLS.calculateAt(dirfd, path, true);
    var now = Date.now(), atime, mtime;
    if (!times) {
      atime = now;
      mtime = now;
    } else {
      var seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
      var nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
      if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
        atime = now;
      } else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
        atime = null;
      } else {
        atime = (seconds*1000) + (nanoseconds/(1000*1000));
      }
      times += {{{ C_STRUCTS.timespec.__size__ }}};
      seconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_sec, 'i53') }}};
      nanoseconds = {{{ makeGetValue('times', C_STRUCTS.timespec.tv_nsec, 'i32') }}};
      if (nanoseconds == {{{ cDefs.UTIME_NOW }}}) {
        mtime = now;
      } else if (nanoseconds == {{{ cDefs.UTIME_OMIT }}}) {
        mtime = null;
      } else {
        mtime = (seconds*1000) + (nanoseconds/(1000*1000));
      }
    }
    // 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);
    }
    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 }}}
    }
    // 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_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);
