summaryrefslogtreecommitdiff
path: root/src/pkg/syscall/exec_plan9.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/syscall/exec_plan9.go')
-rw-r--r--src/pkg/syscall/exec_plan9.go260
1 files changed, 188 insertions, 72 deletions
diff --git a/src/pkg/syscall/exec_plan9.go b/src/pkg/syscall/exec_plan9.go
index 7e4e180fa..ebd57f3e3 100644
--- a/src/pkg/syscall/exec_plan9.go
+++ b/src/pkg/syscall/exec_plan9.go
@@ -7,6 +7,7 @@
package syscall
import (
+ "runtime"
"sync"
"unsafe"
)
@@ -60,8 +61,9 @@ import (
var ForkLock sync.RWMutex
-// Convert array of string to array
-// of NUL-terminated byte pointer.
+// StringSlicePtr is deprecated. Use SlicePtrFromStrings instead.
+// If any string contains a NUL byte this function panics instead
+// of returning an error.
func StringSlicePtr(ss []string) []*byte {
bb := make([]*byte, len(ss)+1)
for i := 0; i < len(ss); i++ {
@@ -71,81 +73,81 @@ func StringSlicePtr(ss []string) []*byte {
return bb
}
-// gbit16 reads a 16-bit numeric value from a 9P protocol message stored in b,
-// returning the value and the remaining slice of b.
-func gbit16(b []byte) (uint16, []byte) {
- return uint16(b[0]) | uint16(b[1])<<8, b[2:]
-}
-
-// gstring reads a string from a 9P protocol message stored in b,
-// returning the value as a Go string and the remaining slice of b.
-func gstring(b []byte) (string, []byte) {
- n, b := gbit16(b)
- return string(b[0:n]), b[n:]
+// SlicePtrFromStrings converts a slice of strings to a slice of
+// pointers to NUL-terminated byte slices. 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) {
- result := make([]string, 0, 100)
+ names = make([]string, 0, 100)
var buf [STATMAX]byte
for {
n, e := Read(dirfd, buf[:])
if e != nil {
- return []string{}, e
+ return nil, e
}
if n == 0 {
break
}
-
for i := 0; i < n; {
m, _ := gbit16(buf[i:])
m += 2
if m < STATFIXLEN {
- return []string{}, NewError("malformed stat buffer")
+ return nil, ErrBadStat
}
- name, _ := gstring(buf[i+41:])
- result = append(result, name)
-
+ s, _, ok := gstring(buf[i+41:])
+ if !ok {
+ return nil, ErrBadStat
+ }
+ names = append(names, s)
i += int(m)
}
}
- return []string{}, nil
+ return
}
// readdupdevice returns a list of currently opened fds (excluding stdin, stdout, stderr) from the dup device #d.
// ForkLock should be write locked before calling, so that no new fds would be created while the fd list is being read.
func readdupdevice() (fds []int, err error) {
dupdevfd, err := Open("#d", O_RDONLY)
-
if err != nil {
return
}
defer Close(dupdevfd)
- fileNames, err := readdirnames(dupdevfd)
+ names, err := readdirnames(dupdevfd)
if err != nil {
return
}
- fds = make([]int, 0, len(fileNames)>>1)
- for _, fdstr := range fileNames {
- if l := len(fdstr); l > 2 && fdstr[l-3] == 'c' && fdstr[l-2] == 't' && fdstr[l-1] == 'l' {
+ fds = make([]int, 0, len(names)/2)
+ for _, name := range names {
+ if n := len(name); n > 3 && name[n-3:n] == "ctl" {
continue
}
-
- fd := int(atoi([]byte(fdstr)))
-
- if fd == 0 || fd == 1 || fd == 2 || fd == dupdevfd {
+ fd := int(atoi([]byte(name)))
+ switch fd {
+ case 0, 1, 2, dupdevfd:
continue
}
-
fds = append(fds, fd)
}
-
- return fds[0:len(fds)], nil
+ return
}
var startupFds []int
@@ -196,7 +198,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
r1, _, _ = RawSyscall(SYS_RFORK, uintptr(RFPROC|RFFDG|RFREND|clearenv|rflag), 0, 0)
if r1 != 0 {
- if int(r1) == -1 {
+ if int32(r1) == -1 {
return 0, NewError(errstr())
}
// parent; return PID
@@ -208,7 +210,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
// Close fds we don't need.
for i = 0; i < len(fdsToClose); i++ {
r1, _, _ = RawSyscall(SYS_CLOSE, uintptr(fdsToClose[i]), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -218,7 +220,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
for i = 0; i < len(envv); i++ {
r1, _, _ = RawSyscall(SYS_CREATE, uintptr(unsafe.Pointer(envv[i].name)), uintptr(O_WRONLY), uintptr(0666))
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
@@ -227,13 +229,13 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
r1, _, _ = RawSyscall6(SYS_PWRITE, uintptr(envfd), uintptr(unsafe.Pointer(envv[i].value)), uintptr(envv[i].nvalue),
^uintptr(0), ^uintptr(0), 0)
- if int(r1) == -1 || int(r1) != envv[i].nvalue {
+ if int32(r1) == -1 || int(r1) != envv[i].nvalue {
goto childerror
}
r1, _, _ = RawSyscall(SYS_CLOSE, uintptr(envfd), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -242,7 +244,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
// Chdir
if dir != nil {
r1, _, _ = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
}
@@ -252,7 +254,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
nextfd = int(len(fd))
if pipe < nextfd {
r1, _, _ = RawSyscall(SYS_DUP, uintptr(pipe), uintptr(nextfd), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
pipe = nextfd
@@ -261,7 +263,7 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
for i = 0; i < len(fd); i++ {
if fd[i] >= 0 && fd[i] < int(i) {
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(nextfd), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
@@ -282,12 +284,17 @@ func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, at
if fd[i] == int(i) {
continue
}
-
r1, _, _ = RawSyscall(SYS_DUP, uintptr(fd[i]), uintptr(i), 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
goto childerror
}
- RawSyscall(SYS_CLOSE, uintptr(fd[i]), 0, 0)
+ }
+
+ // 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.
@@ -374,12 +381,21 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
p[1] = -1
// Convert args to C form.
- argv0p := StringBytePtr(argv0)
- argvp := StringSlicePtr(argv)
+ argv0p, err := BytePtrFromString(argv0)
+ if err != nil {
+ return 0, err
+ }
+ argvp, err := SlicePtrFromStrings(argv)
+ if err != nil {
+ return 0, err
+ }
var dir *byte
if attr.Dir != "" {
- dir = StringBytePtr(attr.Dir)
+ dir, err = BytePtrFromString(attr.Dir)
+ if err != nil {
+ return 0, err
+ }
}
var envvParsed []envItem
if attr.Env != nil {
@@ -390,7 +406,13 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
i++
}
- envvParsed = append(envvParsed, envItem{StringBytePtr("/env/" + v[:i]), StringBytePtr(v[i+1:]), len(v) - 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})
}
}
@@ -400,44 +422,37 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
// get a list of open fds, excluding stdin,stdout and stderr that need to be closed in the child.
// no new fds can be created while we hold the ForkLock for writing.
openFds, e := readdupdevice()
-
if e != nil {
ForkLock.Unlock()
return 0, e
}
fdsToClose := make([]int, 0, len(openFds))
- // exclude fds opened from startup from the list of fds to be closed.
for _, fd := range openFds {
- isReserved := false
- for _, reservedFd := range startupFds {
- if fd == reservedFd {
- isReserved = true
+ doClose := true
+
+ // exclude files opened at startup.
+ for _, sfd := range startupFds {
+ if fd == sfd {
+ doClose = false
break
}
}
- if !isReserved {
- fdsToClose = append(fdsToClose, fd)
- }
- }
-
- // exclude fds requested by the caller from the list of fds to be closed.
- for _, fd := range openFds {
- isReserved := false
- for _, reservedFd := range attr.Files {
- if fd == int(reservedFd) {
- isReserved = true
+ // exclude files explicitly requested by the caller.
+ for _, rfd := range attr.Files {
+ if fd == int(rfd) {
+ doClose = false
break
}
}
- if !isReserved {
+ if doClose {
fdsToClose = append(fdsToClose, fd)
}
}
- // Allocate child status pipe close on exec.
+ // Allocate child status pipe close on exec.
e = cexecPipe(p[:])
if e != nil {
@@ -480,14 +495,75 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
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 forkExec(argv0, argv, attr)
+ 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 = forkExec(argv0, argv, attr)
+ pid, err = startProcess(argv0, argv, attr)
return pid, 0, err
}
@@ -495,7 +571,7 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
func Exec(argv0 string, argv []string, envv []string) (err error) {
if envv != nil {
r1, _, _ := RawSyscall(SYS_RFORK, RFCENVG, 0, 0)
- if int(r1) == -1 {
+ if int32(r1) == -1 {
return NewError(errstr())
}
@@ -519,10 +595,50 @@ func Exec(argv0 string, argv []string, envv []string) (err error) {
}
}
+ 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(StringBytePtr(argv0))),
- uintptr(unsafe.Pointer(&StringSlicePtr(argv)[0])),
+ 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
+}