Files
wazero/internal/syscallfs/syscall_windows.go
Crypt Keeper 319d6cca62 fs: adds base UnimplementedFS type and unwraps PathError (#1046)
This reduces some boilerplate by extracting UnimplementedFS from the
existing FS implementations, such that it returns ENOSYS. This also
removes inconsistency where some methods on FS returned syscall.Errno
and others PathError.

Note: this doesn't get rid of all PathError, yet. We still need to
create a syscallfs.File type which would be able to do that. This is
just one preliminary cleanup before refactoring out the `fs.FS`
embedding from `syscallfs.DS`.

P.S. naming convention is arbitrary, so I took UnimplementedXXX from
grpc. This pattern is used a lot of places, also proxy-wasm-go-sdk, e.g.
`DefaultVMContext`.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-01-18 09:37:12 -06:00

127 lines
3.1 KiB
Go

package syscallfs
import (
"errors"
"io"
"io/fs"
"os"
"syscall"
)
// See https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
const (
// ERROR_ACCESS_DENIED is a Windows error returned by syscall.Unlink
// instead of syscall.EPERM
ERROR_ACCESS_DENIED = syscall.Errno(5)
// ERROR_INVALID_HANDLE is a Windows error returned by syscall.Write
// instead of syscall.EBADF
ERROR_INVALID_HANDLE = syscall.Errno(6)
// ERROR_ALREADY_EXISTS is a Windows error returned by os.Mkdir
// instead of syscall.EEXIST
ERROR_ALREADY_EXISTS = syscall.Errno(183)
// ERROR_DIRECTORY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTDIR
ERROR_DIRECTORY = syscall.Errno(267)
// ERROR_DIR_NOT_EMPTY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTEMPTY
ERROR_DIR_NOT_EMPTY = syscall.Errno(145)
)
func adjustMkdirError(err error) error {
if err == ERROR_ALREADY_EXISTS {
return syscall.EEXIST
}
return err
}
func adjustRmdirError(err error) error {
switch err {
case ERROR_DIRECTORY:
return syscall.ENOTDIR
case ERROR_DIR_NOT_EMPTY:
return syscall.ENOTEMPTY
}
return err
}
func adjustUnlinkError(err error) error {
if err == ERROR_ACCESS_DENIED {
return syscall.EISDIR
}
return err
}
// rename uses os.Rename as `windows.Rename` is internal in Go's source tree.
func rename(old, new string) (err error) {
if err = os.Rename(old, new); err == nil {
return
}
err = errors.Unwrap(err) // unwrap the link error
if err == ERROR_ACCESS_DENIED {
var newIsDir bool
if stat, statErr := os.Stat(new); statErr == nil && stat.IsDir() {
newIsDir = true
}
var oldIsDir bool
if stat, statErr := os.Stat(old); statErr == nil && stat.IsDir() {
oldIsDir = true
}
if oldIsDir && newIsDir {
// Windows doesn't let you overwrite a directory. If we aim to
// allow this, we'll have to delete here and retry.
return syscall.EINVAL
} else if newIsDir {
err = syscall.EISDIR
} else { // use a mappable code
err = syscall.EPERM
}
}
return
}
// maybeWrapFile deals with errno portability issues in Windows. This code is
// likely to change as we complete syscall support needed for WASI and GOOS=js.
//
// If we don't map to syscall.Errno, wasm will crash in odd way attempting the
// same. This approach is an alternative to making our own fs.File public type.
// We aren't doing that yet, as mapping problems are generally contained to
// Windows. Hence, file is intentionally not exported.
func maybeWrapFile(f file) file {
return struct {
readFile
io.Writer
io.WriterAt // for pwrite
syncer
}{f, &windowsWriter{f}, f, f}
}
// windowsWriter translates error codes not mapped properly by Go.
type windowsWriter struct {
w io.Writer
}
// Write implements io.Writer
func (w windowsWriter) Write(p []byte) (n int, err error) {
n, err = w.w.Write(p)
if err == nil {
return
}
// os.File.Wrap wraps the syscall error in a path error
if pe, ok := err.(*fs.PathError); ok {
switch pe.Err {
case ERROR_INVALID_HANDLE:
pe.Err = syscall.EBADF
case ERROR_ACCESS_DENIED:
pe.Err = syscall.EPERM
}
}
return
}