From 57b4df2188388d190fa4f5385f8052c008f9c7a5 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Fri, 24 Mar 2023 23:16:43 -0700 Subject: [PATCH] syscall: add remaining wasip1 files Implements filesystem, networking and os interactions. For #58141 Co-authored-by: Richard Musiol Co-authored-by: Achille Roussel Co-authored-by: Julien Fabre Co-authored-by: Evan Phoenix Change-Id: If5c43ad5bd2955e6d2d4e2822fd68bce89ca786c Reviewed-on: https://go-review.googlesource.com/c/go/+/479619 Reviewed-by: Michael Knyszek TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Ian Lance Taylor Run-TryBot: Cherry Mui --- src/syscall/dirent.go | 2 +- src/syscall/env_unix.go | 2 +- src/syscall/export_wasip1_test.go | 11 + src/syscall/fs_wasip1.go | 809 ++++++++++++++++++++++++++++++ src/syscall/fs_wasip1_test.go | 76 +++ src/syscall/net_wasip1.go | 128 +++++ src/syscall/os_wasip1.go | 8 + src/syscall/syscall_wasip1.go | 463 +++++++++++++++++ src/syscall/tables_wasip1.go | 204 ++++++++ src/syscall/timestruct.go | 2 +- 10 files changed, 1702 insertions(+), 3 deletions(-) create mode 100644 src/syscall/export_wasip1_test.go create mode 100644 src/syscall/fs_wasip1.go create mode 100644 src/syscall/fs_wasip1_test.go create mode 100644 src/syscall/net_wasip1.go create mode 100644 src/syscall/os_wasip1.go create mode 100644 src/syscall/syscall_wasip1.go create mode 100644 src/syscall/tables_wasip1.go diff --git a/src/syscall/dirent.go b/src/syscall/dirent.go index b10608a662..eee94bf73c 100644 --- a/src/syscall/dirent.go +++ b/src/syscall/dirent.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unix || (js && wasm) +//go:build unix || (js && wasm) || wasip1 package syscall diff --git a/src/syscall/env_unix.go b/src/syscall/env_unix.go index 6d917da208..8e87e018e8 100644 --- a/src/syscall/env_unix.go +++ b/src/syscall/env_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unix || (js && wasm) || plan9 +//go:build unix || (js && wasm) || plan9 || wasip1 // Unix environment variables. diff --git a/src/syscall/export_wasip1_test.go b/src/syscall/export_wasip1_test.go new file mode 100644 index 0000000000..aa53d26ef1 --- /dev/null +++ b/src/syscall/export_wasip1_test.go @@ -0,0 +1,11 @@ +// 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 + +func JoinPath(dir, file string) string { + return joinPath(dir, file) +} diff --git a/src/syscall/fs_wasip1.go b/src/syscall/fs_wasip1.go new file mode 100644 index 0000000000..d2265eb3ee --- /dev/null +++ b/src/syscall/fs_wasip1.go @@ -0,0 +1,809 @@ +// 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" +) + +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) +) + +// 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 *iovec, iovsLen size, offset filesize, nread *size) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_pwrite +//go:noescape +func fd_pwrite(fd int32, iovs *iovec, iovsLen size, offset filesize, nwritten *size) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_read +//go:noescape +func fd_read(fd int32, iovs *iovec, iovsLen size, nread *size) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_readdir +//go:noescape +func fd_readdir(fd int32, buf *byte, bufLen size, cookie dircookie, nwritten *size) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_seek +//go:noescape +func fd_seek(fd int32, offset filedelta, whence uint32, newoffset *filesize) 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 *Stat_t) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_write +//go:noescape +func fd_write(fd int32, iovs *iovec, iovsLen size, nwritten *size) 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 *byte, pathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_filestat_get +//go:noescape +func path_filestat_get(fd int32, flags lookupflags, path *byte, pathLen size, buf *Stat_t) Errno + +//go:wasmimport wasi_snapshot_preview1 path_filestat_set_times +//go:noescape +func path_filestat_set_times(fd int32, flags lookupflags, path *byte, 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 *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_readlink +//go:noescape +func path_readlink(fd int32, path *byte, pathLen size, buf *byte, bufLen size, nwritten *size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_remove_directory +//go:noescape +func path_remove_directory(fd int32, path *byte, pathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_rename +//go:noescape +func path_rename(oldFd int32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_symlink +//go:noescape +func path_symlink(oldPath *byte, oldPathLen size, fd int32, newPath *byte, newPathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_unlink_file +//go:noescape +func path_unlink_file(fd int32, path *byte, pathLen size) Errno + +//go:wasmimport wasi_snapshot_preview1 path_open +//go:noescape +func path_open(rootFD int32, dirflags lookupflags, path *byte, pathLen size, oflags oflags, fsRightsBase rights, fsRightsInheriting rights, fsFlags fdflags, fd *int32) Errno + +//go:wasmimport wasi_snapshot_preview1 random_get +//go:noescape +func random_get(buf *byte, bufLen size) 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 *prestat) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_prestat_dir_name +//go:noescape +func fd_prestat_dir_name(fd int32, path *byte, 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, &prestat) + if errno == EBADF { + break + } + if errno != 0 { + panic("fd_prestat: " + errno.Error()) + } + if prestat.typ != preopentypeDir { + continue + } + if int(prestat.dir.prNameLen) > len(dirNameBuf) { + dirNameBuf = make([]byte, prestat.dir.prNameLen) + } + + errno = fd_prestat_dir_name(preopenFd, &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 relatie 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, *byte, 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, unsafe.StringData(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 + } + + // Remove when https://github.com/bytecodealliance/wasmtime/pull/4967 is merged. + var fi Stat_t + if errno := path_filestat_get( + dirFd, + LOOKUP_SYMLINK_FOLLOW, + pathPtr, + pathLen, + &fi, + ); errno != 0 && errno != ENOENT { + return -1, errnoErr(errno) + } + if fi.Filetype == FILETYPE_DIRECTORY { + oflags |= OFLAG_DIRECTORY + // WASM runtimes appear to return EINVAL when passing invalid + // combination of flags to open directories; however, TestOpenError + // in the os package expects EISDIR, so we precheck this condition + // here to emulate the expected behavior. + const invalidFlags = O_WRONLY | O_RDWR | O_CREATE | O_APPEND | O_TRUNC | O_EXCL + if (openmode & invalidFlags) != 0 { + return 0, EISDIR + } + } + + var rights rights + switch openmode & (O_RDONLY | O_WRONLY | O_RDWR) { + case O_RDONLY: + rights = fullRights & ^writeRights + case O_WRONLY: + rights = fullRights & ^readRights + case O_RDWR: + rights = fullRights + } + + 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, + fullRights, + fdflags, + &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), &buf[0], size(len(buf)), cookie, &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, 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, st) + setDefaultMode(st) + return errnoErr(errno) +} + +func Fstat(fd int, st *Stat_t) error { + errno := fd_filestat_get(int32(fd), 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 { + if path == "" { + return EINVAL + } + dirFd, pathPtr, pathLen := preparePath(path) + errno := path_filestat_set_times( + dirFd, + LOOKUP_SYMLINK_FOLLOW, + pathPtr, + pathLen, + timestamp(TimespecToNsec(ts[0])), + timestamp(TimespecToNsec(ts[1])), + 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, &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, + &buf[0], + size(len(buf)), + &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( + unsafe.StringData(path), + size(len(path)), + dirFd, + pathPtr, + pathlen, + ) + return errnoErr(errno) +} + +func Fsync(fd int) error { + errno := fd_sync(int32(fd)) + return errnoErr(errno) +} + +func makeIOVec(b []byte) *iovec { + return &iovec{ + buf: uintptr32(uintptr(unsafe.Pointer(unsafe.SliceData(b)))), + bufLen: size(len(b)), + } +} + +func Read(fd int, b []byte) (int, error) { + var nread size + errno := fd_read(int32(fd), makeIOVec(b), 1, &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, &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), &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), &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), &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(unsafe.SliceData(b), size(len(b))) + return errnoErr(errno) +} diff --git a/src/syscall/fs_wasip1_test.go b/src/syscall/fs_wasip1_test.go new file mode 100644 index 0000000000..f27e6a8cd7 --- /dev/null +++ b/src/syscall/fs_wasip1_test.go @@ -0,0 +1,76 @@ +// 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_test + +import ( + "syscall" + "testing" +) + +var joinPathTests = [...]struct { + dir, file, path string +}{ + 0: {".", ".", "."}, + 1: {"./", "./", "./"}, + 2: {"././././", ".", "."}, + 3: {".", "./././", "./"}, + 4: {".", "a", "a"}, + 5: {".", "a/b", "a/b"}, + 6: {".", "..", ".."}, + 7: {".", "../", "../"}, + 8: {".", "../../", "../../"}, + 9: {".", "../..", "../.."}, + 10: {".", "../..//..///", "../../../"}, + 11: {"/", "/", "/"}, + 12: {"/", "a", "/a"}, + 13: {"/", "a/b", "/a/b"}, + 14: {"/a", "b", "/a/b"}, + 15: {"/", ".", "/"}, + 16: {"/", "..", "/"}, + 17: {"/", "../../", "/"}, + 18: {"/", "/../a/b/c", "/a/b/c"}, + 19: {"/", "/../a/b/c", "/a/b/c"}, + 20: {"/", "./hello/world", "/hello/world"}, + 21: {"/a", "../", "/"}, + 22: {"/a/b/c", "..", "/a/b"}, + 23: {"/a/b/c", "..///..///", "/a/"}, + 24: {"/a/b/c", "..///..///..", "/"}, + 25: {"/a/b/c", "..///..///..///..", "/"}, + 26: {"/a/b/c", "..///..///..///..///..", "/"}, + 27: {"/a/b/c/", "/d/e/f/", "/a/b/c/d/e/f/"}, + 28: {"a/b/c/", ".", "a/b/c"}, + 29: {"a/b/c/", "./d", "a/b/c/d"}, + 30: {"a/b/c/", "./d/", "a/b/c/d/"}, + 31: {"a/b/", "./c/d/", "a/b/c/d/"}, + 32: {"../", "..", "../.."}, + 33: {"a/b/c/d", "e/../..", "a/b/c"}, + 34: {"a/b/c/d", "./e/../..", "a/b/c"}, + 35: {"a/b/c/d", "./e/..//../../f/g//", "a/b/f/g/"}, + 36: {"../../../", "a/../../b/c", "../../b/c"}, + 37: {"/a/b/c", "/.././/hey!", "/a/b/hey!"}, +} + +func TestJoinPath(t *testing.T) { + for _, test := range joinPathTests { + t.Run("", func(t *testing.T) { + path := syscall.JoinPath(test.dir, test.file) + if path != test.path { + t.Errorf("join(%q,%q): want=%q got=%q", test.dir, test.file, test.path, path) + } + }) + } +} + +func BenchmarkJoinPath(b *testing.B) { + for _, test := range joinPathTests { + b.Run("", func(b *testing.B) { + for i := 0; i < b.N; i++ { + syscall.JoinPath(test.dir, test.file) + } + }) + } +} diff --git a/src/syscall/net_wasip1.go b/src/syscall/net_wasip1.go new file mode 100644 index 0000000000..d41e873bed --- /dev/null +++ b/src/syscall/net_wasip1.go @@ -0,0 +1,128 @@ +// 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. + +// wasip1/wasm uses fake networking directly implemented in the net package. +// This file only exists to make the compiler happy. + +//go:build wasip1 + +package syscall + +const ( + AF_UNSPEC = iota + AF_UNIX + AF_INET + AF_INET6 +) + +const ( + SOCK_STREAM = 1 + iota + SOCK_DGRAM + SOCK_RAW + SOCK_SEQPACKET +) + +const ( + IPPROTO_IP = 0 + IPPROTO_IPV4 = 4 + IPPROTO_IPV6 = 0x29 + IPPROTO_TCP = 6 + IPPROTO_UDP = 0x11 +) + +const ( + _ = iota + IPV6_V6ONLY + SOMAXCONN + SO_ERROR +) + +// Misc constants expected by package net but not supported. +const ( + _ = iota + F_DUPFD_CLOEXEC + SYS_FCNTL = 500 // unsupported; same value as net_nacl.go +) + +type Sockaddr interface { +} + +type SockaddrInet4 struct { + Port int + Addr [4]byte +} + +type SockaddrInet6 struct { + Port int + ZoneId uint32 + Addr [16]byte +} + +type SockaddrUnix struct { + Name string +} + +func Socket(proto, sotype, unused int) (fd int, err error) { + return 0, ENOSYS +} + +func Bind(fd int, sa Sockaddr) error { + return ENOSYS +} + +func StopIO(fd int) error { + return ENOSYS +} + +func Listen(fd int, backlog int) error { + return ENOSYS +} + +func Accept(fd int) (newfd int, sa Sockaddr, err error) { + return 0, nil, ENOSYS +} + +func Connect(fd int, sa Sockaddr) error { + return ENOSYS +} + +func Recvfrom(fd int, p []byte, flags int) (n int, from Sockaddr, err error) { + return 0, nil, ENOSYS +} + +func Sendto(fd int, p []byte, flags int, to Sockaddr) error { + return ENOSYS +} + +func Recvmsg(fd int, p, oob []byte, flags int) (n, oobn, recvflags int, from Sockaddr, err error) { + return 0, 0, 0, nil, ENOSYS +} + +func SendmsgN(fd int, p, oob []byte, to Sockaddr, flags int) (n int, err error) { + return 0, ENOSYS +} + +func GetsockoptInt(fd, level, opt int) (value int, err error) { + return 0, ENOSYS +} + +func SetsockoptInt(fd, level, opt int, value int) error { + return ENOSYS +} + +func SetReadDeadline(fd int, t int64) error { + return ENOSYS +} + +func SetWriteDeadline(fd int, t int64) error { + return ENOSYS +} + +func Shutdown(fd int, how int) error { + return ENOSYS +} + +func SetNonblock(fd int, nonblocking bool) error { + return ENOSYS +} diff --git a/src/syscall/os_wasip1.go b/src/syscall/os_wasip1.go new file mode 100644 index 0000000000..01b46f7f56 --- /dev/null +++ b/src/syscall/os_wasip1.go @@ -0,0 +1,8 @@ +// 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. + +package syscall + +//go:wasmimport wasi_snapshot_preview1 proc_exit +func ProcExit(code int32) diff --git a/src/syscall/syscall_wasip1.go b/src/syscall/syscall_wasip1.go new file mode 100644 index 0000000000..4572cad79b --- /dev/null +++ b/src/syscall/syscall_wasip1.go @@ -0,0 +1,463 @@ +// 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/itoa" + "internal/oserror" + "unsafe" +) + +type Dircookie = uint64 + +type Filetype = uint8 + +const ( + FILETYPE_UNKNOWN Filetype = iota + FILETYPE_BLOCK_DEVICE + FILETYPE_CHARACTER_DEVICE + FILETYPE_DIRECTORY + FILETYPE_REGULAR_FILE + FILETYPE_SOCKET_DGRAM + FILETYPE_SOCKET_STREAM + FILETYPE_SYMBOLIC_LINK +) + +type Dirent struct { + // The offset of the next directory entry stored in this directory. + Next Dircookie + // The serial number of the file referred to by this directory entry. + Ino uint64 + // The length of the name of the directory entry. + Namlen uint32 + // The type of the file referred to by this directory entry. + Type Filetype + // Name of the directory entry. + Name *byte +} + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino)) +} + +func direntReclen(buf []byte) (uint64, bool) { + namelen, ok := direntNamlen(buf) + return 24 + namelen, ok +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(Dirent{}.Namlen), unsafe.Sizeof(Dirent{}.Namlen)) +} + +// An Errno is an unsigned number describing an error condition. +// It implements the error interface. The zero Errno is by convention +// a non-error, so code to convert from Errno to error should use: +// +// var err = nil +// if errno != 0 { +// err = errno +// } +type Errno uint32 + +func (e Errno) Error() string { + if 0 <= int(e) && int(e) < len(errorstr) { + s := errorstr[e] + if s != "" { + return s + } + } + return "errno " + itoa.Itoa(int(e)) +} + +func (e Errno) Is(target error) bool { + switch target { + case oserror.ErrPermission: + return e == EACCES || e == EPERM + case oserror.ErrExist: + return e == EEXIST || e == ENOTEMPTY + case oserror.ErrNotExist: + return e == ENOENT + } + return false +} + +func (e Errno) Temporary() bool { + return e == EINTR || e == EMFILE || e.Timeout() +} + +func (e Errno) Timeout() bool { + return e == EAGAIN || e == ETIMEDOUT +} + +// A Signal is a number describing a process signal. +// It implements the os.Signal interface. +type Signal uint8 + +const ( + SIGNONE Signal = iota + SIGHUP + SIGINT + SIGQUIT + SIGILL + SIGTRAP + SIGABRT + SIGBUS + SIGFPE + SIGKILL + SIGUSR1 + SIGSEGV + SIGUSR2 + SIGPIPE + SIGALRM + SIGTERM + SIGCHLD + SIGCONT + SIGSTOP + SIGTSTP + SIGTTIN + SIGTTOU + SIGURG + SIGXCPU + SIGXFSZ + SIGVTARLM + SIGPROF + SIGWINCH + SIGPOLL + SIGPWR + SIGSYS +) + +func (s Signal) Signal() {} + +func (s Signal) String() string { + switch s { + case SIGNONE: + return "no signal" + case SIGHUP: + return "hangup" + case SIGINT: + return "interrupt" + case SIGQUIT: + return "quit" + case SIGILL: + return "illegal instruction" + case SIGTRAP: + return "trace/breakpoint trap" + case SIGABRT: + return "abort" + case SIGBUS: + return "bus error" + case SIGFPE: + return "floating point exception" + case SIGKILL: + return "killed" + case SIGUSR1: + return "user defined signal 1" + case SIGSEGV: + return "segmentation fault" + case SIGUSR2: + return "user defined signal 2" + case SIGPIPE: + return "broken pipe" + case SIGALRM: + return "alarm clock" + case SIGTERM: + return "terminated" + case SIGCHLD: + return "child exited" + case SIGCONT: + return "continued" + case SIGSTOP: + return "stopped (signal)" + case SIGTSTP: + return "stopped" + case SIGTTIN: + return "stopped (tty input)" + case SIGTTOU: + return "stopped (tty output)" + case SIGURG: + return "urgent I/O condition" + case SIGXCPU: + return "CPU time limit exceeded" + case SIGXFSZ: + return "file size limit exceeded" + case SIGVTARLM: + return "virtual timer expired" + case SIGPROF: + return "profiling timer expired" + case SIGWINCH: + return "window changed" + case SIGPOLL: + return "I/O possible" + case SIGPWR: + return "power failure" + case SIGSYS: + return "bad system call" + default: + return "signal " + itoa.Itoa(int(s)) + } +} + +const ( + Stdin = 0 + Stdout = 1 + Stderr = 2 +) + +const ( + O_RDONLY = 0 + O_WRONLY = 1 + O_RDWR = 2 + + O_CREAT = 0100 + O_CREATE = O_CREAT + O_TRUNC = 01000 + O_APPEND = 02000 + O_EXCL = 0200 + O_SYNC = 010000 + + O_CLOEXEC = 0 +) + +const ( + F_DUPFD = 0 + F_GETFD = 1 + F_SETFD = 2 + F_GETFL = 3 + F_SETFL = 4 + F_GETOWN = 5 + F_SETOWN = 6 + F_GETLK = 7 + F_SETLK = 8 + F_SETLKW = 9 + F_RGETLK = 10 + F_RSETLK = 11 + F_CNVT = 12 + F_RSETLKW = 13 + + F_RDLCK = 1 + F_WRLCK = 2 + F_UNLCK = 3 + F_UNLKSYS = 4 +) + +const ( + S_IFMT = 0000370000 + S_IFSHM_SYSV = 0000300000 + S_IFSEMA = 0000270000 + S_IFCOND = 0000260000 + S_IFMUTEX = 0000250000 + S_IFSHM = 0000240000 + S_IFBOUNDSOCK = 0000230000 + S_IFSOCKADDR = 0000220000 + S_IFDSOCK = 0000210000 + + S_IFSOCK = 0000140000 + S_IFLNK = 0000120000 + S_IFREG = 0000100000 + S_IFBLK = 0000060000 + S_IFDIR = 0000040000 + S_IFCHR = 0000020000 + S_IFIFO = 0000010000 + + S_UNSUP = 0000370000 + + S_ISUID = 0004000 + S_ISGID = 0002000 + S_ISVTX = 0001000 + + S_IREAD = 0400 + S_IWRITE = 0200 + S_IEXEC = 0100 + + S_IRWXU = 0700 + S_IRUSR = 0400 + S_IWUSR = 0200 + S_IXUSR = 0100 + + S_IRWXG = 070 + S_IRGRP = 040 + S_IWGRP = 020 + S_IXGRP = 010 + + S_IRWXO = 07 + S_IROTH = 04 + S_IWOTH = 02 + S_IXOTH = 01 +) + +type WaitStatus uint32 + +func (w WaitStatus) Exited() bool { return false } +func (w WaitStatus) ExitStatus() int { return 0 } +func (w WaitStatus) Signaled() bool { return false } +func (w WaitStatus) Signal() Signal { return 0 } +func (w WaitStatus) CoreDump() bool { return false } +func (w WaitStatus) Stopped() bool { return false } +func (w WaitStatus) Continued() bool { return false } +func (w WaitStatus) StopSignal() Signal { return 0 } +func (w WaitStatus) TrapCause() int { return 0 } + +// Rusage is a placeholder to allow compilation of the os/exec package +// because we need Go programs to be portable across platforms. WASI does +// not have a mechanism to to spawn processes so there is no reason for an +// application to take a dependency on this type. +type Rusage struct { + Utime Timeval + Stime Timeval +} + +// ProcAttr is a placeholder to allow compilation of the os/exec package +// because we need Go programs to be portable across platforms. WASI does +// not have a mechanism to to spawn processes so there is no reason for an +// application to take a dependency on this type. +type ProcAttr struct { + Dir string + Env []string + Files []uintptr + Sys *SysProcAttr +} + +type SysProcAttr struct { +} + +func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { + return 0, 0, ENOSYS +} + +func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) { + return 0, 0, ENOSYS +} + +func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { + return 0, 0, ENOSYS +} + +func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) { + return 0, 0, ENOSYS +} + +func Sysctl(key string) (string, error) { + if key == "kern.hostname" { + return "wasip1", nil + } + return "", ENOSYS +} + +func Getuid() int { + return 1 +} + +func Getgid() int { + return 1 +} + +func Geteuid() int { + return 1 +} + +func Getegid() int { + return 1 +} + +func Getgroups() ([]int, error) { + return []int{1}, nil +} + +func Getpid() int { + return 3 +} + +func Getppid() int { + return 2 +} + +func Gettimeofday(tv *Timeval) error { + var time timestamp + if errno := clock_time_get(clockRealtime, 1e3, &time); errno != 0 { + return errno + } + tv.setTimestamp(time) + return nil +} + +func Kill(pid int, signum Signal) error { + // WASI does not have the notion of processes nor signal handlers. + // + // Any signal that the application raises to the process itself will + // be interpreted as being cause for termination. + if pid > 0 && pid != Getpid() { + return ESRCH + } + ProcExit(128 + int32(signum)) + return nil +} + +func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { + return 0, ENOSYS +} + +func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) { + return 0, 0, ENOSYS +} + +func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) { + return 0, ENOSYS +} + +func Umask(mask int) int { + return 0 +} + +type Timespec struct { + Sec int64 + Nsec int64 +} + +func (ts *Timespec) timestamp() timestamp { + return timestamp(ts.Sec*1e9) + timestamp(ts.Nsec) +} + +func (ts *Timespec) setTimestamp(t timestamp) { + ts.Sec = int64(t / 1e9) + ts.Nsec = int64(t % 1e9) +} + +type Timeval struct { + Sec int64 + Usec int64 +} + +func (tv *Timeval) timestamp() timestamp { + return timestamp(tv.Sec*1e9) + timestamp(tv.Usec*1e3) +} + +func (tv *Timeval) setTimestamp(t timestamp) { + tv.Sec = int64(t / 1e9) + tv.Usec = int64((t % 1e9) / 1e3) +} + +func setTimespec(sec, nsec int64) Timespec { + return Timespec{Sec: sec, Nsec: nsec} +} + +func setTimeval(sec, usec int64) Timeval { + return Timeval{Sec: sec, Usec: usec} +} + +type clockid = uint32 + +const ( + clockRealtime clockid = iota + clockMonotonic + clockProcessCPUTimeID + clockThreadCPUTimeID +) + +//go:wasmimport wasi_snapshot_preview1 clock_time_get +//go:noescape +func clock_time_get(id clockid, precision timestamp, time *timestamp) Errno diff --git a/src/syscall/tables_wasip1.go b/src/syscall/tables_wasip1.go new file mode 100644 index 0000000000..973a56e274 --- /dev/null +++ b/src/syscall/tables_wasip1.go @@ -0,0 +1,204 @@ +// 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" + +// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.) +const ( + E2BIG Errno = 1 + EACCES Errno = 2 + EADDRINUSE Errno = 3 + EADDRNOTAVAIL Errno = 4 + EAFNOSUPPORT Errno = 5 + EAGAIN Errno = 6 + EALREADY Errno = 7 + EBADF Errno = 8 + EBADMSG Errno = 9 + EBUSY Errno = 10 + ECANCELED Errno = 11 + ECHILD Errno = 12 + ECONNABORTED Errno = 13 + ECONNREFUSED Errno = 14 + ECONNRESET Errno = 15 + EDEADLK Errno = 16 + EDESTADDRREQ Errno = 17 + EDOM Errno = 18 + EDQUOT Errno = 19 + EEXIST Errno = 20 + EFAULT Errno = 21 + EFBIG Errno = 22 + EHOSTUNREACH Errno = 23 + EIDRM Errno = 24 + EILSEQ Errno = 25 + EINPROGRESS Errno = 26 + EINTR Errno = 27 + EINVAL Errno = 28 + EIO Errno = 29 + EISCONN Errno = 30 + EISDIR Errno = 31 + ELOOP Errno = 32 + EMFILE Errno = 33 + EMLINK Errno = 34 + EMSGSIZE Errno = 35 + EMULTIHOP Errno = 36 + ENAMETOOLONG Errno = 37 + ENETDOWN Errno = 38 + ENETRESET Errno = 39 + ENETUNREACH Errno = 40 + ENFILE Errno = 41 + ENOBUFS Errno = 42 + ENODEV Errno = 43 + ENOENT Errno = 44 + ENOEXEC Errno = 45 + ENOLCK Errno = 46 + ENOLINK Errno = 47 + ENOMEM Errno = 48 + ENOMSG Errno = 49 + ENOPROTOOPT Errno = 50 + ENOSPC Errno = 51 + ENOSYS Errno = 52 + ENOTCONN Errno = 53 + ENOTDIR Errno = 54 + ENOTEMPTY Errno = 55 + ENOTRECOVERABLE Errno = 56 + ENOTSOCK Errno = 57 + ENOTSUP Errno = 58 + ENOTTY Errno = 59 + ENXIO Errno = 60 + EOVERFLOW Errno = 61 + EOWNERDEAD Errno = 62 + EPERM Errno = 63 + EPIPE Errno = 64 + EPROTO Errno = 65 + EPROTONOSUPPORT Errno = 66 + EPROTOTYPE Errno = 67 + ERANGE Errno = 68 + EROFS Errno = 69 + ESPIPE Errno = 70 + ESRCH Errno = 71 + ESTALE Errno = 72 + ETIMEDOUT Errno = 73 + ETXTBSY Errno = 74 + EXDEV Errno = 75 + ENOTCAPABLE Errno = 76 + // needed by src/net/error_unix_test.go + EOPNOTSUPP = ENOTSUP +) + +// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.) +var errorstr = [...]string{ + E2BIG: "Argument list too long", + EACCES: "Permission denied", + EADDRINUSE: "Address already in use", + EADDRNOTAVAIL: "Address not available", + EAFNOSUPPORT: "Address family not supported by protocol family", + EAGAIN: "Try again", + EALREADY: "Socket already connected", + EBADF: "Bad file number", + EBADMSG: "Trying to read unreadable message", + EBUSY: "Device or resource busy", + ECANCELED: "Operation canceled.", + ECHILD: "No child processes", + ECONNABORTED: "Connection aborted", + ECONNREFUSED: "Connection refused", + ECONNRESET: "Connection reset by peer", + EDEADLK: "Deadlock condition", + EDESTADDRREQ: "Destination address required", + EDOM: "Math arg out of domain of func", + EDQUOT: "Quota exceeded", + EEXIST: "File exists", + EFAULT: "Bad address", + EFBIG: "File too large", + EHOSTUNREACH: "Host is unreachable", + EIDRM: "Identifier removed", + EILSEQ: "EILSEQ", + EINPROGRESS: "Connection already in progress", + EINTR: "Interrupted system call", + EINVAL: "Invalid argument", + EIO: "I/O error", + EISCONN: "Socket is already connected", + EISDIR: "Is a directory", + ELOOP: "Too many symbolic links", + EMFILE: "Too many open files", + EMLINK: "Too many links", + EMSGSIZE: "Message too long", + EMULTIHOP: "Multihop attempted", + ENAMETOOLONG: "File name too long", + ENETDOWN: "Network interface is not configured", + ENETRESET: "Network dropped connection on reset", + ENETUNREACH: "Network is unreachable", + ENFILE: "File table overflow", + ENOBUFS: "No buffer space available", + ENODEV: "No such device", + ENOENT: "No such file or directory", + ENOEXEC: "Exec format error", + ENOLCK: "No record locks available", + ENOLINK: "The link has been severed", + ENOMEM: "Out of memory", + ENOMSG: "No message of desired type", + ENOPROTOOPT: "Protocol not available", + ENOSPC: "No space left on device", + ENOSYS: "Not implemented on " + runtime.GOOS, + ENOTCONN: "Socket is not connected", + ENOTDIR: "Not a directory", + ENOTEMPTY: "Directory not empty", + ENOTRECOVERABLE: "State not recoverable", + ENOTSOCK: "Socket operation on non-socket", + ENOTSUP: "Not supported", + ENOTTY: "Not a typewriter", + ENXIO: "No such device or address", + EOVERFLOW: "Value too large for defined data type", + EOWNERDEAD: "Owner died", + EPERM: "Operation not permitted", + EPIPE: "Broken pipe", + EPROTO: "Protocol error", + EPROTONOSUPPORT: "Unknown protocol", + EPROTOTYPE: "Protocol wrong type for socket", + ERANGE: "Math result not representable", + EROFS: "Read-only file system", + ESPIPE: "Illegal seek", + ESRCH: "No such process", + ESTALE: "Stale file handle", + ETIMEDOUT: "Connection timed out", + ETXTBSY: "Text file busy", + EXDEV: "Cross-device link", + ENOTCAPABLE: "Capabilities insufficient", +} + +// Do the interface allocations only once for common +// Errno values. +var ( + errEAGAIN error = EAGAIN + errEINVAL error = EINVAL + errENOENT error = ENOENT +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +// +// We set both noinline and nosplit to reduce code size, this function has many +// call sites in the syscall package, inlining it causes a significant increase +// of the compiled code; the function call ultimately does not make a difference +// in the performance of syscall functions since the time is dominated by calls +// to the imports and path resolution. +// +//go:noinline +//go:nosplit +func errnoErr(e Errno) error { + switch e { + case 0: + return nil + case EAGAIN: + return errEAGAIN + case EINVAL: + return errEINVAL + case ENOENT: + return errENOENT + } + return e +} diff --git a/src/syscall/timestruct.go b/src/syscall/timestruct.go index 42418a693c..4fca63cc40 100644 --- a/src/syscall/timestruct.go +++ b/src/syscall/timestruct.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build unix || (js && wasm) +//go:build unix || (js && wasm) || wasip1 package syscall -- 2.48.1