| // 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 ( |
| "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 |
| |
| 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) |
| } |
| |
| 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 hasPrefix(path, "/") |
| } |
| |
| func isDir(path string) bool { |
| return hasSuffix(path, "/") |
| } |
| |
| func hasPrefix(s, p string) bool { |
| return len(s) >= len(p) && s[:len(p)] == p |
| } |
| |
| func hasSuffix(s, x string) bool { |
| return len(s) >= len(x) && s[len(s)-len(x):] == x |
| } |
| |
| // 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) && 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 ahread 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) |
| } |