diff --git a/go.mod b/go.mod index a4de141365..2031d35e4d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/tinygo-org/tinygo -go 1.19 +go 1.23 require ( github.com/aykevl/go-wasm v0.0.2-0.20240312204833-50275154210c diff --git a/src/os/file.go b/src/os/file.go index b47bf748a7..429515f46c 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -19,6 +19,7 @@ package os import ( "errors" + "internal/poll" "io" "io/fs" "runtime" @@ -61,6 +62,25 @@ func fixCount(n int, err error) (int, error) { return n, err } +// checkWrapErr is the test hook to enable checking unexpected wrapped errors of poll.ErrFileClosing. +// It is set to true in the export_test.go for tests (including fuzz tests). +var checkWrapErr = false + +// wrapErr wraps an error that occurred during an operation on an open file. +// It passes io.EOF through unchanged, otherwise converts +// poll.ErrFileClosing to ErrClosed and wraps the error in a PathError. +func (f *File) wrapErr(op string, err error) error { + if err == nil || err == io.EOF { + return err + } + if err == poll.ErrFileClosing { + err = ErrClosed + } else if checkWrapErr && errors.Is(err, poll.ErrFileClosing) { + panic("unexpected error wrapping poll.ErrFileClosing: " + err.Error()) + } + return &PathError{Op: op, Path: f.name, Err: err} +} + // Remove removes a file or (empty) directory. If the operation fails, it will // return an error of type *PathError. func Remove(path string) error { @@ -257,6 +277,10 @@ func (f *File) SyscallConn() (conn syscall.RawConn, err error) { return } +// Chmod changes the mode of the file to mode. +// If there is an error, it will be of type *PathError. +func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) } + // SetDeadline sets the read and write deadlines for a File. // Calls to SetDeadline for files that do not support deadlines will return ErrNoDeadline // This stub always returns ErrNoDeadline. diff --git a/src/os/file_posix.go b/src/os/file_posix.go index a10ff49196..23b2715fe9 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -36,3 +36,23 @@ func (f *File) setWriteDeadline(t time.Time) error { } return ErrNotImplemented } + +// See docs in file.go:(*File).Chmod. +func (f *File) chmod(mode FileMode) error { + if err := f.checkValid("chmod"); err != nil { + return err + } + if e := f.pfd.Fchmod(syscallMode(mode)); e != nil { + return f.wrapErr("chmod", e) + } + return nil +} + +// checkValid checks whether f is valid for use. +// If not, it returns an appropriate error, perhaps incorporating the operation name op. +func (f *File) checkValid(_ string) error { + if f == nil { + return ErrInvalid + } + return nil +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index d94b80cc3d..22cc91da12 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -10,6 +10,7 @@ package os import ( + "internal/poll" "io" "syscall" _ "unsafe" @@ -42,6 +43,7 @@ type file struct { name string dirinfo *dirInfo // nil unless directory being read appendMode bool + pfd poll.FD } func (f *file) close() (err error) { diff --git a/src/syscall/tables_wasip1.go b/src/syscall/tables_wasip1.go new file mode 100644 index 0000000000..72cd830612 --- /dev/null +++ b/src/syscall/tables_wasip1.go @@ -0,0 +1,123 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build wasip1 + +package syscall + +import "runtime" + +// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.) +var errorstr = [...]string{ + E2BIG: "Argument list too long", + EACCES: "Permission denied", + EADDRINUSE: "Address already in use", + EADDRNOTAVAIL: "Address not available", + EAFNOSUPPORT: "Address family not supported by protocol family", + EAGAIN: "Try again", + EALREADY: "Socket already connected", + EBADF: "Bad file number", + //EBADFD: "file descriptor in bad state", + EBADMSG: "Trying to read unreadable message", + EBUSY: "Device or resource busy", + ECANCELED: "Operation canceled.", + ECHILD: "No child processes", + ECONNABORTED: "Connection aborted", + ECONNREFUSED: "Connection refused", + ECONNRESET: "Connection reset by peer", + EDEADLK: "Deadlock condition", + EDESTADDRREQ: "Destination address required", + EDOM: "Math arg out of domain of func", + EDQUOT: "Quota exceeded", + EEXIST: "File exists", + EFAULT: "Bad address", + EFBIG: "File too large", + EHOSTUNREACH: "Host is unreachable", + EIDRM: "Identifier removed", + EILSEQ: "EILSEQ", + EINPROGRESS: "Connection already in progress", + EINTR: "Interrupted system call", + EINVAL: "Invalid argument", + EIO: "I/O error", + EISCONN: "Socket is already connected", + EISDIR: "Is a directory", + ELOOP: "Too many symbolic links", + EMFILE: "Too many open files", + EMLINK: "Too many links", + EMSGSIZE: "Message too long", + EMULTIHOP: "Multihop attempted", + ENAMETOOLONG: "File name too long", + ENETDOWN: "Network interface is not configured", + ENETRESET: "Network dropped connection on reset", + ENETUNREACH: "Network is unreachable", + ENFILE: "File table overflow", + ENOBUFS: "No buffer space available", + ENODEV: "No such device", + ENOENT: "No such file or directory", + ENOEXEC: "Exec format error", + ENOLCK: "No record locks available", + ENOLINK: "The link has been severed", + ENOMEM: "Out of memory", + ENOMSG: "No message of desired type", + ENOPROTOOPT: "Protocol not available", + ENOSPC: "No space left on device", + ENOSYS: "Not implemented on " + runtime.GOOS, + ENOTCONN: "Socket is not connected", + ENOTDIR: "Not a directory", + ENOTEMPTY: "Directory not empty", + ENOTRECOVERABLE: "State not recoverable", + ENOTSOCK: "Socket operation on non-socket", + ENOTSUP: "Not supported", + ENOTTY: "Not a typewriter", + ENXIO: "No such device or address", + EOVERFLOW: "Value too large for defined data type", + //EOWNERDEAD: "Owner died", + EPERM: "Operation not permitted", + EPIPE: "Broken pipe", + EPROTO: "Protocol error", + EPROTONOSUPPORT: "Unknown protocol", + EPROTOTYPE: "Protocol wrong type for socket", + ERANGE: "Math result not representable", + EROFS: "Read-only file system", + ESPIPE: "Illegal seek", + ESRCH: "No such process", + ESTALE: "Stale file handle", + ETIMEDOUT: "Connection timed out", + ETXTBSY: "Text file busy", + EXDEV: "Cross-device link", + ENOTCAPABLE: "Capabilities insufficient", +} + +// Do the interface allocations only once for common +// Errno values. +var ( + errEAGAIN error = EAGAIN + errEINVAL error = EINVAL + errENOENT error = ENOENT +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +// +// We set both noinline and nosplit to reduce code size, this function has many +// call sites in the syscall package, inlining it causes a significant increase +// of the compiled code; the function call ultimately does not make a difference +// in the performance of syscall functions since the time is dominated by calls +// to the imports and path resolution. +// +//go:noinline +//go:nosplit +func errnoErr(e Errno) error { + switch e { + case 0: + return nil + case EAGAIN: + return errEAGAIN + case EINVAL: + return errEINVAL + case ENOENT: + return errENOENT + } + return e +}