github.com/tetratelabs/wazero@v1.2.1/imports/wasi_snapshot_preview1/fs.go (about)

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"io/fs"
     7  	"math"
     8  	"path"
     9  	"strings"
    10  	"syscall"
    11  	"unsafe"
    12  
    13  	"github.com/tetratelabs/wazero/api"
    14  	"github.com/tetratelabs/wazero/internal/fsapi"
    15  	socketapi "github.com/tetratelabs/wazero/internal/sock"
    16  	"github.com/tetratelabs/wazero/internal/sys"
    17  	"github.com/tetratelabs/wazero/internal/sysfs"
    18  	"github.com/tetratelabs/wazero/internal/wasip1"
    19  	"github.com/tetratelabs/wazero/internal/wasm"
    20  )
    21  
    22  // fdAdvise is the WASI function named FdAdviseName which provides file
    23  // advisory information on a file descriptor.
    24  //
    25  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno
    26  var fdAdvise = newHostFunc(
    27  	wasip1.FdAdviseName, fdAdviseFn,
    28  	[]wasm.ValueType{i32, i64, i64, i32},
    29  	"fd", "offset", "len", "advice",
    30  )
    31  
    32  func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
    33  	fd := int32(params[0])
    34  	_ = params[1]
    35  	_ = params[2]
    36  	advice := byte(params[3])
    37  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
    38  
    39  	_, ok := fsc.LookupFile(fd)
    40  	if !ok {
    41  		return syscall.EBADF
    42  	}
    43  
    44  	switch advice {
    45  	case wasip1.FdAdviceNormal,
    46  		wasip1.FdAdviceSequential,
    47  		wasip1.FdAdviceRandom,
    48  		wasip1.FdAdviceWillNeed,
    49  		wasip1.FdAdviceDontNeed,
    50  		wasip1.FdAdviceNoReuse:
    51  	default:
    52  		return syscall.EINVAL
    53  	}
    54  
    55  	// FdAdvice corresponds to posix_fadvise, but it can only be supported on linux.
    56  	// However, the purpose of the call is just to do best-effort optimization on OS kernels,
    57  	// so just making this noop rather than returning NoSup error makes sense and doesn't affect
    58  	// the semantics of Wasm applications.
    59  	// TODO: invoke posix_fadvise on linux, and partially on darwin.
    60  	// - https://gitlab.com/cznic/fileutil/-/blob/v1.1.2/fileutil_linux.go#L87-95
    61  	// - https://github.com/bytecodealliance/system-interface/blob/62b97f9776b86235f318c3a6e308395a1187439b/src/fs/file_io_ext.rs#L430-L442
    62  	return 0
    63  }
    64  
    65  // fdAllocate is the WASI function named FdAllocateName which forces the
    66  // allocation of space in a file.
    67  //
    68  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
    69  var fdAllocate = newHostFunc(
    70  	wasip1.FdAllocateName, fdAllocateFn,
    71  	[]wasm.ValueType{i32, i64, i64},
    72  	"fd", "offset", "len",
    73  )
    74  
    75  func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
    76  	fd := int32(params[0])
    77  	offset := params[1]
    78  	length := params[2]
    79  
    80  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
    81  	f, ok := fsc.LookupFile(fd)
    82  	if !ok {
    83  		return syscall.EBADF
    84  	}
    85  
    86  	tail := int64(offset + length)
    87  	if tail < 0 {
    88  		return syscall.EINVAL
    89  	}
    90  
    91  	st, errno := f.File.Stat()
    92  	if errno != 0 {
    93  		return errno
    94  	}
    95  
    96  	if st.Size >= tail {
    97  		return 0 // We already have enough space.
    98  	}
    99  
   100  	return f.File.Truncate(tail)
   101  }
   102  
   103  // fdClose is the WASI function named FdCloseName which closes a file
   104  // descriptor.
   105  //
   106  // # Parameters
   107  //
   108  //   - fd: file descriptor to close
   109  //
   110  // Result (Errno)
   111  //
   112  // The return value is 0 except the following error conditions:
   113  //   - syscall.EBADF: the fd was not open.
   114  //   - syscall.ENOTSUP: the fs was a pre-open
   115  //
   116  // Note: This is similar to `close` in POSIX.
   117  // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
   118  // and https://linux.die.net/man/3/close
   119  var fdClose = newHostFunc(wasip1.FdCloseName, fdCloseFn, []api.ValueType{i32}, "fd")
   120  
   121  func fdCloseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   122  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   123  	fd := int32(params[0])
   124  
   125  	return fsc.CloseFile(fd)
   126  }
   127  
   128  // fdDatasync is the WASI function named FdDatasyncName which synchronizes
   129  // the data of a file to disk.
   130  //
   131  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
   132  var fdDatasync = newHostFunc(wasip1.FdDatasyncName, fdDatasyncFn, []api.ValueType{i32}, "fd")
   133  
   134  func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   135  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   136  	fd := int32(params[0])
   137  
   138  	// Check to see if the file descriptor is available
   139  	if f, ok := fsc.LookupFile(fd); !ok {
   140  		return syscall.EBADF
   141  	} else {
   142  		return f.File.Datasync()
   143  	}
   144  }
   145  
   146  // fdFdstatGet is the WASI function named FdFdstatGetName which returns the
   147  // attributes of a file descriptor.
   148  //
   149  // # Parameters
   150  //
   151  //   - fd: file descriptor to get the fdstat attributes data
   152  //   - resultFdstat: offset to write the result fdstat data
   153  //
   154  // Result (Errno)
   155  //
   156  // The return value is 0 except the following error conditions:
   157  //   - syscall.EBADF: `fd` is invalid
   158  //   - syscall.EFAULT: `resultFdstat` points to an offset out of memory
   159  //
   160  // fdstat byte layout is 24-byte size, with the following fields:
   161  //   - fs_filetype 1 byte: the file type
   162  //   - fs_flags 2 bytes: the file descriptor flag
   163  //   - 5 pad bytes
   164  //   - fs_right_base 8 bytes: ignored as rights were removed from WASI.
   165  //   - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI.
   166  //
   167  // For example, with a file corresponding with `fd` was a directory (=3) opened
   168  // with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1,
   169  // this function writes the below to api.Memory:
   170  //
   171  //	                uint16le   padding            uint64le                uint64le
   172  //	       uint8 --+  +--+  +-----------+  +--------------------+  +--------------------+
   173  //	               |  |  |  |           |  |                    |  |                    |
   174  //	     []byte{?, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}
   175  //	resultFdstat --^  ^-- fs_flags         ^-- fs_right_base       ^-- fs_right_inheriting
   176  //	               |
   177  //	               +-- fs_filetype
   178  //
   179  // Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as
   180  // well as additional fields.
   181  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
   182  // and https://linux.die.net/man/3/fsync
   183  var fdFdstatGet = newHostFunc(wasip1.FdFdstatGetName, fdFdstatGetFn, []api.ValueType{i32, i32}, "fd", "result.stat")
   184  
   185  // fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger
   186  // than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24).
   187  func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   188  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   189  
   190  	fd, resultFdstat := int32(params[0]), uint32(params[1])
   191  
   192  	// Ensure we can write the fdstat
   193  	buf, ok := mod.Memory().Read(resultFdstat, 24)
   194  	if !ok {
   195  		return syscall.EFAULT
   196  	}
   197  
   198  	var fdflags uint16
   199  	var st fsapi.Stat_t
   200  	var errno syscall.Errno
   201  	f, ok := fsc.LookupFile(fd)
   202  	if !ok {
   203  		return syscall.EBADF
   204  	} else if st, errno = f.File.Stat(); errno != 0 {
   205  		return errno
   206  	} else if f.File.IsAppend() {
   207  		fdflags |= wasip1.FD_APPEND
   208  	}
   209  
   210  	if f.File.IsNonblock() {
   211  		fdflags |= wasip1.FD_NONBLOCK
   212  	}
   213  
   214  	var fsRightsBase uint32
   215  	var fsRightsInheriting uint32
   216  	fileType := getExtendedWasiFiletype(f.File, st.Mode)
   217  
   218  	switch fileType {
   219  	case wasip1.FILETYPE_DIRECTORY:
   220  		// To satisfy wasi-testsuite, we must advertise that directories cannot
   221  		// be given seek permission (RIGHT_FD_SEEK).
   222  		fsRightsBase = dirRightsBase
   223  		fsRightsInheriting = fileRightsBase | dirRightsBase
   224  	case wasip1.FILETYPE_CHARACTER_DEVICE:
   225  		// According to wasi-libc,
   226  		// > A tty is a character device that we can't seek or tell on.
   227  		// See https://github.com/WebAssembly/wasi-libc/blob/a6f871343313220b76009827ed0153586361c0d5/libc-bottom-half/sources/isatty.c#L13-L18
   228  		fsRightsBase = fileRightsBase &^ wasip1.RIGHT_FD_SEEK &^ wasip1.RIGHT_FD_TELL
   229  	default:
   230  		fsRightsBase = fileRightsBase
   231  	}
   232  
   233  	writeFdstat(buf, fileType, fdflags, fsRightsBase, fsRightsInheriting)
   234  	return 0
   235  }
   236  
   237  // isPreopenedStdio returns true if the FD is sys.FdStdin, sys.FdStdout or
   238  // sys.FdStderr and pre-opened. This double check is needed in case the guest
   239  // closes stdin and re-opens it with a random alternative file.
   240  //
   241  // Currently, we only support non-blocking mode for standard I/O streams.
   242  // Non-blocking mode is rarely supported for regular files, and we don't
   243  // yet have support for sockets, so we make a special case.
   244  //
   245  // Note: this to get or set FD_NONBLOCK, but skip FD_APPEND. Our current
   246  // implementation can't set FD_APPEND, without re-opening files. As stdio are
   247  // pre-opened, we don't know how to re-open them, neither should we close the
   248  // underlying file. Later, we could add support for setting FD_APPEND, similar
   249  // to SetNonblock.
   250  func isPreopenedStdio(fd int32, f *sys.FileEntry) bool {
   251  	return fd <= sys.FdStderr && f.IsPreopen
   252  }
   253  
   254  const fileRightsBase = wasip1.RIGHT_FD_DATASYNC |
   255  	wasip1.RIGHT_FD_READ |
   256  	wasip1.RIGHT_FD_SEEK |
   257  	wasip1.RIGHT_FDSTAT_SET_FLAGS |
   258  	wasip1.RIGHT_FD_SYNC |
   259  	wasip1.RIGHT_FD_TELL |
   260  	wasip1.RIGHT_FD_WRITE |
   261  	wasip1.RIGHT_FD_ADVISE |
   262  	wasip1.RIGHT_FD_ALLOCATE |
   263  	wasip1.RIGHT_FD_FILESTAT_GET |
   264  	wasip1.RIGHT_FD_FILESTAT_SET_SIZE |
   265  	wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
   266  	wasip1.RIGHT_POLL_FD_READWRITE
   267  
   268  const dirRightsBase = wasip1.RIGHT_FD_DATASYNC |
   269  	wasip1.RIGHT_FDSTAT_SET_FLAGS |
   270  	wasip1.RIGHT_FD_SYNC |
   271  	wasip1.RIGHT_PATH_CREATE_DIRECTORY |
   272  	wasip1.RIGHT_PATH_CREATE_FILE |
   273  	wasip1.RIGHT_PATH_LINK_SOURCE |
   274  	wasip1.RIGHT_PATH_LINK_TARGET |
   275  	wasip1.RIGHT_PATH_OPEN |
   276  	wasip1.RIGHT_FD_READDIR |
   277  	wasip1.RIGHT_PATH_READLINK |
   278  	wasip1.RIGHT_PATH_RENAME_SOURCE |
   279  	wasip1.RIGHT_PATH_RENAME_TARGET |
   280  	wasip1.RIGHT_PATH_FILESTAT_GET |
   281  	wasip1.RIGHT_PATH_FILESTAT_SET_SIZE |
   282  	wasip1.RIGHT_PATH_FILESTAT_SET_TIMES |
   283  	wasip1.RIGHT_FD_FILESTAT_GET |
   284  	wasip1.RIGHT_FD_FILESTAT_SET_TIMES |
   285  	wasip1.RIGHT_PATH_SYMLINK |
   286  	wasip1.RIGHT_PATH_REMOVE_DIRECTORY |
   287  	wasip1.RIGHT_PATH_UNLINK_FILE
   288  
   289  func writeFdstat(buf []byte, fileType uint8, fdflags uint16, fsRightsBase, fsRightsInheriting uint32) {
   290  	b := (*[24]byte)(buf)
   291  	le.PutUint16(b[0:], uint16(fileType))
   292  	le.PutUint16(b[2:], fdflags)
   293  	le.PutUint32(b[4:], 0)
   294  	le.PutUint64(b[8:], uint64(fsRightsBase))
   295  	le.PutUint64(b[16:], uint64(fsRightsInheriting))
   296  }
   297  
   298  // fdFdstatSetFlags is the WASI function named FdFdstatSetFlagsName which
   299  // adjusts the flags associated with a file descriptor.
   300  var fdFdstatSetFlags = newHostFunc(wasip1.FdFdstatSetFlagsName, fdFdstatSetFlagsFn, []wasm.ValueType{i32, i32}, "fd", "flags")
   301  
   302  func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   303  	fd, wasiFlag := int32(params[0]), uint16(params[1])
   304  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   305  
   306  	// Currently we only support APPEND and NONBLOCK.
   307  	if wasip1.FD_DSYNC&wasiFlag != 0 || wasip1.FD_RSYNC&wasiFlag != 0 || wasip1.FD_SYNC&wasiFlag != 0 {
   308  		return syscall.EINVAL
   309  	}
   310  
   311  	if f, ok := fsc.LookupFile(fd); !ok {
   312  		return syscall.EBADF
   313  	} else {
   314  		nonblock := wasip1.FD_NONBLOCK&wasiFlag != 0
   315  		errno := f.File.SetNonblock(nonblock)
   316  		if errno != 0 {
   317  			return errno
   318  		}
   319  		if stat, err := f.File.Stat(); err == 0 && stat.Mode.IsRegular() {
   320  			// For normal files, proceed to apply an append flag.
   321  			append := wasip1.FD_APPEND&wasiFlag != 0
   322  			return f.File.SetAppend(append)
   323  		}
   324  	}
   325  
   326  	return 0
   327  }
   328  
   329  // fdFdstatSetRights will not be implemented as rights were removed from WASI.
   330  //
   331  // See https://github.com/bytecodealliance/wasmtime/pull/4666
   332  var fdFdstatSetRights = stubFunction(
   333  	wasip1.FdFdstatSetRightsName,
   334  	[]wasm.ValueType{i32, i64, i64},
   335  	"fd", "fs_rights_base", "fs_rights_inheriting",
   336  )
   337  
   338  // fdFilestatGet is the WASI function named FdFilestatGetName which returns
   339  // the stat attributes of an open file.
   340  //
   341  // # Parameters
   342  //
   343  //   - fd: file descriptor to get the filestat attributes data for
   344  //   - resultFilestat: offset to write the result filestat data
   345  //
   346  // Result (Errno)
   347  //
   348  // The return value is 0 except the following error conditions:
   349  //   - syscall.EBADF: `fd` is invalid
   350  //   - syscall.EIO: could not stat `fd` on filesystem
   351  //   - syscall.EFAULT: `resultFilestat` points to an offset out of memory
   352  //
   353  // filestat byte layout is 64-byte size, with the following fields:
   354  //   - dev 8 bytes: the device ID of device containing the file
   355  //   - ino 8 bytes: the file serial number
   356  //   - filetype 1 byte: the type of the file
   357  //   - 7 pad bytes
   358  //   - nlink 8 bytes: number of hard links to the file
   359  //   - size 8 bytes: for regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link
   360  //   - atim 8 bytes: ast data access timestamp
   361  //   - mtim 8 bytes: last data modification timestamp
   362  //   - ctim 8 bytes: ast file status change timestamp
   363  //
   364  // For example, with a regular file this function writes the below to api.Memory:
   365  //
   366  //	                                                             uint8 --+
   367  //		                         uint64le                uint64le        |        padding               uint64le                uint64le                         uint64le                               uint64le                             uint64le
   368  //		                 +--------------------+  +--------------------+  |  +-----------------+  +--------------------+  +-----------------------+  +----------------------------------+  +----------------------------------+  +----------------------------------+
   369  //		                 |                    |  |                    |  |  |                 |  |                    |  |                       |  |                                  |  |                                  |  |                                  |
   370  //		          []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 80, 0, 0, 0, 0, 0, 0, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23, 160, 153, 212, 128, 110, 221, 35, 23}
   371  //		resultFilestat   ^-- dev                 ^-- ino                 ^                       ^-- nlink               ^-- size                   ^-- atim                              ^-- mtim                              ^-- ctim
   372  //		                                                                 |
   373  //		                                                                 +-- filetype
   374  //
   375  // The following properties of filestat are not implemented:
   376  //   - dev: not supported by Golang FS
   377  //   - ino: not supported by Golang FS
   378  //   - nlink: not supported by Golang FS, we use 1
   379  //   - atime: not supported by Golang FS, we use mtim for this
   380  //   - ctim: not supported by Golang FS, we use mtim for this
   381  //
   382  // Note: This is similar to `fstat` in POSIX.
   383  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat
   384  // and https://linux.die.net/man/3/fstat
   385  var fdFilestatGet = newHostFunc(wasip1.FdFilestatGetName, fdFilestatGetFn, []api.ValueType{i32, i32}, "fd", "result.filestat")
   386  
   387  // fdFilestatGetFn cannot currently use proxyResultParams because filestat is
   388  // larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
   389  func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   390  	return fdFilestatGetFunc(mod, int32(params[0]), uint32(params[1]))
   391  }
   392  
   393  func fdFilestatGetFunc(mod api.Module, fd int32, resultBuf uint32) syscall.Errno {
   394  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   395  
   396  	// Ensure we can write the filestat
   397  	buf, ok := mod.Memory().Read(resultBuf, 64)
   398  	if !ok {
   399  		return syscall.EFAULT
   400  	}
   401  
   402  	f, ok := fsc.LookupFile(fd)
   403  	if !ok {
   404  		return syscall.EBADF
   405  	}
   406  
   407  	st, errno := f.File.Stat()
   408  	if errno != 0 {
   409  		return errno
   410  	}
   411  
   412  	filetype := getExtendedWasiFiletype(f.File, st.Mode)
   413  	return writeFilestat(buf, &st, filetype)
   414  }
   415  
   416  func getExtendedWasiFiletype(file fsapi.File, fm fs.FileMode) (ftype uint8) {
   417  	ftype = getWasiFiletype(fm)
   418  	if ftype == wasip1.FILETYPE_UNKNOWN {
   419  		if _, ok := file.(socketapi.TCPSock); ok {
   420  			ftype = wasip1.FILETYPE_SOCKET_STREAM
   421  		} else if _, ok = file.(socketapi.TCPConn); ok {
   422  			ftype = wasip1.FILETYPE_SOCKET_STREAM
   423  		}
   424  	}
   425  	return
   426  }
   427  
   428  func getWasiFiletype(fm fs.FileMode) uint8 {
   429  	switch {
   430  	case fm.IsRegular():
   431  		return wasip1.FILETYPE_REGULAR_FILE
   432  	case fm.IsDir():
   433  		return wasip1.FILETYPE_DIRECTORY
   434  	case fm&fs.ModeSymlink != 0:
   435  		return wasip1.FILETYPE_SYMBOLIC_LINK
   436  	case fm&fs.ModeDevice != 0:
   437  		// Unlike ModeDevice and ModeCharDevice, FILETYPE_CHARACTER_DEVICE and
   438  		// FILETYPE_BLOCK_DEVICE are set mutually exclusively.
   439  		if fm&fs.ModeCharDevice != 0 {
   440  			return wasip1.FILETYPE_CHARACTER_DEVICE
   441  		}
   442  		return wasip1.FILETYPE_BLOCK_DEVICE
   443  	default: // unknown
   444  		return wasip1.FILETYPE_UNKNOWN
   445  	}
   446  }
   447  
   448  func writeFilestat(buf []byte, st *fsapi.Stat_t, ftype uint8) (errno syscall.Errno) {
   449  	le.PutUint64(buf, st.Dev)
   450  	le.PutUint64(buf[8:], st.Ino)
   451  	le.PutUint64(buf[16:], uint64(ftype))
   452  	le.PutUint64(buf[24:], st.Nlink)
   453  	le.PutUint64(buf[32:], uint64(st.Size))
   454  	le.PutUint64(buf[40:], uint64(st.Atim))
   455  	le.PutUint64(buf[48:], uint64(st.Mtim))
   456  	le.PutUint64(buf[56:], uint64(st.Ctim))
   457  	return
   458  }
   459  
   460  // fdFilestatSetSize is the WASI function named FdFilestatSetSizeName which
   461  // adjusts the size of an open file.
   462  //
   463  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
   464  var fdFilestatSetSize = newHostFunc(wasip1.FdFilestatSetSizeName, fdFilestatSetSizeFn, []wasm.ValueType{i32, i64}, "fd", "size")
   465  
   466  func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   467  	fd := int32(params[0])
   468  	size := uint32(params[1])
   469  
   470  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   471  
   472  	// Check to see if the file descriptor is available
   473  	if f, ok := fsc.LookupFile(fd); !ok {
   474  		return syscall.EBADF
   475  	} else {
   476  		return f.File.Truncate(int64(size))
   477  	}
   478  }
   479  
   480  // fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
   481  // which adjusts the times of an open file.
   482  //
   483  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_timesfd-fd-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
   484  var fdFilestatSetTimes = newHostFunc(
   485  	wasip1.FdFilestatSetTimesName, fdFilestatSetTimesFn,
   486  	[]wasm.ValueType{i32, i64, i64, i32},
   487  	"fd", "atim", "mtim", "fst_flags",
   488  )
   489  
   490  func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   491  	fd := int32(params[0])
   492  	atim := int64(params[1])
   493  	mtim := int64(params[2])
   494  	fstFlags := uint16(params[3])
   495  
   496  	sys := mod.(*wasm.ModuleInstance).Sys
   497  	fsc := sys.FS()
   498  
   499  	f, ok := fsc.LookupFile(fd)
   500  	if !ok {
   501  		return syscall.EBADF
   502  	}
   503  
   504  	times, errno := toTimes(atim, mtim, fstFlags)
   505  	if errno != 0 {
   506  		return errno
   507  	}
   508  
   509  	// Try to update the file timestamps by file-descriptor.
   510  	errno = f.File.Utimens(&times)
   511  
   512  	// Fall back to path based, despite it being less precise.
   513  	switch errno {
   514  	case syscall.EPERM, syscall.ENOSYS:
   515  		errno = f.FS.Utimens(f.Name, &times, true)
   516  	}
   517  
   518  	return errno
   519  }
   520  
   521  func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, errno syscall.Errno) {
   522  	// times[0] == atim, times[1] == mtim
   523  
   524  	// coerce atim into a timespec
   525  	if set, now := fstFlags&wasip1.FstflagsAtim != 0, fstFlags&wasip1.FstflagsAtimNow != 0; set && now {
   526  		errno = syscall.EINVAL
   527  		return
   528  	} else if set {
   529  		times[0] = syscall.NsecToTimespec(atim)
   530  	} else if now {
   531  		times[0].Nsec = sysfs.UTIME_NOW
   532  	} else {
   533  		times[0].Nsec = sysfs.UTIME_OMIT
   534  	}
   535  
   536  	// coerce mtim into a timespec
   537  	if set, now := fstFlags&wasip1.FstflagsMtim != 0, fstFlags&wasip1.FstflagsMtimNow != 0; set && now {
   538  		errno = syscall.EINVAL
   539  		return
   540  	} else if set {
   541  		times[1] = syscall.NsecToTimespec(mtime)
   542  	} else if now {
   543  		times[1].Nsec = sysfs.UTIME_NOW
   544  	} else {
   545  		times[1].Nsec = sysfs.UTIME_OMIT
   546  	}
   547  	return
   548  }
   549  
   550  // fdPread is the WASI function named FdPreadName which reads from a file
   551  // descriptor, without using and updating the file descriptor's offset.
   552  //
   553  // Except for handling offset, this implementation is identical to fdRead.
   554  //
   555  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size
   556  var fdPread = newHostFunc(
   557  	wasip1.FdPreadName, fdPreadFn,
   558  	[]api.ValueType{i32, i32, i32, i64, i32},
   559  	"fd", "iovs", "iovs_len", "offset", "result.nread",
   560  )
   561  
   562  func fdPreadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   563  	return fdReadOrPread(mod, params, true)
   564  }
   565  
   566  // fdPrestatGet is the WASI function named FdPrestatGetName which returns
   567  // the prestat data of a file descriptor.
   568  //
   569  // # Parameters
   570  //
   571  //   - fd: file descriptor to get the prestat
   572  //   - resultPrestat: offset to write the result prestat data
   573  //
   574  // Result (Errno)
   575  //
   576  // The return value is 0 except the following error conditions:
   577  //   - syscall.EBADF: `fd` is invalid or the `fd` is not a pre-opened directory
   578  //   - syscall.EFAULT: `resultPrestat` points to an offset out of memory
   579  //
   580  // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes.
   581  // The only valid tag is `prestat_dir`, which is tag zero. This simplifies the
   582  // byte layout to 4 empty bytes followed by the uint32le encoded path length.
   583  //
   584  // For example, the directory name corresponding with `fd` was "/tmp" and
   585  // parameter resultPrestat=1, this function writes the below to api.Memory:
   586  //
   587  //	                   padding   uint32le
   588  //	        uint8 --+  +-----+  +--------+
   589  //	                |  |     |  |        |
   590  //	      []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?}
   591  //	resultPrestat --^           ^
   592  //	          tag --+           |
   593  //	                            +-- size in bytes of the string "/tmp"
   594  //
   595  // See fdPrestatDirName and
   596  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
   597  var fdPrestatGet = newHostFunc(wasip1.FdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
   598  
   599  func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   600  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   601  	fd, resultPrestat := int32(params[0]), uint32(params[1])
   602  
   603  	name, errno := preopenPath(fsc, fd)
   604  	if errno != 0 {
   605  		return errno
   606  	}
   607  
   608  	// Upper 32-bits are zero because...
   609  	// * Zero-value 8-bit tag, and 3-byte zero-value padding
   610  	prestat := uint64(len(name) << 32)
   611  	if !mod.Memory().WriteUint64Le(resultPrestat, prestat) {
   612  		return syscall.EFAULT
   613  	}
   614  	return 0
   615  }
   616  
   617  // fdPrestatDirName is the WASI function named FdPrestatDirNameName which
   618  // returns the path of the pre-opened directory of a file descriptor.
   619  //
   620  // # Parameters
   621  //
   622  //   - fd: file descriptor to get the path of the pre-opened directory
   623  //   - path: offset in api.Memory to write the result path
   624  //   - pathLen: count of bytes to write to `path`
   625  //   - This should match the uint32le fdPrestatGet writes to offset
   626  //     `resultPrestat`+4
   627  //
   628  // Result (Errno)
   629  //
   630  // The return value is 0 except the following error conditions:
   631  //   - syscall.EBADF: `fd` is invalid
   632  //   - syscall.EFAULT: `path` points to an offset out of memory
   633  //   - syscall.ENAMETOOLONG: `pathLen` is longer than the actual length of the result
   634  //
   635  // For example, the directory name corresponding with `fd` was "/tmp" and
   636  // # Parameters path=1 pathLen=4 (correct), this function will write the below to
   637  // api.Memory:
   638  //
   639  //	               pathLen
   640  //	           +--------------+
   641  //	           |              |
   642  //	[]byte{?, '/', 't', 'm', 'p', ?}
   643  //	    path --^
   644  //
   645  // See fdPrestatGet
   646  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
   647  var fdPrestatDirName = newHostFunc(
   648  	wasip1.FdPrestatDirNameName, fdPrestatDirNameFn,
   649  	[]api.ValueType{i32, i32, i32},
   650  	"fd", "result.path", "result.path_len",
   651  )
   652  
   653  func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   654  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   655  	fd, path, pathLen := int32(params[0]), uint32(params[1]), uint32(params[2])
   656  
   657  	name, errno := preopenPath(fsc, fd)
   658  	if errno != 0 {
   659  		return errno
   660  	}
   661  
   662  	// Some runtimes may have another semantics. See /RATIONALE.md
   663  	if uint32(len(name)) < pathLen {
   664  		return syscall.ENAMETOOLONG
   665  	}
   666  
   667  	if !mod.Memory().Write(path, []byte(name)[:pathLen]) {
   668  		return syscall.EFAULT
   669  	}
   670  	return 0
   671  }
   672  
   673  // fdPwrite is the WASI function named FdPwriteName which writes to a file
   674  // descriptor, without using and updating the file descriptor's offset.
   675  //
   676  // Except for handling offset, this implementation is identical to fdWrite.
   677  //
   678  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size
   679  var fdPwrite = newHostFunc(
   680  	wasip1.FdPwriteName, fdPwriteFn,
   681  	[]api.ValueType{i32, i32, i32, i64, i32},
   682  	"fd", "iovs", "iovs_len", "offset", "result.nwritten",
   683  )
   684  
   685  func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   686  	return fdWriteOrPwrite(mod, params, true)
   687  }
   688  
   689  // fdRead is the WASI function named FdReadName which reads from a file
   690  // descriptor.
   691  //
   692  // # Parameters
   693  //
   694  //   - fd: an opened file descriptor to read data from
   695  //   - iovs: offset in api.Memory to read offset, size pairs representing where
   696  //     to write file data
   697  //   - Both offset and length are encoded as uint32le
   698  //   - iovsCount: count of memory offset, size pairs to read sequentially
   699  //     starting at iovs
   700  //   - resultNread: offset in api.Memory to write the number of bytes read
   701  //
   702  // Result (Errno)
   703  //
   704  // The return value is 0 except the following error conditions:
   705  //   - syscall.EBADF: `fd` is invalid
   706  //   - syscall.EFAULT: `iovs` or `resultNread` point to an offset out of memory
   707  //   - syscall.EIO: a file system error
   708  //
   709  // For example, this function needs to first read `iovs` to determine where
   710  // to write contents. If parameters iovs=1 iovsCount=2, this function reads two
   711  // offset/length pairs from api.Memory:
   712  //
   713  //	                  iovs[0]                  iovs[1]
   714  //	          +---------------------+   +--------------------+
   715  //	          | uint32le    uint32le|   |uint32le    uint32le|
   716  //	          +---------+  +--------+   +--------+  +--------+
   717  //	          |         |  |        |   |        |  |        |
   718  //	[]byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
   719  //	   iovs --^            ^            ^           ^
   720  //	          |            |            |           |
   721  //	 offset --+   length --+   offset --+  length --+
   722  //
   723  // If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter
   724  // resultNread=26, this function writes the below to api.Memory:
   725  //
   726  //	                    iovs[0].length        iovs[1].length
   727  //	                   +--------------+       +----+       uint32le
   728  //	                   |              |       |    |      +--------+
   729  //	[]byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 }
   730  //	  iovs[0].offset --^                      ^           ^
   731  //	                         iovs[1].offset --+           |
   732  //	                                        resultNread --+
   733  //
   734  // Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv
   735  //
   736  // See fdWrite
   737  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
   738  var fdRead = newHostFunc(
   739  	wasip1.FdReadName, fdReadFn,
   740  	[]api.ValueType{i32, i32, i32, i32},
   741  	"fd", "iovs", "iovs_len", "result.nread",
   742  )
   743  
   744  // preader tracks an offset across multiple reads.
   745  type preader struct {
   746  	f      fsapi.File
   747  	offset int64
   748  }
   749  
   750  // Read implements the same function as documented on internalapi.File.
   751  func (w *preader) Read(buf []byte) (n int, errno syscall.Errno) {
   752  	if len(buf) == 0 {
   753  		return 0, 0 // less overhead on zero-length reads.
   754  	}
   755  
   756  	n, err := w.f.Pread(buf, w.offset)
   757  	w.offset += int64(n)
   758  	return n, err
   759  }
   760  
   761  func fdReadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   762  	return fdReadOrPread(mod, params, false)
   763  }
   764  
   765  func fdReadOrPread(mod api.Module, params []uint64, isPread bool) syscall.Errno {
   766  	mem := mod.Memory()
   767  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   768  
   769  	fd := int32(params[0])
   770  	iovs := uint32(params[1])
   771  	iovsCount := uint32(params[2])
   772  
   773  	var resultNread uint32
   774  	var reader func(buf []byte) (n int, errno syscall.Errno)
   775  	if f, ok := fsc.LookupFile(fd); !ok {
   776  		return syscall.EBADF
   777  	} else if isPread {
   778  		offset := int64(params[3])
   779  		reader = (&preader{f: f.File, offset: offset}).Read
   780  		resultNread = uint32(params[4])
   781  	} else {
   782  		reader = f.File.Read
   783  		resultNread = uint32(params[3])
   784  	}
   785  
   786  	nread, errno := readv(mem, iovs, iovsCount, reader)
   787  	if errno != 0 {
   788  		return errno
   789  	}
   790  	if !mem.WriteUint32Le(resultNread, nread) {
   791  		return syscall.EFAULT
   792  	} else {
   793  		return 0
   794  	}
   795  }
   796  
   797  func readv(mem api.Memory, iovs uint32, iovsCount uint32, reader func(buf []byte) (nread int, errno syscall.Errno)) (uint32, syscall.Errno) {
   798  	var nread uint32
   799  	iovsStop := iovsCount << 3 // iovsCount * 8
   800  	iovsBuf, ok := mem.Read(iovs, iovsStop)
   801  	if !ok {
   802  		return 0, syscall.EFAULT
   803  	}
   804  
   805  	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
   806  		offset := le.Uint32(iovsBuf[iovsPos:])
   807  		l := le.Uint32(iovsBuf[iovsPos+4:])
   808  
   809  		if l == 0 { // A zero length iovec could be ahead of another.
   810  			continue
   811  		}
   812  
   813  		b, ok := mem.Read(offset, l)
   814  		if !ok {
   815  			return 0, syscall.EFAULT
   816  		}
   817  
   818  		n, errno := reader(b)
   819  		nread += uint32(n)
   820  
   821  		if errno == syscall.ENOSYS {
   822  			return 0, syscall.EBADF // e.g. unimplemented for read
   823  		} else if errno != 0 {
   824  			return 0, errno
   825  		} else if n < int(l) {
   826  			break // stop when we read less than capacity.
   827  		}
   828  	}
   829  	return nread, 0
   830  }
   831  
   832  // fdReaddir is the WASI function named FdReaddirName which reads directory
   833  // entries from a directory.
   834  //
   835  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_readdirfd-fd-buf-pointeru8-buf_len-size-cookie-dircookie---errno-size
   836  var fdReaddir = newHostFunc(
   837  	wasip1.FdReaddirName, fdReaddirFn,
   838  	[]wasm.ValueType{i32, i32, i32, i64, i32},
   839  	"fd", "buf", "buf_len", "cookie", "result.bufused",
   840  )
   841  
   842  func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
   843  	mem := mod.Memory()
   844  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
   845  
   846  	fd := int32(params[0])
   847  	buf := uint32(params[1])
   848  	bufLen := uint32(params[2])
   849  	// We control the value of the cookie, and it should never be negative.
   850  	// However, we coerce it to signed to ensure the caller doesn't manipulate
   851  	// it in such a way that becomes negative.
   852  	cookie := int64(params[3])
   853  	resultBufused := uint32(params[4])
   854  
   855  	// The bufLen must be enough to write a dirent. Otherwise, the caller can't
   856  	// read what the next cookie is.
   857  	if bufLen < wasip1.DirentSize {
   858  		return syscall.EINVAL
   859  	}
   860  
   861  	// Validate the FD is a directory
   862  	f, errno := openedDir(fsc, fd)
   863  	if errno != 0 {
   864  		return errno
   865  	}
   866  	// Discard the bool value because we validated the fd already.
   867  	dir, errno := fsc.LookupReaddir(fd, f)
   868  	if errno != 0 {
   869  		return errno
   870  	}
   871  	// Validate the cookie and possibly sync the internal state to the one the cookie represents.
   872  	if errno = dir.Rewind(cookie); errno != 0 {
   873  		return errno
   874  	}
   875  
   876  	// Determine how many dirents we can write, excluding a potentially
   877  	// truncated entry.
   878  	dirents, bufused, direntCount, writeTruncatedEntry := maxDirents(dir, bufLen)
   879  
   880  	// Now, write entries to the underlying buffer.
   881  	if bufused > 0 {
   882  		buf, ok := mem.Read(buf, bufused)
   883  		if !ok {
   884  			return syscall.EFAULT
   885  		}
   886  
   887  		// Iterate again on dir, this time writing the entries.
   888  		writeDirents(dirents, direntCount, writeTruncatedEntry, buf, uint64(cookie+1))
   889  	}
   890  
   891  	if !mem.WriteUint32Le(resultBufused, bufused) {
   892  		return syscall.EFAULT
   893  	}
   894  	return 0
   895  }
   896  
   897  const largestDirent = int64(math.MaxUint32 - wasip1.DirentSize)
   898  
   899  // maxDirents returns the maximum count and total entries that can fit in
   900  // maxLen bytes.
   901  //
   902  // truncatedEntryLen is the amount of bytes past bufLen needed to write the
   903  // next entry. We have to return bufused == bufLen unless the directory is
   904  // exhausted.
   905  //
   906  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
   907  // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44
   908  func maxDirents(dir *sys.Readdir, bufLen uint32) (dirents []fsapi.Dirent, bufused, direntCount uint32, writeTruncatedEntry bool) {
   909  	lenRemaining := bufLen
   910  	for {
   911  		d, errno := dir.Peek()
   912  		if errno != 0 {
   913  			return
   914  		}
   915  		dirents = append(dirents, *d)
   916  
   917  		if lenRemaining < wasip1.DirentSize {
   918  			// We don't have enough space in bufLen for another struct,
   919  			// entry. A caller who wants more will retry.
   920  
   921  			// bufused == bufLen means more dirents exist, which is the case
   922  			// when the dirent is larger than bytes remaining.
   923  			bufused = bufLen
   924  			break
   925  		}
   926  
   927  		// use int64 to guard against huge filenames
   928  		nameLen := int64(len(d.Name))
   929  		var entryLen uint32
   930  
   931  		// Check to see if DirentSize + nameLen overflows, or if it would be
   932  		// larger than possible to encode.
   933  		if el := int64(wasip1.DirentSize) + nameLen; el < 0 || el > largestDirent {
   934  			// panic, as testing is difficult. ex we would have to extract a
   935  			// function to get size of a string or allocate a 2^32 size one!
   936  			panic("invalid filename: too large")
   937  		} else { // we know this can fit into a uint32
   938  			entryLen = uint32(el)
   939  		}
   940  
   941  		if entryLen > lenRemaining {
   942  			// We haven't room to write the entry, and docs say to write the
   943  			// header. This helps especially when there is an entry with a very
   944  			// long filename. Ex if bufLen is 4096 and the filename is 4096,
   945  			// we need to write DirentSize(24) + 4096 bytes to write the entry.
   946  			// In this case, we only write up to DirentSize(24) to allow the
   947  			// caller to resize.
   948  
   949  			// bufused == bufLen means more dirents exist, which is the case
   950  			// when the next entry is larger than bytes remaining.
   951  			bufused = bufLen
   952  
   953  			// We do have enough space to write the header, this value will be
   954  			// passed on to writeDirents to only write the header for this entry.
   955  			writeTruncatedEntry = true
   956  			break
   957  		}
   958  
   959  		// This won't go negative because we checked entryLen <= lenRemaining.
   960  		lenRemaining -= entryLen
   961  		bufused += entryLen
   962  		direntCount++
   963  		_ = dir.Advance()
   964  	}
   965  	return
   966  }
   967  
   968  // writeDirents writes the directory entries to the buffer, which is pre-sized
   969  // based on maxDirents.	truncatedEntryLen means write one past entryCount,
   970  // without its name. See maxDirents for why
   971  func writeDirents(
   972  	dirents []fsapi.Dirent,
   973  	direntCount uint32,
   974  	writeTruncatedEntry bool,
   975  	buf []byte,
   976  	d_next uint64,
   977  ) {
   978  	pos, i := uint32(0), uint32(0)
   979  	for ; i < direntCount; i++ {
   980  		e := dirents[i]
   981  		nameLen := uint32(len(e.Name))
   982  
   983  		writeDirent(buf[pos:], d_next, e.Ino, nameLen, e.Type)
   984  		pos += wasip1.DirentSize
   985  
   986  		copy(buf[pos:], e.Name)
   987  		pos += nameLen
   988  		d_next++
   989  	}
   990  
   991  	if !writeTruncatedEntry {
   992  		return
   993  	}
   994  
   995  	// Write a dirent without its name
   996  	dirent := make([]byte, wasip1.DirentSize)
   997  	e := dirents[i]
   998  	writeDirent(dirent, d_next, e.Ino, uint32(len(e.Name)), e.Type)
   999  
  1000  	// Potentially truncate it
  1001  	copy(buf[pos:], dirent)
  1002  }
  1003  
  1004  // writeDirent writes DirentSize bytes
  1005  func writeDirent(buf []byte, dNext uint64, ino uint64, dNamlen uint32, dType fs.FileMode) {
  1006  	le.PutUint64(buf, dNext)        // d_next
  1007  	le.PutUint64(buf[8:], ino)      // d_ino
  1008  	le.PutUint32(buf[16:], dNamlen) // d_namlen
  1009  	filetype := getWasiFiletype(dType)
  1010  	le.PutUint32(buf[20:], uint32(filetype)) //  d_type
  1011  }
  1012  
  1013  // openedDir returns the sys.FileEntry for the directory and 0 if the fd points to a readable directory.
  1014  func openedDir(fsc *sys.FSContext, fd int32) (*sys.FileEntry, syscall.Errno) {
  1015  	if f, ok := fsc.LookupFile(fd); !ok {
  1016  		return nil, syscall.EBADF
  1017  	} else if isDir, errno := f.File.IsDir(); errno != 0 {
  1018  		return nil, errno
  1019  	} else if !isDir {
  1020  		// fd_readdir docs don't indicate whether to return syscall.ENOTDIR or
  1021  		// syscall.EBADF. It has been noticed that rust will crash on syscall.ENOTDIR,
  1022  		// and POSIX C ref seems to not return this, so we don't either.
  1023  		//
  1024  		// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
  1025  		// and https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/dirent.h
  1026  		return nil, syscall.EBADF
  1027  	} else {
  1028  		return f, 0
  1029  	}
  1030  }
  1031  
  1032  // fdRenumber is the WASI function named FdRenumberName which atomically
  1033  // replaces a file descriptor by renumbering another file descriptor.
  1034  //
  1035  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
  1036  var fdRenumber = newHostFunc(wasip1.FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to")
  1037  
  1038  func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1039  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1040  
  1041  	from := int32(params[0])
  1042  	to := int32(params[1])
  1043  
  1044  	if errno := fsc.Renumber(from, to); errno != 0 {
  1045  		return errno
  1046  	}
  1047  	return 0
  1048  }
  1049  
  1050  // fdSeek is the WASI function named FdSeekName which moves the offset of a
  1051  // file descriptor.
  1052  //
  1053  // # Parameters
  1054  //
  1055  //   - fd: file descriptor to move the offset of
  1056  //   - offset: signed int64, which is encoded as uint64, input argument to
  1057  //     `whence`, which results in a new offset
  1058  //   - whence: operator that creates the new offset, given `offset` bytes
  1059  //   - If io.SeekStart, new offset == `offset`.
  1060  //   - If io.SeekCurrent, new offset == existing offset + `offset`.
  1061  //   - If io.SeekEnd, new offset == file size of `fd` + `offset`.
  1062  //   - resultNewoffset: offset in api.Memory to write the new offset to,
  1063  //     relative to start of the file
  1064  //
  1065  // Result (Errno)
  1066  //
  1067  // The return value is 0 except the following error conditions:
  1068  //   - syscall.EBADF: `fd` is invalid
  1069  //   - syscall.EFAULT: `resultNewoffset` points to an offset out of memory
  1070  //   - syscall.EINVAL: `whence` is an invalid value
  1071  //   - syscall.EIO: a file system error
  1072  //   - syscall.EISDIR: the file was a directory.
  1073  //
  1074  // For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4,
  1075  // whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below
  1076  // to api.Memory:
  1077  //
  1078  //	                         uint64le
  1079  //	                  +--------------------+
  1080  //	                  |                    |
  1081  //	        []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? }
  1082  //	resultNewoffset --^
  1083  //
  1084  // Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek
  1085  //
  1086  // See io.Seeker
  1087  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
  1088  var fdSeek = newHostFunc(
  1089  	wasip1.FdSeekName, fdSeekFn,
  1090  	[]api.ValueType{i32, i64, i32, i32},
  1091  	"fd", "offset", "whence", "result.newoffset",
  1092  )
  1093  
  1094  func fdSeekFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1095  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1096  	fd := int32(params[0])
  1097  	offset := params[1]
  1098  	whence := uint32(params[2])
  1099  	resultNewoffset := uint32(params[3])
  1100  
  1101  	if f, ok := fsc.LookupFile(fd); !ok {
  1102  		return syscall.EBADF
  1103  	} else if isDir, _ := f.File.IsDir(); isDir {
  1104  		return syscall.EISDIR // POSIX doesn't forbid seeking a directory, but wasi-testsuite does.
  1105  	} else if newOffset, errno := f.File.Seek(int64(offset), int(whence)); errno != 0 {
  1106  		return errno
  1107  	} else if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
  1108  		return syscall.EFAULT
  1109  	}
  1110  	return 0
  1111  }
  1112  
  1113  // fdSync is the WASI function named FdSyncName which synchronizes the data
  1114  // and metadata of a file to disk.
  1115  //
  1116  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
  1117  var fdSync = newHostFunc(wasip1.FdSyncName, fdSyncFn, []api.ValueType{i32}, "fd")
  1118  
  1119  func fdSyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1120  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1121  	fd := int32(params[0])
  1122  
  1123  	// Check to see if the file descriptor is available
  1124  	if f, ok := fsc.LookupFile(fd); !ok {
  1125  		return syscall.EBADF
  1126  	} else {
  1127  		return f.File.Sync()
  1128  	}
  1129  }
  1130  
  1131  // fdTell is the WASI function named FdTellName which returns the current
  1132  // offset of a file descriptor.
  1133  //
  1134  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
  1135  var fdTell = newHostFunc(wasip1.FdTellName, fdTellFn, []api.ValueType{i32, i32}, "fd", "result.offset")
  1136  
  1137  func fdTellFn(ctx context.Context, mod api.Module, params []uint64) syscall.Errno {
  1138  	fd := params[0]
  1139  	offset := uint64(0)
  1140  	whence := uint64(io.SeekCurrent)
  1141  	resultNewoffset := params[1]
  1142  
  1143  	fdSeekParams := []uint64{fd, offset, whence, resultNewoffset}
  1144  	return fdSeekFn(ctx, mod, fdSeekParams)
  1145  }
  1146  
  1147  // fdWrite is the WASI function named FdWriteName which writes to a file
  1148  // descriptor.
  1149  //
  1150  // # Parameters
  1151  //
  1152  //   - fd: an opened file descriptor to write data to
  1153  //   - iovs: offset in api.Memory to read offset, size pairs representing the
  1154  //     data to write to `fd`
  1155  //   - Both offset and length are encoded as uint32le.
  1156  //   - iovsCount: count of memory offset, size pairs to read sequentially
  1157  //     starting at iovs
  1158  //   - resultNwritten: offset in api.Memory to write the number of bytes
  1159  //     written
  1160  //
  1161  // Result (Errno)
  1162  //
  1163  // The return value is 0 except the following error conditions:
  1164  //   - syscall.EBADF: `fd` is invalid
  1165  //   - syscall.EFAULT: `iovs` or `resultNwritten` point to an offset out of memory
  1166  //   - syscall.EIO: a file system error
  1167  //
  1168  // For example, this function needs to first read `iovs` to determine what to
  1169  // write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two
  1170  // offset/length pairs from api.Memory:
  1171  //
  1172  //	                  iovs[0]                  iovs[1]
  1173  //	          +---------------------+   +--------------------+
  1174  //	          | uint32le    uint32le|   |uint32le    uint32le|
  1175  //	          +---------+  +--------+   +--------+  +--------+
  1176  //	          |         |  |        |   |        |  |        |
  1177  //	[]byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
  1178  //	   iovs --^            ^            ^           ^
  1179  //	          |            |            |           |
  1180  //	 offset --+   length --+   offset --+  length --+
  1181  //
  1182  // This function reads those chunks api.Memory into the `fd` sequentially.
  1183  //
  1184  //	                    iovs[0].length        iovs[1].length
  1185  //	                   +--------------+       +----+
  1186  //	                   |              |       |    |
  1187  //	[]byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? }
  1188  //	  iovs[0].offset --^                      ^
  1189  //	                         iovs[1].offset --+
  1190  //
  1191  // Since "wazero" was written, if parameter resultNwritten=26, this function
  1192  // writes the below to api.Memory:
  1193  //
  1194  //	                   uint32le
  1195  //	                  +--------+
  1196  //	                  |        |
  1197  //	[]byte{ 0..24, ?, 6, 0, 0, 0', ? }
  1198  //	 resultNwritten --^
  1199  //
  1200  // Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev
  1201  //
  1202  // See fdRead
  1203  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
  1204  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
  1205  var fdWrite = newHostFunc(
  1206  	wasip1.FdWriteName, fdWriteFn,
  1207  	[]api.ValueType{i32, i32, i32, i32},
  1208  	"fd", "iovs", "iovs_len", "result.nwritten",
  1209  )
  1210  
  1211  func fdWriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1212  	return fdWriteOrPwrite(mod, params, false)
  1213  }
  1214  
  1215  // pwriter tracks an offset across multiple writes.
  1216  type pwriter struct {
  1217  	f      fsapi.File
  1218  	offset int64
  1219  }
  1220  
  1221  // Write implements the same function as documented on internalapi.File.
  1222  func (w *pwriter) Write(buf []byte) (n int, errno syscall.Errno) {
  1223  	if len(buf) == 0 {
  1224  		return 0, 0 // less overhead on zero-length writes.
  1225  	}
  1226  
  1227  	n, err := w.f.Pwrite(buf, w.offset)
  1228  	w.offset += int64(n)
  1229  	return n, err
  1230  }
  1231  
  1232  func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) syscall.Errno {
  1233  	mem := mod.Memory()
  1234  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1235  
  1236  	fd := int32(params[0])
  1237  	iovs := uint32(params[1])
  1238  	iovsCount := uint32(params[2])
  1239  
  1240  	var resultNwritten uint32
  1241  	var writer func(buf []byte) (n int, errno syscall.Errno)
  1242  	if f, ok := fsc.LookupFile(fd); !ok {
  1243  		return syscall.EBADF
  1244  	} else if isPwrite {
  1245  		offset := int64(params[3])
  1246  		writer = (&pwriter{f: f.File, offset: offset}).Write
  1247  		resultNwritten = uint32(params[4])
  1248  	} else {
  1249  		writer = f.File.Write
  1250  		resultNwritten = uint32(params[3])
  1251  	}
  1252  
  1253  	nwritten, errno := writev(mem, iovs, iovsCount, writer)
  1254  	if errno != 0 {
  1255  		return errno
  1256  	}
  1257  
  1258  	if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) {
  1259  		return syscall.EFAULT
  1260  	}
  1261  	return 0
  1262  }
  1263  
  1264  func writev(mem api.Memory, iovs uint32, iovsCount uint32, writer func(buf []byte) (n int, errno syscall.Errno)) (uint32, syscall.Errno) {
  1265  	var nwritten uint32
  1266  	iovsStop := iovsCount << 3 // iovsCount * 8
  1267  	iovsBuf, ok := mem.Read(iovs, iovsStop)
  1268  	if !ok {
  1269  		return 0, syscall.EFAULT
  1270  	}
  1271  
  1272  	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
  1273  		offset := le.Uint32(iovsBuf[iovsPos:])
  1274  		l := le.Uint32(iovsBuf[iovsPos+4:])
  1275  
  1276  		b, ok := mem.Read(offset, l)
  1277  		if !ok {
  1278  			return 0, syscall.EFAULT
  1279  		}
  1280  		n, errno := writer(b)
  1281  		nwritten += uint32(n)
  1282  		if errno == syscall.ENOSYS {
  1283  			return 0, syscall.EBADF // e.g. unimplemented for write
  1284  		} else if errno != 0 {
  1285  			return 0, errno
  1286  		}
  1287  	}
  1288  	return nwritten, 0
  1289  }
  1290  
  1291  // pathCreateDirectory is the WASI function named PathCreateDirectoryName which
  1292  // creates a directory.
  1293  //
  1294  // # Parameters
  1295  //
  1296  //   - fd: file descriptor of a directory that `path` is relative to
  1297  //   - path: offset in api.Memory to read the path string from
  1298  //   - pathLen: length of `path`
  1299  //
  1300  // # Result (Errno)
  1301  //
  1302  // The return value is 0 except the following error conditions:
  1303  //   - syscall.EBADF: `fd` is invalid
  1304  //   - syscall.ENOENT: `path` does not exist.
  1305  //   - syscall.ENOTDIR: `path` is a file
  1306  //
  1307  // # Notes
  1308  //   - This is similar to mkdirat in POSIX.
  1309  //     See https://linux.die.net/man/2/mkdirat
  1310  //
  1311  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
  1312  var pathCreateDirectory = newHostFunc(
  1313  	wasip1.PathCreateDirectoryName, pathCreateDirectoryFn,
  1314  	[]wasm.ValueType{i32, i32, i32},
  1315  	"fd", "path", "path_len",
  1316  )
  1317  
  1318  func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1319  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1320  
  1321  	fd := int32(params[0])
  1322  	path := uint32(params[1])
  1323  	pathLen := uint32(params[2])
  1324  
  1325  	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
  1326  	if errno != 0 {
  1327  		return errno
  1328  	}
  1329  
  1330  	if errno = preopen.Mkdir(pathName, 0o700); errno != 0 {
  1331  		return errno
  1332  	}
  1333  
  1334  	return 0
  1335  }
  1336  
  1337  // pathFilestatGet is the WASI function named PathFilestatGetName which
  1338  // returns the stat attributes of a file or directory.
  1339  //
  1340  // # Parameters
  1341  //
  1342  //   - fd: file descriptor of the folder to look in for the path
  1343  //   - flags: flags determining the method of how paths are resolved
  1344  //   - path: path under fd to get the filestat attributes data for
  1345  //   - path_len: length of the path that was given
  1346  //   - resultFilestat: offset to write the result filestat data
  1347  //
  1348  // Result (Errno)
  1349  //
  1350  // The return value is 0 except the following error conditions:
  1351  //   - syscall.EBADF: `fd` is invalid
  1352  //   - syscall.ENOTDIR: `fd` points to a file not a directory
  1353  //   - syscall.EIO: could not stat `fd` on filesystem
  1354  //   - syscall.EINVAL: the path contained "../"
  1355  //   - syscall.ENAMETOOLONG: `path` + `path_len` is out of memory
  1356  //   - syscall.EFAULT: `resultFilestat` points to an offset out of memory
  1357  //   - syscall.ENOENT: could not find the path
  1358  //
  1359  // The rest of this implementation matches that of fdFilestatGet, so is not
  1360  // repeated here.
  1361  //
  1362  // Note: This is similar to `fstatat` in POSIX.
  1363  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat
  1364  // and https://linux.die.net/man/2/fstatat
  1365  var pathFilestatGet = newHostFunc(
  1366  	wasip1.PathFilestatGetName, pathFilestatGetFn,
  1367  	[]api.ValueType{i32, i32, i32, i32, i32},
  1368  	"fd", "flags", "path", "path_len", "result.filestat",
  1369  )
  1370  
  1371  func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1372  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1373  
  1374  	fd := int32(params[0])
  1375  	flags := uint16(params[1])
  1376  	path := uint32(params[2])
  1377  	pathLen := uint32(params[3])
  1378  
  1379  	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
  1380  	if errno != 0 {
  1381  		return errno
  1382  	}
  1383  
  1384  	// Stat the file without allocating a file descriptor.
  1385  	var st fsapi.Stat_t
  1386  
  1387  	if (flags & wasip1.LOOKUP_SYMLINK_FOLLOW) == 0 {
  1388  		st, errno = preopen.Lstat(pathName)
  1389  	} else {
  1390  		st, errno = preopen.Stat(pathName)
  1391  	}
  1392  	if errno != 0 {
  1393  		return errno
  1394  	}
  1395  
  1396  	// Write the stat result to memory
  1397  	resultBuf := uint32(params[4])
  1398  	buf, ok := mod.Memory().Read(resultBuf, 64)
  1399  	if !ok {
  1400  		return syscall.EFAULT
  1401  	}
  1402  
  1403  	filetype := getWasiFiletype(st.Mode)
  1404  	return writeFilestat(buf, &st, filetype)
  1405  }
  1406  
  1407  // pathFilestatSetTimes is the WASI function named PathFilestatSetTimesName
  1408  // which adjusts the timestamps of a file or directory.
  1409  //
  1410  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_set_timesfd-fd-flags-lookupflags-path-string-atim-timestamp-mtim-timestamp-fst_flags-fstflags---errno
  1411  var pathFilestatSetTimes = newHostFunc(
  1412  	wasip1.PathFilestatSetTimesName, pathFilestatSetTimesFn,
  1413  	[]wasm.ValueType{i32, i32, i32, i32, i64, i64, i32},
  1414  	"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags",
  1415  )
  1416  
  1417  func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1418  	fd := int32(params[0])
  1419  	flags := uint16(params[1])
  1420  	path := uint32(params[2])
  1421  	pathLen := uint32(params[3])
  1422  	atim := int64(params[4])
  1423  	mtim := int64(params[5])
  1424  	fstFlags := uint16(params[6])
  1425  
  1426  	sys := mod.(*wasm.ModuleInstance).Sys
  1427  	fsc := sys.FS()
  1428  
  1429  	times, errno := toTimes(atim, mtim, fstFlags)
  1430  	if errno != 0 {
  1431  		return errno
  1432  	}
  1433  
  1434  	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
  1435  	if errno != 0 {
  1436  		return errno
  1437  	}
  1438  
  1439  	symlinkFollow := flags&wasip1.LOOKUP_SYMLINK_FOLLOW != 0
  1440  	return preopen.Utimens(pathName, &times, symlinkFollow)
  1441  }
  1442  
  1443  // pathLink is the WASI function named PathLinkName which adjusts the
  1444  // timestamps of a file or directory.
  1445  //
  1446  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link
  1447  var pathLink = newHostFunc(
  1448  	wasip1.PathLinkName, pathLinkFn,
  1449  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32, i32},
  1450  	"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
  1451  )
  1452  
  1453  func pathLinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1454  	mem := mod.Memory()
  1455  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1456  
  1457  	oldFD := int32(params[0])
  1458  	// TODO: use old_flags?
  1459  	_ = uint32(params[1])
  1460  	oldPath := uint32(params[2])
  1461  	oldPathLen := uint32(params[3])
  1462  
  1463  	oldFS, oldName, errno := atPath(fsc, mem, oldFD, oldPath, oldPathLen)
  1464  	if errno != 0 {
  1465  		return errno
  1466  	}
  1467  
  1468  	newFD := int32(params[4])
  1469  	newPath := uint32(params[5])
  1470  	newPathLen := uint32(params[6])
  1471  
  1472  	newFS, newName, errno := atPath(fsc, mem, newFD, newPath, newPathLen)
  1473  	if errno != 0 {
  1474  		return errno
  1475  	}
  1476  
  1477  	if oldFS != newFS { // TODO: handle link across filesystems
  1478  		return syscall.ENOSYS
  1479  	}
  1480  
  1481  	return oldFS.Link(oldName, newName)
  1482  }
  1483  
  1484  // pathOpen is the WASI function named PathOpenName which opens a file or
  1485  // directory. This returns syscall.EBADF if the fd is invalid.
  1486  //
  1487  // # Parameters
  1488  //
  1489  //   - fd: file descriptor of a directory that `path` is relative to
  1490  //   - dirflags: flags to indicate how to resolve `path`
  1491  //   - path: offset in api.Memory to read the path string from
  1492  //   - pathLen: length of `path`
  1493  //   - oFlags: open flags to indicate the method by which to open the file
  1494  //   - fsRightsBase: interpret RIGHT_FD_WRITE to set O_RDWR
  1495  //   - fsRightsInheriting: ignored as rights were removed from WASI.
  1496  //     created file descriptor for `path`
  1497  //   - fdFlags: file descriptor flags
  1498  //   - resultOpenedFD: offset in api.Memory to write the newly created file
  1499  //     descriptor to.
  1500  //   - The result FD value is guaranteed to be less than 2**31
  1501  //
  1502  // Result (Errno)
  1503  //
  1504  // The return value is 0 except the following error conditions:
  1505  //   - syscall.EBADF: `fd` is invalid
  1506  //   - syscall.EFAULT: `resultOpenedFD` points to an offset out of memory
  1507  //   - syscall.ENOENT: `path` does not exist.
  1508  //   - syscall.EEXIST: `path` exists, while `oFlags` requires that it must not.
  1509  //   - syscall.ENOTDIR: `path` is not a directory, while `oFlags` requires it.
  1510  //   - syscall.EIO: a file system error
  1511  //
  1512  // For example, this function needs to first read `path` to determine the file
  1513  // to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero",
  1514  // pathOpen reads the path from api.Memory:
  1515  //
  1516  //	                pathLen
  1517  //	            +------------------------+
  1518  //	            |                        |
  1519  //	[]byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... }
  1520  //	     path --^
  1521  //
  1522  // Then, if parameters resultOpenedFD = 8, and this function opened a new file
  1523  // descriptor 5 with the given flags, this function writes the below to
  1524  // api.Memory:
  1525  //
  1526  //	                  uint32le
  1527  //	                 +--------+
  1528  //	                 |        |
  1529  //	[]byte{ 0..6, ?, 5, 0, 0, 0, ?}
  1530  //	resultOpenedFD --^
  1531  //
  1532  // # Notes
  1533  //   - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat
  1534  //   - The returned file descriptor is not guaranteed to be the lowest-number
  1535  //
  1536  // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
  1537  var pathOpen = newHostFunc(
  1538  	wasip1.PathOpenName, pathOpenFn,
  1539  	[]api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
  1540  	"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd",
  1541  )
  1542  
  1543  func pathOpenFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1544  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1545  
  1546  	preopenFD := int32(params[0])
  1547  
  1548  	// TODO: dirflags is a lookupflags, and it only has one bit: symlink_follow
  1549  	// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#lookupflags
  1550  	dirflags := uint16(params[1])
  1551  
  1552  	path := uint32(params[2])
  1553  	pathLen := uint32(params[3])
  1554  
  1555  	oflags := uint16(params[4])
  1556  
  1557  	rights := uint32(params[5])
  1558  	// inherited rights aren't used
  1559  	_ = params[6]
  1560  
  1561  	fdflags := uint16(params[7])
  1562  	resultOpenedFD := uint32(params[8])
  1563  
  1564  	preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen)
  1565  	if errno != 0 {
  1566  		return errno
  1567  	}
  1568  
  1569  	fileOpenFlags := openFlags(dirflags, oflags, fdflags, rights)
  1570  	isDir := fileOpenFlags&fsapi.O_DIRECTORY != 0
  1571  
  1572  	if isDir && oflags&wasip1.O_CREAT != 0 {
  1573  		return syscall.EINVAL // use pathCreateDirectory!
  1574  	}
  1575  
  1576  	newFD, errno := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600)
  1577  	if errno != 0 {
  1578  		return errno
  1579  	}
  1580  
  1581  	// Check any flags that require the file to evaluate.
  1582  	if isDir {
  1583  		if f, ok := fsc.LookupFile(newFD); !ok {
  1584  			return syscall.EBADF // unexpected
  1585  		} else if isDir, errno := f.File.IsDir(); errno != 0 {
  1586  			_ = fsc.CloseFile(newFD)
  1587  			return errno
  1588  		} else if !isDir {
  1589  			_ = fsc.CloseFile(newFD)
  1590  			return syscall.ENOTDIR
  1591  		}
  1592  	}
  1593  
  1594  	if !mod.Memory().WriteUint32Le(resultOpenedFD, uint32(newFD)) {
  1595  		_ = fsc.CloseFile(newFD)
  1596  		return syscall.EFAULT
  1597  	}
  1598  	return 0
  1599  }
  1600  
  1601  // atPath returns the pre-open specific path after verifying it is a directory.
  1602  //
  1603  // # Notes
  1604  //
  1605  // Languages including Zig and Rust use only pre-opens for the FD because
  1606  // wasi-libc `__wasilibc_find_relpath` will only return a preopen. That said,
  1607  // our wasi.c example shows other languages act differently and can use a non
  1608  // pre-opened file descriptor.
  1609  //
  1610  // We don't handle `AT_FDCWD`, as that's resolved in the compiler. There's no
  1611  // working directory function in WASI, so most assume CWD is "/". Notably, Zig
  1612  // has different behavior which assumes it is whatever the first pre-open name
  1613  // is.
  1614  //
  1615  // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c
  1616  // See https://linux.die.net/man/2/openat
  1617  func atPath(fsc *sys.FSContext, mem api.Memory, fd int32, p, pathLen uint32) (fsapi.FS, string, syscall.Errno) {
  1618  	b, ok := mem.Read(p, pathLen)
  1619  	if !ok {
  1620  		return nil, "", syscall.EFAULT
  1621  	}
  1622  	pathName := string(b)
  1623  
  1624  	// interesting_paths wants us to break on trailing slash if the input ends
  1625  	// up a file, not a directory!
  1626  	hasTrailingSlash := strings.HasSuffix(pathName, "/")
  1627  
  1628  	// interesting_paths includes paths that include relative links but end up
  1629  	// not escaping
  1630  	pathName = path.Clean(pathName)
  1631  
  1632  	// interesting_paths wants to break on root paths or anything that escapes.
  1633  	// This part is the same as fs.FS.Open()
  1634  	if !fs.ValidPath(pathName) {
  1635  		return nil, "", syscall.EPERM
  1636  	}
  1637  
  1638  	// add the trailing slash back
  1639  	if hasTrailingSlash {
  1640  		pathName = pathName + "/"
  1641  	}
  1642  
  1643  	if f, ok := fsc.LookupFile(fd); !ok {
  1644  		return nil, "", syscall.EBADF // closed or invalid
  1645  	} else if isDir, errno := f.File.IsDir(); errno != 0 {
  1646  		return nil, "", errno
  1647  	} else if !isDir {
  1648  		return nil, "", syscall.ENOTDIR
  1649  	} else if f.IsPreopen { // don't append the pre-open name
  1650  		return f.FS, pathName, 0
  1651  	} else {
  1652  		// Join via concat to avoid name conflict on path.Join
  1653  		return f.FS, f.Name + "/" + pathName, 0
  1654  	}
  1655  }
  1656  
  1657  func preopenPath(fsc *sys.FSContext, fd int32) (string, syscall.Errno) {
  1658  	if f, ok := fsc.LookupFile(fd); !ok {
  1659  		return "", syscall.EBADF // closed
  1660  	} else if !f.IsPreopen {
  1661  		return "", syscall.EBADF
  1662  	} else if isDir, errno := f.File.IsDir(); errno != 0 || !isDir {
  1663  		// In wasip1, only directories can be returned by fd_prestat_get as
  1664  		// there are no prestat types defined for files or sockets.
  1665  		return "", errno
  1666  	} else {
  1667  		return f.Name, 0
  1668  	}
  1669  }
  1670  
  1671  func openFlags(dirflags, oflags, fdflags uint16, rights uint32) (openFlags int) {
  1672  	if dirflags&wasip1.LOOKUP_SYMLINK_FOLLOW == 0 {
  1673  		openFlags |= fsapi.O_NOFOLLOW
  1674  	}
  1675  	if oflags&wasip1.O_DIRECTORY != 0 {
  1676  		openFlags |= fsapi.O_DIRECTORY
  1677  		return // Early return for directories as the rest of flags doesn't make sense for it.
  1678  	} else if oflags&wasip1.O_EXCL != 0 {
  1679  		openFlags |= syscall.O_EXCL
  1680  	}
  1681  	// Because we don't implement rights, we paritally rely on the open flags
  1682  	// to determine the mode in which the file will be opened. This will create
  1683  	// divergent behavior compared to WASI runtimes which have a more strict
  1684  	// interpretation of the WASI capabilities model; for example, a program
  1685  	// which sets O_CREAT but does not give read or write permissions will
  1686  	// successfully create a file when running with wazero, but might get a
  1687  	// permission denied error on other runtimes.
  1688  	defaultMode := syscall.O_RDONLY
  1689  	if oflags&wasip1.O_TRUNC != 0 {
  1690  		openFlags |= syscall.O_TRUNC
  1691  		defaultMode = syscall.O_RDWR
  1692  	}
  1693  	if oflags&wasip1.O_CREAT != 0 {
  1694  		openFlags |= syscall.O_CREAT
  1695  		defaultMode = syscall.O_RDWR
  1696  	}
  1697  	if fdflags&wasip1.FD_NONBLOCK != 0 {
  1698  		openFlags |= syscall.O_NONBLOCK
  1699  	}
  1700  	if fdflags&wasip1.FD_APPEND != 0 {
  1701  		openFlags |= syscall.O_APPEND
  1702  		defaultMode = syscall.O_RDWR
  1703  	}
  1704  	// Since rights were discontinued in wasi, we only interpret RIGHT_FD_WRITE
  1705  	// because it is the only way to know that we need to set write permissions
  1706  	// on a file if the application did not pass any of O_CREATE, O_APPEND, nor
  1707  	// O_TRUNC.
  1708  	const r = wasip1.RIGHT_FD_READ
  1709  	const w = wasip1.RIGHT_FD_WRITE
  1710  	const rw = r | w
  1711  	switch {
  1712  	case (rights & rw) == rw:
  1713  		openFlags |= syscall.O_RDWR
  1714  	case (rights & w) == w:
  1715  		openFlags |= syscall.O_WRONLY
  1716  	case (rights & r) == r:
  1717  		openFlags |= syscall.O_RDONLY
  1718  	default:
  1719  		openFlags |= defaultMode
  1720  	}
  1721  	return
  1722  }
  1723  
  1724  // pathReadlink is the WASI function named PathReadlinkName that reads the
  1725  // contents of a symbolic link.
  1726  //
  1727  // See: https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_readlinkfd-fd-path-string-buf-pointeru8-buf_len-size---errno-size
  1728  var pathReadlink = newHostFunc(
  1729  	wasip1.PathReadlinkName, pathReadlinkFn,
  1730  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
  1731  	"fd", "path", "path_len", "buf", "buf_len", "result.bufused",
  1732  )
  1733  
  1734  func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1735  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1736  
  1737  	fd := int32(params[0])
  1738  	path := uint32(params[1])
  1739  	pathLen := uint32(params[2])
  1740  	buf := uint32(params[3])
  1741  	bufLen := uint32(params[4])
  1742  	resultBufused := uint32(params[5])
  1743  
  1744  	if pathLen == 0 || bufLen == 0 {
  1745  		return syscall.EINVAL
  1746  	}
  1747  
  1748  	mem := mod.Memory()
  1749  	preopen, p, errno := atPath(fsc, mem, fd, path, pathLen)
  1750  	if errno != 0 {
  1751  		return errno
  1752  	}
  1753  
  1754  	dst, errno := preopen.Readlink(p)
  1755  	if errno != 0 {
  1756  		return errno
  1757  	}
  1758  
  1759  	if ok := mem.WriteString(buf, dst); !ok {
  1760  		return syscall.EFAULT
  1761  	}
  1762  
  1763  	if !mem.WriteUint32Le(resultBufused, uint32(len(dst))) {
  1764  		return syscall.EFAULT
  1765  	}
  1766  	return 0
  1767  }
  1768  
  1769  // pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which
  1770  // removes a directory.
  1771  //
  1772  // # Parameters
  1773  //
  1774  //   - fd: file descriptor of a directory that `path` is relative to
  1775  //   - path: offset in api.Memory to read the path string from
  1776  //   - pathLen: length of `path`
  1777  //
  1778  // # Result (Errno)
  1779  //
  1780  // The return value is 0 except the following error conditions:
  1781  //   - syscall.EBADF: `fd` is invalid
  1782  //   - syscall.ENOENT: `path` does not exist.
  1783  //   - syscall.ENOTEMPTY: `path` is not empty
  1784  //   - syscall.ENOTDIR: `path` is a file
  1785  //
  1786  // # Notes
  1787  //   - This is similar to unlinkat with AT_REMOVEDIR in POSIX.
  1788  //     See https://linux.die.net/man/2/unlinkat
  1789  //
  1790  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
  1791  var pathRemoveDirectory = newHostFunc(
  1792  	wasip1.PathRemoveDirectoryName, pathRemoveDirectoryFn,
  1793  	[]wasm.ValueType{i32, i32, i32},
  1794  	"fd", "path", "path_len",
  1795  )
  1796  
  1797  func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1798  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1799  
  1800  	fd := int32(params[0])
  1801  	path := uint32(params[1])
  1802  	pathLen := uint32(params[2])
  1803  
  1804  	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
  1805  	if errno != 0 {
  1806  		return errno
  1807  	}
  1808  
  1809  	return preopen.Rmdir(pathName)
  1810  }
  1811  
  1812  // pathRename is the WASI function named PathRenameName which renames a file or
  1813  // directory.
  1814  //
  1815  // # Parameters
  1816  //
  1817  //   - fd: file descriptor of a directory that `old_path` is relative to
  1818  //   - old_path: offset in api.Memory to read the old path string from
  1819  //   - old_path_len: length of `old_path`
  1820  //   - new_fd: file descriptor of a directory that `new_path` is relative to
  1821  //   - new_path: offset in api.Memory to read the new path string from
  1822  //   - new_path_len: length of `new_path`
  1823  //
  1824  // # Result (Errno)
  1825  //
  1826  // The return value is 0 except the following error conditions:
  1827  //   - syscall.EBADF: `fd` or `new_fd` are invalid
  1828  //   - syscall.ENOENT: `old_path` does not exist.
  1829  //   - syscall.ENOTDIR: `old` is a directory and `new` exists, but is a file.
  1830  //   - syscall.EISDIR: `old` is a file and `new` exists, but is a directory.
  1831  //
  1832  // # Notes
  1833  //   - This is similar to unlinkat in POSIX.
  1834  //     See https://linux.die.net/man/2/renameat
  1835  //
  1836  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_renamefd-fd-old_path-string-new_fd-fd-new_path-string---errno
  1837  var pathRename = newHostFunc(
  1838  	wasip1.PathRenameName, pathRenameFn,
  1839  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
  1840  	"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
  1841  )
  1842  
  1843  func pathRenameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1844  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1845  
  1846  	fd := int32(params[0])
  1847  	oldPath := uint32(params[1])
  1848  	oldPathLen := uint32(params[2])
  1849  
  1850  	newFD := int32(params[3])
  1851  	newPath := uint32(params[4])
  1852  	newPathLen := uint32(params[5])
  1853  
  1854  	oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), fd, oldPath, oldPathLen)
  1855  	if errno != 0 {
  1856  		return errno
  1857  	}
  1858  
  1859  	newFS, newPathName, errno := atPath(fsc, mod.Memory(), newFD, newPath, newPathLen)
  1860  	if errno != 0 {
  1861  		return errno
  1862  	}
  1863  
  1864  	if oldFS != newFS { // TODO: handle renames across filesystems
  1865  		return syscall.ENOSYS
  1866  	}
  1867  
  1868  	return oldFS.Rename(oldPathName, newPathName)
  1869  }
  1870  
  1871  // pathSymlink is the WASI function named PathSymlinkName which creates a
  1872  // symbolic link.
  1873  //
  1874  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink
  1875  var pathSymlink = newHostFunc(
  1876  	wasip1.PathSymlinkName, pathSymlinkFn,
  1877  	[]wasm.ValueType{i32, i32, i32, i32, i32},
  1878  	"old_path", "old_path_len", "fd", "new_path", "new_path_len",
  1879  )
  1880  
  1881  func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1882  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1883  
  1884  	oldPath := uint32(params[0])
  1885  	oldPathLen := uint32(params[1])
  1886  	fd := int32(params[2])
  1887  	newPath := uint32(params[3])
  1888  	newPathLen := uint32(params[4])
  1889  
  1890  	mem := mod.Memory()
  1891  
  1892  	dir, ok := fsc.LookupFile(fd)
  1893  	if !ok {
  1894  		return syscall.EBADF // closed
  1895  	} else if isDir, errno := dir.File.IsDir(); errno != 0 {
  1896  		return errno
  1897  	} else if !isDir {
  1898  		return syscall.ENOTDIR
  1899  	}
  1900  
  1901  	if oldPathLen == 0 || newPathLen == 0 {
  1902  		return syscall.EINVAL
  1903  	}
  1904  
  1905  	oldPathBuf, ok := mem.Read(oldPath, oldPathLen)
  1906  	if !ok {
  1907  		return syscall.EFAULT
  1908  	}
  1909  
  1910  	newPathBuf, ok := mem.Read(newPath, newPathLen)
  1911  	if !ok {
  1912  		return syscall.EFAULT
  1913  	}
  1914  
  1915  	return dir.FS.Symlink(
  1916  		// Do not join old path since it's only resolved when dereference the link created here.
  1917  		// And the dereference result depends on the opening directory's file descriptor at that point.
  1918  		bufToStr(oldPathBuf),
  1919  		path.Join(dir.Name, bufToStr(newPathBuf)),
  1920  	)
  1921  }
  1922  
  1923  // bufToStr converts the given byte slice as string unsafely.
  1924  func bufToStr(buf []byte) string {
  1925  	// TODO: use unsafe.String after flooring Go 1.20.
  1926  	return *(*string)(unsafe.Pointer(&buf))
  1927  }
  1928  
  1929  // pathUnlinkFile is the WASI function named PathUnlinkFileName which unlinks a
  1930  // file.
  1931  //
  1932  // # Parameters
  1933  //
  1934  //   - fd: file descriptor of a directory that `path` is relative to
  1935  //   - path: offset in api.Memory to read the path string from
  1936  //   - pathLen: length of `path`
  1937  //
  1938  // # Result (Errno)
  1939  //
  1940  // The return value is 0 except the following error conditions:
  1941  //   - syscall.EBADF: `fd` is invalid
  1942  //   - syscall.ENOENT: `path` does not exist.
  1943  //   - syscall.EISDIR: `path` is a directory
  1944  //
  1945  // # Notes
  1946  //   - This is similar to unlinkat without AT_REMOVEDIR in POSIX.
  1947  //     See https://linux.die.net/man/2/unlinkat
  1948  //
  1949  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno
  1950  var pathUnlinkFile = newHostFunc(
  1951  	wasip1.PathUnlinkFileName, pathUnlinkFileFn,
  1952  	[]wasm.ValueType{i32, i32, i32},
  1953  	"fd", "path", "path_len",
  1954  )
  1955  
  1956  func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
  1957  	fsc := mod.(*wasm.ModuleInstance).Sys.FS()
  1958  
  1959  	fd := int32(params[0])
  1960  	path := uint32(params[1])
  1961  	pathLen := uint32(params[2])
  1962  
  1963  	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
  1964  	if errno != 0 {
  1965  		return errno
  1966  	}
  1967  
  1968  	return preopen.Unlink(pathName)
  1969  }