summaryrefslogtreecommitdiffstats
path: root/src/syscall/fs_wasip1.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/syscall/fs_wasip1.go')
-rw-r--r--src/syscall/fs_wasip1.go933
1 files changed, 933 insertions, 0 deletions
diff --git a/src/syscall/fs_wasip1.go b/src/syscall/fs_wasip1.go
new file mode 100644
index 0000000..4ad3f96
--- /dev/null
+++ b/src/syscall/fs_wasip1.go
@@ -0,0 +1,933 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package syscall
+
+import (
+ "runtime"
+ "unsafe"
+)
+
+func init() {
+ // Try to set stdio to non-blocking mode before the os package
+ // calls NewFile for each fd. NewFile queries the non-blocking flag
+ // but doesn't change it, even if the runtime supports non-blocking
+ // stdio. Since WebAssembly modules are single-threaded, blocking
+ // system calls temporarily halt execution of the module. If the
+ // runtime supports non-blocking stdio, the Go runtime is able to
+ // use the WASI net poller to poll for read/write readiness and is
+ // able to schedule goroutines while waiting.
+ SetNonblock(0, true)
+ SetNonblock(1, true)
+ SetNonblock(2, true)
+}
+
+type uintptr32 = uint32
+type size = uint32
+type fdflags = uint32
+type filesize = uint64
+type filetype = uint8
+type lookupflags = uint32
+type oflags = uint32
+type rights = uint64
+type timestamp = uint64
+type dircookie = uint64
+type filedelta = int64
+type fstflags = uint32
+
+type iovec struct {
+ buf uintptr32
+ bufLen size
+}
+
+const (
+ LOOKUP_SYMLINK_FOLLOW = 0x00000001
+)
+
+const (
+ OFLAG_CREATE = 0x0001
+ OFLAG_DIRECTORY = 0x0002
+ OFLAG_EXCL = 0x0004
+ OFLAG_TRUNC = 0x0008
+)
+
+const (
+ FDFLAG_APPEND = 0x0001
+ FDFLAG_DSYNC = 0x0002
+ FDFLAG_NONBLOCK = 0x0004
+ FDFLAG_RSYNC = 0x0008
+ FDFLAG_SYNC = 0x0010
+)
+
+const (
+ RIGHT_FD_DATASYNC = 1 << iota
+ RIGHT_FD_READ
+ RIGHT_FD_SEEK
+ RIGHT_FDSTAT_SET_FLAGS
+ RIGHT_FD_SYNC
+ RIGHT_FD_TELL
+ RIGHT_FD_WRITE
+ RIGHT_FD_ADVISE
+ RIGHT_FD_ALLOCATE
+ RIGHT_PATH_CREATE_DIRECTORY
+ RIGHT_PATH_CREATE_FILE
+ RIGHT_PATH_LINK_SOURCE
+ RIGHT_PATH_LINK_TARGET
+ RIGHT_PATH_OPEN
+ RIGHT_FD_READDIR
+ RIGHT_PATH_READLINK
+ RIGHT_PATH_RENAME_SOURCE
+ RIGHT_PATH_RENAME_TARGET
+ RIGHT_PATH_FILESTAT_GET
+ RIGHT_PATH_FILESTAT_SET_SIZE
+ RIGHT_PATH_FILESTAT_SET_TIMES
+ RIGHT_FD_FILESTAT_GET
+ RIGHT_FD_FILESTAT_SET_SIZE
+ RIGHT_FD_FILESTAT_SET_TIMES
+ RIGHT_PATH_SYMLINK
+ RIGHT_PATH_REMOVE_DIRECTORY
+ RIGHT_PATH_UNLINK_FILE
+ RIGHT_POLL_FD_READWRITE
+ RIGHT_SOCK_SHUTDOWN
+ RIGHT_SOCK_ACCEPT
+)
+
+const (
+ WHENCE_SET = 0
+ WHENCE_CUR = 1
+ WHENCE_END = 2
+)
+
+const (
+ FILESTAT_SET_ATIM = 0x0001
+ FILESTAT_SET_ATIM_NOW = 0x0002
+ FILESTAT_SET_MTIM = 0x0004
+ FILESTAT_SET_MTIM_NOW = 0x0008
+)
+
+const (
+ // Despite the rights being defined as a 64 bits integer in the spec,
+ // wasmtime crashes the program if we set any of the upper 32 bits.
+ fullRights = rights(^uint32(0))
+ readRights = rights(RIGHT_FD_READ | RIGHT_FD_READDIR)
+ writeRights = rights(RIGHT_FD_DATASYNC | RIGHT_FD_WRITE | RIGHT_FD_ALLOCATE | RIGHT_PATH_FILESTAT_SET_SIZE)
+
+ // Some runtimes have very strict expectations when it comes to which
+ // rights can be enabled on files opened by path_open. The fileRights
+ // constant is used as a mask to retain only bits for operations that
+ // are supported on files.
+ fileRights rights = RIGHT_FD_DATASYNC |
+ RIGHT_FD_READ |
+ RIGHT_FD_SEEK |
+ RIGHT_FDSTAT_SET_FLAGS |
+ RIGHT_FD_SYNC |
+ RIGHT_FD_TELL |
+ RIGHT_FD_WRITE |
+ RIGHT_FD_ADVISE |
+ RIGHT_FD_ALLOCATE |
+ RIGHT_PATH_CREATE_DIRECTORY |
+ RIGHT_PATH_CREATE_FILE |
+ RIGHT_PATH_LINK_SOURCE |
+ RIGHT_PATH_LINK_TARGET |
+ RIGHT_PATH_OPEN |
+ RIGHT_FD_READDIR |
+ RIGHT_PATH_READLINK |
+ RIGHT_PATH_RENAME_SOURCE |
+ RIGHT_PATH_RENAME_TARGET |
+ RIGHT_PATH_FILESTAT_GET |
+ RIGHT_PATH_FILESTAT_SET_SIZE |
+ RIGHT_PATH_FILESTAT_SET_TIMES |
+ RIGHT_FD_FILESTAT_GET |
+ RIGHT_FD_FILESTAT_SET_SIZE |
+ RIGHT_FD_FILESTAT_SET_TIMES |
+ RIGHT_PATH_SYMLINK |
+ RIGHT_PATH_REMOVE_DIRECTORY |
+ RIGHT_PATH_UNLINK_FILE |
+ RIGHT_POLL_FD_READWRITE
+
+ // Runtimes like wasmtime and wasmedge will refuse to open directories
+ // if the rights requested by the application exceed the operations that
+ // can be performed on a directory.
+ dirRights rights = RIGHT_FD_SEEK |
+ RIGHT_FDSTAT_SET_FLAGS |
+ RIGHT_FD_SYNC |
+ RIGHT_PATH_CREATE_DIRECTORY |
+ RIGHT_PATH_CREATE_FILE |
+ RIGHT_PATH_LINK_SOURCE |
+ RIGHT_PATH_LINK_TARGET |
+ RIGHT_PATH_OPEN |
+ RIGHT_FD_READDIR |
+ RIGHT_PATH_READLINK |
+ RIGHT_PATH_RENAME_SOURCE |
+ RIGHT_PATH_RENAME_TARGET |
+ RIGHT_PATH_FILESTAT_GET |
+ RIGHT_PATH_FILESTAT_SET_SIZE |
+ RIGHT_PATH_FILESTAT_SET_TIMES |
+ RIGHT_FD_FILESTAT_GET |
+ RIGHT_FD_FILESTAT_SET_TIMES |
+ RIGHT_PATH_SYMLINK |
+ RIGHT_PATH_REMOVE_DIRECTORY |
+ RIGHT_PATH_UNLINK_FILE
+)
+
+// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_closefd-fd---result-errno
+//
+//go:wasmimport wasi_snapshot_preview1 fd_close
+//go:noescape
+func fd_close(fd int32) Errno
+
+// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---result-errno
+//
+//go:wasmimport wasi_snapshot_preview1 fd_filestat_set_size
+//go:noescape
+func fd_filestat_set_size(fd int32, set_size filesize) Errno
+
+// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_preadfd-fd-iovs-iovec_array-offset-filesize---resultsize-errno
+//
+//go:wasmimport wasi_snapshot_preview1 fd_pread
+//go:noescape
+func fd_pread(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nread unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_pwrite
+//go:noescape
+func fd_pwrite(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nwritten unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_read
+//go:noescape
+func fd_read(fd int32, iovs unsafe.Pointer, iovsLen size, nread unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_readdir
+//go:noescape
+func fd_readdir(fd int32, buf unsafe.Pointer, bufLen size, cookie dircookie, nwritten unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_seek
+//go:noescape
+func fd_seek(fd int32, offset filedelta, whence uint32, newoffset unsafe.Pointer) Errno
+
+// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fd_fdstat_set_rightsfd-fd-fs_rights_base-rights-fs_rights_inheriting-rights---result-errno
+//
+//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_rights
+//go:noescape
+func fd_fdstat_set_rights(fd int32, rightsBase rights, rightsInheriting rights) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_filestat_get
+//go:noescape
+func fd_filestat_get(fd int32, buf unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_write
+//go:noescape
+func fd_write(fd int32, iovs unsafe.Pointer, iovsLen size, nwritten unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_sync
+//go:noescape
+func fd_sync(fd int32) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_create_directory
+//go:noescape
+func path_create_directory(fd int32, path unsafe.Pointer, pathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_filestat_get
+//go:noescape
+func path_filestat_get(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, buf unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_filestat_set_times
+//go:noescape
+func path_filestat_set_times(fd int32, flags lookupflags, path unsafe.Pointer, pathLen size, atim timestamp, mtim timestamp, fstflags fstflags) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_link
+//go:noescape
+func path_link(oldFd int32, oldFlags lookupflags, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_readlink
+//go:noescape
+func path_readlink(fd int32, path unsafe.Pointer, pathLen size, buf unsafe.Pointer, bufLen size, nwritten unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_remove_directory
+//go:noescape
+func path_remove_directory(fd int32, path unsafe.Pointer, pathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_rename
+//go:noescape
+func path_rename(oldFd int32, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_symlink
+//go:noescape
+func path_symlink(oldPath unsafe.Pointer, oldPathLen size, fd int32, newPath unsafe.Pointer, newPathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_unlink_file
+//go:noescape
+func path_unlink_file(fd int32, path unsafe.Pointer, pathLen size) Errno
+
+//go:wasmimport wasi_snapshot_preview1 path_open
+//go:noescape
+func path_open(rootFD int32, dirflags lookupflags, path unsafe.Pointer, pathLen size, oflags oflags, fsRightsBase rights, fsRightsInheriting rights, fsFlags fdflags, fd unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 random_get
+//go:noescape
+func random_get(buf unsafe.Pointer, bufLen size) Errno
+
+// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fdstat-record
+// fdflags must be at offset 2, hence the uint16 type rather than the
+// fdflags (uint32) type.
+type fdstat struct {
+ filetype filetype
+ fdflags uint16
+ rightsBase rights
+ rightsInheriting rights
+}
+
+//go:wasmimport wasi_snapshot_preview1 fd_fdstat_get
+//go:noescape
+func fd_fdstat_get(fd int32, buf unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags
+//go:noescape
+func fd_fdstat_set_flags(fd int32, flags fdflags) Errno
+
+func fd_fdstat_get_flags(fd int) (uint32, error) {
+ var stat fdstat
+ errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
+ return uint32(stat.fdflags), errnoErr(errno)
+}
+
+func fd_fdstat_get_type(fd int) (uint8, error) {
+ var stat fdstat
+ errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
+ return stat.filetype, errnoErr(errno)
+}
+
+type preopentype = uint8
+
+const (
+ preopentypeDir preopentype = iota
+)
+
+type prestatDir struct {
+ prNameLen size
+}
+
+type prestat struct {
+ typ preopentype
+ dir prestatDir
+}
+
+//go:wasmimport wasi_snapshot_preview1 fd_prestat_get
+//go:noescape
+func fd_prestat_get(fd int32, prestat unsafe.Pointer) Errno
+
+//go:wasmimport wasi_snapshot_preview1 fd_prestat_dir_name
+//go:noescape
+func fd_prestat_dir_name(fd int32, path unsafe.Pointer, pathLen size) Errno
+
+type opendir struct {
+ fd int32
+ name string
+}
+
+// List of preopen directories that were exposed by the runtime. The first one
+// is assumed to the be root directory of the file system, and others are seen
+// as mount points at sub paths of the root.
+var preopens []opendir
+
+// Current working directory. We maintain this as a string and resolve paths in
+// the code because wasmtime does not allow relative path lookups outside of the
+// scope of a directory; a previous approach we tried consisted in maintaining
+// open a file descriptor to the current directory so we could perform relative
+// path lookups from that location, but it resulted in breaking path resolution
+// from the current directory to its parent.
+var cwd string
+
+func init() {
+ dirNameBuf := make([]byte, 256)
+ // We start looking for preopens at fd=3 because 0, 1, and 2 are reserved
+ // for standard input and outputs.
+ for preopenFd := int32(3); ; preopenFd++ {
+ var prestat prestat
+
+ errno := fd_prestat_get(preopenFd, unsafe.Pointer(&prestat))
+ if errno == EBADF {
+ break
+ }
+ if errno == ENOTDIR || prestat.typ != preopentypeDir {
+ continue
+ }
+ if errno != 0 {
+ panic("fd_prestat: " + errno.Error())
+ }
+ if int(prestat.dir.prNameLen) > len(dirNameBuf) {
+ dirNameBuf = make([]byte, prestat.dir.prNameLen)
+ }
+
+ errno = fd_prestat_dir_name(preopenFd, unsafe.Pointer(&dirNameBuf[0]), prestat.dir.prNameLen)
+ if errno != 0 {
+ panic("fd_prestat_dir_name: " + errno.Error())
+ }
+
+ preopens = append(preopens, opendir{
+ fd: preopenFd,
+ name: string(dirNameBuf[:prestat.dir.prNameLen]),
+ })
+ }
+
+ if cwd, _ = Getenv("PWD"); cwd != "" {
+ cwd = joinPath("/", cwd)
+ } else if len(preopens) > 0 {
+ cwd = preopens[0].name
+ }
+}
+
+// Provided by package runtime.
+func now() (sec int64, nsec int32)
+
+//go:nosplit
+func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) {
+ i := 0
+ for i < len(path) {
+ for i < len(path) && path[i] == '/' {
+ i++
+ }
+
+ j := i
+ for j < len(path) && path[j] != '/' {
+ j++
+ }
+
+ s := path[i:j]
+ i = j
+
+ switch s {
+ case "":
+ continue
+ case ".":
+ continue
+ case "..":
+ if !lookupParent {
+ k := len(buf)
+ for k > 0 && buf[k-1] != '/' {
+ k--
+ }
+ for k > 1 && buf[k-1] == '/' {
+ k--
+ }
+ buf = buf[:k]
+ if k == 0 {
+ lookupParent = true
+ } else {
+ s = ""
+ continue
+ }
+ }
+ default:
+ lookupParent = false
+ }
+
+ if len(buf) > 0 && buf[len(buf)-1] != '/' {
+ buf = append(buf, '/')
+ }
+ buf = append(buf, s...)
+ }
+ return buf, lookupParent
+}
+
+// joinPath concatenates dir and file paths, producing a cleaned path where
+// "." and ".." have been removed, unless dir is relative and the references
+// to parent directories in file represented a location relative to a parent
+// of dir.
+//
+// This function is used for path resolution of all wasi functions expecting
+// a path argument; the returned string is heap allocated, which we may want
+// to optimize in the future. Instead of returning a string, the function
+// could append the result to an output buffer that the functions in this
+// file can manage to have allocated on the stack (e.g. initializing to a
+// fixed capacity). Since it will significantly increase code complexity,
+// we prefer to optimize for readability and maintainability at this time.
+func joinPath(dir, file string) string {
+ buf := make([]byte, 0, len(dir)+len(file)+1)
+ if isAbs(dir) {
+ buf = append(buf, '/')
+ }
+ buf, lookupParent := appendCleanPath(buf, dir, false)
+ buf, _ = appendCleanPath(buf, file, lookupParent)
+ // The appendCleanPath function cleans the path so it does not inject
+ // references to the current directory. If both the dir and file args
+ // were ".", this results in the output buffer being empty so we handle
+ // this condition here.
+ if len(buf) == 0 {
+ buf = append(buf, '.')
+ }
+ // If the file ended with a '/' we make sure that the output also ends
+ // with a '/'. This is needed to ensure that programs have a mechanism
+ // to represent dereferencing symbolic links pointing to directories.
+ if buf[len(buf)-1] != '/' && isDir(file) {
+ buf = append(buf, '/')
+ }
+ return unsafe.String(&buf[0], len(buf))
+}
+
+func isAbs(path string) bool {
+ return hasPrefix(path, "/")
+}
+
+func isDir(path string) bool {
+ return hasSuffix(path, "/")
+}
+
+func hasPrefix(s, p string) bool {
+ return len(s) >= len(p) && s[:len(p)] == p
+}
+
+func hasSuffix(s, x string) bool {
+ return len(s) >= len(x) && s[len(s)-len(x):] == x
+}
+
+// preparePath returns the preopen file descriptor of the directory to perform
+// path resolution from, along with the pair of pointer and length for the
+// relative expression of path from the directory.
+//
+// If the path argument is not absolute, it is first appended to the current
+// working directory before resolution.
+func preparePath(path string) (int32, unsafe.Pointer, size) {
+ var dirFd = int32(-1)
+ var dirName string
+
+ dir := "/"
+ if !isAbs(path) {
+ dir = cwd
+ }
+ path = joinPath(dir, path)
+
+ for _, p := range preopens {
+ if len(p.name) > len(dirName) && hasPrefix(path, p.name) {
+ dirFd, dirName = p.fd, p.name
+ }
+ }
+
+ path = path[len(dirName):]
+ for isAbs(path) {
+ path = path[1:]
+ }
+ if len(path) == 0 {
+ path = "."
+ }
+
+ return dirFd, stringPointer(path), size(len(path))
+}
+
+func Open(path string, openmode int, perm uint32) (int, error) {
+ if path == "" {
+ return -1, EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+
+ var oflags oflags
+ if (openmode & O_CREATE) != 0 {
+ oflags |= OFLAG_CREATE
+ }
+ if (openmode & O_TRUNC) != 0 {
+ oflags |= OFLAG_TRUNC
+ }
+ if (openmode & O_EXCL) != 0 {
+ oflags |= OFLAG_EXCL
+ }
+
+ var rights rights
+ switch openmode & (O_RDONLY | O_WRONLY | O_RDWR) {
+ case O_RDONLY:
+ rights = fileRights & ^writeRights
+ case O_WRONLY:
+ rights = fileRights & ^readRights
+ case O_RDWR:
+ rights = fileRights
+ }
+
+ var fdflags fdflags
+ if (openmode & O_APPEND) != 0 {
+ fdflags |= FDFLAG_APPEND
+ }
+ if (openmode & O_SYNC) != 0 {
+ fdflags |= FDFLAG_SYNC
+ }
+
+ var fd int32
+ errno := path_open(
+ dirFd,
+ LOOKUP_SYMLINK_FOLLOW,
+ pathPtr,
+ pathLen,
+ oflags,
+ rights,
+ fileRights,
+ fdflags,
+ unsafe.Pointer(&fd),
+ )
+ if errno == EISDIR && oflags == 0 && fdflags == 0 && ((rights & writeRights) == 0) {
+ // wasmtime and wasmedge will error if attempting to open a directory
+ // because we are asking for too many rights. However, we cannot
+ // determine ahread of time if the path we are about to open is a
+ // directory, so instead we fallback to a second call to path_open with
+ // a more limited set of rights.
+ //
+ // This approach is subject to a race if the file system is modified
+ // concurrently, so we also inject OFLAG_DIRECTORY to ensure that we do
+ // not accidentally open a file which is not a directory.
+ errno = path_open(
+ dirFd,
+ LOOKUP_SYMLINK_FOLLOW,
+ pathPtr,
+ pathLen,
+ oflags|OFLAG_DIRECTORY,
+ rights&dirRights,
+ fileRights,
+ fdflags,
+ unsafe.Pointer(&fd),
+ )
+ }
+ return int(fd), errnoErr(errno)
+}
+
+func Close(fd int) error {
+ errno := fd_close(int32(fd))
+ return errnoErr(errno)
+}
+
+func CloseOnExec(fd int) {
+ // nothing to do - no exec
+}
+
+func Mkdir(path string, perm uint32) error {
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_create_directory(dirFd, pathPtr, pathLen)
+ return errnoErr(errno)
+}
+
+func ReadDir(fd int, buf []byte, cookie dircookie) (int, error) {
+ var nwritten size
+ errno := fd_readdir(int32(fd), unsafe.Pointer(&buf[0]), size(len(buf)), cookie, unsafe.Pointer(&nwritten))
+ return int(nwritten), errnoErr(errno)
+}
+
+type Stat_t struct {
+ Dev uint64
+ Ino uint64
+ Filetype uint8
+ Nlink uint64
+ Size uint64
+ Atime uint64
+ Mtime uint64
+ Ctime uint64
+
+ Mode int
+
+ // Uid and Gid are always zero on wasip1 platforms
+ Uid uint32
+ Gid uint32
+}
+
+func Stat(path string, st *Stat_t) error {
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(st))
+ setDefaultMode(st)
+ return errnoErr(errno)
+}
+
+func Lstat(path string, st *Stat_t) error {
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_filestat_get(dirFd, 0, pathPtr, pathLen, unsafe.Pointer(st))
+ setDefaultMode(st)
+ return errnoErr(errno)
+}
+
+func Fstat(fd int, st *Stat_t) error {
+ errno := fd_filestat_get(int32(fd), unsafe.Pointer(st))
+ setDefaultMode(st)
+ return errnoErr(errno)
+}
+
+func setDefaultMode(st *Stat_t) {
+ // WASI does not support unix-like permissions, but Go programs are likely
+ // to expect the permission bits to not be zero so we set defaults to help
+ // avoid breaking applications that are migrating to WASM.
+ if st.Filetype == FILETYPE_DIRECTORY {
+ st.Mode = 0700
+ } else {
+ st.Mode = 0600
+ }
+}
+
+func Unlink(path string) error {
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_unlink_file(dirFd, pathPtr, pathLen)
+ return errnoErr(errno)
+}
+
+func Rmdir(path string) error {
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_remove_directory(dirFd, pathPtr, pathLen)
+ return errnoErr(errno)
+}
+
+func Chmod(path string, mode uint32) error {
+ var stat Stat_t
+ return Stat(path, &stat)
+}
+
+func Fchmod(fd int, mode uint32) error {
+ var stat Stat_t
+ return Fstat(fd, &stat)
+}
+
+func Chown(path string, uid, gid int) error {
+ return ENOSYS
+}
+
+func Fchown(fd int, uid, gid int) error {
+ return ENOSYS
+}
+
+func Lchown(path string, uid, gid int) error {
+ return ENOSYS
+}
+
+func UtimesNano(path string, ts []Timespec) error {
+ // UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go
+ const UTIME_OMIT = -0x2
+ if path == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ atime := TimespecToNsec(ts[0])
+ mtime := TimespecToNsec(ts[1])
+ if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT {
+ var st Stat_t
+ if err := Stat(path, &st); err != nil {
+ return err
+ }
+ if ts[0].Nsec == UTIME_OMIT {
+ atime = int64(st.Atime)
+ }
+ if ts[1].Nsec == UTIME_OMIT {
+ mtime = int64(st.Mtime)
+ }
+ }
+ errno := path_filestat_set_times(
+ dirFd,
+ LOOKUP_SYMLINK_FOLLOW,
+ pathPtr,
+ pathLen,
+ timestamp(atime),
+ timestamp(mtime),
+ FILESTAT_SET_ATIM|FILESTAT_SET_MTIM,
+ )
+ return errnoErr(errno)
+}
+
+func Rename(from, to string) error {
+ if from == "" || to == "" {
+ return EINVAL
+ }
+ oldDirFd, oldPathPtr, oldPathLen := preparePath(from)
+ newDirFd, newPathPtr, newPathLen := preparePath(to)
+ errno := path_rename(
+ oldDirFd,
+ oldPathPtr,
+ oldPathLen,
+ newDirFd,
+ newPathPtr,
+ newPathLen,
+ )
+ return errnoErr(errno)
+}
+
+func Truncate(path string, length int64) error {
+ if path == "" {
+ return EINVAL
+ }
+ fd, err := Open(path, O_WRONLY, 0)
+ if err != nil {
+ return err
+ }
+ defer Close(fd)
+ return Ftruncate(fd, length)
+}
+
+func Ftruncate(fd int, length int64) error {
+ errno := fd_filestat_set_size(int32(fd), filesize(length))
+ return errnoErr(errno)
+}
+
+const ImplementsGetwd = true
+
+func Getwd() (string, error) {
+ return cwd, nil
+}
+
+func Chdir(path string) error {
+ if path == "" {
+ return EINVAL
+ }
+
+ dir := "/"
+ if !isAbs(path) {
+ dir = cwd
+ }
+ path = joinPath(dir, path)
+
+ var stat Stat_t
+ dirFd, pathPtr, pathLen := preparePath(path)
+ errno := path_filestat_get(dirFd, LOOKUP_SYMLINK_FOLLOW, pathPtr, pathLen, unsafe.Pointer(&stat))
+ if errno != 0 {
+ return errnoErr(errno)
+ }
+ if stat.Filetype != FILETYPE_DIRECTORY {
+ return ENOTDIR
+ }
+ cwd = path
+ return nil
+}
+
+func Readlink(path string, buf []byte) (n int, err error) {
+ if path == "" {
+ return 0, EINVAL
+ }
+ if len(buf) == 0 {
+ return 0, nil
+ }
+ dirFd, pathPtr, pathLen := preparePath(path)
+ var nwritten size
+ errno := path_readlink(
+ dirFd,
+ pathPtr,
+ pathLen,
+ unsafe.Pointer(&buf[0]),
+ size(len(buf)),
+ unsafe.Pointer(&nwritten),
+ )
+ // For some reason wasmtime returns ERANGE when the output buffer is
+ // shorter than the symbolic link value. os.Readlink expects a nil
+ // error and uses the fact that n is greater or equal to the buffer
+ // length to assume that it needs to try again with a larger size.
+ // This condition is handled in os.Readlink.
+ return int(nwritten), errnoErr(errno)
+}
+
+func Link(path, link string) error {
+ if path == "" || link == "" {
+ return EINVAL
+ }
+ oldDirFd, oldPathPtr, oldPathLen := preparePath(path)
+ newDirFd, newPathPtr, newPathLen := preparePath(link)
+ errno := path_link(
+ oldDirFd,
+ 0,
+ oldPathPtr,
+ oldPathLen,
+ newDirFd,
+ newPathPtr,
+ newPathLen,
+ )
+ return errnoErr(errno)
+}
+
+func Symlink(path, link string) error {
+ if path == "" || link == "" {
+ return EINVAL
+ }
+ dirFd, pathPtr, pathlen := preparePath(link)
+ errno := path_symlink(
+ stringPointer(path),
+ size(len(path)),
+ dirFd,
+ pathPtr,
+ pathlen,
+ )
+ return errnoErr(errno)
+}
+
+func Fsync(fd int) error {
+ errno := fd_sync(int32(fd))
+ return errnoErr(errno)
+}
+
+func bytesPointer(b []byte) unsafe.Pointer {
+ return unsafe.Pointer(unsafe.SliceData(b))
+}
+
+func stringPointer(s string) unsafe.Pointer {
+ return unsafe.Pointer(unsafe.StringData(s))
+}
+
+func makeIOVec(b []byte) unsafe.Pointer {
+ return unsafe.Pointer(&iovec{
+ buf: uintptr32(uintptr(bytesPointer(b))),
+ bufLen: size(len(b)),
+ })
+}
+
+func Read(fd int, b []byte) (int, error) {
+ var nread size
+ errno := fd_read(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nread))
+ runtime.KeepAlive(b)
+ return int(nread), errnoErr(errno)
+}
+
+func Write(fd int, b []byte) (int, error) {
+ var nwritten size
+ errno := fd_write(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&nwritten))
+ runtime.KeepAlive(b)
+ return int(nwritten), errnoErr(errno)
+}
+
+func Pread(fd int, b []byte, offset int64) (int, error) {
+ var nread size
+ errno := fd_pread(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nread))
+ runtime.KeepAlive(b)
+ return int(nread), errnoErr(errno)
+}
+
+func Pwrite(fd int, b []byte, offset int64) (int, error) {
+ var nwritten size
+ errno := fd_pwrite(int32(fd), makeIOVec(b), 1, filesize(offset), unsafe.Pointer(&nwritten))
+ runtime.KeepAlive(b)
+ return int(nwritten), errnoErr(errno)
+}
+
+func Seek(fd int, offset int64, whence int) (int64, error) {
+ var newoffset filesize
+ errno := fd_seek(int32(fd), filedelta(offset), uint32(whence), unsafe.Pointer(&newoffset))
+ return int64(newoffset), errnoErr(errno)
+}
+
+func Dup(fd int) (int, error) {
+ return 0, ENOSYS
+}
+
+func Dup2(fd, newfd int) error {
+ return ENOSYS
+}
+
+func Pipe(fd []int) error {
+ return ENOSYS
+}
+
+func RandomGet(b []byte) error {
+ errno := random_get(bytesPointer(b), size(len(b)))
+ return errnoErr(errno)
+}