mirror of https://go.googlesource.com/go
933 lines
24 KiB
Go
933 lines
24 KiB
Go
// 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"
|
|
"runtime"
|
|
"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 {
|
|
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 unsafe.Pointer, iovsLen size, offset filesize, nread unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_pwrite
|
|
//go:noescape
|
|
func fd_pwrite(fd int32, iovs unsafe.Pointer, iovsLen size, offset filesize, nwritten unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_read
|
|
//go:noescape
|
|
func fd_read(fd int32, iovs unsafe.Pointer, iovsLen size, nread unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_readdir
|
|
//go:noescape
|
|
func fd_readdir(fd int32, buf unsafe.Pointer, bufLen size, cookie dircookie, nwritten unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_seek
|
|
//go:noescape
|
|
func fd_seek(fd int32, offset filedelta, whence uint32, newoffset unsafe.Pointer) 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 unsafe.Pointer, iovsLen size, nwritten unsafe.Pointer) 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 unsafe.Pointer, pathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_filestat_get
|
|
//go:noescape
|
|
func path_filestat_get(fd int32, flags lookupflags, path unsafe.Pointer, 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 unsafe.Pointer, 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 unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_readlink
|
|
//go:noescape
|
|
func path_readlink(fd int32, path unsafe.Pointer, pathLen size, buf unsafe.Pointer, bufLen size, nwritten unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_remove_directory
|
|
//go:noescape
|
|
func path_remove_directory(fd int32, path unsafe.Pointer, pathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_rename
|
|
//go:noescape
|
|
func path_rename(oldFd int32, oldPath unsafe.Pointer, oldPathLen size, newFd int32, newPath unsafe.Pointer, newPathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_symlink
|
|
//go:noescape
|
|
func path_symlink(oldPath unsafe.Pointer, oldPathLen size, fd int32, newPath unsafe.Pointer, newPathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_unlink_file
|
|
//go:noescape
|
|
func path_unlink_file(fd int32, path unsafe.Pointer, pathLen size) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 path_open
|
|
//go:noescape
|
|
func path_open(rootFD int32, dirflags lookupflags, path unsafe.Pointer, pathLen size, oflags oflags, fsRightsBase rights, fsRightsInheriting rights, fsFlags fdflags, fd unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 random_get
|
|
//go:noescape
|
|
func random_get(buf unsafe.Pointer, 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 {
|
|
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 unsafe.Pointer) 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), unsafe.Pointer(&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), unsafe.Pointer(&stat))
|
|
return stat.filetype, errnoErr(errno)
|
|
}
|
|
|
|
type preopentype = uint8
|
|
|
|
const (
|
|
preopentypeDir preopentype = iota
|
|
)
|
|
|
|
type prestatDir struct {
|
|
prNameLen size
|
|
}
|
|
|
|
type prestat struct {
|
|
typ preopentype
|
|
dir prestatDir
|
|
}
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_prestat_get
|
|
//go:noescape
|
|
func fd_prestat_get(fd int32, prestat unsafe.Pointer) Errno
|
|
|
|
//go:wasmimport wasi_snapshot_preview1 fd_prestat_dir_name
|
|
//go:noescape
|
|
func fd_prestat_dir_name(fd int32, path unsafe.Pointer, 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, unsafe.Pointer(&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, unsafe.Pointer(&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, unsafe.Pointer, 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, stringPointer(path), size(len(path))
|
|
}
|
|
|
|
func Open(path string, openmode int, perm uint32) (int, error) {
|
|
if path == "" {
|
|
return -1, EINVAL
|
|
}
|
|
dirFd, pathPtr, pathLen := preparePath(path)
|
|
|
|
var oflags oflags
|
|
if (openmode & O_CREATE) != 0 {
|
|
oflags |= OFLAG_CREATE
|
|
}
|
|
if (openmode & O_TRUNC) != 0 {
|
|
oflags |= OFLAG_TRUNC
|
|
}
|
|
if (openmode & O_EXCL) != 0 {
|
|
oflags |= OFLAG_EXCL
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
var fdflags fdflags
|
|
if (openmode & O_APPEND) != 0 {
|
|
fdflags |= FDFLAG_APPEND
|
|
}
|
|
if (openmode & O_SYNC) != 0 {
|
|
fdflags |= FDFLAG_SYNC
|
|
}
|
|
|
|
var fd int32
|
|
errno := path_open(
|
|
dirFd,
|
|
LOOKUP_SYMLINK_FOLLOW,
|
|
pathPtr,
|
|
pathLen,
|
|
oflags,
|
|
rights,
|
|
fileRights,
|
|
fdflags,
|
|
unsafe.Pointer(&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,
|
|
unsafe.Pointer(&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), unsafe.Pointer(&buf[0]), size(len(buf)), cookie, unsafe.Pointer(&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,
|
|
unsafe.Pointer(&buf[0]),
|
|
size(len(buf)),
|
|
unsafe.Pointer(&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(
|
|
stringPointer(path),
|
|
size(len(path)),
|
|
dirFd,
|
|
pathPtr,
|
|
pathlen,
|
|
)
|
|
return errnoErr(errno)
|
|
}
|
|
|
|
func Fsync(fd int) error {
|
|
errno := fd_sync(int32(fd))
|
|
return errnoErr(errno)
|
|
}
|
|
|
|
func bytesPointer(b []byte) unsafe.Pointer {
|
|
return unsafe.Pointer(unsafe.SliceData(b))
|
|
}
|
|
|
|
func stringPointer(s string) unsafe.Pointer {
|
|
return unsafe.Pointer(unsafe.StringData(s))
|
|
}
|
|
|
|
func makeIOVec(b []byte) unsafe.Pointer {
|
|
return unsafe.Pointer(&iovec{
|
|
buf: uintptr32(uintptr(bytesPointer(b))),
|
|
bufLen: size(len(b)),
|
|
})
|
|
}
|
|
|
|
func Read(fd int, b []byte) (int, error) {
|
|
var nread size
|
|
errno := fd_read(int32(fd), makeIOVec(b), 1, unsafe.Pointer(&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, unsafe.Pointer(&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), unsafe.Pointer(&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), unsafe.Pointer(&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), unsafe.Pointer(&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(bytesPointer(b), size(len(b)))
|
|
return errnoErr(errno)
|
|
}
|