1
0
mirror of https://github.com/golang/go synced 2024-10-04 17:21:20 -06:00
go/src/syscall/exec_plan9.go
Richard Miller 258a4c3daf syscall,os,net: don't use ForkLock in plan9
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>
2016-05-06 16:43:07 +00:00

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
}