diff options
-rw-r--r-- | src/os/dir_other.go | 2 | ||||
-rw-r--r-- | src/os/dir_wasi.go | 117 | ||||
-rw-r--r-- | src/os/file.go | 3 | ||||
-rw-r--r-- | src/os/file_anyos_test.go | 13 | ||||
-rw-r--r-- | src/syscall/syscall_libc.go | 9 | ||||
-rw-r--r-- | src/syscall/syscall_libc_darwin.go | 9 | ||||
-rw-r--r-- | src/syscall/syscall_libc_nintendoswitch.go | 9 | ||||
-rw-r--r-- | src/syscall/syscall_libc_wasi.go | 135 |
8 files changed, 259 insertions, 38 deletions
diff --git a/src/os/dir_other.go b/src/os/dir_other.go index 5b83ca441..9a1b39421 100644 --- a/src/os/dir_other.go +++ b/src/os/dir_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi || windows +//go:build baremetal || js || windows // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go new file mode 100644 index 000000000..088dcc7ff --- /dev/null +++ b/src/os/dir_wasi.go @@ -0,0 +1,117 @@ +// 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. + +// This file was derived from src/os/dir_darwin.go since the logic for wasi is +// fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in +// a similar way that the darwin code uses functions from libc. + +//go:build wasi + +package os + +import ( + "io" + "runtime" + "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 } + +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)) + } + 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 + } + if dirent.Ino == 0 { + continue + } + 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: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + } + return ^FileMode(0) +} diff --git a/src/os/file.go b/src/os/file.go index 19dd9a96d..153d8a026 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -187,7 +187,8 @@ func (f *File) Close() (err error) { } else { // Some platforms manage extra state other than the system handle which // needs to be released when the file is closed. For example, darwin - // files have a DIR object holding a dup of the file descriptor. + // files have a DIR object holding a dup of the file descriptor, and + // linux files hold a buffer which needs to be released to a pool. // // These platform-specific logic is provided by the (*file).close method // which is why we do not call the handle's Close method directly. diff --git a/src/os/file_anyos_test.go b/src/os/file_anyos_test.go index 36a28814a..c7d6e50ac 100644 --- a/src/os/file_anyos_test.go +++ b/src/os/file_anyos_test.go @@ -30,12 +30,19 @@ func TestTempDir(t *testing.T) { } func TestChdir(t *testing.T) { - // create and cd into a new directory + // Save and restore the current working directory after the test, otherwise + // we might break other tests that depend on it. + // + // Note that it doesn't work if Chdir is broken, but then this test should + // fail and highlight the issue if that is the case. oldDir, err := Getwd() if err != nil { t.Errorf("Getwd() returned %v", err) return } + defer Chdir(oldDir) + + // create and cd into a new directory dir := "_os_test_TestChDir" Remove(dir) err = Mkdir(dir, 0755) @@ -60,9 +67,7 @@ func TestChdir(t *testing.T) { t.Errorf("Close %s: %s", file, err) } // cd back to original directory - // TODO: emulate "cd .." in wasi-libc better? - //err = Chdir("..") - err = Chdir(oldDir) + err = Chdir("..") if err != nil { t.Errorf("Chdir ..: %s", err) } diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7300afc88..aaba78377 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -97,15 +97,6 @@ func Chdir(path string) (err error) { return } -func Chmod(path string, mode uint32) (err error) { - data := cstring(path) - fail := int(libc_chmod(&data[0], mode)) - if fail < 0 { - err = getErrno() - } - return -} - func Mkdir(path string, mode uint32) (err error) { data := cstring(path) fail := int(libc_mkdir(&data[0], mode)) diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index 0b908255a..704ba29ca 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -267,6 +267,15 @@ func Pipe2(fds []int, flags int) (err error) { return } +func Chmod(path string, mode uint32) (err error) { + data := cstring(path) + fail := int(libc_chmod(&data[0], mode)) + if fail < 0 { + err = getErrno() + } + return +} + func closedir(dir uintptr) (err error) { e := libc_closedir(unsafe.Pointer(dir)) if e != 0 { diff --git a/src/syscall/syscall_libc_nintendoswitch.go b/src/syscall/syscall_libc_nintendoswitch.go index 707650d39..358774991 100644 --- a/src/syscall/syscall_libc_nintendoswitch.go +++ b/src/syscall/syscall_libc_nintendoswitch.go @@ -79,6 +79,15 @@ type RawSockaddrInet6 struct { // stub } +func Chmod(path string, mode uint32) (err error) { + data := cstring(path) + fail := int(libc_chmod(&data[0], mode)) + if fail < 0 { + err = getErrno() + } + return +} + // int open(const char *pathname, int flags, mode_t mode); // //export open diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 29043b4a3..a3bd3a487 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -49,9 +49,10 @@ const ( ) const ( - __WASI_OFLAGS_CREAT = 1 - __WASI_OFLAGS_EXCL = 4 - __WASI_OFLAGS_TRUNC = 8 + __WASI_OFLAGS_CREAT = 1 + __WASI_OFLAGS_DIRECTORY = 2 + __WASI_OFLAGS_EXCL = 4 + __WASI_OFLAGS_TRUNC = 8 __WASI_FDFLAGS_APPEND = 1 __WASI_FDFLAGS_DSYNC = 2 @@ -59,13 +60,24 @@ const ( __WASI_FDFLAGS_RSYNC = 8 __WASI_FDFLAGS_SYNC = 16 + __WASI_FILETYPE_UNKNOWN = 0 + __WASI_FILETYPE_BLOCK_DEVICE = 1 + __WASI_FILETYPE_CHARACTER_DEVICE = 2 + __WASI_FILETYPE_DIRECTORY = 3 + __WASI_FILETYPE_REGULAR_FILE = 4 + __WASI_FILETYPE_SOCKET_DGRAM = 5 + __WASI_FILETYPE_SOCKET_STREAM = 6 + __WASI_FILETYPE_SYMBOLIC_LINK = 7 + + // ../../lib/wasi-libc/libc-bottom-half/headers/public/__header_fcntl.h O_RDONLY = 0x04000000 O_WRONLY = 0x10000000 O_RDWR = O_RDONLY | O_WRONLY - O_CREAT = __WASI_OFLAGS_CREAT << 12 - O_TRUNC = __WASI_OFLAGS_TRUNC << 12 - O_EXCL = __WASI_OFLAGS_EXCL << 12 + O_CREAT = __WASI_OFLAGS_CREAT << 12 + O_TRUNC = __WASI_OFLAGS_TRUNC << 12 + O_EXCL = __WASI_OFLAGS_EXCL << 12 + O_DIRECTORY = __WASI_OFLAGS_DIRECTORY << 12 O_APPEND = __WASI_FDFLAGS_APPEND O_DSYNC = __WASI_FDFLAGS_DSYNC @@ -103,6 +115,7 @@ const ( SYS_FSTATAT64 SYS_OPENAT SYS_UNLINKAT + PATH_MAX = 4096 ) //go:extern errno @@ -266,29 +279,80 @@ const ( S_IXUSR = 0x40 ) -// dummy +// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__header_dirent.h const ( - DT_BLK = 0x6 - DT_CHR = 0x2 - DT_DIR = 0x4 - DT_FIFO = 0x1 - DT_LNK = 0xa - DT_REG = 0x8 - DT_SOCK = 0xc - DT_UNKNOWN = 0x0 - DT_WHT = 0xe + DT_BLK = __WASI_FILETYPE_BLOCK_DEVICE + DT_CHR = __WASI_FILETYPE_CHARACTER_DEVICE + DT_DIR = __WASI_FILETYPE_DIRECTORY + DT_FIFO = __WASI_FILETYPE_SOCKET_STREAM + DT_LNK = __WASI_FILETYPE_SYMBOLIC_LINK + DT_REG = __WASI_FILETYPE_REGULAR_FILE + DT_UNKNOWN = __WASI_FILETYPE_UNKNOWN ) -// dummy +// 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 - Reclen uint16 - Type uint8 - Name [1024]int8 + 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 Fdopendir(fd int) (dir uintptr, err error) { + d := libc_fdopendir(int32(fd)) + + if d == nil { + err = getErrno() + } + return uintptr(d), err +} + +func Fdclosedir(dir uintptr) (err error) { + // Unlike on other unix platform where only closedir exists, wasi-libc has + // fdclosedir which releases resources and returns the file descriptor but + // does not close it. This is useful for us since we want to be able to keep + // using it. + n := libc_fdclosedir(unsafe.Pointer(dir)) + + if n < 0 { + err = getErrno() + } + return } -func ReadDirent(fd int, buf []byte) (n int, err error) { - return -1, ENOSYS +func Readdir(dir uintptr) (dirent *Dirent, err error) { + // There might be a leftover errno value in the global variable, so we have + // to clear it before calling readdir because we cannot know whether a nil + // return means that we reached EOF or that an error occured. + libcErrno = 0 + + dirent = libc_readdir(unsafe.Pointer(dir)) + + if dirent == nil && libcErrno != 0 { + err = getErrno() + } + return } func Stat(path string, p *Stat_t) (err error) { @@ -323,6 +387,16 @@ func Pipe2(p []int, flags int) (err error) { return ENOSYS // TODO } +func Chmod(path string, mode uint32) (err error) { + // wasi does not have chmod, but there are tests that validate that calling + // os.Chmod does not error (e.g. io/fs.TestIssue51617). + // + // We make a call to Lstat instead so we detect conditions like the path not + // existing, but we don't honnor the request to modify the file permissions. + stat := Stat_t{} + return Lstat(path, &stat) +} + func Getpagesize() int { // per upstream return 65536 @@ -373,3 +447,18 @@ func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 // //export open func libc_open(pathname *byte, flags int32, mode uint32) int32 + +// DIR *fdopendir(int); +// +//export fdopendir +func libc_fdopendir(fd int32) unsafe.Pointer + +// int fdclosedir(DIR *); +// +//export fdclosedir +func libc_fdclosedir(unsafe.Pointer) int32 + +// struct dirent *readdir(DIR *); +// +//export readdir +func libc_readdir(unsafe.Pointer) *Dirent |