// 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 ( "internal/stringslite" "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 // fd_fdstat_get_flags is accessed from internal/syscall/unix //go:linkname fd_fdstat_get_flags 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) } // fd_fdstat_get_type is accessed from net //go:linkname fd_fdstat_get_type 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 stringslite.HasPrefix(path, "/") } func isDir(path string) bool { return stringslite.HasSuffix(path, "/") } // 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) && stringslite.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 ahead 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) }