Files
llgo/runtime/internal/clite/syscall/fs_wasm.go
2025-05-17 10:48:26 +08:00

324 lines
8.2 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 wasm
package syscall
import (
"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#-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
}
type preopentype = uint8
const (
preopentypeDir preopentype = iota
)
type prestatDir struct {
_ structs.HostLayout
prNameLen size
}
type prestat struct {
_ structs.HostLayout
typ preopentype
dir prestatDir
}
// 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
//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 hasPrefix(path, "/")
}
func isDir(path string) bool {
return hasSuffix(path, "/")
}
func hasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
func hasSuffix(s, suffix string) bool {
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}
type Stat_t struct {
Dev uint64
Ino uint64
Filetype uint8
Nlink uint64
Size uint64
Atime uint64
Mtime uint64
Ctime uint64
}