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