diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ecc8b535..1c1abb91 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,8 +14,8 @@ jobs: test: strategy: matrix: - # os: [macos-latest, ubuntu-latest] - os: [macos-latest] + os: [macos-latest, ubuntu-latest] + # os: [macos-latest] llvm: [18] runs-on: ${{ matrix.os }} steps: diff --git a/c/libuv/error.go b/c/libuv/error.go index 309604e4..13f0029f 100644 --- a/c/libuv/error.go +++ b/c/libuv/error.go @@ -9,85 +9,85 @@ import ( ) const ( - E2BIG Errno = Errno(syscall.E2BIG) - EACCES Errno = Errno(syscall.EACCES) - EADDRINUSE Errno = Errno(syscall.EADDRINUSE) - EADDRNOTAVAIL Errno = Errno(syscall.EADDRNOTAVAIL) - EAFNOSUPPORT Errno = Errno(syscall.EAFNOSUPPORT) - EAGAIN Errno = Errno(syscall.EAGAIN) - EALREADY Errno = Errno(syscall.EALREADY) - EBADF Errno = Errno(syscall.EBADF) - EBUSY Errno = Errno(syscall.EBUSY) - ECANCELED Errno = Errno(syscall.ECANCELED) - ECONNABORTED Errno = Errno(syscall.ECONNABORTED) - ECONNREFUSED Errno = Errno(syscall.ECONNREFUSED) - ECONNRESET Errno = Errno(syscall.ECONNRESET) - EDESTADDRREQ Errno = Errno(syscall.EDESTADDRREQ) - EEXIST Errno = Errno(syscall.EEXIST) - EFAULT Errno = Errno(syscall.EFAULT) - EFBIG Errno = Errno(syscall.EFBIG) - EHOSTUNREACH Errno = Errno(syscall.EHOSTUNREACH) - EINTR Errno = Errno(syscall.EINTR) - EINVAL Errno = Errno(syscall.EINVAL) - EIO Errno = Errno(syscall.EIO) - EISCONN Errno = Errno(syscall.EISCONN) - EISDIR Errno = Errno(syscall.EISDIR) - ELOOP Errno = Errno(syscall.ELOOP) - EMFILE Errno = Errno(syscall.EMFILE) - EMSGSIZE Errno = Errno(syscall.EMSGSIZE) - ENAMETOOLONG Errno = Errno(syscall.ENAMETOOLONG) - ENETDOWN Errno = Errno(syscall.ENETDOWN) - ENETUNREACH Errno = Errno(syscall.ENETUNREACH) - ENFILE Errno = Errno(syscall.ENFILE) - ENOBUFS Errno = Errno(syscall.ENOBUFS) - ENODEV Errno = Errno(syscall.ENODEV) - ENOENT Errno = Errno(syscall.ENOENT) - ENOMEM Errno = Errno(syscall.ENOMEM) - ENOPROTOOPT Errno = Errno(syscall.ENOPROTOOPT) - ENOSPC Errno = Errno(syscall.ENOSPC) - ENOSYS Errno = Errno(syscall.ENOSYS) - ENOTCONN Errno = Errno(syscall.ENOTCONN) - ENOTDIR Errno = Errno(syscall.ENOTDIR) - ENOTEMPTY Errno = Errno(syscall.ENOTEMPTY) - ENOTSOCK Errno = Errno(syscall.ENOTSOCK) - ENOTSUP Errno = Errno(syscall.ENOTSUP) - EOVERFLOW Errno = Errno(syscall.EOVERFLOW) - EPERM Errno = Errno(syscall.EPERM) - EPIPE Errno = Errno(syscall.EPIPE) - EPROTO Errno = Errno(syscall.EPROTO) - EPROTONOSUPPORT Errno = Errno(syscall.EPROTONOSUPPORT) - EPROTOTYPE Errno = Errno(syscall.EPROTOTYPE) - ERANGE Errno = Errno(syscall.ERANGE) - EROFS Errno = Errno(syscall.EROFS) - ESHUTDOWN Errno = Errno(syscall.ESHUTDOWN) - ESPIPE Errno = Errno(syscall.ESPIPE) - ESRCH Errno = Errno(syscall.ESRCH) - ETIMEDOUT Errno = Errno(syscall.ETIMEDOUT) - ETXTBSY Errno = Errno(syscall.ETXTBSY) - EXDEV Errno = Errno(syscall.EXDEV) - ENXIO Errno = Errno(syscall.ENXIO) - EMLINK Errno = Errno(syscall.EMLINK) - EHOSTDOWN Errno = Errno(syscall.EHOSTDOWN) - ENOTTY Errno = Errno(syscall.ENOTTY) - EFTYPE Errno = Errno(syscall.EFTYPE) - EILSEQ Errno = Errno(syscall.EILSEQ) - ESOCKTNOSUPPORT Errno = Errno(syscall.ESOCKTNOSUPPORT) + E2BIG = Errno(syscall.E2BIG) + EACCES = Errno(syscall.EACCES) + EADDRINUSE = Errno(syscall.EADDRINUSE) + EADDRNOTAVAIL = Errno(syscall.EADDRNOTAVAIL) + EAFNOSUPPORT = Errno(syscall.EAFNOSUPPORT) + EAGAIN = Errno(syscall.EAGAIN) + EALREADY = Errno(syscall.EALREADY) + EBADF = Errno(syscall.EBADF) + EBUSY = Errno(syscall.EBUSY) + ECANCELED = Errno(syscall.ECANCELED) + ECONNABORTED = Errno(syscall.ECONNABORTED) + ECONNREFUSED = Errno(syscall.ECONNREFUSED) + ECONNRESET = Errno(syscall.ECONNRESET) + EDESTADDRREQ = Errno(syscall.EDESTADDRREQ) + EEXIST = Errno(syscall.EEXIST) + EFAULT = Errno(syscall.EFAULT) + EFBIG = Errno(syscall.EFBIG) + EHOSTUNREACH = Errno(syscall.EHOSTUNREACH) + EINTR = Errno(syscall.EINTR) + EINVAL = Errno(syscall.EINVAL) + EIO = Errno(syscall.EIO) + EISCONN = Errno(syscall.EISCONN) + EISDIR = Errno(syscall.EISDIR) + ELOOP = Errno(syscall.ELOOP) + EMFILE = Errno(syscall.EMFILE) + EMSGSIZE = Errno(syscall.EMSGSIZE) + ENAMETOOLONG = Errno(syscall.ENAMETOOLONG) + ENETDOWN = Errno(syscall.ENETDOWN) + ENETUNREACH = Errno(syscall.ENETUNREACH) + ENFILE = Errno(syscall.ENFILE) + ENOBUFS = Errno(syscall.ENOBUFS) + ENODEV = Errno(syscall.ENODEV) + ENOENT = Errno(syscall.ENOENT) + ENOMEM = Errno(syscall.ENOMEM) + ENOPROTOOPT = Errno(syscall.ENOPROTOOPT) + ENOSPC = Errno(syscall.ENOSPC) + ENOSYS = Errno(syscall.ENOSYS) + ENOTCONN = Errno(syscall.ENOTCONN) + ENOTDIR = Errno(syscall.ENOTDIR) + ENOTEMPTY = Errno(syscall.ENOTEMPTY) + ENOTSOCK = Errno(syscall.ENOTSOCK) + ENOTSUP = Errno(syscall.ENOTSUP) + EOVERFLOW = Errno(syscall.EOVERFLOW) + EPERM = Errno(syscall.EPERM) + EPIPE = Errno(syscall.EPIPE) + EPROTO = Errno(syscall.EPROTO) + EPROTONOSUPPORT = Errno(syscall.EPROTONOSUPPORT) + EPROTOTYPE = Errno(syscall.EPROTOTYPE) + ERANGE = Errno(syscall.ERANGE) + EROFS = Errno(syscall.EROFS) + ESHUTDOWN = Errno(syscall.ESHUTDOWN) + ESPIPE = Errno(syscall.ESPIPE) + ESRCH = Errno(syscall.ESRCH) + ETIMEDOUT = Errno(syscall.ETIMEDOUT) + ETXTBSY = Errno(syscall.ETXTBSY) + EXDEV = Errno(syscall.EXDEV) + ENXIO = Errno(syscall.ENXIO) + EMLINK = Errno(syscall.EMLINK) + EHOSTDOWN = Errno(syscall.EHOSTDOWN) + ENOTTY = Errno(syscall.ENOTTY) + //EFTYPE = Errno(syscall.EFTYPE) + EILSEQ = Errno(syscall.EILSEQ) + ESOCKTNOSUPPORT = Errno(syscall.ESOCKTNOSUPPORT) ) const ( - EAI_ADDRFAMILY Errno = Errno(net.EAI_ADDRFAMILY) - EAI_AGAIN Errno = Errno(net.EAI_AGAIN) - EAI_BADFLAGS Errno = Errno(net.EAI_BADFLAGS) - EAI_BADHINTS Errno = Errno(net.EAI_BADHINTS) - EAI_FAIL Errno = Errno(net.EAI_FAIL) - EAI_FAMILY Errno = Errno(net.EAI_FAMILY) - EAI_MEMORY Errno = Errno(net.EAI_MEMORY) - EAI_NODATA Errno = Errno(net.EAI_NODATA) - EAI_NONAME Errno = Errno(net.EAI_NONAME) - EAI_OVERFLOW Errno = Errno(net.EAI_OVERFLOW) - EAI_PROTOCOL Errno = Errno(net.EAI_PROTOCOL) - EAI_SERVICE Errno = Errno(net.EAI_SERVICE) - EAI_SOCKTYPE Errno = Errno(net.EAI_SOCKTYPE) + EAI_ADDRFAMILY = Errno(net.EAI_ADDRFAMILY) + EAI_AGAIN = Errno(net.EAI_AGAIN) + EAI_BADFLAGS = Errno(net.EAI_BADFLAGS) + EAI_BADHINTS = Errno(net.EAI_BADHINTS) + EAI_FAIL = Errno(net.EAI_FAIL) + EAI_FAMILY = Errno(net.EAI_FAMILY) + EAI_MEMORY = Errno(net.EAI_MEMORY) + EAI_NODATA = Errno(net.EAI_NODATA) + EAI_NONAME = Errno(net.EAI_NONAME) + EAI_OVERFLOW = Errno(net.EAI_OVERFLOW) + EAI_PROTOCOL = Errno(net.EAI_PROTOCOL) + EAI_SERVICE = Errno(net.EAI_SERVICE) + EAI_SOCKTYPE = Errno(net.EAI_SOCKTYPE) ) const ( diff --git a/internal/lib/os/wait_wait6.go b/internal/lib/os/wait_wait6.go new file mode 100644 index 00000000..994b8a60 --- /dev/null +++ b/internal/lib/os/wait_wait6.go @@ -0,0 +1,32 @@ +// Copyright 2016 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 dragonfly || freebsd || netbsd + +package os + +import ( + "syscall" +) + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and reports whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + var errno syscall.Errno + for { + _, errno = wait6(_P_PID, p.Pid, syscall.WEXITED|syscall.WNOWAIT) + if errno != syscall.EINTR { + break + } + } + // TODO(xsw): + // runtime.KeepAlive(p) + if errno == syscall.ENOSYS { + return false, nil + } else if errno != 0 { + return false, NewSyscallError("wait6", errno) + } + return true, nil +} diff --git a/internal/lib/os/wait_waitid.go b/internal/lib/os/wait_waitid.go new file mode 100644 index 00000000..301254b3 --- /dev/null +++ b/internal/lib/os/wait_waitid.go @@ -0,0 +1,55 @@ +// Copyright 2016 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. + +// We used to used this code for Darwin, but according to issue #19314 +// waitid returns if the process is stopped, even when using WEXITED. + +//go:build linux + +package os + +import ( + "syscall" + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +const _P_PID = 1 + +// int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); +// +//go:linkname waitid C.waitid +func waitid(idtype, id uintptr, infop *uint64, options c.Int) c.Int + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and reports whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + // The waitid system call expects a pointer to a siginfo_t, + // which is 128 bytes on all Linux systems. + // On darwin/amd64, it requires 104 bytes. + // We don't care about the values it returns. + var siginfo [16]uint64 + psig := &siginfo[0] + var e syscall.Errno + for { + e = syscall.Errno(waitid(_P_PID, uintptr(p.Pid), psig, syscall.WEXITED|syscall.WNOWAIT)) + if e != syscall.EINTR { + break + } + } + // TODO(xsw): + // runtime.KeepAlive(p) + if e != 0 { + // waitid has been available since Linux 2.6.9, but + // reportedly is not available in Ubuntu on Windows. + // See issue 16610. + if e == syscall.ENOSYS { + return false, nil + } + return false, NewSyscallError("waitid", e) + } + return true, nil +} diff --git a/internal/lib/syscall/exec_libc.go b/internal/lib/syscall/exec_libc.go index 44557867..1c38a19a 100644 --- a/internal/lib/syscall/exec_libc.go +++ b/internal/lib/syscall/exec_libc.go @@ -11,6 +11,8 @@ package syscall import ( "runtime" "unsafe" + + "github.com/goplus/llgo/c" ) type SysProcAttr struct { @@ -77,8 +79,8 @@ func init() { // split the stack, or acquire mutexes). We can't call RawSyscall // because it's not safe even for BSD-subsystem calls. // -//go:norace -func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +// func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any // declarations require heap allocation (e.g., err1). var ( diff --git a/internal/lib/syscall/exec_libc2.go b/internal/lib/syscall/exec_libc2.go index 61b2c3b3..4c45b80b 100644 --- a/internal/lib/syscall/exec_libc2.go +++ b/internal/lib/syscall/exec_libc2.go @@ -52,8 +52,6 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to rawSyscall are okay because they are assembly // functions that do not grow the stack. -// -//go:norace func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err1 Errno) { // Declare all variables at top in case any // declarations require heap allocation (e.g., err1). diff --git a/internal/lib/syscall/exec_linux.go b/internal/lib/syscall/exec_linux.go index c66e820f..fb93fce3 100644 --- a/internal/lib/syscall/exec_linux.go +++ b/internal/lib/syscall/exec_linux.go @@ -6,6 +6,8 @@ package syscall +import "github.com/goplus/llgo/c" + // Linux unshare/clone/clone2/clone3 flags, architecture-independent, // copied from linux/sched.h. const ( @@ -119,8 +121,8 @@ func runtime_AfterForkInChild() // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. // -//go:norace -func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +// func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { +func forkAndExecInChild(argv0 *c.Char, argv, envv **c.Char, chroot, dir *c.Char, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { /* TODO(xsw): // Set up and fork. This returns immediately in the parent or // if there's an error. diff --git a/internal/lib/syscall/forkpipe2.go b/internal/lib/syscall/forkpipe2.go new file mode 100644 index 00000000..b5cd1d30 --- /dev/null +++ b/internal/lib/syscall/forkpipe2.go @@ -0,0 +1,105 @@ +// Copyright 2017 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 dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package syscall + +import ( + "sync" + + "github.com/goplus/llgo/c/syscall" +) + +// forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file +// descriptors. +func forkExecPipe(p []int) error { + return Pipe2(p, syscall.O_CLOEXEC) +} + +var ( + // Guard the forking variable. + forkingLock sync.Mutex + // Number of goroutines currently forking, and thus the + // number of goroutines holding a conceptual write lock + // on ForkLock. + forking int +) + +// TODO(xsw): +// hasWaitingReaders reports whether any goroutine is waiting +// to acquire a read lock on rw. It is defined in the sync package. +func hasWaitingReaders(rw *sync.RWMutex) bool { + panic("todo: syscall.hasWaitingReaders in sync package") +} + +// acquireForkLock acquires a write lock on ForkLock. +// ForkLock is exported and we've promised that during a fork +// we will call ForkLock.Lock, so that no other threads create +// new fds that are not yet close-on-exec before we fork. +// But that forces all fork calls to be serialized, which is bad. +// But we haven't promised that serialization, and it is essentially +// undetectable by other users of ForkLock, which is good. +// Avoid the serialization by ensuring that ForkLock is locked +// at the first fork and unlocked when there are no more forks. +func acquireForkLock() { + forkingLock.Lock() + defer forkingLock.Unlock() + + if forking == 0 { + // There is no current write lock on ForkLock. + ForkLock.Lock() + forking++ + return + } + + // ForkLock is currently locked for writing. + + if hasWaitingReaders(&ForkLock) { + // ForkLock is locked for writing, and at least one + // goroutine is waiting to read from it. + // To avoid lock starvation, allow readers to proceed. + // The simple way to do this is for us to acquire a + // read lock. That will block us until all current + // conceptual write locks are released. + // + // Note that this case is unusual on modern systems + // with O_CLOEXEC and SOCK_CLOEXEC. On those systems + // the standard library should never take a read + // lock on ForkLock. + + forkingLock.Unlock() + + ForkLock.RLock() + ForkLock.RUnlock() + + forkingLock.Lock() + + // Readers got a chance, so now take the write lock. + + if forking == 0 { + ForkLock.Lock() + } + } + + forking++ +} + +// releaseForkLock releases the conceptual write lock on ForkLock +// acquired by acquireForkLock. +func releaseForkLock() { + forkingLock.Lock() + defer forkingLock.Unlock() + + if forking <= 0 { + panic("syscall.releaseForkLock: negative count") + } + + forking-- + + if forking == 0 { + // No more conceptual write locks. + ForkLock.Unlock() + } +} diff --git a/internal/lib/syscall/syscall_linux.go b/internal/lib/syscall/syscall_linux.go index a3e05ff2..17d9fe95 100644 --- a/internal/lib/syscall/syscall_linux.go +++ b/internal/lib/syscall/syscall_linux.go @@ -11,6 +11,108 @@ package syscall +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/syscall" +) + +// ----------------------------------------------------------------------------- + +type WaitStatus uint32 + +// Wait status is 7 bits at bottom, either 0 (exited), +// 0x7F (stopped), or a signal number that caused an exit. +// The 0x80 bit is whether there was a core dump. +// An extra number (exit code, signal causing a stop) +// is in the high bits. At least that's the idea. +// There are various irregularities. For example, the +// "continued" status is 0xFFFF, distinguishing itself +// from stopped via the core dump bit. + +const ( + mask = 0x7F + core = 0x80 + exited = 0x00 + stopped = 0x7F + shift = 8 +) + +func (w WaitStatus) Exited() bool { return w&mask == exited } + +func (w WaitStatus) Signaled() bool { return w&mask != stopped && w&mask != exited } + +func (w WaitStatus) Stopped() bool { return w&0xFF == stopped } + +func (w WaitStatus) Continued() bool { return w == 0xFFFF } + +func (w WaitStatus) CoreDump() bool { return w.Signaled() && w&core != 0 } + +func (w WaitStatus) ExitStatus() int { + if !w.Exited() { + return -1 + } + return int(w>>shift) & 0xFF +} + +func (w WaitStatus) Signal() Signal { + if !w.Signaled() { + return -1 + } + return Signal(w & mask) +} + +func (w WaitStatus) StopSignal() Signal { + if !w.Stopped() { + return -1 + } + return Signal(w>>shift) & 0xFF +} + +/* TODO(xsw): +func (w WaitStatus) TrapCause() int { + if w.StopSignal() != SIGTRAP { + return -1 + } + return int(w>>shift) >> 8 +} +*/ + +func Wait4(pid int, wstatus *WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) { + var status c.Int + wpid, err = wait4(pid, &status, options, rusage) + if wstatus != nil { + *wstatus = WaitStatus(status) + } + return +} + +// ----------------------------------------------------------------------------- + +// int pipe2(int pipefd[2], int flags); +// +//go:linkname pipe2 C.pipe2 +func pipe2(pipefd *[2]c.Int, flags c.Int) c.Int + +func Pipe2(p []int, flags int) error { + if len(p) != 2 { + return Errno(syscall.EINVAL) + } + var pp [2]c.Int + ret := pipe2(&pp, c.Int(flags)) + if ret == 0 { + p[0] = int(pp[0]) + p[1] = int(pp[1]) + return nil + } + return Errno(ret) +} + +// ----------------------------------------------------------------------------- + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { panic("todo: syscall.Faccessat") } + +// -----------------------------------------------------------------------------