Files
wazero/internal/sysfs/syscall_windows.go
Crypt Keeper 8918d73020 ci: supports building with Go 1.20 and raises floor version to 1.18 (#1096)
This moves our floor version to the same we'll release 1.0 with: 1.18.
This is congruent with our version policy which is current-2.

Fixes #921

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2023-02-06 17:29:08 +02:00

161 lines
4.2 KiB
Go

package sysfs
import (
"errors"
"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_NEGATIVE_SEEK is a Windows error returned by os.Truncate
// instead of syscall.EINVAL
ERROR_NEGATIVE_SEEK = syscall.Errno(131)
// ERROR_DIR_NOT_EMPTY is a Windows error returned by syscall.Rmdir
// instead of syscall.ENOTEMPTY
ERROR_DIR_NOT_EMPTY = syscall.Errno(145)
// 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)
)
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 adjustTruncateError(err error) error {
if err == ERROR_NEGATIVE_SEEK {
return syscall.EINVAL
}
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, fs FS, path string, flag int, perm fs.FileMode) file {
return &windowsWrappedFile{f, fs, path, flag, perm, false}
}
type windowsWrappedFile struct {
file
fs FS
path string
flag int
perm fs.FileMode
readDirInitialized bool
}
// ReadDir implements fs.ReadDirFile.
func (w *windowsWrappedFile) ReadDir(n int) ([]fs.DirEntry, error) {
if !w.readDirInitialized {
// On Windows, once the directory is opened, changes to the directory
// is not visible on ReadDir on that already-opened file handle.
//
// In order to provide consistent behavior with other platforms, we re-open it.
if err := w.Close(); err != nil {
return nil, err
}
newW, err := w.fs.OpenFile(w.path, w.flag, w.perm)
if err != nil {
return nil, err
}
*w = *newW.(*windowsWrappedFile)
w.readDirInitialized = true
}
return w.file.ReadDir(n)
}
// Write implements io.Writer
func (w *windowsWrappedFile) Write(p []byte) (n int, err error) {
n, err = w.file.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:
// go1.20 returns access denied, not invalid handle, writing to a directory.
if stat, statErr := StatPath(w.fs, w.path); statErr == nil && stat.IsDir() {
pe.Err = syscall.EBADF
} else {
pe.Err = syscall.EPERM
}
}
}
return
}