mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 03:53:42 +00:00
289fceb3ea
* Move environment functions to their own files.
* Rewrite the WASIp2 version of environment variables to be much
simpler (don't go through C functions).
1308 lines
27 KiB
Go
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
|
|
}
|