]> Cypherpunks repositories - gostls13.git/commitdiff
syscall: add remaining wasip1 files
authorJohan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Sat, 25 Mar 2023 06:16:43 +0000 (23:16 -0700)
committerJohan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Thu, 6 Apr 2023 20:58:35 +0000 (20:58 +0000)
Implements filesystem, networking and os interactions.

For #58141

Co-authored-by: Richard Musiol <neelance@gmail.com>
Co-authored-by: Achille Roussel <achille.roussel@gmail.com>
Co-authored-by: Julien Fabre <ju.pryz@gmail.com>
Co-authored-by: Evan Phoenix <evan@phx.io>
Change-Id: If5c43ad5bd2955e6d2d4e2822fd68bce89ca786c
Reviewed-on: https://go-review.googlesource.com/c/go/+/479619
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>

src/syscall/dirent.go
src/syscall/env_unix.go
src/syscall/export_wasip1_test.go [new file with mode: 0644]
src/syscall/fs_wasip1.go [new file with mode: 0644]
src/syscall/fs_wasip1_test.go [new file with mode: 0644]
src/syscall/net_wasip1.go [new file with mode: 0644]
src/syscall/os_wasip1.go [new file with mode: 0644]
src/syscall/syscall_wasip1.go [new file with mode: 0644]
src/syscall/tables_wasip1.go [new file with mode: 0644]
src/syscall/timestruct.go

index b10608a6629c531ae7cd704e17f4a5d8c1ade9e1..eee94bf73c0c55670ca77066658abf5323419ab2 100644 (file)
@@ -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
 
index 6d917da20835205c24ad6062aed2cf0a8344d91d..8e87e018e82de37d90c064df3bb6d8d7d3c57f8b 100644 (file)
@@ -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 (file)
index 0000000..aa53d26
--- /dev/null
@@ -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 (file)
index 0000000..d2265eb
--- /dev/null
@@ -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 (file)
index 0000000..f27e6a8
--- /dev/null
@@ -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 (file)
index 0000000..d41e873
--- /dev/null
@@ -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 (file)
index 0000000..01b46f7
--- /dev/null
@@ -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 (file)
index 0000000..4572cad
--- /dev/null
@@ -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 (file)
index 0000000..973a56e
--- /dev/null
@@ -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
+}
index 42418a693cf36c05d1fb9a33ed222a7af12720f1..4fca63cc40cc43499c9a46fe147aeb9e473ba56b 100644 (file)
@@ -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