mirror of
https://github.com/golang/go
synced 2024-10-04 17:21:20 -06:00
258a4c3daf
This is the follow-on to CL 22610: now that it's the child instead of the parent which lists unwanted fds to close in syscall.StartProcess, plan9 no longer needs the ForkLock to protect the list from changing. The readdupdevice function is also now unused and can be removed. Change-Id: I904c8bbf5dbaa7022b0f1a1de0862cd3064ca8c7 Reviewed-on: https://go-review.googlesource.com/22842 Reviewed-by: David du Colombier <0intro@gmail.com> Run-TryBot: David du Colombier <0intro@gmail.com> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
609 lines
13 KiB
Go
609 lines
13 KiB
Go
// Copyright 2009 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.
|
|
|
|
// Fork, exec, wait, etc.
|
|
|
|
package syscall
|
|
|
|
import (
|
|
"runtime"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
// ForkLock is not used on plan9.
|
|
var ForkLock sync.RWMutex
|
|
|
|
// gstringb reads a non-empty string from b, prefixed with a 16-bit length in little-endian order.
|
|
// It returns the string as a byte slice, or nil if b is too short to contain the length or
|
|
// the full string.
|
|
//go:nosplit
|
|
func gstringb(b []byte) []byte {
|
|
if len(b) < 2 {
|
|
return nil
|
|
}
|
|
n, b := gbit16(b)
|
|
if int(n) > len(b) {
|
|
return nil
|
|
}
|
|
return b[:n]
|
|
}
|
|
|
|
// Offset of the name field in a 9P directory entry - see UnmarshalDir() in dir_plan9.go
|
|
const nameOffset = 39
|
|
|
|
// gdirname returns the first filename from a buffer of directory entries,
|
|
// and a slice containing the remaining directory entries.
|
|
// If the buffer doesn't start with a valid directory entry, the returned name is nil.
|
|
//go:nosplit
|
|
func gdirname(buf []byte) (name []byte, rest []byte) {
|
|
if len(buf) < 2 {
|
|
return
|
|
}
|
|
size, buf := gbit16(buf)
|
|
if size < STATFIXLEN || int(size) > len(buf) {
|
|
return
|
|
}
|
|
name = gstringb(buf[nameOffset:size])
|
|
rest = buf[size:]
|
|
return
|
|
}
|
|
|
|
// StringSlicePtr converts a slice of strings to a slice of pointers
|
|
// to NUL-terminated byte arrays. If any string contains a NUL byte
|
|
// this function panics instead of returning an error.
|
|
//
|
|
// Deprecated: Use SlicePtrFromStrings instead.
|
|
func StringSlicePtr(ss []string) []*byte {
|
|
bb := make([]*byte, len(ss)+1)
|
|
for i := 0; i < len(ss); i++ {
|
|
bb[i] = StringBytePtr(ss[i])
|
|
}
|
|
bb[len(ss)] = nil
|
|
return bb
|
|
}
|
|
|
|
// SlicePtrFromStrings converts a slice of strings to a slice of
|
|
// pointers to NUL-terminated byte arrays. If any string contains
|
|
// a NUL byte, it returns (nil, EINVAL).
|
|
func SlicePtrFromStrings(ss []string) ([]*byte, error) {
|
|
var err error
|
|
bb := make([]*byte, len(ss)+1)
|
|
for i := 0; i < len(ss); i++ {
|
|
bb[i], err = BytePtrFromString(ss[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
bb[len(ss)] = nil
|
|
return bb, nil
|
|
}
|
|
|
|
// readdirnames returns the names of files inside the directory represented by dirfd.
|
|
func readdirnames(dirfd int) (names []string, err error) {
|
|
names = make([]string, 0, 100)
|
|
var buf [STATMAX]byte
|
|
|
|
for {
|
|
n, e := Read(dirfd, buf[:])
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
if n == 0 {
|
|
break
|
|
}
|
|
for b := buf[:n]; len(b) > 0; {
|
|
var s []byte
|
|
s, b = gdirname(b)
|
|
if s == nil {
|
|
return nil, ErrBadStat
|
|
}
|
|
names = append(names, string(s))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// name of the directory containing names and control files for all open file descriptors
|
|
var dupdev, _ = BytePtrFromString("#d")
|
|
|
|
// forkAndExecInChild forks the process, calling dup onto 0..len(fd)
|
|
// and finally invoking exec(argv0, argvv, envv) in the child.
|
|
// If a dup or exec fails, it writes the error string to pipe.
|
|
// (The pipe write end is close-on-exec so if exec succeeds, it will be closed.)
|
|
//
|
|
// In the child, this function must not acquire any locks, because
|
|
// they might have been locked at the time of the fork. This means
|
|
// no rescheduling, no malloc calls, and no new stack segments.
|
|
// The calls to RawSyscall are okay because they are assembly
|
|
// functions that do not grow the stack.
|
|
//go:norace
|
|
func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, attr *ProcAttr, pipe int, rflag int) (pid int, err error) {
|
|
// Declare all variables at top in case any
|
|
// declarations require heap allocation (e.g., errbuf).
|
|
var (
|
|
r1 uintptr
|
|
nextfd int
|
|
i int
|
|
clearenv int
|
|
envfd int
|
|
errbuf [ERRMAX]byte
|
|
statbuf [STATMAX]byte
|
|
dupdevfd int
|
|
)
|
|
|
|
// Guard against side effects of shuffling fds below.
|
|
// Make sure that nextfd is beyond any currently open files so
|
|
// that we can't run the risk of overwriting any of them.
|
|
fd := make([]int, len(attr.Files))
|
|
nextfd = len(attr.Files)
|
|
for i, ufd := range attr.Files {
|
|
if nextfd < int(ufd) {
|
|
nextfd = int(ufd)
|
|
}
|
|
fd[i] = int(ufd)
|
|
}
|
|
nextfd++
|
|
|
|
if envv != nil {
|
|
clearenv = RFCENVG
|
|
}
|
|
|
|
// About to call fork.
|
|
// No more allocation or calls of non-assembly functions.
|
|
r1, _, _ = RawSyscall(SYS_RFORK, uintptr(RFPROC|RFFDG|RFREND|clearenv|rflag), 0, 0)
|
|
|
|
if r1 != 0 {
|
|
if int32(r1) == -1 {
|
|
return 0, NewError(errstr())
|
|
}
|
|
// parent; return PID
|
|
return int(r1), nil
|
|
}
|
|
|
|
// Fork succeeded, now in child.
|
|
|
|
// Close fds we don't need.
|
|
r1, _, _ = RawSyscall(SYS_OPEN, uintptr(unsafe.Pointer(dupdev)), uintptr(O_RDONLY), 0)
|
|
dupdevfd = int(r1)
|
|
if dupdevfd == -1 {
|
|
goto childerror
|
|
}
|
|
dirloop:
|
|
for {
|
|
r1, _, _ = RawSyscall6(SYS_PREAD, uintptr(dupdevfd), uintptr(unsafe.Pointer(&statbuf[0])), uintptr(len(statbuf)), ^uintptr(0), ^uintptr(0), 0)
|
|
n := int(r1)
|
|
switch n {
|
|
case -1:
|
|
goto childerror
|
|
case 0:
|
|
break dirloop
|
|
}
|
|
for b := statbuf[:n]; len(b) > 0; {
|
|
var s []byte
|
|
s, b = gdirname(b)
|
|
if s == nil {
|
|
copy(errbuf[:], ErrBadStat.Error())
|
|
goto childerror1
|
|
}
|
|
if s[len(s)-1] == 'l' {
|
|
// control file for descriptor <N> is named <N>ctl
|
|
continue
|
|
}
|
|
closeFdExcept(int(atoi(s)), pipe, dupdevfd, fd)
|
|
}
|
|
}
|
|
RawSyscall(SYS_CLOSE, uintptr(dupdevfd), 0, 0)
|
|
|
|
// Write new environment variables.
|
|
if envv != nil {
|
|
for i = 0; i < len(envv); i++ {
|
|
r1, _, _ = RawSyscall(SYS_CREATE, uintptr(unsafe.Pointer(envv[i].name)), uintptr(O_WRONLY), uintptr(0666))
|
|
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
|
|
envfd = int(r1)
|
|
|
|
r1, _, _ = RawSyscall6(SYS_PWRITE, uintptr(envfd), uintptr(unsafe.Pointer(envv[i].value)), uintptr(envv[i].nvalue),
|
|
^uintptr(0), ^uintptr(0), 0)
|
|
|
|
if int32(r1) == -1 || int(r1) != envv[i].nvalue {
|
|
goto childerror
|
|
}
|
|
|
|
r1, _, _ = RawSyscall(SYS_CLOSE, uintptr(envfd), 0, 0)
|
|
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
}
|
|
}
|
|
|
|
// Chdir
|
|
if dir != nil {
|
|
r1, _, _ = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
}
|
|
|
|
// Pass 1: look for fd[i] < i and move those up above len(fd)
|
|
// so that pass 2 won't stomp on an fd it needs later.
|
|
if pipe < nextfd {
|
|
r1, _, _ = RawSyscall(SYS_DUP, uintptr(pipe), uintptr(nextfd), 0)
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
pipe = nextfd
|
|
nextfd++
|
|
}
|
|
for i = 0; i < len(fd); i++ {
|
|
if fd[i] >= 0 && fd[i] < int(i) {
|
|
if nextfd == pipe { // don't stomp on pipe
|
|
nextfd++
|
|
}
|
|
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(nextfd), 0)
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
|
|
fd[i] = nextfd
|
|
nextfd++
|
|
}
|
|
}
|
|
|
|
// Pass 2: dup fd[i] down onto i.
|
|
for i = 0; i < len(fd); i++ {
|
|
if fd[i] == -1 {
|
|
RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
|
|
continue
|
|
}
|
|
if fd[i] == int(i) {
|
|
continue
|
|
}
|
|
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(i), 0)
|
|
if int32(r1) == -1 {
|
|
goto childerror
|
|
}
|
|
}
|
|
|
|
// Pass 3: close fd[i] if it was moved in the previous pass.
|
|
for i = 0; i < len(fd); i++ {
|
|
if fd[i] >= 0 && fd[i] != int(i) {
|
|
RawSyscall(SYS_CLOSE, uintptr(fd[i]), 0, 0)
|
|
}
|
|
}
|
|
|
|
// Time to exec.
|
|
r1, _, _ = RawSyscall(SYS_EXEC,
|
|
uintptr(unsafe.Pointer(argv0)),
|
|
uintptr(unsafe.Pointer(&argv[0])), 0)
|
|
|
|
childerror:
|
|
// send error string on pipe
|
|
RawSyscall(SYS_ERRSTR, uintptr(unsafe.Pointer(&errbuf[0])), uintptr(len(errbuf)), 0)
|
|
childerror1:
|
|
errbuf[len(errbuf)-1] = 0
|
|
i = 0
|
|
for i < len(errbuf) && errbuf[i] != 0 {
|
|
i++
|
|
}
|
|
|
|
RawSyscall6(SYS_PWRITE, uintptr(pipe), uintptr(unsafe.Pointer(&errbuf[0])), uintptr(i),
|
|
^uintptr(0), ^uintptr(0), 0)
|
|
|
|
for {
|
|
RawSyscall(SYS_EXITS, 0, 0, 0)
|
|
}
|
|
|
|
// Calling panic is not actually safe,
|
|
// but the for loop above won't break
|
|
// and this shuts up the compiler.
|
|
panic("unreached")
|
|
}
|
|
|
|
// close the numbered file descriptor, unless it is fd1, fd2, or a member of fds.
|
|
//go:nosplit
|
|
func closeFdExcept(n int, fd1 int, fd2 int, fds []int) {
|
|
if n == fd1 || n == fd2 {
|
|
return
|
|
}
|
|
for _, fd := range fds {
|
|
if n == fd {
|
|
return
|
|
}
|
|
}
|
|
RawSyscall(SYS_CLOSE, uintptr(n), 0, 0)
|
|
}
|
|
|
|
func cexecPipe(p []int) error {
|
|
e := Pipe(p)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
fd, e := Open("#d/"+itoa(p[1]), O_CLOEXEC)
|
|
if e != nil {
|
|
Close(p[0])
|
|
Close(p[1])
|
|
return e
|
|
}
|
|
|
|
Close(fd)
|
|
return nil
|
|
}
|
|
|
|
type envItem struct {
|
|
name *byte
|
|
value *byte
|
|
nvalue int
|
|
}
|
|
|
|
type ProcAttr struct {
|
|
Dir string // Current working directory.
|
|
Env []string // Environment.
|
|
Files []uintptr // File descriptors.
|
|
Sys *SysProcAttr
|
|
}
|
|
|
|
type SysProcAttr struct {
|
|
Rfork int // additional flags to pass to rfork
|
|
}
|
|
|
|
var zeroProcAttr ProcAttr
|
|
var zeroSysProcAttr SysProcAttr
|
|
|
|
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
|
var (
|
|
p [2]int
|
|
n int
|
|
errbuf [ERRMAX]byte
|
|
wmsg Waitmsg
|
|
)
|
|
|
|
if attr == nil {
|
|
attr = &zeroProcAttr
|
|
}
|
|
sys := attr.Sys
|
|
if sys == nil {
|
|
sys = &zeroSysProcAttr
|
|
}
|
|
|
|
p[0] = -1
|
|
p[1] = -1
|
|
|
|
// Convert args to C form.
|
|
argv0p, err := BytePtrFromString(argv0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
argvp, err := SlicePtrFromStrings(argv)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
destDir := attr.Dir
|
|
if destDir == "" {
|
|
wdmu.Lock()
|
|
destDir = wdStr
|
|
wdmu.Unlock()
|
|
}
|
|
var dir *byte
|
|
if destDir != "" {
|
|
dir, err = BytePtrFromString(destDir)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
var envvParsed []envItem
|
|
if attr.Env != nil {
|
|
envvParsed = make([]envItem, 0, len(attr.Env))
|
|
for _, v := range attr.Env {
|
|
i := 0
|
|
for i < len(v) && v[i] != '=' {
|
|
i++
|
|
}
|
|
|
|
envname, err := BytePtrFromString("/env/" + v[:i])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
envvalue := make([]byte, len(v)-i)
|
|
copy(envvalue, v[i+1:])
|
|
envvParsed = append(envvParsed, envItem{envname, &envvalue[0], len(v) - i})
|
|
}
|
|
}
|
|
|
|
// Allocate child status pipe close on exec.
|
|
e := cexecPipe(p[:])
|
|
|
|
if e != nil {
|
|
return 0, e
|
|
}
|
|
|
|
// Kick off child.
|
|
pid, err = forkAndExecInChild(argv0p, argvp, envvParsed, dir, attr, p[1], sys.Rfork)
|
|
|
|
if err != nil {
|
|
if p[0] >= 0 {
|
|
Close(p[0])
|
|
Close(p[1])
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
// Read child error status from pipe.
|
|
Close(p[1])
|
|
n, err = Read(p[0], errbuf[:])
|
|
Close(p[0])
|
|
|
|
if err != nil || n != 0 {
|
|
if n > 0 {
|
|
err = NewError(string(errbuf[:n]))
|
|
} else if err == nil {
|
|
err = NewError("failed to read exec status")
|
|
}
|
|
|
|
// Child failed; wait for it to exit, to make sure
|
|
// the zombies don't accumulate.
|
|
for wmsg.Pid != pid {
|
|
Await(&wmsg)
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
// Read got EOF, so pipe closed on exec, so exec succeeded.
|
|
return pid, nil
|
|
}
|
|
|
|
type waitErr struct {
|
|
Waitmsg
|
|
err error
|
|
}
|
|
|
|
var procs struct {
|
|
sync.Mutex
|
|
waits map[int]chan *waitErr
|
|
}
|
|
|
|
// startProcess starts a new goroutine, tied to the OS
|
|
// thread, which runs the process and subsequently waits
|
|
// for it to finish, communicating the process stats back
|
|
// to any goroutines that may have been waiting on it.
|
|
//
|
|
// Such a dedicated goroutine is needed because on
|
|
// Plan 9, only the parent thread can wait for a child,
|
|
// whereas goroutines tend to jump OS threads (e.g.,
|
|
// between starting a process and running Wait(), the
|
|
// goroutine may have been rescheduled).
|
|
func startProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
|
type forkRet struct {
|
|
pid int
|
|
err error
|
|
}
|
|
|
|
forkc := make(chan forkRet, 1)
|
|
go func() {
|
|
runtime.LockOSThread()
|
|
var ret forkRet
|
|
|
|
ret.pid, ret.err = forkExec(argv0, argv, attr)
|
|
// If fork fails there is nothing to wait for.
|
|
if ret.err != nil || ret.pid == 0 {
|
|
forkc <- ret
|
|
return
|
|
}
|
|
|
|
waitc := make(chan *waitErr, 1)
|
|
|
|
// Mark that the process is running.
|
|
procs.Lock()
|
|
if procs.waits == nil {
|
|
procs.waits = make(map[int]chan *waitErr)
|
|
}
|
|
procs.waits[ret.pid] = waitc
|
|
procs.Unlock()
|
|
|
|
forkc <- ret
|
|
|
|
var w waitErr
|
|
for w.err == nil && w.Pid != ret.pid {
|
|
w.err = Await(&w.Waitmsg)
|
|
}
|
|
waitc <- &w
|
|
close(waitc)
|
|
}()
|
|
ret := <-forkc
|
|
return ret.pid, ret.err
|
|
}
|
|
|
|
// Combination of fork and exec, careful to be thread safe.
|
|
func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
|
return startProcess(argv0, argv, attr)
|
|
}
|
|
|
|
// StartProcess wraps ForkExec for package os.
|
|
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
|
|
pid, err = startProcess(argv0, argv, attr)
|
|
return pid, 0, err
|
|
}
|
|
|
|
// Ordinary exec.
|
|
func Exec(argv0 string, argv []string, envv []string) (err error) {
|
|
if envv != nil {
|
|
r1, _, _ := RawSyscall(SYS_RFORK, RFCENVG, 0, 0)
|
|
if int32(r1) == -1 {
|
|
return NewError(errstr())
|
|
}
|
|
|
|
for _, v := range envv {
|
|
i := 0
|
|
for i < len(v) && v[i] != '=' {
|
|
i++
|
|
}
|
|
|
|
fd, e := Create("/env/"+v[:i], O_WRONLY, 0666)
|
|
if e != nil {
|
|
return e
|
|
}
|
|
|
|
_, e = Write(fd, []byte(v[i+1:]))
|
|
if e != nil {
|
|
Close(fd)
|
|
return e
|
|
}
|
|
Close(fd)
|
|
}
|
|
}
|
|
|
|
argv0p, err := BytePtrFromString(argv0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
argvp, err := SlicePtrFromStrings(argv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, _, e1 := Syscall(SYS_EXEC,
|
|
uintptr(unsafe.Pointer(argv0p)),
|
|
uintptr(unsafe.Pointer(&argvp[0])),
|
|
0)
|
|
|
|
return e1
|
|
}
|
|
|
|
// WaitProcess waits until the pid of a
|
|
// running process is found in the queue of
|
|
// wait messages. It is used in conjunction
|
|
// with ForkExec/StartProcess to wait for a
|
|
// running process to exit.
|
|
func WaitProcess(pid int, w *Waitmsg) (err error) {
|
|
procs.Lock()
|
|
ch := procs.waits[pid]
|
|
procs.Unlock()
|
|
|
|
var wmsg *waitErr
|
|
if ch != nil {
|
|
wmsg = <-ch
|
|
procs.Lock()
|
|
if procs.waits[pid] == ch {
|
|
delete(procs.waits, pid)
|
|
}
|
|
procs.Unlock()
|
|
}
|
|
if wmsg == nil {
|
|
// ch was missing or ch is closed
|
|
return NewError("process not found")
|
|
}
|
|
if wmsg.err != nil {
|
|
return wmsg.err
|
|
}
|
|
if w != nil {
|
|
*w = wmsg.Waitmsg
|
|
}
|
|
return nil
|
|
}
|