From 1b06948fb0e5541a57be993579173b31c464c227 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Fri, 26 Jul 2024 13:46:21 +0800 Subject: [PATCH] library: os, syscall --- internal/lib/os/wait_wait6.go | 32 ++++++++ internal/lib/os/wait_waitid.go | 55 ++++++++++++++ internal/lib/syscall/forkpipe2.go | 101 ++++++++++++++++++++++++++ internal/lib/syscall/syscall_linux.go | 59 +++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 internal/lib/os/wait_wait6.go create mode 100644 internal/lib/os/wait_waitid.go create mode 100644 internal/lib/syscall/forkpipe2.go 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..c14aba38 --- /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 ( + _ "unsafe" + + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/c/syscall" +) + +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 *[16]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, 0, 0)) + 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/forkpipe2.go b/internal/lib/syscall/forkpipe2.go new file mode 100644 index 00000000..9a93b258 --- /dev/null +++ b/internal/lib/syscall/forkpipe2.go @@ -0,0 +1,101 @@ +// 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" + +// forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file +// descriptors. +func forkExecPipe(p []int) error { + return Pipe2(p, 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..cad706d3 100644 --- a/internal/lib/syscall/syscall_linux.go +++ b/internal/lib/syscall/syscall_linux.go @@ -11,6 +11,65 @@ package 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 Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { panic("todo: syscall.Faccessat") }