Repository: tetratelabs/wazero
Pull request #1264 (2023-03-21 14:23:39)

sysfs: requires all methods to return syscall.Errno
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.


Patch:
diff --git a/imports/wasi_snapshot_preview1/args.go b/imports/wasi_snapshot_preview1/args.go
index 67869025..8e61c472 100644
--- a/imports/wasi_snapshot_preview1/args.go
+++ b/imports/wasi_snapshot_preview1/args.go
@@ -2,6 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"context"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -22,7 +23,7 @@ import (
 // Result (Errno)
 //
 // The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoFault: there is not enough memory to write results
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if argsSizesGet wrote argc=2 and argvLen=5 for arguments:
 // "a" and "bc" parameters argv=7 and argvBuf=1, this function writes the below
@@ -42,7 +43,7 @@ import (
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 var argsGet = newHostFunc(ArgsGetName, argsGetFn, []api.ValueType{i32, i32}, "argv", "argv_buf")
 
-func argsGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func argsGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	argv, argvBuf := uint32(params[0]), uint32(params[1])
 	return writeOffsetsAndNullTerminatedValues(mod.Memory(), sysCtx.Args(), argv, argvBuf, sysCtx.ArgsSize())
@@ -60,7 +61,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // Result (Errno)
 //
 // The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoFault: there is not enough memory to write results
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if args are "a", "bc" and parameters resultArgc=1 and
 // resultArgvLen=6, this function writes the below to api.Memory:
@@ -79,7 +80,7 @@ func argsGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 var argsSizesGet = newHostFunc(ArgsSizesGetName, argsSizesGetFn, []api.ValueType{i32, i32}, "result.argc", "result.argv_len")
 
-func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	mem := mod.Memory()
 	resultArgc, resultArgvLen := uint32(params[0]), uint32(params[1])
@@ -87,10 +88,10 @@ func argsSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// argc and argv_len offsets are not necessarily sequential, so we have to
 	// write them independently.
 	if !mem.WriteUint32Le(resultArgc, uint32(len(sysCtx.Args()))) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 	if !mem.WriteUint32Le(resultArgvLen, sysCtx.ArgsSize()) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
diff --git a/imports/wasi_snapshot_preview1/clock.go b/imports/wasi_snapshot_preview1/clock.go
index 99fa924f..be25f5c3 100644
--- a/imports/wasi_snapshot_preview1/clock.go
+++ b/imports/wasi_snapshot_preview1/clock.go
@@ -2,6 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"context"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -19,10 +20,10 @@ import (
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoNotsup: the clock ID is not supported.
-//   - ErrnoInval: the clock ID is invalid.
-//   - ErrnoFault: there is not enough memory to write results
+// The return value is 0 except the following error conditions:
+//   - syscall.ENOTSUP: the clock ID is not supported.
+//   - syscall.EINVAL: the clock ID is invalid.
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if the resolution is 100ns, this function writes the below to
 // api.Memory:
@@ -38,7 +39,7 @@ import (
 // See https://linux.die.net/man/3/clock_getres
 var clockResGet = newHostFunc(ClockResGetName, clockResGetFn, []api.ValueType{i32, i32}, "id", "result.resolution")
 
-func clockResGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func clockResGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	id, resultResolution := uint32(params[0]), uint32(params[1])
 
@@ -49,13 +50,13 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	case ClockIDMonotonic:
 		resolution = uint64(sysCtx.NanotimeResolution())
 	default:
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	if !mod.Memory().WriteUint64Le(resultResolution, resolution) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // clockTimeGet is the WASI function named ClockTimeGetName that returns
@@ -71,10 +72,10 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoNotsup: the clock ID is not supported.
-//   - ErrnoInval: the clock ID is invalid.
-//   - ErrnoFault: there is not enough memory to write results
+// The return value is 0 except the following error conditions:
+//   - syscall.ENOTSUP: the clock ID is not supported.
+//   - syscall.EINVAL: the clock ID is invalid.
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if time.Now returned exactly midnight UTC 2022-01-01
 // (1640995200000000000), and parameters resultTimestamp=1, this function
@@ -91,7 +92,7 @@ func clockResGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // See https://linux.die.net/man/3/clock_gettime
 var clockTimeGet = newHostFunc(ClockTimeGetName, clockTimeGetFn, []api.ValueType{i32, i64, i32}, "id", "precision", "result.timestamp")
 
-func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	id := uint32(params[0])
 	// TODO: precision is currently ignored.
@@ -105,11 +106,11 @@ func clockTimeGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	case ClockIDMonotonic:
 		val = sysCtx.Nanotime()
 	default:
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	if !mod.Memory().WriteUint64Le(resultTimestamp, uint64(val)) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
diff --git a/imports/wasi_snapshot_preview1/environ.go b/imports/wasi_snapshot_preview1/environ.go
index fabf4992..daaa46b4 100644
--- a/imports/wasi_snapshot_preview1/environ.go
+++ b/imports/wasi_snapshot_preview1/environ.go
@@ -2,6 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"context"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -22,8 +23,8 @@ import (
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoFault: there is not enough memory to write results
+// The return value is 0 except the following error conditions:
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if environSizesGet wrote environc=2 and environLen=9 for
 // environment variables: "a=b", "b=cd" and parameters environ=11 and
@@ -42,7 +43,7 @@ import (
 // See https://en.wikipedia.org/wiki/Null-terminated_string
 var environGet = newHostFunc(EnvironGetName, environGetFn, []api.ValueType{i32, i32}, "environ", "environ_buf")
 
-func environGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func environGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	environ, environBuf := uint32(params[0]), uint32(params[1])
 
@@ -61,8 +62,8 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoFault: there is not enough memory to write results
+// The return value is 0 except the following error conditions:
+//   - syscall.EFAULT: there is not enough memory to write results
 //
 // For example, if environ are "a=b","b=cd" and parameters resultEnvironc=1 and
 // resultEnvironvLen=6, this function writes the below to api.Memory:
@@ -82,7 +83,7 @@ func environGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // and https://en.wikipedia.org/wiki/Null-terminated_string
 var environSizesGet = newHostFunc(EnvironSizesGetName, environSizesGetFn, []api.ValueType{i32, i32}, "result.environc", "result.environv_len")
 
-func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	mem := mod.Memory()
 	resultEnvironc, resultEnvironvLen := uint32(params[0]), uint32(params[1])
@@ -90,10 +91,10 @@ func environSizesGetFn(_ context.Context, mod api.Module, params []uint64) Errno
 	// environc and environv_len offsets are not necessarily sequential, so we
 	// have to write them independently.
 	if !mem.WriteUint32Le(resultEnvironc, uint32(len(sysCtx.Environ()))) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 	if !mem.WriteUint32Le(resultEnvironvLen, sysCtx.EnvironSize()) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
diff --git a/imports/wasi_snapshot_preview1/fs.go b/imports/wasi_snapshot_preview1/fs.go
index 7b187709..9569bfe9 100644
--- a/imports/wasi_snapshot_preview1/fs.go
+++ b/imports/wasi_snapshot_preview1/fs.go
@@ -37,7 +37,7 @@ var fdAdvise = newHostFunc(
 	"fd", "offset", "len", "advice",
 )
 
-func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := uint32(params[0])
 	_ = params[1]
 	_ = params[2]
@@ -46,7 +46,7 @@ func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 
 	_, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	switch advice {
@@ -57,7 +57,7 @@ func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 		FdAdviceDontNeed,
 		FdAdviceNoReuse:
 	default:
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	// FdAdvice corresponds to posix_fadvise, but it can only be supported on linux.
@@ -67,7 +67,7 @@ func fdAdviseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// TODO: invoke posix_fadvise on linux, and partially on darwin.
 	// - https://gitlab.com/cznic/fileutil/-/blob/v1.1.2/fileutil_linux.go#L87-95
 	// - https://github.com/bytecodealliance/system-interface/blob/62b97f9776b86235f318c3a6e308395a1187439b/src/fs/file_io_ext.rs#L430-L442
-	return ErrnoSuccess
+	return 0
 }
 
 // fdAllocate is the WASI function named FdAllocateName which forces the
@@ -80,7 +80,7 @@ var fdAllocate = newHostFunc(
 	"fd", "offset", "len",
 )
 
-func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := uint32(params[0])
 	offset := params[1]
 	length := params[2]
@@ -88,33 +88,30 @@ func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	f, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	tail := int64(offset + length)
 	if tail < 0 {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	st, err := f.Stat()
 	if err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	}
 
 	if st.Size >= tail {
 		// We already have enough space.
-		return ErrnoSuccess
+		return 0
 	}
 
 	osf, ok := f.File.(truncateFile)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
-	if err := osf.Truncate(tail); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	return platform.UnwrapOSError(osf.Truncate(tail))
 }
 
 // fdClose is the WASI function named FdCloseName which closes a file
@@ -126,23 +123,20 @@ func fdAllocateFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: the fd was not open.
-//   - ErrnoNotsup: the fs was a pre-open
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: the fd was not open.
+//   - syscall.ENOTSUP: the fs was a pre-open
 //
 // Note: This is similar to `close` in POSIX.
 // See https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_close
 // and https://linux.die.net/man/3/close
 var fdClose = newHostFunc(FdCloseName, fdCloseFn, []api.ValueType{i32}, "fd")
 
-func fdCloseFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdCloseFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd := uint32(params[0])
 
-	if err := fsc.CloseFile(fd); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	return fsc.CloseFile(fd)
 }
 
 // fdDatasync is the WASI function named FdDatasyncName which synchronizes
@@ -151,17 +145,16 @@ func fdCloseFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_datasyncfd-fd---errno
 var fdDatasync = newHostFunc(FdDatasyncName, fdDatasyncFn, []api.ValueType{i32}, "fd")
 
-func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd := uint32(params[0])
 
 	// Check to see if the file descriptor is available
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
-	} else if err := sysfs.FileDatasync(f.File); err != nil {
-		return ToErrno(err)
+		return syscall.EBADF
+	} else {
+		return sysfs.FileDatasync(f.File)
 	}
-	return ErrnoSuccess
 }
 
 // fdFdstatGet is the WASI function named FdFdstatGetName which returns the
@@ -174,9 +167,9 @@ func fdDatasyncFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `resultFdstat` points to an offset out of memory
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `resultFdstat` points to an offset out of memory
 //
 // fdstat byte layout is 24-byte size, with the following fields:
 //   - fs_filetype 1 byte: the file type
@@ -205,7 +198,7 @@ var fdFdstatGet = newHostFunc(FdFdstatGetName, fdFdstatGetFn, []api.ValueType{i3
 
 // fdFdstatGetFn cannot currently use proxyResultParams because fdstat is larger
 // than api.ValueTypeI64 (i64 == 8 bytes, but fdstat is 24).
-func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd, resultFdstat := uint32(params[0]), uint32(params[1])
@@ -213,25 +206,25 @@ func fdFdstatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// Ensure we can write the fdstat
 	buf, ok := mod.Memory().Read(resultFdstat, 24)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	var fdflags uint16
-	var stat fs.FileInfo
+	var st fs.FileInfo
 	var err error
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
-	} else if stat, err = f.File.Stat(); err != nil {
-		return ToErrno(err)
+		return syscall.EBADF
+	} else if st, err = f.File.Stat(); err != nil {
+		return platform.UnwrapOSError(err)
 	} else if _, ok := f.File.(io.Writer); ok {
 		// TODO: maybe cache flags to open instead
 		fdflags = FD_APPEND
 	}
 
-	filetype := getWasiFiletype(stat.Mode())
+	filetype := getWasiFiletype(st.Mode())
 	writeFdstat(buf, filetype, fdflags)
 
-	return ErrnoSuccess
+	return 0
 }
 
 var blockFdstat = []byte{
@@ -252,13 +245,13 @@ func writeFdstat(buf []byte, filetype uint8, fdflags uint16) {
 // adjusts the flags associated with a file descriptor.
 var fdFdstatSetFlags = newHostFunc(FdFdstatSetFlagsName, fdFdstatSetFlagsFn, []wasm.ValueType{i32, i32}, "fd", "flags")
 
-func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd, wasiFlag := uint32(params[0]), uint16(params[1])
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	// We can only support APPEND flag.
 	if FD_DSYNC&wasiFlag != 0 || FD_NONBLOCK&wasiFlag != 0 || FD_RSYNC&wasiFlag != 0 || FD_SYNC&wasiFlag != 0 {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	var flag int
@@ -266,10 +259,7 @@ func fdFdstatSetFlagsFn(_ context.Context, mod api.Module, params []uint64) Errn
 		flag = syscall.O_APPEND
 	}
 
-	if err := fsc.ChangeOpenFlag(fd, flag); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	return fsc.ChangeOpenFlag(fd, flag)
 }
 
 // fdFdstatSetRights will not be implemented as rights were removed from WASI.
@@ -291,10 +281,10 @@ var fdFdstatSetRights = stubFunction(
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoIo: could not stat `fd` on filesystem
-//   - ErrnoFault: `resultFilestat` points to an offset out of memory
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EIO: could not stat `fd` on filesystem
+//   - syscall.EFAULT: `resultFilestat` points to an offset out of memory
 //
 // filestat byte layout is 64-byte size, with the following fields:
 //   - dev 8 bytes: the device ID of device containing the file
@@ -332,34 +322,30 @@ var fdFilestatGet = newHostFunc(FdFilestatGetName, fdFilestatGetFn, []api.ValueT
 
 // fdFilestatGetFn cannot currently use proxyResultParams because filestat is
 // larger than api.ValueTypeI64 (i64 == 8 bytes, but filestat is 64).
-func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	return fdFilestatGetFunc(mod, uint32(params[0]), uint32(params[1]))
 }
 
-func fdFilestatGetFunc(mod api.Module, fd, resultBuf uint32) Errno {
+func fdFilestatGetFunc(mod api.Module, fd, resultBuf uint32) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	// Ensure we can write the filestat
 	buf, ok := mod.Memory().Read(resultBuf, 64)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	f, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	st, err := f.Stat()
 	if err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	}
 
-	if err = writeFilestat(buf, &st); err != nil {
-		return ToErrno(err)
-	}
-
-	return ErrnoSuccess
+	return writeFilestat(buf, &st)
 }
 
 func getWasiFiletype(fm fs.FileMode) uint8 {
@@ -382,7 +368,7 @@ func getWasiFiletype(fm fs.FileMode) uint8 {
 	}
 }
 
-func writeFilestat(buf []byte, st *platform.Stat_t) (err error) {
+func writeFilestat(buf []byte, st *platform.Stat_t) (errno syscall.Errno) {
 	le.PutUint64(buf, st.Dev)
 	le.PutUint64(buf[8:], st.Ino)
 	le.PutUint64(buf[16:], uint64(getWasiFiletype(st.Mode)))
@@ -400,7 +386,7 @@ func writeFilestat(buf []byte, st *platform.Stat_t) (err error) {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_filestat_set_sizefd-fd-size-filesize---errno
 var fdFilestatSetSize = newHostFunc(FdFilestatSetSizeName, fdFilestatSetSizeFn, []wasm.ValueType{i32, i64}, "fd", "size")
 
-func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := uint32(params[0])
 	size := uint32(params[1])
 
@@ -408,13 +394,13 @@ func fdFilestatSetSizeFn(_ context.Context, mod api.Module, params []uint64) Err
 
 	// Check to see if the file descriptor is available
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	} else if truncateFile, ok := f.File.(truncateFile); !ok {
-		return ErrnoBadf // possibly a fake file
+		return syscall.EBADF // possibly a fake file
 	} else if err := truncateFile.Truncate(int64(size)); err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdFilestatSetTimes is the WASI function named functionFdFilestatSetTimes
@@ -427,7 +413,7 @@ var fdFilestatSetTimes = newHostFunc(
 	"fd", "atim", "mtim", "fst_flags",
 )
 
-func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := uint32(params[0])
 	atim := int64(params[1])
 	mtim := int64(params[2])
@@ -438,32 +424,32 @@ func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) Er
 
 	f, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	times, errno := toTimes(atim, mtim, fstFlags)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	// Try to update the file timestamps by file-descriptor.
-	err := platform.UtimensFile(f.File, &times)
+	errno = platform.UtimensFile(f.File, &times)
 
 	// Fall back to path based, despite it being less precise.
-	switch err {
+	switch errno {
 	case syscall.EPERM, syscall.ENOSYS:
-		err = f.FS.Utimens(f.Name, &times, true)
+		errno = f.FS.Utimens(f.Name, &times, true)
 	}
 
-	return ToErrno(err)
+	return errno
 }
 
-func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, errno Errno) {
+func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, errno syscall.Errno) {
 	// times[0] == atim, times[1] == mtim
 
 	// coerce atim into a timespec
 	if set, now := fstFlags&FstflagsAtim != 0, fstFlags&FstflagsAtimNow != 0; set && now {
-		errno = ErrnoInval
+		errno = syscall.EINVAL
 		return
 	} else if set {
 		times[0] = syscall.NsecToTimespec(atim)
@@ -475,7 +461,7 @@ func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, err
 
 	// coerce mtim into a timespec
 	if set, now := fstFlags&FstflagsMtim != 0, fstFlags&FstflagsMtimNow != 0; set && now {
-		errno = ErrnoInval
+		errno = syscall.EINVAL
 		return
 	} else if set {
 		times[1] = syscall.NsecToTimespec(mtime)
@@ -499,7 +485,7 @@ var fdPread = newHostFunc(
 	"fd", "iovs", "iovs_len", "offset", "result.nread",
 )
 
-func fdPreadFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdPreadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	return fdReadOrPread(mod, params, true)
 }
 
@@ -513,9 +499,9 @@ func fdPreadFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid or the `fd` is not a pre-opened directory
-//   - ErrnoFault: `resultPrestat` points to an offset out of memory
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid or the `fd` is not a pre-opened directory
+//   - syscall.EFAULT: `resultPrestat` points to an offset out of memory
 //
 // prestat byte layout is 8 bytes, beginning with an 8-bit tag and 3 pad bytes.
 // The only valid tag is `prestat_dir`, which is tag zero. This simplifies the
@@ -536,12 +522,12 @@ func fdPreadFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#prestat
 var fdPrestatGet = newHostFunc(FdPrestatGetName, fdPrestatGetFn, []api.ValueType{i32, i32}, "fd", "result.prestat")
 
-func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd, resultPrestat := uint32(params[0]), uint32(params[1])
 
 	name, errno := preopenPath(fsc, fd)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
@@ -549,9 +535,9 @@ func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// * Zero-value 8-bit tag, and 3-byte zero-value padding
 	prestat := uint64(len(name) << 32)
 	if !mod.Memory().WriteUint64Le(resultPrestat, prestat) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdPrestatDirName is the WASI function named FdPrestatDirNameName which
@@ -567,10 +553,10 @@ func fdPrestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `path` points to an offset out of memory
-//   - ErrnoNametoolong: `pathLen` is longer than the actual length of the result
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `path` points to an offset out of memory
+//   - syscall.ENAMETOOLONG: `pathLen` is longer than the actual length of the result
 //
 // For example, the directory name corresponding with `fd` was "/tmp" and
 // # Parameters path=1 pathLen=4 (correct), this function will write the below to
@@ -590,24 +576,24 @@ var fdPrestatDirName = newHostFunc(
 	"fd", "result.path", "result.path_len",
 )
 
-func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdPrestatDirNameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd, path, pathLen := uint32(params[0]), uint32(params[1]), uint32(params[2])
 
 	name, errno := preopenPath(fsc, fd)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	// Some runtimes may have another semantics. See /RATIONALE.md
 	if uint32(len(name)) < pathLen {
-		return ErrnoNametoolong
+		return syscall.ENAMETOOLONG
 	}
 
 	if !mod.Memory().Write(path, []byte(name)[:pathLen]) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdPwrite is the WASI function named FdPwriteName which writes to a file
@@ -622,7 +608,7 @@ var fdPwrite = newHostFunc(
 	"fd", "iovs", "iovs_len", "offset", "result.nwritten",
 )
 
-func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	return fdWriteOrPwrite(mod, params, true)
 }
 
@@ -641,10 +627,10 @@ func fdPwriteFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `iovs` or `resultNread` point to an offset out of memory
-//   - ErrnoIo: a file system error
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `iovs` or `resultNread` point to an offset out of memory
+//   - syscall.EIO: a file system error
 //
 // For example, this function needs to first read `iovs` to determine where
 // to write contents. If parameters iovs=1 iovsCount=2, this function reads two
@@ -681,11 +667,11 @@ var fdRead = newHostFunc(
 	"fd", "iovs", "iovs_len", "result.nread",
 )
 
-func fdReadFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdReadFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	return fdReadOrPread(mod, params, false)
 }
 
-func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
+func fdReadOrPread(mod api.Module, params []uint64, isPread bool) syscall.Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
@@ -693,7 +679,7 @@ func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
 
 	r, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	var reader io.Reader = r.File
@@ -714,7 +700,7 @@ func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
 	iovsStop := iovsCount << 3 // iovsCount * 8
 	iovsBuf, ok := mem.Read(iovs, iovsStop)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
@@ -723,23 +709,23 @@ func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
 
 		b, ok := mem.Read(offset, l)
 		if !ok {
-			return ErrnoFault
+			return syscall.EFAULT
 		}
 
 		n, err := reader.Read(b)
 		nread += uint32(n)
 
 		shouldContinue, errno := fdRead_shouldContinueRead(uint32(n), l, err)
-		if errno != ErrnoSuccess {
+		if errno != 0 {
 			return errno
 		} else if !shouldContinue {
 			break
 		}
 	}
 	if !mem.WriteUint32Le(resultNread, nread) {
-		return ErrnoFault
+		return syscall.EFAULT
 	} else {
-		return ErrnoSuccess
+		return 0
 	}
 }
 
@@ -748,16 +734,16 @@ func fdReadOrPread(mod api.Module, params []uint64, isPread bool) Errno {
 //
 // Note: When there are both bytes read (n) and an error, this continues.
 // See /RATIONALE.md "Why ignore the error returned by io.Reader when n > 1?"
-func fdRead_shouldContinueRead(n, l uint32, err error) (bool, Errno) {
+func fdRead_shouldContinueRead(n, l uint32, err error) (bool, syscall.Errno) {
 	if errors.Is(err, io.EOF) {
-		return false, ErrnoSuccess // EOF isn't an error, and we shouldn't continue.
+		return false, 0 // EOF isn't an error, and we shouldn't continue.
 	} else if err != nil && n == 0 {
-		return false, ErrnoIo
+		return false, syscall.EIO
 	} else if err != nil {
-		return false, ErrnoSuccess // Allow the caller to process n bytes.
+		return false, 0 // Allow the caller to process n bytes.
 	}
 	// Continue reading, unless there's a partial read or nothing to read.
-	return n == l && n != 0, ErrnoSuccess
+	return n == l && n != 0, 0
 }
 
 // fdReaddir is the WASI function named FdReaddirName which reads directory
@@ -770,7 +756,7 @@ var fdReaddir = newHostFunc(
 	"fd", "buf", "buf_len", "cookie", "result.bufused",
 )
 
-func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
@@ -786,12 +772,12 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// The bufLen must be enough to write a dirent. Otherwise, the caller can't
 	// read what the next cookie is.
 	if bufLen < DirentSize {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	// Validate the FD is a directory
 	rd, dir, errno := openedDir(fsc, fd)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
@@ -801,9 +787,9 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 		// https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/cloudlibc/src/libc/dirent/rewinddir.c#L10-L12
 		//
 		// Since we cannot unwind fs.ReadDirFile results, we re-open while keeping the same file descriptor.
-		f, err := fsc.ReOpenDir(fd)
-		if err != nil {
-			return ToErrno(err)
+		f, errno := fsc.ReOpenDir(fd)
+		if errno != 0 {
+			return errno
 		}
 		rd, dir = f.File, f.ReadDir
 	}
@@ -824,17 +810,16 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	// The host keeps state for any unread entries from the prior call because
 	// we cannot seek to a previous directory position. Collect these entries.
 	dirents, errno := lastDirents(dir, cookie)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	// Add entries for dot and dot-dot as wasi-testsuite requires them.
 	if cookie == 0 && dirents == nil {
-		var err error
 		if f, ok := fsc.LookupFile(fd); !ok {
-			return ErrnoBadf
-		} else if dirents, err = dotDirents(f); err != nil {
-			return ToErrno(err)
+			return syscall.EBADF
+		} else if dirents, errno = dotDirents(f); errno != 0 {
+			return errno
 		}
 		dir.Dirents = dirents
 		dir.CountRead = 2 // . and ..
@@ -844,8 +829,8 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	if entryCount := len(dirents); entryCount < maxDirEntries {
 		// Note: platform.Readdir does not return io.EOF as it is
 		// inconsistently returned (e.g. darwin does, but linux doesn't).
-		l, err := platform.Readdir(rd, maxDirEntries-entryCount)
-		if errno = ToErrno(err); errno != ErrnoSuccess {
+		l, errno := platform.Readdir(rd, maxDirEntries-entryCount)
+		if errno != 0 {
 			return errno
 		}
 
@@ -873,31 +858,31 @@ func fdReaddirFn(_ context.Context, mod api.Module, params []uint64) Errno {
 
 		buf, ok := mem.Read(buf, bufused)
 		if !ok {
-			return ErrnoFault
+			return syscall.EFAULT
 		}
 
 		writeDirents(dirents, direntCount, writeTruncatedEntry, buf, d_next)
 	}
 
 	if !mem.WriteUint32Le(resultBufused, bufused) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // dotDirents returns "." and "..", where "." because wasi-testsuite does inode
 // validation.
-func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, error) {
+func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, syscall.Errno) {
 	dotIno, ft, err := f.CachedStat()
 	if err != nil {
-		return nil, err
+		return nil, platform.UnwrapOSError(err)
 	} else if ft.Type() != fs.ModeDir {
 		return nil, syscall.ENOTDIR
 	}
 	dotDotIno := uint64(0)
 	if !f.IsPreopen && f.Name != "." {
-		if st, err := f.FS.Stat(path.Dir(f.Name)); err != nil {
-			return nil, err
+		if st, errno := f.FS.Stat(path.Dir(f.Name)); errno != 0 {
+			return nil, errno
 		} else {
 			dotDotIno = st.Ino
 		}
@@ -905,22 +890,22 @@ func dotDirents(f *sys.FileEntry) ([]*platform.Dirent, error) {
 	return []*platform.Dirent{
 		{Name: ".", Ino: dotIno, Type: fs.ModeDir},
 		{Name: "..", Ino: dotDotIno, Type: fs.ModeDir},
-	}, nil
+	}, 0
 }
 
 const largestDirent = int64(math.MaxUint32 - DirentSize)
 
 // lastDirents is broken out from fdReaddirFn for testability.
-func lastDirents(dir *sys.ReadDir, cookie int64) (dirents []*platform.Dirent, errno Errno) {
+func lastDirents(dir *sys.ReadDir, cookie int64) (dirents []*platform.Dirent, errno syscall.Errno) {
 	if cookie < 0 {
-		errno = ErrnoInval // invalid as we will never send a negative cookie.
+		errno = syscall.EINVAL // invalid as we will never send a negative cookie.
 		return
 	}
 
 	entryCount := int64(len(dir.Dirents))
 	if entryCount == 0 { // there was no prior call
 		if cookie != 0 {
-			errno = ErrnoInval // invalid as we haven't sent that cookie
+			errno = syscall.EINVAL // invalid as we haven't sent that cookie
 		}
 		return
 	}
@@ -931,9 +916,9 @@ func lastDirents(dir *sys.ReadDir, cookie int64) (dirents []*platform.Dirent, er
 
 	switch {
 	case cookiePos < 0: // cookie is asking for results outside our window.
-		errno = ErrnoNosys // we can't implement directory seeking backwards.
+		errno = syscall.ENOSYS // we can't implement directory seeking backwards.
 	case cookiePos > entryCount:
-		errno = ErrnoInval // invalid as we read that far, yet.
+		errno = syscall.EINVAL // invalid as we read that far, yet.
 	case cookiePos > 0: // truncate so to avoid large lists.
 		dirents = dir.Dirents[cookiePos:]
 	default:
@@ -1052,25 +1037,25 @@ func writeDirent(buf []byte, dNext uint64, ino uint64, dNamlen uint32, dType fs.
 	le.PutUint32(buf[20:], uint32(filetype)) //  d_type
 }
 
-// openedDir returns the directory and ErrnoSuccess if the fd points to a readable directory.
-func openedDir(fsc *sys.FSContext, fd uint32) (fs.File, *sys.ReadDir, Errno) {
+// openedDir returns the directory and 0 if the fd points to a readable directory.
+func openedDir(fsc *sys.FSContext, fd uint32) (fs.File, *sys.ReadDir, syscall.Errno) {
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return nil, nil, ErrnoBadf
+		return nil, nil, syscall.EBADF
 	} else if _, ft, err := f.CachedStat(); err != nil {
-		return nil, nil, ToErrno(err)
+		return nil, nil, platform.UnwrapOSError(err)
 	} else if ft.Type() != fs.ModeDir {
-		// fd_readdir docs don't indicate whether to return ErrnoNotdir or
-		// ErrnoBadf. It has been noticed that rust will crash on ErrnoNotdir,
+		// fd_readdir docs don't indicate whether to return syscall.ENOTDIR or
+		// syscall.EBADF. It has been noticed that rust will crash on syscall.ENOTDIR,
 		// and POSIX C ref seems to not return this, so we don't either.
 		//
 		// See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#fd_readdir
 		// and https://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/dirent.h
-		return nil, nil, ErrnoBadf
+		return nil, nil, syscall.EBADF
 	} else {
 		if f.ReadDir == nil {
 			f.ReadDir = &sys.ReadDir{}
 		}
-		return f.File, f.ReadDir, ErrnoSuccess
+		return f.File, f.ReadDir, 0
 	}
 }
 
@@ -1080,16 +1065,16 @@ func openedDir(fsc *sys.FSContext, fd uint32) (fs.File, *sys.ReadDir, Errno) {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_renumberfd-fd-to-fd---errno
 var fdRenumber = newHostFunc(FdRenumberName, fdRenumberFn, []wasm.ValueType{i32, i32}, "fd", "to")
 
-func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	from := uint32(params[0])
 	to := uint32(params[1])
 
-	if err := fsc.Renumber(from, to); err != nil {
-		return ToErrno(err)
+	if errno := fsc.Renumber(from, to); errno != 0 {
+		return errno
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdSeek is the WASI function named FdSeekName which moves the offset of a
@@ -1109,11 +1094,11 @@ func fdRenumberFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `resultNewoffset` points to an offset out of memory
-//   - ErrnoInval: `whence` is an invalid value
-//   - ErrnoIo: a file system error
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `resultNewoffset` points to an offset out of memory
+//   - syscall.EINVAL: `whence` is an invalid value
+//   - syscall.EIO: a file system error
 //
 // For example, if fd 3 is a file with offset 0, and parameters fd=3, offset=4,
 // whence=0 (=io.SeekStart), resultNewOffset=1, this function writes the below
@@ -1135,7 +1120,7 @@ var fdSeek = newHostFunc(
 	"fd", "offset", "whence", "result.newoffset",
 )
 
-func fdSeekFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdSeekFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd := uint32(params[0])
 	offset := params[1]
@@ -1145,29 +1130,29 @@ func fdSeekFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	var seeker io.Seeker
 	// Check to see if the file descriptor is available
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 		// fs.FS doesn't declare io.Seeker, but implementations such as os.File implement it.
 	} else if _, ft, err := f.CachedStat(); err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	} else if ft.Type() == fs.ModeDir {
-		return ErrnoBadf
+		return syscall.EBADF
 	} else if seeker, ok = f.File.(io.Seeker); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	}
 
 	if whence > io.SeekEnd /* exceeds the largest valid whence */ {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	newOffset, err := seeker.Seek(int64(offset), int(whence))
 	if err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	}
 
 	if !mod.Memory().WriteUint64Le(resultNewoffset, uint64(newOffset)) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdSync is the WASI function named FdSyncName which synchronizes the data
@@ -1176,19 +1161,19 @@ func fdSeekFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_syncfd-fd---errno
 var fdSync = newHostFunc(FdSyncName, fdSyncFn, []api.ValueType{i32}, "fd")
 
-func fdSyncFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdSyncFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	fd := uint32(params[0])
 
 	// Check to see if the file descriptor is available
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	} else if syncFile, ok := f.File.(syncFile); !ok {
-		return ErrnoBadf // possibly a fake file
+		return syscall.EBADF // possibly a fake file
 	} else if err := syncFile.Sync(); err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // fdTell is the WASI function named FdTellName which returns the current
@@ -1197,7 +1182,7 @@ func fdSyncFn(_ context.Context, mod api.Module, params []uint64) Errno {
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-fd_tellfd-fd---errno-filesize
 var fdTell = newHostFunc(FdTellName, fdTellFn, []api.ValueType{i32, i32}, "fd", "result.offset")
 
-func fdTellFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func fdTellFn(ctx context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := params[0]
 	offset := uint64(0)
 	whence := uint64(io.SeekCurrent)
@@ -1223,10 +1208,10 @@ func fdTellFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `iovs` or `resultNwritten` point to an offset out of memory
-//   - ErrnoIo: a file system error
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `iovs` or `resultNwritten` point to an offset out of memory
+//   - syscall.EIO: a file system error
 //
 // For example, this function needs to first read `iovs` to determine what to
 // write to `fd`. If parameters iovs=1 iovsCount=2, this function reads two
@@ -1271,11 +1256,11 @@ var fdWrite = newHostFunc(
 	"fd", "iovs", "iovs_len", "result.nwritten",
 )
 
-func fdWriteFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func fdWriteFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	return fdWriteOrPwrite(mod, params, false)
 }
 
-func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
+func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) syscall.Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
@@ -1286,13 +1271,13 @@ func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
 	var resultNwritten uint32
 	var writer io.Writer
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	} else if isPwrite {
 		offset := int64(params[3])
 		writer = sysfs.WriterAtOffset(f.File, offset)
 		resultNwritten = uint32(params[4])
 	} else if writer, ok = f.File.(io.Writer); !ok {
-		return ErrnoBadf
+		return syscall.EBADF
 	} else {
 		resultNwritten = uint32(params[3])
 	}
@@ -1302,7 +1287,7 @@ func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
 	iovsStop := iovsCount << 3 // iovsCount * 8
 	iovsBuf, ok := mem.Read(iovs, iovsStop)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	for iovsPos := uint32(0); iovsPos < iovsStop; iovsPos += 8 {
@@ -1315,20 +1300,20 @@ func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
 		} else {
 			b, ok := mem.Read(offset, l)
 			if !ok {
-				return ErrnoFault
+				return syscall.EFAULT
 			}
 			n, err = writer.Write(b)
 			if err != nil {
-				return ToErrno(err)
+				return platform.UnwrapOSError(err)
 			}
 		}
 		nwritten += uint32(n)
 	}
 
 	if !mod.Memory().WriteUint32Le(resultNwritten, nwritten) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // pathCreateDirectory is the WASI function named PathCreateDirectoryName which
@@ -1342,10 +1327,10 @@ func fdWriteOrPwrite(mod api.Module, params []uint64, isPwrite bool) Errno {
 //
 // # Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoNoent: `path` does not exist.
-//   - ErrnoNotdir: `path` is a file
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.ENOENT: `path` does not exist.
+//   - syscall.ENOTDIR: `path` is a file
 //
 // # Notes
 //   - This is similar to mkdirat in POSIX.
@@ -1358,7 +1343,7 @@ var pathCreateDirectory = newHostFunc(
 	"fd", "path", "path_len",
 )
 
-func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1366,15 +1351,15 @@ func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
 	pathLen := uint32(params[2])
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
-	if err := preopen.Mkdir(pathName, 0o700); err != nil {
-		return ToErrno(err)
+	if errno = preopen.Mkdir(pathName, 0o700); errno != 0 {
+		return errno
 	}
 
-	return ErrnoSuccess
+	return 0
 }
 
 // pathFilestatGet is the WASI function named PathFilestatGetName which
@@ -1390,14 +1375,14 @@ func pathCreateDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoNotdir: `fd` points to a file not a directory
-//   - ErrnoIo: could not stat `fd` on filesystem
-//   - ErrnoInval: the path contained "../"
-//   - ErrnoNametoolong: `path` + `path_len` is out of memory
-//   - ErrnoFault: `resultFilestat` points to an offset out of memory
-//   - ErrnoNoent: could not find the path
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.ENOTDIR: `fd` points to a file not a directory
+//   - syscall.EIO: could not stat `fd` on filesystem
+//   - syscall.EINVAL: the path contained "../"
+//   - syscall.ENAMETOOLONG: `path` + `path_len` is out of memory
+//   - syscall.EFAULT: `resultFilestat` points to an offset out of memory
+//   - syscall.ENOENT: could not find the path
 //
 // The rest of this implementation matches that of fdFilestatGet, so is not
 // repeated here.
@@ -1411,7 +1396,7 @@ var pathFilestatGet = newHostFunc(
 	"fd", "flags", "path", "path_len", "result.filestat",
 )
 
-func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1420,7 +1405,7 @@ func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno
 	pathLen := uint32(params[3])
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
@@ -1433,28 +1418,24 @@ func pathFilestatGetFn(_ context.Context, mod api.Module, params []uint64) Errno
 	// This could be optimized by modifying Stat/Lstat to return the `Stat_t`
 	// value instead of passing a pointer as output parameter.
 	var st platform.Stat_t
-	var err error
 
 	if (flags & LOOKUP_SYMLINK_FOLLOW) == 0 {
-		st, err = preopen.Lstat(pathName)
+		st, errno = preopen.Lstat(pathName)
 	} else {
-		st, err = preopen.Stat(pathName)
+		st, errno = preopen.Stat(pathName)
 	}
-	if err != nil {
-		return ToErrno(err)
+	if errno != 0 {
+		return errno
 	}
 
 	// Write the stat result to memory
 	resultBuf := uint32(params[4])
 	buf, ok := mod.Memory().Read(resultBuf, 64)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
-	if err = writeFilestat(buf, &st); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	return writeFilestat(buf, &st)
 }
 
 // pathFilestatSetTimes is the WASI function named PathFilestatSetTimesName
@@ -1467,7 +1448,7 @@ var pathFilestatSetTimes = newHostFunc(
 	"fd", "flags", "path", "path_len", "atim", "mtim", "fst_flags",
 )
 
-func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fd := uint32(params[0])
 	flags := uint16(params[1])
 	path := uint32(params[2])
@@ -1480,18 +1461,17 @@ func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64)
 	fsc := sys.FS()
 
 	times, errno := toTimes(atim, mtim, fstFlags)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	symlinkFollow := flags&LOOKUP_SYMLINK_FOLLOW != 0
-	err := preopen.Utimens(pathName, &times, symlinkFollow)
-	return ToErrno(err)
+	return preopen.Utimens(pathName, &times, symlinkFollow)
 }
 
 // pathLink is the WASI function named PathLinkName which adjusts the
@@ -1504,7 +1484,7 @@ var pathLink = newHostFunc(
 	"old_fd", "old_flags", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
 )
 
-func pathLinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathLinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	mem := mod.Memory()
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
@@ -1515,7 +1495,7 @@ func pathLinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	oldPathLen := uint32(params[3])
 
 	oldFS, oldName, errno := atPath(fsc, mem, oldFd, oldPath, oldPathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
@@ -1524,22 +1504,19 @@ func pathLinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	newPathLen := uint32(params[6])
 
 	newFS, newName, errno := atPath(fsc, mem, newFD, newPath, newPathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	if oldFS != newFS { // TODO: handle link across filesystems
-		return ErrnoNosys
+		return syscall.ENOSYS
 	}
 
-	if err := oldFS.Link(oldName, newName); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	return oldFS.Link(oldName, newName)
 }
 
 // pathOpen is the WASI function named PathOpenName which opens a file or
-// directory. This returns ErrnoBadf if the fd is invalid.
+// directory. This returns syscall.EBADF if the fd is invalid.
 //
 // # Parameters
 //
@@ -1558,13 +1535,13 @@ func pathLinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoFault: `resultOpenedFd` points to an offset out of memory
-//   - ErrnoNoent: `path` does not exist.
-//   - ErrnoExist: `path` exists, while `oFlags` requires that it must not.
-//   - ErrnoNotdir: `path` is not a directory, while `oFlags` requires it.
-//   - ErrnoIo: a file system error
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.EFAULT: `resultOpenedFd` points to an offset out of memory
+//   - syscall.ENOENT: `path` does not exist.
+//   - syscall.EEXIST: `path` exists, while `oFlags` requires that it must not.
+//   - syscall.ENOTDIR: `path` is not a directory, while `oFlags` requires it.
+//   - syscall.EIO: a file system error
 //
 // For example, this function needs to first read `path` to determine the file
 // to open. If parameters `path` = 1, `pathLen` = 6, and the path is "wazero",
@@ -1597,7 +1574,7 @@ var pathOpen = newHostFunc(
 	"fd", "dirflags", "path", "path_len", "oflags", "fs_rights_base", "fs_rights_inheriting", "fdflags", "result.opened_fd",
 )
 
-func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathOpenFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	preopenFD := uint32(params[0])
@@ -1619,7 +1596,7 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	resultOpenedFd := uint32(params[8])
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), preopenFD, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
@@ -1627,32 +1604,32 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	isDir := fileOpenFlags&platform.O_DIRECTORY != 0
 
 	if isDir && oflags&O_CREAT != 0 {
-		return ErrnoInval // use pathCreateDirectory!
+		return syscall.EINVAL // use pathCreateDirectory!
 	}
 
-	newFD, err := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600)
-	if err != nil {
-		return ToErrno(err)
+	newFD, errno := fsc.OpenFile(preopen, pathName, fileOpenFlags, 0o600)
+	if errno != 0 {
+		return errno
 	}
 
 	// Check any flags that require the file to evaluate.
 	if isDir {
 		if f, ok := fsc.LookupFile(newFD); !ok {
-			return ErrnoBadf // unexpected
+			return syscall.EBADF // unexpected
 		} else if _, ft, err := f.CachedStat(); err != nil {
 			_ = fsc.CloseFile(newFD)
-			return ToErrno(err)
+			return platform.UnwrapOSError(err)
 		} else if ft.Type() != fs.ModeDir {
 			_ = fsc.CloseFile(newFD)
-			return ErrnoNotdir
+			return syscall.ENOTDIR
 		}
 	}
 
 	if !mod.Memory().WriteUint32Le(resultOpenedFd, newFD) {
 		_ = fsc.CloseFile(newFD)
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // atPath returns the pre-open specific path after verifying it is a directory.
@@ -1671,34 +1648,34 @@ func pathOpenFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // See https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/at_fdcwd.c
 // See https://linux.die.net/man/2/openat
-func atPath(fsc *sys.FSContext, mem api.Memory, fd, path, pathLen uint32) (sysfs.FS, string, Errno) {
+func atPath(fsc *sys.FSContext, mem api.Memory, fd, path, pathLen uint32) (sysfs.FS, string, syscall.Errno) {
 	b, ok := mem.Read(path, pathLen)
 	if !ok {
-		return nil, "", ErrnoFault
+		return nil, "", syscall.EFAULT
 	}
 	pathName := string(b)
 
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return nil, "", ErrnoBadf // closed
+		return nil, "", syscall.EBADF // closed
 	} else if _, ft, err := f.CachedStat(); err != nil {
-		return nil, "", ToErrno(err)
+		return nil, "", platform.UnwrapOSError(err)
 	} else if ft.Type() != fs.ModeDir {
-		return nil, "", ErrnoNotdir
+		return nil, "", syscall.ENOTDIR
 	} else if f.IsPreopen { // don't append the pre-open name
-		return f.FS, pathName, ErrnoSuccess
+		return f.FS, pathName, 0
 	} else {
 		// Join via concat to avoid name conflict on path.Join
-		return f.FS, f.Name + "/" + pathName, ErrnoSuccess
+		return f.FS, f.Name + "/" + pathName, 0
 	}
 }
 
-func preopenPath(fsc *sys.FSContext, fd uint32) (string, Errno) {
+func preopenPath(fsc *sys.FSContext, fd uint32) (string, syscall.Errno) {
 	if f, ok := fsc.LookupFile(fd); !ok {
-		return "", ErrnoBadf // closed
+		return "", syscall.EBADF // closed
 	} else if !f.IsPreopen {
-		return "", ErrnoBadf
+		return "", syscall.EBADF
 	} else {
-		return f.Name, ErrnoSuccess
+		return f.Name, 0
 	}
 }
 
@@ -1744,7 +1721,7 @@ var pathReadlink = newHostFunc(
 	"fd", "path", "path_len", "buf", "buf_len", "result.bufused",
 )
 
-func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1755,28 +1732,28 @@ func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	resultBufused := uint32(params[5])
 
 	if pathLen == 0 || bufLen == 0 {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	mem := mod.Memory()
-	preopen, p, en := atPath(fsc, mem, fd, path, pathLen)
-	if en != ErrnoSuccess {
-		return en
+	preopen, p, errno := atPath(fsc, mem, fd, path, pathLen)
+	if errno != 0 {
+		return errno
 	}
 
-	dst, err := preopen.Readlink(p)
-	if err != nil {
-		return ToErrno(err)
+	dst, errno := preopen.Readlink(p)
+	if errno != 0 {
+		return errno
 	}
 
 	if ok := mem.WriteString(buf, dst); !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	if !mem.WriteUint32Le(resultBufused, uint32(len(dst))) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // pathRemoveDirectory is the WASI function named PathRemoveDirectoryName which
@@ -1790,11 +1767,11 @@ func pathReadlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 //
 // # Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoNoent: `path` does not exist.
-//   - ErrnoNotempty: `path` is not empty
-//   - ErrnoNotdir: `path` is a file
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.ENOENT: `path` does not exist.
+//   - syscall.ENOTEMPTY: `path` is not empty
+//   - syscall.ENOTDIR: `path` is a file
 //
 // # Notes
 //   - This is similar to unlinkat with AT_REMOVEDIR in POSIX.
@@ -1807,7 +1784,7 @@ var pathRemoveDirectory = newHostFunc(
 	"fd", "path", "path_len",
 )
 
-func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1815,15 +1792,11 @@ func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
 	pathLen := uint32(params[2])
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
-	if err := preopen.Rmdir(pathName); err != nil {
-		return ToErrno(err)
-	}
-
-	return ErrnoSuccess
+	return preopen.Rmdir(pathName)
 }
 
 // pathRename is the WASI function named PathRenameName which renames a file or
@@ -1840,11 +1813,11 @@ func pathRemoveDirectoryFn(_ context.Context, mod api.Module, params []uint64) E
 //
 // # Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` or `new_fd` are invalid
-//   - ErrnoNoent: `old_path` does not exist.
-//   - ErrnoNotdir: `old` is a directory and `new` exists, but is a file.
-//   - ErrnoIsdir: `old` is a file and `new` exists, but is a directory.
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` or `new_fd` are invalid
+//   - syscall.ENOENT: `old_path` does not exist.
+//   - syscall.ENOTDIR: `old` is a directory and `new` exists, but is a file.
+//   - syscall.EISDIR: `old` is a file and `new` exists, but is a directory.
 //
 // # Notes
 //   - This is similar to unlinkat in POSIX.
@@ -1857,7 +1830,7 @@ var pathRename = newHostFunc(
 	"fd", "old_path", "old_path_len", "new_fd", "new_path", "new_path_len",
 )
 
-func pathRenameFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathRenameFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1869,24 +1842,20 @@ func pathRenameFn(_ context.Context, mod api.Module, params []uint64) Errno {
 	newPathLen := uint32(params[5])
 
 	oldFS, oldPathName, errno := atPath(fsc, mod.Memory(), fd, oldPath, oldPathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	newFS, newPathName, errno := atPath(fsc, mod.Memory(), newFD, newPath, newPathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
 	if oldFS != newFS { // TODO: handle renames across filesystems
-		return ErrnoNosys
-	}
-
-	if err := oldFS.Rename(oldPathName, newPathName); err != nil {
-		return ToErrno(err)
+		return syscall.ENOSYS
 	}
 
-	return ErrnoSuccess
+	return oldFS.Rename(oldPathName, newPathName)
 }
 
 // pathSymlink is the WASI function named PathSymlinkName which creates a
@@ -1899,7 +1868,7 @@ var pathSymlink = newHostFunc(
 	"old_path", "old_path_len", "fd", "new_path", "new_path_len",
 )
 
-func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	oldPath := uint32(params[0])
@@ -1912,36 +1881,33 @@ func pathSymlinkFn(_ context.Context, mod api.Module, params []uint64) Errno {
 
 	dir, ok := fsc.LookupFile(fd)
 	if !ok {
-		return ErrnoBadf // closed
+		return syscall.EBADF // closed
 	} else if _, ft, err := dir.CachedStat(); err != nil {
-		return ToErrno(err)
+		return platform.UnwrapOSError(err)
 	} else if ft.Type() != fs.ModeDir {
-		return ErrnoNotdir
+		return syscall.ENOTDIR
 	}
 
 	if oldPathLen == 0 || newPathLen == 0 {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	oldPathBuf, ok := mem.Read(oldPath, oldPathLen)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	newPathBuf, ok := mem.Read(newPath, newPathLen)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
-	if err := dir.FS.Symlink(
+	return dir.FS.Symlink(
 		// Do not join old path since it's only resolved when dereference the link created here.
 		// And the dereference result depends on the opening directory's file descriptor at that point.
 		bufToStr(oldPathBuf, int(oldPathLen)),
 		path.Join(dir.Name, bufToStr(newPathBuf, int(newPathLen))),
-	); err != nil {
-		return ToErrno(err)
-	}
-	return ErrnoSuccess
+	)
 }
 
 // bufToStr converts the given byte slice as string unsafely.
@@ -1964,10 +1930,10 @@ func bufToStr(buf []byte, l int) string {
 //
 // # Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoBadf: `fd` is invalid
-//   - ErrnoNoent: `path` does not exist.
-//   - ErrnoIsdir: `path` is a directory
+// The return value is 0 except the following error conditions:
+//   - syscall.EBADF: `fd` is invalid
+//   - syscall.ENOENT: `path` does not exist.
+//   - syscall.EISDIR: `path` is a directory
 //
 // # Notes
 //   - This is similar to unlinkat without AT_REMOVEDIR in POSIX.
@@ -1980,7 +1946,7 @@ var pathUnlinkFile = newHostFunc(
 	"fd", "path", "path_len",
 )
 
-func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	fd := uint32(params[0])
@@ -1988,13 +1954,9 @@ func pathUnlinkFileFn(_ context.Context, mod api.Module, params []uint64) Errno
 	pathLen := uint32(params[2])
 
 	preopen, pathName, errno := atPath(fsc, mod.Memory(), fd, path, pathLen)
-	if errno != ErrnoSuccess {
+	if errno != 0 {
 		return errno
 	}
 
-	if err := preopen.Unlink(pathName); err != nil {
-		return ToErrno(err)
-	}
-
-	return ErrnoSuccess
+	return preopen.Unlink(pathName)
 }
diff --git a/imports/wasi_snapshot_preview1/poll.go b/imports/wasi_snapshot_preview1/poll.go
index 38230839..7084d033 100644
--- a/imports/wasi_snapshot_preview1/poll.go
+++ b/imports/wasi_snapshot_preview1/poll.go
@@ -2,6 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"context"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	"github.com/tetratelabs/wazero/internal/platform"
@@ -17,20 +18,20 @@ import (
 //
 //   - in: pointer to the subscriptions (48 bytes each)
 //   - out: pointer to the resulting events (32 bytes each)
-//   - nsubscriptions: count of subscriptions, zero returns ErrnoInval.
+//   - nsubscriptions: count of subscriptions, zero returns syscall.EINVAL.
 //   - resultNevents: count of events.
 //
 // Result (Errno)
 //
-// The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoInval: the parameters are invalid
-//   - ErrnoNotsup: a parameters is valid, but not yet supported.
-//   - ErrnoFault: there is not enough memory to read the subscriptions or
+// The return value is 0 except the following error conditions:
+//   - syscall.EINVAL: the parameters are invalid
+//   - syscall.ENOTSUP: a parameters is valid, but not yet supported.
+//   - syscall.EFAULT: there is not enough memory to read the subscriptions or
 //     write results.
 //
 // # Notes
 //
-//   - Since the `out` pointer nests Errno, the result is always ErrnoSuccess.
+//   - Since the `out` pointer nests Errno, the result is always 0.
 //   - This is similar to `poll` in POSIX.
 //
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#poll_oneoff
@@ -41,14 +42,14 @@ var pollOneoff = newHostFunc(
 	"in", "out", "nsubscriptions", "result.nevents",
 )
 
-func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
+func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) syscall.Errno {
 	in := uint32(params[0])
 	out := uint32(params[1])
 	nsubscriptions := uint32(params[2])
 	resultNevents := uint32(params[3])
 
 	if nsubscriptions == 0 {
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	mem := mod.Memory()
@@ -56,17 +57,17 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 	// Ensure capacity prior to the read loop to reduce error handling.
 	inBuf, ok := mem.Read(in, nsubscriptions*48)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 	outBuf, ok := mem.Read(out, nsubscriptions*32)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	// Eagerly write the number of events which will equal subscriptions unless
 	// there's a fault in parsing (not processing).
 	if !mod.Memory().WriteUint32Le(resultNevents, nsubscriptions) {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	// Loop through all subscriptions and write their output.
@@ -78,7 +79,7 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 		outOffset := i * 32
 
 		eventType := inBuf[inOffset+8] // +8 past userdata
-		var errno Errno                // errno for this specific event (1-byte)
+		var errno syscall.Errno        // errno for this specific event (1-byte)
 		switch eventType {
 		case EventTypeClock: // handle later
 			// +8 past userdata +8 contents_offset
@@ -87,23 +88,27 @@ func pollOneoffFn(ctx context.Context, mod api.Module, params []uint64) Errno {
 			// +8 past userdata +8 contents_offset
 			errno = processFDEvent(mod, eventType, inBuf[inOffset+8+8:])
 		default:
-			return ErrnoInval
+			return syscall.EINVAL
 		}
 
 		// Write the event corresponding to the processed subscription.
 		// https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-event-struct
 		copy(outBuf, inBuf[inOffset:inOffset+8]) // userdata
-		outBuf[outOffset+8] = byte(errno)        // uint16, but safe as < 255
+		if errno != 0 {
+			outBuf[outOffset+8] = byte(ToErrno(errno)) // uint16, but safe as < 255
+		} else { // special case ass ErrnoSuccess is zero
+			outBuf[outOffset+8] = 0
+		}
 		outBuf[outOffset+9] = 0
 		le.PutUint32(outBuf[outOffset+10:], uint32(eventType))
 		// TODO: When FD events are supported, write outOffset+16
 	}
-	return ErrnoSuccess
+	return 0
 }
 
 // processClockEvent supports only relative name events, as that's what's used
 // to implement sleep in various compilers including Rust, Zig and TinyGo.
-func processClockEvent(_ context.Context, mod api.Module, inBuf []byte) Errno {
+func processClockEvent(_ context.Context, mod api.Module, inBuf []byte) syscall.Errno {
 	_ /* ID */ = le.Uint32(inBuf[0:8])          // See below
 	timeout := le.Uint64(inBuf[8:16])           // nanos if relative
 	_ /* precision */ = le.Uint64(inBuf[16:24]) // Unused
@@ -113,9 +118,9 @@ func processClockEvent(_ context.Context, mod api.Module, inBuf []byte) Errno {
 	switch flags {
 	case 0: // relative time
 	case 1: // subscription_clock_abstime
-		return ErrnoNotsup
+		return syscall.ENOTSUP
 	default: // subclockflags has only one flag defined.
-		return ErrnoInval
+		return syscall.EINVAL
 	}
 
 	// https://linux.die.net/man/3/clock_settime says relative timers are
@@ -124,29 +129,29 @@ func processClockEvent(_ context.Context, mod api.Module, inBuf []byte) Errno {
 
 	sysCtx := mod.(*wasm.CallContext).Sys
 	sysCtx.Nanosleep(int64(timeout))
-	return ErrnoSuccess
+	return 0
 }
 
-// processFDEvent returns a validation error or ErrnoNotsup as file or socket
+// processFDEvent returns a validation error or syscall.ENOTSUP as file or socket
 // subscriptions are not yet supported.
-func processFDEvent(mod api.Module, eventType byte, inBuf []byte) Errno {
+func processFDEvent(mod api.Module, eventType byte, inBuf []byte) syscall.Errno {
 	fd := le.Uint32(inBuf)
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	// Choose the best error, which falls back to unsupported, until we support
 	// files.
-	errno := ErrnoNotsup
+	errno := syscall.ENOTSUP
 	if eventType == EventTypeFdRead {
 		if _, ok := fsc.LookupFile(fd); ok {
 			// If fd is a pipe, IsTerminal is false.
 			if platform.IsTerminal(uintptr(fd)) {
-				errno = ErrnoBadf
+				errno = syscall.EBADF
 			}
 		} else {
-			errno = ErrnoBadf
+			errno = syscall.EBADF
 		}
 	} else if eventType == EventTypeFdWrite && internalsys.WriterForFile(fsc, fd) == nil {
-		errno = ErrnoBadf
+		errno = syscall.EBADF
 	}
 
 	return errno
diff --git a/imports/wasi_snapshot_preview1/random.go b/imports/wasi_snapshot_preview1/random.go
index 0e841afd..73ed1d72 100644
--- a/imports/wasi_snapshot_preview1/random.go
+++ b/imports/wasi_snapshot_preview1/random.go
@@ -3,6 +3,7 @@ package wasi_snapshot_preview1
 import (
 	"context"
 	"io"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -20,8 +21,8 @@ import (
 // Result (Errno)
 //
 // The return value is ErrnoSuccess except the following error conditions:
-//   - ErrnoFault: `buf` or `bufLen` point to an offset out of memory
-//   - ErrnoIo: a file system error
+//   - syscall.EFAULT: `buf` or `bufLen` point to an offset out of memory
+//   - syscall.EIO: a file system error
 //
 // For example, if underlying random source was seeded like
 // `rand.NewSource(42)`, we expect api.Memory to contain:
@@ -35,20 +36,20 @@ import (
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-random_getbuf-pointeru8-bufLen-size---errno
 var randomGet = newHostFunc(RandomGetName, randomGetFn, []api.ValueType{i32, i32}, "buf", "buf_len")
 
-func randomGetFn(_ context.Context, mod api.Module, params []uint64) Errno {
+func randomGetFn(_ context.Context, mod api.Module, params []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	randSource := sysCtx.RandSource()
 	buf, bufLen := uint32(params[0]), uint32(params[1])
 
 	randomBytes, ok := mod.Memory().Read(buf, bufLen)
 	if !ok { // out-of-range
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	// We can ignore the returned n as it only != byteCount on error
 	if _, err := io.ReadAtLeast(randSource, randomBytes, int(bufLen)); err != nil {
-		return ErrnoIo
+		return syscall.EIO
 	}
 
-	return ErrnoSuccess
+	return 0
 }
diff --git a/imports/wasi_snapshot_preview1/sched.go b/imports/wasi_snapshot_preview1/sched.go
index 3a135814..d9c7151d 100644
--- a/imports/wasi_snapshot_preview1/sched.go
+++ b/imports/wasi_snapshot_preview1/sched.go
@@ -2,6 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"context"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/api"
 	. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
@@ -14,8 +15,8 @@ import (
 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#-sched_yield---errno
 var schedYield = newHostFunc(SchedYieldName, schedYieldFn, nil)
 
-func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) Errno {
+func schedYieldFn(_ context.Context, mod api.Module, _ []uint64) syscall.Errno {
 	sysCtx := mod.(*wasm.CallContext).Sys
 	sysCtx.Osyield()
-	return ErrnoSuccess
+	return 0
 }
diff --git a/imports/wasi_snapshot_preview1/wasi.go b/imports/wasi_snapshot_preview1/wasi.go
index 9c3ed19a..acf084ee 100644
--- a/imports/wasi_snapshot_preview1/wasi.go
+++ b/imports/wasi_snapshot_preview1/wasi.go
@@ -19,6 +19,7 @@ package wasi_snapshot_preview1
 import (
 	"context"
 	"encoding/binary"
+	"syscall"
 
 	"github.com/tetratelabs/wazero"
 	"github.com/tetratelabs/wazero/api"
@@ -218,18 +219,18 @@ func exportFunctions(builder wazero.HostModuleBuilder) {
 // writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values
 // for args or environ, given a pre-defined bytesLen (which includes NUL
 // terminators).
-func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) Errno {
+func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) syscall.Errno {
 	// The caller may not place bytes directly after offsets, so we have to
 	// read them independently.
 	valuesLen := len(values)
 	offsetsLen := uint32(valuesLen * 4) // uint32Le
 	offsetsBuf, ok := mem.Read(offsets, offsetsLen)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 	bytesBuf, ok := mem.Read(bytes, bytesLen)
 	if !ok {
-		return ErrnoFault
+		return syscall.EFAULT
 	}
 
 	// Loop through the values, first writing the location of its data to
@@ -252,7 +253,7 @@ func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offset
 		bI++
 	}
 
-	return ErrnoSuccess
+	return 0
 }
 
 func newHostFunc(
@@ -274,12 +275,17 @@ func newHostFunc(
 
 // wasiFunc special cases that all WASI functions return a single Errno
 // result. The returned value will be written back to the stack at index zero.
-type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) Errno
+type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) syscall.Errno
 
 // Call implements the same method as documented on api.GoModuleFunction.
 func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
 	// Write the result back onto the stack
-	stack[0] = uint64(f(ctx, mod, stack))
+	errno := f(ctx, mod, stack)
+	if errno != 0 {
+		stack[0] = uint64(ToErrno(errno))
+	} else { // special case ass ErrnoSuccess is zero
+		stack[0] = 0
+	}
 }
 
 // stubFunction stubs for GrainLang per #271.
diff --git a/internal/gojs/errno.go b/internal/gojs/errno.go
index 24d8b179..afc6ee07 100644
--- a/internal/gojs/errno.go
+++ b/internal/gojs/errno.go
@@ -30,6 +30,8 @@ var (
 	ErrnoBadf = &Errno{"EBADF"}
 	// ErrnoExist File exists.
 	ErrnoExist = &Errno{"EEXIST"}
+	// ErrnoFault Bad address.
+	ErrnoFault = &Errno{"EFAULT"}
 	// ErrnoIntr Interrupted function.
 	ErrnoIntr = &Errno{"EINTR"}
 	// ErrnoInval Invalid argument.
@@ -77,6 +79,8 @@ func ToErrno(err error) *Errno {
 		return ErrnoBadf
 	case syscall.EEXIST:
 		return ErrnoExist
+	case syscall.EFAULT:
+		return ErrnoFault
 	case syscall.EINTR:
 		return ErrnoIntr
 	case syscall.EINVAL:
diff --git a/internal/gojs/fs.go b/internal/gojs/fs.go
index dd93aa55..1737c7c0 100644
--- a/internal/gojs/fs.go
+++ b/internal/gojs/fs.go
@@ -110,9 +110,9 @@ func (o *jsfsOpen) invoke(ctx context.Context, mod api.Module, args ...interface
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	fd, err := fsc.OpenFile(fsc.RootFS(), path, int(flags), perm)
+	fd, errno := fsc.OpenFile(fsc.RootFS(), path, int(flags), perm)
 
-	return callback.invoke(ctx, mod, goos.RefJsfs, err, fd) // note: error first
+	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first
 }
 
 // jsfsStat implements jsFn for syscall.Stat
@@ -134,8 +134,8 @@ func (s *jsfsStat) invoke(ctx context.Context, mod api.Module, args ...interface
 func syscallStat(mod api.Module, path string) (*jsSt, error) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	if st, err := fsc.RootFS().Stat(path); err != nil {
-		return nil, err
+	if st, errno := fsc.RootFS().Stat(path); errno != 0 {
+		return nil, errno
 	} else {
 		return newJsSt(st), nil
 	}
@@ -161,8 +161,8 @@ func (l *jsfsLstat) invoke(ctx context.Context, mod api.Module, args ...interfac
 func syscallLstat(mod api.Module, path string) (*jsSt, error) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	if st, err := fsc.RootFS().Lstat(path); err != nil {
-		return nil, err
+	if st, errno := fsc.RootFS().Lstat(path); errno != 0 {
+		return nil, errno
 	} else {
 		return newJsSt(st), nil
 	}
@@ -191,7 +191,7 @@ func syscallFstat(fsc *internalsys.FSContext, fd uint32) (*jsSt, error) {
 	}
 
 	if st, err := f.Stat(); err != nil {
-		return nil, err
+		return nil, platform.UnwrapOSError(err)
 	} else {
 		return newJsSt(st), nil
 	}
@@ -222,9 +222,9 @@ func (jsfsClose) invoke(ctx context.Context, mod api.Module, args ...interface{}
 	fd := goos.ValueToUint32(args[0])
 	callback := args[1].(funcWrapper)
 
-	err := fsc.CloseFile(fd)
+	errno := fsc.CloseFile(fd)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsRead implements jsFn for syscall.Read and syscall.Pread, called by
@@ -345,14 +345,14 @@ func syscallReaddir(_ context.Context, mod api.Module, name string) (*objectArra
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
 	// don't allocate a file descriptor
-	f, err := fsc.RootFS().OpenFile(name, os.O_RDONLY, 0)
-	if err != nil {
-		return nil, err
+	f, errno := fsc.RootFS().OpenFile(name, os.O_RDONLY, 0)
+	if errno != 0 {
+		return nil, errno
 	}
 	defer f.Close() //nolint
 
-	if names, err := platform.Readdirnames(f, -1); err != nil {
-		return nil, err
+	if names, errno := platform.Readdirnames(f, -1); errno != 0 {
+		return nil, errno
 	} else {
 		entries := make([]interface{}, 0, len(names))
 		for _, e := range names {
@@ -378,16 +378,16 @@ func (m *jsfsMkdir) invoke(ctx context.Context, mod api.Module, args ...interfac
 	root := fsc.RootFS()
 
 	var fd uint32
-	var err error
+	var errno syscall.Errno
 	// We need at least read access to open the file descriptor
 	if perm == 0 {
 		perm = 0o0500
 	}
-	if err = root.Mkdir(path, perm); err == nil {
-		fd, err = fsc.OpenFile(root, path, os.O_RDONLY, 0)
+	if errno = root.Mkdir(path, perm); errno == 0 {
+		fd, errno = fsc.OpenFile(root, path, os.O_RDONLY, 0)
 	}
 
-	return callback.invoke(ctx, mod, goos.RefJsfs, err, fd) // note: error first
+	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), fd) // note: error first
 }
 
 // jsfsRmdir implements jsFn for the following
@@ -402,9 +402,9 @@ func (r *jsfsRmdir) invoke(ctx context.Context, mod api.Module, args ...interfac
 	callback := args[1].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Rmdir(path)
+	errno := fsc.RootFS().Rmdir(path)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsRename implements jsFn for the following
@@ -421,9 +421,9 @@ func (r *jsfsRename) invoke(ctx context.Context, mod api.Module, args ...interfa
 	callback := args[2].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Rename(from, to)
+	errno := fsc.RootFS().Rename(from, to)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsUnlink implements jsFn for the following
@@ -438,9 +438,9 @@ func (u *jsfsUnlink) invoke(ctx context.Context, mod api.Module, args ...interfa
 	callback := args[1].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Unlink(path)
+	errno := fsc.RootFS().Unlink(path)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsUtimes implements jsFn for the following
@@ -460,9 +460,9 @@ func (u *jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interfa
 	times := [2]syscall.Timespec{
 		syscall.NsecToTimespec(atimeSec * 1e9), syscall.NsecToTimespec(mtimeSec * 1e9),
 	}
-	err := fsc.RootFS().Utimens(path, &times, true)
+	errno := fsc.RootFS().Utimens(path, &times, true)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsChmod implements jsFn for the following
@@ -478,9 +478,9 @@ func (c *jsfsChmod) invoke(ctx context.Context, mod api.Module, args ...interfac
 	callback := args[2].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Chmod(path, mode)
+	errno := fsc.RootFS().Chmod(path, mode)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsFchmod implements jsFn for the following
@@ -495,16 +495,16 @@ func (jsfsFchmod) invoke(ctx context.Context, mod api.Module, args ...interface{
 
 	// Check to see if the file descriptor is available
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	var err error
+	var errno syscall.Errno
 	if f, ok := fsc.LookupFile(fd); !ok {
-		err = syscall.EBADF
+		errno = syscall.EBADF
 	} else if chmodFile, ok := f.File.(chmodFile); !ok {
-		err = syscall.EBADF // possibly a fake file
+		errno = syscall.EBADF // possibly a fake file
 	} else {
-		err = chmodFile.Chmod(mode)
+		errno = platform.UnwrapOSError(chmodFile.Chmod(mode))
 	}
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsChown implements jsFn for the following
@@ -521,9 +521,9 @@ func (c *jsfsChown) invoke(ctx context.Context, mod api.Module, args ...interfac
 	callback := args[3].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Chown(path, int(uid), int(gid))
+	errno := fsc.RootFS().Chown(path, int(uid), int(gid))
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsFchown implements jsFn for the following
@@ -539,14 +539,14 @@ func (jsfsFchown) invoke(ctx context.Context, mod api.Module, args ...interface{
 
 	// Check to see if the file descriptor is available
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	var err error
+	var errno syscall.Errno
 	if f, ok := fsc.LookupFile(fd); !ok {
-		err = syscall.EBADF
+		errno = syscall.EBADF
 	} else {
-		err = platform.ChownFile(f.File, int(uid), int(gid))
+		errno = platform.ChownFile(f.File, int(uid), int(gid))
 	}
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsLchown implements jsFn for the following
@@ -563,9 +563,9 @@ func (l *jsfsLchown) invoke(ctx context.Context, mod api.Module, args ...interfa
 	callback := args[3].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Lchown(path, int(uid), int(gid))
+	errno := fsc.RootFS().Lchown(path, int(uid), int(gid))
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsTruncate implements jsFn for the following
@@ -581,9 +581,9 @@ func (t *jsfsTruncate) invoke(ctx context.Context, mod api.Module, args ...inter
 	callback := args[2].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Truncate(path, length)
+	errno := fsc.RootFS().Truncate(path, length)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsFtruncate implements jsFn for the following
@@ -598,16 +598,16 @@ func (jsfsFtruncate) invoke(ctx context.Context, mod api.Module, args ...interfa
 
 	// Check to see if the file descriptor is available
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	var err error
+	var errno syscall.Errno
 	if f, ok := fsc.LookupFile(fd); !ok {
-		err = syscall.EBADF
+		errno = syscall.EBADF
 	} else if truncateFile, ok := f.File.(truncateFile); !ok {
-		err = syscall.EBADF // possibly a fake file
+		errno = syscall.EBADF // possibly a fake file
 	} else {
-		err = truncateFile.Truncate(length)
+		errno = platform.UnwrapOSError(truncateFile.Truncate(length))
 	}
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsReadlink implements jsFn for syscall.Readlink
@@ -622,9 +622,9 @@ func (r *jsfsReadlink) invoke(ctx context.Context, mod api.Module, args ...inter
 	callback := args[1].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	dst, err := fsc.RootFS().Readlink(path)
+	dst, errno := fsc.RootFS().Readlink(path)
 
-	return callback.invoke(ctx, mod, goos.RefJsfs, err, dst) // note: error first
+	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(errno), dst) // note: error first
 }
 
 // jsfsLink implements jsFn for the following
@@ -641,9 +641,9 @@ func (l *jsfsLink) invoke(ctx context.Context, mod api.Module, args ...interface
 	callback := args[2].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Link(path, link)
+	errno := fsc.RootFS().Link(path, link)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsSymlink implements jsFn for the following
@@ -659,9 +659,9 @@ func (s *jsfsSymlink) invoke(ctx context.Context, mod api.Module, args ...interf
 	callback := args[2].(funcWrapper)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	err := fsc.RootFS().Symlink(dst, link)
+	errno := fsc.RootFS().Symlink(dst, link)
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsfsFsync implements jsFn for the following
@@ -675,16 +675,16 @@ func (jsfsFsync) invoke(ctx context.Context, mod api.Module, args ...interface{}
 
 	// Check to see if the file descriptor is available
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	var err error
+	var errno syscall.Errno
 	if f, ok := fsc.LookupFile(fd); !ok {
-		err = syscall.EBADF
+		errno = syscall.EBADF
 	} else if syncFile, ok := f.File.(syncFile); !ok {
-		err = syscall.EBADF // possibly a fake file
+		errno = syscall.EBADF // possibly a fake file
 	} else {
-		err = syncFile.Sync()
+		errno = platform.UnwrapOSError(syncFile.Sync())
 	}
 
-	return jsfsInvoke(ctx, mod, callback, err)
+	return jsfsInvoke(ctx, mod, callback, errno)
 }
 
 // jsSt is pre-parsed from fs_js.go setStat to avoid thrashing
@@ -751,6 +751,13 @@ func (s *jsSt) call(_ context.Context, _ api.Module, _ goos.Ref, method string,
 	panic(fmt.Sprintf("TODO: stat.%s", method))
 }
 
-func jsfsInvoke(ctx context.Context, mod api.Module, callback funcWrapper, err error) (interface{}, error) {
-	return callback.invoke(ctx, mod, goos.RefJsfs, err, err == nil) // note: error first
+func jsfsInvoke(ctx context.Context, mod api.Module, callback funcWrapper, err syscall.Errno) (interface{}, error) {
+	return callback.invoke(ctx, mod, goos.RefJsfs, maybeError(err), err == 0) // note: error first
+}
+
+func maybeError(errno syscall.Errno) error {
+	if errno != 0 {
+		return errno
+	}
+	return nil
 }
diff --git a/internal/platform/chown.go b/internal/platform/chown.go
index fd169025..d8c273d5 100644
--- a/internal/platform/chown.go
+++ b/internal/platform/chown.go
@@ -8,20 +8,22 @@ import (
 
 // Chown is like os.Chown, except it returns a syscall.Errno, not a
 // fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
-// exist. See https://linux.die.net/man/3/chown
+// exist. A syscall.Errno of zero is success.
 //
 // Note: This always returns syscall.ENOSYS on windows.
-func Chown(path string, uid, gid int) error {
+// See https://linux.die.net/man/3/chown
+func Chown(path string, uid, gid int) syscall.Errno {
 	err := os.Chown(path, uid, gid)
 	return UnwrapOSError(err)
 }
 
 // Lchown is like os.Lchown, except it returns a syscall.Errno, not a
 // fs.PathError. For example, this returns syscall.ENOENT if the path doesn't
-// exist. See https://linux.die.net/man/3/lchown
+// exist. A syscall.Errno of zero is success.
 //
 // Note: This always returns syscall.ENOSYS on windows.
-func Lchown(path string, uid, gid int) error {
+// See https://linux.die.net/man/3/lchown
+func Lchown(path string, uid, gid int) syscall.Errno {
 	err := os.Lchown(path, uid, gid)
 	return UnwrapOSError(err)
 }
@@ -31,10 +33,9 @@ func Lchown(path string, uid, gid int) error {
 // or directory was closed. See https://linux.die.net/man/3/fchown
 //
 // Note: This always returns syscall.ENOSYS on windows.
-func ChownFile(f fs.File, uid, gid int) error {
+func ChownFile(f fs.File, uid, gid int) syscall.Errno {
 	if f, ok := f.(fdFile); ok {
-		err := fchown(f.Fd(), uid, gid)
-		return UnwrapOSError(err)
+		return fchown(f.Fd(), uid, gid)
 	}
 	return syscall.ENOSYS
 }
diff --git a/internal/platform/chown_unix.go b/internal/platform/chown_unix.go
index 69bdf9a4..5e0753da 100644
--- a/internal/platform/chown_unix.go
+++ b/internal/platform/chown_unix.go
@@ -4,6 +4,6 @@ package platform
 
 import "syscall"
 
-func fchown(fd uintptr, uid, gid int) error {
-	return syscall.Fchown(int(fd), uid, gid)
+func fchown(fd uintptr, uid, gid int) syscall.Errno {
+	return UnwrapOSError(syscall.Fchown(int(fd), uid, gid))
 }
diff --git a/internal/platform/chown_unsupported.go b/internal/platform/chown_unsupported.go
index 1fec3803..38b7187d 100644
--- a/internal/platform/chown_unsupported.go
+++ b/internal/platform/chown_unsupported.go
@@ -6,6 +6,6 @@ import "syscall"
 
 // fchown is not supported on windows. For example, syscall.Fchown returns
 // syscall.EWINDOWS, which is the same as syscall.ENOSYS.
-func fchown(fd uintptr, uid, gid int) error {
+func fchown(fd uintptr, uid, gid int) syscall.Errno {
 	return syscall.ENOSYS
 }
diff --git a/internal/platform/dir.go b/internal/platform/dir.go
index d9c338e3..b58386df 100644
--- a/internal/platform/dir.go
+++ b/internal/platform/dir.go
@@ -16,23 +16,22 @@ import (
 //
 // # Errors
 //
-// If the error is non-nil, it is a syscall.Errno. io.EOF is not returned
-// because implementations return that value inconsistently.
+// A zero syscall.Errno is success.
 //
-// For portability reasons, no error is returned when the file is closed or
-// removed while open.
+// 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, err error) {
+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 err = adjustReaddirErr(err); err != nil {
+		if errno = adjustReaddirErr(err); errno != 0 {
 			return
 		}
 	case fs.ReadDirFile:
-		var entries []fs.DirEntry
-		entries, err = f.ReadDir(n)
-		if err = adjustReaddirErr(err); err != nil {
+		entries, err := f.ReadDir(n)
+		if errno = adjustReaddirErr(err); errno != 0 {
 			return
 		}
 		names = make([]string, 0, len(entries))
@@ -40,9 +39,8 @@ func Readdirnames(f fs.File, n int) (names []string, err error) {
 			names = append(names, e.Name())
 		}
 	default:
-		err = syscall.ENOTDIR
+		errno = syscall.ENOTDIR
 	}
-	err = UnwrapOSError(err)
 	return
 }
 
@@ -83,20 +81,18 @@ func (d *Dirent) IsDir() bool {
 //
 // # Errors
 //
-// If the error is non-nil, it is a syscall.Errno. io.EOF is not returned
-// because implementations return that value inconsistently.
+// A zero syscall.Errno is success.
 //
-// For portability reasons, no error is returned when the file is closed or
-// removed while open.
+// 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, err error) {
+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:
-		var fis []fs.FileInfo
-		fis, err = f.Readdir(n)
-		if err = adjustReaddirErr(err); err != nil {
+		fis, e := f.Readdir(n)
+		if errno = adjustReaddirErr(e); errno != 0 {
 			return
 		}
 		dirents = make([]*Dirent, 0, len(fis))
@@ -104,15 +100,14 @@ func Readdir(f fs.File, n int) (dirents []*Dirent, err error) {
 		// linux/darwin won't have to fan out to lstat, but windows will.
 		var ino uint64
 		for _, t := range fis {
-			if ino, err = inoFromFileInfo(f, t); err != nil {
+			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:
-		var entries []fs.DirEntry
-		entries, err = f.ReadDir(n)
-		if err = adjustReaddirErr(err); err != nil {
+		entries, e := f.ReadDir(n)
+		if errno = adjustReaddirErr(e); errno != 0 {
 			return
 		}
 		dirents = make([]*Dirent, 0, len(entries))
@@ -121,23 +116,23 @@ func Readdir(f fs.File, n int) (dirents []*Dirent, err error) {
 			dirents = append(dirents, &Dirent{Name: e.Name(), Type: e.Type()})
 		}
 	default:
-		err = syscall.ENOTDIR
+		errno = syscall.ENOTDIR
 	}
 	return
 }
 
-func adjustReaddirErr(err error) error {
+func adjustReaddirErr(err error) syscall.Errno {
 	if err == io.EOF {
-		return nil // e.g. Readdir on darwin returns io.EOF, but linux doesn't.
-	} else if err = UnwrapOSError(err); err != nil {
+		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 err {
+		switch errno {
 		case syscall.EIO, syscall.EBADF: // closed while open
-			return nil
+			return 0
 		case syscall.ENOENT: // Linux error when removed while open
-			return nil
+			return 0
 		}
-		return err
+		return errno
 	}
-	return err
+	return 0
 }
diff --git a/internal/platform/errno.go b/internal/platform/errno.go
index c2941934..43e03426 100644
--- a/internal/platform/errno.go
+++ b/internal/platform/errno.go
@@ -2,6 +2,8 @@
 
 package platform
 
-func adjustErrno(err error) error {
+import "syscall"
+
+func adjustErrno(err syscall.Errno) syscall.Errno {
 	return err
 }
diff --git a/internal/platform/errno_windows.go b/internal/platform/errno_windows.go
index a860ac6a..7cba50d4 100644
--- a/internal/platform/errno_windows.go
+++ b/internal/platform/errno_windows.go
@@ -42,7 +42,7 @@ const (
 	ERROR_PRIVILEGE_NOT_HELD = syscall.Errno(0x522)
 )
 
-func adjustErrno(err syscall.Errno) error {
+func adjustErrno(err syscall.Errno) syscall.Errno {
 	// Note: In windows, ERROR_PATH_NOT_FOUND(0x3) maps to syscall.ENOTDIR
 	switch err {
 	case ERROR_ALREADY_EXISTS:
diff --git a/internal/platform/error.go b/internal/platform/error.go
index 7730715a..82ed9b6c 100644
--- a/internal/platform/error.go
+++ b/internal/platform/error.go
@@ -1,15 +1,16 @@
 package platform
 
 import (
+	"io"
 	"io/fs"
 	"os"
 	"syscall"
 )
 
-// UnwrapOSError returns a syscall.Errno or nil if the input is nil.
-func UnwrapOSError(err error) error {
+// UnwrapOSError returns a syscall.Errno or zero if the input is nil.
+func UnwrapOSError(err error) syscall.Errno {
 	if err == nil {
-		return nil
+		return 0
 	}
 	err = underlyingError(err)
 	if se, ok := err.(syscall.Errno); ok {
@@ -19,7 +20,8 @@ func UnwrapOSError(err error) error {
 	//
 	// Note: Once we have our own file type, we should never see these.
 	switch err {
-	case nil:
+	case nil, io.EOF:
+		return 0
 	case fs.ErrInvalid:
 		return syscall.EINVAL
 	case fs.ErrPermission:
diff --git a/internal/platform/futimens.go b/internal/platform/futimens.go
index 16db9975..0b1419c0 100644
--- a/internal/platform/futimens.go
+++ b/internal/platform/futimens.go
@@ -45,7 +45,7 @@ const (
 //     values UTIME_NOW or UTIME_NOW.
 //   - This is like `utimensat` with `AT_FDCWD` in POSIX. See
 //     https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
-func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error {
+func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
 	err := utimens(path, times, symlinkFollow)
 	return UnwrapOSError(err)
 }
@@ -58,7 +58,7 @@ func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error
 //     cannot use this to update timestamps on a directory (syscall.EPERM).
 //   - This is like the function `futimens` in POSIX. See
 //     https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
-func UtimensFile(f fs.File, times *[2]syscall.Timespec) error {
+func UtimensFile(f fs.File, times *[2]syscall.Timespec) syscall.Errno {
 	if f, ok := f.(fdFile); ok {
 		err := futimens(f.Fd(), times)
 		return UnwrapOSError(err)
@@ -102,16 +102,16 @@ func utimensPortable(path string, times *[2]syscall.Timespec, symlinkFollow bool
 
 	// Now, either one of the inputs is a special value, or neither. This means
 	// we don't have a risk of re-reading the clock or re-doing stat.
-	if atim, err := normalizeTimespec(path, times, 0); err != nil {
+	if atim, err := normalizeTimespec(path, times, 0); err != 0 {
 		return err
-	} else if mtim, err := normalizeTimespec(path, times, 1); err != nil {
+	} else if mtim, err := normalizeTimespec(path, times, 1); err != 0 {
 		return err
 	} else {
 		return syscall.UtimesNano(path, []syscall.Timespec{atim, mtim})
 	}
 }
 
-func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts syscall.Timespec, err error) { //nolint:unused
+func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts syscall.Timespec, err syscall.Errno) { //nolint:unused
 	switch times[i].Nsec {
 	case UTIME_NOW: // declined in Go per golang/go#31880.
 		ts = nowTimespec()
@@ -122,7 +122,7 @@ func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts sysca
 		// - https://github.com/golang/go/issues/32558.
 		// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
 		var st Stat_t
-		if st, err = stat(path); err != nil {
+		if st, err = stat(path); err != 0 {
 			return
 		}
 		switch i {
diff --git a/internal/platform/open_file.go b/internal/platform/open_file.go
index cff965a3..2430c3f7 100644
--- a/internal/platform/open_file.go
+++ b/internal/platform/open_file.go
@@ -15,9 +15,9 @@ const (
 	O_NOFOLLOW  = syscall.O_NOFOLLOW
 )
 
-// OpenFile is like os.OpenFile except it returns syscall.Errno
-func OpenFile(name string, flag int, perm fs.FileMode) (f *os.File, err error) {
-	f, err = os.OpenFile(name, flag, perm)
-	err = UnwrapOSError(err)
-	return
+// OpenFile is like os.OpenFile except it returns syscall.Errno. A zero
+// syscall.Errno is success.
+func OpenFile(path string, flag int, perm fs.FileMode) (File, syscall.Errno) {
+	f, err := os.OpenFile(path, flag, perm)
+	return f, UnwrapOSError(err)
 }
diff --git a/internal/platform/open_file_js.go b/internal/platform/open_file_js.go
index 4b799d99..182cb481 100644
--- a/internal/platform/open_file_js.go
+++ b/internal/platform/open_file_js.go
@@ -3,6 +3,7 @@ package platform
 import (
 	"io/fs"
 	"os"
+	"syscall"
 )
 
 // See the comments on the same constants in open_file_windows.go
@@ -11,9 +12,8 @@ const (
 	O_NOFOLLOW  = 1 << 30
 )
 
-func OpenFile(name string, flag int, perm fs.FileMode) (f *os.File, err error) {
+func OpenFile(path string, flag int, perm fs.FileMode) (File, syscall.Errno) {
 	flag &= ^(O_DIRECTORY | O_NOFOLLOW) // erase placeholders
-	f, err = os.OpenFile(name, flag, perm)
-	err = UnwrapOSError(err)
-	return
+	f, err := os.OpenFile(path, flag, perm)
+	return f, UnwrapOSError(err)
 }
diff --git a/internal/platform/open_file_windows.go b/internal/platform/open_file_windows.go
index ab27e603..65484558 100644
--- a/internal/platform/open_file_windows.go
+++ b/internal/platform/open_file_windows.go
@@ -27,35 +27,36 @@ const (
 	O_NOFOLLOW  = 1 << 30
 )
 
-func OpenFile(path string, flag int, perm fs.FileMode) (File, error) {
-	if f, err := openFile(path, flag, perm); err != nil {
-		return nil, err
+func OpenFile(path string, flag int, perm fs.FileMode) (File, syscall.Errno) {
+	if f, errno := openFile(path, flag, perm); errno != 0 {
+		return nil, errno
 	} else {
-		return &windowsWrappedFile{File: f, path: path, flag: flag, perm: perm}, nil
+		return &windowsWrappedFile{File: f, path: path, flag: flag, perm: perm}, 0
 	}
 }
 
-func openFile(path string, flag int, perm fs.FileMode) (*os.File, error) {
+func openFile(path string, flag int, perm fs.FileMode) (*os.File, syscall.Errno) {
 	isDir := flag&O_DIRECTORY > 0
 	flag &= ^(O_DIRECTORY | O_NOFOLLOW) // erase placeholders
 
 	// TODO: document why we are opening twice
 	fd, err := open(path, flag|syscall.O_CLOEXEC, uint32(perm))
 	if err == nil {
-		return os.NewFile(uintptr(fd), path), nil
+		return os.NewFile(uintptr(fd), path), 0
 	}
 
 	// TODO: Set FILE_SHARE_DELETE for directory as well.
 	f, err := os.OpenFile(path, flag, perm)
-	if err = UnwrapOSError(err); err == nil {
-		return f, nil
+	errno := UnwrapOSError(err)
+	if errno == 0 {
+		return f, 0
 	}
 
-	switch err {
+	switch errno {
 	// To match expectations of WASI, e.g. TinyGo TestStatBadDir, return
 	// ENOENT, not ENOTDIR.
 	case syscall.ENOTDIR:
-		err = syscall.ENOENT
+		errno = syscall.ENOENT
 	case syscall.ENOENT:
 		if isSymlink(path) {
 			// Either symlink or hard link not found. We change the returned
@@ -63,13 +64,13 @@ func openFile(path string, flag int, perm fs.FileMode) (*os.File, error) {
 			// behavior across OSes.
 			if isDir {
 				// Dangling symlink dir must raise ENOTDIR.
-				err = syscall.ENOTDIR
+				errno = syscall.ENOTDIR
 			} else {
-				err = syscall.ELOOP
+				errno = syscall.ELOOP
 			}
 		}
 	}
-	return f, err
+	return f, errno
 }
 
 func isSymlink(path string) bool {
diff --git a/internal/platform/rename.go b/internal/platform/rename.go
index 6c39a556..2b537fae 100644
--- a/internal/platform/rename.go
+++ b/internal/platform/rename.go
@@ -4,9 +4,9 @@ package platform
 
 import "syscall"
 
-func Rename(from, to string) error {
+func Rename(from, to string) syscall.Errno {
 	if from == to {
-		return nil
+		return 0
 	}
-	return syscall.Rename(from, to)
+	return UnwrapOSError(syscall.Rename(from, to))
 }
diff --git a/internal/platform/rename_windows.go b/internal/platform/rename_windows.go
index 48a2d32e..c3b406e5 100644
--- a/internal/platform/rename_windows.go
+++ b/internal/platform/rename_windows.go
@@ -6,9 +6,9 @@ import (
 	"syscall"
 )
 
-func Rename(from, to string) error {
+func Rename(from, to string) syscall.Errno {
 	if from == to {
-		return nil
+		return 0
 	}
 
 	fromStat, err := os.Stat(from)
@@ -25,21 +25,21 @@ func Rename(from, to string) error {
 		} else if !fromIsDir && !toIsDir { // file to file
 			// Use os.Rename instead of syscall.Rename in order to allow the overrides of the existing file.
 			// Underneath os.Rename, it uses MoveFileEx instead of MoveFile (used by syscall.Rename).
-			return os.Rename(from, to)
+			return UnwrapOSError(os.Rename(from, to))
 		} else { // dir to dir
 			if dirs, _ := os.ReadDir(to); len(dirs) == 0 {
 				// On Windows, renaming to the empty dir will be rejected,
 				// so first we remove the empty dir, and then rename to it.
 				if err := os.Remove(to); err != nil {
-					return err
+					return UnwrapOSError(err)
 				}
-				return syscall.Rename(from, to)
+				return UnwrapOSError(syscall.Rename(from, to))
 			}
 			return syscall.ENOTEMPTY
 		}
 	} else if !errors.Is(err, syscall.ENOENT) { // Failed to stat the destination.
-		return err
+		return UnwrapOSError(err)
 	} else { // Destination not-exist.
-		return syscall.Rename(from, to)
+		return UnwrapOSError(syscall.Rename(from, to))
 	}
 }
diff --git a/internal/platform/stat.go b/internal/platform/stat.go
index 059ded7c..b6858a24 100644
--- a/internal/platform/stat.go
+++ b/internal/platform/stat.go
@@ -58,34 +58,32 @@ type Stat_t struct {
 // The primary difference between this and Stat is, when the path is a
 // symbolic link, the stat is about the link, not its target, such as directory
 // listings.
-func Lstat(path string) (Stat_t, error) {
-	st, err := lstat(path) // extracted to override more expensively in windows
-	return st, UnwrapOSError(err)
+func Lstat(path string) (Stat_t, syscall.Errno) {
+	return lstat(path) // extracted to override more expensively in windows
 }
 
 // Stat is like syscall.Stat. This returns syscall.ENOENT if the path doesn't
 // exist.
-func Stat(path string) (Stat_t, error) {
-	st, err := stat(path) // extracted to override more expensively in windows
-	return st, UnwrapOSError(err)
+func Stat(path string) (Stat_t, syscall.Errno) {
+	return stat(path) // extracted to override more expensively in windows
 }
 
 // StatFile is like syscall.Fstat, but for fs.File instead of a file
 // descriptor. This returns syscall.EBADF if the file or directory was closed.
 // Note: windows allows you to stat a closed directory.
-func StatFile(f fs.File) (Stat_t, error) {
-	st, err := statFile(f)
-	if err = UnwrapOSError(err); err == syscall.EIO {
-		err = syscall.EBADF
+func StatFile(f fs.File) (Stat_t, syscall.Errno) {
+	st, errno := statFile(f)
+	if errno == syscall.EIO {
+		errno = syscall.EBADF
 	}
-	return st, err
+	return st, errno
 }
 
-func defaultStatFile(f fs.File) (Stat_t, error) {
+func defaultStatFile(f fs.File) (Stat_t, syscall.Errno) {
 	if t, err := f.Stat(); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	} else {
-		return statFromFileInfo(t), nil
+		return statFromFileInfo(t), 0
 	}
 }
 
diff --git a/internal/platform/stat_bsd.go b/internal/platform/stat_bsd.go
index f3640b44..90959f8f 100644
--- a/internal/platform/stat_bsd.go
+++ b/internal/platform/stat_bsd.go
@@ -8,27 +8,27 @@ import (
 	"syscall"
 )
 
-func lstat(path string) (Stat_t, error) {
+func lstat(path string) (Stat_t, syscall.Errno) {
 	if t, err := os.Lstat(path); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	} else {
-		return statFromFileInfo(t), nil
+		return statFromFileInfo(t), 0
 	}
 }
 
-func stat(path string) (Stat_t, error) {
+func stat(path string) (Stat_t, syscall.Errno) {
 	if t, err := os.Stat(path); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	} else {
-		return statFromFileInfo(t), nil
+		return statFromFileInfo(t), 0
 	}
 }
 
-func statFile(f fs.File) (Stat_t, error) {
+func statFile(f fs.File) (Stat_t, syscall.Errno) {
 	return defaultStatFile(f)
 }
 
-func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) {
+func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err syscall.Errno) {
 	if d, ok := t.Sys().(*syscall.Stat_t); ok {
 		ino = d.Ino
 	}
diff --git a/internal/platform/stat_linux.go b/internal/platform/stat_linux.go
index 19a82438..136081ef 100644
--- a/internal/platform/stat_linux.go
+++ b/internal/platform/stat_linux.go
@@ -11,27 +11,27 @@ import (
 	"syscall"
 )
 
-func lstat(path string) (Stat_t, error) {
+func lstat(path string) (Stat_t, syscall.Errno) {
 	if t, err := os.Lstat(path); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	} else {
-		return statFromFileInfo(t), nil
+		return statFromFileInfo(t), 0
 	}
 }
 
-func stat(path string) (Stat_t, error) {
+func stat(path string) (Stat_t, syscall.Errno) {
 	if t, err := os.Stat(path); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	} else {
-		return statFromFileInfo(t), nil
+		return statFromFileInfo(t), 0
 	}
 }
 
-func statFile(f fs.File) (Stat_t, error) {
+func statFile(f fs.File) (Stat_t, syscall.Errno) {
 	return defaultStatFile(f)
 }
 
-func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err error) {
+func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err syscall.Errno) {
 	if d, ok := t.Sys().(*syscall.Stat_t); ok {
 		ino = d.Ino
 	}
diff --git a/internal/platform/stat_unsupported.go b/internal/platform/stat_unsupported.go
index d2c73b9c..32e94438 100644
--- a/internal/platform/stat_unsupported.go
+++ b/internal/platform/stat_unsupported.go
@@ -5,29 +5,32 @@ package platform
 import (
 	"io/fs"
 	"os"
+	"syscall"
 )
 
-func lstat(path string) (Stat_t, error) {
+func lstat(path string) (Stat_t, syscall.Errno) {
 	t, err := os.Lstat(path)
-	if err = UnwrapOSError(err); err != nil {
-		return statFromFileInfo(t), nil
+	if errno := UnwrapOSError(err); errno == 0 {
+		return statFromFileInfo(t), 0
+	} else {
+		return Stat_t{}, errno
 	}
-	return Stat_t{}, err
 }
 
-func stat(path string) (Stat_t, error) {
+func stat(path string) (Stat_t, syscall.Errno) {
 	t, err := os.Stat(path)
-	if err = UnwrapOSError(err); err == nil {
-		return statFromFileInfo(t), nil
+	if errno := UnwrapOSError(err); errno == 0 {
+		return statFromFileInfo(t), 0
+	} else {
+		return Stat_t{}, errno
 	}
-	return Stat_t{}, err
 }
 
-func statFile(f fs.File) (Stat_t, error) {
+func statFile(f fs.File) (Stat_t, syscall.Errno) {
 	return defaultStatFile(f)
 }
 
-func inoFromFileInfo(readdirFile, fs.FileInfo) (ino uint64, err error) {
+func inoFromFileInfo(_ readdirFile, t fs.FileInfo) (ino uint64, err syscall.Errno) {
 	return
 }
 
diff --git a/internal/platform/stat_windows.go b/internal/platform/stat_windows.go
index e0a517ba..289b95fa 100644
--- a/internal/platform/stat_windows.go
+++ b/internal/platform/stat_windows.go
@@ -8,7 +8,7 @@ import (
 	"syscall"
 )
 
-func lstat(path string) (Stat_t, error) {
+func lstat(path string) (Stat_t, syscall.Errno) {
 	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
 	// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
 	// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
@@ -16,12 +16,12 @@ func lstat(path string) (Stat_t, error) {
 	return statPath(attrs, path)
 }
 
-func stat(path string) (Stat_t, error) {
+func stat(path string) (Stat_t, syscall.Errno) {
 	attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
 	return statPath(attrs, path)
 }
 
-func statPath(createFileAttrs uint32, path string) (Stat_t, error) {
+func statPath(createFileAttrs uint32, path string) (Stat_t, syscall.Errno) {
 	if len(path) == 0 {
 		return Stat_t{}, syscall.ENOENT
 	}
@@ -39,21 +39,24 @@ func statPath(createFileAttrs uint32, path string) (Stat_t, error) {
 		if err == syscall.ENOTDIR {
 			err = syscall.ENOENT
 		}
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	}
 	defer syscall.CloseHandle(h)
 
 	return statHandle(h)
 }
 
-func statFile(f fs.File) (Stat_t, error) {
+func statFile(f fs.File) (Stat_t, syscall.Errno) {
 	if of, ok := f.(fdFile); ok {
 		// Attempt to get the stat by handle, which works for normal files
 		st, err := statHandle(syscall.Handle(of.Fd()))
 
 		// ERROR_INVALID_HANDLE happens before Go 1.20. Don't fail as we only
 		// use that approach to fill in inode data, which is not critical.
-		if err != ERROR_INVALID_HANDLE {
+		//
+		// Note: statHandle uses UnwrapOSError which coerces
+		// ERROR_INVALID_HANDLE to EBADF.
+		if err != syscall.EBADF {
 			return st, err
 		}
 	}
@@ -61,10 +64,11 @@ func statFile(f fs.File) (Stat_t, error) {
 }
 
 // inoFromFileInfo uses stat to get the inode information of the file.
-func inoFromFileInfo(f readdirFile, t fs.FileInfo) (ino uint64, err error) {
+func inoFromFileInfo(f readdirFile, t fs.FileInfo) (ino uint64, errno syscall.Errno) {
 	if pf, ok := f.(PathFile); ok {
 		inoPath := path.Clean(path.Join(pf.Path(), t.Name()))
-		if st, err := lstat(inoPath); err == nil {
+		var st Stat_t
+		if st, errno = Lstat(inoPath); errno == 0 {
 			ino = st.Ino
 		}
 	}
@@ -88,15 +92,15 @@ func statFromFileInfo(t fs.FileInfo) Stat_t {
 	}
 }
 
-func statHandle(h syscall.Handle) (Stat_t, error) {
+func statHandle(h syscall.Handle) (Stat_t, syscall.Errno) {
 	winFt, err := syscall.GetFileType(h)
 	if err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	}
 
 	var fi syscall.ByHandleFileInformation
 	if err = syscall.GetFileInformationByHandle(h, &fi); err != nil {
-		return Stat_t{}, err
+		return Stat_t{}, UnwrapOSError(err)
 	}
 
 	var m fs.FileMode
@@ -128,5 +132,5 @@ func statHandle(h syscall.Handle) (Stat_t, error) {
 	st.Atim = fi.LastAccessTime.Nanoseconds()
 	st.Mtim = fi.LastWriteTime.Nanoseconds()
 	st.Ctim = fi.CreationTime.Nanoseconds()
-	return st, nil
+	return st, 0
 }
diff --git a/internal/platform/sync.go b/internal/platform/sync.go
index cf00b0fe..a93e24ba 100644
--- a/internal/platform/sync.go
+++ b/internal/platform/sync.go
@@ -1,11 +1,14 @@
 package platform
 
-import "io/fs"
+import (
+	"io/fs"
+	"syscall"
+)
 
 // Fdatasync is like syscall.Fdatasync except that's only defined in linux.
 //
 // Note: This returns with no error instead of syscall.ENOSYS when
 // unimplemented. This prevents fake filesystems from erring.
-func Fdatasync(f fs.File) (err error) {
+func Fdatasync(f fs.File) syscall.Errno {
 	return fdatasync(f)
 }
diff --git a/internal/platform/sync_linux.go b/internal/platform/sync_linux.go
index c634431d..41ae44c8 100644
--- a/internal/platform/sync_linux.go
+++ b/internal/platform/sync_linux.go
@@ -7,14 +7,14 @@ import (
 	"syscall"
 )
 
-func fdatasync(f fs.File) (err error) {
+func fdatasync(f fs.File) syscall.Errno {
 	if fd, ok := f.(fdFile); ok {
-		return syscall.Fdatasync(int(fd.Fd()))
+		return UnwrapOSError(syscall.Fdatasync(int(fd.Fd())))
 	}
 
 	// Attempt to sync everything, even if we only need to sync the data.
 	if s, ok := f.(syncFile); ok {
-		err = s.Sync()
+		return UnwrapOSError(s.Sync())
 	}
-	return
+	return 0
 }
diff --git a/internal/platform/sync_unsupported.go b/internal/platform/sync_unsupported.go
index 8fc60518..38284aa5 100644
--- a/internal/platform/sync_unsupported.go
+++ b/internal/platform/sync_unsupported.go
@@ -4,12 +4,13 @@ package platform
 
 import (
 	"io/fs"
+	"syscall"
 )
 
-func fdatasync(f fs.File) error {
+func fdatasync(f fs.File) syscall.Errno {
 	// Attempt to sync everything, even if we only need to sync the data.
 	if s, ok := f.(syncFile); ok {
-		return s.Sync()
+		return UnwrapOSError(s.Sync())
 	}
-	return nil
+	return 0
 }
diff --git a/internal/platform/unlink.go b/internal/platform/unlink.go
index 898d4b4c..9555da51 100644
--- a/internal/platform/unlink.go
+++ b/internal/platform/unlink.go
@@ -4,10 +4,10 @@ package platform
 
 import "syscall"
 
-func Unlink(name string) error {
+func Unlink(name string) (errno syscall.Errno) {
 	err := syscall.Unlink(name)
-	if err = UnwrapOSError(err); err == syscall.EPERM {
-		err = syscall.EISDIR
+	if errno = UnwrapOSError(err); errno == syscall.EPERM {
+		errno = syscall.EISDIR
 	}
-	return err
+	return errno
 }
diff --git a/internal/platform/unlink_windows.go b/internal/platform/unlink_windows.go
index e5be95fd..9831e10b 100644
--- a/internal/platform/unlink_windows.go
+++ b/internal/platform/unlink_windows.go
@@ -7,19 +7,19 @@ import (
 	"syscall"
 )
 
-func Unlink(name string) (err error) {
-	err = syscall.Unlink(name)
+func Unlink(name string) syscall.Errno {
+	err := syscall.Unlink(name)
 	if err == nil {
-		return
+		return 0
 	}
-	err = UnwrapOSError(err)
-	if err == syscall.EPERM {
+	errno := UnwrapOSError(err)
+	if errno == syscall.EPERM {
 		lstat, errLstat := os.Lstat(name)
 		if errLstat == nil && lstat.Mode()&os.ModeSymlink != 0 {
-			err = UnwrapOSError(os.Remove(name))
+			errno = UnwrapOSError(os.Remove(name))
 		} else {
-			err = syscall.EISDIR
+			errno = syscall.EISDIR
 		}
 	}
-	return
+	return errno
 }
diff --git a/internal/platform/wrap_windows.go b/internal/platform/wrap_windows.go
index ba7d4c8f..59cfce3b 100644
--- a/internal/platform/wrap_windows.go
+++ b/internal/platform/wrap_windows.go
@@ -88,9 +88,9 @@ func (w *windowsWrappedFile) maybeInitDir() error {
 	if err := w.File.Close(); err != nil {
 		return err
 	}
-	newW, err := openFile(w.path, w.flag, w.perm)
-	if err != nil {
-		return &fs.PathError{Op: "OpenFile", Path: w.path, Err: err}
+	newW, errno := openFile(w.path, w.flag, w.perm)
+	if errno != 0 {
+		return &fs.PathError{Op: "OpenFile", Path: w.path, Err: errno}
 	}
 	w.File = newW
 	w.dirInitialized = true
@@ -120,8 +120,8 @@ func (w *windowsWrappedFile) requireFile(op string, readOnly, isDir bool) error
 // getFileType caches the file type as this cannot change on an open file.
 func (w *windowsWrappedFile) getFileType() (fs.FileMode, error) {
 	if w.fileType == nil {
-		st, err := StatFile(w.File)
-		if err != nil {
+		st, errno := StatFile(w.File)
+		if errno != 0 {
 			return 0, nil
 		}
 		ft := st.Mode & fs.ModeType
diff --git a/internal/sys/fs.go b/internal/sys/fs.go
index f59ecd1c..010bd5fa 100644
--- a/internal/sys/fs.go
+++ b/internal/sys/fs.go
@@ -115,26 +115,26 @@ type lazyDir struct {
 
 // Stat implements fs.File
 func (r *lazyDir) Stat() (fs.FileInfo, error) {
-	if f, err := r.file(); err != nil {
+	if f, err := r.file(); err != 0 {
 		return nil, err
 	} else {
 		return f.Stat()
 	}
 }
 
-func (r *lazyDir) file() (f fs.File, err error) {
+func (r *lazyDir) file() (f fs.File, errno syscall.Errno) {
 	if f = r.f; r.f != nil {
 		return
 	}
-	r.f, err = r.fs.OpenFile(".", os.O_RDONLY, 0)
+	r.f, errno = r.fs.OpenFile(".", os.O_RDONLY, 0)
 	f = r.f
 	return
 }
 
 // Read implements fs.File
 func (r *lazyDir) Read(p []byte) (n int, err error) {
-	if f, err := r.file(); err != nil {
-		return 0, err
+	if f, errno := r.file(); errno != 0 {
+		return 0, errno
 	} else {
 		return f.Read(p)
 	}
@@ -142,10 +142,14 @@ func (r *lazyDir) Read(p []byte) (n int, err error) {
 
 // Close implements fs.File
 func (r *lazyDir) Close() error {
-	if f, err := r.file(); err != nil {
-		return nil
-	} else {
+	f, errno := r.file()
+	switch errno {
+	case 0:
 		return f.Close()
+	case syscall.ENOENT:
+		return nil
+	default:
+		return errno
 	}
 }
 
@@ -199,16 +203,19 @@ func (f *FileEntry) CachedStat() (ino uint64, fileType fs.FileMode, err error) {
 
 // Stat returns the underlying stat of this file.
 func (f *FileEntry) Stat() (st platform.Stat_t, err error) {
+	var errno syscall.Errno
 	if ld, ok := f.File.(*lazyDir); ok {
 		var sf fs.File
-		if sf, err = ld.file(); err == nil {
-			st, err = platform.StatFile(sf)
+		if sf, errno = ld.file(); errno == 0 {
+			st, errno = platform.StatFile(sf)
 		}
 	} else {
-		st, err = platform.StatFile(f.File)
+		st, errno = platform.StatFile(f.File)
 	}
 
-	if err == nil {
+	if errno != 0 {
+		err = errno
+	} else {
 		f.cachedStat = &cachedStat{Ino: st.Ino, Type: st.Mode & fs.ModeType}
 	}
 	return
@@ -325,9 +332,9 @@ func (c *FSContext) RootFS() sysfs.FS {
 
 // OpenFile opens the file into the table and returns its file descriptor.
 // The result must be closed by CloseFile or Close.
-func (c *FSContext) OpenFile(fs sysfs.FS, path string, flag int, perm fs.FileMode) (uint32, error) {
-	if f, err := fs.OpenFile(path, flag, perm); err != nil {
-		return 0, err
+func (c *FSContext) OpenFile(fs sysfs.FS, path string, flag int, perm fs.FileMode) (uint32, syscall.Errno) {
+	if f, errno := fs.OpenFile(path, flag, perm); errno != 0 {
+		return 0, errno
 	} else {
 		fe := &FileEntry{openPath: path, FS: fs, File: f, openFlag: flag, openPerm: perm}
 		if path == "/" || path == "." {
@@ -336,54 +343,54 @@ func (c *FSContext) OpenFile(fs sysfs.FS, path string, flag int, perm fs.FileMod
 			fe.Name = path
 		}
 		newFD := c.openedFiles.Insert(fe)
-		return newFD, nil
+		return newFD, 0
 	}
 }
 
 // ReOpenDir re-opens the directory while keeping the same file descriptor.
 // TODO: this might not be necessary once we have our own File type.
-func (c *FSContext) ReOpenDir(fd uint32) (*FileEntry, error) {
+func (c *FSContext) ReOpenDir(fd uint32) (*FileEntry, syscall.Errno) {
 	f, ok := c.openedFiles.Lookup(fd)
 	if !ok {
 		return nil, syscall.EBADF
 	} else if _, ft, err := f.CachedStat(); err != nil {
-		return nil, err
+		return nil, platform.UnwrapOSError(err)
 	} else if ft.Type() != fs.ModeDir {
 		return nil, syscall.EISDIR
 	}
 
-	if err := c.reopen(f); err != nil {
-		return f, err
+	if errno := c.reopen(f); errno != 0 {
+		return nil, errno
 	}
 
 	f.ReadDir.CountRead, f.ReadDir.Dirents = 0, nil
-	return f, nil
+	return f, 0
 }
 
-func (c *FSContext) reopen(f *FileEntry) error {
+func (c *FSContext) reopen(f *FileEntry) syscall.Errno {
 	if err := f.File.Close(); err != nil {
-		return err
+		return platform.UnwrapOSError(err)
 	}
 
 	// Re-opens with  the same parameters as before.
-	opened, err := f.FS.OpenFile(f.openPath, f.openFlag, f.openPerm)
-	if err != nil {
-		return err
+	opened, errno := f.FS.OpenFile(f.openPath, f.openFlag, f.openPerm)
+	if errno != 0 {
+		return errno
 	}
 
 	// Reset the state.
 	f.File = opened
-	return nil
+	return 0
 }
 
 // ChangeOpenFlag changes the open flag of the given opened file pointed by `fd`.
 // Currently, this only supports the change of syscall.O_APPEND flag.
-func (c *FSContext) ChangeOpenFlag(fd uint32, flag int) error {
+func (c *FSContext) ChangeOpenFlag(fd uint32, flag int) syscall.Errno {
 	f, ok := c.LookupFile(fd)
 	if !ok {
 		return syscall.EBADF
 	} else if _, ft, err := f.CachedStat(); err != nil {
-		return err
+		return platform.UnwrapOSError(err)
 	} else if ft.Type() == fs.ModeDir {
 		return syscall.EISDIR
 	}
@@ -404,10 +411,7 @@ func (c *FSContext) ChangeOpenFlag(fd uint32, flag int) error {
 	//
 	// Therefore, here we re-open the file while keeping the file descriptor.
 	// TODO: this might be improved once we have our own File type.
-	if err := c.reopen(f); err != nil {
-		return err
-	}
-	return nil
+	return c.reopen(f)
 }
 
 // LookupFile returns a file if it is in the table.
@@ -417,7 +421,7 @@ func (c *FSContext) LookupFile(fd uint32) (*FileEntry, bool) {
 }
 
 // Renumber assigns the file pointed by the descriptor `from` to `to`.
-func (c *FSContext) Renumber(from, to uint32) error {
+func (c *FSContext) Renumber(from, to uint32) syscall.Errno {
 	fromFile, ok := c.openedFiles.Lookup(from)
 	if !ok {
 		return syscall.EBADF
@@ -439,17 +443,17 @@ func (c *FSContext) Renumber(from, to uint32) error {
 
 	c.openedFiles.Delete(from)
 	c.openedFiles.InsertAt(fromFile, to)
-	return nil
+	return 0
 }
 
 // CloseFile returns any error closing the existing file.
-func (c *FSContext) CloseFile(fd uint32) error {
+func (c *FSContext) CloseFile(fd uint32) syscall.Errno {
 	f, ok := c.openedFiles.Lookup(fd)
 	if !ok {
 		return syscall.EBADF
 	}
 	c.openedFiles.Delete(fd)
-	return f.File.Close()
+	return platform.UnwrapOSError(f.File.Close())
 }
 
 // Close implements api.Closer
diff --git a/internal/sysfs/adapter.go b/internal/sysfs/adapter.go
index cc5b253c..8ccd227b 100644
--- a/internal/sysfs/adapter.go
+++ b/internal/sysfs/adapter.go
@@ -7,6 +7,7 @@ import (
 	"path"
 	"runtime"
 	"strings"
+	"syscall"
 
 	"github.com/tetratelabs/wazero/internal/platform"
 )
@@ -44,14 +45,14 @@ func (a *adapter) Open(name string) (fs.File, error) {
 }
 
 // OpenFile implements FS.OpenFile
-func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
+func (a *adapter) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno) {
 	path = cleanPath(path)
 	f, err := a.fs.Open(path)
 	return f, platform.UnwrapOSError(err)
 }
 
 // Stat implements FS.Stat
-func (a *adapter) Stat(path string) (platform.Stat_t, error) {
+func (a *adapter) Stat(path string) (platform.Stat_t, syscall.Errno) {
 	name := cleanPath(path)
 	f, err := a.fs.Open(name)
 	if err != nil {
@@ -62,7 +63,7 @@ func (a *adapter) Stat(path string) (platform.Stat_t, error) {
 }
 
 // Lstat implements FS.Lstat
-func (a *adapter) Lstat(path string) (platform.Stat_t, error) {
+func (a *adapter) Lstat(path string) (platform.Stat_t, syscall.Errno) {
 	// At this time, we make the assumption that fs.FS instances do not support
 	// symbolic links, therefore Lstat is the same as Stat. This is obviously
 	// not true but until fs.FS has a solid story for how to handle symlinks we
@@ -103,7 +104,7 @@ func fsOpen(f FS, name string) (fs.File, error) {
 		}
 	}
 
-	if f, err := f.OpenFile(name, os.O_RDONLY, 0); err != nil {
+	if f, err := f.OpenFile(name, os.O_RDONLY, 0); err != 0 {
 		return nil, &fs.PathError{Op: "open", Path: name, Err: err}
 	} else {
 		return f, nil
diff --git a/internal/sysfs/dirfs.go b/internal/sysfs/dirfs.go
index e209a8aa..92c81b52 100644
--- a/internal/sysfs/dirfs.go
+++ b/internal/sysfs/dirfs.go
@@ -41,96 +41,95 @@ func (d *dirFS) Open(name string) (fs.File, error) {
 }
 
 // OpenFile implements FS.OpenFile
-func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
+func (d *dirFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno) {
 	return platform.OpenFile(d.join(path), flag, perm)
 }
 
 // Lstat implements FS.Lstat
-func (d *dirFS) Lstat(path string) (platform.Stat_t, error) {
+func (d *dirFS) Lstat(path string) (platform.Stat_t, syscall.Errno) {
 	return platform.Lstat(d.join(path))
 }
 
 // Stat implements FS.Stat
-func (d *dirFS) Stat(path string) (platform.Stat_t, error) {
+func (d *dirFS) Stat(path string) (platform.Stat_t, syscall.Errno) {
 	return platform.Stat(d.join(path))
 }
 
 // Mkdir implements FS.Mkdir
-func (d *dirFS) Mkdir(path string, perm fs.FileMode) (err error) {
-	err = os.Mkdir(d.join(path), perm)
-	if err = platform.UnwrapOSError(err); err == syscall.ENOTDIR {
-		err = syscall.ENOENT
+func (d *dirFS) Mkdir(path string, perm fs.FileMode) (errno syscall.Errno) {
+	err := os.Mkdir(d.join(path), perm)
+	if errno = platform.UnwrapOSError(err); errno == syscall.ENOTDIR {
+		errno = syscall.ENOENT
 	}
 	return
 }
 
 // Chmod implements FS.Chmod
-func (d *dirFS) Chmod(path string, perm fs.FileMode) error {
+func (d *dirFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
 	err := os.Chmod(d.join(path), perm)
 	return platform.UnwrapOSError(err)
 }
 
 // Chown implements FS.Chown
-func (d *dirFS) Chown(path string, uid, gid int) error {
+func (d *dirFS) Chown(path string, uid, gid int) syscall.Errno {
 	return platform.Chown(d.join(path), uid, gid)
 }
 
 // Lchown implements FS.Lchown
-func (d *dirFS) Lchown(path string, uid, gid int) error {
+func (d *dirFS) Lchown(path string, uid, gid int) syscall.Errno {
 	return platform.Lchown(d.join(path), uid, gid)
 }
 
 // Rename implements FS.Rename
-func (d *dirFS) Rename(from, to string) error {
+func (d *dirFS) Rename(from, to string) syscall.Errno {
 	from, to = d.join(from), d.join(to)
-	err := platform.Rename(from, to)
-	return platform.UnwrapOSError(err)
+	return platform.Rename(from, to)
 }
 
 // Readlink implements FS.Readlink
-func (d *dirFS) Readlink(path string) (string, error) {
+func (d *dirFS) Readlink(path string) (string, syscall.Errno) {
 	// Note: do not use syscall.Readlink as that causes race on Windows.
 	// In any case, syscall.Readlink does almost the same logic as os.Readlink.
 	dst, err := os.Readlink(d.join(path))
 	if err != nil {
 		return "", platform.UnwrapOSError(err)
 	}
-	return platform.ToPosixPath(dst), nil
+	return platform.ToPosixPath(dst), 0
 }
 
 // Link implements FS.Link.
-func (d *dirFS) Link(oldName, newName string) error {
+func (d *dirFS) Link(oldName, newName string) syscall.Errno {
 	err := os.Link(d.join(oldName), d.join(newName))
 	return platform.UnwrapOSError(err)
 }
 
 // Rmdir implements FS.Rmdir
-func (d *dirFS) Rmdir(path string) error {
+func (d *dirFS) Rmdir(path string) syscall.Errno {
 	err := syscall.Rmdir(d.join(path))
 	return platform.UnwrapOSError(err)
 }
 
 // Unlink implements FS.Unlink
-func (d *dirFS) Unlink(path string) (err error) {
+func (d *dirFS) Unlink(path string) (err syscall.Errno) {
 	return platform.Unlink(d.join(path))
 }
 
 // Symlink implements FS.Symlink
-func (d *dirFS) Symlink(oldName, link string) (err error) {
+func (d *dirFS) Symlink(oldName, link string) syscall.Errno {
 	// Note: do not resolve `oldName` relative to this dirFS. The link result is always resolved
 	// when dereference the `link` on its usage (e.g. readlink, read, etc).
 	// https://github.com/bytecodealliance/cap-std/blob/v1.0.4/cap-std/src/fs/dir.rs#L404-L409
-	err = os.Symlink(oldName, d.join(link))
+	err := os.Symlink(oldName, d.join(link))
 	return platform.UnwrapOSError(err)
 }
 
 // Utimens implements FS.Utimens
-func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error {
+func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
 	return platform.Utimens(d.join(path), times, symlinkFollow)
 }
 
 // Truncate implements FS.Truncate
-func (d *dirFS) Truncate(path string, size int64) error {
+func (d *dirFS) Truncate(path string, size int64) syscall.Errno {
 	// Use os.Truncate as syscall.Truncate doesn't exist on Windows.
 	err := os.Truncate(d.join(path), size)
 	return platform.UnwrapOSError(err)
diff --git a/internal/sysfs/readfs.go b/internal/sysfs/readfs.go
index 1d18a9d0..bec2dc3d 100644
--- a/internal/sysfs/readfs.go
+++ b/internal/sysfs/readfs.go
@@ -37,7 +37,7 @@ func (r *readFS) Open(name string) (fs.File, error) {
 }
 
 // OpenFile implements FS.OpenFile
-func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
+func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno) {
 	// TODO: Once the real implementation is complete, move the below to
 	// /RATIONALE.md. Doing this while the type is unstable creates
 	// documentation drift as we expect a lot of reshaping meanwhile.
@@ -63,11 +63,11 @@ func (r *readFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, err
 	default: // os.O_RDONLY so we are ok!
 	}
 
-	f, err := r.fs.OpenFile(path, flag, perm)
-	if err != nil {
-		return nil, err
+	f, errno := r.fs.OpenFile(path, flag, perm)
+	if errno != 0 {
+		return nil, errno
 	}
-	return maskForReads(f), nil
+	return maskForReads(f), 0
 }
 
 // maskForReads masks the file with read-only interfaces used by wazero.
@@ -141,71 +141,71 @@ func maskForReads(f fs.File) fs.File {
 }
 
 // Lstat implements FS.Lstat
-func (r *readFS) Lstat(path string) (platform.Stat_t, error) {
+func (r *readFS) Lstat(path string) (platform.Stat_t, syscall.Errno) {
 	return r.fs.Lstat(path)
 }
 
 // Stat implements FS.Stat
-func (r *readFS) Stat(path string) (platform.Stat_t, error) {
+func (r *readFS) Stat(path string) (platform.Stat_t, syscall.Errno) {
 	return r.fs.Stat(path)
 }
 
 // Readlink implements FS.Readlink
-func (r *readFS) Readlink(path string) (dst string, err error) {
+func (r *readFS) Readlink(path string) (dst string, err syscall.Errno) {
 	return r.fs.Readlink(path)
 }
 
 // Mkdir implements FS.Mkdir
-func (r *readFS) Mkdir(path string, perm fs.FileMode) error {
+func (r *readFS) Mkdir(path string, perm fs.FileMode) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Chmod implements FS.Chmod
-func (r *readFS) Chmod(path string, perm fs.FileMode) error {
+func (r *readFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Chown implements FS.Chown
-func (r *readFS) Chown(path string, uid, gid int) error {
+func (r *readFS) Chown(path string, uid, gid int) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Lchown implements FS.Lchown
-func (r *readFS) Lchown(path string, uid, gid int) error {
+func (r *readFS) Lchown(path string, uid, gid int) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Rename implements FS.Rename
-func (r *readFS) Rename(from, to string) error {
+func (r *readFS) Rename(from, to string) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Rmdir implements FS.Rmdir
-func (r *readFS) Rmdir(path string) error {
+func (r *readFS) Rmdir(path string) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Link implements FS.Link
-func (r *readFS) Link(_, _ string) error {
+func (r *readFS) Link(_, _ string) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Symlink implements FS.Symlink
-func (r *readFS) Symlink(_, _ string) error {
+func (r *readFS) Symlink(_, _ string) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Unlink implements FS.Unlink
-func (r *readFS) Unlink(path string) error {
+func (r *readFS) Unlink(path string) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Utimens implements FS.Utimens
-func (r *readFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error {
+func (r *readFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
 	return syscall.EROFS
 }
 
 // Truncate implements FS.Truncate
-func (r *readFS) Truncate(string, int64) error {
+func (r *readFS) Truncate(string, int64) syscall.Errno {
 	return syscall.EROFS
 }
diff --git a/internal/sysfs/rootfs.go b/internal/sysfs/rootfs.go
index 3a9ff181..cdac5e76 100644
--- a/internal/sysfs/rootfs.go
+++ b/internal/sysfs/rootfs.go
@@ -123,11 +123,11 @@ func (c *CompositeFS) Open(name string) (fs.File, error) {
 }
 
 // OpenFile implements FS.OpenFile
-func (c *CompositeFS) OpenFile(path string, flag int, perm fs.FileMode) (f fs.File, err error) {
+func (c *CompositeFS) OpenFile(path string, flag int, perm fs.FileMode) (f fs.File, err syscall.Errno) {
 	matchIndex, relativePath := c.chooseFS(path)
 
 	f, err = c.fs[matchIndex].OpenFile(relativePath, flag, perm)
-	if err != nil {
+	if err != 0 {
 		return
 	}
 
@@ -192,7 +192,7 @@ func (d *openRootDir) readDir() (err error) {
 }
 
 func (d *openRootDir) rootEntry(name string, fsI int) (fs.DirEntry, error) {
-	if st, err := d.c.fs[fsI].Stat("."); err != nil {
+	if st, err := d.c.fs[fsI].Stat("."); err != 0 {
 		return nil, err
 	} else {
 		return &dirInfo{name, st}, nil
@@ -246,43 +246,43 @@ func (d *openRootDir) ReadDir(count int) ([]fs.DirEntry, error) {
 }
 
 // Lstat implements FS.Lstat
-func (c *CompositeFS) Lstat(path string) (platform.Stat_t, error) {
+func (c *CompositeFS) Lstat(path string) (platform.Stat_t, syscall.Errno) {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Lstat(relativePath)
 }
 
 // Stat implements FS.Stat
-func (c *CompositeFS) Stat(path string) (platform.Stat_t, error) {
+func (c *CompositeFS) Stat(path string) (platform.Stat_t, syscall.Errno) {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Stat(relativePath)
 }
 
 // Mkdir implements FS.Mkdir
-func (c *CompositeFS) Mkdir(path string, perm fs.FileMode) error {
+func (c *CompositeFS) Mkdir(path string, perm fs.FileMode) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Mkdir(relativePath, perm)
 }
 
 // Chmod implements FS.Chmod
-func (c *CompositeFS) Chmod(path string, perm fs.FileMode) error {
+func (c *CompositeFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Chmod(relativePath, perm)
 }
 
 // Chown implements FS.Chown
-func (c *CompositeFS) Chown(path string, uid, gid int) error {
+func (c *CompositeFS) Chown(path string, uid, gid int) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Chown(relativePath, uid, gid)
 }
 
 // Lchown implements FS.Lchown
-func (c *CompositeFS) Lchown(path string, uid, gid int) error {
+func (c *CompositeFS) Lchown(path string, uid, gid int) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Lchown(relativePath, uid, gid)
 }
 
 // Rename implements FS.Rename
-func (c *CompositeFS) Rename(from, to string) error {
+func (c *CompositeFS) Rename(from, to string) syscall.Errno {
 	fromFS, fromPath := c.chooseFS(from)
 	toFS, toPath := c.chooseFS(to)
 	if fromFS != toFS {
@@ -292,13 +292,13 @@ func (c *CompositeFS) Rename(from, to string) error {
 }
 
 // Readlink implements FS.Readlink
-func (c *CompositeFS) Readlink(path string) (string, error) {
+func (c *CompositeFS) Readlink(path string) (string, syscall.Errno) {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Readlink(relativePath)
 }
 
 // Link implements FS.Link.
-func (c *CompositeFS) Link(oldName, newName string) error {
+func (c *CompositeFS) Link(oldName, newName string) syscall.Errno {
 	fromFS, oldNamePath := c.chooseFS(oldName)
 	toFS, newNamePath := c.chooseFS(newName)
 	if fromFS != toFS {
@@ -308,13 +308,13 @@ func (c *CompositeFS) Link(oldName, newName string) error {
 }
 
 // Utimens implements FS.Utimens
-func (c *CompositeFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error {
+func (c *CompositeFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Utimens(relativePath, times, symlinkFollow)
 }
 
 // Symlink implements FS.Symlink
-func (c *CompositeFS) Symlink(oldName, link string) (err error) {
+func (c *CompositeFS) Symlink(oldName, link string) (err syscall.Errno) {
 	fromFS, oldNamePath := c.chooseFS(oldName)
 	toFS, linkPath := c.chooseFS(link)
 	if fromFS != toFS {
@@ -324,19 +324,19 @@ func (c *CompositeFS) Symlink(oldName, link string) (err error) {
 }
 
 // Truncate implements FS.Truncate
-func (c *CompositeFS) Truncate(path string, size int64) error {
+func (c *CompositeFS) Truncate(path string, size int64) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Truncate(relativePath, size)
 }
 
 // Rmdir implements FS.Rmdir
-func (c *CompositeFS) Rmdir(path string) error {
+func (c *CompositeFS) Rmdir(path string) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Rmdir(relativePath)
 }
 
 // Unlink implements FS.Unlink
-func (c *CompositeFS) Unlink(path string) error {
+func (c *CompositeFS) Unlink(path string) syscall.Errno {
 	matchIndex, relativePath := c.chooseFS(path)
 	return c.fs[matchIndex].Unlink(relativePath)
 }
@@ -479,10 +479,10 @@ loop:
 type fakeRootFS struct{ UnimplementedFS }
 
 // OpenFile implements FS.OpenFile
-func (*fakeRootFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
+func (*fakeRootFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno) {
 	switch path {
 	case ".", "/", "":
-		return fakeRootDir{}, nil
+		return fakeRootDir{}, 0
 	}
 	return nil, syscall.ENOENT
 }
diff --git a/internal/sysfs/sysfs.go b/internal/sysfs/sysfs.go
index e677c120..41869723 100644
--- a/internal/sysfs/sysfs.go
+++ b/internal/sysfs/sysfs.go
@@ -19,7 +19,20 @@ import (
 // Implementations should embed UnimplementedFS for forward compatability. Any
 // unsupported method or parameter should return syscall.ENOSYS.
 //
-// See https://github.com/golang/go/issues/45757
+// # Errors
+//
+// All methods that can return an error return a syscall.Errno, which is zero
+// on success.
+//
+// Restricting to syscall.Errno matches current WebAssembly host functions,
+// which are constrained to well-known error codes. For example, `GOOS=js` maps
+// hard coded values and panics otherwise. More commonly, WASI maps syscall
+// errors to u32 numeric values.
+//
+// # Notes
+//
+// A writable filesystem abstraction is not yet implemented as of Go 1.20. See
+// https://github.com/golang/go/issues/45757
 type FS interface {
 	// String should return a human-readable format of the filesystem
 	//
@@ -31,7 +44,8 @@ type FS interface {
 	String() string
 
 	// OpenFile is similar to os.OpenFile, except the path is relative to this
-	// file system, and syscall.Errno are returned instead of a os.PathError.
+	// file system, and syscall.Errno are returned instead of an os.PathError.
+	// A zero syscall.Errno is success.
 	//
 	// # Errors
 	//
@@ -57,7 +71,7 @@ type FS interface {
 	//   - flag are the same as OpenFile, for example, os.O_CREATE.
 	//   - Implications of permissions when os.O_CREATE are described in Chmod
 	//     notes.
-	OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error)
+	OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno)
 	// ^^ TODO: Consider syscall.Open, though this implies defining and
 	// coercing flags and perms similar to what is done in os.OpenFile.
 
@@ -75,7 +89,7 @@ type FS interface {
 	//     same value.
 	//   - When the path is a symbolic link, the stat returned is for the link,
 	//     not the file it refers to.
-	Lstat(path string) (platform.Stat_t, error)
+	Lstat(path string) (platform.Stat_t, syscall.Errno)
 
 	// Stat is similar to syscall.Stat, except the path is relative to this
 	// file system.
@@ -91,10 +105,11 @@ type FS interface {
 	//     same value.
 	//   - When the path is a symbolic link, the stat returned is for the file
 	//     it refers to.
-	Stat(path string) (platform.Stat_t, error)
+	Stat(path string) (platform.Stat_t, syscall.Errno)
 
 	// Mkdir is similar to os.Mkdir, except the path is relative to this file
-	// system, and syscall.Errno are returned instead of a os.PathError.
+	// system, and syscall.Errno are returned instead of a os.PathError. A zero
+	// syscall.Errno is success.
 	//
 	// # Errors
 	//
@@ -106,12 +121,13 @@ type FS interface {
 	// # Notes
 	//
 	//   - Implications of permissions are described in Chmod notes.
-	Mkdir(path string, perm fs.FileMode) error
+	Mkdir(path string, perm fs.FileMode) syscall.Errno
 	// ^^ TODO: Consider syscall.Mkdir, though this implies defining and
 	// coercing flags and perms similar to what is done in os.Mkdir.
 
 	// Chmod is similar to os.Chmod, except the path is relative to this file
-	// system, and syscall.Errno are returned instead of a os.PathError.
+	// system, and syscall.Errno are returned instead of a os.PathError. A zero
+	// syscall.Errno is success.
 	//
 	// # Errors
 	//
@@ -124,12 +140,13 @@ type FS interface {
 	//   - Windows ignores the execute bit, and any permissions come back as
 	//     group and world. For example, chmod of 0400 reads back as 0444, and
 	//     0700 0666. Also, permissions on directories aren't supported at all.
-	Chmod(path string, perm fs.FileMode) error
+	Chmod(path string, perm fs.FileMode) syscall.Errno
 	// ^^ TODO: Consider syscall.Chmod, though this implies defining and
 	// coercing flags and perms similar to what is done in os.Chmod.
 
 	// Chown is like os.Chown except the path is relative to this file
 	// system, and syscall.Errno are returned instead of an os.PathError.
+	// A zero syscall.Errno is success.
 	//
 	// # Errors
 	//
@@ -141,11 +158,11 @@ type FS interface {
 	//
 	//   - Windows will always return syscall.ENOSYS
 	//   - This is similar to https://linux.die.net/man/3/chown
-	Chown(path string, uid, gid int) error
+	Chown(path string, uid, gid int) syscall.Errno
 
 	// Lchown is like os.Lchown except the path is relative to this file
-	// system, and syscall.Errno are returned instead of a os.PathError.
-	//	See https://linux.die.net/man/3/lchown
+	// system, and syscall.Errno are returned instead of an os.PathError. A
+	// zero syscall.Errno is success.
 	//
 	// # Errors
 	//
@@ -157,7 +174,7 @@ type FS interface {
 	//
 	//   - Windows will always return syscall.ENOSYS
 	//   - This is similar to https://linux.die.net/man/3/lchown
-	Lchown(path string, uid, gid int) error
+	Lchown(path string, uid, gid int) syscall.Errno
 
 	// Rename is similar to syscall.Rename, except the path is relative to this
 	// file system.
@@ -175,7 +192,7 @@ type FS interface {
 	// # Notes
 	//
 	//   -  Windows doesn't let you overwrite an existing directory.
-	Rename(from, to string) error
+	Rename(from, to string) syscall.Errno
 
 	// Rmdir is similar to syscall.Rmdir, except the path is relative to this
 	// file system.
@@ -191,7 +208,7 @@ type FS interface {
 	// # Notes
 	//
 	//   - As of Go 1.19, Windows maps syscall.ENOTDIR to syscall.ENOENT.
-	Rmdir(path string) error
+	Rmdir(path string) syscall.Errno
 
 	// Unlink is similar to syscall.Unlink, except the path is relative to this
 	// file system.
@@ -208,7 +225,7 @@ type FS interface {
 	//   - On Windows, syscall.Unlink doesn't delete symlink to directory unlike other platforms. Implementations might
 	//     want to combine syscall.RemoveDirectory with syscall.Unlink in order to delete such links on Windows.
 	//     See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya
-	Unlink(path string) error
+	Unlink(path string) syscall.Errno
 
 	// Link is similar to syscall.Link, except the path is relative to this
 	// file system. This creates "hard" link from oldPath to newPath, in
@@ -220,7 +237,7 @@ type FS interface {
 	//   - syscall.EPERM: `oldPath` is invalid.
 	//   - syscall.ENOENT: `oldPath` doesn't exist.
 	//   - syscall.EISDIR: `newPath` exists, but is a directory.
-	Link(oldPath, newPath string) error
+	Link(oldPath, newPath string) syscall.Errno
 
 	// Symlink is similar to syscall.Symlink, except the `oldPath` is relative
 	// to this file system. This creates "soft" link from oldPath to newPath,
@@ -242,7 +259,7 @@ type FS interface {
 	//   - Symlinks in Windows requires `SeCreateSymbolicLinkPrivilege`.
 	//     Otherwise, syscall.EPERM results.
 	//     See https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
-	Symlink(oldPath, linkName string) error
+	Symlink(oldPath, linkName string) syscall.Errno
 
 	// Readlink is similar to syscall.Readlink, except the path is relative to
 	// this file system.
@@ -256,7 +273,7 @@ type FS interface {
 	//   - On Windows, the path separator is different from other platforms,
 	//     but to provide consistent results to Wasm, this normalizes to a "/"
 	//     separator.
-	Readlink(path string) (string, error)
+	Readlink(path string) (string, syscall.Errno)
 
 	// Truncate is similar to syscall.Truncate, except the path is relative to
 	// this file system.
@@ -267,7 +284,7 @@ type FS interface {
 	//   - syscall.EINVAL: `path` is invalid or size is negative.
 	//   - syscall.ENOENT: `path` doesn't exist
 	//   - syscall.EACCES: `path` doesn't have write access.
-	Truncate(path string, size int64) error
+	Truncate(path string, size int64) syscall.Errno
 
 	// Utimens set file access and modification times on a path relative to
 	// this file system, at nanosecond precision.
@@ -296,7 +313,7 @@ type FS interface {
 	//     values UTIME_NOW or UTIME_NOW.
 	//   - This is like `utimensat` with `AT_FDCWD` in POSIX. See
 	//     https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
-	Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error
+	Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno
 }
 
 // ReaderAtOffset gets an io.Reader from a fs.File that reads from an offset,
@@ -317,7 +334,7 @@ func ReaderAtOffset(f fs.File, offset int64) io.Reader {
 }
 
 // FileDatasync is like syscall.Fdatasync except that's only defined in linux.
-func FileDatasync(f fs.File) (err error) {
+func FileDatasync(f fs.File) (err syscall.Errno) {
 	return platform.Fdatasync(f)
 }
 
diff --git a/internal/sysfs/unsupported.go b/internal/sysfs/unsupported.go
index 7e5a4bc7..6fb9114f 100644
--- a/internal/sysfs/unsupported.go
+++ b/internal/sysfs/unsupported.go
@@ -22,76 +22,76 @@ func (UnimplementedFS) Open(name string) (fs.File, error) {
 }
 
 // OpenFile implements FS.OpenFile
-func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, error) {
+func (UnimplementedFS) OpenFile(path string, flag int, perm fs.FileMode) (fs.File, syscall.Errno) {
 	return nil, syscall.ENOSYS
 }
 
 // Lstat implements FS.Lstat
-func (UnimplementedFS) Lstat(path string) (platform.Stat_t, error) {
+func (UnimplementedFS) Lstat(path string) (platform.Stat_t, syscall.Errno) {
 	return platform.Stat_t{}, syscall.ENOSYS
 }
 
 // Stat implements FS.Stat
-func (UnimplementedFS) Stat(path string) (platform.Stat_t, error) {
+func (UnimplementedFS) Stat(path string) (platform.Stat_t, syscall.Errno) {
 	return platform.Stat_t{}, syscall.ENOSYS
 }
 
 // Readlink implements FS.Readlink
-func (UnimplementedFS) Readlink(path string) (string, error) {
+func (UnimplementedFS) Readlink(path string) (string, syscall.Errno) {
 	return "", syscall.ENOSYS
 }
 
 // Mkdir implements FS.Mkdir
-func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) error {
+func (UnimplementedFS) Mkdir(path string, perm fs.FileMode) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Chmod implements FS.Chmod
-func (UnimplementedFS) Chmod(path string, perm fs.FileMode) error {
+func (UnimplementedFS) Chmod(path string, perm fs.FileMode) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Chown implements FS.Chown
-func (UnimplementedFS) Chown(path string, uid, gid int) error {
+func (UnimplementedFS) Chown(path string, uid, gid int) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Lchown implements FS.Lchown
-func (UnimplementedFS) Lchown(path string, uid, gid int) error {
+func (UnimplementedFS) Lchown(path string, uid, gid int) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Rename implements FS.Rename
-func (UnimplementedFS) Rename(from, to string) error {
+func (UnimplementedFS) Rename(from, to string) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Rmdir implements FS.Rmdir
-func (UnimplementedFS) Rmdir(path string) error {
+func (UnimplementedFS) Rmdir(path string) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Link implements FS.Link
-func (UnimplementedFS) Link(_, _ string) error {
+func (UnimplementedFS) Link(_, _ string) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Symlink implements FS.Symlink
-func (UnimplementedFS) Symlink(_, _ string) error {
+func (UnimplementedFS) Symlink(_, _ string) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Unlink implements FS.Unlink
-func (UnimplementedFS) Unlink(path string) error {
+func (UnimplementedFS) Unlink(path string) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Utimens implements FS.Utimens
-func (UnimplementedFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) error {
+func (UnimplementedFS) Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
 	return syscall.ENOSYS
 }
 
 // Truncate implements FS.Truncate
-func (UnimplementedFS) Truncate(string, int64) error {
+func (UnimplementedFS) Truncate(string, int64) syscall.Errno {
 	return syscall.ENOSYS
 }
diff --git a/internal/wasi_snapshot_preview1/errno.go b/internal/wasi_snapshot_preview1/errno.go
index d29ea10c..6537120b 100644
--- a/internal/wasi_snapshot_preview1/errno.go
+++ b/internal/wasi_snapshot_preview1/errno.go
@@ -2,10 +2,7 @@ package wasi_snapshot_preview1
 
 import (
 	"fmt"
-	"io"
 	"syscall"
-
-	"github.com/tetratelabs/wazero/internal/platform"
 )
 
 // Errno is neither uint16 nor an alias for parity with wasm.ValueType.
@@ -266,13 +263,10 @@ var errnoToString = [...]string{
 // Note: Coercion isn't centralized in sys.FSContext because ABI use different
 // error codes. For example, wasi-filesystem and GOOS=js don't map to these
 // Errno.
-func ToErrno(err error) Errno {
-	if err == nil || err == io.EOF {
-		return ErrnoSuccess // io.EOF has no value in WASI, and isn't an error.
-	}
-	errno := platform.UnwrapOSError(err)
-
+func ToErrno(errno syscall.Errno) Errno {
 	switch errno {
+	case 0:
+		return ErrnoSuccess
 	case syscall.EACCES:
 		return ErrnoAcces
 	case syscall.EAGAIN:
@@ -281,6 +275,8 @@ func ToErrno(err error) Errno {
 		return ErrnoBadf
 	case syscall.EEXIST:
 		return ErrnoExist
+	case syscall.EFAULT:
+		return ErrnoFault
 	case syscall.EINTR:
 		return ErrnoIntr
 	case syscall.EINVAL:


Test patch:
diff --git a/imports/wasi_snapshot_preview1/fs_test.go b/imports/wasi_snapshot_preview1/fs_test.go
index fb400392..8e0963ab 100644
--- a/imports/wasi_snapshot_preview1/fs_test.go
+++ b/imports/wasi_snapshot_preview1/fs_test.go
@@ -11,6 +11,7 @@ import (
 	"path"
 	"runtime"
 	"strings"
+	"syscall"
 	"testing"
 	"time"
 
@@ -56,8 +57,8 @@ func Test_fdAllocate(t *testing.T) {
 	preopen := fsc.RootFS()
 	defer r.Close(testCtx)
 
-	fd, err := fsc.OpenFile(preopen, fileName, os.O_RDWR, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR, 0)
+	require.Zero(t, errno)
 
 	f, ok := fsc.LookupFile(fd)
 	require.True(t, ok)
@@ -134,11 +135,11 @@ func Test_fdClose(t *testing.T) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fdToClose, err := fsc.OpenFile(preopen, path1, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fdToClose, errno := fsc.OpenFile(preopen, path1, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
-	fdToKeep, err := fsc.OpenFile(preopen, path2, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fdToKeep, errno := fsc.OpenFile(preopen, path2, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	// Close
 	requireErrnoResult(t, ErrnoSuccess, mod, FdCloseName, uint64(fdToClose))
@@ -227,11 +228,11 @@ func Test_fdFdstatGet(t *testing.T) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fileFD, err := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
-	dirFD, err := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	tests := []struct {
 		name             string
@@ -378,8 +379,8 @@ func Test_fdFdstatSetFlags(t *testing.T) {
 	defer r.Close(testCtx)
 
 	// First, open it with O_APPEND.
-	fd, err := fsc.OpenFile(preopen, fileName, os.O_RDWR|os.O_APPEND, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, fileName, os.O_RDWR|os.O_APPEND, 0)
+	require.Zero(t, errno)
 
 	writeWazero := func() {
 		iovs := uint32(1) // arbitrary offset
@@ -472,11 +473,11 @@ func Test_fdFilestatGet(t *testing.T) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fileFD, err := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fileFD, errno := fsc.OpenFile(preopen, file, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
-	dirFD, err := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	dirFD, errno := fsc.OpenFile(preopen, dir, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	tests := []struct {
 		name               string
@@ -1810,9 +1811,9 @@ var (
 			panic(err)
 		}
 		defer d.Close()
-		dirents, err := platform.Readdir(d, -1)
-		if err != nil {
-			panic(err)
+		dirents, errno := platform.Readdir(d, -1)
+		if errno != 0 {
+			panic(errno)
 		}
 		dots := []*platform.Dirent{
 			{Name: ".", Type: fs.ModeDir},
@@ -1884,8 +1885,8 @@ func Test_fdReaddir(t *testing.T) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fd, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	tests := []struct {
 		name            string
@@ -1970,8 +1971,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				dirent, err := platform.Readdir(dir, 1)
-				require.NoError(t, err)
+				dirent, errno := platform.Readdir(dir, 1)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -1995,8 +1996,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				dirent, err := platform.Readdir(dir, 1)
-				require.NoError(t, err)
+				dirent, errno := platform.Readdir(dir, 1)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -2021,8 +2022,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				dirent, err := platform.Readdir(dir, 1)
-				require.NoError(t, err)
+				dirent, errno := platform.Readdir(dir, 1)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -2046,8 +2047,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				dirent, err := platform.Readdir(dir, 1)
-				require.NoError(t, err)
+				dirent, errno := platform.Readdir(dir, 1)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -2071,8 +2072,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				two, err := platform.Readdir(dir, 2)
-				require.NoError(t, err)
+				two, errno := platform.Readdir(dir, 2)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -2096,8 +2097,8 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				two, err := platform.Readdir(dir, 2)
-				require.NoError(t, err)
+				two, errno := platform.Readdir(dir, 2)
+				require.Zero(t, errno)
 
 				return &sys.FileEntry{
 					File: dir,
@@ -2121,7 +2122,7 @@ func Test_fdReaddir(t *testing.T) {
 			dir: func() *sys.FileEntry {
 				dir, err := fstest.FS.Open("dir")
 				require.NoError(t, err)
-				_, err = platform.Readdir(dir, 3)
+				_, errno = platform.Readdir(dir, 3)
 				require.NoError(t, err)
 
 				return &sys.FileEntry{
@@ -2189,8 +2190,8 @@ func Test_fdReaddir_Rewind(t *testing.T) {
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 
-	fd, err := fsc.OpenFile(fsc.RootFS(), "dir", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(fsc.RootFS(), "dir", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	mem := mod.Memory()
 	const resultBufused, buf, bufSize = 0, 8, 200
@@ -2253,11 +2254,11 @@ func Test_fdReaddir_Errors(t *testing.T) {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fileFD, err := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fileFD, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
-	dirFD, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	dirFD, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	tests := []struct {
 		name                           string
@@ -2453,12 +2454,12 @@ func Test_fdRenumber(t *testing.T) {
 			preopen := fsc.RootFS()
 
 			// Sanity check of the file descriptor assignment.
-			fileFDAssigned, err := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
-			require.NoError(t, err)
+			fileFDAssigned, errno := fsc.OpenFile(preopen, "animals.txt", os.O_RDONLY, 0)
+			require.Zero(t, errno)
 			require.Equal(t, uint32(fileFD), fileFDAssigned)
 
-			dirFDAssigned, err := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
-			require.NoError(t, err)
+			dirFDAssigned, errno := fsc.OpenFile(preopen, "dir", os.O_RDONLY, 0)
+			require.Zero(t, errno)
 			require.Equal(t, uint32(dirFD), dirFDAssigned)
 
 			requireErrnoResult(t, tc.expectedErrno, mod, FdRenumberName, uint64(tc.from), uint64(tc.to))
@@ -2565,7 +2566,7 @@ func Test_fdSeek_Errors(t *testing.T) {
 	defer r.Close(testCtx)
 
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	require.NoError(t, fsc.RootFS().Mkdir("dir", 0o0700))
+	require.Zero(t, fsc.RootFS().Mkdir("dir", 0o0700))
 	dirFD := requireOpenFD(t, mod, "dir")
 
 	memorySize := mod.Memory().Size()
@@ -3551,10 +3552,10 @@ func Test_pathFilestatSetTimes(t *testing.T) {
 			fsc := sys.FS()
 
 			var oldSt platform.Stat_t
-			var err error
+			var errno syscall.Errno
 			if tc.expectedErrno == ErrnoSuccess {
-				oldSt, err = fsc.RootFS().Stat(pathName)
-				require.NoError(t, err)
+				oldSt, errno = fsc.RootFS().Stat(pathName)
+				require.Zero(t, errno)
 			}
 
 			requireErrnoResult(t, tc.expectedErrno, mod, PathFilestatSetTimesName, uint64(fd), uint64(tc.flags),
@@ -3565,8 +3566,8 @@ func Test_pathFilestatSetTimes(t *testing.T) {
 				return
 			}
 
-			newSt, err := fsc.RootFS().Stat(pathName)
-			require.NoError(t, err)
+			newSt, errno := fsc.RootFS().Stat(pathName)
+			require.Zero(t, errno)
 
 			if platform.CompilerSupported() {
 				if tc.fstFlags&FstflagsAtim != 0 {
@@ -3604,13 +3605,13 @@ func Test_pathLink(t *testing.T) {
 	newDirPath := joinPath(tmpDir, newDirName)
 	require.NoError(t, os.MkdirAll(joinPath(tmpDir, newDirName), 0o700))
 	fsc := mod.(*wasm.CallContext).Sys.FS()
-	newFd, err := fsc.OpenFile(fsc.RootFS(), newDirName, 0o600, 0)
-	require.NoError(t, err)
+	newFd, errno := fsc.OpenFile(fsc.RootFS(), newDirName, 0o600, 0)
+	require.Zero(t, errno)
 
 	mem := mod.Memory()
 
 	fileName := "file"
-	err = os.WriteFile(joinPath(oldDirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
+	err := os.WriteFile(joinPath(oldDirPath, fileName), []byte{1, 2, 3, 4}, 0o700)
 	require.NoError(t, err)
 
 	file := uint32(0xff)
@@ -3641,8 +3642,8 @@ func Test_pathLink(t *testing.T) {
 			require.NoError(t, f.Close())
 		}()
 
-		st, err := platform.StatFile(f)
-		require.NoError(t, err)
+		st, errno := platform.StatFile(f)
+		require.Zero(t, errno)
 		require.False(t, st.Mode&os.ModeSymlink == os.ModeSymlink)
 		require.Equal(t, uint64(2), st.Nlink)
 	})
@@ -3761,7 +3762,7 @@ func Test_pathOpen(t *testing.T) {
 				contents := []byte("hello")
 				_, err := sys.WriterForFile(fsc, expectedOpenedFd).Write(contents)
 				require.NoError(t, err)
-				require.NoError(t, fsc.CloseFile(expectedOpenedFd))
+				require.Zero(t, fsc.CloseFile(expectedOpenedFd))
 
 				// verify the contents were appended
 				b := readFile(t, dir, appendName)
@@ -3793,7 +3794,7 @@ func Test_pathOpen(t *testing.T) {
 				contents := []byte("hello")
 				_, err := sys.WriterForFile(fsc, expectedOpenedFd).Write(contents)
 				require.NoError(t, err)
-				require.NoError(t, fsc.CloseFile(expectedOpenedFd))
+				require.Zero(t, fsc.CloseFile(expectedOpenedFd))
 
 				// verify the contents were written
 				b := readFile(t, dir, "creat")
@@ -3825,7 +3826,7 @@ func Test_pathOpen(t *testing.T) {
 				contents := []byte("hello")
 				_, err := sys.WriterForFile(fsc, expectedOpenedFd).Write(contents)
 				require.NoError(t, err)
-				require.NoError(t, fsc.CloseFile(expectedOpenedFd))
+				require.Zero(t, fsc.CloseFile(expectedOpenedFd))
 
 				// verify the contents were written
 				b := readFile(t, dir, joinPath(dirName, "O_CREAT-O_TRUNC"))
@@ -3890,7 +3891,7 @@ func Test_pathOpen(t *testing.T) {
 				contents := []byte("hello")
 				_, err := sys.WriterForFile(fsc, expectedOpenedFd).Write(contents)
 				require.NoError(t, err)
-				require.NoError(t, fsc.CloseFile(expectedOpenedFd))
+				require.Zero(t, fsc.CloseFile(expectedOpenedFd))
 
 				// verify the contents were truncated
 				b := readFile(t, dir, "trunc")
@@ -3958,8 +3959,8 @@ func requireOpenFD(t *testing.T, mod api.Module, path string) uint32 {
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fd, err := fsc.OpenFile(preopen, path, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, path, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 	return fd
 }
 
@@ -4838,8 +4839,8 @@ func requireOpenFile(t *testing.T, tmpDir string, pathName string, data []byte,
 	fsc := mod.(*wasm.CallContext).Sys.FS()
 	preopen := fsc.RootFS()
 
-	fd, err := fsc.OpenFile(preopen, pathName, oflags, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, pathName, oflags, 0)
+	require.Zero(t, errno)
 
 	return mod, fd, log, r
 }
@@ -4867,12 +4868,12 @@ func Test_fdReaddir_dotEntriesHaveRealInodes(t *testing.T) {
 		uint64(sys.FdPreopen), uint64(0), uint64(len(readDirTarget)))
 
 	// Open the directory, before writing files!
-	fd, err := fsc.OpenFile(preopen, readDirTarget, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fd, errno := fsc.OpenFile(preopen, readDirTarget, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	// get the real inode of the current directory
-	st, err := preopen.Stat(readDirTarget)
-	require.NoError(t, err)
+	st, errno := preopen.Stat(readDirTarget)
+	require.Zero(t, errno)
 	dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}         // d_next = 1
 	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
 	dirents = append(dirents, 1, 0, 0, 0)             // d_namlen = 1 character
@@ -4880,8 +4881,8 @@ func Test_fdReaddir_dotEntriesHaveRealInodes(t *testing.T) {
 	dirents = append(dirents, '.')                    // name
 
 	// get the real inode of the parent directory
-	st, err = preopen.Stat(".")
-	require.NoError(t, err)
+	st, errno = preopen.Stat(".")
+	require.Zero(t, errno)
 	dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
 	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
 	dirents = append(dirents, 2, 0, 0, 0)             // d_namlen = 2 characters
@@ -4926,8 +4927,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
 		uint64(sys.FdPreopen), uint64(0), uint64(len(dirName)))
 
 	// Open the directory, before writing files!
-	dirFD, err := fsc.OpenFile(preopen, dirName, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	dirFD, errno := fsc.OpenFile(preopen, dirName, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	// Then write a file to the directory.
 	f, err := os.Create(joinPath(dirPath, "file"))
@@ -4935,8 +4936,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
 	defer f.Close()
 
 	// get the real inode of the current directory
-	st, err := preopen.Stat(dirName)
-	require.NoError(t, err)
+	st, errno := preopen.Stat(dirName)
+	require.Zero(t, errno)
 	dirents := []byte{1, 0, 0, 0, 0, 0, 0, 0}         // d_next = 1
 	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
 	dirents = append(dirents, 1, 0, 0, 0)             // d_namlen = 1 character
@@ -4944,8 +4945,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
 	dirents = append(dirents, '.')                    // name
 
 	// get the real inode of the parent directory
-	st, err = preopen.Stat(".")
-	require.NoError(t, err)
+	st, errno = preopen.Stat(".")
+	require.Zero(t, errno)
 	dirents = append(dirents, 2, 0, 0, 0, 0, 0, 0, 0) // d_next = 2
 	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
 	dirents = append(dirents, 2, 0, 0, 0)             // d_namlen = 2 characters
@@ -4953,8 +4954,8 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
 	dirents = append(dirents, '.', '.')               // name
 
 	// get the real inode of the file
-	st, err = platform.StatFile(f)
-	require.NoError(t, err)
+	st, errno = platform.StatFile(f)
+	require.Zero(t, errno)
 	dirents = append(dirents, 3, 0, 0, 0, 0, 0, 0, 0) // d_next = 3
 	dirents = append(dirents, u64.LeBytes(st.Ino)...) // d_ino
 	dirents = append(dirents, 4, 0, 0, 0)             // d_namlen = 4 characters
diff --git a/imports/wasi_snapshot_preview1/fs_unit_test.go b/imports/wasi_snapshot_preview1/fs_unit_test.go
index dcb01fa0..5d8f12d6 100644
--- a/imports/wasi_snapshot_preview1/fs_unit_test.go
+++ b/imports/wasi_snapshot_preview1/fs_unit_test.go
@@ -19,7 +19,7 @@ func Test_fdRead_shouldContinueRead(t *testing.T) {
 		n, l          uint32
 		err           error
 		expectedOk    bool
-		expectedErrno Errno
+		expectedErrno syscall.Errno
 	}{
 		{
 			name: "break when nothing to read",
@@ -66,13 +66,13 @@ func Test_fdRead_shouldContinueRead(t *testing.T) {
 		{
 			name:          "return ErrnoIo on error on nothing to read",
 			err:           io.ErrClosedPipe,
-			expectedErrno: ErrnoIo,
+			expectedErrno: syscall.EIO,
 		},
 		{
 			name:          "return ErrnoIo on error on nothing read",
 			l:             4,
 			err:           io.ErrClosedPipe,
-			expectedErrno: ErrnoIo,
+			expectedErrno: syscall.EIO,
 		},
 		{ // Special case, allows processing data before err
 			name: "break on error on partial read",
@@ -93,7 +93,7 @@ func Test_fdRead_shouldContinueRead(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ok, errno := fdRead_shouldContinueRead(tc.n, tc.l, tc.err)
 			require.Equal(t, tc.expectedOk, ok)
-			require.Equal(t, tc.expectedErrno, errno)
+			require.EqualErrno(t, tc.expectedErrno, errno)
 		})
 	}
 }
@@ -104,7 +104,7 @@ func Test_lastDirents(t *testing.T) {
 		f               *sys.ReadDir
 		cookie          int64
 		expectedDirents []*platform.Dirent
-		expectedErrno   Errno
+		expectedErrno   syscall.Errno
 	}{
 		{
 			name: "no prior call",
@@ -112,7 +112,7 @@ func Test_lastDirents(t *testing.T) {
 		{
 			name:          "no prior call, but passed a cookie",
 			cookie:        1,
-			expectedErrno: ErrnoInval,
+			expectedErrno: syscall.EINVAL,
 		},
 		{
 			name: "cookie is negative",
@@ -121,7 +121,7 @@ func Test_lastDirents(t *testing.T) {
 				Dirents:   testDirents,
 			},
 			cookie:        -1,
-			expectedErrno: ErrnoInval,
+			expectedErrno: syscall.EINVAL,
 		},
 		{
 			name: "cookie is greater than last d_next",
@@ -130,7 +130,7 @@ func Test_lastDirents(t *testing.T) {
 				Dirents:   testDirents,
 			},
 			cookie:        5,
-			expectedErrno: ErrnoInval,
+			expectedErrno: syscall.EINVAL,
 		},
 		{
 			name: "cookie is last pos",
@@ -157,7 +157,7 @@ func Test_lastDirents(t *testing.T) {
 				Dirents:   testDirents,
 			},
 			cookie:        1,
-			expectedErrno: ErrnoNosys, // not implemented
+			expectedErrno: syscall.ENOSYS, // not implemented
 		},
 		{
 			name: "read from the beginning (cookie=0)",
@@ -179,7 +179,7 @@ func Test_lastDirents(t *testing.T) {
 				f = &sys.ReadDir{}
 			}
 			entries, errno := lastDirents(f, tc.cookie)
-			require.Equal(t, tc.expectedErrno, errno)
+			require.EqualErrno(t, tc.expectedErrno, errno)
 			require.Equal(t, tc.expectedDirents, entries)
 		})
 	}
@@ -286,9 +286,9 @@ var (
 			panic(err)
 		}
 		defer dir.Close()
-		dirents, err := platform.Readdir(dir, -1)
-		if err != nil {
-			panic(err)
+		dirents, errno := platform.Readdir(dir, -1)
+		if errno != 0 {
+			panic(errno)
 		}
 		return dirents
 	}()
diff --git a/imports/wasi_snapshot_preview1/wasi_bench_test.go b/imports/wasi_snapshot_preview1/wasi_bench_test.go
index 8a08c5c0..e0313b4e 100644
--- a/imports/wasi_snapshot_preview1/wasi_bench_test.go
+++ b/imports/wasi_snapshot_preview1/wasi_bench_test.go
@@ -192,9 +192,9 @@ func Benchmark_fdReaddir(b *testing.B) {
 
 			// Open the root directory as a file-descriptor.
 			fsc := mod.(*wasm.CallContext).Sys.FS()
-			fd, err := fsc.OpenFile(fsc.RootFS(), ".", os.O_RDONLY, 0)
-			if err != nil {
-				b.Fatal(err)
+			fd, errno := fsc.OpenFile(fsc.RootFS(), ".", os.O_RDONLY, 0)
+			if errno != 0 {
+				b.Fatal(errno)
 			}
 			f, ok := fsc.LookupFile(fd)
 			if !ok {
@@ -216,8 +216,8 @@ func Benchmark_fdReaddir(b *testing.B) {
 				if err = f.File.Close(); err != nil {
 					b.Fatal(err)
 				}
-				if f.File, err = fsc.RootFS().OpenFile(".", os.O_RDONLY, 0); err != nil {
-					b.Fatal(err)
+				if f.File, errno = fsc.RootFS().OpenFile(".", os.O_RDONLY, 0); errno != 0 {
+					b.Fatal(errno)
 				}
 				f.ReadDir = nil
 
@@ -318,9 +318,9 @@ func Benchmark_pathFilestat(b *testing.B) {
 			fd := sys.FdPreopen
 			if bc.fd != sys.FdPreopen {
 				fsc := mod.(*wasm.CallContext).Sys.FS()
-				fd, err = fsc.OpenFile(fsc.RootFS(), "zig", os.O_RDONLY, 0)
-				if err != nil {
-					b.Fatal(err)
+				fd, errno := fsc.OpenFile(fsc.RootFS(), "zig", os.O_RDONLY, 0)
+				if errno != 0 {
+					b.Fatal(errno)
 				}
 				defer fsc.CloseFile(fd) //nolint
 			}
diff --git a/internal/fstest/fstest.go b/internal/fstest/fstest.go
index aac0c564..a9f81eca 100644
--- a/internal/fstest/fstest.go
+++ b/internal/fstest/fstest.go
@@ -100,9 +100,9 @@ func WriteTestFiles(tmpDir string) (err error) {
 			}
 
 			// os.Stat uses GetFileInformationByHandle internally.
-			var st platform.Stat_t
-			if st, err = platform.Stat(path); err != nil {
-				return err
+			st, errno := platform.Stat(path)
+			if errno != 0 {
+				return errno
 			}
 			if st.Mtim == info.ModTime().UnixNano() {
 				return nil // synced!
diff --git a/internal/gojs/errno_test.go b/internal/gojs/errno_test.go
index 266a6e0b..40a380d9 100644
--- a/internal/gojs/errno_test.go
+++ b/internal/gojs/errno_test.go
@@ -39,6 +39,11 @@ func TestToErrno(t *testing.T) {
 			input:    syscall.EEXIST,
 			expected: ErrnoExist,
 		},
+		{
+			name:     "syscall.EFAULT",
+			input:    syscall.EFAULT,
+			expected: ErrnoFault,
+		},
 		{
 			name:     "syscall.EINTR",
 			input:    syscall.EINTR,
diff --git a/internal/platform/bench_test.go b/internal/platform/bench_test.go
index 51cbdfee..9a6b0122 100644
--- a/internal/platform/bench_test.go
+++ b/internal/platform/bench_test.go
@@ -28,7 +28,7 @@ func Benchmark_UtimensFile(b *testing.B) {
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		if err := UtimensFile(f, times); err != nil {
+		if err := UtimensFile(f, times); err != 0 {
 			b.Fatal(err)
 		}
 	}
diff --git a/internal/platform/chown_unix_test.go b/internal/platform/chown_unix_test.go
index 6f499c72..7cc47dae 100644
--- a/internal/platform/chown_unix_test.go
+++ b/internal/platform/chown_unix_test.go
@@ -17,8 +17,10 @@ func TestChown(t *testing.T) {
 
 	dir := path.Join(tmpDir, "dir")
 	require.NoError(t, os.Mkdir(dir, 0o0777))
-	dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+
+	dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	dirStat, err := dirF.Stat()
 	require.NoError(t, err)
 	dirSys := dirStat.Sys().(*syscall.Stat_t)
@@ -30,12 +32,12 @@ func TestChown(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.NoError(t, Chown(dir, -1, -1))
+		require.Zero(t, Chown(dir, -1, -1))
 		checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.NoError(t, Chown(dir, -1, gid))
+		require.Zero(t, Chown(dir, -1, gid))
 		checkUidGid(t, dir, dirSys.Uid, uint32(gid))
 	})
 
@@ -44,11 +46,11 @@ func TestChown(t *testing.T) {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
 			// Test using our Chown
-			require.NoError(t, Chown(dir, -1, g))
+			require.Zero(t, Chown(dir, -1, g))
 			checkUidGid(t, dir, dirSys.Uid, uint32(g))
 
 			// Revert back with os.File.Chown
-			require.NoError(t, dirF.Chown(-1, gid))
+			require.NoError(t, dirF.(*os.File).Chown(-1, gid))
 			checkUidGid(t, dir, dirSys.Uid, uint32(gid))
 		})
 	}
@@ -63,10 +65,13 @@ func TestChownFile(t *testing.T) {
 
 	dir := path.Join(tmpDir, "dir")
 	require.NoError(t, os.Mkdir(dir, 0o0777))
-	dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+
+	dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	dirStat, err := dirF.Stat()
 	require.NoError(t, err)
+
 	dirSys := dirStat.Sys().(*syscall.Stat_t)
 
 	// Similar to TestChownFile in os_unix_test.go, we can't expect to change
@@ -76,12 +81,12 @@ func TestChownFile(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.NoError(t, ChownFile(dirF, -1, -1))
+		require.Zero(t, ChownFile(dirF, -1, -1))
 		checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.NoError(t, ChownFile(dirF, -1, gid))
+		require.Zero(t, ChownFile(dirF, -1, gid))
 		checkUidGid(t, dir, dirSys.Uid, uint32(gid))
 	})
 
@@ -90,11 +95,11 @@ func TestChownFile(t *testing.T) {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
 			// Test using our ChownFile
-			require.NoError(t, ChownFile(dirF, -1, g))
+			require.Zero(t, ChownFile(dirF, -1, g))
 			checkUidGid(t, dir, dirSys.Uid, uint32(g))
 
 			// Revert back with os.File.Chown
-			require.NoError(t, dirF.Chown(-1, gid))
+			require.NoError(t, dirF.(*os.File).Chown(-1, gid))
 			checkUidGid(t, dir, dirSys.Uid, uint32(gid))
 		})
 	}
@@ -110,18 +115,24 @@ func TestLchown(t *testing.T) {
 
 	dir := path.Join(tmpDir, "dir")
 	require.NoError(t, os.Mkdir(dir, 0o0777))
-	dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+
+	dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	dirStat, err := dirF.Stat()
 	require.NoError(t, err)
+
 	dirSys := dirStat.Sys().(*syscall.Stat_t)
 
 	link := path.Join(tmpDir, "link")
 	require.NoError(t, os.Symlink(dir, link))
-	linkF, err := OpenFile(link, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+
+	linkF, errno := OpenFile(link, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	linkStat, err := linkF.Stat()
 	require.NoError(t, err)
+
 	linkSys := linkStat.Sys().(*syscall.Stat_t)
 
 	// Similar to TestLchown in os_unix_test.go, we can't expect to change
@@ -131,12 +142,12 @@ func TestLchown(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.NoError(t, Lchown(link, -1, -1))
+		require.Zero(t, Lchown(link, -1, -1))
 		checkUidGid(t, link, linkSys.Uid, linkSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.NoError(t, Chown(dir, -1, gid))
+		require.Zero(t, Chown(dir, -1, gid))
 		checkUidGid(t, link, linkSys.Uid, uint32(gid))
 		// Make sure the target didn't change.
 		checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
@@ -147,7 +158,7 @@ func TestLchown(t *testing.T) {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
 			// Test using our Lchown
-			require.NoError(t, Lchown(link, -1, g))
+			require.Zero(t, Lchown(link, -1, g))
 			checkUidGid(t, link, linkSys.Uid, uint32(g))
 			// Make sure the target didn't change.
 			checkUidGid(t, dir, dirSys.Uid, dirSys.Gid)
diff --git a/internal/platform/dir_test.go b/internal/platform/dir_test.go
index 5e506b29..2a3607ce 100644
--- a/internal/platform/dir_test.go
+++ b/internal/platform/dir_test.go
@@ -38,21 +38,22 @@ func TestReaddirnames(t *testing.T) {
 			defer dotF.Close()
 
 			t.Run("dir", func(t *testing.T) {
-				names, err := platform.Readdirnames(dotF, -1)
-				require.NoError(t, err)
+				names, errno := platform.Readdirnames(dotF, -1)
+				require.Zero(t, errno)
+
 				sort.Strings(names)
 				require.Equal(t, []string{"animals.txt", "dir", "empty.txt", "emptydir", "sub"}, names)
 
 				// read again even though it is exhausted
-				_, err = platform.Readdirnames(dotF, 100)
-				require.NoError(t, err)
+				_, errno = platform.Readdirnames(dotF, 100)
+				require.Zero(t, errno)
 			})
 
 			// Don't err if something else closed the directory while reading.
 			t.Run("closed dir", func(t *testing.T) {
 				require.NoError(t, dotF.Close())
-				_, err := platform.Readdir(dotF, -1)
-				require.NoError(t, err)
+				_, errno := platform.Readdir(dotF, -1)
+				require.Zero(t, errno)
 			})
 
 			dirF, err := tc.fs.Open("dir")
@@ -60,17 +61,17 @@ func TestReaddirnames(t *testing.T) {
 			defer dirF.Close()
 
 			t.Run("partial", func(t *testing.T) {
-				names1, err := platform.Readdirnames(dirF, 1)
-				require.NoError(t, err)
+				names1, errno := platform.Readdirnames(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(names1))
 
-				names2, err := platform.Readdirnames(dirF, 1)
-				require.NoError(t, err)
+				names2, errno := platform.Readdirnames(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(names2))
 
 				// read exactly the last entry
-				names3, err := platform.Readdirnames(dirF, 1)
-				require.NoError(t, err)
+				names3, errno := platform.Readdirnames(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(names3))
 
 				names := []string{names1[0], names2[0], names3[0]}
@@ -79,8 +80,8 @@ func TestReaddirnames(t *testing.T) {
 				require.Equal(t, []string{"-", "a-", "ab-"}, names)
 
 				// no error reading an exhausted directory
-				_, err = platform.Readdirnames(dirF, 1)
-				require.NoError(t, err)
+				_, errno = platform.Readdirnames(dirF, 1)
+				require.Zero(t, errno)
 			})
 
 			fileF, err := tc.fs.Open("empty.txt")
@@ -88,8 +89,8 @@ func TestReaddirnames(t *testing.T) {
 			defer fileF.Close()
 
 			t.Run("file", func(t *testing.T) {
-				_, err := platform.Readdirnames(fileF, -1)
-				require.EqualErrno(t, syscall.ENOTDIR, err)
+				_, errno := platform.Readdirnames(fileF, -1)
+				require.EqualErrno(t, syscall.ENOTDIR, errno)
 			})
 
 			subdirF, err := tc.fs.Open("sub")
@@ -97,8 +98,9 @@ func TestReaddirnames(t *testing.T) {
 			defer subdirF.Close()
 
 			t.Run("subdir", func(t *testing.T) {
-				names, err := platform.Readdirnames(subdirF, -1)
-				require.NoError(t, err)
+				names, errno := platform.Readdirnames(subdirF, -1)
+				require.Zero(t, errno)
+
 				require.Equal(t, []string{"test.txt"}, names)
 			})
 		})
@@ -130,8 +132,8 @@ func TestReaddir(t *testing.T) {
 			defer dotF.Close()
 
 			t.Run("dir", func(t *testing.T) {
-				dirents, err := platform.Readdir(dotF, -1)
-				require.NoError(t, err) // no io.EOF when -1 is used
+				dirents, errno := platform.Readdir(dotF, -1)
+				require.Zero(t, errno) // no io.EOF when -1 is used
 				sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
 
 				requireIno(t, dirents, tc.expectIno)
@@ -145,16 +147,16 @@ func TestReaddir(t *testing.T) {
 				}, dirents)
 
 				// read again even though it is exhausted
-				dirents, err = platform.Readdir(dotF, 100)
-				require.NoError(t, err)
+				dirents, errno = platform.Readdir(dotF, 100)
+				require.Zero(t, errno)
 				require.Zero(t, len(dirents))
 			})
 
 			// Don't err if something else closed the directory while reading.
 			t.Run("closed dir", func(t *testing.T) {
 				require.NoError(t, dotF.Close())
-				_, err := platform.Readdir(dotF, -1)
-				require.NoError(t, err)
+				_, errno := platform.Readdir(dotF, -1)
+				require.Zero(t, errno)
 			})
 
 			fileF, err := tc.fs.Open("empty.txt")
@@ -162,8 +164,8 @@ func TestReaddir(t *testing.T) {
 			defer fileF.Close()
 
 			t.Run("file", func(t *testing.T) {
-				_, err := platform.Readdir(fileF, -1)
-				require.EqualErrno(t, syscall.ENOTDIR, err)
+				_, errno := platform.Readdir(fileF, -1)
+				require.EqualErrno(t, syscall.ENOTDIR, errno)
 			})
 
 			dirF, err := tc.fs.Open("dir")
@@ -171,17 +173,17 @@ func TestReaddir(t *testing.T) {
 			defer dirF.Close()
 
 			t.Run("partial", func(t *testing.T) {
-				dirents1, err := platform.Readdir(dirF, 1)
-				require.NoError(t, err)
+				dirents1, errno := platform.Readdir(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(dirents1))
 
-				dirents2, err := platform.Readdir(dirF, 1)
-				require.NoError(t, err)
+				dirents2, errno := platform.Readdir(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(dirents2))
 
 				// read exactly the last entry
-				dirents3, err := platform.Readdir(dirF, 1)
-				require.NoError(t, err)
+				dirents3, errno := platform.Readdir(dirF, 1)
+				require.Zero(t, errno)
 				require.Equal(t, 1, len(dirents3))
 
 				dirents := []*platform.Dirent{dirents1[0], dirents2[0], dirents3[0]}
@@ -196,8 +198,8 @@ func TestReaddir(t *testing.T) {
 				}, dirents)
 
 				// no error reading an exhausted directory
-				_, err = platform.Readdir(dirF, 1)
-				require.NoError(t, err)
+				_, errno = platform.Readdir(dirF, 1)
+				require.Zero(t, errno)
 			})
 
 			subdirF, err := tc.fs.Open("sub")
@@ -205,8 +207,8 @@ func TestReaddir(t *testing.T) {
 			defer subdirF.Close()
 
 			t.Run("subdir", func(t *testing.T) {
-				dirents, err := platform.Readdir(subdirF, -1)
-				require.NoError(t, err)
+				dirents, errno := platform.Readdir(subdirF, -1)
+				require.Zero(t, errno)
 				sort.Slice(dirents, func(i, j int) bool { return dirents[i].Name < dirents[j].Name })
 
 				require.Equal(t, 1, len(dirents))
@@ -222,8 +224,8 @@ func TestReaddir(t *testing.T) {
 		require.NoError(t, err)
 		defer dirF.Close()
 
-		dirents, err := platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		dirents, errno := platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(dirents))
 
 		// Speculatively try to remove even if it won't likely work
@@ -235,8 +237,8 @@ func TestReaddir(t *testing.T) {
 			require.NoError(t, err)
 		}
 
-		_, err = platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		_, errno = platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 		// don't validate the contents as due to caching it might be present.
 	})
 }
diff --git a/internal/platform/error_test.go b/internal/platform/error_test.go
index f24b90ea..81a071fb 100644
--- a/internal/platform/error_test.go
+++ b/internal/platform/error_test.go
@@ -3,6 +3,7 @@ package platform
 import (
 	"errors"
 	"fmt"
+	"io"
 	"io/fs"
 	"os"
 	"syscall"
@@ -17,6 +18,11 @@ func TestUnwrapOSError(t *testing.T) {
 		input    error
 		expected syscall.Errno
 	}{
+		{
+			name:     "io.EOF is not an error",
+			input:    io.EOF,
+			expected: 0,
+		},
 		{
 			name:     "LinkError ErrInvalid",
 			input:    &os.LinkError{Err: fs.ErrInvalid},
@@ -77,7 +83,7 @@ func TestUnwrapOSError(t *testing.T) {
 		})
 	}
 
-	t.Run("nil", func(t *testing.T) {
-		require.Nil(t, UnwrapOSError(nil))
+	t.Run("nil -> zero", func(t *testing.T) {
+		require.Zero(t, UnwrapOSError(nil))
 	})
 }
diff --git a/internal/platform/futimens_test.go b/internal/platform/futimens_test.go
index 94f1d1c2..673e5178 100644
--- a/internal/platform/futimens_test.go
+++ b/internal/platform/futimens_test.go
@@ -15,6 +15,7 @@ func TestUtimens(t *testing.T) {
 	t.Run("doesn't exist", func(t *testing.T) {
 		err := Utimens("nope", nil, true)
 		require.EqualErrno(t, syscall.ENOENT, err)
+
 		err = Utimens("nope", nil, false)
 		if SupportsSymlinkNoFollow {
 			require.EqualErrno(t, syscall.ENOENT, err)
@@ -146,8 +147,8 @@ func testFutimens(t *testing.T, usePath bool) {
 					panic(tc)
 				}
 
-				oldSt, err := Lstat(statPath)
-				require.NoError(t, err)
+				oldSt, errno := Lstat(statPath)
+				require.Zero(t, errno)
 
 				if usePath {
 					err = Utimens(path, tc.times, !symlinkNoFollow)
@@ -155,7 +156,7 @@ func testFutimens(t *testing.T, usePath bool) {
 						require.EqualErrno(t, syscall.ENOSYS, err)
 						return
 					}
-					require.NoError(t, err)
+					require.Zero(t, err)
 				} else {
 					flag := syscall.O_RDWR
 					if path == dir {
@@ -165,15 +166,17 @@ func testFutimens(t *testing.T, usePath bool) {
 							t.Skip("windows cannot update timestamps on a dir")
 						}
 					}
-					f, err := OpenFile(path, flag, 0)
-					require.NoError(t, err)
-					err = UtimensFile(f, tc.times)
+
+					f, errno := OpenFile(path, flag, 0)
+					require.Zero(t, errno)
+
+					errno = UtimensFile(f, tc.times)
 					require.NoError(t, f.Close())
-					require.NoError(t, err)
+					require.Zero(t, errno)
 				}
 
-				newSt, err := Lstat(statPath)
-				require.NoError(t, err)
+				newSt, errno := Lstat(statPath)
+				require.Zero(t, errno)
 
 				if CompilerSupported() {
 					if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
@@ -218,20 +221,22 @@ func TestUtimensFile(t *testing.T) {
 		file := path.Join(t.TempDir(), "file")
 		err := os.WriteFile(file, []byte{}, 0o700)
 		require.NoError(t, err)
-		fileF, err := OpenFile(file, syscall.O_RDWR, 0)
-		require.NoError(t, err)
+
+		fileF, errno := OpenFile(file, syscall.O_RDWR, 0)
+		require.Zero(t, errno)
 		require.NoError(t, fileF.Close())
 
-		err = UtimensFile(fileF, nil)
-		require.EqualErrno(t, syscall.EBADF, err)
+		errno = UtimensFile(fileF, nil)
+		require.EqualErrno(t, syscall.EBADF, errno)
 	})
 
 	t.Run("closed dir", func(t *testing.T) {
 		dir := path.Join(t.TempDir(), "dir")
 		err := os.Mkdir(dir, 0o700)
 		require.NoError(t, err)
-		dirF, err := OpenFile(dir, syscall.O_RDONLY, 0)
-		require.NoError(t, err)
+
+		dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
+		require.Zero(t, errno)
 		require.NoError(t, dirF.Close())
 
 		err = UtimensFile(dirF, nil)
diff --git a/internal/platform/open_file_test.go b/internal/platform/open_file_test.go
index 468c03d3..fc2a1c09 100644
--- a/internal/platform/open_file_test.go
+++ b/internal/platform/open_file_test.go
@@ -16,8 +16,8 @@ func TestOpenFile(t *testing.T) {
 	// from os.TestDirFSPathsValid
 	if runtime.GOOS != "windows" {
 		t.Run("strange name", func(t *testing.T) {
-			f, err := OpenFile(path.Join(tmpDir, `e:xperi\ment.txt`), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
-			require.NoError(t, err)
+			f, errno := OpenFile(path.Join(tmpDir, `e:xperi\ment.txt`), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
+			require.Zero(t, errno)
 			require.NoError(t, f.Close())
 		})
 	}
@@ -27,30 +27,30 @@ func TestOpenFile_Errors(t *testing.T) {
 	tmpDir := t.TempDir()
 
 	t.Run("not found must be ENOENT", func(t *testing.T) {
-		_, err := OpenFile(path.Join(tmpDir, "not-really-exist.txt"), os.O_RDONLY, 0o600)
-		require.EqualErrno(t, syscall.ENOENT, err)
+		_, errno := OpenFile(path.Join(tmpDir, "not-really-exist.txt"), os.O_RDONLY, 0o600)
+		require.EqualErrno(t, syscall.ENOENT, errno)
 	})
 
 	// This is the same as https://github.com/ziglang/zig/blob/d24ebf1d12cf66665b52136a2807f97ff021d78d/lib/std/os/test.zig#L105-L112
 	t.Run("try creating on existing file must be EEXIST", func(t *testing.T) {
 		filepath := path.Join(tmpDir, "file.txt")
-		f, err := OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
+		f, errno := OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
+		require.Zero(t, errno)
 		defer require.NoError(t, f.Close())
-		require.NoError(t, err)
 
-		_, err = OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
-		require.EqualErrno(t, syscall.EEXIST, err)
+		_, errno = OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o666)
+		require.EqualErrno(t, syscall.EEXIST, errno)
 	})
 
 	t.Run("writing to a read-only file is EBADF", func(t *testing.T) {
 		path := path.Join(tmpDir, "file")
 		require.NoError(t, os.WriteFile(path, nil, 0o600))
 
-		f, err := OpenFile(path, os.O_RDONLY, 0)
+		f, errno := OpenFile(path, os.O_RDONLY, 0)
 		defer require.NoError(t, f.Close())
-		require.NoError(t, err)
+		require.Zero(t, errno)
 
-		_, err = f.Write([]byte{1, 2, 3, 4})
+		_, err := f.Write([]byte{1, 2, 3, 4})
 		require.EqualErrno(t, syscall.EBADF, UnwrapOSError(err))
 	})
 
@@ -58,11 +58,11 @@ func TestOpenFile_Errors(t *testing.T) {
 		path := path.Join(tmpDir, "diragain")
 		require.NoError(t, os.Mkdir(path, 0o755))
 
-		f, err := OpenFile(path, os.O_RDONLY, 0)
+		f, errno := OpenFile(path, os.O_RDONLY, 0)
 		defer require.NoError(t, f.Close())
-		require.NoError(t, err)
+		require.Zero(t, errno)
 
-		_, err = f.Write([]byte{1, 2, 3, 4})
+		_, err := f.Write([]byte{1, 2, 3, 4})
 		require.EqualErrno(t, syscall.EBADF, UnwrapOSError(err))
 	})
 
@@ -74,10 +74,10 @@ func TestOpenFile_Errors(t *testing.T) {
 		err := os.Symlink(target, symlink)
 		require.NoError(t, err)
 
-		_, err = OpenFile(symlink, O_DIRECTORY|O_NOFOLLOW, 0o0666)
-		require.EqualErrno(t, syscall.ENOTDIR, err)
+		_, errno := OpenFile(symlink, O_DIRECTORY|O_NOFOLLOW, 0o0666)
+		require.EqualErrno(t, syscall.ENOTDIR, errno)
 
-		_, err = OpenFile(symlink, O_NOFOLLOW, 0o0666)
-		require.EqualErrno(t, syscall.ELOOP, err)
+		_, errno = OpenFile(symlink, O_NOFOLLOW, 0o0666)
+		require.EqualErrno(t, syscall.ELOOP, errno)
 	})
 }
diff --git a/internal/platform/rename_test.go b/internal/platform/rename_test.go
index e2f29bc4..25bbf2c0 100644
--- a/internal/platform/rename_test.go
+++ b/internal/platform/rename_test.go
@@ -30,8 +30,8 @@ func TestRename(t *testing.T) {
 		require.NoError(t, err)
 
 		file2Path := path.Join(tmpDir, "file2")
-		err = Rename(file1Path, file2Path)
-		require.NoError(t, err)
+		errno := Rename(file1Path, file2Path)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
 		_, err = os.Stat(file1Path)
@@ -48,12 +48,12 @@ func TestRename(t *testing.T) {
 		require.NoError(t, os.Mkdir(dir1Path, 0o700))
 
 		dir2Path := path.Join(tmpDir, "dir2")
-		err := Rename(dir1Path, dir2Path)
-		require.NoError(t, err)
+		errno := Rename(dir1Path, dir2Path)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
-		_, err = os.Stat(dir1Path)
-		require.EqualErrno(t, syscall.ENOENT, errors.Unwrap(err))
+		_, err := os.Stat(dir1Path)
+		require.EqualErrno(t, syscall.ENOENT, UnwrapOSError(err))
 
 		s, err := os.Stat(dir2Path)
 		require.NoError(t, err)
@@ -107,8 +107,8 @@ func TestRename(t *testing.T) {
 		dir2Path := path.Join(tmpDir, "dir2")
 		require.NoError(t, os.Mkdir(dir2Path, 0o700))
 
-		err = Rename(dir1Path, dir2Path)
-		require.NoError(t, err)
+		errno := Rename(dir1Path, dir2Path)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
 		_, err = os.Stat(dir1Path)
@@ -158,8 +158,8 @@ func TestRename(t *testing.T) {
 		err = os.WriteFile(file2Path, file2Contents, 0o600)
 		require.NoError(t, err)
 
-		err = Rename(file1Path, file2Path)
-		require.NoError(t, err)
+		errno := Rename(file1Path, file2Path)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
 		_, err = os.Stat(file1Path)
@@ -176,8 +176,8 @@ func TestRename(t *testing.T) {
 		dir1Path := path.Join(tmpDir, "dir1")
 		require.NoError(t, os.Mkdir(dir1Path, 0o700))
 
-		err := Rename(dir1Path, dir1Path)
-		require.NoError(t, err)
+		errno := Rename(dir1Path, dir1Path)
+		require.Zero(t, errno)
 
 		s, err := os.Stat(dir1Path)
 		require.NoError(t, err)
@@ -191,8 +191,8 @@ func TestRename(t *testing.T) {
 		err := os.WriteFile(file1Path, file1Contents, 0o600)
 		require.NoError(t, err)
 
-		err = Rename(file1Path, file1Path)
-		require.NoError(t, err)
+		errno := Rename(file1Path, file1Path)
+		require.Zero(t, errno)
 
 		b, err := os.ReadFile(file1Path)
 		require.NoError(t, err)
diff --git a/internal/platform/stat_test.go b/internal/platform/stat_test.go
index ca455c26..53437167 100644
--- a/internal/platform/stat_test.go
+++ b/internal/platform/stat_test.go
@@ -15,15 +15,16 @@ import (
 func TestLstat(t *testing.T) {
 	tmpDir := t.TempDir()
 
-	_, err := Lstat(path.Join(tmpDir, "cat"))
-	require.EqualErrno(t, syscall.ENOENT, err)
-	_, err = Lstat(path.Join(tmpDir, "sub/cat"))
-	require.EqualErrno(t, syscall.ENOENT, err)
+	_, errno := Lstat(path.Join(tmpDir, "cat"))
+	require.EqualErrno(t, syscall.ENOENT, errno)
+	_, errno = Lstat(path.Join(tmpDir, "sub/cat"))
+	require.EqualErrno(t, syscall.ENOENT, errno)
 
 	var st Stat_t
 	t.Run("dir", func(t *testing.T) {
-		st, err = Lstat(tmpDir)
-		require.NoError(t, err)
+		st, errno = Lstat(tmpDir)
+		require.Zero(t, errno)
+
 		require.True(t, st.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -33,8 +34,9 @@ func TestLstat(t *testing.T) {
 
 	t.Run("file", func(t *testing.T) {
 		require.NoError(t, os.WriteFile(file, []byte{1, 2}, 0o400))
-		stFile, err = Lstat(file)
-		require.NoError(t, err)
+		stFile, errno = Lstat(file)
+		require.Zero(t, errno)
+
 		require.Zero(t, stFile.Mode.Type())
 		require.Equal(t, int64(2), stFile.Size)
 		require.NotEqual(t, uint64(0), stFile.Ino)
@@ -49,8 +51,9 @@ func TestLstat(t *testing.T) {
 	t.Run("subdir", func(t *testing.T) {
 		require.NoError(t, os.Mkdir(subdir, 0o500))
 
-		stSubdir, err = Lstat(subdir)
-		require.NoError(t, err)
+		stSubdir, errno = Lstat(subdir)
+		require.Zero(t, errno)
+
 		require.True(t, stSubdir.Mode.IsDir())
 		require.NotEqual(t, uint64(0), stSubdir.Ino)
 	})
@@ -61,8 +64,8 @@ func TestLstat(t *testing.T) {
 
 	t.Run("link to dir link", func(t *testing.T) {
 		pathLink := subdir + "-link"
-		stLink, err := Lstat(pathLink)
-		require.NoError(t, err)
+		stLink, errno := Lstat(pathLink)
+		require.Zero(t, errno)
 
 		requireLinkStat(t, pathLink, stLink)
 	})
@@ -72,8 +75,8 @@ func requireLinkStat(t *testing.T, path string, stat Stat_t) {
 	link := path + "-link"
 	require.NoError(t, os.Symlink(path, link))
 
-	stLink, err := Lstat(link)
-	require.NoError(t, err)
+	stLink, errno := Lstat(link)
+	require.Zero(t, errno)
 
 	require.NotEqual(t, uint64(0), stLink.Ino)
 	require.NotEqual(t, stat.Ino, stLink.Ino) // inodes are not equal
@@ -91,16 +94,17 @@ func requireLinkStat(t *testing.T, path string, stat Stat_t) {
 func TestStat(t *testing.T) {
 	tmpDir := t.TempDir()
 
-	_, err := Stat(path.Join(tmpDir, "cat"))
-	require.EqualErrno(t, syscall.ENOENT, err)
-	_, err = Stat(path.Join(tmpDir, "sub/cat"))
-	require.EqualErrno(t, syscall.ENOENT, err)
+	_, errno := Stat(path.Join(tmpDir, "cat"))
+	require.EqualErrno(t, syscall.ENOENT, errno)
+	_, errno = Stat(path.Join(tmpDir, "sub/cat"))
+	require.EqualErrno(t, syscall.ENOENT, errno)
 
 	var st Stat_t
 
 	t.Run("dir", func(t *testing.T) {
-		st, err = Stat(tmpDir)
-		require.NoError(t, err)
+		st, errno = Stat(tmpDir)
+		require.Zero(t, errno)
+
 		require.True(t, st.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -111,8 +115,9 @@ func TestStat(t *testing.T) {
 	t.Run("file", func(t *testing.T) {
 		require.NoError(t, os.WriteFile(file, nil, 0o400))
 
-		stFile, err = Stat(file)
-		require.NoError(t, err)
+		stFile, errno = Stat(file)
+		require.Zero(t, errno)
+
 		require.False(t, stFile.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -121,8 +126,9 @@ func TestStat(t *testing.T) {
 		link := path.Join(tmpDir, "file-link")
 		require.NoError(t, os.Symlink(file, link))
 
-		stLink, err := Stat(link)
-		require.NoError(t, err)
+		stLink, errno := Stat(link)
+		require.Zero(t, errno)
+
 		require.Equal(t, stFile, stLink) // resolves to the file
 	})
 
@@ -131,8 +137,9 @@ func TestStat(t *testing.T) {
 	t.Run("subdir", func(t *testing.T) {
 		require.NoError(t, os.Mkdir(subdir, 0o500))
 
-		stSubdir, err = Stat(subdir)
-		require.NoError(t, err)
+		stSubdir, errno = Stat(subdir)
+		require.Zero(t, errno)
+
 		require.True(t, stSubdir.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -141,8 +148,9 @@ func TestStat(t *testing.T) {
 		link := path.Join(tmpDir, "dir-link")
 		require.NoError(t, os.Symlink(subdir, link))
 
-		stLink, err := Stat(link)
-		require.NoError(t, err)
+		stLink, errno := Stat(link)
+		require.Zero(t, errno)
+
 		require.Equal(t, stSubdir, stLink) // resolves to the dir
 	})
 }
@@ -152,13 +160,14 @@ func TestStatFile(t *testing.T) {
 
 	var st Stat_t
 
-	tmpDirF, err := OpenFile(tmpDir, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	tmpDirF, errno := OpenFile(tmpDir, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
 	defer tmpDirF.Close()
 
 	t.Run("dir", func(t *testing.T) {
-		st, err = StatFile(tmpDirF)
-		require.NoError(t, err)
+		st, errno = StatFile(tmpDirF)
+		require.Zero(t, errno)
+
 		require.True(t, st.Mode.IsDir())
 		requireDirectoryDevIno(t, st)
 	})
@@ -168,39 +177,41 @@ func TestStatFile(t *testing.T) {
 	if runtime.GOOS != "windows" {
 		t.Run("closed dir", func(t *testing.T) {
 			require.NoError(t, tmpDirF.Close())
-			st, err = StatFile(tmpDirF)
-			require.EqualErrno(t, syscall.EBADF, err)
+			st, errno = StatFile(tmpDirF)
+			require.EqualErrno(t, syscall.EBADF, errno)
 		})
 	}
 
 	file := path.Join(tmpDir, "file")
 	require.NoError(t, os.WriteFile(file, nil, 0o400))
-	fileF, err := OpenFile(file, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	fileF, errno := OpenFile(file, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
 	defer fileF.Close()
 
 	t.Run("file", func(t *testing.T) {
-		st, err = StatFile(fileF)
-		require.NoError(t, err)
+		st, errno = StatFile(fileF)
+		require.Zero(t, errno)
+
 		require.False(t, st.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
 
 	t.Run("closed file", func(t *testing.T) {
 		require.NoError(t, fileF.Close())
-		_, err = StatFile(fileF)
-		require.EqualErrno(t, syscall.EBADF, err)
+		_, errno = StatFile(fileF)
+		require.EqualErrno(t, syscall.EBADF, errno)
 	})
 
 	subdir := path.Join(tmpDir, "sub")
 	require.NoError(t, os.Mkdir(subdir, 0o500))
-	subdirF, err := OpenFile(subdir, syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	subdirF, errno := OpenFile(subdir, syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
 	defer subdirF.Close()
 
 	t.Run("subdir", func(t *testing.T) {
-		st, err = StatFile(subdirF)
-		require.NoError(t, err)
+		st, errno = StatFile(subdirF)
+		require.Zero(t, errno)
+
 		require.True(t, st.Mode.IsDir())
 		requireDirectoryDevIno(t, st)
 	})
@@ -208,8 +219,8 @@ func TestStatFile(t *testing.T) {
 	if runtime.GOOS != "windows" { // windows allows you to stat a closed dir
 		t.Run("closed subdir", func(t *testing.T) {
 			require.NoError(t, subdirF.Close())
-			st, err = StatFile(subdirF)
-			require.EqualErrno(t, syscall.EBADF, err)
+			st, errno = StatFile(subdirF)
+			require.EqualErrno(t, syscall.EBADF, errno)
 		})
 	}
 }
@@ -257,8 +268,9 @@ func Test_StatFile_times(t *testing.T) {
 			require.NoError(t, err)
 			defer f.Close()
 
-			st, err := StatFile(f)
-			require.NoError(t, err)
+			st, errno := StatFile(f)
+			require.Zero(t, errno)
+
 			require.Equal(t, st.Atim, tc.atimeNsec)
 			require.Equal(t, st.Mtim, tc.mtimeNsec)
 		})
@@ -289,19 +301,20 @@ func TestStatFile_dev_inode(t *testing.T) {
 	defer l2.Close()
 
 	// First, stat the directory
-	st1, err := StatFile(d)
-	require.NoError(t, err)
+	st1, errno := StatFile(d)
+	require.Zero(t, errno)
+
 	requireDirectoryDevIno(t, st1)
 
 	// Now, stat the files in it
-	st1, err = StatFile(f1)
-	require.NoError(t, err)
+	st1, errno = StatFile(f1)
+	require.Zero(t, errno)
 
-	st2, err := StatFile(f2)
-	require.NoError(t, err)
+	st2, errno := StatFile(f2)
+	require.Zero(t, errno)
 
-	st3, err := StatFile(l2)
-	require.NoError(t, err)
+	st3, errno := StatFile(l2)
+	require.Zero(t, errno)
 
 	// The files should be on the same device, but different inodes
 	require.Equal(t, st1.Dev, st2.Dev)
@@ -309,8 +322,8 @@ func TestStatFile_dev_inode(t *testing.T) {
 	require.Equal(t, st2, st3) // stat on a link is for its target
 
 	// Redoing stat should result in the same inodes
-	st1Again, err := StatFile(f1)
-	require.NoError(t, err)
+	st1Again, errno := StatFile(f1)
+	require.Zero(t, errno)
 	require.Equal(t, st1.Dev, st1Again.Dev)
 
 	// On Windows, we cannot rename while opening.
@@ -320,13 +333,13 @@ func TestStatFile_dev_inode(t *testing.T) {
 	require.NoError(t, l2.Close())
 
 	// Renaming a file shouldn't change its inodes.
-	require.NoError(t, Rename(path1, path2))
+	require.Zero(t, Rename(path1, path2))
 	f1, err = os.Open(path2)
 	require.NoError(t, err)
 	defer f1.Close()
 
-	st1Again, err = StatFile(f1)
-	require.NoError(t, err)
+	st1Again, errno = StatFile(f1)
+	require.Zero(t, errno)
 	require.Equal(t, st1.Dev, st1Again.Dev)
 	require.Equal(t, st1.Ino, st1Again.Ino)
 }
@@ -359,10 +372,11 @@ func TestStat_uid_gid(t *testing.T) {
 		tmpDir := t.TempDir()
 		dir := path.Join(tmpDir, "dir")
 		require.NoError(t, os.Mkdir(dir, 0o0700))
-		require.NoError(t, chgid(dir, gid))
+		require.Zero(t, chgid(dir, gid))
+
+		st, errno := Stat(dir)
+		require.Zero(t, errno)
 
-		st, err := Stat(dir)
-		require.NoError(t, err)
 		require.Equal(t, uid, st.Uid)
 		require.Equal(t, gid, st.Gid)
 	})
@@ -371,10 +385,11 @@ func TestStat_uid_gid(t *testing.T) {
 		tmpDir := t.TempDir()
 		link := path.Join(tmpDir, "link")
 		require.NoError(t, os.Symlink(tmpDir, link))
-		require.NoError(t, chgid(link, gid))
+		require.Zero(t, chgid(link, gid))
+
+		st, errno := Lstat(link)
+		require.Zero(t, errno)
 
-		st, err := Lstat(link)
-		require.NoError(t, err)
 		require.Equal(t, uid, st.Uid)
 		require.Equal(t, gid, st.Gid)
 	})
@@ -383,10 +398,11 @@ func TestStat_uid_gid(t *testing.T) {
 		tmpDir := t.TempDir()
 		file := path.Join(tmpDir, "file")
 		require.NoError(t, os.WriteFile(file, nil, 0o0600))
-		require.NoError(t, chgid(file, gid))
+		require.Zero(t, chgid(file, gid))
+
+		st, errno := Lstat(file)
+		require.Zero(t, errno)
 
-		st, err := Lstat(file)
-		require.NoError(t, err)
 		require.Equal(t, uid, st.Uid)
 		require.Equal(t, gid, st.Gid)
 	})
diff --git a/internal/platform/sync_test.go b/internal/platform/sync_test.go
index f50380b7..ac2afb00 100644
--- a/internal/platform/sync_test.go
+++ b/internal/platform/sync_test.go
@@ -14,30 +14,30 @@ import (
 // sync anyway. There is no test in Go for syscall.Fdatasync, but closest is
 // similar to below. Effectively, this only tests that things don't error.
 func Test_Fdatasync(t *testing.T) {
-	f, err := os.CreateTemp("", t.Name())
-	require.NoError(t, err)
+	f, errno := os.CreateTemp("", t.Name())
+	require.NoError(t, errno)
 	defer f.Close()
 
 	expected := "hello world!"
 
 	// Write the expected data
-	_, err = f.Write([]byte(expected))
-	require.NoError(t, err)
+	_, errno = f.Write([]byte(expected))
+	require.NoError(t, errno)
 
 	// Sync the data.
-	if err = Fdatasync(f); err == syscall.ENOSYS {
+	if errno = Fdatasync(f); errno == syscall.ENOSYS {
 		return // don't continue if it isn't supported.
 	}
-	require.NoError(t, err)
+	require.Zero(t, errno)
 
 	// Rewind while the file is still open.
-	_, err = f.Seek(0, io.SeekStart)
+	_, err := f.Seek(0, io.SeekStart)
 	require.NoError(t, err)
 
 	// Read data from the file
 	var buf bytes.Buffer
-	_, err = io.Copy(&buf, f)
-	require.NoError(t, err)
+	_, errno = io.Copy(&buf, f)
+	require.NoError(t, errno)
 
 	// It may be the case that sync worked.
 	require.Equal(t, expected, buf.String())
diff --git a/internal/platform/unlink_test.go b/internal/platform/unlink_test.go
index 3e9d8d29..e74bca15 100644
--- a/internal/platform/unlink_test.go
+++ b/internal/platform/unlink_test.go
@@ -12,8 +12,8 @@ import (
 func TestUnlink(t *testing.T) {
 	t.Run("doesn't exist", func(t *testing.T) {
 		name := "non-existent"
-		err := Unlink(name)
-		require.EqualErrno(t, syscall.ENOENT, err)
+		errno := Unlink(name)
+		require.EqualErrno(t, syscall.ENOENT, errno)
 	})
 
 	t.Run("target: dir", func(t *testing.T) {
@@ -22,8 +22,8 @@ func TestUnlink(t *testing.T) {
 		dir := path.Join(tmpDir, "dir")
 		require.NoError(t, os.Mkdir(dir, 0o700))
 
-		err := Unlink(dir)
-		require.EqualErrno(t, syscall.EISDIR, err)
+		errno := Unlink(dir)
+		require.EqualErrno(t, syscall.EISDIR, errno)
 
 		require.NoError(t, os.Remove(dir))
 	})
@@ -40,8 +40,8 @@ func TestUnlink(t *testing.T) {
 		require.NoError(t, os.Symlink("subdir", symlinkName))
 
 		// Unlinking the symlink should suceed.
-		err := Unlink(symlinkName)
-		require.NoError(t, err)
+		errno := Unlink(symlinkName)
+		require.Zero(t, errno)
 	})
 
 	t.Run("file exists", func(t *testing.T) {
@@ -51,7 +51,7 @@ func TestUnlink(t *testing.T) {
 
 		require.NoError(t, os.WriteFile(name, []byte{}, 0o600))
 
-		require.NoError(t, Unlink(name))
+		require.Zero(t, Unlink(name))
 		_, err := os.Stat(name)
 		require.Error(t, err)
 	})
diff --git a/internal/sys/fs_test.go b/internal/sys/fs_test.go
index 8f22ea9f..20f5d449 100644
--- a/internal/sys/fs_test.go
+++ b/internal/sys/fs_test.go
@@ -76,8 +76,8 @@ func TestNewFSContext(t *testing.T) {
 
 			// Verify that each call to OpenFile returns a different file
 			// descriptor.
-			f1, err := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0)
-			require.NoError(t, err)
+			f1, errno := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0)
+			require.Zero(t, errno)
 			require.NotEqual(t, FdPreopen, f1)
 
 			// Verify that file descriptors are reused.
@@ -88,9 +88,9 @@ func TestNewFSContext(t *testing.T) {
 			// test to ensure that our implementation properly reuses descriptor
 			// numbers but if we were to change the reuse strategy, this test
 			// would likely break and need to be updated.
-			require.NoError(t, fsc.CloseFile(f1))
-			f2, err := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0)
-			require.NoError(t, err)
+			require.Zero(t, fsc.CloseFile(f1))
+			f2, errno := fsc.OpenFile(preopenedDir.FS, preopenedDir.Name, 0, 0)
+			require.Zero(t, errno)
 			require.Equal(t, f1, f2)
 		})
 	}
@@ -105,14 +105,14 @@ func TestFSContext_CloseFile(t *testing.T) {
 	require.NoError(t, err)
 	defer fsc.Close(testCtx)
 
-	fdToClose, err := fsc.OpenFile(testFS, "empty.txt", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fdToClose, errno := fsc.OpenFile(testFS, "empty.txt", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
-	fdToKeep, err := fsc.OpenFile(testFS, "test.txt", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	fdToKeep, errno := fsc.OpenFile(testFS, "test.txt", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	// Close
-	require.NoError(t, fsc.CloseFile(fdToClose))
+	require.Zero(t, fsc.CloseFile(fdToClose))
 
 	// Verify fdToClose is closed and removed from the opened FDs.
 	_, ok := fsc.LookupFile(fdToClose)
@@ -123,10 +123,10 @@ func TestFSContext_CloseFile(t *testing.T) {
 	require.True(t, ok)
 
 	t.Run("EBADF for an invalid FD", func(t *testing.T) {
-		require.Equal(t, syscall.EBADF, fsc.CloseFile(42)) // 42 is an arbitrary invalid FD
+		require.EqualErrno(t, syscall.EBADF, fsc.CloseFile(42)) // 42 is an arbitrary invalid FD
 	})
 	t.Run("Can close a pre-open", func(t *testing.T) {
-		require.NoError(t, fsc.CloseFile(FdPreopen))
+		require.Zero(t, fsc.CloseFile(FdPreopen))
 	})
 }
 
@@ -187,8 +187,8 @@ func TestContext_Close(t *testing.T) {
 	// Verify base case
 	require.Equal(t, 1+FdPreopen, uint32(fsc.openedFiles.Len()))
 
-	_, err = fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	_, errno := fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 	require.Equal(t, 2+FdPreopen, uint32(fsc.openedFiles.Len()))
 
 	// Closing should not err.
@@ -210,8 +210,8 @@ func TestContext_Close_Error(t *testing.T) {
 	require.NoError(t, err)
 
 	// open another file
-	_, err = fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	_, errno := fsc.OpenFile(testFS, "foo", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 
 	require.EqualError(t, fsc.Close(testCtx), "error closing")
 
@@ -224,8 +224,8 @@ func TestFSContext_ReOpenDir(t *testing.T) {
 	dirFs := sysfs.NewDirFS(tmpDir)
 
 	const dirName = "dir"
-	err := dirFs.Mkdir(dirName, 0o700)
-	require.NoError(t, err)
+	errno := dirFs.Mkdir(dirName, 0o700)
+	require.Zero(t, errno)
 
 	fsc, err := NewFSContext(nil, nil, nil, dirFs)
 	require.NoError(t, err)
@@ -234,8 +234,8 @@ func TestFSContext_ReOpenDir(t *testing.T) {
 	}()
 
 	t.Run("ok", func(t *testing.T) {
-		dirFd, err := fsc.OpenFile(dirFs, dirName, os.O_RDONLY, 0o600)
-		require.NoError(t, err)
+		dirFd, errno := fsc.OpenFile(dirFs, dirName, os.O_RDONLY, 0o600)
+		require.Zero(t, errno)
 
 		ent, ok := fsc.LookupFile(dirFd)
 		require.True(t, ok)
@@ -244,24 +244,25 @@ func TestFSContext_ReOpenDir(t *testing.T) {
 		ent.ReadDir = &ReadDir{Dirents: make([]*platform.Dirent, 10), CountRead: 12345}
 
 		// Then reopen the same file descriptor.
-		ent, err = fsc.ReOpenDir(dirFd)
-		require.NoError(t, err)
+		ent, errno = fsc.ReOpenDir(dirFd)
+		require.Zero(t, errno)
 
 		// Verify the read dir state has been reset.
 		require.Equal(t, &ReadDir{}, ent.ReadDir)
 	})
 
 	t.Run("non existing ", func(t *testing.T) {
-		_, err = fsc.ReOpenDir(12345)
-		require.ErrorIs(t, err, syscall.EBADF)
+		_, errno = fsc.ReOpenDir(12345)
+		require.EqualErrno(t, syscall.EBADF, errno)
 	})
 
 	t.Run("not dir", func(t *testing.T) {
 		const fileName = "dog"
-		fd, err := fsc.OpenFile(dirFs, fileName, os.O_CREATE, 0o600)
-		require.NoError(t, err)
-		_, err = fsc.ReOpenDir(fd)
-		require.ErrorIs(t, err, syscall.EISDIR)
+		fd, errno := fsc.OpenFile(dirFs, fileName, os.O_CREATE, 0o600)
+		require.Zero(t, errno)
+
+		_, errno = fsc.ReOpenDir(fd)
+		require.EqualErrno(t, syscall.EISDIR, errno)
 	})
 }
 
@@ -270,8 +271,8 @@ func TestFSContext_Renumber(t *testing.T) {
 	dirFs := sysfs.NewDirFS(tmpDir)
 
 	const dirName = "dir"
-	err := dirFs.Mkdir(dirName, 0o700)
-	require.NoError(t, err)
+	errno := dirFs.Mkdir(dirName, 0o700)
+	require.Zero(t, errno)
 
 	c, err := NewFSContext(nil, nil, nil, dirFs)
 	require.NoError(t, err)
@@ -280,13 +281,13 @@ func TestFSContext_Renumber(t *testing.T) {
 	}()
 
 	for _, toFd := range []uint32{10, 100, 100} {
-		fromFd, err := c.OpenFile(dirFs, dirName, os.O_RDONLY, 0)
-		require.NoError(t, err)
+		fromFd, errno := c.OpenFile(dirFs, dirName, os.O_RDONLY, 0)
+		require.Zero(t, errno)
 
 		prevDirFile, ok := c.LookupFile(fromFd)
 		require.True(t, ok)
 
-		require.Equal(t, nil, c.Renumber(fromFd, toFd))
+		require.Zero(t, c.Renumber(fromFd, toFd))
 
 		renumberedDirFile, ok := c.LookupFile(toFd)
 		require.True(t, ok)
@@ -322,27 +323,28 @@ func TestFSContext_ChangeOpenFlag(t *testing.T) {
 	const fileName = "dir"
 	require.NoError(t, os.WriteFile(path.Join(tmpDir, fileName), []byte("0123456789"), 0o600))
 
-	c, err := NewFSContext(nil, nil, nil, dirFs)
-	require.NoError(t, err)
+	c, errno := NewFSContext(nil, nil, nil, dirFs)
+	require.NoError(t, errno)
 	defer func() {
 		require.NoError(t, c.Close(context.Background()))
 	}()
 
 	// Without APPEND.
-	fd, err := c.OpenFile(dirFs, fileName, os.O_RDWR, 0o600)
-	require.NoError(t, err)
+	fd, errno := c.OpenFile(dirFs, fileName, os.O_RDWR, 0o600)
+	require.Zero(t, errno)
+
 	f0, ok := c.openedFiles.Lookup(fd)
 	require.True(t, ok)
 	require.Equal(t, f0.openFlag&syscall.O_APPEND, 0)
 
 	// Set the APPEND flag.
-	require.NoError(t, c.ChangeOpenFlag(fd, syscall.O_APPEND))
+	require.Zero(t, c.ChangeOpenFlag(fd, syscall.O_APPEND))
 	f1, ok := c.openedFiles.Lookup(fd)
 	require.True(t, ok)
 	require.Equal(t, f1.openFlag&syscall.O_APPEND, syscall.O_APPEND)
 
 	// Remove the APPEND flag.
-	require.NoError(t, c.ChangeOpenFlag(fd, 0))
+	require.Zero(t, c.ChangeOpenFlag(fd, 0))
 	f2, ok := c.openedFiles.Lookup(fd)
 	require.True(t, ok)
 	require.Equal(t, f2.openFlag&syscall.O_APPEND, 0)
diff --git a/internal/sys/sys_test.go b/internal/sys/sys_test.go
index b2dbab68..b8c4903e 100644
--- a/internal/sys/sys_test.go
+++ b/internal/sys/sys_test.go
@@ -88,8 +88,8 @@ func TestFileEntry_cachedStat(t *testing.T) {
 	dirFS := sysfs.NewDirFS(tmpDir)
 
 	// get the expected inode
-	st, err := platform.Stat(tmpDir)
-	require.NoError(t, err)
+	st, errno := platform.Stat(tmpDir)
+	require.Zero(t, errno)
 
 	tests := []struct {
 		name        string
diff --git a/internal/sysfs/adapter_test.go b/internal/sysfs/adapter_test.go
index 75aa7c7e..56384165 100644
--- a/internal/sysfs/adapter_test.go
+++ b/internal/sysfs/adapter_test.go
@@ -125,8 +125,9 @@ func TestAdapt_Lstat(t *testing.T) {
 		fullPath := joinPath(tmpDir, path)
 		linkPath := joinPath(tmpDir, path+"-link")
 		require.NoError(t, os.Symlink(fullPath, linkPath))
-		_, err := testFS.Lstat(filepath.Base(linkPath))
-		require.NoError(t, err)
+
+		_, errno := testFS.Lstat(filepath.Base(linkPath))
+		require.Zero(t, errno)
 	}
 }
 
diff --git a/internal/sysfs/dirfs_test.go b/internal/sysfs/dirfs_test.go
index 8da4fcb5..76cd41e2 100644
--- a/internal/sysfs/dirfs_test.go
+++ b/internal/sysfs/dirfs_test.go
@@ -19,22 +19,22 @@ func TestNewDirFS(t *testing.T) {
 	testFS := NewDirFS(".")
 
 	// Guest can look up /
-	f, err := testFS.OpenFile("/", os.O_RDONLY, 0)
-	require.NoError(t, err)
+	f, errno := testFS.OpenFile("/", os.O_RDONLY, 0)
+	require.Zero(t, errno)
 	require.NoError(t, f.Close())
 
 	t.Run("host path not found", func(t *testing.T) {
 		testFS := NewDirFS("a")
-		_, err = testFS.OpenFile(".", os.O_RDONLY, 0)
-		require.EqualErrno(t, syscall.ENOENT, err)
+		_, errno = testFS.OpenFile(".", os.O_RDONLY, 0)
+		require.EqualErrno(t, syscall.ENOENT, errno)
 	})
 	t.Run("host path not a directory", func(t *testing.T) {
 		arg0 := os.Args[0] // should be safe in scratch tests which don't have the source mounted.
 
 		testFS := NewDirFS(arg0)
-		d, err := testFS.OpenFile(".", os.O_RDONLY, 0)
-		require.NoError(t, err)
-		_, err = d.(fs.ReadDirFile).ReadDir(-1)
+		d, errno := testFS.OpenFile(".", os.O_RDONLY, 0)
+		require.Zero(t, errno)
+		_, err := d.(fs.ReadDirFile).ReadDir(-1)
 		require.EqualErrno(t, syscall.ENOTDIR, platform.UnwrapOSError(err))
 	})
 }
@@ -66,7 +66,7 @@ func TestDirFS_Lstat(t *testing.T) {
 
 	testFS := NewDirFS(tmpDir)
 	for _, path := range []string{"animals.txt", "sub", "sub-link"} {
-		require.NoError(t, testFS.Symlink(path, path+"-link"))
+		require.Zero(t, testFS.Symlink(path, path+"-link"))
 	}
 
 	testLstat(t, testFS)
@@ -80,9 +80,11 @@ func TestDirFS_MkDir(t *testing.T) {
 	realPath := path.Join(tmpDir, name)
 
 	t.Run("doesn't exist", func(t *testing.T) {
-		require.NoError(t, testFS.Mkdir(name, fs.ModeDir))
+		require.Zero(t, testFS.Mkdir(name, fs.ModeDir))
+
 		stat, err := os.Stat(realPath)
 		require.NoError(t, err)
+
 		require.Equal(t, name, stat.Name())
 		require.True(t, stat.IsDir())
 	})
@@ -129,19 +131,20 @@ func testChmod(t *testing.T, testFS FS, path string) {
 	requireMode(t, testFS, path, 0o444)
 
 	// Test adding write, using 0o666 not 0o600 for read-back on windows.
-	require.NoError(t, testFS.Chmod(path, 0o666))
+	require.Zero(t, testFS.Chmod(path, 0o666))
 	requireMode(t, testFS, path, 0o666)
 
 	if runtime.GOOS != "windows" {
 		// Test clearing group and world, setting owner read+execute.
-		require.NoError(t, testFS.Chmod(path, 0o500))
+		require.Zero(t, testFS.Chmod(path, 0o500))
 		requireMode(t, testFS, path, 0o500)
 	}
 }
 
 func requireMode(t *testing.T, testFS FS, path string, mode fs.FileMode) {
-	st, err := testFS.Stat(path)
-	require.NoError(t, err)
+	st, errno := testFS.Stat(path)
+	require.Zero(t, errno)
+
 	require.Equal(t, mode, st.Mode.Perm())
 }
 
@@ -165,17 +168,17 @@ func TestDirFS_Rename(t *testing.T) {
 		file1 := "file1"
 		file1Path := path.Join(tmpDir, file1)
 		file1Contents := []byte{1}
-		err := os.WriteFile(file1Path, file1Contents, 0o600)
-		require.NoError(t, err)
+		errno := os.WriteFile(file1Path, file1Contents, 0o600)
+		require.NoError(t, errno)
 
 		file2 := "file2"
 		file2Path := path.Join(tmpDir, file2)
-		err = testFS.Rename(file1, file2)
-		require.NoError(t, err)
+		errno = testFS.Rename(file1, file2)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
-		_, err = os.Stat(file1Path)
-		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
+		_, errno = os.Stat(file1Path)
+		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(errno))
 
 		s, err := os.Stat(file2Path)
 		require.NoError(t, err)
@@ -191,11 +194,11 @@ func TestDirFS_Rename(t *testing.T) {
 
 		dir2 := "dir2"
 		dir2Path := path.Join(tmpDir, dir2)
-		err := testFS.Rename(dir1, dir2)
-		require.NoError(t, err)
+		errrno := testFS.Rename(dir1, dir2)
+		require.Zero(t, errrno)
 
 		// Show the prior path no longer exists
-		_, err = os.Stat(dir1Path)
+		_, err := os.Stat(dir1Path)
 		require.EqualErrno(t, syscall.ENOENT, platform.UnwrapOSError(err))
 
 		s, err := os.Stat(dir2Path)
@@ -218,8 +221,8 @@ func TestDirFS_Rename(t *testing.T) {
 		require.NoError(t, err)
 		require.NoError(t, f.Close())
 
-		err = testFS.Rename(dir1, dir2)
-		require.EqualErrno(t, syscall.ENOTDIR, err)
+		errno := testFS.Rename(dir1, dir2)
+		require.EqualErrno(t, syscall.ENOTDIR, errno)
 	})
 	t.Run("file to dir", func(t *testing.T) {
 		tmpDir := t.TempDir()
@@ -235,8 +238,8 @@ func TestDirFS_Rename(t *testing.T) {
 		dir1Path := path.Join(tmpDir, dir1)
 		require.NoError(t, os.Mkdir(dir1Path, 0o700))
 
-		err = testFS.Rename(file1, dir1)
-		require.EqualErrno(t, syscall.EISDIR, err)
+		errno := testFS.Rename(file1, dir1)
+		require.EqualErrno(t, syscall.EISDIR, errno)
 	})
 
 	// Similar to https://github.com/ziglang/zig/blob/0.10.1/lib/std/fs/test.zig#L567-L582
@@ -259,8 +262,8 @@ func TestDirFS_Rename(t *testing.T) {
 		dir2Path := path.Join(tmpDir, dir2)
 		require.NoError(t, os.Mkdir(dir2Path, 0o700))
 
-		err = testFS.Rename(dir1, dir2)
-		require.NoError(t, err)
+		errno := testFS.Rename(dir1, dir2)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
 		_, err = os.Stat(dir1Path)
@@ -296,8 +299,8 @@ func TestDirFS_Rename(t *testing.T) {
 		err = os.WriteFile(path.Join(dir2Path, "existing.txt"), []byte("any thing"), 0o600)
 		require.NoError(t, err)
 
-		err = testFS.Rename(dir1, dir2)
-		require.EqualErrno(t, syscall.ENOTEMPTY, err)
+		errno := testFS.Rename(dir1, dir2)
+		require.EqualErrno(t, syscall.ENOTEMPTY, errno)
 	})
 
 	t.Run("file to file", func(t *testing.T) {
@@ -316,8 +319,8 @@ func TestDirFS_Rename(t *testing.T) {
 		err = os.WriteFile(file2Path, file2Contents, 0o600)
 		require.NoError(t, err)
 
-		err = testFS.Rename(file1, file2)
-		require.NoError(t, err)
+		errno := testFS.Rename(file1, file2)
+		require.Zero(t, errno)
 
 		// Show the prior path no longer exists
 		_, err = os.Stat(file1Path)
@@ -336,8 +339,8 @@ func TestDirFS_Rename(t *testing.T) {
 		dir1Path := path.Join(tmpDir, dir1)
 		require.NoError(t, os.Mkdir(dir1Path, 0o700))
 
-		err := testFS.Rename(dir1, dir1)
-		require.NoError(t, err)
+		errno := testFS.Rename(dir1, dir1)
+		require.Zero(t, errno)
 
 		s, err := os.Stat(dir1Path)
 		require.NoError(t, err)
@@ -353,8 +356,8 @@ func TestDirFS_Rename(t *testing.T) {
 		err := os.WriteFile(file1Path, file1Contents, 0o600)
 		require.NoError(t, err)
 
-		err = testFS.Rename(file1, file1)
-		require.NoError(t, err)
+		errno := testFS.Rename(file1, file1)
+		require.Zero(t, errno)
 
 		b, err := os.ReadFile(file1Path)
 		require.NoError(t, err)
@@ -404,8 +407,8 @@ func TestDirFS_Rmdir(t *testing.T) {
 		require.NoError(t, os.Remove(fileInDir))
 
 		// After deletion, try removing directory.
-		err := testFS.Rmdir(name)
-		require.NoError(t, err)
+		errno := testFS.Rmdir(name)
+		require.Zero(t, errno)
 	})
 
 	t.Run("dir empty", func(t *testing.T) {
@@ -415,7 +418,7 @@ func TestDirFS_Rmdir(t *testing.T) {
 		name := "rmdir"
 		realPath := path.Join(tmpDir, name)
 		require.NoError(t, os.Mkdir(realPath, 0o700))
-		require.NoError(t, testFS.Rmdir(name))
+		require.Zero(t, testFS.Rmdir(name))
 		_, err := os.Stat(realPath)
 		require.Error(t, err)
 	})
@@ -428,14 +431,14 @@ func TestDirFS_Rmdir(t *testing.T) {
 		realPath := path.Join(tmpDir, name)
 		require.NoError(t, os.Mkdir(realPath, 0o700))
 
-		f, err := testFS.OpenFile(name, platform.O_DIRECTORY, 0o700)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile(name, platform.O_DIRECTORY, 0o700)
+		require.Zero(t, errno)
 		defer func() {
 			require.NoError(t, f.Close())
 		}()
 
-		require.NoError(t, testFS.Rmdir(name))
-		_, err = os.Stat(realPath)
+		require.Zero(t, testFS.Rmdir(name))
+		_, err := os.Stat(realPath)
 		require.Error(t, err)
 	})
 
@@ -491,11 +494,11 @@ func TestDirFS_Unlink(t *testing.T) {
 
 		// Create a symlink to the subdirectory.
 		const symlinkName = "symlink-to-dir"
-		require.NoError(t, testFS.Symlink("subdir", symlinkName))
+		require.Zero(t, testFS.Symlink("subdir", symlinkName))
 
 		// Unlinking the symlink should suceed.
 		err := testFS.Unlink(symlinkName)
-		require.NoError(t, err)
+		require.Zero(t, err)
 	})
 
 	t.Run("file exists", func(t *testing.T) {
@@ -507,7 +510,8 @@ func TestDirFS_Unlink(t *testing.T) {
 
 		require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
 
-		require.NoError(t, testFS.Unlink(name))
+		require.Zero(t, testFS.Unlink(name))
+
 		_, err := os.Stat(realPath)
 		require.Error(t, err)
 	})
@@ -610,15 +614,15 @@ func TestDirFS_Utimesns(t *testing.T) {
 				testFS := NewDirFS(tmpDir)
 
 				file := path.Join(tmpDir, "file")
-				err := os.WriteFile(file, []byte{}, 0o700)
-				require.NoError(t, err)
+				errno := os.WriteFile(file, []byte{}, 0o700)
+				require.NoError(t, errno)
 
 				link := file + "-link"
 				require.NoError(t, os.Symlink(file, link))
 
 				dir := path.Join(tmpDir, "dir")
-				err = os.Mkdir(dir, 0o700)
-				require.NoError(t, err)
+				errno = os.Mkdir(dir, 0o700)
+				require.NoError(t, errno)
 
 				var path, statPath string
 				switch fileType {
@@ -638,18 +642,18 @@ func TestDirFS_Utimesns(t *testing.T) {
 					panic(tc)
 				}
 
-				oldSt, err := testFS.Lstat(statPath)
-				require.NoError(t, err)
+				oldSt, errno := testFS.Lstat(statPath)
+				require.Zero(t, errno)
 
-				err = testFS.Utimens(path, tc.times, !symlinkNoFollow)
+				errno = testFS.Utimens(path, tc.times, !symlinkNoFollow)
 				if symlinkNoFollow && !platform.SupportsSymlinkNoFollow {
-					require.EqualErrno(t, syscall.ENOSYS, err)
+					require.EqualErrno(t, syscall.ENOSYS, errno)
 					return
 				}
-				require.NoError(t, err)
+				require.Zero(t, errno)
 
-				newSt, err := testFS.Lstat(statPath)
-				require.NoError(t, err)
+				newSt, errno := testFS.Lstat(statPath)
+				require.Zero(t, errno)
 
 				if platform.CompilerSupported() {
 					if tc.times != nil && tc.times[0].Nsec == platform.UTIME_OMIT {
@@ -711,8 +715,8 @@ func TestDirFS_Stat(t *testing.T) {
 			name := `e:xperi\ment.txt`
 			require.NoError(t, os.WriteFile(path.Join(tmpDir, name), nil, 0o600))
 
-			_, err := testFS.Stat(name)
-			require.NoError(t, err)
+			_, errno := testFS.Stat(name)
+			require.Zero(t, errno)
 		})
 	}
 }
@@ -758,8 +762,8 @@ func TestDirFS_Truncate(t *testing.T) {
 			realPath := path.Join(tmpDir, name)
 			require.NoError(t, os.WriteFile(realPath, content, 0o0600))
 
-			err := testFS.Truncate(name, tc.size)
-			require.NoError(t, err)
+			errno := testFS.Truncate(name, tc.size)
+			require.Zero(t, errno)
 
 			actual, err := os.ReadFile(realPath)
 			require.NoError(t, err)
@@ -821,12 +825,12 @@ func Test_fdReaddir_opened_file_written(t *testing.T) {
 	testFS := NewDirFS(root)
 
 	const readDirTarget = "dir"
-	err := testFS.Mkdir(readDirTarget, 0o700)
-	require.NoError(t, err)
+	errno := testFS.Mkdir(readDirTarget, 0o700)
+	require.Zero(t, errno)
 
 	// Open the directory, before writing files!
-	dirFile, err := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0)
-	require.NoError(t, err)
+	dirFile, errno := testFS.OpenFile(readDirTarget, os.O_RDONLY, 0)
+	require.Zero(t, errno)
 	defer dirFile.Close()
 
 	// Then write a file to the directory.
@@ -853,12 +857,12 @@ func TestDirFS_Link(t *testing.T) {
 
 	testFS := NewDirFS(tmpDir)
 
-	require.ErrorIs(t, testFS.Link("cat", ""), syscall.ENOENT)
-	require.ErrorIs(t, testFS.Link("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
-	require.ErrorIs(t, testFS.Link("sub/test.txt", "."), syscall.EEXIST)
-	require.ErrorIs(t, testFS.Link("sub/test.txt", ""), syscall.EEXIST)
-	require.ErrorIs(t, testFS.Link("sub/test.txt", "/"), syscall.EEXIST)
-	require.NoError(t, testFS.Link("sub/test.txt", "foo"))
+	require.EqualErrno(t, testFS.Link("cat", ""), syscall.ENOENT)
+	require.EqualErrno(t, testFS.Link("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
+	require.EqualErrno(t, testFS.Link("sub/test.txt", "."), syscall.EEXIST)
+	require.EqualErrno(t, testFS.Link("sub/test.txt", ""), syscall.EEXIST)
+	require.EqualErrno(t, testFS.Link("sub/test.txt", "/"), syscall.EEXIST)
+	require.Zero(t, testFS.Link("sub/test.txt", "foo"))
 }
 
 func TestDirFS_Symlink(t *testing.T) {
@@ -870,10 +874,10 @@ func TestDirFS_Symlink(t *testing.T) {
 
 	testFS := NewDirFS(tmpDir)
 
-	require.ErrorIs(t, testFS.Symlink("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
+	require.EqualErrno(t, testFS.Symlink("sub/test.txt", "sub/test.txt"), syscall.EEXIST)
 	// Non-existing old name is allowed.
-	require.NoError(t, testFS.Symlink("non-existing", "aa"))
-	require.NoError(t, testFS.Symlink("sub/", "symlinked-subdir"))
+	require.Zero(t, testFS.Symlink("non-existing", "aa"))
+	require.Zero(t, testFS.Symlink("sub/", "symlinked-subdir"))
 
 	st, err := os.Lstat(path.Join(tmpDir, "aa"))
 	require.NoError(t, err)
diff --git a/internal/sysfs/dirfs_unix_test.go b/internal/sysfs/dirfs_unix_test.go
index 2cc7ef97..a3bbea38 100644
--- a/internal/sysfs/dirfs_unix_test.go
+++ b/internal/sysfs/dirfs_unix_test.go
@@ -17,11 +17,13 @@ func TestDirFS_Chown(t *testing.T) {
 	tmpDir := t.TempDir()
 	testFS := NewDirFS(tmpDir)
 
-	require.NoError(t, testFS.Mkdir("dir", 0o0777))
-	dirF, err := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	require.Zero(t, testFS.Mkdir("dir", 0o0777))
+	dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	dirStat, err := dirF.Stat()
 	require.NoError(t, err)
+
 	dirSys := dirStat.Sys().(*syscall.Stat_t)
 
 	// Similar to TestChown in os_unix_test.go, we can't expect to change
@@ -31,12 +33,12 @@ func TestDirFS_Chown(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.NoError(t, testFS.Chown("dir", -1, -1))
+		require.Zero(t, testFS.Chown("dir", -1, -1))
 		checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.NoError(t, testFS.Chown("dir", -1, gid))
+		require.Zero(t, testFS.Chown("dir", -1, gid))
 		checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid))
 	})
 
@@ -45,11 +47,11 @@ func TestDirFS_Chown(t *testing.T) {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
 			// Test using our Chown
-			require.NoError(t, testFS.Chown("dir", -1, g))
+			require.Zero(t, testFS.Chown("dir", -1, g))
 			checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(g))
 
 			// Revert back with platform.ChownFile
-			require.NoError(t, platform.ChownFile(dirF, -1, gid))
+			require.Zero(t, platform.ChownFile(dirF, -1, gid))
 			checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, uint32(gid))
 		})
 	}
@@ -63,18 +65,22 @@ func TestDirFS_Lchown(t *testing.T) {
 	tmpDir := t.TempDir()
 	testFS := NewDirFS(tmpDir)
 
-	require.NoError(t, testFS.Mkdir("dir", 0o0777))
-	dirF, err := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	require.Zero(t, testFS.Mkdir("dir", 0o0777))
+	dirF, errno := testFS.OpenFile("dir", syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	dirStat, err := dirF.Stat()
 	require.NoError(t, err)
+
 	dirSys := dirStat.Sys().(*syscall.Stat_t)
 
-	require.NoError(t, testFS.Symlink("dir", "link"))
-	linkF, err := testFS.OpenFile("link", syscall.O_RDONLY, 0)
-	require.NoError(t, err)
+	require.Zero(t, testFS.Symlink("dir", "link"))
+	linkF, errno := testFS.OpenFile("link", syscall.O_RDONLY, 0)
+	require.Zero(t, errno)
+
 	linkStat, err := linkF.Stat()
 	require.NoError(t, err)
+
 	linkSys := linkStat.Sys().(*syscall.Stat_t)
 
 	// Similar to TestLchown in os_unix_test.go, we can't expect to change
@@ -84,12 +90,12 @@ func TestDirFS_Lchown(t *testing.T) {
 	require.NoError(t, err)
 
 	t.Run("-1 parameters means leave alone", func(t *testing.T) {
-		require.NoError(t, testFS.Lchown("link", -1, -1))
+		require.Zero(t, testFS.Lchown("link", -1, -1))
 		checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, linkSys.Gid)
 	})
 
 	t.Run("change gid, but not uid", func(t *testing.T) {
-		require.NoError(t, testFS.Chown("dir", -1, gid))
+		require.Zero(t, testFS.Chown("dir", -1, gid))
 		checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(gid))
 		// Make sure the target didn't change.
 		checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
@@ -100,7 +106,7 @@ func TestDirFS_Lchown(t *testing.T) {
 		g := g
 		t.Run(fmt.Sprintf("change to gid %d", g), func(t *testing.T) {
 			// Test using our Lchown
-			require.NoError(t, testFS.Lchown("link", -1, g))
+			require.Zero(t, testFS.Lchown("link", -1, g))
 			checkUidGid(t, path.Join(tmpDir, "link"), linkSys.Uid, uint32(g))
 			// Make sure the target didn't change.
 			checkUidGid(t, path.Join(tmpDir, "dir"), dirSys.Uid, dirSys.Gid)
diff --git a/internal/sysfs/readfs_test.go b/internal/sysfs/readfs_test.go
index 5c9b5b68..e053f335 100644
--- a/internal/sysfs/readfs_test.go
+++ b/internal/sysfs/readfs_test.go
@@ -41,7 +41,7 @@ func TestReadFS_Lstat(t *testing.T) {
 
 	writeable := NewDirFS(tmpDir)
 	for _, path := range []string{"animals.txt", "sub", "sub-link"} {
-		require.NoError(t, writeable.Symlink(path, path+"-link"))
+		require.Zero(t, writeable.Symlink(path, path+"-link"))
 	}
 
 	testFS := NewReadFS(writeable)
diff --git a/internal/sysfs/rootfs_test.go b/internal/sysfs/rootfs_test.go
index 744d00b9..49074111 100644
--- a/internal/sysfs/rootfs_test.go
+++ b/internal/sysfs/rootfs_test.go
@@ -48,13 +48,14 @@ func TestNewRootFS(t *testing.T) {
 		require.Equal(t, "[.:/tmp]", rootFS.String())
 
 		// Guest can look up /tmp
-		f, err := rootFS.OpenFile("/tmp", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := rootFS.OpenFile("/tmp", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		require.NoError(t, f.Close())
 
 		// Guest can look up / and see "/tmp" in it
-		f, err = rootFS.OpenFile("/", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno = rootFS.OpenFile("/", os.O_RDONLY, 0)
+		require.Zero(t, errno)
+
 		dirents, err := f.(fs.ReadDirFile).ReadDir(-1)
 		require.NoError(t, err)
 		require.Equal(t, 1, len(dirents))
@@ -95,9 +96,10 @@ func TestNewRootFS(t *testing.T) {
 		require.NotEqual(t, testFS2, rootFS)
 
 		t.Run("last wins", func(t *testing.T) {
-			f, err := rootFS.OpenFile("/tmp/a", os.O_RDONLY, 0)
-			require.NoError(t, err)
+			f, errno := rootFS.OpenFile("/tmp/a", os.O_RDONLY, 0)
+			require.Zero(t, errno)
 			defer f.Close()
+
 			b, err := io.ReadAll(f)
 			require.NoError(t, err)
 			require.Equal(t, []byte{2}, b)
@@ -105,8 +107,8 @@ func TestNewRootFS(t *testing.T) {
 
 		// This test is covered by fstest.TestFS, but doing again here
 		t.Run("root includes prefix mount", func(t *testing.T) {
-			f, err := rootFS.OpenFile(".", os.O_RDONLY, 0)
-			require.NoError(t, err)
+			f, errno := rootFS.OpenFile(".", os.O_RDONLY, 0)
+			require.Zero(t, errno)
 			defer f.Close()
 
 			require.Equal(t, []string{"a", "tmp"}, readDirNames(t, f))
@@ -115,8 +117,8 @@ func TestNewRootFS(t *testing.T) {
 }
 
 func readDirNames(t *testing.T, f fs.File) []string {
-	names, err := platform.Readdirnames(f, -1)
-	require.NoError(t, err)
+	names, errno := platform.Readdirnames(f, -1)
+	require.Zero(t, errno)
 	sort.Strings(names)
 	return names
 }
@@ -279,8 +281,8 @@ func TestRootFS_examples(t *testing.T) {
 			require.NoError(t, err)
 
 			for _, p := range tc.expected {
-				f, err := root.OpenFile(p, os.O_RDONLY, 0)
-				require.NoError(t, err, p)
+				f, errno := root.OpenFile(p, os.O_RDONLY, 0)
+				require.Zero(t, errno, p)
 				require.NoError(t, f.Close(), p)
 			}
 
diff --git a/internal/sysfs/sysfs_test.go b/internal/sysfs/sysfs_test.go
index aef35713..865c94ee 100644
--- a/internal/sysfs/sysfs_test.go
+++ b/internal/sysfs/sysfs_test.go
@@ -24,8 +24,8 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS FS) {
 	err := os.WriteFile(realPath, []byte{}, 0o600)
 	require.NoError(t, err)
 
-	f, err := testFS.OpenFile(file, os.O_RDWR, 0)
-	require.NoError(t, err)
+	f, errno := testFS.OpenFile(file, os.O_RDWR, 0)
+	require.Zero(t, errno)
 	defer f.Close()
 
 	w, ok := f.(io.Writer)
@@ -46,8 +46,8 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS FS) {
 
 	// re-create as read-only, using 0444 to allow read-back on windows.
 	require.NoError(t, os.Remove(realPath))
-	f, err = testFS.OpenFile(file, os.O_RDONLY|os.O_CREATE, 0o444)
-	require.NoError(t, err)
+	f, errno = testFS.OpenFile(file, os.O_RDONLY|os.O_CREATE, 0o444)
+	require.Zero(t, errno)
 	defer f.Close()
 
 	w, ok = f.(io.Writer)
@@ -67,27 +67,27 @@ func testOpen_O_RDWR(t *testing.T, tmpDir string, testFS FS) {
 	// from os.TestDirFSPathsValid
 	if runtime.GOOS != "windows" {
 		t.Run("strange name", func(t *testing.T) {
-			f, err := testFS.OpenFile(`e:xperi\ment.txt`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
-			require.NoError(t, err)
+			f, errno = testFS.OpenFile(`e:xperi\ment.txt`, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
+			require.Zero(t, errno)
 			defer f.Close()
 
-			_, err = platform.StatFile(f)
-			require.NoError(t, err)
+			_, errno = platform.StatFile(f)
+			require.Zero(t, errno)
 		})
 	}
 }
 
 func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 	t.Run("doesn't exist", func(t *testing.T) {
-		_, err := testFS.OpenFile("nope", os.O_RDONLY, 0)
+		_, errno := testFS.OpenFile("nope", os.O_RDONLY, 0)
 
 		// We currently follow os.Open not syscall.Open, so the error is wrapped.
-		require.EqualErrno(t, syscall.ENOENT, err)
+		require.EqualErrno(t, syscall.ENOENT, errno)
 	})
 
 	t.Run("readdir . opens root", func(t *testing.T) {
-		f, err := testFS.OpenFile(".", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile(".", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 
 		dirents := requireReaddir(t, f, -1, expectIno)
@@ -102,8 +102,8 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 	})
 
 	t.Run("readdirnames . opens root", func(t *testing.T) {
-		f, err := testFS.OpenFile(".", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile(".", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 
 		names := requireReaddirnames(t, f, -1)
@@ -111,8 +111,8 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 	})
 
 	t.Run("readdir empty", func(t *testing.T) {
-		f, err := testFS.OpenFile("emptydir", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile("emptydir", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 
 		entries := requireReaddir(t, f, -1, expectIno)
@@ -120,8 +120,8 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 	})
 
 	t.Run("readdirnames empty", func(t *testing.T) {
-		f, err := testFS.OpenFile("emptydir", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile("emptydir", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 
 		names := requireReaddirnames(t, f, -1)
@@ -129,21 +129,21 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 	})
 
 	t.Run("readdir partial", func(t *testing.T) {
-		dirF, err := testFS.OpenFile("dir", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		dirF, errno := testFS.OpenFile("dir", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer dirF.Close()
 
-		dirents1, err := platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		dirents1, errno := platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(dirents1))
 
-		dirents2, err := platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		dirents2, errno := platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(dirents2))
 
 		// read exactly the last entry
-		dirents3, err := platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		dirents3, errno := platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(dirents3))
 
 		dirents := []*platform.Dirent{dirents1[0], dirents2[0], dirents3[0]}
@@ -158,28 +158,28 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 		}, dirents)
 
 		// no error reading an exhausted directory
-		_, err = platform.Readdir(dirF, 1)
-		require.NoError(t, err)
+		_, errno = platform.Readdir(dirF, 1)
+		require.Zero(t, errno)
 	})
 
 	// TODO: consolidate duplicated tests from platform once we have our own
 	// file type
 	t.Run("readdirnames partial", func(t *testing.T) {
-		dirF, err := testFS.OpenFile("dir", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		dirF, errno := testFS.OpenFile("dir", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer dirF.Close()
 
-		names1, err := platform.Readdirnames(dirF, 1)
-		require.NoError(t, err)
+		names1, errno := platform.Readdirnames(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(names1))
 
-		names2, err := platform.Readdirnames(dirF, 1)
-		require.NoError(t, err)
+		names2, errno := platform.Readdirnames(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(names2))
 
 		// read exactly the last entry
-		names3, err := platform.Readdirnames(dirF, 1)
-		require.NoError(t, err)
+		names3, errno := platform.Readdirnames(dirF, 1)
+		require.Zero(t, errno)
 		require.Equal(t, 1, len(names3))
 
 		names := []string{names1[0], names2[0], names3[0]}
@@ -188,13 +188,13 @@ func testOpen_Read(t *testing.T, testFS FS, expectIno bool) {
 		require.Equal(t, []string{"-", "a-", "ab-"}, names)
 
 		// no error reading an exhausted directory
-		_, err = platform.Readdirnames(dirF, 1)
-		require.NoError(t, err)
+		_, errno = platform.Readdirnames(dirF, 1)
+		require.Zero(t, errno)
 	})
 
 	t.Run("file exists", func(t *testing.T) {
-		f, err := testFS.OpenFile("animals.txt", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 
 		fileContents := []byte(`bear
@@ -230,18 +230,18 @@ human
 		// currently supported in WASI or GOOS=js
 		const O_NOATIME = 0x40000
 
-		f, err := testFS.OpenFile("animals.txt", os.O_RDONLY|O_NOATIME, 0)
-		require.NoError(t, err)
+		f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY|O_NOATIME, 0)
+		require.Zero(t, errno)
 		defer f.Close()
 	})
 
 	t.Run("writing to a read-only file is EBADF", func(t *testing.T) {
-		f, err := testFS.OpenFile("animals.txt", os.O_RDONLY, 0)
+		f, errno := testFS.OpenFile("animals.txt", os.O_RDONLY, 0)
 		defer require.NoError(t, f.Close())
-		require.NoError(t, err)
+		require.Zero(t, errno)
 
 		if w, ok := f.(io.Writer); ok {
-			_, err = w.Write([]byte{1, 2, 3, 4})
+			_, err := w.Write([]byte{1, 2, 3, 4})
 			require.EqualErrno(t, syscall.EBADF, platform.UnwrapOSError(err))
 		} else {
 			t.Skip("not an io.Writer")
@@ -249,12 +249,12 @@ human
 	})
 
 	t.Run("writing to a directory is EBADF", func(t *testing.T) {
-		f, err := testFS.OpenFile("sub", os.O_RDONLY, 0)
+		f, errno := testFS.OpenFile("sub", os.O_RDONLY, 0)
 		defer require.NoError(t, f.Close())
-		require.NoError(t, err)
+		require.Zero(t, errno)
 
 		if w, ok := f.(io.Writer); ok {
-			_, err = w.Write([]byte{1, 2, 3, 4})
+			_, err := w.Write([]byte{1, 2, 3, 4})
 			require.EqualErrno(t, syscall.EBADF, platform.UnwrapOSError(err))
 		} else {
 			t.Skip("not an io.Writer")
@@ -263,16 +263,16 @@ human
 }
 
 func testLstat(t *testing.T, testFS FS) {
-	_, err := testFS.Lstat("cat")
-	require.EqualErrno(t, syscall.ENOENT, err)
-	_, err = testFS.Lstat("sub/cat")
-	require.EqualErrno(t, syscall.ENOENT, err)
+	_, errno := testFS.Lstat("cat")
+	require.EqualErrno(t, syscall.ENOENT, errno)
+	_, errno = testFS.Lstat("sub/cat")
+	require.EqualErrno(t, syscall.ENOENT, errno)
 
 	var st platform.Stat_t
 
 	t.Run("dir", func(t *testing.T) {
-		st, err = testFS.Lstat(".")
-		require.NoError(t, err)
+		st, errno = testFS.Lstat(".")
+		require.Zero(t, errno)
 		require.True(t, st.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -280,8 +280,9 @@ func testLstat(t *testing.T, testFS FS) {
 	var stFile platform.Stat_t
 
 	t.Run("file", func(t *testing.T) {
-		stFile, err = testFS.Lstat("animals.txt")
-		require.NoError(t, err)
+		stFile, errno = testFS.Lstat("animals.txt")
+		require.Zero(t, errno)
+
 		require.Zero(t, stFile.Mode.Type())
 		require.Equal(t, int64(30), stFile.Size)
 		require.NotEqual(t, uint64(0), st.Ino)
@@ -293,8 +294,9 @@ func testLstat(t *testing.T, testFS FS) {
 
 	var stSubdir platform.Stat_t
 	t.Run("subdir", func(t *testing.T) {
-		stSubdir, err = testFS.Lstat("sub")
-		require.NoError(t, err)
+		stSubdir, errno = testFS.Lstat("sub")
+		require.Zero(t, errno)
+
 		require.True(t, stSubdir.Mode.IsDir())
 		require.NotEqual(t, uint64(0), st.Ino)
 	})
@@ -305,8 +307,8 @@ func testLstat(t *testing.T, testFS FS) {
 
 	t.Run("link to dir link", func(t *testing.T) {
 		pathLink := "sub-link"
-		stLink, err := testFS.Lstat(pathLink)
-		require.NoError(t, err)
+		stLink, errno := testFS.Lstat(pathLink)
+		require.Zero(t, errno)
 
 		requireLinkStat(t, testFS, pathLink, stLink)
 	})
@@ -314,8 +316,9 @@ func testLstat(t *testing.T, testFS FS) {
 
 func requireLinkStat(t *testing.T, testFS FS, path string, stat platform.Stat_t) {
 	link := path + "-link"
-	stLink, err := testFS.Lstat(link)
-	require.NoError(t, err)
+	stLink, errno := testFS.Lstat(link)
+	require.Zero(t, errno)
+
 	require.NotEqual(t, stat.Ino, stLink.Ino) // inodes are not equal
 	require.Equal(t, fs.ModeSymlink, stLink.Mode.Type())
 	// From https://linux.die.net/man/2/lstat:
@@ -329,19 +332,21 @@ func requireLinkStat(t *testing.T, testFS FS, path string, stat platform.Stat_t)
 }
 
 func testStat(t *testing.T, testFS FS) {
-	_, err := testFS.Stat("cat")
-	require.EqualErrno(t, syscall.ENOENT, err)
-	_, err = testFS.Stat("sub/cat")
-	require.EqualErrno(t, syscall.ENOENT, err)
+	_, errno := testFS.Stat("cat")
+	require.EqualErrno(t, syscall.ENOENT, errno)
+	_, errno = testFS.Stat("sub/cat")
+	require.EqualErrno(t, syscall.ENOENT, errno)
+
+	st, errno := testFS.Stat("sub/test.txt")
+	require.Zero(t, errno)
 
-	st, err := testFS.Stat("sub/test.txt")
-	require.NoError(t, err)
 	require.False(t, st.Mode.IsDir())
 	require.NotEqual(t, uint64(0), st.Dev)
 	require.NotEqual(t, uint64(0), st.Ino)
 
-	st, err = testFS.Stat("sub")
-	require.NoError(t, err)
+	st, errno = testFS.Stat("sub")
+	require.Zero(t, errno)
+
 	require.True(t, st.Mode.IsDir())
 	// windows before go 1.20 has trouble reading the inode information on directories.
 	if runtime.GOOS != "windows" || platform.IsGo120 {
@@ -353,8 +358,9 @@ func testStat(t *testing.T, testFS FS) {
 // requireReaddir ensures the input file is a directory, and returns its
 // entries.
 func requireReaddir(t *testing.T, f fs.File, n int, expectIno bool) []*platform.Dirent {
-	entries, err := platform.Readdir(f, n)
-	require.NoError(t, err)
+	entries, errno := platform.Readdir(f, n)
+	require.Zero(t, errno)
+
 	sort.Slice(entries, func(i, j int) bool { return entries[i].Name < entries[j].Name })
 	if _, ok := f.(*openRootDir); ok {
 		// TODO: get inodes to work on the root directory of a composite FS
@@ -368,8 +374,8 @@ func requireReaddir(t *testing.T, f fs.File, n int, expectIno bool) []*platform.
 // requireReaddirnames ensures the input file is a directory, and returns its
 // entries.
 func requireReaddirnames(t *testing.T, f fs.File, n int) []string {
-	names, err := platform.Readdirnames(f, n)
-	require.NoError(t, err)
+	names, errno := platform.Readdirnames(f, n)
+	require.Zero(t, errno)
 	sort.Strings(names)
 	return names
 }
@@ -388,11 +394,11 @@ func testReadlink(t *testing.T, readFS, writeFS FS) {
 	}
 
 	for _, tl := range testLinks {
-		err := writeFS.Symlink(tl.old, tl.dst) // not os.Symlink for windows compat
-		require.NoError(t, err, "%v", tl)
+		errno := writeFS.Symlink(tl.old, tl.dst) // not os.Symlink for windows compat
+		require.Zero(t, errno, "%v", tl)
 
-		dst, err := readFS.Readlink(tl.dst)
-		require.NoError(t, err)
+		dst, errno := readFS.Readlink(tl.dst)
+		require.Zero(t, errno)
 		require.Equal(t, tl.old, dst)
 	}
 
@@ -574,8 +580,8 @@ func TestWriterAtOffset(t *testing.T) {
 		tc := tc
 
 		t.Run(tc.name, func(t *testing.T) {
-			f, err := tc.fs.OpenFile(readerAtFile, os.O_RDWR|os.O_CREATE, 0o600)
-			require.NoError(t, err)
+			f, errno := tc.fs.OpenFile(readerAtFile, os.O_RDWR|os.O_CREATE, 0o600)
+			require.Zero(t, errno)
 			defer f.Close()
 
 			w := f.(io.Writer)
@@ -640,8 +646,8 @@ func TestWriterAtOffset_empty(t *testing.T) {
 		tc := tc
 
 		t.Run(tc.name, func(t *testing.T) {
-			f, err := tc.fs.OpenFile(emptyFile, os.O_RDWR|os.O_CREATE, 0o600)
-			require.NoError(t, err)
+			f, errno := tc.fs.OpenFile(emptyFile, os.O_RDWR|os.O_CREATE, 0o600)
+			require.Zero(t, errno)
 			defer f.Close()
 
 			r := f.(io.Writer)
@@ -668,15 +674,15 @@ func TestWriterAtOffset_Unsupported(t *testing.T) {
 	tmpDir := t.TempDir()
 	dirFS := NewDirFS(tmpDir)
 
-	f, err := dirFS.OpenFile(readerAtFile, os.O_RDWR|os.O_CREATE, 0o600)
-	require.NoError(t, err)
+	f, errno := dirFS.OpenFile(readerAtFile, os.O_RDWR|os.O_CREATE, 0o600)
+	require.Zero(t, errno)
 	defer f.Close()
 
 	// mask both io.WriterAt and io.Seeker
 	ra := WriterAtOffset(struct{ fs.File }{f}, 0)
 
 	buf := make([]byte, 3)
-	_, err = ra.Write(buf)
+	_, err := ra.Write(buf)
 	require.Equal(t, syscall.ENOSYS, err)
 }
 
@@ -684,8 +690,8 @@ func TestWriterAtOffset_Unsupported(t *testing.T) {
 // sync anyway. There is no test in Go for os.File Sync, but closest is similar
 // to below. Effectively, this only tests that things don't error.
 func Test_FileSync(t *testing.T) {
-	testSync(t, func(f fs.File) error {
-		return f.(interface{ Sync() error }).Sync()
+	testSync(t, func(f fs.File) syscall.Errno {
+		return platform.UnwrapOSError(f.(interface{ Sync() error }).Sync())
 	})
 }
 
@@ -694,7 +700,7 @@ func Test_FileDatasync(t *testing.T) {
 	testSync(t, FileDatasync)
 }
 
-func testSync(t *testing.T, sync func(fs.File) error) {
+func testSync(t *testing.T, sync func(fs.File) syscall.Errno) {
 	f, err := os.CreateTemp("", t.Name())
 	require.NoError(t, err)
 	defer f.Close()
@@ -706,7 +712,7 @@ func testSync(t *testing.T, sync func(fs.File) error) {
 	require.NoError(t, err)
 
 	// Sync the data.
-	require.NoError(t, sync(f))
+	require.Zero(t, sync(f))
 
 	// Rewind while the file is still open.
 	_, err = f.Seek(0, io.SeekStart)
diff --git a/internal/wasi_snapshot_preview1/errno_test.go b/internal/wasi_snapshot_preview1/errno_test.go
index 4ac7e341..08746e81 100644
--- a/internal/wasi_snapshot_preview1/errno_test.go
+++ b/internal/wasi_snapshot_preview1/errno_test.go
@@ -1,7 +1,6 @@
 package wasi_snapshot_preview1
 
 import (
-	"io"
 	"syscall"
 	"testing"
 )
@@ -9,16 +8,11 @@ import (
 func TestToErrno(t *testing.T) {
 	tests := []struct {
 		name     string
-		input    error
+		input    syscall.Errno
 		expected Errno
 	}{
 		{
-			name:     "nil is not an error",
-			expected: ErrnoSuccess,
-		},
-		{
-			name:     "io.EOF is not an error",
-			input:    io.EOF,
+			name:     "zero is not an error",
 			expected: ErrnoSuccess,
 		},
 		{
@@ -41,6 +35,11 @@ func TestToErrno(t *testing.T) {
 			input:    syscall.EEXIST,
 			expected: ErrnoExist,
 		},
+		{
+			name:     "syscall.EFAULT",
+			input:    syscall.EFAULT,
+			expected: ErrnoFault,
+		},
 		{
 			name:     "syscall.EINTR",
 			input:    syscall.EINTR,
diff --git a/internal/wasm/call_context_test.go b/internal/wasm/call_context_test.go
index cd6174b1..99619410 100644
--- a/internal/wasm/call_context_test.go
+++ b/internal/wasm/call_context_test.go
@@ -109,8 +109,8 @@ func TestCallContext_Close(t *testing.T) {
 		sysCtx := internalsys.DefaultContext(testFS)
 		fsCtx := sysCtx.FS()
 
-		_, err := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		_, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 
 		m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
 		require.NoError(t, err)
@@ -144,8 +144,8 @@ func TestCallContext_Close(t *testing.T) {
 		sysCtx := internalsys.DefaultContext(testFS)
 		fsCtx := sysCtx.FS()
 
-		_, err := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		_, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 
 		m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
 		require.NoError(t, err)
@@ -213,8 +213,8 @@ func TestCallContext_CallDynamic(t *testing.T) {
 		sysCtx := internalsys.DefaultContext(testFS)
 		fsCtx := sysCtx.FS()
 
-		_, err := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
-		require.NoError(t, err)
+		_, errno := fsCtx.OpenFile(testFS, "/foo", os.O_RDONLY, 0)
+		require.Zero(t, errno)
 
 		m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
 		require.NoError(t, err)
@@ -242,8 +242,8 @@ func TestCallContext_CallDynamic(t *testing.T) {
 		fsCtx := sysCtx.FS()
 
 		path := "/foo"
-		_, err := fsCtx.OpenFile(testFS, path, os.O_RDONLY, 0)
-		require.NoError(t, err)
+		_, errno := fsCtx.OpenFile(testFS, path, os.O_RDONLY, 0)
+		require.Zero(t, errno)
 
 		m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
 		require.NoError(t, err)