aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/os/dir_other.go2
-rw-r--r--src/os/dir_wasi.go117
-rw-r--r--src/os/file.go3
-rw-r--r--src/os/file_anyos_test.go13
-rw-r--r--src/syscall/syscall_libc.go9
-rw-r--r--src/syscall/syscall_libc_darwin.go9
-rw-r--r--src/syscall/syscall_libc_nintendoswitch.go9
-rw-r--r--src/syscall/syscall_libc_wasi.go135
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