Files
tinygo/src/syscall/libc_wasip2.go
T
Ayke van Laethem 289fceb3ea syscall: refactor environment handling
* Move environment functions to their own files.
  * Rewrite the WASIp2 version of environment variables to be much
    simpler (don't go through C functions).
2024-11-19 07:51:58 +01:00

1308 lines
27 KiB
Go

//go:build wasip2
// mini libc wrapping wasi preview2 calls in a libc api
package syscall
import (
"unsafe"
"internal/cm"
"internal/wasi/cli/v0.2.0/environment"
"internal/wasi/cli/v0.2.0/stderr"
"internal/wasi/cli/v0.2.0/stdin"
"internal/wasi/cli/v0.2.0/stdout"
wallclock "internal/wasi/clocks/v0.2.0/wall-clock"
"internal/wasi/filesystem/v0.2.0/preopens"
"internal/wasi/filesystem/v0.2.0/types"
ioerror "internal/wasi/io/v0.2.0/error"
"internal/wasi/io/v0.2.0/streams"
"internal/wasi/random/v0.2.0/random"
)
func goString(cstr *byte) string {
return unsafe.String(cstr, strlen(cstr))
}
//export strlen
func strlen(cstr *byte) uintptr {
if cstr == nil {
return 0
}
ptr := unsafe.Pointer(cstr)
var i uintptr
for p := (*byte)(ptr); *p != 0; p = (*byte)(unsafe.Add(unsafe.Pointer(p), 1)) {
i++
}
return i
}
// ssize_t write(int fd, const void *buf, size_t count)
//
//export write
func write(fd int32, buf *byte, count uint) int {
if stream, ok := wasiStreams[fd]; ok {
return writeStream(stream, buf, count, 0)
}
stream, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if stream.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
n := pwrite(fd, buf, count, int64(stream.offset))
if n == -1 {
return -1
}
stream.offset += int64(n)
return int(n)
}
// ssize_t read(int fd, void *buf, size_t count);
//
//export read
func read(fd int32, buf *byte, count uint) int {
if stream, ok := wasiStreams[fd]; ok {
return readStream(stream, buf, count, 0)
}
stream, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if stream.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
n := pread(fd, buf, count, int64(stream.offset))
if n == -1 {
// error during pread
return -1
}
stream.offset += int64(n)
return int(n)
}
// At the moment, each time we have a file read or write we create a new stream. Future implementations
// could change the current in or out file stream lazily. We could do this by tracking input and output
// offsets individually, and if they don't match the current main offset, reopen the file stream at that location.
type wasiFile struct {
d types.Descriptor
oflag int32 // original open flags: O_RDONLY, O_WRONLY, O_RDWR
offset int64 // current fd offset; updated with each read/write
refs int
}
// Need to figure out which system calls we're using:
// stdin/stdout/stderr want streams, so we use stream read/write
// but for regular files we can use the descriptor and explicitly write a buffer to the offset?
// The mismatch comes from trying to combine these.
var wasiFiles map[int32]*wasiFile = make(map[int32]*wasiFile)
func findFreeFD() int32 {
var newfd int32
for wasiStreams[newfd] != nil || wasiFiles[newfd] != nil {
newfd++
}
return newfd
}
var wasiErrno ioerror.Error
type wasiStream struct {
in *streams.InputStream
out *streams.OutputStream
refs int
}
// This holds entries for stdin/stdout/stderr.
var wasiStreams map[int32]*wasiStream
func init() {
sin := stdin.GetStdin()
sout := stdout.GetStdout()
serr := stderr.GetStderr()
wasiStreams = map[int32]*wasiStream{
0: &wasiStream{
in: &sin,
refs: 1,
},
1: &wasiStream{
out: &sout,
refs: 1,
},
2: &wasiStream{
out: &serr,
refs: 1,
},
}
}
func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
if stream.in == nil {
// not a stream we can read from
libcErrno = EBADF
return -1
}
if offset != 0 {
libcErrno = EINVAL
return -1
}
libcErrno = 0
list, err, isErr := stream.in.BlockingRead(uint64(count)).Result()
if isErr {
if err.Closed() {
libcErrno = 0
return 0
} else if err := err.LastOperationFailed(); err != nil {
wasiErrno = *err
libcErrno = EWASIERROR
}
return -1
}
copy(unsafe.Slice(buf, count), list.Slice())
return int(list.Len())
}
func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
if stream.out == nil {
// not a stream we can write to
libcErrno = EBADF
return -1
}
if offset != 0 {
libcErrno = EINVAL
return -1
}
src := unsafe.Slice(buf, count)
var remaining = count
// The blocking-write-and-flush call allows a maximum of 4096 bytes at a time.
// We loop here by instead of doing subscribe/check-write/poll-one/write by hand.
for remaining > 0 {
len := uint(4096)
if len > remaining {
len = remaining
}
_, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result()
if isErr {
if err.Closed() {
libcErrno = 0
return 0
} else if err := err.LastOperationFailed(); err != nil {
wasiErrno = *err
libcErrno = EWASIERROR
}
return -1
}
remaining -= len
}
return int(count)
}
//go:linkname memcpy runtime.memcpy
func memcpy(dst, src unsafe.Pointer, size uintptr)
// ssize_t pread(int fd, void *buf, size_t count, off_t offset);
//
//export pread
func pread(fd int32, buf *byte, count uint, offset int64) int {
// TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ?
if stream, ok := wasiStreams[fd]; ok {
return readStream(stream, buf, count, offset)
}
streams, ok := wasiFiles[fd]
if !ok {
// TODO(dgryski): EINVAL?
libcErrno = EBADF
return -1
}
if streams.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
if streams.oflag&O_RDONLY == 0 {
libcErrno = EBADF
return -1
}
listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
list := listEOF.F0
copy(unsafe.Slice(buf, count), list.Slice())
// TODO(dgryski): EOF bool is ignored?
return int(list.Len())
}
// ssize_t pwrite(int fd, void *buf, size_t count, off_t offset);
//
//export pwrite
func pwrite(fd int32, buf *byte, count uint, offset int64) int {
// TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ?
if stream, ok := wasiStreams[fd]; ok {
return writeStream(stream, buf, count, 0)
}
streams, ok := wasiFiles[fd]
if !ok {
// TODO(dgryski): EINVAL?
libcErrno = EBADF
return -1
}
if streams.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
if streams.oflag&O_WRONLY == 0 {
libcErrno = EBADF
return -1
}
n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result()
if isErr {
// TODO(dgryski):
libcErrno = errorCodeToErrno(err)
return -1
}
return int(n)
}
// ssize_t lseek(int fd, off_t offset, int whence);
//
//export lseek
func lseek(fd int32, offset int64, whence int) int64 {
if _, ok := wasiStreams[fd]; ok {
// can't lseek a stream
libcErrno = EBADF
return -1
}
stream, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if stream.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
switch whence {
case 0: // SEEK_SET
stream.offset = offset
case 1: // SEEK_CUR
stream.offset += offset
case 2: // SEEK_END
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream.offset = int64(stat.Size) + offset
}
return int64(stream.offset)
}
// int close(int fd)
//
//export close
func close(fd int32) int32 {
if streams, ok := wasiStreams[fd]; ok {
if streams.out != nil {
// ignore any error
streams.out.BlockingFlush()
}
if streams.refs--; streams.refs == 0 {
if streams.out != nil {
streams.out.ResourceDrop()
streams.out = nil
}
if streams.in != nil {
streams.in.ResourceDrop()
streams.in = nil
}
}
delete(wasiStreams, fd)
return 0
}
streams, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if streams.refs--; streams.refs == 0 && streams.d != cm.ResourceNone {
streams.d.ResourceDrop()
streams.d = 0
}
delete(wasiFiles, fd)
return 0
}
// int dup(int fd)
//
//export dup
func dup(fd int32) int32 {
// is fd a stream?
if stream, ok := wasiStreams[fd]; ok {
newfd := findFreeFD()
stream.refs++
wasiStreams[newfd] = stream
return newfd
}
// is fd a file?
if file, ok := wasiFiles[fd]; ok {
// scan for first free file descriptor
newfd := findFreeFD()
file.refs++
wasiFiles[newfd] = file
return newfd
}
// unknown file descriptor
libcErrno = EBADF
return -1
}
// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
//
//export mmap
func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer {
libcErrno = ENOSYS
return unsafe.Pointer(^uintptr(0))
}
// int munmap(void *addr, size_t length);
//
//export munmap
func munmap(addr unsafe.Pointer, length uintptr) int32 {
libcErrno = ENOSYS
return -1
}
// int mprotect(void *addr, size_t len, int prot);
//
//export mprotect
func mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 {
libcErrno = ENOSYS
return -1
}
// int chmod(const char *pathname, mode_t mode);
//
//export chmod
func chmod(pathname *byte, mode uint32) int32 {
return 0
}
// int mkdir(const char *pathname, mode_t mode);
//
//export mkdir
func mkdir(pathname *byte, mode uint32) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
_, err, isErr := dir.d.CreateDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int rmdir(const char *pathname);
//
//export rmdir
func rmdir(pathname *byte) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
_, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int rename(const char *from, *to);
//
//export rename
func rename(from, to *byte) int32 {
fromPath := goString(from)
fromDir, fromRelPath := findPreopenForPath(fromPath)
if fromDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
toPath := goString(to)
toDir, toRelPath := findPreopenForPath(toPath)
if toDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
_, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int symlink(const char *from, *to);
//
//export symlink
func symlink(from, to *byte) int32 {
fromPath := goString(from)
fromDir, fromRelPath := findPreopenForPath(fromPath)
if fromDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
toPath := goString(to)
toDir, toRelPath := findPreopenForPath(toPath)
if toDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
if fromDir.d != toDir.d {
libcErrno = EACCES
return -1
}
// TODO(dgryski): check fromDir == toDir?
_, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int link(const char *from, *to);
//
//export link
func link(from, to *byte) int32 {
fromPath := goString(from)
fromDir, fromRelPath := findPreopenForPath(fromPath)
if fromDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
toPath := goString(to)
toDir, toRelPath := findPreopenForPath(toPath)
if toDir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
if fromDir.d != toDir.d {
libcErrno = EACCES
return -1
}
// TODO(dgryski): check fromDir == toDir?
_, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int fsync(int fd);
//
//export fsync
func fsync(fd int32) int32 {
if _, ok := wasiStreams[fd]; ok {
// can't sync a stream
libcErrno = EBADF
return -1
}
streams, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if streams.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
if streams.oflag&O_WRONLY == 0 {
libcErrno = EBADF
return -1
}
_, err, isErr := streams.d.SyncData().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// ssize_t readlink(const char *path, void *buf, size_t count);
//
//export readlink
func readlink(pathname *byte, buf *byte, count uint) int {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
s, err, isErr := dir.d.ReadLinkAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
size := uintptr(count)
if size > uintptr(len(s)) {
size = uintptr(len(s))
}
memcpy(unsafe.Pointer(buf), unsafe.Pointer(unsafe.StringData(s)), size)
return int(size)
}
// int unlink(const char *pathname);
//
//export unlink
func unlink(pathname *byte) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
_, err, isErr := dir.d.UnlinkFileAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
return 0
}
// int getpagesize(void);
//
//export getpagesize
func getpagesize() int {
return 65536
}
// int stat(const char *path, struct stat * buf);
//
//export stat
func stat(pathname *byte, dst *Stat_t) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, &stat)
return 0
}
// int fstat(int fd, struct stat * buf);
//
//export fstat
func fstat(fd int32, dst *Stat_t) int32 {
if _, ok := wasiStreams[fd]; ok {
// TODO(dgryski): fill in stat buffer for stdin etc
return -1
}
stream, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return -1
}
if stream.d == cm.ResourceNone {
libcErrno = EBADF
return -1
}
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, &stat)
return 0
}
func setStatFromWASIStat(sstat *Stat_t, wstat *types.DescriptorStat) {
// This will cause problems for people who want to compare inodes
sstat.Dev = 0
sstat.Ino = 0
sstat.Rdev = 0
sstat.Nlink = uint64(wstat.LinkCount)
sstat.Mode = p2fileTypeToStatType(wstat.Type)
// No uid/gid
sstat.Uid = 0
sstat.Gid = 0
sstat.Size = int64(wstat.Size)
// made up numbers
sstat.Blksize = 512
sstat.Blocks = (sstat.Size + 511) / int64(sstat.Blksize)
setOptTime := func(t *Timespec, o *wallclock.DateTime) {
t.Sec = 0
t.Nsec = 0
if o != nil {
t.Sec = int32(o.Seconds)
t.Nsec = int64(o.Nanoseconds)
}
}
setOptTime(&sstat.Atim, wstat.DataAccessTimestamp.Some())
setOptTime(&sstat.Mtim, wstat.DataModificationTimestamp.Some())
setOptTime(&sstat.Ctim, wstat.StatusChangeTimestamp.Some())
}
// int lstat(const char *path, struct stat * buf);
//
//export lstat
func lstat(pathname *byte, dst *Stat_t) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, &stat)
return 0
}
func init() {
populateEnvironment()
populatePreopens()
}
type wasiDir struct {
d types.Descriptor // wasip2 descriptor
root string // root path for this descriptor
rel string // relative path under root
}
var libcCWD wasiDir
var wasiPreopens map[string]types.Descriptor
func populatePreopens() {
var cwd string
// find CWD
result := environment.InitialCWD()
if s := result.Some(); s != nil {
cwd = *s
} else if s, _ := Getenv("PWD"); s != "" {
cwd = s
}
dirs := preopens.GetDirectories().Slice()
preopens := make(map[string]types.Descriptor, len(dirs))
for _, tup := range dirs {
desc, path := tup.F0, tup.F1
if path == cwd {
libcCWD.d = desc
libcCWD.root = path
libcCWD.rel = ""
}
preopens[path] = desc
}
wasiPreopens = preopens
}
// -- BEGIN fs_wasip1.go --
// The following section has been taken from upstream Go with the following copyright:
// 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:nosplit
func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) {
i := 0
for i < len(path) {
for i < len(path) && path[i] == '/' {
i++
}
j := i
for j < len(path) && path[j] != '/' {
j++
}
s := path[i:j]
i = j
switch s {
case "":
continue
case ".":
continue
case "..":
if !lookupParent {
k := len(buf)
for k > 0 && buf[k-1] != '/' {
k--
}
for k > 1 && buf[k-1] == '/' {
k--
}
buf = buf[:k]
if k == 0 {
lookupParent = true
} else {
s = ""
continue
}
}
default:
lookupParent = false
}
if len(buf) > 0 && buf[len(buf)-1] != '/' {
buf = append(buf, '/')
}
buf = append(buf, s...)
}
return buf, lookupParent
}
// joinPath concatenates dir and file paths, producing a cleaned path where
// "." and ".." have been removed, unless dir is relative and the references
// to parent directories in file represented a location relative to a parent
// of dir.
//
// This function is used for path resolution of all wasi functions expecting
// a path argument; the returned string is heap allocated, which we may want
// to optimize in the future. Instead of returning a string, the function
// could append the result to an output buffer that the functions in this
// file can manage to have allocated on the stack (e.g. initializing to a
// fixed capacity). Since it will significantly increase code complexity,
// we prefer to optimize for readability and maintainability at this time.
func joinPath(dir, file string) string {
buf := make([]byte, 0, len(dir)+len(file)+1)
if isAbs(dir) {
buf = append(buf, '/')
}
buf, lookupParent := appendCleanPath(buf, dir, true)
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
}
// findPreopenForPath finds which preopen it relates to and return that descriptor/root and the path relative to that directory descriptor/root
func findPreopenForPath(path string) (wasiDir, string) {
dir := "/"
var wasidir wasiDir
if !isAbs(path) {
dir = libcCWD.root
wasidir = libcCWD
if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" {
path = libcCWD.rel + "/" + path
}
}
path = joinPath(dir, path)
var best string
for k, v := range wasiPreopens {
if len(k) > len(best) && hasPrefix(path, k) {
wasidir = wasiDir{d: v, root: k}
best = wasidir.root
}
}
if hasPrefix(path, wasidir.root) {
path = path[len(wasidir.root):]
}
for isAbs(path) {
path = path[1:]
}
if len(path) == 0 {
path = "."
}
return wasidir, path
}
// -- END fs_wasip1.go --
// int open(const char *pathname, int flags, mode_t mode);
//
//export open
func open(pathname *byte, flags int32, mode uint32) int32 {
path := goString(pathname)
dir, relPath := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
var dflags types.DescriptorFlags
if (flags & O_RDONLY) == O_RDONLY {
dflags |= types.DescriptorFlagsRead
}
if (flags & O_WRONLY) == O_WRONLY {
dflags |= types.DescriptorFlagsWrite
}
var oflags types.OpenFlags
if flags&O_CREAT == O_CREAT {
oflags |= types.OpenFlagsCreate
}
if flags&O_DIRECTORY == O_DIRECTORY {
oflags |= types.OpenFlagsDirectory
}
if flags&O_EXCL == O_EXCL {
oflags |= types.OpenFlagsExclusive
}
if flags&O_TRUNC == O_TRUNC {
oflags |= types.OpenFlagsTruncate
}
// By default, follow symlinks for open() unless O_NOFOLLOW was passed
var pflags types.PathFlags = types.PathFlagsSymlinkFollow
if flags&O_NOFOLLOW == O_NOFOLLOW {
// O_NOFOLLOW was passed, so turn off SymlinkFollow
pflags &^= types.PathFlagsSymlinkFollow
}
descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream := wasiFile{
d: descriptor,
oflag: flags,
refs: 1,
}
if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) {
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream.offset = int64(stat.Size)
}
libcfd := findFreeFD()
wasiFiles[libcfd] = &stream
return int32(libcfd)
}
func errorCodeToErrno(err types.ErrorCode) Errno {
switch err {
case types.ErrorCodeAccess:
return EACCES
case types.ErrorCodeWouldBlock:
return EAGAIN
case types.ErrorCodeAlready:
return EALREADY
case types.ErrorCodeBadDescriptor:
return EBADF
case types.ErrorCodeBusy:
return EBUSY
case types.ErrorCodeDeadlock:
return EDEADLK
case types.ErrorCodeQuota:
return EDQUOT
case types.ErrorCodeExist:
return EEXIST
case types.ErrorCodeFileTooLarge:
return EFBIG
case types.ErrorCodeIllegalByteSequence:
return EILSEQ
case types.ErrorCodeInProgress:
return EINPROGRESS
case types.ErrorCodeInterrupted:
return EINTR
case types.ErrorCodeInvalid:
return EINVAL
case types.ErrorCodeIO:
return EIO
case types.ErrorCodeIsDirectory:
return EISDIR
case types.ErrorCodeLoop:
return ELOOP
case types.ErrorCodeTooManyLinks:
return EMLINK
case types.ErrorCodeMessageSize:
return EMSGSIZE
case types.ErrorCodeNameTooLong:
return ENAMETOOLONG
case types.ErrorCodeNoDevice:
return ENODEV
case types.ErrorCodeNoEntry:
return ENOENT
case types.ErrorCodeNoLock:
return ENOLCK
case types.ErrorCodeInsufficientMemory:
return ENOMEM
case types.ErrorCodeInsufficientSpace:
return ENOSPC
case types.ErrorCodeNotDirectory:
return ENOTDIR
case types.ErrorCodeNotEmpty:
return ENOTEMPTY
case types.ErrorCodeNotRecoverable:
return ENOTRECOVERABLE
case types.ErrorCodeUnsupported:
return ENOSYS
case types.ErrorCodeNoTTY:
return ENOTTY
case types.ErrorCodeNoSuchDevice:
return ENXIO
case types.ErrorCodeOverflow:
return EOVERFLOW
case types.ErrorCodeNotPermitted:
return EPERM
case types.ErrorCodePipe:
return EPIPE
case types.ErrorCodeReadOnly:
return EROFS
case types.ErrorCodeInvalidSeek:
return ESPIPE
case types.ErrorCodeTextFileBusy:
return ETXTBSY
case types.ErrorCodeCrossDevice:
return EXDEV
}
return Errno(err)
}
type libc_DIR struct {
d types.DirectoryEntryStream
}
// DIR *fdopendir(int);
//
//export fdopendir
func fdopendir(fd int32) unsafe.Pointer {
if _, ok := wasiStreams[fd]; ok {
libcErrno = EBADF
return nil
}
stream, ok := wasiFiles[fd]
if !ok {
libcErrno = EBADF
return nil
}
if stream.d == cm.ResourceNone {
libcErrno = EBADF
return nil
}
dir, err, isErr := stream.d.ReadDirectory().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return nil
}
return unsafe.Pointer(&libc_DIR{d: dir})
}
// int fdclosedir(DIR *);
//
//export fdclosedir
func fdclosedir(dirp unsafe.Pointer) int32 {
if dirp == nil {
return 0
}
dir := (*libc_DIR)(dirp)
if dir.d == cm.ResourceNone {
return 0
}
dir.d.ResourceDrop()
dir.d = cm.ResourceNone
return 0
}
// struct dirent *readdir(DIR *);
//
//export readdir
func readdir(dirp unsafe.Pointer) *Dirent {
if dirp == nil {
return nil
}
dir := (*libc_DIR)(dirp)
if dir.d == cm.ResourceNone {
return nil
}
someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return nil
}
entry := someEntry.Some()
if entry == nil {
libcErrno = 0
return nil
}
// 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[];
// };
buf := make([]byte, unsafe.Sizeof(Dirent{})+uintptr(len(entry.Name)))
dirent := (*Dirent)((unsafe.Pointer)(&buf[0]))
// No inodes in wasi
dirent.Ino = 0
dirent.Type = p2fileTypeToDirentType(entry.Type)
copy(buf[unsafe.Offsetof(dirent.Type)+1:], entry.Name)
return dirent
}
func p2fileTypeToDirentType(t types.DescriptorType) uint8 {
switch t {
case types.DescriptorTypeUnknown:
return DT_UNKNOWN
case types.DescriptorTypeBlockDevice:
return DT_BLK
case types.DescriptorTypeCharacterDevice:
return DT_CHR
case types.DescriptorTypeDirectory:
return DT_DIR
case types.DescriptorTypeFIFO:
return DT_FIFO
case types.DescriptorTypeSymbolicLink:
return DT_LNK
case types.DescriptorTypeRegularFile:
return DT_REG
case types.DescriptorTypeSocket:
return DT_FIFO
}
return DT_UNKNOWN
}
func p2fileTypeToStatType(t types.DescriptorType) uint32 {
switch t {
case types.DescriptorTypeUnknown:
return 0
case types.DescriptorTypeBlockDevice:
return S_IFBLK
case types.DescriptorTypeCharacterDevice:
return S_IFCHR
case types.DescriptorTypeDirectory:
return S_IFDIR
case types.DescriptorTypeFIFO:
return S_IFIFO
case types.DescriptorTypeSymbolicLink:
return S_IFLNK
case types.DescriptorTypeRegularFile:
return S_IFREG
case types.DescriptorTypeSocket:
return S_IFSOCK
}
return 0
}
// void arc4random_buf (void *, size_t);
//
//export arc4random_buf
func arc4random_buf(p unsafe.Pointer, l uint) {
result := random.GetRandomBytes(uint64(l))
s := result.Slice()
memcpy(unsafe.Pointer(p), unsafe.Pointer(unsafe.SliceData(s)), uintptr(l))
}
// int chdir(char *name)
//
//export chdir
func chdir(name *byte) int {
path := goString(name) + "/"
if !isAbs(path) {
path = joinPath(libcCWD.root+"/"+libcCWD.rel+"/", path)
}
if path == "." {
return 0
}
dir, rel := findPreopenForPath(path)
if dir.d == cm.ResourceNone {
libcErrno = EACCES
return -1
}
_, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
libcCWD = dir
// keep the same cwd base but update "rel" to point to new base path
libcCWD.rel = rel
return 0
}
// char *getcwd(char *buf, size_t size)
//
//export getcwd
func getcwd(buf *byte, size uint) *byte {
cwd := libcCWD.root
if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" {
cwd += libcCWD.rel
}
if buf == nil {
b := make([]byte, len(cwd)+1)
buf = unsafe.SliceData(b)
} else if size == 0 {
libcErrno = EINVAL
return nil
}
if size < uint(len(cwd)+1) {
libcErrno = ERANGE
return nil
}
s := unsafe.Slice(buf, size)
s[size-1] = 0 // Enforce NULL termination
copy(s, cwd)
return buf
}
// int truncate(const char *path, off_t length);
//
//export truncate
func truncate(path *byte, length int64) int32 {
libcErrno = ENOSYS
return -1
}