Files
wazero/internal/platform/dir.go
Crypt Keeper 36bf277534 sysfs: requires all methods to return syscall.Errno (#1264)
This forces all syscall functions, notably filesystem, to return numeric
codes as opposed to mapping in two different areas. The result of this
change is better consolidation in call sites of `sysfs.FS`, while
further refactoring is needed to address consolidation of file errors.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-03-22 07:47:57 +01:00

139 lines
3.9 KiB
Go

package platform
import (
"fmt"
"io"
"io/fs"
"syscall"
)
// Readdirnames reads the names of the directory associated with file and
// returns a slice of up to n strings in an arbitrary order. This is a stateful
// function, so subsequent calls return any next values.
//
// If n > 0, Readdirnames returns at most n entries or an error.
// If n <= 0, Readdirnames returns all remaining entries or an error.
//
// # Errors
//
// A zero syscall.Errno is success.
//
// For portability reasons, no error is returned on io.EOF, when the file is
// closed or removed while open.
// See https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs.zig#L635-L637
func Readdirnames(f fs.File, n int) (names []string, errno syscall.Errno) {
switch f := f.(type) {
case readdirnamesFile:
var err error
names, err = f.Readdirnames(n)
if errno = adjustReaddirErr(err); errno != 0 {
return
}
case fs.ReadDirFile:
entries, err := f.ReadDir(n)
if errno = adjustReaddirErr(err); errno != 0 {
return
}
names = make([]string, 0, len(entries))
for _, e := range entries {
names = append(names, e.Name())
}
default:
errno = syscall.ENOTDIR
}
return
}
// Dirent is an entry read from a directory.
//
// This is a portable variant of syscall.Dirent containing fields needed for
// WebAssembly ABI including WASI snapshot-01 and wasi-filesystem. Unlike
// fs.DirEntry, this may include the Ino.
type Dirent struct {
// ^^ Dirent name matches syscall.Dirent
// Name is the base name of the directory entry.
Name string
// Ino is the file serial number, or zero if not available.
Ino uint64
// Type is fs.FileMode masked on fs.ModeType. For example, zero is a
// regular file, fs.ModeDir is a directory and fs.ModeIrregular is unknown.
Type fs.FileMode
}
func (d *Dirent) String() string {
return fmt.Sprintf("name=%s, type=%v, ino=%d", d.Name, d.Type, d.Ino)
}
// IsDir returns true if the Type is fs.ModeDir.
func (d *Dirent) IsDir() bool {
return d.Type == fs.ModeDir
}
// Readdir reads the contents of the directory associated with file and returns
// a slice of up to n Dirent values in an arbitrary order. This is a stateful
// function, so subsequent calls return any next values.
//
// If n > 0, Readdir returns at most n entries or an error.
// If n <= 0, Readdir returns all remaining entries or an error.
//
// # Errors
//
// A zero syscall.Errno is success.
//
// For portability reasons, no error is returned on io.EOF, when the file is
// closed or removed while open.
// See https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs.zig#L635-L637
func Readdir(f fs.File, n int) (dirents []*Dirent, errno syscall.Errno) {
// ^^ case format is to match POSIX and similar to os.File.Readdir
switch f := f.(type) {
case readdirFile:
fis, e := f.Readdir(n)
if errno = adjustReaddirErr(e); errno != 0 {
return
}
dirents = make([]*Dirent, 0, len(fis))
// linux/darwin won't have to fan out to lstat, but windows will.
var ino uint64
for _, t := range fis {
if ino, errno = inoFromFileInfo(f, t); errno != 0 {
return
}
dirents = append(dirents, &Dirent{Name: t.Name(), Ino: ino, Type: t.Mode().Type()})
}
case fs.ReadDirFile:
entries, e := f.ReadDir(n)
if errno = adjustReaddirErr(e); errno != 0 {
return
}
dirents = make([]*Dirent, 0, len(entries))
for _, e := range entries {
// By default, we don't attempt to read inode data
dirents = append(dirents, &Dirent{Name: e.Name(), Type: e.Type()})
}
default:
errno = syscall.ENOTDIR
}
return
}
func adjustReaddirErr(err error) syscall.Errno {
if err == io.EOF {
return 0 // e.g. Readdir on darwin returns io.EOF, but linux doesn't.
} else if errno := UnwrapOSError(err); errno != 0 {
// Ignore errors when the file was closed or removed.
switch errno {
case syscall.EIO, syscall.EBADF: // closed while open
return 0
case syscall.ENOENT: // Linux error when removed while open
return 0
}
return errno
}
return 0
}