text: // Part of the Wasmtime Project, under the Apache License v2.0 with LLVM Exceptions.
  // See https://github.com/CraneStation/wasmtime/blob/master/LICENSE for license information.
  //
  // Significant parts of this file are derived from cloudabi-utils. See
  // https://github.com/CraneStation/wasmtime/blob/master/lib/wasi/sandboxed-system-primitives/src/LICENSE
  // for license information.
  //
  // The upstream file contains the following copyright notice:
  //
  // Copyright (c) 2016-2018 Nuxi, https://nuxi.nl/
  
  #include "ssp_config.h"
  
  #include <sys/types.h>
  
  #include <sys/ioctl.h>
  #include <sys/mman.h>
  #include <sys/resource.h>
  #include <sys/socket.h>
  #include <sys/stat.h>
  #include <sys/time.h>
  #include <sys/uio.h>
  
  #include <assert.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <poll.h>
  #include <sched.h>
  #include <signal.h>
  #include <stdbool.h>
  #include <stddef.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <time.h>
  #include <unistd.h>
  
  #include <wasmtime_ssp.h>
  
  #include "wasi_internal.h"
  #include "common/time.h"
  
  #include "locking.h"
  #include "numeric_limits.h"
  #include "random.h"
  #include "wasi_api_fd_internal.h"
  #include "common/rights.h"
  #include "common/str.h"
  
  #include "bh_common.h"
  #include "bh_assert.h"
  
  #if 0 /* TODO: -std=gnu99 causes compile error, comment them first */
  // struct iovec must have the same layout as __wasi_iovec_t.
  static_assert(offsetof(struct iovec, iov_base) ==
                    offsetof(__wasi_iovec_t, buf),
                "Offset mismatch");
  static_assert(sizeof(((struct iovec *)0)->iov_base) ==
                    sizeof(((__wasi_iovec_t *)0)->buf),
                "Size mismatch");
  static_assert(offsetof(struct iovec, iov_len) ==
                    offsetof(__wasi_iovec_t, buf_len),
                "Offset mismatch");
  static_assert(sizeof(((struct iovec *)0)->iov_len) ==
                    sizeof(((__wasi_iovec_t *)0)->buf_len),
                "Size mismatch");
  static_assert(sizeof(struct iovec) == sizeof(__wasi_iovec_t),
                "Size mismatch");
  
  // struct iovec must have the same layout as __wasi_ciovec_t.
  static_assert(offsetof(struct iovec, iov_base) ==
                    offsetof(__wasi_ciovec_t, buf),
                "Offset mismatch");
  static_assert(sizeof(((struct iovec *)0)->iov_base) ==
                    sizeof(((__wasi_ciovec_t *)0)->buf),
                "Size mismatch");
  static_assert(offsetof(struct iovec, iov_len) ==
                    offsetof(__wasi_ciovec_t, buf_len),
                "Offset mismatch");
  static_assert(sizeof(((struct iovec *)0)->iov_len) ==
                    sizeof(((__wasi_ciovec_t *)0)->buf_len),
                "Size mismatch");
  static_assert(sizeof(struct iovec) == sizeof(__wasi_ciovec_t),
                "Size mismatch");
  #endif
  
  #if defined(WASMTIME_SSP_STATIC_CURFDS)
  static __thread struct fd_table *curfds;
  static __thread struct fd_prestats *prestats;
  static __thread struct argv_environ_values *argv_environ;
  #endif
  
  __wasi_errno_t wasmtime_ssp_clock_res_get(
      __wasi_clockid_t clock_id,
      __wasi_timestamp_t *resolution
  ) {
    clockid_t nclock_id;
    if (!convert_clockid(clock_id, &nclock_id))
      return __WASI_EINVAL;
    struct timespec ts;
    if (clock_getres(nclock_id, &ts) < 0)
      return convert_errno(errno);
    *resolution = convert_timespec(&ts);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_clock_time_get(
      __wasi_clockid_t clock_id,
      __wasi_timestamp_t precision,
      __wasi_timestamp_t *time
  ) {
    clockid_t nclock_id;
    if (!convert_clockid(clock_id, &nclock_id))
      return __WASI_EINVAL;
    struct timespec ts;
    if (clock_gettime(nclock_id, &ts) < 0)
      return convert_errno(errno);
    *time = convert_timespec(&ts);
    return 0;
  }
  
  struct fd_prestat {
    const char *dir;
  };
  
  void fd_prestats_init(
      struct fd_prestats *pt
  ) {
    rwlock_init(&pt->lock);
    pt->prestats = NULL;
    pt->size = 0;
    pt->used = 0;
  #if defined(WASMTIME_SSP_STATIC_CURFDS)
    prestats = pt;
  #endif
  }
  
  // Grows the preopened resource table to a required lower bound and a
  // minimum number of free preopened resource table entries.
  static bool fd_prestats_grow(
      struct fd_prestats *pt,
      size_t min,
      size_t incr
  ) REQUIRES_EXCLUSIVE(pt->lock) {
    if (pt->size <= min || pt->size < (pt->used + incr) * 2) {
      // Keep on doubling the table size until we've met our constraints.
      size_t size = pt->size == 0 ? 1 : pt->size;
      while (size <= min || size < (pt->used + incr) * 2)
        size *= 2;
  
      // Grow the file descriptor table's allocation.
      struct fd_prestat *prestats = wasm_runtime_malloc((uint32)(sizeof(*prestats) * size));
      if (prestats == NULL)
        return false;
  
      if (pt->prestats && pt->size > 0) {
        bh_memcpy_s(prestats, (uint32)(sizeof(*prestats) * size),
                    pt->prestats, (uint32)(sizeof(*prestats) * pt->size));
      }
  
      if (pt->prestats)
        wasm_runtime_free(pt->prestats);
  
      // Mark all new file descriptors as unused.
      for (size_t i = pt->size; i < size; ++i)
        prestats[i].dir = NULL;
      pt->prestats = prestats;
      pt->size = size;
    }
    return true;
  }
  
  // Inserts a preopened resource record into the preopened resource table.
  bool fd_prestats_insert(
      struct fd_prestats *pt,
      const char *dir,
      __wasi_fd_t fd
  ) {
    // Grow the preopened resource table if needed.
    rwlock_wrlock(&pt->lock);
    if (!fd_prestats_grow(pt, fd, 1)) {
      rwlock_unlock(&pt->lock);
      return false;
    }
  
    pt->prestats[fd].dir = bh_strdup(dir);
    rwlock_unlock(&pt->lock);
  
    if (pt->prestats[fd].dir == NULL)
      return false;
  
    return true;
  }
  
  // Looks up a preopened resource table entry by number.
  static __wasi_errno_t fd_prestats_get_entry(
      struct fd_prestats *pt,
      __wasi_fd_t fd,
      struct fd_prestat **ret
  ) REQUIRES_SHARED(pt->lock) {
    // Test for file descriptor existence.
    if (fd >= pt->size)
      return __WASI_EBADF;
    struct fd_prestat *prestat = &pt->prestats[fd];
    if (prestat->dir == NULL)
      return __WASI_EBADF;
  
    *ret = prestat;
    return 0;
  }
  
  void fd_table_init(
      struct fd_table *ft
  ) {
    rwlock_init(&ft->lock);
    ft->entries = NULL;
    ft->size = 0;
    ft->used = 0;
  #if defined(WASMTIME_SSP_STATIC_CURFDS)
    curfds = ft;
  #endif
  }
  
  // Determines the type of a file descriptor and its maximum set of
  // rights that should be attached to it.
  __wasi_errno_t fd_determine_type_rights(
      int fd,
      __wasi_filetype_t *type,
      __wasi_rights_t *rights_base,
      __wasi_rights_t *rights_inheriting
  ) {
    struct stat sb;
    if (fstat(fd, &sb) < 0)
      return convert_errno(errno);
    if (S_ISBLK(sb.st_mode)) {
      *type = __WASI_FILETYPE_BLOCK_DEVICE;
      *rights_base = RIGHTS_BLOCK_DEVICE_BASE;
      *rights_inheriting = RIGHTS_BLOCK_DEVICE_INHERITING;
    } else if (S_ISCHR(sb.st_mode)) {
      *type = __WASI_FILETYPE_CHARACTER_DEVICE;
  #if CONFIG_HAS_ISATTY
      if (isatty(fd)) {
        *rights_base = RIGHTS_TTY_BASE;
        *rights_inheriting = RIGHTS_TTY_INHERITING;
      } else
  #endif
      {
        *rights_base = RIGHTS_CHARACTER_DEVICE_BASE;
        *rights_inheriting = RIGHTS_CHARACTER_DEVICE_INHERITING;
      }
    } else if (S_ISDIR(sb.st_mode)) {
      *type = __WASI_FILETYPE_DIRECTORY;
      *rights_base = RIGHTS_DIRECTORY_BASE;
      *rights_inheriting = RIGHTS_DIRECTORY_INHERITING;
    } else if (S_ISREG(sb.st_mode)) {
      *type = __WASI_FILETYPE_REGULAR_FILE;
      *rights_base = RIGHTS_REGULAR_FILE_BASE;
      *rights_inheriting = RIGHTS_REGULAR_FILE_INHERITING;
    } else if (S_ISSOCK(sb.st_mode)) {
      int socktype;
      socklen_t socktypelen = sizeof(socktype);
      if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &socktypelen) < 0)
        return convert_errno(errno);
      switch (socktype) {
        case SOCK_DGRAM:
          *type = __WASI_FILETYPE_SOCKET_DGRAM;
          break;
        case SOCK_STREAM:
          *type = __WASI_FILETYPE_SOCKET_STREAM;
          break;
        default:
          return __WASI_EINVAL;
      }
      *rights_base = RIGHTS_SOCKET_BASE;
      *rights_inheriting = RIGHTS_SOCKET_INHERITING;
    } else if (S_ISFIFO(sb.st_mode)) {
      *type = __WASI_FILETYPE_SOCKET_STREAM;
      *rights_base = RIGHTS_SOCKET_BASE;
      *rights_inheriting = RIGHTS_SOCKET_INHERITING;
    } else {
      return __WASI_EINVAL;
    }
  
    // Strip off read/write bits based on the access mode.
    switch (fcntl(fd, F_GETFL) & O_ACCMODE) {
      case O_RDONLY:
        *rights_base &= ~(__wasi_rights_t)__WASI_RIGHT_FD_WRITE;
        break;
      case O_WRONLY:
        *rights_base &= ~(__wasi_rights_t)__WASI_RIGHT_FD_READ;
        break;
    }
    return 0;
  }
  
  #define CLOSE_NON_STD_FD(fd) do {  \
      if (fd > 2)                    \
        close(fd);                   \
    } while (0)
  
  // Lowers the reference count on a file descriptor object. When the
  // reference count reaches zero, its resources are cleaned up.
  void fd_object_release(
      struct fd_object *fo
  ) UNLOCKS(fo->refcount) {
    if (refcount_release(&fo->refcount)) {
      switch (fo->type) {
        case __WASI_FILETYPE_DIRECTORY:
          // For directories we may keep track of a DIR object. Calling
          // closedir() on it also closes the underlying file descriptor.
          mutex_destroy(&fo->directory.lock);
          if (fo->directory.handle == NULL) {
            CLOSE_NON_STD_FD(fd_number(fo));
          } else {
            closedir(fo->directory.handle);
          }
          break;
        default:
          CLOSE_NON_STD_FD(fd_number(fo));
          break;
      }
      wasm_runtime_free(fo);
    }
  }
  
  // Picks an unused slot from the file descriptor table.
  __wasi_fd_t fd_table_unused(
      struct fd_table *ft
  ) REQUIRES_SHARED(ft->lock) {
    assert(ft->size > ft->used && "File descriptor table has no free slots");
    for (;;) {
      __wasi_fd_t fd = (__wasi_fd_t)random_uniform(ft->size);
      if (ft->entries[fd].object == NULL)
        return fd;
    }
  }
  
  __wasi_errno_t wasmtime_ssp_fd_prestat_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_prestats *prestats,
  #endif
      __wasi_fd_t fd,
      __wasi_prestat_t *buf
  ) {
    rwlock_rdlock(&prestats->lock);
    struct fd_prestat *prestat;
    __wasi_errno_t error = fd_prestats_get_entry(prestats, fd, &prestat);
    if (error != 0) {
      rwlock_unlock(&prestats->lock);
      return error;
    }
  
    *buf = (__wasi_prestat_t) {
      .pr_type = __WASI_PREOPENTYPE_DIR,
    };
    
    buf->u.dir.pr_name_len = strlen(prestat->dir);
  
    rwlock_unlock(&prestats->lock);
  
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_prestat_dir_name(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_prestats *prestats,
  #endif
      __wasi_fd_t fd,
      char *path,
      size_t path_len
  ) {
    rwlock_rdlock(&prestats->lock);
    struct fd_prestat *prestat;
    __wasi_errno_t error = fd_prestats_get_entry(prestats, fd, &prestat);
    if (error != 0) {
      rwlock_unlock(&prestats->lock);
      return error;
    }
    if (path_len != strlen(prestat->dir)) {
      rwlock_unlock(&prestats->lock);
      return EINVAL;
    }
  
    bh_memcpy_s(path, (uint32)path_len, prestat->dir, (uint32)path_len);
  
    rwlock_unlock(&prestats->lock);
  
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_close(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
      struct fd_prestats *prestats,
  #endif
      __wasi_fd_t fd
  ) {
    // Don't allow closing a pre-opened resource.
    // TODO: Eventually, we do want to permit this, once libpreopen in
    // userspace is capable of removing entries from its tables as well.
    {
      rwlock_rdlock(&prestats->lock);
      struct fd_prestat *prestat;
      __wasi_errno_t error = fd_prestats_get_entry(prestats, fd, &prestat);
      rwlock_unlock(&prestats->lock);
      if (error == 0) {
        return __WASI_ENOTSUP;
      }
    }
  
    // Validate the file descriptor.
    struct fd_table *ft = curfds;
    rwlock_wrlock(&ft->lock);
    struct fd_entry *fe;
    __wasi_errno_t error = fd_table_get_entry(ft, fd, 0, 0, &fe);
    if (error != 0) {
      rwlock_unlock(&ft->lock);
      return error;
    }
  
    // Remove it from the file descriptor table.
    struct fd_object *fo;
    fd_table_detach(ft, fd, &fo);
    rwlock_unlock(&ft->lock);
    fd_object_release(fo);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_datasync(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_DATASYNC, 0);
    if (error != 0)
      return error;
  
  #if CONFIG_HAS_FDATASYNC
    int ret = fdatasync(fd_number(fo));
  #else
    int ret = fsync(fd_number(fo));
  #endif
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_pread(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const __wasi_iovec_t *iov,
      size_t iovcnt,
      __wasi_filesize_t offset,
      size_t *nread
  ) {
    if (iovcnt == 0)
      return __WASI_EINVAL;
  
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds,
        &fo, fd, __WASI_RIGHT_FD_READ, 0);
    if (error != 0)
      return error;
  
  #if CONFIG_HAS_PREADV
    ssize_t len =
        preadv(fd_number(fo), (const struct iovec *)iov, (int)iovcnt, (off_t)offset);
    fd_object_release(fo);
    if (len < 0)
      return convert_errno(errno);
    *nread = (size_t)len;
    return 0;
  #else
    if (iovcnt == 1) {
      ssize_t len = pread(fd_number(fo), iov->buf, iov->buf_len, offset);
      fd_object_release(fo);
      if (len < 0)
        return convert_errno(errno);
      *nread = len;
      return 0;
    } else {
      // Allocate a single buffer to fit all data.
      size_t totalsize = 0;
      for (size_t i = 0; i < iovcnt; ++i)
        totalsize += iov[i].buf_len;
      char *buf = wasm_runtime_malloc(totalsize);
      if (buf == NULL) {
        fd_object_release(fo);
        return __WASI_ENOMEM;
      }
  
      // Perform a single read operation.
      ssize_t len = pread(fd_number(fo), buf, totalsize, offset);
      fd_object_release(fo);
      if (len < 0) {
        wasm_runtime_free(buf);
        return convert_errno(errno);
      }
  
      // Copy data back to vectors.
      size_t bufoff = 0;
      for (size_t i = 0; i < iovcnt; ++i) {
        if (bufoff + iov[i].buf_len < len) {
          bh_memcpy_s(iov[i].buf, iov[i].buf_len, buf + bufoff, iov[i].buf_len);
          bufoff += iov[i].buf_len;
        } else {
          bh_memcpy_s(iov[i].buf, iov[i].buf_len, buf + bufoff, len - bufoff);
          break;
        }
      }
      wasm_runtime_free(buf);
      *nread = len;
      return 0;
    }
  #endif
  }
  
  __wasi_errno_t wasmtime_ssp_fd_pwrite(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const __wasi_ciovec_t *iov,
      size_t iovcnt,
      __wasi_filesize_t offset,
      size_t *nwritten
  ) {
    if (iovcnt == 0)
      return __WASI_EINVAL;
  
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds,
        &fo, fd, __WASI_RIGHT_FD_WRITE, 0);
    if (error != 0)
      return error;
  
    ssize_t len;
  #if CONFIG_HAS_PWRITEV
    len = pwritev(fd_number(fo), (const struct iovec *)iov, (int)iovcnt, (off_t)offset);
  #else
    if (iovcnt == 1) {
      len = pwrite(fd_number(fo), iov->buf, iov->buf_len, offset);
    } else {
      // Allocate a single buffer to fit all data.
      size_t totalsize = 0;
      for (size_t i = 0; i < iovcnt; ++i)
        totalsize += iov[i].buf_len;
      char *buf = wasm_runtime_malloc(totalsize);
      if (buf == NULL) {
        fd_object_release(fo);
        return __WASI_ENOMEM;
      }
      size_t bufoff = 0;
      for (size_t i = 0; i < iovcnt; ++i) {
        bh_memcpy_s(buf + bufoff, totalsize - bufoff,
                    iov[i].buf, iov[i].buf_len);
        bufoff += iov[i].buf_len;
      }
  
      // Perform a single write operation.
      len = pwrite(fd_number(fo), buf, totalsize, offset);
      wasm_runtime_free(buf);
    }
  #endif
    fd_object_release(fo);
    if (len < 0)
      return convert_errno(errno);
    *nwritten = (size_t)len;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_read(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const __wasi_iovec_t *iov,
      size_t iovcnt,
      size_t *nread
  ) {
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_READ, 0);
    if (error != 0)
      return error;
      
    ssize_t len = readv(fd_number(fo), (const struct iovec *)iov, (int)iovcnt);
    fd_object_release(fo);
    if (len < 0)
      return convert_errno(errno);
    *nread = (size_t)len;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_renumber(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
      struct fd_prestats *prestats,
  #endif
      __wasi_fd_t from,
      __wasi_fd_t to
  ) {
    // Don't allow renumbering over a pre-opened resource.
    // TODO: Eventually, we do want to permit this, once libpreopen in
    // userspace is capable of removing entries from its tables as well.
    {
      rwlock_rdlock(&prestats->lock);
      struct fd_prestat *prestat;
      __wasi_errno_t error = fd_prestats_get_entry(prestats, to, &prestat);
      if (error != 0) {
        error = fd_prestats_get_entry(prestats, from, &prestat);
      }
      rwlock_unlock(&prestats->lock);
      if (error == 0) {
        return __WASI_ENOTSUP;
      }
    }
  
    struct fd_table *ft = curfds;
    rwlock_wrlock(&ft->lock);
    struct fd_entry *fe_from;
    __wasi_errno_t error = fd_table_get_entry(ft, from, 0, 0, &fe_from);
    if (error != 0) {
      rwlock_unlock(&ft->lock);
      return error;
    }
    struct fd_entry *fe_to;
    error = fd_table_get_entry(ft, to, 0, 0, &fe_to);
    if (error != 0) {
      rwlock_unlock(&ft->lock);
      return error;
    }
  
    struct fd_object *fo;
    fd_table_detach(ft, to, &fo);
    refcount_acquire(&fe_from->object->refcount);
    fd_table_attach(ft, to, fe_from->object, fe_from->rights_base,
                    fe_from->rights_inheriting);
    fd_object_release(fo);
  
    // Remove the old fd from the file descriptor table.
    fd_table_detach(ft, from, &fo);
    fd_object_release(fo);
    --ft->used;
  
    rwlock_unlock(&ft->lock);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_seek(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filedelta_t offset,
      __wasi_whence_t whence,
      __wasi_filesize_t *newoffset
  ) {
    int nwhence;
    switch (whence) {
      case __WASI_WHENCE_CUR:
        nwhence = SEEK_CUR;
        break;
      case __WASI_WHENCE_END:
        nwhence = SEEK_END;
        break;
      case __WASI_WHENCE_SET:
        nwhence = SEEK_SET;
        break;
      default:
        return __WASI_EINVAL;
    }
  
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd,
                      offset == 0 && whence == __WASI_WHENCE_CUR
                          ? __WASI_RIGHT_FD_TELL
                          : __WASI_RIGHT_FD_SEEK | __WASI_RIGHT_FD_TELL,
                      0);
    if (error != 0)
      return error;
  
    off_t ret = lseek(fd_number(fo), offset, nwhence);
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    *newoffset = (__wasi_filesize_t)ret;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_tell(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filesize_t *newoffset
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_TELL, 0);
    if (error != 0)
      return error;
  
    off_t ret = lseek(fd_number(fo), 0, SEEK_CUR);
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    *newoffset = (__wasi_filesize_t)ret;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_fdstat_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_fdstat_t *buf
  ) {
    struct fd_table *ft = curfds;
    rwlock_rdlock(&ft->lock);
    struct fd_entry *fe;
    __wasi_errno_t error = fd_table_get_entry(ft, fd, 0, 0, &fe);
    if (error != 0) {
      rwlock_unlock(&ft->lock);
      return error;
    }
  
    // Extract file descriptor type and rights.
    struct fd_object *fo = fe->object;
    *buf = (__wasi_fdstat_t){
        .fs_filetype = fo->type,
        .fs_rights_base = fe->rights_base,
        .fs_rights_inheriting = fe->rights_inheriting,
    };
  
    // Fetch file descriptor flags.
    int ret;
    switch (fo->type) {
      default:
        ret = fcntl(fd_number(fo), F_GETFL);
        break;
    }
    rwlock_unlock(&ft->lock);
    if (ret < 0)
      return convert_errno(errno);
  
    if ((ret & O_APPEND) != 0)
      buf->fs_flags |= __WASI_FDFLAG_APPEND;
  #ifdef O_DSYNC
    if ((ret & O_DSYNC) != 0)
      buf->fs_flags |= __WASI_FDFLAG_DSYNC;
  #endif
    if ((ret & O_NONBLOCK) != 0)
      buf->fs_flags |= __WASI_FDFLAG_NONBLOCK;
  #ifdef O_RSYNC
    if ((ret & O_RSYNC) != 0)
      buf->fs_flags |= __WASI_FDFLAG_RSYNC;
  #endif
    if ((ret & O_SYNC) != 0)
      buf->fs_flags |= __WASI_FDFLAG_SYNC;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_fdstat_set_flags(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_fdflags_t fs_flags
  ) {
    int noflags = 0;
    if ((fs_flags & __WASI_FDFLAG_APPEND) != 0)
      noflags |= O_APPEND;
    if ((fs_flags & __WASI_FDFLAG_DSYNC) != 0)
  #ifdef O_DSYNC
      noflags |= O_DSYNC;
  #else
      noflags |= O_SYNC;
  #endif
    if ((fs_flags & __WASI_FDFLAG_NONBLOCK) != 0)
      noflags |= O_NONBLOCK;
    if ((fs_flags & __WASI_FDFLAG_RSYNC) != 0)
  #ifdef O_RSYNC
      noflags |= O_RSYNC;
  #else
      noflags |= O_SYNC;
  #endif
    if ((fs_flags & __WASI_FDFLAG_SYNC) != 0)
      noflags |= O_SYNC;
  
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_FDSTAT_SET_FLAGS, 0);
    if (error != 0)
      return error;
  
    int ret = fcntl(fd_number(fo), F_SETFL, noflags);
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_fdstat_set_rights(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_rights_t fs_rights_base,
      __wasi_rights_t fs_rights_inheriting
  ) {
    struct fd_table *ft = curfds;
    rwlock_wrlock(&ft->lock);
    struct fd_entry *fe;
    __wasi_errno_t error =
        fd_table_get_entry(ft, fd, fs_rights_base, fs_rights_inheriting, &fe);
    if (error != 0) {
      rwlock_unlock(&ft->lock);
      return error;
    }
  
    // Restrict the rights on the file descriptor.
    fe->rights_base = fs_rights_base;
    fe->rights_inheriting = fs_rights_inheriting;
    rwlock_unlock(&ft->lock);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_sync(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd
  ) {
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_SYNC, 0);
    if (error != 0)
      return error;
  
    int ret = fsync(fd_number(fo));
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_write(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const __wasi_ciovec_t *iov,
      size_t iovcnt,
      size_t *nwritten
  ) {
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_WRITE, 0);
    if (error != 0)
      return error;
  
    ssize_t len = writev(fd_number(fo), (const struct iovec *)iov, (int)iovcnt);
    fd_object_release(fo);
    if (len < 0)
      return convert_errno(errno);
    *nwritten = (size_t)len;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_advise(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filesize_t offset,
      __wasi_filesize_t len,
      __wasi_advice_t advice
  ) {
  #ifdef POSIX_FADV_NORMAL
    int nadvice;
    switch (advice) {
      case __WASI_ADVICE_DONTNEED:
        nadvice = POSIX_FADV_DONTNEED;
        break;
      case __WASI_ADVICE_NOREUSE:
        nadvice = POSIX_FADV_NOREUSE;
        break;
      case __WASI_ADVICE_NORMAL:
        nadvice = POSIX_FADV_NORMAL;
        break;
      case __WASI_ADVICE_RANDOM:
        nadvice = POSIX_FADV_RANDOM;
        break;
      case __WASI_ADVICE_SEQUENTIAL:
        nadvice = POSIX_FADV_SEQUENTIAL;
        break;
      case __WASI_ADVICE_WILLNEED:
        nadvice = POSIX_FADV_WILLNEED;
        break;
      default:
        return __WASI_EINVAL;
    }
  
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_ADVISE, 0);
    if (error != 0)
      return error;
  
    int ret = posix_fadvise(fd_number(fo), (off_t)offset, (off_t)len, nadvice);
    fd_object_release(fo);
    if (ret != 0)
      return convert_errno(ret);
    return 0;
  #else
    // Advisory information can safely be ignored if unsupported.
    switch (advice) {
      case __WASI_ADVICE_DONTNEED:
      case __WASI_ADVICE_NOREUSE:
      case __WASI_ADVICE_NORMAL:
      case __WASI_ADVICE_RANDOM:
      case __WASI_ADVICE_SEQUENTIAL:
      case __WASI_ADVICE_WILLNEED:
        break;
      default:
        return __WASI_EINVAL;
    }
  
    // At least check for file descriptor existence.
    struct fd_table *ft = curfds;
    rwlock_rdlock(&ft->lock);
    struct fd_entry *fe;
    __wasi_errno_t error =
        fd_table_get_entry(ft, fd, __WASI_RIGHT_FD_ADVISE, 0, &fe);
    rwlock_unlock(&ft->lock);
    return error;
  #endif
  }
  
  __wasi_errno_t wasmtime_ssp_fd_allocate(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filesize_t offset,
      __wasi_filesize_t len
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_ALLOCATE, 0);
    if (error != 0)
      return error;
  
  #if CONFIG_HAS_POSIX_FALLOCATE
    int ret = posix_fallocate(fd_number(fo), (off_t)offset, (off_t)len);
  #else
    // At least ensure that the file is grown to the right size.
    // TODO(ed): See if this can somehow be implemented without any race
    // conditions. We may end up shrinking the file right now.
    struct stat sb;
    int ret = fstat(fd_number(fo), &sb);
    if (ret == 0 && sb.st_size < offset + len)
      ret = ftruncate(fd_number(fo), offset + len);
  #endif
  
    fd_object_release(fo);
    if (ret != 0)
      return convert_errno(ret);
    return 0;
  }
  
  // Reads the entire contents of a symbolic link, returning the contents
  // in an allocated buffer. The allocated buffer is large enough to fit
  // at least one extra byte, so the caller may append a trailing slash to
  // it. This is needed by path_get().
  static char *readlinkat_dup(
      int fd,
      const char *path,
      size_t *p_len
  ) {
    char *buf = NULL;
    size_t len = 32;
    size_t len_org = len;
  
    for (;;) {
      char *newbuf = wasm_runtime_malloc((uint32)len);
  
      if (newbuf == NULL) {
        if (buf)
          wasm_runtime_free(buf);
        return NULL;
      }
  
      if (buf != NULL) {
        bh_memcpy_s(newbuf, (uint32)len, buf, (uint32)len_org);
        wasm_runtime_free(buf);
      }
  
      buf = newbuf;
      ssize_t ret = readlinkat(fd, path, buf, len);
      if (ret < 0) {
        wasm_runtime_free(buf);
        return NULL;
      }
      if ((size_t)ret + 1 < len) {
        buf[ret] = '\0';
        *p_len = len;
        return buf;
      }
      len_org = len;
      len *= 2;
    }
  }
  
  // Creates a lease to a file descriptor and pathname pair. If the
  // operating system does not implement Capsicum, it also normalizes the
  // pathname to ensure the target path is placed underneath the
  // directory.
  __wasi_errno_t path_get(
      struct fd_table *curfds,
      struct path_access *pa,
      __wasi_fd_t fd,
      __wasi_lookupflags_t flags,
      const char *upath,
      size_t upathlen,
      __wasi_rights_t rights_base,
      __wasi_rights_t rights_inheriting,
      bool needs_final_component
  ) TRYLOCKS_EXCLUSIVE(0, pa->fd_object->refcount) {
    char *path = str_nullterminate(upath, upathlen);
    if (path == NULL)
      return convert_errno(errno);
  
    // Fetch the directory file descriptor.
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, rights_base, rights_inheriting);
    if (error != 0) {
      wasm_runtime_free(path);
      return error;
    }
  
  #if CONFIG_HAS_CAP_ENTER
    // Rely on the kernel to constrain access to automatically constrain
    // access to files stored underneath this directory.
    pa->fd = fd_number(fo);
    pa->path = pa->path_start = path;
    pa->follow = (flags & __WASI_LOOKUP_SYMLINK_FOLLOW) != 0;
    pa->fd_object = fo;
    return 0;
  #else
    // The implementation provides no mechanism to constrain lookups to a
    // directory automatically. Emulate this logic by resolving the
    // pathname manually.
  
    // Stack of directory file descriptors. Index 0 always corresponds
    // with the directory provided to this function. Entering a directory
    // causes a file descriptor to be pushed, while handling ".." entries
    // causes an entry to be popped. Index 0 cannot be popped, as this
    // would imply escaping the base directory.
    int fds[128];
    fds[0] = fd_number(fo);
    size_t curfd = 0;
  
    // Stack of pathname strings used for symlink expansion. By using a
    // stack, there is no need to concatenate any pathname strings while
    // expanding symlinks.
    char *paths[32];
    char *paths_start[32];
    paths[0] = paths_start[0] = path;
    size_t curpath = 0;
    size_t expansions = 0;
    char *symlink;
    size_t symlink_len;
  
    for (;;) {
      // Extract the next pathname component from 'paths[curpath]', null
      // terminate it and store it in 'file'. 'ends_with_slashes' stores
      // whether the pathname component is followed by one or more
      // trailing slashes, as this requires it to be a directory.
      char *file = paths[curpath];
      char *file_end = file + strcspn(file, "/");
      paths[curpath] = file_end + strspn(file_end, "/");
      bool ends_with_slashes = *file_end == '/';
      *file_end = '\0';
  
      // Test for empty pathname strings and absolute paths.
      if (file == file_end) {
        error = ends_with_slashes ? __WASI_ENOTCAPABLE : __WASI_ENOENT;
        goto fail;
      }
  
      if (strcmp(file, ".") == 0) {
        // Skip component.
      } else if (strcmp(file, "..") == 0) {
        // Pop a directory off the stack.
        if (curfd == 0) {
          // Attempted to go to parent directory of the directory file
          // descriptor.
          error = __WASI_ENOTCAPABLE;
          goto fail;
        }
        close(fds[curfd--]);
      } else if (curpath > 0 || *paths[curpath] != '\0' ||
                 (ends_with_slashes && !needs_final_component)) {
        // A pathname component whose name we're not interested in that is
        // followed by a slash or is followed by other pathname
        // components. In other words, a pathname component that must be a
        // directory. First attempt to obtain a directory file descriptor
        // for it.
        int newdir =
  #ifdef O_SEARCH
            openat(fds[curfd], file, O_SEARCH | O_DIRECTORY | O_NOFOLLOW);
  #else
            openat(fds[curfd], file, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
  #endif
        if (newdir != -1) {
          // Success. Push it onto the directory stack.
          if (curfd + 1 == sizeof(fds) / sizeof(fds[0])) {
            close(newdir);
            error = __WASI_ENAMETOOLONG;
            goto fail;
          }
          fds[++curfd] = newdir;
        } else {
          // Failed to open it. Attempt symlink expansion.
          if (errno != ELOOP && errno != EMLINK && errno != ENOTDIR) {
            error = convert_errno(errno);
            goto fail;
          }
          symlink = readlinkat_dup(fds[curfd], file, &symlink_len);
          if (symlink != NULL)
            goto push_symlink;
  
          // readlink returns EINVAL if the path isn't a symlink. In that case,
          // it's more informative to return ENOTDIR.
          if (errno == EINVAL)
            errno = ENOTDIR;
  
          error = convert_errno(errno);
          goto fail;
        }
      } else {
        // The final pathname component. Depending on whether it ends with
        // a slash or the symlink-follow flag is set, perform symlink
        // expansion.
        if (ends_with_slashes ||
            (flags & __WASI_LOOKUP_SYMLINK_FOLLOW) != 0) {
          symlink = readlinkat_dup(fds[curfd], file, &symlink_len);
          if (symlink != NULL)
            goto push_symlink;
          if (errno != EINVAL && errno != ENOENT) {
            error = convert_errno(errno);
            goto fail;
          }
        }
  
        // Not a symlink, meaning we're done. Return the filename,
        // together with the directory containing this file.
        //
        // If the file was followed by a trailing slash, we must retain
        // it, to ensure system calls properly return ENOTDIR.
        // Unfortunately, this opens up a race condition, because this
        // means that users of path_get() will perform symlink expansion a
        // second time. There is nothing we can do to mitigate this, as
        // far as I know.
        if (ends_with_slashes)
          *file_end = '/';
        pa->path = file;
        pa->path_start = paths_start[0];
        goto success;
      }
  
      if (*paths[curpath] == '\0') {
        if (curpath == 0) {
          // No further pathname components to process. We may end up here
          // when called on paths like ".", "a/..", but also if the path
          // had trailing slashes and the caller is not interested in the
          // name of the pathname component.
          wasm_runtime_free(paths_start[0]);
          pa->path = ".";
          pa->path_start = NULL;
          goto success;
        }
  
        // Finished expanding symlink. Continue processing along the
        // original path.
        wasm_runtime_free(paths_start[curpath--]);
      }
      continue;
  
    push_symlink:
      // Prevent infinite loops by placing an upper limit on the number of
      // symlink expansions.
      if (++expansions == 128) {
        wasm_runtime_free(symlink);
        error = __WASI_ELOOP;
        goto fail;
      }
  
      if (*paths[curpath] == '\0') {
        // The original path already finished processing. Replace it by
        // this symlink entirely.
        wasm_runtime_free(paths_start[curpath]);
      } else if (curpath + 1 == sizeof(paths) / sizeof(paths[0])) {
        // Too many nested symlinks. Stop processing.
        wasm_runtime_free(symlink);
        error = __WASI_ELOOP;
        goto fail;
      } else {
        // The original path still has components left. Retain the
        // components that remain, so we can process them afterwards.
        ++curpath;
      }
  
      // Append a trailing slash to the symlink if the path leading up to
      // it also contained one. Otherwise we would not throw ENOTDIR if
      // the target is not a directory.
      if (ends_with_slashes)
        bh_strcat_s(symlink, (uint32)symlink_len, "/");
      paths[curpath] = paths_start[curpath] = symlink;
    }
  
  success:
    // Return the lease. Close all directories, except the one the caller
    // needs to use.
    for (size_t i = 1; i < curfd; ++i)
      close(fds[i]);
    pa->fd = fds[curfd];
    pa->follow = false;
    pa->fd_object = fo;
    return 0;
  
  fail:
    // Failure. Free all resources.
    for (size_t i = 1; i <= curfd; ++i)
      close(fds[i]);
    for (size_t i = 0; i <= curpath; ++i)
      wasm_runtime_free(paths_start[i]);
    fd_object_release(fo);
    return error;
  #endif
  }
  
  static __wasi_errno_t path_get_nofollow(
      struct fd_table *curfds,
      struct path_access *pa,
      __wasi_fd_t fd,
      const char *path,
      size_t pathlen,
      __wasi_rights_t rights_base,
      __wasi_rights_t rights_inheriting,
      bool needs_final_component
  ) TRYLOCKS_EXCLUSIVE(0, pa->fd_object->refcount) {
    __wasi_lookupflags_t flags = 0;
    return path_get(curfds, pa, fd, flags, path, pathlen, rights_base, rights_inheriting,
                    needs_final_component);
  }
  
  void path_put(
      struct path_access *pa
  ) UNLOCKS(pa->fd_object->refcount) {
    wasm_runtime_free(pa->path_start);
    if (fd_number(pa->fd_object) != pa->fd)
      close(pa->fd);
    fd_object_release(pa->fd_object);
  }
  
  __wasi_errno_t wasmtime_ssp_path_create_directory(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const char *path,
      size_t pathlen
  ) {
    struct path_access pa;
    __wasi_errno_t error =
        path_get_nofollow(curfds, &pa, fd, path, pathlen,
                          __WASI_RIGHT_PATH_CREATE_DIRECTORY, 0, true);
    if (error != 0)
      return error;
  
    int ret = mkdirat(pa.fd, pa.path, 0777);
    path_put(&pa);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  static bool
  validate_path(const char *path, struct fd_prestats *pt)
  {
      size_t i;
      char path_resolved[PATH_MAX], prestat_dir_resolved[PATH_MAX];
      char *path_real, *prestat_dir_real;
  
      if (!(path_real = realpath(path, path_resolved)))
          /* path doesn't exist, creating a link to this file
             is allowed: if this file is to be created in
             the future, WASI will strictly check whether it
             can be created or not. */
          return true;
  
      for (i = 0; i < pt->size; i++) {
          if (pt->prestats[i].dir) {
              if (!(prestat_dir_real = realpath(pt->prestats[i].dir,
                                                prestat_dir_resolved)))
                  return false;
              if (!strncmp(path_real, prestat_dir_real, strlen(prestat_dir_real)))
                  return true;
          }
      }
  
      return false;
  }
  
  __wasi_errno_t wasmtime_ssp_path_link(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
      struct fd_prestats *prestats,
  #endif
      __wasi_fd_t old_fd,
      __wasi_lookupflags_t old_flags,
      const char *old_path,
      size_t old_path_len,
      __wasi_fd_t new_fd,
      const char *new_path,
      size_t new_path_len
  ) {
    struct path_access old_pa;
    __wasi_errno_t error = path_get(curfds, &old_pa, old_fd, old_flags, old_path, old_path_len,
                                    __WASI_RIGHT_PATH_LINK_SOURCE, 0, false);
    if (error != 0)
      return error;
  
    struct path_access new_pa;
    error = path_get_nofollow(curfds, &new_pa, new_fd, new_path, new_path_len,
                              __WASI_RIGHT_PATH_LINK_TARGET, 0, true);
    if (error != 0) {
      path_put(&old_pa);
      return error;
    }
  
    rwlock_rdlock(&prestats->lock);
    if (!validate_path(old_pa.path, prestats)
        || !validate_path(new_pa.path, prestats)) {
        rwlock_unlock(&prestats->lock);
        return __WASI_EBADF;
    }
    rwlock_unlock(&prestats->lock);
  
    int ret = linkat(old_pa.fd, old_pa.path, new_pa.fd, new_pa.path,
                     old_pa.follow ? AT_SYMLINK_FOLLOW : 0);
    if (ret < 0 && errno == ENOTSUP && !old_pa.follow) {
      // OS X doesn't allow creating hardlinks to symbolic links.
      // Duplicate the symbolic link instead.
      size_t target_len;
      char *target = readlinkat_dup(old_pa.fd, old_pa.path, &target_len);
      if (target != NULL) {
        bh_assert(target[target_len] == '\0');
        rwlock_rdlock(&prestats->lock);
        if (!validate_path(target, prestats)) {
            rwlock_unlock(&prestats->lock);
            wasm_runtime_free(target);
            return __WASI_EBADF;
        }
        rwlock_unlock(&prestats->lock);
        ret = symlinkat(target, new_pa.fd, new_pa.path);
        wasm_runtime_free(target);
      }
    }
    path_put(&old_pa);
    path_put(&new_pa);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  // Inserts a numerical file descriptor into the file descriptor table.
  __wasi_errno_t fd_table_insert_fd(
      struct fd_table *ft,
      int in,
      __wasi_filetype_t type,
      __wasi_rights_t rights_base,
      __wasi_rights_t rights_inheriting,
      __wasi_fd_t *out
  ) REQUIRES_UNLOCKED(ft->lock) {
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_new(type, &fo);
    if (error != 0) {
      close(in);
      return error;
    }
    fo->number = in;
    if (type == __WASI_FILETYPE_DIRECTORY) {
      mutex_init(&fo->directory.lock);
      fo->directory.handle = NULL;
    }
    return fd_table_insert(ft, fo, rights_base, rights_inheriting, out);
  }
  
  __wasi_errno_t wasmtime_ssp_path_open(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t dirfd,
      __wasi_lookupflags_t dirflags,
      const char *path,
      size_t pathlen,
      __wasi_oflags_t oflags,
      __wasi_rights_t fs_rights_base,
      __wasi_rights_t fs_rights_inheriting,
      __wasi_fdflags_t fs_flags,
      __wasi_fd_t *fd
  ) {
    // Rights that should be installed on the new file descriptor.
    __wasi_rights_t rights_base = fs_rights_base;
    __wasi_rights_t rights_inheriting = fs_rights_inheriting;
  
    // Which open() mode should be used to satisfy the needed rights.
    bool read =
        (rights_base & (__WASI_RIGHT_FD_READ | __WASI_RIGHT_FD_READDIR)) != 0;
    bool write =
        (rights_base & (__WASI_RIGHT_FD_DATASYNC | __WASI_RIGHT_FD_WRITE |
                        __WASI_RIGHT_FD_ALLOCATE |
                        __WASI_RIGHT_FD_FILESTAT_SET_SIZE)) != 0;
    int noflags = write ? read ? O_RDWR : O_WRONLY : O_RDONLY;
  
    // Which rights are needed on the directory file descriptor.
    __wasi_rights_t needed_base = __WASI_RIGHT_PATH_OPEN;
    __wasi_rights_t needed_inheriting = rights_base | rights_inheriting;
  
    // Convert open flags.
    if ((oflags & __WASI_O_CREAT) != 0) {
      noflags |= O_CREAT;
      needed_base |= __WASI_RIGHT_PATH_CREATE_FILE;
    }
    if ((oflags & __WASI_O_DIRECTORY) != 0)
      noflags |= O_DIRECTORY;
    if ((oflags & __WASI_O_EXCL) != 0)
      noflags |= O_EXCL;
    if ((oflags & __WASI_O_TRUNC) != 0) {
      noflags |= O_TRUNC;
      needed_base |= __WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
    }
  
    // Convert file descriptor flags.
    if ((fs_flags & __WASI_FDFLAG_APPEND) != 0)
      noflags |= O_APPEND;
    if ((fs_flags & __WASI_FDFLAG_DSYNC) != 0) {
  #ifdef O_DSYNC
      noflags |= O_DSYNC;
  #else
      noflags |= O_SYNC;
  #endif
      needed_inheriting |= __WASI_RIGHT_FD_DATASYNC;
    }
    if ((fs_flags & __WASI_FDFLAG_NONBLOCK) != 0)
      noflags |= O_NONBLOCK;
    if ((fs_flags & __WASI_FDFLAG_RSYNC) != 0) {
  #ifdef O_RSYNC
      noflags |= O_RSYNC;
  #else
      noflags |= O_SYNC;
  #endif
      needed_inheriting |= __WASI_RIGHT_FD_SYNC;
    }
    if ((fs_flags & __WASI_FDFLAG_SYNC) != 0) {
      noflags |= O_SYNC;
      needed_inheriting |= __WASI_RIGHT_FD_SYNC;
    }
    if (write && (noflags & (O_APPEND | O_TRUNC)) == 0)
      needed_inheriting |= __WASI_RIGHT_FD_SEEK;
  
    struct path_access pa;
    __wasi_errno_t error =
        path_get(curfds, &pa, dirfd, dirflags, path, pathlen, needed_base, needed_inheriting,
                 (oflags & __WASI_O_CREAT) != 0);
    if (error != 0)
      return error;
    if (!pa.follow)
      noflags |= O_NOFOLLOW;
  
    int nfd = openat(pa.fd, pa.path, noflags, 0666);
    if (nfd < 0) {
      int openat_errno = errno;
      // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket.
      if (openat_errno == ENXIO) {
        struct stat sb;
        int ret =
            fstatat(pa.fd, pa.path, &sb, pa.follow ? 0 : AT_SYMLINK_NOFOLLOW);
        path_put(&pa);
        return ret == 0 && S_ISSOCK(sb.st_mode) ? __WASI_ENOTSUP
                                                : __WASI_ENXIO;
      }
      // Linux returns ENOTDIR instead of ELOOP when using O_NOFOLLOW|O_DIRECTORY
      // on a symlink.
      if (openat_errno == ENOTDIR && (noflags & (O_NOFOLLOW | O_DIRECTORY)) != 0) {
        struct stat sb;
        int ret = fstatat(pa.fd, pa.path, &sb, AT_SYMLINK_NOFOLLOW);
        if (S_ISLNK(sb.st_mode)) {
          path_put(&pa);
          return __WASI_ELOOP;
        }
        (void)ret;
      }
      path_put(&pa);
      // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
      // a symlink.
      if (!pa.follow && openat_errno == EMLINK)
        return __WASI_ELOOP;
      return convert_errno(openat_errno);
    }
    path_put(&pa);
  
    // Determine the type of the new file descriptor and which rights
    // contradict with this type.
    __wasi_filetype_t type;
    __wasi_rights_t max_base, max_inheriting;
    error = fd_determine_type_rights(nfd, &type, &max_base, &max_inheriting);
    if (error != 0) {
      close(nfd);
      return error;
    }
  
    {
      struct stat sb;
  
      if (fstat(nfd, &sb) < 0) {
        close(nfd);
        return convert_errno(errno);
      }
  
      if (S_ISDIR(sb.st_mode))
        rights_base |= RIGHTS_DIRECTORY_BASE;
      else if (S_ISREG(sb.st_mode))
        rights_base |= RIGHTS_REGULAR_FILE_BASE;
    }
  
    return fd_table_insert_fd(curfds, nfd, type, rights_base & max_base,
                              rights_inheriting & max_inheriting, fd);
  }
  
  // Copies out directory entry metadata or filename, potentially
  // truncating it in the process.
  static void fd_readdir_put(
      void *buf,
      size_t bufsize,
      size_t *bufused,
      const void *elem,
      size_t elemsize
  ) {
    size_t bufavail = bufsize - *bufused;
    if (elemsize > bufavail)
      elemsize = bufavail;
    bh_memcpy_s((char *)buf + *bufused, (uint32)bufavail, elem, (uint32)elemsize);
    *bufused += elemsize;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_readdir(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      void *buf,
      size_t nbyte,
      __wasi_dircookie_t cookie,
      size_t *bufused
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_READDIR, 0);
    if (error != 0) {
      return error;
    }
  
    // Create a directory handle if none has been opened yet.
    mutex_lock(&fo->directory.lock);
    DIR *dp = fo->directory.handle;
    if (dp == NULL) {
      dp = fdopendir(fd_number(fo));
      if (dp == NULL) {
        mutex_unlock(&fo->directory.lock);
        fd_object_release(fo);
        return convert_errno(errno);
      }
      fo->directory.handle = dp;
      fo->directory.offset = __WASI_DIRCOOKIE_START;
    }
  
    // Seek to the right position if the requested offset does not match
    // the current offset.
    if (fo->directory.offset != cookie) {
      if (cookie == __WASI_DIRCOOKIE_START)
        rewinddir(dp);
      else
        seekdir(dp, (long)cookie);
      fo->directory.offset = cookie;
    }
  
    *bufused = 0;
    while (*bufused < nbyte) {
      // Read the next directory entry.
      errno = 0;
      struct dirent *de = readdir(dp);
      if (de == NULL) {
        mutex_unlock(&fo->directory.lock);
        fd_object_release(fo);
        return errno == 0 || *bufused > 0 ? 0 : convert_errno(errno);
      }
      fo->directory.offset = (__wasi_dircookie_t)telldir(dp);
  
      // Craft a directory entry and copy that back.
      size_t namlen = strlen(de->d_name);
      __wasi_dirent_t cde = {
          .d_next = fo->directory.offset,
          .d_ino = de->d_ino,
          .d_namlen = (uint32)namlen,
      };
      switch (de->d_type) {
        case DT_BLK:
          cde.d_type = __WASI_FILETYPE_BLOCK_DEVICE;
          break;
        case DT_CHR:
          cde.d_type = __WASI_FILETYPE_CHARACTER_DEVICE;
          break;
        case DT_DIR:
          cde.d_type = __WASI_FILETYPE_DIRECTORY;
          break;
        case DT_FIFO:
          cde.d_type = __WASI_FILETYPE_SOCKET_STREAM;
          break;
        case DT_LNK:
          cde.d_type = __WASI_FILETYPE_SYMBOLIC_LINK;
          break;
        case DT_REG:
          cde.d_type = __WASI_FILETYPE_REGULAR_FILE;
          break;
  #ifdef DT_SOCK
        case DT_SOCK:
          // Technically not correct, but good enough.
          cde.d_type = __WASI_FILETYPE_SOCKET_STREAM;
          break;
  #endif
        default:
          cde.d_type = __WASI_FILETYPE_UNKNOWN;
          break;
      }
      fd_readdir_put(buf, nbyte, bufused, &cde, sizeof(cde));
      fd_readdir_put(buf, nbyte, bufused, de->d_name, namlen);
    }
    mutex_unlock(&fo->directory.lock);
    fd_object_release(fo);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_readlink(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const char *path,
      size_t pathlen,
      char *buf,
      size_t bufsize,
      size_t *bufused
  ) {
    struct path_access pa;
    __wasi_errno_t error = path_get_nofollow(curfds,
        &pa, fd, path, pathlen, __WASI_RIGHT_PATH_READLINK, 0, false);
    if (error != 0)
      return error;
  
    // Linux requires that the buffer size is positive. whereas POSIX does
    // not. Use a fake buffer to store the results if the size is zero.
    char fakebuf[1];
    ssize_t len = readlinkat(pa.fd, pa.path, bufsize == 0 ? fakebuf : buf,
                             bufsize == 0 ? sizeof(fakebuf) : bufsize);
    path_put(&pa);
    if (len < 0)
      return convert_errno(errno);
    *bufused = (size_t)len < bufsize ? (size_t)len : bufsize;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_rename(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t old_fd,
      const char *old_path,
      size_t old_path_len,
      __wasi_fd_t new_fd,
      const char *new_path,
      size_t new_path_len
  ) {
    struct path_access old_pa;
    __wasi_errno_t error = path_get_nofollow(curfds, &old_pa, old_fd, old_path, old_path_len,
                                             __WASI_RIGHT_PATH_RENAME_SOURCE, 0, true);
    if (error != 0)
      return error;
  
    struct path_access new_pa;
    error = path_get_nofollow(curfds, &new_pa, new_fd, new_path, new_path_len,
                              __WASI_RIGHT_PATH_RENAME_TARGET, 0, true);
    if (error != 0) {
      path_put(&old_pa);
      return error;
    }
  
    int ret = renameat(old_pa.fd, old_pa.path, new_pa.fd, new_pa.path);
    path_put(&old_pa);
    path_put(&new_pa);
    if (ret < 0) {
      return convert_errno(errno);
    }
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_filestat_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filestat_t *buf
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_FILESTAT_GET, 0);
    if (error != 0)
      return error;
  
    int ret;
    switch (fo->type) {
      default: {
        struct stat sb;
        ret = fstat(fd_number(fo), &sb);
        convert_stat(&sb, buf);
        break;
      }
    }
    buf->st_filetype = fo->type;
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  static void convert_timestamp(
      __wasi_timestamp_t in,
      struct timespec *out
  ) {
    // Store sub-second remainder.
    out->tv_nsec = (__syscall_slong_t)(in % 1000000000);
    in /= 1000000000;
  
    // Clamp to the maximum in case it would overflow our system's time_t.
    out->tv_sec = (time_t)in < NUMERIC_MAX(time_t) ? (time_t)in : NUMERIC_MAX(time_t);
  }
  
  // Converts the provided timestamps and flags to a set of arguments for
  // futimens() and utimensat().
  static void convert_utimens_arguments(
      __wasi_timestamp_t st_atim,
      __wasi_timestamp_t st_mtim,
      __wasi_fstflags_t fstflags,
      struct timespec *ts
  ) {
    if ((fstflags & __WASI_FILESTAT_SET_ATIM_NOW) != 0) {
      ts[0].tv_nsec = UTIME_NOW;
    } else if ((fstflags & __WASI_FILESTAT_SET_ATIM) != 0) {
      convert_timestamp(st_atim, &ts[0]);
    } else {
      ts[0].tv_nsec = UTIME_OMIT;
    }
  
    if ((fstflags & __WASI_FILESTAT_SET_MTIM_NOW) != 0) {
      ts[1].tv_nsec = UTIME_NOW;
    } else if ((fstflags & __WASI_FILESTAT_SET_MTIM) != 0) {
      convert_timestamp(st_mtim, &ts[1]);
    } else {
      ts[1].tv_nsec = UTIME_OMIT;
    }
  }
  
  __wasi_errno_t wasmtime_ssp_fd_filestat_set_size(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_filesize_t st_size
  ) {
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_FILESTAT_SET_SIZE, 0);
    if (error != 0)
      return error;
  
    int ret = ftruncate(fd_number(fo), (off_t)st_size);
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_fd_filestat_set_times(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_timestamp_t st_atim,
      __wasi_timestamp_t st_mtim,
      __wasi_fstflags_t fstflags
  ) {
    if ((fstflags & ~(__WASI_FILESTAT_SET_ATIM | __WASI_FILESTAT_SET_ATIM_NOW |
                      __WASI_FILESTAT_SET_MTIM | __WASI_FILESTAT_SET_MTIM_NOW)) != 0)
      return __WASI_EINVAL;
  
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, fd, __WASI_RIGHT_FD_FILESTAT_SET_TIMES, 0);
    if (error != 0)
      return error;
  
    struct timespec ts[2];
    convert_utimens_arguments(st_atim, st_mtim, fstflags, ts);
    int ret = futimens(fd_number(fo), ts);
  
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_filestat_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_lookupflags_t flags,
      const char *path,
      size_t pathlen,
      __wasi_filestat_t *buf
  ) {
    struct path_access pa;
    __wasi_errno_t error =
        path_get(curfds, &pa, fd, flags, path, pathlen, __WASI_RIGHT_PATH_FILESTAT_GET, 0, false);
    if (error != 0)
      return error;
  
    struct stat sb;
    int ret = fstatat(pa.fd, pa.path, &sb, pa.follow ? 0 : AT_SYMLINK_NOFOLLOW);
    path_put(&pa);
    if (ret < 0)
      return convert_errno(errno);
    convert_stat(&sb, buf);
  
    // Convert the file type. In the case of sockets there is no way we
    // can easily determine the exact socket type.
    if (S_ISBLK(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_BLOCK_DEVICE;
    else if (S_ISCHR(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_CHARACTER_DEVICE;
    else if (S_ISDIR(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_DIRECTORY;
    else if (S_ISFIFO(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_SOCKET_STREAM;
    else if (S_ISLNK(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_SYMBOLIC_LINK;
    else if (S_ISREG(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_REGULAR_FILE;
    else if (S_ISSOCK(sb.st_mode))
      buf->st_filetype = __WASI_FILETYPE_SOCKET_STREAM;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_filestat_set_times(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      __wasi_lookupflags_t flags,
      const char *path,
      size_t pathlen,
      __wasi_timestamp_t st_atim,
      __wasi_timestamp_t st_mtim,
      __wasi_fstflags_t fstflags
  ) {
    if (((fstflags & ~(__WASI_FILESTAT_SET_ATIM | __WASI_FILESTAT_SET_ATIM_NOW |
                       __WASI_FILESTAT_SET_MTIM | __WASI_FILESTAT_SET_MTIM_NOW)) != 0)
        /* ATIM & ATIM_NOW can't be set at the same time */
        || ((fstflags & __WASI_FILESTAT_SET_ATIM) != 0
            && (fstflags & __WASI_FILESTAT_SET_ATIM_NOW) != 0)
        /* MTIM & MTIM_NOW can't be set at the same time */
        || ((fstflags & __WASI_FILESTAT_SET_MTIM) != 0
            && (fstflags & __WASI_FILESTAT_SET_MTIM_NOW) != 0))
      return __WASI_EINVAL;
  
    struct path_access pa;
    __wasi_errno_t error = path_get(curfds,
        &pa, fd, flags, path, pathlen, __WASI_RIGHT_PATH_FILESTAT_SET_TIMES, 0, false);
    if (error != 0)
      return error;
  
    struct timespec ts[2];
    convert_utimens_arguments(st_atim, st_mtim, fstflags, ts);
    int ret = utimensat(pa.fd, pa.path, ts, pa.follow ? 0 : AT_SYMLINK_NOFOLLOW);
  
    path_put(&pa);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_symlink(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
      struct fd_prestats *prestats,
  #endif
      const char *old_path,
      size_t old_path_len,
      __wasi_fd_t fd,
      const char *new_path,
      size_t new_path_len
  ) {
    char *target = str_nullterminate(old_path, old_path_len);
    if (target == NULL)
      return convert_errno(errno);
  
    struct path_access pa;
    __wasi_errno_t error = path_get_nofollow(curfds,
        &pa, fd, new_path, new_path_len, __WASI_RIGHT_PATH_SYMLINK, 0, true);
    if (error != 0) {
      wasm_runtime_free(target);
      return error;
    }
  
    rwlock_rdlock(&prestats->lock);
    if (!validate_path(target, prestats)) {
        rwlock_unlock(&prestats->lock);
        wasm_runtime_free(target);
        return __WASI_EBADF;
    }
    rwlock_unlock(&prestats->lock);
  
    int ret = symlinkat(target, pa.fd, pa.path);
    path_put(&pa);
    wasm_runtime_free(target);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_unlink_file(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const char *path,
      size_t pathlen
  ) {
    struct path_access pa;
    __wasi_errno_t error = path_get_nofollow(curfds,
        &pa, fd, path, pathlen, __WASI_RIGHT_PATH_UNLINK_FILE, 0, true);
    if (error != 0)
      return error;
  
    int ret = unlinkat(pa.fd, pa.path, 0);
  #ifndef __linux__
    // Non-Linux implementations may return EPERM when attempting to remove a
    // directory without REMOVEDIR. While that's what POSIX specifies, it's
    // less useful. Adjust this to EISDIR. It doesn't matter that this is not
    // atomic with the unlinkat, because if the file is removed and a directory
    // is created before fstatat sees it, we're racing with that change anyway
    // and unlinkat could have legitimately seen the directory if the race had
    // turned out differently.
    if (ret < 0 && errno == EPERM) {
      struct stat statbuf;
      if (fstatat(pa.fd, pa.path, &statbuf, AT_SYMLINK_NOFOLLOW) == 0 &&
          S_ISDIR(statbuf.st_mode)) {
        errno = EISDIR;
      }
    }
  #endif
    path_put(&pa);
    if (ret < 0) {
      return convert_errno(errno);
    }
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_path_remove_directory(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t fd,
      const char *path,
      size_t pathlen
  ) {
    struct path_access pa;
    __wasi_errno_t error = path_get_nofollow(curfds,
        &pa, fd, path, pathlen, __WASI_RIGHT_PATH_REMOVE_DIRECTORY, 0, true);
    if (error != 0)
      return error;
  
    int ret = unlinkat(pa.fd, pa.path, AT_REMOVEDIR);
  #ifndef __linux__
    // POSIX permits either EEXIST or ENOTEMPTY when the directory is not empty.
    // Map it to ENOTEMPTY.
    if (ret < 0 && errno == EEXIST) {
      errno = ENOTEMPTY;
    }
  #endif
    path_put(&pa);
    if (ret < 0) {
      return convert_errno(errno);
    }
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_poll_oneoff(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      const __wasi_subscription_t *in,
      __wasi_event_t *out,
      size_t nsubscriptions,
      size_t *nevents
  ) NO_LOCK_ANALYSIS {
    // Sleeping.
    if (nsubscriptions == 1 && in[0].type == __WASI_EVENTTYPE_CLOCK) {
      out[0] = (__wasi_event_t){
          .userdata = in[0].userdata,
          .type = in[0].type,
      };
  #if CONFIG_HAS_CLOCK_NANOSLEEP
      clockid_t clock_id;
      if (convert_clockid(in[0].u.clock.clock_id, &clock_id)) {
        struct timespec ts;
        convert_timestamp(in[0].u.clock.timeout, &ts);
        int ret = clock_nanosleep(
            clock_id,
            (in[0].u.clock.flags & __WASI_SUBSCRIPTION_CLOCK_ABSTIME) != 0
                ? TIMER_ABSTIME
                : 0,
            &ts, NULL);
        if (ret != 0)
          out[0].error = convert_errno(ret);
      } else {
        out[0].error = __WASI_ENOTSUP;
      }
  #else
      switch (in[0].u.clock.clock_id) {
        case __WASI_CLOCK_MONOTONIC:
          if ((in[0].u.clock.flags & __WASI_SUBSCRIPTION_CLOCK_ABSTIME) != 0) {
            // TODO(ed): Implement.
            fputs("Unimplemented absolute sleep on monotonic clock\n", stderr);
            out[0].error = __WASI_ENOSYS;
          } else {
            // Perform relative sleeps on the monotonic clock also using
            // nanosleep(). This is incorrect, but good enough for now.
            struct timespec ts;
            convert_timestamp(in[0].u.clock.timeout, &ts);
            nanosleep(&ts, NULL);
          }
          break;
        case __WASI_CLOCK_REALTIME:
          if ((in[0].u.clock.flags & __WASI_SUBSCRIPTION_CLOCK_ABSTIME) != 0) {
            // Sleeping to an absolute point in time can only be done
            // by waiting on a condition variable.
            struct mutex mutex;
            mutex_init(&mutex);
            struct cond cond;
            cond_init_realtime(&cond);
            mutex_lock(&mutex);
            cond_timedwait(&cond, &mutex, in[0].u.clock.timeout, true);
            mutex_unlock(&mutex);
            mutex_destroy(&mutex);
            cond_destroy(&cond);
          } else {
            // Relative sleeps can be done using nanosleep().
            struct timespec ts;
            convert_timestamp(in[0].u.clock.timeout, &ts);
            nanosleep(&ts, NULL);
          }
          break;
        default:
          out[0].error = __WASI_ENOTSUP;
          break;
      }
  #endif
      *nevents = 1;
      return 0;
    }
  
    // Last option: call into poll(). This can only be done in case all
    // subscriptions consist of __WASI_EVENTTYPE_FD_READ and
    // __WASI_EVENTTYPE_FD_WRITE entries. There may be up to one
    // __WASI_EVENTTYPE_CLOCK entry to act as a timeout. These are also
    // the subscriptions generate by cloudlibc's poll() and select().
    struct fd_object **fos = wasm_runtime_malloc((uint32)(nsubscriptions * sizeof(*fos)));
    if (fos == NULL)
      return __WASI_ENOMEM;
    struct pollfd *pfds = wasm_runtime_malloc((uint32)(nsubscriptions * sizeof(*pfds)));
    if (pfds == NULL) {
      wasm_runtime_free(fos);
      return __WASI_ENOMEM;
    }
  
    // Convert subscriptions to pollfd entries. Increase the reference
    // count on the file descriptors to ensure they remain valid across
    // the call to poll().
    struct fd_table *ft = curfds;
    rwlock_rdlock(&ft->lock);
    *nevents = 0;
    const __wasi_subscription_t *clock_subscription = NULL;
    for (size_t i = 0; i < nsubscriptions; ++i) {
      const __wasi_subscription_t *s = &in[i];
      switch (s->type) {
        case __WASI_EVENTTYPE_FD_READ:
        case __WASI_EVENTTYPE_FD_WRITE: {
          __wasi_errno_t error =
              fd_object_get_locked(&fos[i], ft, s->u.fd_readwrite.fd,
                                   __WASI_RIGHT_POLL_FD_READWRITE, 0);
          if (error == 0) {
            // Proper file descriptor on which we can poll().
            pfds[i] = (struct pollfd){
                .fd = fd_number(fos[i]),
                .events = s->type == __WASI_EVENTTYPE_FD_READ ? POLLRDNORM
                                                                : POLLWRNORM,
            };
          } else {
            // Invalid file descriptor or rights missing.
            fos[i] = NULL;
            pfds[i] = (struct pollfd){.fd = -1};
            out[(*nevents)++] = (__wasi_event_t){
                .userdata = s->userdata,
                .error = error,
                .type = s->type,
            };
          }
          break;
        }
        case __WASI_EVENTTYPE_CLOCK:
          if (clock_subscription == NULL &&
              (s->u.clock.flags & __WASI_SUBSCRIPTION_CLOCK_ABSTIME) == 0) {
            // Relative timeout.
            fos[i] = NULL;
            pfds[i] = (struct pollfd){.fd = -1};
            clock_subscription = s;
            break;
          }
        // Fallthrough.
        default:
          // Unsupported event.
          fos[i] = NULL;
          pfds[i] = (struct pollfd){.fd = -1};
          out[(*nevents)++] = (__wasi_event_t){
              .userdata = s->userdata,
              .error = __WASI_ENOSYS,
              .type = s->type,
          };
          break;
      }
    }
    rwlock_unlock(&ft->lock);
  
    // Use a zero-second timeout in case we've already generated events in
    // the loop above.
    int timeout;
    if (*nevents != 0) {
      timeout = 0;
    } else if (clock_subscription != NULL) {
      __wasi_timestamp_t ts = clock_subscription->u.clock.timeout / 1000000;
      timeout = ts > INT_MAX ? -1 : (int)ts;
    } else {
      timeout = -1;
    }
    int ret = poll(pfds, nsubscriptions, timeout);
  
    __wasi_errno_t error = 0;
    if (ret == -1) {
      error = convert_errno(errno);
    } else if (ret == 0 && *nevents == 0 && clock_subscription != NULL) {
      // No events triggered. Trigger the clock event.
      out[(*nevents)++] = (__wasi_event_t){
          .userdata = clock_subscription->userdata,
          .type = __WASI_EVENTTYPE_CLOCK,
      };
    } else {
      // Events got triggered. Don't trigger the clock event.
      for (size_t i = 0; i < nsubscriptions; ++i) {
        if (pfds[i].fd >= 0) {
          __wasi_filesize_t nbytes = 0;
          if (in[i].type == __WASI_EVENTTYPE_FD_READ) {
            int l;
            if (ioctl(fd_number(fos[i]), FIONREAD, &l) == 0)
              nbytes = (__wasi_filesize_t)l;
          }
          if ((pfds[i].revents & POLLNVAL) != 0) {
            // Bad file descriptor. This normally cannot occur, as
            // referencing the file descriptor object will always ensure
            // the descriptor is valid. Still, macOS may sometimes return
            // this on FIFOs when reaching end-of-file.
            out[(*nevents)++] = (__wasi_event_t){
                .userdata = in[i].userdata,
  #ifdef __APPLE__
                .u.fd_readwrite.nbytes = nbytes,
                .u.fd_readwrite.flags = __WASI_EVENT_FD_READWRITE_HANGUP,
  #else
                .error = __WASI_EBADF,
  #endif
                .type = in[i].type,
            };
          } else if ((pfds[i].revents & POLLERR) != 0) {
            // File descriptor is in an error state.
            out[(*nevents)++] = (__wasi_event_t){
                .userdata = in[i].userdata,
                .error = __WASI_EIO,
                .type = in[i].type,
            };
          } else if ((pfds[i].revents & POLLHUP) != 0) {
            // End-of-file.
            out[(*nevents)++] = (__wasi_event_t){
                .userdata = in[i].userdata,
                .type = in[i].type,
                .u.fd_readwrite.nbytes = nbytes,
                .u.fd_readwrite.flags = __WASI_EVENT_FD_READWRITE_HANGUP,
            };
          } else if ((pfds[i].revents & (POLLRDNORM | POLLWRNORM)) != 0) {
            // Read or write possible.
            out[(*nevents)++] = (__wasi_event_t){
                .userdata = in[i].userdata,
                .type = in[i].type,
                .u.fd_readwrite.nbytes = nbytes,
            };
          }
        }
      }
    }
  
    for (size_t i = 0; i < nsubscriptions; ++i)
      if (fos[i] != NULL)
        fd_object_release(fos[i]);
    wasm_runtime_free(fos);
    wasm_runtime_free(pfds);
    return error;
  }
  
  void wasmtime_ssp_proc_exit(
      __wasi_exitcode_t rval
  ) {
    _Exit((int32)rval);
  }
  
  __wasi_errno_t wasmtime_ssp_proc_raise(
      __wasi_signal_t sig
  ) {
    static const int signals[] = {
  #define X(v) [__WASI_##v] = v
        X(SIGABRT), X(SIGALRM), X(SIGBUS), X(SIGCHLD), X(SIGCONT), X(SIGFPE),
        X(SIGHUP),  X(SIGILL),  X(SIGINT), X(SIGKILL), X(SIGPIPE), X(SIGQUIT),
        X(SIGSEGV), X(SIGSTOP), X(SIGSYS), X(SIGTERM), X(SIGTRAP), X(SIGTSTP),
        X(SIGTTIN), X(SIGTTOU), X(SIGURG), X(SIGUSR1), X(SIGUSR2), X(SIGVTALRM),
        X(SIGXCPU), X(SIGXFSZ),
  #undef X
    };
    if (sig >= sizeof(signals) / sizeof(signals[0]) || signals[sig] == 0)
      return __WASI_EINVAL;
  
  #if CONFIG_TLS_USE_GSBASE
    // TLS on OS X depends on installing a SIGSEGV handler. Reset SIGSEGV
    // to the default action before raising.
    if (sig == __WASI_SIGSEGV) {
      struct sigaction sa = {
          .sa_handler = SIG_DFL,
      };
      sigemptyset(&sa.sa_mask);
      sigaction(SIGSEGV, &sa, NULL);
    }
  #endif
  
    if (raise(signals[sig]) < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_random_get(
      void *buf,
      size_t nbyte
  ) {
    random_buf(buf, nbyte);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_sock_recv(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t sock,
      const __wasi_iovec_t *ri_data,
      size_t ri_data_len,
      __wasi_riflags_t ri_flags,
      size_t *ro_datalen,
      __wasi_roflags_t *ro_flags
  ) {
    // Convert input to msghdr.
    struct msghdr hdr = {
        .msg_iov = (struct iovec *)ri_data,
        .msg_iovlen = ri_data_len,
    };
    int nflags = 0;
    if ((ri_flags & __WASI_SOCK_RECV_PEEK) != 0)
      nflags |= MSG_PEEK;
    if ((ri_flags & __WASI_SOCK_RECV_WAITALL) != 0)
      nflags |= MSG_WAITALL;
  
    struct fd_object *fo;
    __wasi_errno_t error = fd_object_get(curfds, &fo, sock, __WASI_RIGHT_FD_READ, 0);
    if (error != 0) {
      return error;
    }
  
    ssize_t datalen = recvmsg(fd_number(fo), &hdr, nflags);
    fd_object_release(fo);
    if (datalen < 0) {
      return convert_errno(errno);
    }
  
  
    // Convert msghdr to output.
    *ro_datalen = (size_t)datalen;
    *ro_flags = 0;
    if ((hdr.msg_flags & MSG_TRUNC) != 0)
      *ro_flags |= __WASI_SOCK_RECV_DATA_TRUNCATED;
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_sock_send(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t sock,
      const __wasi_ciovec_t *si_data,
      size_t si_data_len,
      __wasi_siflags_t si_flags,
      size_t *so_datalen
  ) NO_LOCK_ANALYSIS {
    // Convert input to msghdr.
    struct msghdr hdr = {
        .msg_iov = (struct iovec *)si_data,
        .msg_iovlen = si_data_len,
    };
  
    // Attach file descriptors if present.
    __wasi_errno_t error;
  
    // Send message.
    struct fd_object *fo;
    error = fd_object_get(curfds, &fo, sock, __WASI_RIGHT_FD_WRITE, 0);
    if (error != 0)
      goto out;
    ssize_t len = sendmsg(fd_number(fo), &hdr, 0);
    fd_object_release(fo);
    if (len < 0) {
      error = convert_errno(errno);
    } else {
      *so_datalen = (size_t)len;
    }
  
  out:
    return error;
  }
  
  __wasi_errno_t wasmtime_ssp_sock_shutdown(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
      struct fd_table *curfds,
  #endif
      __wasi_fd_t sock,
      __wasi_sdflags_t how
  ) {
    int nhow;
    switch (how) {
      case __WASI_SHUT_RD:
        nhow = SHUT_RD;
        break;
      case __WASI_SHUT_WR:
        nhow = SHUT_WR;
        break;
      case __WASI_SHUT_RD | __WASI_SHUT_WR:
        nhow = SHUT_RDWR;
        break;
      default:
        return __WASI_EINVAL;
    }
  
    struct fd_object *fo;
    __wasi_errno_t error =
        fd_object_get(curfds, &fo, sock, __WASI_RIGHT_SOCK_SHUTDOWN, 0);
    if (error != 0)
      return error;
  
    int ret = shutdown(fd_number(fo), nhow);
    fd_object_release(fo);
    if (ret < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_sched_yield(void) {
    if (sched_yield() < 0)
      return convert_errno(errno);
    return 0;
  }
  
  __wasi_errno_t wasmtime_ssp_args_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
    struct argv_environ_values *argv_environ,
  #endif
    char **argv,
    char *argv_buf
  ) {
    for (size_t i = 0; i < argv_environ->argc; ++i) {
      argv[i] = argv_buf + (argv_environ->argv[i] - argv_environ->argv_buf);
    }
    argv[argv_environ->argc] = NULL;
    bh_memcpy_s(argv_buf, (uint32)argv_environ->argv_buf_size,
                argv_environ->argv_buf, (uint32)argv_environ->argv_buf_size);
    return __WASI_ESUCCESS;
  }
  
  __wasi_errno_t wasmtime_ssp_args_sizes_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
    struct argv_environ_values *argv_environ,
  #endif
    size_t *argc,
    size_t *argv_buf_size
  ) {
    *argc = argv_environ->argc;
    *argv_buf_size = argv_environ->argv_buf_size;
    return __WASI_ESUCCESS;
  }
  
  __wasi_errno_t wasmtime_ssp_environ_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
    struct argv_environ_values *argv_environ,
  #endif
    char **environ,
    char *environ_buf
  ) {
    for (size_t i = 0; i < argv_environ->environ_count; ++i) {
      environ[i] = environ_buf + (argv_environ->environ[i] - argv_environ->environ_buf);
    }
    environ[argv_environ->environ_count] = NULL;
    bh_memcpy_s(environ_buf, (uint32)argv_environ->environ_buf_size,
                argv_environ->environ_buf, (uint32)argv_environ->environ_buf_size);
    return __WASI_ESUCCESS;
  }
  
  __wasi_errno_t wasmtime_ssp_environ_sizes_get(
  #if !defined(WASMTIME_SSP_STATIC_CURFDS)
    struct argv_environ_values *argv_environ,
  #endif
    size_t *environ_count,
    size_t *environ_buf_size
  ) {
    *environ_count = argv_environ->environ_count;
    *environ_buf_size = argv_environ->environ_buf_size;
    return __WASI_ESUCCESS;
  }
  
  bool argv_environ_init(struct argv_environ_values *argv_environ,
                         const size_t *argv_offsets, size_t argv_offsets_len,
                         const char *argv_buf, size_t argv_buf_len,
                         const size_t *environ_offsets, size_t environ_offsets_len,
                         const char *environ_buf, size_t environ_buf_len)
  {
      uint64 total_size;
      size_t i;
  
      memset(argv_environ, 0, sizeof(struct argv_environ_values));
  
      argv_environ->argc = argv_offsets_len;
      argv_environ->argv_buf_size = argv_buf_len;
  
      total_size = sizeof(char *) * (uint64)argv_offsets_len;
      if (total_size >= UINT32_MAX
          || !(argv_environ->argv = wasm_runtime_malloc((uint32)total_size)))
          return false;
  
  
      if (argv_buf_len >= UINT32_MAX
          || !(argv_environ->argv_buf = wasm_runtime_malloc((uint32)argv_buf_len)))
          goto fail1;
  
      for (i = 0; i < argv_offsets_len; ++i) {
          argv_environ->argv[i] = argv_environ->argv_buf + argv_offsets[i];
      }
      bh_memcpy_s(argv_environ->argv_buf, (uint32)argv_buf_len,
                  argv_buf, (uint32)argv_buf_len);
  
      argv_environ->environ_count = environ_offsets_len;
      argv_environ->environ_buf_size = environ_buf_len;
  
      total_size = sizeof(char *) * (uint64)environ_offsets_len;
      if (total_size >= UINT32_MAX
          || !(argv_environ->environ = wasm_runtime_malloc((uint32)total_size)))
          goto fail2;
  
      if (environ_buf_len >= UINT32_MAX
          || !(argv_environ->environ_buf = wasm_runtime_malloc((uint32)environ_buf_len)))
          goto fail3;
  
      for (i = 0; i < environ_offsets_len; ++i) {
          argv_environ->environ[i] = argv_environ->environ_buf + environ_offsets[i];
      }
      bh_memcpy_s(argv_environ->environ_buf, (uint32)environ_buf_len,
                  environ_buf, (uint32)environ_buf_len);
  
      return true;
  
  fail3:
      wasm_runtime_free(argv_environ->environ);
  fail2:
      wasm_runtime_free(argv_environ->argv_buf);
  fail1:
      wasm_runtime_free(argv_environ->argv);
  
      memset(argv_environ, 0, sizeof(struct argv_environ_values));
      return false;
  }
  
  void argv_environ_destroy(struct argv_environ_values *argv_environ)
  {
      if (argv_environ->argv_buf)
          wasm_runtime_free(argv_environ->argv_buf);
      if (argv_environ->argv)
          wasm_runtime_free(argv_environ->argv);
      if (argv_environ->environ_buf)
          wasm_runtime_free(argv_environ->environ_buf);
      if (argv_environ->environ)
          wasm_runtime_free(argv_environ->environ);
  }
  
  void fd_table_destroy(struct fd_table *ft)
  {
      if (ft->entries) {
          for (uint32 i = 0; i < ft->size; i++) {
              if (ft->entries[i].object != NULL) {
                  fd_object_release(ft->entries[i].object);
              }
          }
          wasm_runtime_free(ft->entries);
      }
  }
  
  void fd_prestats_destroy(struct fd_prestats *pt)
  {
      if (pt->prestats) {
          for (uint32 i = 0; i < pt->size; i++) {
              if (pt->prestats[i].dir != NULL) {
                  wasm_runtime_free((void*)pt->prestats[i].dir);
              }
          }
          wasm_runtime_free(pt->prestats);
      }
  }
  