diff options
Diffstat (limited to 'src/syscall/exec_plan9.go')
-rw-r--r-- | src/syscall/exec_plan9.go | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/src/syscall/exec_plan9.go b/src/syscall/exec_plan9.go new file mode 100644 index 0000000..8f28b5a --- /dev/null +++ b/src/syscall/exec_plan9.go @@ -0,0 +1,609 @@ +// 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 ( + "internal/itoa" + "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] < 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] == 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] >= len(fd) { + 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) + } +} + +// 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.Itoa(p[1]), O_RDWR|O_CLOEXEC) + if e != nil { + Close(p[0]) + Close(p[1]) + return e + } + + Close(p[1]) + p[1] = 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 +} |