wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/fs.go (about)

     1  package wasi_snapshot_preview1
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"errors"
     7  	"io"
     8  	"io/fs"
     9  	"math"
    10  	"path"
    11  	"syscall"
    12  
    13  	"wa-lang.org/wazero/api"
    14  	internalsys "wa-lang.org/wazero/internal/sys"
    15  	"wa-lang.org/wazero/internal/wasm"
    16  )
    17  
    18  const (
    19  	functionFdAdvise           = "fd_advise"
    20  	functionFdAllocate         = "fd_allocate"
    21  	functionFdClose            = "fd_close"
    22  	functionFdDatasync         = "fd_datasync"
    23  	functionFdFdstatGet        = "fd_fdstat_get"
    24  	functionFdFdstatSetFlags   = "fd_fdstat_set_flags"
    25  	functionFdFdstatSetRights  = "fd_fdstat_set_rights"
    26  	functionFdFilestatGet      = "fd_filestat_get"
    27  	functionFdFilestatSetSize  = "fd_filestat_set_size"
    28  	functionFdFilestatSetTimes = "fd_filestat_set_times"
    29  	functionFdPread            = "fd_pread"
    30  	functionFdPrestatGet       = "fd_prestat_get"
    31  	functionFdPrestatDirName   = "fd_prestat_dir_name"
    32  	functionFdPwrite           = "fd_pwrite"
    33  	functionFdRead             = "fd_read"
    34  	functionFdReaddir          = "fd_readdir"
    35  	functionFdRenumber         = "fd_renumber"
    36  	functionFdSeek             = "fd_seek"
    37  	functionFdSync             = "fd_sync"
    38  	functionFdTell             = "fd_tell"
    39  	functionFdWrite            = "fd_write"
    40  
    41  	functionPathCreateDirectory  = "path_create_directory"
    42  	functionPathFilestatGet      = "path_filestat_get"
    43  	functionPathFilestatSetTimes = "path_filestat_set_times"
    44  	functionPathLink             = "path_link"
    45  	functionPathOpen             = "path_open"
    46  	functionPathReadlink         = "path_readlink"
    47  	functionPathRemoveDirectory  = "path_remove_directory"
    48  	functionPathRename           = "path_rename"
    49  	functionPathSymlink          = "path_symlink"
    50  	functionPathUnlinkFile       = "path_unlink_file"
    51  )
    52  
    53  // fdAdvise is the WASI function named functionFdAdvise which provides file
    54  // advisory information on a file descriptor.
    55  //
    56  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_advisefd-fd-offset-filesize-len-filesize-advice-advice---errno
    57  var fdAdvise = stubFunction(
    58  	functionFdAdvise,
    59  	[]wasm.ValueType{i32, i64, i64, i32},
    60  	[]string{"fd", "offset", "len", "result.advice"},
    61  )
    62  
    63  // fdAllocate is the WASI function named functionFdAllocate which forces the
    64  // allocation of space in a file.
    65  //
    66  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_allocatefd-fd-offset-filesize-len-filesize---errno
    67  var fdAllocate = stubFunction(
    68  	functionFdAllocate,
    69  	[]wasm.ValueType{i32, i64, i64},
    70  	[]string{"fd", "offset", "len"},
    71  )
    72  
    73  // fdClose is the WASI function named functionFdClose which closes a file
    74  // descriptor.
    75  //
    76  // # Parameters
    77  //
    78  //   - fd: file descriptor to close
    79  //
    80  // Result (Errno)
    81  //
    82  // The return value is ErrnoSuccess except the following error conditions:
    83  //   - ErrnoBadf: the fd was not open.
    84  //
    85  // Note: This is similar to `close` in POSIX.
    86  // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
    87  // and https://linux.die.net/man/3/close
    88  var fdClose = &wasm.HostFunc{
    89  	ExportNames: []string{functionFdClose},
    90  	Name:        functionFdClose,
    91  	ParamTypes:  []api.ValueType{i32},
    92  	ParamNames:  []string{"fd"},
    93  	ResultTypes: []api.ValueType{i32},
    94  	Code: &wasm.Code{
    95  		IsHostFunction: true,
    96  		GoFunc:         wasiFunc(fdCloseFn),
    97  	},
    98  }
    99  
   100  func fdCloseFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   101  	sysCtx := mod.(*wasm.CallContext).Sys
   102  	fd := uint32(params[0])
   103  
   104  	if ok := sysCtx.FS(ctx).CloseFile(ctx, fd); !ok {
   105  		return ErrnoBadf
   106  	}
   107  	return ErrnoSuccess
   108  }
   109  
   110  // fdDatasync is the WASI function named functionFdDatasync which synchronizes
   111  // the data of a file to disk.
   112  //
   113  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
   114  var fdDatasync = stubFunction(
   115  	functionFdDatasync,
   116  	[]wasm.ValueType{i32},
   117  	[]string{"fd"},
   118  )
   119  
   120  // fdFdstatGet is the WASI function named functionFdFdstatGet which returns the
   121  // attributes of a file descriptor.
   122  //
   123  // # Parameters
   124  //
   125  //   - fd: file descriptor to get the fdstat attributes data
   126  //   - resultFdstat: offset to write the result fdstat data
   127  //
   128  // Result (Errno)
   129  //
   130  // The return value is ErrnoSuccess except the following error conditions:
   131  //   - ErrnoBadf: `fd` is invalid
   132  //   - ErrnoFault: `resultFdstat` points to an offset out of memory
   133  //
   134  // fdstat byte layout is 24-byte size, with the following fields:
   135  //   - fs_filetype 1 byte: the file type
   136  //   - fs_flags 2 bytes: the file descriptor flag
   137  //   - 5 pad bytes
   138  //   - fs_right_base 8 bytes: ignored as rights were removed from WASI.
   139  //   - fs_right_inheriting 8 bytes: ignored as rights were removed from WASI.
   140  //
   141  // For example, with a file corresponding with `fd` was a directory (=3) opened
   142  // with `fd_read` right (=1) and no fs_flags (=0), parameter resultFdstat=1,
   143  // this function writes the below to api.Memory:
   144  //
   145  //	                uint16le   padding            uint64le                uint64le
   146  //	       uint8 --+  +--+  +-----------+  +--------------------+  +--------------------+
   147  //	               |  |  |  |           |  |                    |  |                    |
   148  //	     []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}
   149  //	resultFdstat --^  ^-- fs_flags         ^-- fs_right_base       ^-- fs_right_inheriting
   150  //	               |
   151  //	               +-- fs_filetype
   152  //
   153  // Note: fdFdstatGet returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as
   154  // well as additional fields.
   155  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fdstat
   156  // and https://linux.die.net/man/3/fsync
   157  var fdFdstatGet = &wasm.HostFunc{
   158  	ExportNames: []string{functionFdFdstatGet},
   159  	Name:        functionFdFdstatGet,
   160  	ParamTypes:  []api.ValueType{i32, i32},
   161  	ParamNames:  []string{"fd", "result.stat"},
   162  	ResultTypes: []api.ValueType{i32},
   163  	Code: &wasm.Code{
   164  		IsHostFunction: true,
   165  		GoFunc:         wasiFunc(fdFdstatGetFn),
   166  	},
   167  }
   168  
   169  func fdFdstatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   170  	sysCtx := mod.(*wasm.CallContext).Sys
   171  	// TODO: actually write the fdstat!
   172  	fd, _ := uint32(params[0]), uint32(params[1])
   173  
   174  	if _, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok {
   175  		return ErrnoBadf
   176  	}
   177  	return ErrnoSuccess
   178  }
   179  
   180  // fdFdstatSetFlags is the WASI function named functionFdFdstatSetFlags which
   181  // adjusts the flags associated with a file descriptor.
   182  //
   183  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_fdstat_set_flagsfd-fd-flags-fdflags---errnoand is stubbed for GrainLang per #271
   184  var fdFdstatSetFlags = stubFunction(
   185  	functionFdFdstatSetFlags,
   186  	[]wasm.ValueType{i32, i32},
   187  	[]string{"fd", "flags"},
   188  )
   189  
   190  // fdFdstatSetRights will not be implemented as rights were removed from WASI.
   191  //
   192  // See https://github.com/bytecodealliance/wasmtime/pull/4666
   193  var fdFdstatSetRights = stubFunction(
   194  	functionFdFdstatSetRights,
   195  	[]wasm.ValueType{i32, i64, i64},
   196  	[]string{"fd", "fs_rights_base", "fs_rights_inheriting"},
   197  )
   198  
   199  // fdFilestatGet is the WASI function named functionFdFilestatGet which returns
   200  // the stat attributes of an open file.
   201  //
   202  // # Parameters
   203  //
   204  //   - fd: file descriptor to get the filestat attributes data for
   205  //   - resultFilestat: offset to write the result filestat data
   206  //
   207  // Result (Errno)
   208  //
   209  // The return value is ErrnoSuccess except the following error conditions:
   210  //   - ErrnoBadf: `fd` is invalid
   211  //   - ErrnoIo: could not stat `fd` on filesystem
   212  //   - ErrnoFault: `resultFilestat` points to an offset out of memory
   213  //
   214  // filestat byte layout is 64-byte size, with the following fields:
   215  //   - dev 8 bytes: the device ID of device containing the file
   216  //   - ino 8 bytes: the file serial number
   217  //   - filetype 1 byte: the type of the file
   218  //   - 7 pad bytes
   219  //   - nlink 8 bytes: number of hard links to the file
   220  //   - 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
   221  //   - atim 8 bytes: ast data access timestamp
   222  //   - mtim 8 bytes: last data modification timestamp
   223  //   - ctim 8 bytes: ast file status change timestamp
   224  //
   225  // For example, with a regular file this function writes the below to api.Memory:
   226  //
   227  //	                                                             uint8 --+
   228  //		                         uint64le                uint64le        |        padding               uint64le                uint64le                         uint64le                               uint64le                             uint64le
   229  //		                 +--------------------+  +--------------------+  |  +-----------------+  +--------------------+  +-----------------------+  +----------------------------------+  +----------------------------------+  +----------------------------------+
   230  //		                 |                    |  |                    |  |  |                 |  |                    |  |                       |  |                                  |  |                                  |  |                                  |
   231  //		          []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}
   232  //		resultFilestat   ^-- dev                 ^-- ino                 ^                       ^-- nlink               ^-- size                   ^-- atim                              ^-- mtim                              ^-- ctim
   233  //		                                                                 |
   234  //		                                                                 +-- filetype
   235  //
   236  // The following properties of filestat are not implemented:
   237  //   - dev: not supported by Golang FS
   238  //   - ino: not supported by Golang FS
   239  //   - nlink: not supported by Golang FS
   240  //   - atime: not supported by Golang FS, we use mtim for this
   241  //   - ctim: not supported by Golang FS, we use mtim for this
   242  //
   243  // Note: This is similar to `fstat` in POSIX.
   244  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_getfd-fd---errno-filestat
   245  // and https://linux.die.net/man/3/fstat
   246  var fdFilestatGet = &wasm.HostFunc{
   247  	ExportNames: []string{functionFdFilestatGet},
   248  	Name:        functionFdFilestatGet,
   249  	ParamTypes:  []api.ValueType{i32, i32},
   250  	ParamNames:  []string{"fd", "result.buf"},
   251  	ResultTypes: []api.ValueType{i32},
   252  	Code: &wasm.Code{
   253  		IsHostFunction: true,
   254  		GoFunc:         wasiFunc(fdFilestatGetFn),
   255  	},
   256  }
   257  
   258  type wasiFiletype uint8
   259  
   260  const (
   261  	wasiFiletypeUnknown wasiFiletype = iota
   262  	wasiFiletypeBlockDevice
   263  	wasiFiletypeCharacterDevice
   264  	wasiFiletypeDirectory
   265  	wasiFiletypeRegularFile
   266  	wasiFiletypeSocketDgram
   267  	wasiFiletypeSocketStream
   268  	wasiFiletypeSymbolicLink
   269  )
   270  
   271  func fdFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   272  	return fdFilestatGetFunc(ctx, mod, uint32(params[0]), uint32(params[1]))
   273  }
   274  
   275  func fdFilestatGetFunc(ctx context.Context, mod api.Module, fd, resultBuf uint32) Errno {
   276  	sysCtx := mod.(*wasm.CallContext).Sys
   277  	file, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
   278  	if !ok {
   279  		return ErrnoBadf
   280  	}
   281  
   282  	fileStat, err := file.File.Stat()
   283  	if err != nil {
   284  		return ErrnoIo
   285  	}
   286  
   287  	fileMode := fileStat.Mode()
   288  
   289  	wasiFileMode := wasiFiletypeUnknown
   290  	if fileMode&fs.ModeDevice != 0 {
   291  		wasiFileMode = wasiFiletypeBlockDevice
   292  	} else if fileMode&fs.ModeCharDevice != 0 {
   293  		wasiFileMode = wasiFiletypeCharacterDevice
   294  	} else if fileMode&fs.ModeDir != 0 {
   295  		wasiFileMode = wasiFiletypeDirectory
   296  	} else if fileMode&fs.ModeType == 0 {
   297  		wasiFileMode = wasiFiletypeRegularFile
   298  	} else if fileMode&fs.ModeSymlink != 0 {
   299  		wasiFileMode = wasiFiletypeSymbolicLink
   300  	}
   301  
   302  	buf, ok := mod.Memory().Read(ctx, resultBuf, 64)
   303  	if !ok {
   304  		return ErrnoFault
   305  	}
   306  
   307  	buf[16] = uint8(wasiFileMode)
   308  	size := uint64(fileStat.Size())
   309  	binary.LittleEndian.PutUint64(buf[32:], size)
   310  	mtim := uint64(fileStat.ModTime().UnixNano())
   311  	binary.LittleEndian.PutUint64(buf[40:], mtim)
   312  	binary.LittleEndian.PutUint64(buf[48:], mtim)
   313  	binary.LittleEndian.PutUint64(buf[56:], mtim)
   314  
   315  	return ErrnoSuccess
   316  }
   317  
   318  // fdFilestatSetSize is the WASI function named functionFdFilestatSetSize which
   319  // adjusts the size of an open file.
   320  //
   321  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
   322  var fdFilestatSetSize = stubFunction(
   323  	functionFdFilestatSetSize,
   324  	[]wasm.ValueType{i32, i64},
   325  	[]string{"fd", "size"},
   326  )
   327  
   328  // fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
   329  // which adjusts the times of an open file.
   330  //
   331  // 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
   332  var fdFilestatSetTimes = stubFunction(
   333  	functionFdFilestatSetTimes,
   334  	[]wasm.ValueType{i32, i64, i64, i32},
   335  	[]string{"fd", "atim", "mtim", "fst_flags"},
   336  )
   337  
   338  // fdPread is the WASI function named functionFdPread which reads from a file
   339  // descriptor, without using and updating the file descriptor's offset.
   340  //
   341  // Except for handling offset, this implementation is identical to fdRead.
   342  //
   343  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---errno-size
   344  var fdPread = &wasm.HostFunc{
   345  	ExportNames: []string{functionFdPread},
   346  	Name:        functionFdPread,
   347  	ParamTypes:  []api.ValueType{i32, i32, i32, i64, i32},
   348  	ParamNames:  []string{"fd", "iovs", "iovs_len", "offset", "result.size"},
   349  	ResultTypes: []api.ValueType{i32},
   350  	Code: &wasm.Code{
   351  		IsHostFunction: true,
   352  		GoFunc:         wasiFunc(fdPreadFn),
   353  	},
   354  }
   355  
   356  func fdPreadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   357  	return fdReadOrPread(ctx, mod, params, true)
   358  }
   359  
   360  // fdPrestatGet is the WASI function named functionFdPrestatGet which returns
   361  // the prestat data of a file descriptor.
   362  //
   363  // # Parameters
   364  //
   365  //   - fd: file descriptor to get the prestat
   366  //   - resultPrestat: offset to write the result prestat data
   367  //
   368  // Result (Errno)
   369  //
   370  // The return value is ErrnoSuccess except the following error conditions:
   371  //   - ErrnoBadf: `fd` is invalid or the `fd` is not a pre-opened directory
   372  //   - ErrnoFault: `resultPrestat` points to an offset out of memory
   373  //
   374  // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes.
   375  // The only valid tag is `prestat_dir`, which is tag zero. This simplifies the
   376  // byte layout to 4 empty bytes followed by the uint32le encoded path length.
   377  //
   378  // For example, the directory name corresponding with `fd` was "/tmp" and
   379  // parameter resultPrestat=1, this function writes the below to api.Memory:
   380  //
   381  //	                   padding   uint32le
   382  //	        uint8 --+  +-----+  +--------+
   383  //	                |  |     |  |        |
   384  //	      []byte{?, 0, 0, 0, 0, 4, 0, 0, 0, ?}
   385  //	resultPrestat --^           ^
   386  //	          tag --+           |
   387  //	                            +-- size in bytes of the string "/tmp"
   388  //
   389  // See fdPrestatDirName and
   390  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
   391  var fdPrestatGet = &wasm.HostFunc{
   392  	ExportNames: []string{functionFdPrestatGet},
   393  	Name:        functionFdPrestatGet,
   394  	ParamTypes:  []api.ValueType{i32, i32},
   395  	ParamNames:  []string{"fd", "result.prestat"},
   396  	ResultTypes: []api.ValueType{i32},
   397  	Code: &wasm.Code{
   398  		IsHostFunction: true,
   399  		GoFunc:         wasiFunc(fdPrestatGetFn),
   400  	},
   401  }
   402  
   403  func fdPrestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   404  	sysCtx := mod.(*wasm.CallContext).Sys
   405  	fd, resultPrestat := uint32(params[0]), uint32(params[1])
   406  
   407  	entry, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
   408  	if !ok {
   409  		return ErrnoBadf
   410  	}
   411  
   412  	// Zero-value 8-bit tag, and 3-byte zero-value paddings, which is uint32le(0) in short.
   413  	if !mod.Memory().WriteUint32Le(ctx, resultPrestat, uint32(0)) {
   414  		return ErrnoFault
   415  	}
   416  
   417  	// Write the length of the directory name at offset 4.
   418  	if !mod.Memory().WriteUint32Le(ctx, resultPrestat+4, uint32(len(entry.Path))) {
   419  		return ErrnoFault
   420  	}
   421  	return ErrnoSuccess
   422  }
   423  
   424  // fdPrestatDirName is the WASI function named functionFdPrestatDirName which
   425  // returns the path of the pre-opened directory of a file descriptor.
   426  //
   427  // # Parameters
   428  //
   429  //   - fd: file descriptor to get the path of the pre-opened directory
   430  //   - path: offset in api.Memory to write the result path
   431  //   - pathLen: count of bytes to write to `path`
   432  //   - This should match the uint32le fdPrestatGet writes to offset
   433  //     `resultPrestat`+4
   434  //
   435  // Result (Errno)
   436  //
   437  // The return value is ErrnoSuccess except the following error conditions:
   438  //   - ErrnoBadf: `fd` is invalid
   439  //   - ErrnoFault: `path` points to an offset out of memory
   440  //   - ErrnoNametoolong: `pathLen` is longer than the actual length of the result
   441  //
   442  // For example, the directory name corresponding with `fd` was "/tmp" and
   443  // # Parameters path=1 pathLen=4 (correct), this function will write the below to
   444  // api.Memory:
   445  //
   446  //	               pathLen
   447  //	           +--------------+
   448  //	           |              |
   449  //	[]byte{?, '/', 't', 'm', 'p', ?}
   450  //	    path --^
   451  //
   452  // See fdPrestatGet
   453  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_prestat_dir_name
   454  var fdPrestatDirName = &wasm.HostFunc{
   455  	ExportNames: []string{functionFdPrestatDirName},
   456  	Name:        functionFdPrestatDirName,
   457  	ParamTypes:  []api.ValueType{i32, i32, i32},
   458  	ParamNames:  []string{"fd", "path", "path_len"},
   459  	ResultTypes: []api.ValueType{i32},
   460  	Code: &wasm.Code{
   461  		IsHostFunction: true,
   462  		GoFunc:         wasiFunc(fdPrestatDirNameFn),
   463  	},
   464  }
   465  
   466  func fdPrestatDirNameFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   467  	sysCtx := mod.(*wasm.CallContext).Sys
   468  	fd, path, pathLen := uint32(params[0]), uint32(params[1]), uint32(params[2])
   469  
   470  	f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd)
   471  	if !ok {
   472  		return ErrnoBadf
   473  	}
   474  
   475  	// Some runtimes may have another semantics. See /RATIONALE.md
   476  	if uint32(len(f.Path)) < pathLen {
   477  		return ErrnoNametoolong
   478  	}
   479  
   480  	// TODO: fdPrestatDirName may have to return ErrnoNotdir if the type of the
   481  	// prestat data of `fd` is not a PrestatDir.
   482  	if !mod.Memory().Write(ctx, path, []byte(f.Path)[:pathLen]) {
   483  		return ErrnoFault
   484  	}
   485  	return ErrnoSuccess
   486  }
   487  
   488  // fdPwrite is the WASI function named functionFdPwrite which writes to a file
   489  // descriptor, without using and updating the file descriptor's offset.
   490  //
   491  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_pwritefd-fd-iovs-ciovec_array-offset-filesize---errno-size
   492  var fdPwrite = stubFunction(functionFdPwrite,
   493  	[]wasm.ValueType{i32, i32, i32, i64, i32},
   494  	[]string{"fd", "iovs", "iovs_len", "offset", "result.nwritten"},
   495  )
   496  
   497  // fdRead is the WASI function named functionFdRead which reads from a file
   498  // descriptor.
   499  //
   500  // # Parameters
   501  //
   502  //   - fd: an opened file descriptor to read data from
   503  //   - iovs: offset in api.Memory to read offset, size pairs representing where
   504  //     to write file data
   505  //   - Both offset and length are encoded as uint32le
   506  //   - iovsCount: count of memory offset, size pairs to read sequentially
   507  //     starting at iovs
   508  //   - resultSize: offset in api.Memory to write the number of bytes read
   509  //
   510  // Result (Errno)
   511  //
   512  // The return value is ErrnoSuccess except the following error conditions:
   513  //   - ErrnoBadf: `fd` is invalid
   514  //   - ErrnoFault: `iovs` or `resultSize` point to an offset out of memory
   515  //   - ErrnoIo: a file system error
   516  //
   517  // For example, this function needs to first read `iovs` to determine where
   518  // to write contents. If parameters iovs=1 iovsCount=2, this function reads two
   519  // offset/length pairs from api.Memory:
   520  //
   521  //	                  iovs[0]                  iovs[1]
   522  //	          +---------------------+   +--------------------+
   523  //	          | uint32le    uint32le|   |uint32le    uint32le|
   524  //	          +---------+  +--------+   +--------+  +--------+
   525  //	          |         |  |        |   |        |  |        |
   526  //	[]byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
   527  //	   iovs --^            ^            ^           ^
   528  //	          |            |            |           |
   529  //	 offset --+   length --+   offset --+  length --+
   530  //
   531  // If the contents of the `fd` parameter was "wazero" (6 bytes) and parameter
   532  // resultSize=26, this function writes the below to api.Memory:
   533  //
   534  //	                    iovs[0].length        iovs[1].length
   535  //	                   +--------------+       +----+       uint32le
   536  //	                   |              |       |    |      +--------+
   537  //	[]byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ?, 6, 0, 0, 0 }
   538  //	  iovs[0].offset --^                      ^           ^
   539  //	                         iovs[1].offset --+           |
   540  //	                                         resultSize --+
   541  //
   542  // Note: This is similar to `readv` in POSIX. https://linux.die.net/man/3/readv
   543  //
   544  // See fdWrite
   545  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_read
   546  var fdRead = &wasm.HostFunc{
   547  	ExportNames: []string{functionFdRead},
   548  	Name:        functionFdRead,
   549  	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
   550  	ParamNames:  []string{"fd", "iovs", "iovs_len", "result.size"},
   551  	ResultTypes: []api.ValueType{i32},
   552  	Code: &wasm.Code{
   553  		IsHostFunction: true,
   554  		GoFunc:         wasiFunc(fdReadFn),
   555  	},
   556  }
   557  
   558  func fdReadFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   559  	return fdReadOrPread(ctx, mod, params, false)
   560  }
   561  
   562  func fdReadOrPread(ctx context.Context, mod api.Module, params []uint64, isPread bool) Errno {
   563  	sysCtx := mod.(*wasm.CallContext).Sys
   564  	mem := mod.Memory()
   565  	fd := uint32(params[0])
   566  	iovs := uint32(params[1])
   567  	iovsCount := uint32(params[2])
   568  
   569  	var offset int64
   570  	var resultSize uint32
   571  	if isPread {
   572  		offset = int64(params[3])
   573  		resultSize = uint32(params[4])
   574  	} else {
   575  		resultSize = uint32(params[3])
   576  	}
   577  
   578  	r := internalsys.FdReader(ctx, sysCtx, fd)
   579  	if r == nil {
   580  		return ErrnoBadf
   581  	}
   582  
   583  	if isPread {
   584  		if s, ok := r.(io.Seeker); ok {
   585  			if _, err := s.Seek(offset, io.SeekStart); err != nil {
   586  				return ErrnoFault
   587  			}
   588  		} else {
   589  			return ErrnoInval
   590  		}
   591  	}
   592  
   593  	var nread uint32
   594  	for i := uint32(0); i < iovsCount; i++ {
   595  		iov := iovs + i*8
   596  		offset, ok := mem.ReadUint32Le(ctx, iov)
   597  		if !ok {
   598  			return ErrnoFault
   599  		}
   600  		l, ok := mem.ReadUint32Le(ctx, iov+4)
   601  		if !ok {
   602  			return ErrnoFault
   603  		}
   604  		b, ok := mem.Read(ctx, offset, l)
   605  		if !ok {
   606  			return ErrnoFault
   607  		}
   608  
   609  		n, err := r.Read(b)
   610  		nread += uint32(n)
   611  
   612  		shouldContinue, errno := fdRead_shouldContinueRead(uint32(n), l, err)
   613  		if errno != ErrnoSuccess {
   614  			return errno
   615  		} else if !shouldContinue {
   616  			break
   617  		}
   618  	}
   619  	if !mem.WriteUint32Le(ctx, resultSize, nread) {
   620  		return ErrnoFault
   621  	} else {
   622  		return ErrnoSuccess
   623  	}
   624  }
   625  
   626  // fdRead_shouldContinueRead decides whether to continue reading the next iovec
   627  // based on the amount read (n/l) and a possible error returned from io.Reader.
   628  //
   629  // Note: When there are both bytes read (n) and an error, this continues.
   630  // See /RATIONALE.md "Why ignore the error returned by io.Reader when n > 1?"
   631  func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) {
   632  	if errors.Is(err, io.EOF) {
   633  		return false, ErrnoSuccess // EOF isn't an error, and we shouldn't continue.
   634  	} else if err != nil && n == 0 {
   635  		return false, ErrnoIo
   636  	} else if err != nil {
   637  		return false, ErrnoSuccess // Allow the caller to process n bytes.
   638  	}
   639  	// Continue reading, unless there's a partial read or nothing to read.
   640  	return n == l && n != 0, ErrnoSuccess
   641  }
   642  
   643  // fdReaddir is the WASI function named functionFdReaddir which reads directory
   644  // entries from a directory.
   645  //
   646  // 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
   647  var fdReaddir = &wasm.HostFunc{
   648  	ExportNames: []string{functionFdReaddir},
   649  	Name:        functionFdReaddir,
   650  	ParamTypes:  []wasm.ValueType{i32, i32, i32, i64, i32},
   651  	ParamNames:  []string{"fd", "buf", "buf_len", "cookie", "result.bufused"},
   652  	ResultTypes: []api.ValueType{i32},
   653  	Code: &wasm.Code{
   654  		IsHostFunction: true,
   655  		GoFunc:         wasiFunc(fdReaddirFn),
   656  	},
   657  }
   658  
   659  func fdReaddirFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   660  	fd := uint32(params[0])
   661  	buf := uint32(params[1])
   662  	bufLen := uint32(params[2])
   663  	// We control the value of the cookie, and it should never be negative.
   664  	// However, we coerce it to signed to ensure the caller doesn't manipulate
   665  	// it in such a way that becomes negative.
   666  	cookie := int64(params[3])
   667  	resultBufused := uint32(params[4])
   668  
   669  	// Validate the FD is a directory
   670  	rd, dir, errno := openedDir(ctx, mod, fd)
   671  	if errno != ErrnoSuccess {
   672  		return errno
   673  	}
   674  
   675  	// expect a cookie only if we are continuing a read.
   676  	if cookie == 0 && dir.CountRead > 0 {
   677  		return ErrnoInval // invalid as a cookie is minimally one.
   678  	}
   679  
   680  	// First, determine the maximum directory entries that can be encoded as
   681  	// dirents. The total size is direntSize(24) + nameSize, for each file.
   682  	// Since a zero-length file name is invalid, the minimum size entry is
   683  	// 25 (direntSize + 1 character).
   684  	maxDirEntries := int(bufLen/direntSize + 1)
   685  
   686  	// While unlikely maxDirEntries will fit into bufLen, add one more just in
   687  	// case, as we need to know if we hit the end of the directory or not to
   688  	// write the correct bufused (e.g. == bufLen unless EOF).
   689  	//	>> If less than the size of the read buffer, the end of the
   690  	//	>> directory has been reached.
   691  	maxDirEntries += 1
   692  
   693  	// The host keeps state for any unread entries from the prior call because
   694  	// we cannot seek to a previous directory position. Collect these entries.
   695  	entries, errno := lastDirEntries(dir, cookie)
   696  	if errno != ErrnoSuccess {
   697  		return errno
   698  	}
   699  
   700  	// Check if we have maxDirEntries, and read more from the FS as needed.
   701  	if entryCount := len(entries); entryCount < maxDirEntries {
   702  		if l, err := rd.ReadDir(maxDirEntries - entryCount); err != io.EOF {
   703  			if err != nil {
   704  				return ErrnoIo
   705  			}
   706  			dir.CountRead += uint64(len(l))
   707  			entries = append(entries, l...)
   708  			// Replace the cache with up to maxDirEntries, starting at cookie.
   709  			dir.Entries = entries
   710  		}
   711  	}
   712  
   713  	mem := mod.Memory()
   714  
   715  	// Determine how many dirents we can write, excluding a potentially
   716  	// truncated entry.
   717  	bufused, direntCount, writeTruncatedEntry := maxDirents(entries, bufLen)
   718  
   719  	// Now, write entries to the underlying buffer.
   720  	if bufused > 0 {
   721  
   722  		// d_next is the index of the next file in the list, so it should
   723  		// always be one higher than the requested cookie.
   724  		d_next := uint64(cookie + 1)
   725  		// ^^ yes this can overflow to negative, which means our implementation
   726  		// doesn't support writing greater than max int64 entries.
   727  
   728  		dirents, ok := mem.Read(ctx, buf, bufused)
   729  		if !ok {
   730  			return ErrnoFault
   731  		}
   732  
   733  		writeDirents(entries, direntCount, writeTruncatedEntry, dirents, d_next)
   734  	}
   735  
   736  	if !mem.WriteUint32Le(ctx, resultBufused, bufused) {
   737  		return ErrnoFault
   738  	}
   739  	return ErrnoSuccess
   740  }
   741  
   742  const largestDirent = int64(math.MaxUint32 - direntSize)
   743  
   744  // lastDirEntries is broken out from fdReaddirFn for testability.
   745  func lastDirEntries(dir *internalsys.ReadDir, cookie int64) (entries []fs.DirEntry, errno Errno) {
   746  	if cookie < 0 {
   747  		errno = ErrnoInval // invalid as we will never send a negative cookie.
   748  		return
   749  	}
   750  
   751  	entryCount := int64(len(dir.Entries))
   752  	if entryCount == 0 { // there was no prior call
   753  		if cookie != 0 {
   754  			errno = ErrnoInval // invalid as we haven't sent that cookie
   755  		}
   756  		return
   757  	}
   758  
   759  	// Get the first absolute position in our window of results
   760  	firstPos := int64(dir.CountRead) - entryCount
   761  	cookiePos := cookie - firstPos
   762  
   763  	switch {
   764  	case cookiePos < 0: // cookie is asking for results outside our window.
   765  		errno = ErrnoNosys // we can't implement directory seeking backwards.
   766  	case cookiePos == 0: // cookie is asking for the next page.
   767  	case cookiePos > entryCount:
   768  		errno = ErrnoInval // invalid as we read that far, yet.
   769  	case cookiePos > 0: // truncate so to avoid large lists.
   770  		entries = dir.Entries[cookiePos:]
   771  	default:
   772  		entries = dir.Entries
   773  	}
   774  	if len(entries) == 0 {
   775  		entries = nil
   776  	}
   777  	return
   778  }
   779  
   780  // direntSize is the size of the dirent struct, which should be followed by the
   781  // length of a file name.
   782  const direntSize = uint32(24)
   783  
   784  // maxDirents returns the maximum count and total entries that can fit in
   785  // maxLen bytes.
   786  //
   787  // truncatedEntryLen is the amount of bytes past bufLen needed to write the
   788  // next entry. We have to return bufused == bufLen unless the directory is
   789  // exhausted.
   790  //
   791  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
   792  // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c#L44
   793  func maxDirents(entries []fs.DirEntry, bufLen uint32) (bufused, direntCount uint32, writeTruncatedEntry bool) {
   794  	lenRemaining := bufLen
   795  	for _, e := range entries {
   796  		if lenRemaining < direntSize {
   797  			// We don't have enough space in bufLen for another struct,
   798  			// entry. A caller who wants more will retry.
   799  
   800  			// bufused == bufLen means more entries exist, which is the case
   801  			// when the dirent is larger than bytes remaining.
   802  			bufused = bufLen
   803  			break
   804  		}
   805  
   806  		// use int64 to guard against huge filenames
   807  		nameLen := int64(len(e.Name()))
   808  		var entryLen uint32
   809  
   810  		// Check to see if direntSize + nameLen overflows, or if it would be
   811  		// larger than possible to encode.
   812  		if el := int64(direntSize) + nameLen; el < 0 || el > largestDirent {
   813  			// panic, as testing is difficult. ex we would have to extract a
   814  			// function to get size of a string or allocate a 2^32 size one!
   815  			panic("invalid filename: too large")
   816  		} else { // we know this can fit into a uint32
   817  			entryLen = uint32(el)
   818  		}
   819  
   820  		if entryLen > lenRemaining {
   821  			// We haven't room to write the entry, and docs say to write the
   822  			// header. This helps especially when there is an entry with a very
   823  			// long filename. Ex if bufLen is 4096 and the filename is 4096,
   824  			// we need to write direntSize(24) + 4096 bytes to write the entry.
   825  			// In this case, we only write up to direntSize(24) to allow the
   826  			// caller to resize.
   827  
   828  			// bufused == bufLen means more entries exist, which is the case
   829  			// when the next entry is larger than bytes remaining.
   830  			bufused = bufLen
   831  
   832  			// We do have enough space to write the header, this value will be
   833  			// passed on to writeDirents to only write the header for this entry.
   834  			writeTruncatedEntry = true
   835  			break
   836  		}
   837  
   838  		// This won't go negative because we checked entryLen <= lenRemaining.
   839  		lenRemaining -= entryLen
   840  		bufused += entryLen
   841  		direntCount++
   842  	}
   843  	return
   844  }
   845  
   846  // writeDirents writes the directory entries to the buffer, which is pre-sized
   847  // based on maxDirents.	truncatedEntryLen means write one past entryCount,
   848  // without its name. See maxDirents for why
   849  func writeDirents(
   850  	entries []fs.DirEntry,
   851  	entryCount uint32,
   852  	writeTruncatedEntry bool,
   853  	dirents []byte,
   854  	d_next uint64,
   855  ) {
   856  	pos, i := uint32(0), uint32(0)
   857  	for ; i < entryCount; i++ {
   858  		e := entries[i]
   859  		nameLen := uint32(len(e.Name()))
   860  
   861  		writeDirent(dirents[pos:], d_next, nameLen, e.IsDir())
   862  		pos += direntSize
   863  
   864  		copy(dirents[pos:], e.Name())
   865  		pos += nameLen
   866  		d_next++
   867  	}
   868  
   869  	if !writeTruncatedEntry {
   870  		return
   871  	}
   872  
   873  	// Write a dirent without its name
   874  	dirent := make([]byte, direntSize)
   875  	e := entries[i]
   876  	writeDirent(dirent, d_next, uint32(len(e.Name())), e.IsDir())
   877  
   878  	// Potentially truncate it
   879  	copy(dirents[pos:], dirent)
   880  }
   881  
   882  // writeDirent writes direntSize bytes
   883  func writeDirent(buf []byte, dNext uint64, dNamlen uint32, dType bool) {
   884  	binary.LittleEndian.PutUint64(buf, dNext)        // d_next
   885  	binary.LittleEndian.PutUint64(buf[8:], 0)        // no d_ino
   886  	binary.LittleEndian.PutUint32(buf[16:], dNamlen) // d_namlen
   887  
   888  	filetype := wasiFiletypeRegularFile
   889  	if dType {
   890  		filetype = wasiFiletypeDirectory
   891  	}
   892  	binary.LittleEndian.PutUint32(buf[20:], uint32(filetype)) //  d_type
   893  }
   894  
   895  // openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory.
   896  func openedDir(ctx context.Context, mod api.Module, fd uint32) (fs.ReadDirFile, *internalsys.ReadDir, Errno) {
   897  	fsc := mod.(*wasm.CallContext).Sys.FS(ctx)
   898  	if f, ok := fsc.OpenedFile(ctx, fd); !ok {
   899  		return nil, nil, ErrnoBadf
   900  	} else if d, ok := f.File.(fs.ReadDirFile); !ok {
   901  		return nil, nil, ErrnoNotdir
   902  	} else {
   903  		if f.ReadDir == nil {
   904  			f.ReadDir = &internalsys.ReadDir{}
   905  		}
   906  		return d, f.ReadDir, ErrnoSuccess
   907  	}
   908  }
   909  
   910  // fdRenumber is the WASI function named functionFdRenumber which atomically
   911  // replaces a file descriptor by renumbering another file descriptor.
   912  //
   913  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
   914  var fdRenumber = stubFunction(
   915  	functionFdRenumber,
   916  	[]wasm.ValueType{i32, i32},
   917  	[]string{"fd", "to"},
   918  )
   919  
   920  // fdSeek is the WASI function named functionFdSeek which moves the offset of a
   921  // file descriptor.
   922  //
   923  // # Parameters
   924  //
   925  //   - fd: file descriptor to move the offset of
   926  //   - offset: signed int64, which is encoded as uint64, input argument to
   927  //     `whence`, which results in a new offset
   928  //   - whence: operator that creates the new offset, given `offset` bytes
   929  //   - If io.SeekStart, new offset == `offset`.
   930  //   - If io.SeekCurrent, new offset == existing offset + `offset`.
   931  //   - If io.SeekEnd, new offset == file size of `fd` + `offset`.
   932  //   - resultNewoffset: offset in api.Memory to write the new offset to,
   933  //     relative to start of the file
   934  //
   935  // Result (Errno)
   936  //
   937  // The return value is ErrnoSuccess except the following error conditions:
   938  //   - ErrnoBadf: `fd` is invalid
   939  //   - ErrnoFault: `resultNewoffset` points to an offset out of memory
   940  //   - ErrnoInval: `whence` is an invalid value
   941  //   - ErrnoIo: a file system error
   942  //
   943  // For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4,
   944  // whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below
   945  // to api.Memory:
   946  //
   947  //	                         uint64le
   948  //	                  +--------------------+
   949  //	                  |                    |
   950  //	        []byte{?, 4, 0, 0, 0, 0, 0, 0, 0, ? }
   951  //	resultNewoffset --^
   952  //
   953  // Note: This is similar to `lseek` in POSIX. https://linux.die.net/man/3/lseek
   954  //
   955  // See io.Seeker
   956  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_seek
   957  var fdSeek = &wasm.HostFunc{
   958  	ExportNames: []string{functionFdSeek},
   959  	Name:        functionFdSeek,
   960  	ParamTypes:  []api.ValueType{i32, i64, i32, i32},
   961  	ParamNames:  []string{"fd", "offset", "whence", "result.newoffset"},
   962  	ResultTypes: []api.ValueType{i32},
   963  	Code: &wasm.Code{
   964  		IsHostFunction: true,
   965  		GoFunc:         wasiFunc(fdSeekFn),
   966  	},
   967  }
   968  
   969  func fdSeekFn(ctx context.Context, mod api.Module, params []uint64) Errno {
   970  	sysCtx := mod.(*wasm.CallContext).Sys
   971  	fd := uint32(params[0])
   972  	offset := params[1]
   973  	whence := uint32(params[2])
   974  	resultNewoffset := uint32(params[3])
   975  
   976  	var seeker io.Seeker
   977  	// Check to see if the file descriptor is available
   978  	if f, ok := sysCtx.FS(ctx).OpenedFile(ctx, fd); !ok || f.File == nil {
   979  		return ErrnoBadf
   980  		// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it.
   981  	} else if seeker, ok = f.File.(io.Seeker); !ok {
   982  		return ErrnoBadf
   983  	}
   984  
   985  	if whence > io.SeekEnd /* exceeds the largest valid whence */ {
   986  		return ErrnoInval
   987  	}
   988  
   989  	newOffset, err := seeker.Seek(int64(offset), int(whence))
   990  	if err != nil {
   991  		return ErrnoIo
   992  	}
   993  	if !mod.Memory().WriteUint64Le(ctx, resultNewoffset, uint64(newOffset)) {
   994  		return ErrnoFault
   995  	}
   996  	return ErrnoSuccess
   997  }
   998  
   999  // fdSync is the WASI function named functionFdSync which synchronizes the data
  1000  // and metadata of a file to disk.
  1001  //
  1002  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
  1003  var fdSync = stubFunction(
  1004  	functionFdSync,
  1005  	[]wasm.ValueType{i32},
  1006  	[]string{"fd"},
  1007  )
  1008  
  1009  // fdTell is the WASI function named functionFdTell which returns the current
  1010  // offset of a file descriptor.
  1011  //
  1012  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
  1013  var fdTell = stubFunction(
  1014  	functionFdTell,
  1015  	[]wasm.ValueType{i32, i32},
  1016  	[]string{"fd", "result.offset"},
  1017  )
  1018  
  1019  // fdWrite is the WASI function named functionFdWrite which writes to a file
  1020  // descriptor.
  1021  //
  1022  // # Parameters
  1023  //
  1024  //   - fd: an opened file descriptor to write data to
  1025  //   - iovs: offset in api.Memory to read offset, size pairs representing the
  1026  //     data to write to `fd`
  1027  //   - Both offset and length are encoded as uint32le.
  1028  //   - iovsCount: count of memory offset, size pairs to read sequentially
  1029  //     starting at iovs
  1030  //   - resultSize: offset in api.Memory to write the number of bytes written
  1031  //
  1032  // Result (Errno)
  1033  //
  1034  // The return value is ErrnoSuccess except the following error conditions:
  1035  //   - ErrnoBadf: `fd` is invalid
  1036  //   - ErrnoFault: `iovs` or `resultSize` point to an offset out of memory
  1037  //   - ErrnoIo: a file system error
  1038  //
  1039  // For example, this function needs to first read `iovs` to determine what to
  1040  // write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two
  1041  // offset/length pairs from api.Memory:
  1042  //
  1043  //	                  iovs[0]                  iovs[1]
  1044  //	          +---------------------+   +--------------------+
  1045  //	          | uint32le    uint32le|   |uint32le    uint32le|
  1046  //	          +---------+  +--------+   +--------+  +--------+
  1047  //	          |         |  |        |   |        |  |        |
  1048  //	[]byte{?, 18, 0, 0, 0, 4, 0, 0, 0, 23, 0, 0, 0, 2, 0, 0, 0, ?... }
  1049  //	   iovs --^            ^            ^           ^
  1050  //	          |            |            |           |
  1051  //	 offset --+   length --+   offset --+  length --+
  1052  //
  1053  // This function reads those chunks api.Memory into the `fd` sequentially.
  1054  //
  1055  //	                    iovs[0].length        iovs[1].length
  1056  //	                   +--------------+       +----+
  1057  //	                   |              |       |    |
  1058  //	[]byte{ 0..16, ?, 'w', 'a', 'z', 'e', ?, 'r', 'o', ? }
  1059  //	  iovs[0].offset --^                      ^
  1060  //	                         iovs[1].offset --+
  1061  //
  1062  // Since "wazero" was written, if parameter resultSize=26, this function writes
  1063  // the below to api.Memory:
  1064  //
  1065  //	                   uint32le
  1066  //	                  +--------+
  1067  //	                  |        |
  1068  //	[]byte{ 0..24, ?, 6, 0, 0, 0', ? }
  1069  //	     resultSize --^
  1070  //
  1071  // Note: This is similar to `writev` in POSIX. https://linux.die.net/man/3/writev
  1072  //
  1073  // See fdRead
  1074  // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#ciovec
  1075  // and https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_write
  1076  var fdWrite = &wasm.HostFunc{
  1077  	ExportNames: []string{functionFdWrite},
  1078  	Name:        functionFdWrite,
  1079  	ParamTypes:  []api.ValueType{i32, i32, i32, i32},
  1080  	ParamNames:  []string{"fd", "iovs", "iovs_len", "result.size"},
  1081  	ResultTypes: []api.ValueType{i32},
  1082  	Code: &wasm.Code{
  1083  		IsHostFunction: true,
  1084  		GoFunc:         wasiFunc(fdWriteFn),
  1085  	},
  1086  }
  1087  
  1088  func fdWriteFn(ctx context.Context, mod api.Module, params []uint64) Errno {
  1089  	fd := uint32(params[0])
  1090  	iovs := uint32(params[1])
  1091  	iovsCount := uint32(params[2])
  1092  	resultSize := uint32(params[3])
  1093  
  1094  	sysCtx := mod.(*wasm.CallContext).Sys
  1095  	writer := internalsys.FdWriter(ctx, sysCtx, fd)
  1096  	if writer == nil {
  1097  		return ErrnoBadf
  1098  	}
  1099  
  1100  	var err error
  1101  	var nwritten uint32
  1102  	for i := uint32(0); i < iovsCount; i++ {
  1103  		iov := iovs + i*8
  1104  		offset, ok := mod.Memory().ReadUint32Le(ctx, iov)
  1105  		if !ok {
  1106  			return ErrnoFault
  1107  		}
  1108  		// Note: emscripten has been known to write zero length iovec. However,
  1109  		// it is not common in other compilers, so we don't optimize for it.
  1110  		l, ok := mod.Memory().ReadUint32Le(ctx, iov+4)
  1111  		if !ok {
  1112  			return ErrnoFault
  1113  		}
  1114  
  1115  		var n int
  1116  		if writer == io.Discard { // special-case default
  1117  			n = int(l)
  1118  		} else {
  1119  			b, ok := mod.Memory().Read(ctx, offset, l)
  1120  			if !ok {
  1121  				return ErrnoFault
  1122  			}
  1123  			n, err = writer.Write(b)
  1124  			if err != nil {
  1125  				return ErrnoIo
  1126  			}
  1127  		}
  1128  		nwritten += uint32(n)
  1129  	}
  1130  	if !mod.Memory().WriteUint32Le(ctx, resultSize, nwritten) {
  1131  		return ErrnoFault
  1132  	}
  1133  	return ErrnoSuccess
  1134  }
  1135  
  1136  // pathCreateDirectory is the WASI function named functionPathCreateDirectory
  1137  // which creates a directory.
  1138  //
  1139  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_create_directoryfd-fd-path-string---errno
  1140  var pathCreateDirectory = stubFunction(
  1141  	functionPathCreateDirectory,
  1142  	[]wasm.ValueType{i32, i32, i32},
  1143  	[]string{"fd", "path", "path_len"},
  1144  )
  1145  
  1146  // pathFilestatGet is the WASI function named functionPathFilestatGet which
  1147  // returns the stat attributes of a file or directory.
  1148  //
  1149  // # Parameters
  1150  //
  1151  //   - fd: file descriptor of the folder to look in for the path
  1152  //   - flags: flags determining the method of how paths are resolved
  1153  //   - path: path under fd to get the filestat attributes data for
  1154  //   - path_len: length of the path that was given
  1155  //   - resultFilestat: offset to write the result filestat data
  1156  //
  1157  // Result (Errno)
  1158  //
  1159  // The return value is ErrnoSuccess except the following error conditions:
  1160  //   - ErrnoBadf: `fd` is invalid
  1161  //   - ErrnoNotdir: `fd` points to a file not a directory
  1162  //   - ErrnoIo: could not stat `fd` on filesystem
  1163  //   - ErrnoInval: the path contained "../"
  1164  //   - ErrnoNametoolong: `path` + `path_len` is out of memory
  1165  //   - ErrnoFault: `resultFilestat` points to an offset out of memory
  1166  //   - ErrnoNoent: could not find the path
  1167  //
  1168  // The rest of this implementation matches that of fdFilestatGet, so is not
  1169  // repeated here.
  1170  //
  1171  // Note: This is similar to `fstatat` in POSIX.
  1172  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_filestat_getfd-fd-flags-lookupflags-path-string---errno-filestat
  1173  // and https://linux.die.net/man/2/fstatat
  1174  var pathFilestatGet = &wasm.HostFunc{
  1175  	ExportNames: []string{functionPathFilestatGet},
  1176  	Name:        functionPathFilestatGet,
  1177  	ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32},
  1178  	ParamNames:  []string{"fd", "flags", "path", "path_len", "result.buf"},
  1179  	ResultTypes: []api.ValueType{i32},
  1180  	Code: &wasm.Code{
  1181  		IsHostFunction: true,
  1182  		GoFunc:         wasiFunc(pathFilestatGetFn),
  1183  	},
  1184  }
  1185  
  1186  func pathFilestatGetFn(ctx context.Context, mod api.Module, params []uint64) Errno {
  1187  	sysCtx := mod.(*wasm.CallContext).Sys
  1188  	fsc := sysCtx.FS(ctx)
  1189  
  1190  	fd := uint32(params[0])
  1191  
  1192  	// TODO: implement flags?
  1193  	// flags := uint32(params[1])
  1194  
  1195  	pathOffset := uint32(params[2])
  1196  	pathLen := uint32(params[3])
  1197  	resultBuf := uint32(params[4])
  1198  
  1199  	// open_at isn't supported in fs.FS, so we check the path can't escape,
  1200  	// then join it with its parent
  1201  	b, ok := mod.Memory().Read(ctx, pathOffset, pathLen)
  1202  	if !ok {
  1203  		return ErrnoNametoolong
  1204  	}
  1205  	pathName := string(b)
  1206  
  1207  	if dir, ok := fsc.OpenedFile(ctx, fd); !ok {
  1208  		return ErrnoBadf
  1209  	} else if dir.File == nil { // root
  1210  	} else if _, ok := dir.File.(fs.ReadDirFile); !ok {
  1211  		return ErrnoNotdir
  1212  	} else {
  1213  		pathName = path.Join(dir.Path, pathName)
  1214  	}
  1215  
  1216  	// Sadly, we need to open the file to stat it.
  1217  	pathFd, errnoResult := openFile(ctx, fsc, pathName)
  1218  	if errnoResult != ErrnoSuccess {
  1219  		return errnoResult
  1220  	}
  1221  
  1222  	// Close it when the function returns.
  1223  	defer fsc.CloseFile(ctx, pathFd)
  1224  	return fdFilestatGetFunc(ctx, mod, pathFd, resultBuf)
  1225  }
  1226  
  1227  // pathFilestatSetTimes is the WASI function named functionPathFilestatSetTimes
  1228  // which adjusts the timestamps of a file or directory.
  1229  //
  1230  // 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
  1231  var pathFilestatSetTimes = stubFunction(
  1232  	functionPathFilestatSetTimes,
  1233  	[]wasm.ValueType{i32, i32, i32, i32, i64, i64, i32},
  1234  	[]string{"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags"},
  1235  )
  1236  
  1237  // pathLink is the WASI function named functionPathLink which adjusts the
  1238  // timestamps of a file or directory.
  1239  //
  1240  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_link
  1241  var pathLink = stubFunction(
  1242  	functionPathLink,
  1243  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32, i32},
  1244  	[]string{"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"},
  1245  )
  1246  
  1247  // pathOpen is the WASI function named functionPathOpen which opens a file or
  1248  // directory. This returns ErrnoBadf if the fd is invalid.
  1249  //
  1250  // # Parameters
  1251  //
  1252  //   - fd: file descriptor of a directory that `path` is relative to
  1253  //   - dirflags: flags to indicate how to resolve `path`
  1254  //   - path: offset in api.Memory to read the path string from
  1255  //   - pathLen: length of `path`
  1256  //   - oFlags: open flags to indicate the method by which to open the file
  1257  //   - fsRightsBase: ignored as rights were removed from WASI.
  1258  //   - fsRightsInheriting: ignored as rights were removed from WASI.
  1259  //     created file descriptor for `path`
  1260  //   - fdFlags: file descriptor flags
  1261  //   - resultOpenedFd: offset in api.Memory to write the newly created file
  1262  //     descriptor to.
  1263  //   - The result FD value is guaranteed to be less than 2**31
  1264  //
  1265  // Result (Errno)
  1266  //
  1267  // The return value is ErrnoSuccess except the following error conditions:
  1268  //   - ErrnoBadf: `fd` is invalid
  1269  //   - ErrnoFault: `resultOpenedFd` points to an offset out of memory
  1270  //   - ErrnoNoent: `path` does not exist.
  1271  //   - ErrnoExist: `path` exists, while `oFlags` requires that it must not.
  1272  //   - ErrnoNotdir: `path` is not a directory, while `oFlags` requires it.
  1273  //   - ErrnoIo: a file system error
  1274  //
  1275  // For example, this function needs to first read `path` to determine the file
  1276  // to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero",
  1277  // pathOpen reads the path from api.Memory:
  1278  //
  1279  //	                pathLen
  1280  //	            +------------------------+
  1281  //	            |                        |
  1282  //	[]byte{ ?, 'w', 'a', 'z', 'e', 'r', 'o', ?... }
  1283  //	     path --^
  1284  //
  1285  // Then, if parameters resultOpenedFd = 8, and this function opened a new file
  1286  // descriptor 5 with the given flags, this function writes the below to
  1287  // api.Memory:
  1288  //
  1289  //	                  uint32le
  1290  //	                 +--------+
  1291  //	                 |        |
  1292  //	[]byte{ 0..6, ?, 5, 0, 0, 0, ?}
  1293  //	resultOpenedFd --^
  1294  //
  1295  // # Notes
  1296  //   - This is similar to `openat` in POSIX. https://linux.die.net/man/3/openat
  1297  //   - The returned file descriptor is not guaranteed to be the lowest-number
  1298  //
  1299  // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#path_open
  1300  var pathOpen = &wasm.HostFunc{
  1301  	ExportNames: []string{functionPathOpen},
  1302  	Name:        functionPathOpen,
  1303  	ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32, i64, i64, i32, i32},
  1304  	ParamNames:  []string{"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd"},
  1305  	ResultTypes: []api.ValueType{i32},
  1306  	Code: &wasm.Code{
  1307  		IsHostFunction: true,
  1308  		GoFunc:         wasiFunc(pathOpenFn),
  1309  	},
  1310  }
  1311  
  1312  func pathOpenFn(ctx context.Context, mod api.Module, params []uint64) Errno {
  1313  	sysCtx := mod.(*wasm.CallContext).Sys
  1314  	fsc := sysCtx.FS(ctx)
  1315  
  1316  	fd := uint32(params[0])
  1317  	_ /* dirflags */ = uint32(params[1])
  1318  	path := uint32(params[2])
  1319  	pathLen := uint32(params[3])
  1320  	_ /* oflags */ = uint32(params[4])
  1321  	// rights aren't used
  1322  	_, _ = params[5], params[6]
  1323  	_ /* fdflags */ = uint32(params[7])
  1324  	resultOpenedFd := uint32(params[8])
  1325  
  1326  	if _, ok := fsc.OpenedFile(ctx, fd); !ok {
  1327  		return ErrnoBadf
  1328  	}
  1329  
  1330  	b, ok := mod.Memory().Read(ctx, path, pathLen)
  1331  	if !ok {
  1332  		return ErrnoFault
  1333  	}
  1334  
  1335  	newFD, errnoResult := openFile(ctx, fsc, string(b))
  1336  	if errnoResult != ErrnoSuccess {
  1337  		return errnoResult
  1338  	}
  1339  
  1340  	if !mod.Memory().WriteUint32Le(ctx, resultOpenedFd, newFD) {
  1341  		_ = fsc.CloseFile(ctx, newFD)
  1342  		return ErrnoFault
  1343  	}
  1344  	return ErrnoSuccess
  1345  }
  1346  
  1347  // pathReadlink is the WASI function named functionPathReadlink that reads the
  1348  // contents of a symbolic link.
  1349  //
  1350  // 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
  1351  var pathReadlink = stubFunction(
  1352  	functionPathReadlink,
  1353  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
  1354  	[]string{"fd", "path", "path_len", "buf", "buf_len", "result.bufused"},
  1355  )
  1356  
  1357  // pathRemoveDirectory is the WASI function named functionPathRemoveDirectory
  1358  // which removes a directory.
  1359  //
  1360  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_remove_directoryfd-fd-path-string---errno
  1361  var pathRemoveDirectory = stubFunction(
  1362  	functionPathRemoveDirectory,
  1363  	[]wasm.ValueType{i32, i32, i32},
  1364  	[]string{"fd", "path", "path_len"},
  1365  )
  1366  
  1367  // pathRename is the WASI function named functionPathRename which renames a
  1368  // file or directory.
  1369  var pathRename = stubFunction(
  1370  	functionPathRename,
  1371  	[]wasm.ValueType{i32, i32, i32, i32, i32, i32},
  1372  	[]string{"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len"},
  1373  )
  1374  
  1375  // pathSymlink is the WASI function named functionPathSymlink which creates a
  1376  // symbolic link.
  1377  //
  1378  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#path_symlink
  1379  var pathSymlink = stubFunction(
  1380  	functionPathSymlink,
  1381  	[]wasm.ValueType{i32, i32, i32, i32, i32},
  1382  	[]string{"old_path", "old_path_len", "fd", "new_path", "new_path_len"},
  1383  )
  1384  
  1385  // pathUnlinkFile is the WASI function named functionPathUnlinkFile which
  1386  // unlinks a file.
  1387  //
  1388  // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-path_unlink_filefd-fd-path-string---errno
  1389  var pathUnlinkFile = stubFunction(
  1390  	functionPathUnlinkFile,
  1391  	[]wasm.ValueType{i32, i32, i32},
  1392  	[]string{"fd", "path", "path_len"},
  1393  )
  1394  
  1395  // openFile attempts to open the file at the given path. Errors coerce to WASI
  1396  // Errno, returned as a slice to avoid allocation per-error.
  1397  //
  1398  // Note: Coercion isn't centralized in internalsys.FSContext because ABI use
  1399  // different error codes. For example, wasi-filesystem and GOOS=js don't map to
  1400  // these Errno.
  1401  func openFile(ctx context.Context, fsc *internalsys.FSContext, name string) (fd uint32, errno Errno) {
  1402  	newFD, err := fsc.OpenFile(ctx, name)
  1403  	if err == nil {
  1404  		fd = newFD
  1405  		errno = ErrnoSuccess
  1406  		return
  1407  	}
  1408  	// handle all the cases of FS.Open or internal to FSContext.OpenFile
  1409  	switch {
  1410  	case errors.Is(err, fs.ErrInvalid):
  1411  		errno = ErrnoInval
  1412  	case errors.Is(err, fs.ErrNotExist):
  1413  		// fs.FS is allowed to return this instead of ErrInvalid on an invalid path
  1414  		errno = ErrnoNoent
  1415  	case errors.Is(err, fs.ErrExist):
  1416  		errno = ErrnoExist
  1417  	case errors.Is(err, syscall.EBADF):
  1418  		// fsc.OpenFile currently returns this on out of file descriptors
  1419  		errno = ErrnoBadf
  1420  	default:
  1421  		errno = ErrnoIo
  1422  	}
  1423  	return
  1424  }