From 2593d494c936040144e45deb797aee246afcee74 Mon Sep 17 00:00:00 2001 From: George Jenkins Date: Mon, 20 Jan 2025 11:33:16 -0800 Subject: [PATCH] A buncha stuff that happened next --- compiler/compiler.go | 12 +- go.mod | 2 +- src/os/byteorder.go | 149 +++++ src/os/dir_wasi.go | 225 +++++++- src/os/dirent_wasip1.go | 52 ++ src/runtime/poll.go | 10 + src/syscall/byteorder.go | 149 +++++ src/syscall/dirent.go | 100 ++++ src/syscall/fs_wasip1.go | 948 +++++++++++++++++++++++++++++++ src/syscall/net_fake.go | 64 +++ src/syscall/net_wasip1.go | 86 +++ src/syscall/syscall.go | 11 - src/syscall/syscall_libc_wasi.go | 56 +- src/syscall/syscall_unix.go | 11 + src/syscall/syscall_wasip1.go | 492 ++++++++++++++++ 15 files changed, 2303 insertions(+), 64 deletions(-) create mode 100644 src/os/byteorder.go create mode 100644 src/os/dirent_wasip1.go create mode 100644 src/syscall/byteorder.go create mode 100644 src/syscall/dirent.go create mode 100644 src/syscall/fs_wasip1.go create mode 100644 src/syscall/net_fake.go create mode 100644 src/syscall/net_wasip1.go create mode 100644 src/syscall/syscall_wasip1.go diff --git a/compiler/compiler.go b/compiler/compiler.go index 28ec312dd0..c5e1362a86 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -464,8 +464,13 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { members[i] = c.getLLVMType(typ.At(i).Type()) } return c.ctx.StructType(members, false) + //case *types.Alias: + // if typ.Obj().Name() == "any" && typ.Obj().Parent() == types.Universe { + // return c.getLLVMRuntimeType("_interface") + // } + // panic("unknown object type: " + goType.String()) default: - panic("unknown type: " + goType.String()) + panic(fmt.Sprintf("unknown type: %s %T", goType.String(), typ)) } } @@ -659,6 +664,11 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { return md case *types.TypeParam: return c.getDIType(typ.Underlying()) + //case *types.Alias: + // if typ.Obj().Name() == "any" && typ.Obj().Parent() == types.Universe { + // return c.getDIType(c.program.ImportedPackage("runtime").Members["_interface"].(*ssa.Type).Type()) + // } + // panic("unknown object while generating DWARF debug type: " + typ.String()) default: panic("unknown type while generating DWARF debug type: " + typ.String()) } diff --git a/go.mod b/go.mod index 2031d35e4d..a4de141365 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tinygo-org/tinygo -go 1.23 +go 1.19 require ( github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c diff --git a/src/os/byteorder.go b/src/os/byteorder.go new file mode 100644 index 0000000000..555f1984e2 --- /dev/null +++ b/src/os/byteorder.go @@ -0,0 +1,149 @@ +// Copyright 2024 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 byteorder provides functions for decoding and encoding +// little and big endian integer types from/to byte slices. +package os + +func LEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[0]) | uint16(b[1])<<8 +} + +func LEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func LEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +func LEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func LEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func LEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + ) +} + +func LEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func LEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func LEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56), + ) +} + +func BEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[1]) | uint16(b[0])<<8 +} + +func BEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func BEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v>>8), + byte(v), + ) +} + +func BEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func BEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func BEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func BEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func BEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func BEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v>>56), + byte(v>>48), + byte(v>>40), + byte(v>>32), + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go index 6d9313110e..e2725c7325 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasi.go @@ -11,60 +11,131 @@ package os import ( + "internal/goarch" "io" "runtime" + "sync" "syscall" "unsafe" ) -// opaque DIR* returned by fdopendir -// -// We add an unused field so it is not the empty struct, which is usually -// a special case in Go. -type dirInfo struct{ _ int32 } +//// opaque DIR* returned by fdopendir +//// +//// We add an unused field so it is not the empty struct, which is usually +//// a special case in Go. +//type dirInfo struct{ _ int32 } + +// Auxiliary information if the File describes a directory +type dirInfo struct { + mu sync.Mutex + buf *[]byte // buffer for directory I/O + nbuf int // length of buf; return value from Getdirentries + bufp int // location of next record in buf. +} + +const ( + // More than 5760 to work around https://golang.org/issue/24015. + blockSize = 8192 +) + +var dirBufPool = sync.Pool{ + New: func() any { + // The buffer must be at least a block long. + buf := make([]byte, blockSize) + return &buf + }, +} func (d *dirInfo) close() { syscall.Fdclosedir(uintptr(unsafe.Pointer(d))) } func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { - if f.dirinfo == nil { - dir, errno := syscall.Fdopendir(syscallFd(f.handle.(unixFileHandle))) - if errno != nil { - return nil, nil, nil, &PathError{Op: "fdopendir", Path: f.name, Err: errno} - } - f.dirinfo = (*dirInfo)(unsafe.Pointer(dir)) + // If this file has no dirInfo, create one. + d := f.dirinfo + if d == nil { + d = new(dirInfo) + f.dirinfo = d + } + d.mu.Lock() + defer d.mu.Unlock() + if d.buf == nil { + d.buf = dirBufPool.Get().(*[]byte) } - d := uintptr(unsafe.Pointer(f.dirinfo)) - // see src/os/dir_unix.go + // Change the meaning of n for the implementation below. + // + // The n above was for the public interface of "if n <= 0, + // Readdir returns all the FileInfo from the directory in a + // single slice". + // + // But below, we use only negative to mean looping until the + // end and positive to mean bounded, with positive + // terminating at 0. if n == 0 { n = -1 } for n != 0 { - dirent, errno := syscall.Readdir(d) - if errno != nil { - if errno == syscall.EINTR { - continue + // Refill the buffer if necessary + if d.bufp >= d.nbuf { + d.bufp = 0 + var errno error + d.nbuf, errno = f.pfd.ReadDirent(*d.buf) + runtime.KeepAlive(f) + if errno != nil { + return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno} + } + if d.nbuf <= 0 { + // Optimization: we can return the buffer to the pool, there is nothing else to read. + dirBufPool.Put(d.buf) + d.buf = nil + break // EOF } - return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} } - if dirent == nil { // EOF + + // Drain the buffer + buf := (*d.buf)[d.bufp:d.nbuf] + reclen, ok := direntReclen(buf) + if !ok || reclen > uint64(len(buf)) { + break + } + rec := buf[:reclen] + d.bufp += int(reclen) + ino, ok := direntIno(rec) + if !ok { + break + } + // When building to wasip1, the host runtime might be running on Windows + // or might expose a remote file system which does not have the concept + // of inodes. Therefore, we cannot make the assumption that it is safe + // to skip entries with zero inodes. + if ino == 0 && runtime.GOOS != "wasip1" { + continue + } + const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) + namlen, ok := direntNamlen(rec) + if !ok || namoff+namlen > uint64(len(rec)) { break } - name := dirent.Name() + name := rec[namoff : namoff+namlen] + for i, c := range name { + if c == 0 { + name = name[:i] + break + } + } // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { continue } - if n > 0 { + if n > 0 { // see 'n == 0' comment above n-- } if mode == readdirName { names = append(names, string(name)) } else if mode == readdirDirEntry { - de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type)) + de, err := newUnixDirent(f.name, string(name), direntType(rec)) if IsNotExist(err) { // File disappeared between readdir and stat. // Treat as if it didn't exist. @@ -86,7 +157,6 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn } infos = append(infos, info) } - runtime.KeepAlive(f) } if n > 0 && len(names)+len(dirents)+len(infos) == 0 { @@ -95,6 +165,74 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn return names, dirents, infos, nil } +//func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { +// if f.dirinfo == nil { +// dir, errno := syscall.Fdopendir(syscallFd(f.handle.(unixFileHandle))) +// if errno != nil { +// return nil, nil, nil, &PathError{Op: "fdopendir", Path: f.name, Err: errno} +// } +// f.dirinfo = (*dirInfo)(unsafe.Pointer(dir)) +// } +// d := uintptr(unsafe.Pointer(f.dirinfo)) +// +// // see src/os/dir_unix.go +// if n == 0 { +// n = -1 +// } +// +// for n != 0 { +// dirent, errno := syscall.Readdir(d) +// if errno != nil { +// if errno == syscall.EINTR { +// continue +// } +// return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} +// } +// if dirent == nil { // EOF +// break +// } +// name := dirent.Name() +// // Check for useless names before allocating a string. +// if string(name) == "." || string(name) == ".." { +// continue +// } +// if n > 0 { +// n-- +// } +// if mode == readdirName { +// names = append(names, string(name)) +// } else if mode == readdirDirEntry { +// de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type)) +// if IsNotExist(err) { +// // File disappeared between readdir and stat. +// // Treat as if it didn't exist. +// continue +// } +// if err != nil { +// return nil, dirents, nil, err +// } +// dirents = append(dirents, de) +// } else { +// info, err := lstat(f.name + "/" + string(name)) +// if IsNotExist(err) { +// // File disappeared between readdir + stat. +// // Treat as if it didn't exist. +// continue +// } +// if err != nil { +// return nil, nil, infos, err +// } +// infos = append(infos, info) +// } +// runtime.KeepAlive(f) +// } +// +// if n > 0 && len(names)+len(dirents)+len(infos) == 0 { +// return nil, nil, nil, io.EOF +// } +// return names, dirents, infos, nil +//} + func dtToType(typ uint8) FileMode { switch typ { case syscall.DT_BLK: @@ -112,3 +250,44 @@ func dtToType(typ uint8) FileMode { } return ^FileMode(0) } + +// readInt returns the size-bytes unsigned integer in native byte order at offset off. +func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { + if len(b) < int(off+size) { + return 0, false + } + if goarch.BigEndian { + return readIntBE(b[off:], size), true + } + return readIntLE(b[off:], size), true +} + +func readIntBE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + return uint64(BEUint16(b)) + case 4: + return uint64(BEUint32(b)) + case 8: + return uint64(BEUint64(b)) + default: + panic("syscall: readInt with unsupported size") + } +} + +func readIntLE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + return uint64(LEUint16(b)) + case 4: + return uint64(LEUint32(b)) + case 8: + return uint64(LEUint64(b)) + default: + panic("syscall: readInt with unsupported size") + } +} diff --git a/src/os/dirent_wasip1.go b/src/os/dirent_wasip1.go new file mode 100644 index 0000000000..d3f10b2aeb --- /dev/null +++ b/src/os/dirent_wasip1.go @@ -0,0 +1,52 @@ +// 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 os + +import ( + "syscall" + "unsafe" +) + +// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-dirent-record +const sizeOfDirent = 24 + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) +} + +func direntReclen(buf []byte) (uint64, bool) { + namelen, ok := direntNamlen(buf) + return sizeOfDirent + namelen, ok +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen)) +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + switch syscall.Filetype(buf[off]) { + case syscall.FILETYPE_BLOCK_DEVICE: + return ModeDevice + case syscall.FILETYPE_CHARACTER_DEVICE: + return ModeDevice | ModeCharDevice + case syscall.FILETYPE_DIRECTORY: + return ModeDir + case syscall.FILETYPE_REGULAR_FILE: + return 0 + case syscall.FILETYPE_SOCKET_DGRAM: + return ModeSocket + case syscall.FILETYPE_SOCKET_STREAM: + return ModeSocket + case syscall.FILETYPE_SYMBOLIC_LINK: + return ModeSymlink + } + return ^FileMode(0) // unknown +} diff --git a/src/runtime/poll.go b/src/runtime/poll.go index 2713bbea7e..c79aec09a2 100644 --- a/src/runtime/poll.go +++ b/src/runtime/poll.go @@ -21,3 +21,13 @@ func poll_runtime_pollClose(ctx uintptr) { func poll_runtime_pollUnblock(ctx uintptr) { panic("todo: runtime_pollUnblock") } + +//go:linkname poll_runtime_pollReset internal/poll.runtime_pollReset +func poll_runtime_pollReset(ctx uintptr) { + panic("todo: runtime_pollReset") +} + +//go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait +func poll_runtime_pollWait(ctx uintptr) { + panic("todo: runtime_pollWait") +} diff --git a/src/syscall/byteorder.go b/src/syscall/byteorder.go new file mode 100644 index 0000000000..78c7fb3c47 --- /dev/null +++ b/src/syscall/byteorder.go @@ -0,0 +1,149 @@ +// Copyright 2024 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 byteorder provides functions for decoding and encoding +// little and big endian integer types from/to byte slices. +package syscall + +func LEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[0]) | uint16(b[1])<<8 +} + +func LEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +func LEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +func LEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func LEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) +} + +func LEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + ) +} + +func LEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} + +func LEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) +} + +func LEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56), + ) +} + +func BEUint16(b []byte) uint16 { + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint16(b[1]) | uint16(b[0])<<8 +} + +func BEPutUint16(b []byte, v uint16) { + _ = b[1] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 8) + b[1] = byte(v) +} + +func BEAppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v>>8), + byte(v), + ) +} + +func BEUint32(b []byte) uint32 { + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24 +} + +func BEPutUint32(b []byte, v uint32) { + _ = b[3] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) +} + +func BEAppendUint32(b []byte, v uint32) []byte { + return append(b, + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} + +func BEUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func BEPutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +func BEAppendUint64(b []byte, v uint64) []byte { + return append(b, + byte(v>>56), + byte(v>>48), + byte(v>>40), + byte(v>>32), + byte(v>>24), + byte(v>>16), + byte(v>>8), + byte(v), + ) +} diff --git a/src/syscall/dirent.go b/src/syscall/dirent.go new file mode 100644 index 0000000000..4a4e579d21 --- /dev/null +++ b/src/syscall/dirent.go @@ -0,0 +1,100 @@ +// Copyright 2009 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 unix || (js && wasm) || wasip1 + +package syscall + +import ( + "internal/goarch" + "runtime" + "unsafe" +) + +// readInt returns the size-bytes unsigned integer in native byte order at offset off. +func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { + if len(b) < int(off+size) { + return 0, false + } + if goarch.BigEndian { + return readIntBE(b[off:], size), true + } + return readIntLE(b[off:], size), true +} + +func readIntBE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + return uint64(BEUint16(b)) + case 4: + return uint64(BEUint32(b)) + case 8: + return uint64(BEUint64(b)) + default: + panic("syscall: readInt with unsupported size") + } +} + +func readIntLE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + return uint64(LEUint16(b)) + case 4: + return uint64(LEUint32(b)) + case 8: + return uint64(LEUint64(b)) + default: + panic("syscall: readInt with unsupported size") + } +} + +// ParseDirent parses up to max directory entries in buf, +// appending the names to names. It returns the number of +// bytes consumed from buf, the number of entries added +// to names, and the new names slice. +func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) { + origlen := len(buf) + count = 0 + for max != 0 && len(buf) > 0 { + reclen, ok := direntReclen(buf) + if !ok || reclen > uint64(len(buf)) { + return origlen, count, names + } + rec := buf[:reclen] + buf = buf[reclen:] + ino, ok := direntIno(rec) + if !ok { + break + } + // See src/os/dir_unix.go for the reason why this condition is + // excluded on wasip1. + if ino == 0 && runtime.GOOS != "wasip1" { // File absent in directory. + continue + } + const namoff = uint64(unsafe.Offsetof(Dirent{}.Name)) + namlen, ok := direntNamlen(rec) + if !ok || namoff+namlen > uint64(len(rec)) { + break + } + name := rec[namoff : namoff+namlen] + for i, c := range name { + if c == 0 { + name = name[:i] + break + } + } + // Check for useless names before allocating a string. + if string(name) == "." || string(name) == ".." { + continue + } + max-- + count++ + names = append(names, string(name)) + } + return origlen - len(buf), count, names +} diff --git a/src/syscall/fs_wasip1.go b/src/syscall/fs_wasip1.go new file mode 100644 index 0000000000..2ef9bc7107 --- /dev/null +++ b/src/syscall/fs_wasip1.go @@ -0,0 +1,948 @@ +// 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/stringslite" + "structs" + "unsafe" +) + +func init() { + // Try to set stdio to non-blocking mode before the os package + // calls NewFile for each fd. NewFile queries the non-blocking flag + // but doesn't change it, even if the runtime supports non-blocking + // stdio. Since WebAssembly modules are single-threaded, blocking + // system calls temporarily halt execution of the module. If the + // runtime supports non-blocking stdio, the Go runtime is able to + // use the WASI net poller to poll for read/write readiness and is + // able to schedule goroutines while waiting. + SetNonblock(0, true) + SetNonblock(1, true) + SetNonblock(2, true) +} + +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 { + _ structs.HostLayout + 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) + + // Some runtimes have very strict expectations when it comes to which + // rights can be enabled on files opened by path_open. The fileRights + // constant is used as a mask to retain only bits for operations that + // are supported on files. + fileRights rights = RIGHT_FD_DATASYNC | + 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 + + // Runtimes like wasmtime and wasmedge will refuse to open directories + // if the rights requested by the application exceed the operations that + // can be performed on a directory. + dirRights rights = RIGHT_FD_SEEK | + RIGHT_FDSTAT_SET_FLAGS | + RIGHT_FD_SYNC | + 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_TIMES | + RIGHT_PATH_SYMLINK | + RIGHT_PATH_REMOVE_DIRECTORY | + RIGHT_PATH_UNLINK_FILE +) + +// 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 unsafe.Pointer) 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 unsafe.Pointer) 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 + +// https://github.com/WebAssembly/WASI/blob/a2b96e81c0586125cc4dc79a5be0b78d9a059925/legacy/preview1/docs.md#-fdstat-record +// fdflags must be at offset 2, hence the uint16 type rather than the +// fdflags (uint32) type. +type fdstat struct { + _ structs.HostLayout + filetype filetype + fdflags uint16 + rightsBase rights + rightsInheriting rights +} + +//go:wasmimport wasi_snapshot_preview1 fd_fdstat_get +//go:noescape +func fd_fdstat_get(fd int32, buf *fdstat) Errno + +//go:wasmimport wasi_snapshot_preview1 fd_fdstat_set_flags +//go:noescape +func fd_fdstat_set_flags(fd int32, flags fdflags) Errno + +// fd_fdstat_get_flags is accessed from internal/syscall/unix +//go:linkname fd_fdstat_get_flags + +func fd_fdstat_get_flags(fd int) (uint32, error) { + var stat fdstat + errno := fd_fdstat_get(int32(fd), &stat) + return uint32(stat.fdflags), errnoErr(errno) +} + +// fd_fdstat_get_type is accessed from net +//go:linkname fd_fdstat_get_type + +func fd_fdstat_get_type(fd int) (uint8, error) { + var stat fdstat + errno := fd_fdstat_get(int32(fd), &stat) + return stat.filetype, errnoErr(errno) +} + +type preopentype = uint8 + +const ( + preopentypeDir preopentype = iota +) + +type prestatDir struct { + _ structs.HostLayout + prNameLen size +} + +type prestat struct { + _ structs.HostLayout + 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 == ENOTDIR || prestat.typ != preopentypeDir { + continue + } + if errno != 0 { + panic("fd_prestat: " + errno.Error()) + } + 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 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, 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 stringslite.HasPrefix(path, "/") +} + +func isDir(path string) bool { + return stringslite.HasSuffix(path, "/") +} + +// 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) && stringslite.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) +// return openat(dirFd, pathPtr, pathLen, openmode, perm) +//} + +//func Openat(dirFd int, path string, openmode int, perm uint32) (int, error) { +// return openat(int32(dirFd), unsafe.StringData(path), size(len(path)), openmode, perm) +//} + +//func openat(dirFd int32, pathPtr *byte, pathLen size, openmode int, perm uint32) (int, error) { +// 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 +// } +// +// var rights rights +// switch openmode & (O_RDONLY | O_WRONLY | O_RDWR) { +// case O_RDONLY: +// rights = fileRights & ^writeRights +// case O_WRONLY: +// rights = fileRights & ^readRights +// case O_RDWR: +// rights = fileRights +// } +// +// if (openmode & O_DIRECTORY) != 0 { +// if openmode&(O_WRONLY|O_RDWR) != 0 { +// return -1, EISDIR +// } +// oflags |= OFLAG_DIRECTORY +// rights &= dirRights +// } +// +// var fdflags fdflags +// if (openmode & O_APPEND) != 0 { +// fdflags |= FDFLAG_APPEND +// } +// if (openmode & O_SYNC) != 0 { +// fdflags |= FDFLAG_SYNC +// } +// +// var lflags lookupflags +// if openmode&O_NOFOLLOW == 0 { +// lflags = LOOKUP_SYMLINK_FOLLOW +// } +// +// var fd int32 +// errno := path_open( +// dirFd, +// lflags, +// pathPtr, +// pathLen, +// oflags, +// rights, +// fileRights, +// fdflags, +// &fd, +// ) +// if errno == EISDIR && oflags == 0 && fdflags == 0 && ((rights & writeRights) == 0) { +// // wasmtime and wasmedge will error if attempting to open a directory +// // because we are asking for too many rights. However, we cannot +// // determine ahead of time if the path we are about to open is a +// // directory, so instead we fallback to a second call to path_open with +// // a more limited set of rights. +// // +// // This approach is subject to a race if the file system is modified +// // concurrently, so we also inject OFLAG_DIRECTORY to ensure that we do +// // not accidentally open a file which is not a directory. +// errno = path_open( +// dirFd, +// LOOKUP_SYMLINK_FOLLOW, +// pathPtr, +// pathLen, +// oflags|OFLAG_DIRECTORY, +// rights&dirRights, +// fileRights, +// 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, unsafe.Pointer(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, unsafe.Pointer(st)) +// setDefaultMode(st) +// return errnoErr(errno) +//} + +//func Fstat(fd int, st *Stat_t) error { +// errno := fd_filestat_get(int32(fd), unsafe.Pointer(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 { +// // UTIME_OMIT value must match internal/syscall/unix/at_wasip1.go +// const UTIME_OMIT = -0x2 +// if path == "" { +// return EINVAL +// } +// dirFd, pathPtr, pathLen := preparePath(path) +// atime := TimespecToNsec(ts[0]) +// mtime := TimespecToNsec(ts[1]) +// if ts[0].Nsec == UTIME_OMIT || ts[1].Nsec == UTIME_OMIT { +// var st Stat_t +// if err := Stat(path, &st); err != nil { +// return err +// } +// if ts[0].Nsec == UTIME_OMIT { +// atime = int64(st.Atime) +// } +// if ts[1].Nsec == UTIME_OMIT { +// mtime = int64(st.Mtime) +// } +// } +// errno := path_filestat_set_times( +// dirFd, +// LOOKUP_SYMLINK_FOLLOW, +// pathPtr, +// pathLen, +// timestamp(atime), +// timestamp(mtime), +// 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, unsafe.Pointer(&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/net_fake.go b/src/syscall/net_fake.go new file mode 100644 index 0000000000..26452bd8ff --- /dev/null +++ b/src/syscall/net_fake.go @@ -0,0 +1,64 @@ +// 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. + +// Fake networking for js/wasm and wasip1/wasm. + +//go:build js || 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 ( + SOMAXCONN = 0x80 +) + +const ( + _ = iota + IPV6_V6ONLY + SO_ERROR +) + +// Misc constants expected by package net but not supported. +const ( + _ = iota + F_DUPFD_CLOEXEC +) + +type Sockaddr any + +type SockaddrInet4 struct { + Port int + Addr [4]byte +} + +type SockaddrInet6 struct { + Port int + ZoneId uint32 + Addr [16]byte +} + +type SockaddrUnix struct { + Name string +} diff --git a/src/syscall/net_wasip1.go b/src/syscall/net_wasip1.go new file mode 100644 index 0000000000..fefd939a0a --- /dev/null +++ b/src/syscall/net_wasip1.go @@ -0,0 +1,86 @@ +// 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 + +const ( + SHUT_RD = 0x1 + SHUT_WR = 0x2 + SHUT_RDWR = SHUT_RD | SHUT_WR +) + +type sdflags = uint32 + +//go:wasmimport wasi_snapshot_preview1 sock_accept +//go:noescape +func sock_accept(fd int32, flags fdflags, newfd *int32) Errno + +//go:wasmimport wasi_snapshot_preview1 sock_shutdown +//go:noescape +func sock_shutdown(fd int32, flags sdflags) Errno + +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) (int, Sockaddr, error) { + var newfd int32 + errno := sock_accept(int32(fd), 0, &newfd) + return int(newfd), nil, errnoErr(errno) +} + +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 { + errno := sock_shutdown(int32(fd), sdflags(how)) + return errnoErr(errno) +} diff --git a/src/syscall/syscall.go b/src/syscall/syscall.go index f22289c5ad..0407b37093 100644 --- a/src/syscall/syscall.go +++ b/src/syscall/syscall.go @@ -4,19 +4,8 @@ import ( "errors" ) -const ( - MSG_DONTWAIT = 0x40 - AF_INET = 0x2 - AF_INET6 = 0xa -) - func Exit(code int) -type Rlimit struct { - Cur uint64 - Max uint64 -} - func Setrlimit(resource int, rlim *Rlimit) error { return errors.New("Setrlimit not implemented") } diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index bbf81cd059..169acc7862 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -230,7 +230,7 @@ const ( // // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h type Timespec struct { - Sec int32 + Sec int64 Nsec int64 } @@ -301,34 +301,34 @@ const ( DT_UNKNOWN = __WASI_FILETYPE_UNKNOWN ) -// Dirent is returned by pointer from Readdir to iterate over directory entries. +//// 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 +//} // -// 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 (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)) diff --git a/src/syscall/syscall_unix.go b/src/syscall/syscall_unix.go index b5b8f4eb78..d6902b529f 100644 --- a/src/syscall/syscall_unix.go +++ b/src/syscall/syscall_unix.go @@ -2,6 +2,17 @@ package syscall +const ( + MSG_DONTWAIT = 0x40 + AF_INET = 0x2 + AF_INET6 = 0xa +) + +type Rlimit struct { + Cur uint64 + Max uint64 +} + func Exec(argv0 string, argv []string, envv []string) (err error) // The two SockaddrInet* structs have been copied from the Go source tree. diff --git a/src/syscall/syscall_wasip1.go b/src/syscall/syscall_wasip1.go new file mode 100644 index 0000000000..3e1f90a4cc --- /dev/null +++ b/src/syscall/syscall_wasip1.go @@ -0,0 +1,492 @@ +// 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 ( + "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 +// case errors.ErrUnsupported: +// return e == ENOSYS +// } +// 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_DIRECTORY = 020000 +// O_NOFOLLOW = 0400 +// +// 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 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 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 + +func SetNonblock(fd int, nonblocking bool) error { + flags, err := fd_fdstat_get_flags(fd) + if err != nil { + return err + } + if nonblocking { + flags |= FDFLAG_NONBLOCK + } else { + flags &^= FDFLAG_NONBLOCK + } + errno := fd_fdstat_set_flags(int32(fd), flags) + return errnoErr(errno) +} + +type Rlimit struct { + Cur uint64 + Max uint64 +} + +const ( + RLIMIT_NOFILE = iota +) + +func Getrlimit(which int, lim *Rlimit) error { + return ENOSYS +}