diff options
Diffstat (limited to 'src/syscall/syscall_libc_wasi.go')
-rw-r--r-- | src/syscall/syscall_libc_wasi.go | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go new file mode 100644 index 000000000..2d83a8714 --- /dev/null +++ b/src/syscall/syscall_libc_wasi.go @@ -0,0 +1,479 @@ +//go:build wasip1 || wasip2 + +package syscall + +import ( + "internal/itoa" + "unsafe" +) + +// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt +// disagrees with ../../lib/wasi-libc/libc-top-half/musl/arch/wasm32/bits/signal.h for SIGCHLD? +// https://github.com/WebAssembly/wasi-libc/issues/271 + +type Signal int + +const ( + SIGINT Signal = 2 + SIGQUIT Signal = 3 + SIGILL Signal = 4 + SIGTRAP Signal = 5 + SIGABRT Signal = 6 + SIGBUS Signal = 7 + SIGFPE Signal = 8 + SIGKILL Signal = 9 + SIGSEGV Signal = 11 + SIGPIPE Signal = 13 + SIGTERM Signal = 15 + SIGCHLD Signal = 17 +) + +func (s Signal) Signal() {} + +func (s Signal) String() string { + if 0 <= s && int(s) < len(signals) { + str := signals[s] + if str != "" { + return str + } + } + return "signal " + itoa.Itoa(int(s)) +} + +var signals = [...]string{} + +const ( + Stdin = 0 + Stdout = 1 + Stderr = 2 +) + +const ( + __WASI_OFLAGS_CREAT = 1 + __WASI_OFLAGS_DIRECTORY = 2 + __WASI_OFLAGS_EXCL = 4 + __WASI_OFLAGS_TRUNC = 8 + + __WASI_FDFLAGS_APPEND = 1 + __WASI_FDFLAGS_DSYNC = 2 + __WASI_FDFLAGS_NONBLOCK = 4 + __WASI_FDFLAGS_RSYNC = 8 + __WASI_FDFLAGS_SYNC = 16 + + __WASI_FILETYPE_UNKNOWN = 0 + __WASI_FILETYPE_BLOCK_DEVICE = 1 + __WASI_FILETYPE_CHARACTER_DEVICE = 2 + __WASI_FILETYPE_DIRECTORY = 3 + __WASI_FILETYPE_REGULAR_FILE = 4 + __WASI_FILETYPE_SOCKET_DGRAM = 5 + __WASI_FILETYPE_SOCKET_STREAM = 6 + __WASI_FILETYPE_SYMBOLIC_LINK = 7 + + // ../../lib/wasi-libc/libc-bottom-half/headers/public/__header_fcntl.h + O_NOFOLLOW = 0x01000000 + O_RDONLY = 0x04000000 + O_WRONLY = 0x10000000 + O_RDWR = O_RDONLY | O_WRONLY + + O_CREAT = __WASI_OFLAGS_CREAT << 12 + O_TRUNC = __WASI_OFLAGS_TRUNC << 12 + O_EXCL = __WASI_OFLAGS_EXCL << 12 + O_DIRECTORY = __WASI_OFLAGS_DIRECTORY << 12 + + O_APPEND = __WASI_FDFLAGS_APPEND + O_DSYNC = __WASI_FDFLAGS_DSYNC + O_NONBLOCK = __WASI_FDFLAGS_NONBLOCK + O_RSYNC = __WASI_FDFLAGS_RSYNC + O_SYNC = __WASI_FDFLAGS_SYNC + + O_CLOEXEC = 0 + + // ../../lib/wasi-libc/sysroot/include/sys/mman.h + MAP_FILE = 0 + MAP_SHARED = 0x01 + MAP_PRIVATE = 0x02 + MAP_ANON = 0x20 + MAP_ANONYMOUS = MAP_ANON + + // ../../lib/wasi-libc/sysroot/include/sys/mman.h + PROT_NONE = 0 + PROT_READ = 1 + PROT_WRITE = 2 + PROT_EXEC = 4 + + // ../../lib/wasi-libc/expected/wasm32-wasi/predefined-macros.txt + F_GETFL = 3 + F_SETFL = 4 +) + +// These values are needed as a stub until Go supports WASI as a full target. +// The constant values don't have a meaning and don't correspond to anything +// real. +const ( + _ = iota + SYS_FCNTL + SYS_FCNTL64 + SYS_FSTATAT64 + SYS_OPENAT + SYS_UNLINKAT + PATH_MAX = 4096 +) + +func getErrno() error { + // libcErrno is the errno from wasi-libc for wasip1 and the errno for libc_wasip2 for wasip2 + return libcErrno +} + +func (e Errno) Is(target error) bool { + switch target.Error() { + case "permission denied": + return e == EACCES || e == EPERM || e == ENOTCAPABLE // ENOTCAPABLE is unique in WASI + case "file already exists": + return e == EEXIST || e == ENOTEMPTY + case "file does not exist": + return e == ENOENT + } + return false +} + +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__errno.h +const ( + E2BIG Errno = 1 /* Argument list too long */ + EACCES Errno = 2 /* Permission denied */ + EADDRINUSE Errno = 3 /* Address already in use */ + EADDRNOTAVAIL Errno = 4 /* Address not available */ + EAFNOSUPPORT Errno = 5 /* Address family not supported by protocol family */ + EAGAIN Errno = 6 /* Try again */ + EWOULDBLOCK Errno = EAGAIN /* Operation would block */ + EALREADY Errno = 7 /* Socket already connected */ + EBADF Errno = 8 /* Bad file number */ + EBADMSG Errno = 9 /* Trying to read unreadable message */ + EBUSY Errno = 10 /* Device or resource busy */ + ECANCELED Errno = 11 /* Operation canceled. */ + ECHILD Errno = 12 /* No child processes */ + ECONNABORTED Errno = 13 /* Connection aborted */ + ECONNREFUSED Errno = 14 /* Connection refused */ + ECONNRESET Errno = 15 /* Connection reset by peer */ + EDEADLK Errno = 16 /* Deadlock condition */ + EDESTADDRREQ Errno = 17 /* Destination address required */ + EDOM Errno = 18 /* Math arg out of domain of func */ + EDQUOT Errno = 19 /* Quota exceeded */ + EEXIST Errno = 20 /* File exists */ + EFAULT Errno = 21 /* Bad address */ + EFBIG Errno = 22 /* File too large */ + EHOSTUNREACH Errno = 23 /* Host is unreachable */ + EIDRM Errno = 24 /* Identifier removed */ + EILSEQ Errno = 25 + EINPROGRESS Errno = 26 /* Connection already in progress */ + EINTR Errno = 27 /* Interrupted system call */ + EINVAL Errno = 28 /* Invalid argument */ + EIO Errno = 29 /* I/O error */ + EISCONN Errno = 30 /* Socket is already connected */ + EISDIR Errno = 31 /* Is a directory */ + ELOOP Errno = 32 /* Too many symbolic links */ + EMFILE Errno = 33 /* Too many open files */ + EMLINK Errno = 34 /* Too many links */ + EMSGSIZE Errno = 35 /* Message too long */ + EMULTIHOP Errno = 36 /* Multihop attempted */ + ENAMETOOLONG Errno = 37 /* File name too long */ + ENETDOWN Errno = 38 /* Network interface is not configured */ + ENETRESET Errno = 39 + ENETUNREACH Errno = 40 /* Network is unreachable */ + ENFILE Errno = 41 /* File table overflow */ + ENOBUFS Errno = 42 /* No buffer space available */ + ENODEV Errno = 43 /* No such device */ + ENOENT Errno = 44 /* No such file or directory */ + ENOEXEC Errno = 45 /* Exec format error */ + ENOLCK Errno = 46 /* No record locks available */ + ENOLINK Errno = 47 /* The link has been severed */ + ENOMEM Errno = 48 /* Out of memory */ + ENOMSG Errno = 49 /* No message of desired type */ + ENOPROTOOPT Errno = 50 /* Protocol not available */ + ENOSPC Errno = 51 /* No space left on device */ + ENOSYS Errno = 52 /* Function not implemented */ + ENOTCONN Errno = 53 /* Socket is not connected */ + ENOTDIR Errno = 54 /* Not a directory */ + ENOTEMPTY Errno = 55 /* Directory not empty */ + ENOTRECOVERABLE Errno = 56 /* State not recoverable */ + ENOTSOCK Errno = 57 /* Socket operation on non-socket */ + ESOCKTNOSUPPORT Errno = 58 /* Socket type not supported */ + EOPNOTSUPP Errno = 58 /* Operation not supported on transport endpoint */ + ENOTSUP Errno = EOPNOTSUPP /* Not supported */ + ENOTTY Errno = 59 /* Not a typewriter */ + ENXIO Errno = 60 /* No such device or address */ + EOVERFLOW Errno = 61 /* Value too large for defined data type */ + EPERM Errno = 63 /* Operation not permitted */ + EPIPE Errno = 64 /* Broken pipe */ + EPROTO Errno = 65 /* Protocol error */ + EPROTONOSUPPORT Errno = 66 /* Unknown protocol */ + EPROTOTYPE Errno = 67 /* Protocol wrong type for socket */ + ERANGE Errno = 68 /* Math result not representable */ + EROFS Errno = 69 /* Read-only file system */ + ESPIPE Errno = 70 /* Illegal seek */ + ESRCH Errno = 71 /* No such process */ + ESTALE Errno = 72 + ETIMEDOUT Errno = 73 /* Connection timed out */ + ETXTBSY Errno = 74 /* Text file busy */ + EXDEV Errno = 75 /* Cross-device link */ + ENOTCAPABLE Errno = 76 /* Extension: Capabilities insufficient. */ + + EWASIERROR Errno = 255 /* Unknown WASI error */ +) + +// TODO(ydnar): remove Timespec for WASI Preview 2 (seconds is uint64). +// +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h +type Timespec struct { + Sec int32 + Nsec int64 +} + +// Unix returns the time stored in ts as seconds plus nanoseconds. +func (ts *Timespec) Unix() (sec int64, nsec int64) { + return int64(ts.Sec), int64(ts.Nsec) +} + +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_stat.h +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__typedef_ino_t.h +// etc. +type Stat_t struct { + Dev uint64 + Ino uint64 + Nlink uint64 + Mode uint32 + Uid uint32 + Gid uint32 + Pad_cgo_0 [4]byte + Rdev uint64 + Size int64 + Blksize int32 + Blocks int64 + + Atim Timespec + Mtim Timespec + Ctim Timespec + Qspare [3]int64 +} + +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-top-half/musl/include/sys/stat.h +const ( + S_IFBLK = 0x6000 + S_IFCHR = 0x2000 + S_IFDIR = 0x4000 + S_IFIFO = 0x1000 + S_IFLNK = 0xa000 + S_IFMT = 0xf000 + S_IFREG = 0x8000 + S_IFSOCK = 0xc000 + S_IREAD = 0x100 + S_IRGRP = 0x20 + S_IROTH = 0x4 + S_IRUSR = 0x100 + S_IRWXG = 0x38 + S_IRWXO = 0x7 + S_IRWXU = 0x1c0 + S_ISGID = 0x400 + S_ISUID = 0x800 + S_ISVTX = 0x200 + S_IWGRP = 0x10 + S_IWOTH = 0x2 + S_IWRITE = 0x80 + S_IWUSR = 0x80 + S_IXGRP = 0x8 + S_IXOTH = 0x1 + S_IXUSR = 0x40 +) + +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__header_dirent.h +const ( + DT_BLK = __WASI_FILETYPE_BLOCK_DEVICE + DT_CHR = __WASI_FILETYPE_CHARACTER_DEVICE + DT_DIR = __WASI_FILETYPE_DIRECTORY + DT_FIFO = __WASI_FILETYPE_SOCKET_STREAM + DT_LNK = __WASI_FILETYPE_SYMBOLIC_LINK + DT_REG = __WASI_FILETYPE_REGULAR_FILE + DT_UNKNOWN = __WASI_FILETYPE_UNKNOWN +) + +// Dirent is returned by pointer from Readdir to iterate over directory entries. +// +// The pointer is managed by wasi-libc and is only valid until the next call to +// Readdir or Fdclosedir. +// +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_dirent.h +type Dirent struct { + Ino uint64 + Type uint8 +} + +func (dirent *Dirent) Name() []byte { + // The dirent C struct uses a flexible array member to indicate that the + // directory name is laid out in memory right after the struct data: + // + // struct dirent { + // ino_t d_ino; + // unsigned char d_type; + // char d_name[]; + // }; + name := (*[PATH_MAX]byte)(unsafe.Add(unsafe.Pointer(dirent), 9)) + for i, c := range name { + if c == 0 { + return name[:i:i] + } + } + return name[:] +} + +func Fdopendir(fd int) (dir uintptr, err error) { + d := libc_fdopendir(int32(fd)) + + if d == nil { + err = getErrno() + } + return uintptr(d), err +} + +func Fdclosedir(dir uintptr) (err error) { + // Unlike on other unix platform where only closedir exists, wasi-libc has + // fdclosedir which releases resources and returns the file descriptor but + // does not close it. This is useful for us since we want to be able to keep + // using it. + n := libc_fdclosedir(unsafe.Pointer(dir)) + + if n < 0 { + err = getErrno() + } + return +} + +func Readdir(dir uintptr) (dirent *Dirent, err error) { + // There might be a leftover errno value in the global variable, so we have + // to clear it before calling readdir because we cannot know whether a nil + // return means that we reached EOF or that an error occurred. + libcErrno = 0 + + dirent = libc_readdir(unsafe.Pointer(dir)) + + if dirent == nil && libcErrno != 0 { + err = getErrno() + } + return +} + +func Stat(path string, p *Stat_t) (err error) { + data := cstring(path) + n := libc_stat(&data[0], unsafe.Pointer(p)) + + if n < 0 { + err = getErrno() + } + return +} + +func Fstat(fd int, p *Stat_t) (err error) { + n := libc_fstat(int32(fd), unsafe.Pointer(p)) + + if n < 0 { + err = getErrno() + } + return +} + +func Lstat(path string, p *Stat_t) (err error) { + data := cstring(path) + n := libc_lstat(&data[0], unsafe.Pointer(p)) + if n < 0 { + err = getErrno() + } + return +} + +func Pipe2(p []int, flags int) (err error) { + return ENOSYS // TODO +} + +func Chmod(path string, mode uint32) (err error) { + // wasi does not have chmod, but there are tests that validate that calling + // os.Chmod does not error (e.g. io/fs.TestIssue51617). + // + // We make a call to Lstat instead so we detect conditions like the path not + // existing, but we don't honnor the request to modify the file permissions. + stat := Stat_t{} + return Lstat(path, &stat) +} + +// TODO: should this return runtime.wasmPageSize? +func Getpagesize() int { + return libc_getpagesize() +} + +type Utsname struct { + Sysname [65]int8 + Nodename [65]int8 + Release [65]int8 + Version [65]int8 + Machine [65]int8 + Domainname [65]int8 +} + +//go:linkname faccessat syscall.Faccessat +func faccessat(dirfd int, path string, mode uint32, flags int) (err error) { + return ENOSYS +} + +// Stub Utsname, needed because WASI pretends to be linux/arm. +func Uname(buf *Utsname) (err error) + +type RawSockaddrInet4 struct { + // stub +} + +type RawSockaddrInet6 struct { + // stub +} + +// This is a stub, it is not functional. +func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) + +// This is a stub, it is not functional. +func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) + +// int getpagesize(void); +// +//export getpagesize +func libc_getpagesize() int + +// int stat(const char *path, struct stat * buf); +// +//export stat +func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 + +// int fstat(int fd, struct stat * buf); +// +//export fstat +func libc_fstat(fd int32, ptr unsafe.Pointer) int32 + +// int lstat(const char *path, struct stat * buf); +// +//export lstat +func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 + +// int open(const char *pathname, int flags, mode_t mode); +// +//export open +func libc_open(pathname *byte, flags int32, mode uint32) int32 + +// DIR *fdopendir(int); +// +//export fdopendir +func libc_fdopendir(fd int32) unsafe.Pointer + +// int fdclosedir(DIR *); +// +//export fdclosedir +func libc_fdclosedir(unsafe.Pointer) int32 + +// struct dirent *readdir(DIR *); +// +//export readdir +func libc_readdir(unsafe.Pointer) *Dirent |