c/os: fork/exec

This commit is contained in:
xushiwei
2024-07-12 00:39:16 +08:00
parent aac820a8d5
commit 11682e487e
12 changed files with 1083 additions and 25 deletions

20
_demo/cexec/exec.go Normal file
View File

@@ -0,0 +1,20 @@
package main
import (
"runtime"
"unsafe"
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/c/os"
)
func main() {
ls := c.Str("ls")
args := []*c.Char{ls, c.Str("-l"), nil}
if runtime.GOOS == "windows" {
ls = c.Str("dir")
args = []*c.Char{ls, nil}
}
os.Execvp(ls, unsafe.SliceData(args))
}

26
_demo/sysexec/exec.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"os/exec"
"runtime"
"syscall"
)
func main() {
ls := "ls"
args := []string{"ls", "-l"}
if runtime.GOOS == "windows" {
ls = "dir"
args = nil
}
lspath, _ := exec.LookPath(ls)
if lspath != "" {
ls = lspath
}
err := syscall.Exec(ls, args, nil)
if err != nil {
fmt.Println("syscall.Exec error:", err)
return
}
}

View File

@@ -108,8 +108,8 @@ func Chroot(path *c.Char) c.Int
// -----------------------------------------------------------------------------
//go:linkname Environ C.environ
func Environ() **c.Char
//go:linkname Environ environ
var Environ **c.Char
//go:linkname Getenv C.getenv
func Getenv(name *c.Char) *c.Char
@@ -208,6 +208,38 @@ func Isatty(fd c.Int) c.Int
// -----------------------------------------------------------------------------
// Execl(const char *path, const char *arg0, ..., /*, (char *)0, */)
//
// Execl requires the full path of the program to be provided.
//
//go:linkname Execl C.execl
func Execl(path *c.Char, __llgo_va_list ...any) c.Int
// Execle(const char *path, const char *arg0, ..., /* (char *)0, char *const envp[] */)
//
//go:linkname Execle C.execle
func Execle(path *c.Char, __llgo_va_list ...any) c.Int
// Execlp(const char *file, const char *arg0, ..., /*, (char *)0, */)
//
// Execlp only needs to provide the program name and it will search for the program in the
// paths specified in the PATH environment variable.
//
//go:linkname Execlp C.execlp
func Execlp(file *c.Char, __llgo_va_list ...any) c.Int
//go:linkname Execv C.execv
func Execv(path *c.Char, argv **c.Char) c.Int
//go:linkname Execve C.execve
func Execve(path *c.Char, argv **c.Char, envp **c.Char) c.Int
//go:linkname Execvp C.execvp
func Execvp(file *c.Char, argv **c.Char) c.Int
//go:linkname Fork C.fork
func Fork() c.Int
//go:linkname Kill C.kill
func Kill(pid c.Int, sig c.Int) c.Int

21
c/syscall/syscall.go Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package syscall
const (
LLGoPackage = "decl"
)

View File

@@ -531,7 +531,8 @@ func ignoreName(name string) bool {
func supportedInternal(name string) bool {
return strings.HasPrefix(name, "abi.") || strings.HasPrefix(name, "bytealg.") ||
strings.HasPrefix(name, "oserror.") || strings.HasPrefix(name, "reflectlite.")
strings.HasPrefix(name, "oserror.") || strings.HasPrefix(name, "reflectlite.") ||
strings.HasPrefix(name, "syscall/execenv.")
}
// -----------------------------------------------------------------------------

View File

@@ -673,14 +673,12 @@ func canSkipToBuild(pkgPath string) bool {
type none struct{}
var hasAltPkg = map[string]none{
//"errors": {},
"fmt": {},
"internal/abi": {},
"internal/bytealg": {},
"internal/oserror": {},
"internal/reflectlite": {},
//"io": {},
//"io/fs": {},
"internal/syscall/execenv": {},
"math": {},
"math/cmplx": {},
"reflect": {},

View File

@@ -0,0 +1,20 @@
// Copyright 2020 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 !windows
package execenv
// llgo:skipall
import "syscall"
// Default will return the default environment
// variables based on the process attributes
// provided.
//
// Defaults to syscall.Environ() on all platforms
// other than Windows.
func Default(sys *syscall.SysProcAttr) ([]string, error) {
return syscall.Environ(), nil
}

View File

@@ -0,0 +1,48 @@
// Copyright 2020 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 windows
package execenv
// llgo:skipall
import (
"internal/syscall/windows"
"syscall"
"unsafe"
)
// Default will return the default environment
// variables based on the process attributes
// provided.
//
// If the process attributes contain a token, then
// the environment variables will be sourced from
// the defaults for that user token, otherwise they
// will be sourced from syscall.Environ().
func Default(sys *syscall.SysProcAttr) (env []string, err error) {
if sys == nil || sys.Token == 0 {
return syscall.Environ(), nil
}
var blockp *uint16
err = windows.CreateEnvironmentBlock(&blockp, sys.Token, false)
if err != nil {
return nil, err
}
defer windows.DestroyEnvironmentBlock(blockp)
const size = unsafe.Sizeof(*blockp)
for *blockp != 0 { // environment block ends with empty string
// find NUL terminator
end := unsafe.Add(unsafe.Pointer(blockp), size)
for *(*uint16)(end) != 0 {
end = unsafe.Add(end, size)
}
entry := unsafe.Slice(blockp, (uintptr(end)-uintptr(unsafe.Pointer(blockp)))/2)
env = append(env, syscall.UTF16ToString(entry))
blockp = (*uint16)(unsafe.Add(end, size))
}
return
}

View File

@@ -8,6 +8,8 @@ package os
import (
"syscall"
"github.com/goplus/llgo/internal/lib/internal/syscall/execenv"
)
// The only signal values guaranteed to be present in the os package on all
@@ -21,7 +23,6 @@ var (
)
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
/* TODO(xsw):
// If there is no SysProcAttr (ie. no Chroot or changed
// UID/GID), double-check existence of the directory we want
// to chdir into. We can make the error clearer this way.
@@ -51,16 +52,15 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
pid, h, e := syscall.StartProcess(name, argv, sysattr)
// TODO(xsw):
// Make sure we don't run the finalizers of attr.Files.
runtime.KeepAlive(attr)
// runtime.KeepAlive(attr)
if e != nil {
return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
}
return newProcess(pid, h), nil
*/
panic("todo: os.startProcess")
}
func (p *Process) kill() error {

View File

@@ -0,0 +1,319 @@
// Copyright 2011 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 aix || solaris
// This file handles forkAndExecInChild function for OS using libc syscall like AIX or Solaris.
package syscall
import (
"runtime"
"unsafe"
)
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Setsid bool // Create session.
// Setpgid sets the process group ID of the child to Pgid,
// or, if Pgid == 0, to the new child's process ID.
Setpgid bool
// Setctty sets the controlling terminal of the child to
// file descriptor Ctty. Ctty must be a descriptor number
// in the child process: an index into ProcAttr.Files.
// This is only meaningful if Setsid is true.
Setctty bool
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
// Foreground places the child process group in the foreground.
// This implies Setpgid. The Ctty field must be set to
// the descriptor of the controlling TTY.
// Unlike Setctty, in this case Ctty must be a descriptor
// number in the parent process.
Foreground bool
Pgid int // Child's process group ID if Setpgid.
}
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()
func chdir(path uintptr) (err Errno)
func chroot1(path uintptr) (err Errno)
func closeFD(fd uintptr) (err Errno)
func dup2child(old uintptr, new uintptr) (val uintptr, err Errno)
func execve(path uintptr, argv uintptr, envp uintptr) (err Errno)
func exit(code uintptr)
func fcntl1(fd uintptr, cmd uintptr, arg uintptr) (val uintptr, err Errno)
func forkx(flags uintptr) (pid uintptr, err Errno)
func getpid() (pid uintptr, err Errno)
func ioctl(fd uintptr, req uintptr, arg uintptr) (err Errno)
func setgid(gid uintptr) (err Errno)
func setgroups1(ngid uintptr, gid uintptr) (err Errno)
func setrlimit1(which uintptr, lim unsafe.Pointer) (err Errno)
func setsid() (pid uintptr, err Errno)
func setuid(uid uintptr) (err Errno)
func setpgid(pid uintptr, pgid uintptr) (err Errno)
func write1(fd uintptr, buf uintptr, nbyte uintptr) (n uintptr, err Errno)
// syscall defines this global on our behalf to avoid a build dependency on other platforms
func init() {
execveLibc = execve
}
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// If a dup or exec fails, write the errno error to pipe.
// (Pipe 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.
//
// We call hand-crafted syscalls, implemented in
// ../runtime/syscall_solaris.go, rather than generated libc wrappers
// because we need to avoid lazy-loading the functions (might malloc,
// split the stack, or acquire mutexes). We can't call RawSyscall
// because it's not safe even for BSD-subsystem calls.
//
//go:norace
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
// Declare all variables at top in case any
// declarations require heap allocation (e.g., err1).
var (
r1 uintptr
err1 Errno
nextfd int
i int
pgrp _Pid_t
cred *Credential
ngroups, groups uintptr
)
rlim, rlimOK := origRlimitNofile.Load().(Rlimit)
// 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++
// About to call fork.
// No more allocation or calls of non-assembly functions.
runtime_BeforeFork()
r1, err1 = forkx(0x1) // FORK_NOSIGCHLD
if err1 != 0 {
runtime_AfterFork()
return 0, err1
}
if r1 != 0 {
// parent; return PID
runtime_AfterFork()
return int(r1), 0
}
// Fork succeeded, now in child.
// Session ID
if sys.Setsid {
_, err1 = setsid()
if err1 != 0 {
goto childerror
}
}
// Set process group
if sys.Setpgid || sys.Foreground {
// Place child in process group.
err1 = setpgid(0, uintptr(sys.Pgid))
if err1 != 0 {
goto childerror
}
}
if sys.Foreground {
pgrp = _Pid_t(sys.Pgid)
if pgrp == 0 {
r1, err1 = getpid()
if err1 != 0 {
goto childerror
}
pgrp = _Pid_t(r1)
}
// Place process group in foreground.
err1 = ioctl(uintptr(sys.Ctty), uintptr(TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp)))
if err1 != 0 {
goto childerror
}
}
// Restore the signal mask. We do this after TIOCSPGRP to avoid
// having the kernel send a SIGTTOU signal to the process group.
runtime_AfterForkInChild()
// Chroot
if chroot != nil {
err1 = chroot1(uintptr(unsafe.Pointer(chroot)))
if err1 != 0 {
goto childerror
}
}
// User and groups
if cred = sys.Credential; cred != nil {
ngroups = uintptr(len(cred.Groups))
groups = uintptr(0)
if ngroups > 0 {
groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
}
if !cred.NoSetGroups {
err1 = setgroups1(ngroups, groups)
if err1 != 0 {
goto childerror
}
}
err1 = setgid(uintptr(cred.Gid))
if err1 != 0 {
goto childerror
}
err1 = setuid(uintptr(cred.Uid))
if err1 != 0 {
goto childerror
}
}
// Chdir
if dir != nil {
err1 = chdir(uintptr(unsafe.Pointer(dir)))
if err1 != 0 {
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 {
switch runtime.GOOS {
case "illumos", "solaris":
_, err1 = fcntl1(uintptr(pipe), _F_DUP2FD_CLOEXEC, uintptr(nextfd))
default:
_, err1 = dup2child(uintptr(pipe), uintptr(nextfd))
if err1 != 0 {
goto childerror
}
_, err1 = fcntl1(uintptr(nextfd), F_SETFD, FD_CLOEXEC)
}
if err1 != 0 {
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++
}
switch runtime.GOOS {
case "illumos", "solaris":
_, err1 = fcntl1(uintptr(fd[i]), _F_DUP2FD_CLOEXEC, uintptr(nextfd))
default:
_, err1 = dup2child(uintptr(fd[i]), uintptr(nextfd))
if err1 != 0 {
goto childerror
}
_, err1 = fcntl1(uintptr(nextfd), F_SETFD, FD_CLOEXEC)
}
if err1 != 0 {
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 {
closeFD(uintptr(i))
continue
}
if fd[i] == i {
// dup2(i, i) won't clear close-on-exec flag on Linux,
// probably not elsewhere either.
_, err1 = fcntl1(uintptr(fd[i]), F_SETFD, 0)
if err1 != 0 {
goto childerror
}
continue
}
// The new fd is created NOT close-on-exec,
// which is exactly what we want.
_, err1 = dup2child(uintptr(fd[i]), uintptr(i))
if err1 != 0 {
goto childerror
}
}
// By convention, we don't close-on-exec the fds we are
// started with, so if len(fd) < 3, close 0, 1, 2 as needed.
// Programs that know they inherit fds >= 3 will need
// to set them close-on-exec.
for i = len(fd); i < 3; i++ {
closeFD(uintptr(i))
}
// Detach fd 0 from tty
if sys.Noctty {
err1 = ioctl(0, uintptr(TIOCNOTTY), 0)
if err1 != 0 {
goto childerror
}
}
// Set the controlling TTY to Ctty
if sys.Setctty {
// On AIX, TIOCSCTTY is undefined
if TIOCSCTTY == 0 {
err1 = ENOSYS
goto childerror
}
err1 = ioctl(uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
if err1 != 0 {
goto childerror
}
}
// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
setrlimit1(RLIMIT_NOFILE, unsafe.Pointer(&rlim))
}
// Time to exec.
err1 = execve(
uintptr(unsafe.Pointer(argv0)),
uintptr(unsafe.Pointer(&argv[0])),
uintptr(unsafe.Pointer(&envv[0])))
childerror:
// send error code on pipe
write1(uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
for {
exit(253)
}
}
func ioctlPtr(fd, req uintptr, arg unsafe.Pointer) (err Errno) {
return ioctl(fd, req, uintptr(arg))
}

View File

@@ -0,0 +1,290 @@
// Copyright 2011 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 darwin || (openbsd && !mips64)
package syscall
type SysProcAttr struct {
Chroot string // Chroot.
Credential *Credential // Credential.
Ptrace bool // Enable tracing.
Setsid bool // Create session.
// Setpgid sets the process group ID of the child to Pgid,
// or, if Pgid == 0, to the new child's process ID.
Setpgid bool
// Setctty sets the controlling terminal of the child to
// file descriptor Ctty. Ctty must be a descriptor number
// in the child process: an index into ProcAttr.Files.
// This is only meaningful if Setsid is true.
Setctty bool
Noctty bool // Detach fd 0 from controlling terminal
Ctty int // Controlling TTY fd
// Foreground places the child process group in the foreground.
// This implies Setpgid. The Ctty field must be set to
// the descriptor of the controlling TTY.
// Unlike Setctty, in this case Ctty must be a descriptor
// number in the parent process.
Foreground bool
Pgid int // Child's process group ID if Setpgid.
}
/* TODO(xsw):
// Implemented in runtime package.
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()
*/
// Fork, dup fd onto 0..len(fd), and exec(argv0, argvv, envv) in child.
// If a dup or exec fails, write the errno error to pipe.
// (Pipe 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.
// For the same reason compiler does not race instrument it.
// The calls to rawSyscall are okay because they are assembly
// functions that do not grow the stack.
//
//go:norace
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err1 Errno) {
/* TODO(xsw):
// Declare all variables at top in case any
// declarations require heap allocation (e.g., err1).
var (
r1 uintptr
nextfd int
i int
err error
pgrp _C_int
cred *Credential
ngroups, groups uintptr
)
rlim, rlimOK := origRlimitNofile.Load().(Rlimit)
// 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++
// About to call fork.
// No more allocation or calls of non-assembly functions.
runtime_BeforeFork()
r1, _, err1 = rawSyscall(abi.FuncPCABI0(libc_fork_trampoline), 0, 0, 0)
if err1 != 0 {
runtime_AfterFork()
return 0, err1
}
if r1 != 0 {
// parent; return PID
runtime_AfterFork()
return int(r1), 0
}
// Fork succeeded, now in child.
// Enable tracing if requested.
if sys.Ptrace {
if err = ptrace(PTRACE_TRACEME, 0, 0, 0); err != nil {
err1 = err.(Errno)
goto childerror
}
}
// Session ID
if sys.Setsid {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_setsid_trampoline), 0, 0, 0)
if err1 != 0 {
goto childerror
}
}
// Set process group
if sys.Setpgid || sys.Foreground {
// Place child in process group.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_setpgid_trampoline), 0, uintptr(sys.Pgid), 0)
if err1 != 0 {
goto childerror
}
}
if sys.Foreground {
// This should really be pid_t, however _C_int (aka int32) is
// generally equivalent.
pgrp = _C_int(sys.Pgid)
if pgrp == 0 {
r1, _, err1 = rawSyscall(abi.FuncPCABI0(libc_getpid_trampoline), 0, 0, 0)
if err1 != 0 {
goto childerror
}
pgrp = _C_int(r1)
}
// Place process group in foreground.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_ioctl_trampoline), uintptr(sys.Ctty), uintptr(TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp)))
if err1 != 0 {
goto childerror
}
}
// Restore the signal mask. We do this after TIOCSPGRP to avoid
// having the kernel send a SIGTTOU signal to the process group.
runtime_AfterForkInChild()
// Chroot
if chroot != nil {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_chroot_trampoline), uintptr(unsafe.Pointer(chroot)), 0, 0)
if err1 != 0 {
goto childerror
}
}
// User and groups
if cred = sys.Credential; cred != nil {
ngroups = uintptr(len(cred.Groups))
groups = uintptr(0)
if ngroups > 0 {
groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
}
if !cred.NoSetGroups {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_setgroups_trampoline), ngroups, groups, 0)
if err1 != 0 {
goto childerror
}
}
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_setgid_trampoline), uintptr(cred.Gid), 0, 0)
if err1 != 0 {
goto childerror
}
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_setuid_trampoline), uintptr(cred.Uid), 0, 0)
if err1 != 0 {
goto childerror
}
}
// Chdir
if dir != nil {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_chdir_trampoline), uintptr(unsafe.Pointer(dir)), 0, 0)
if err1 != 0 {
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 {
if runtime.GOOS == "openbsd" {
_, _, err1 = rawSyscall(dupTrampoline, uintptr(pipe), uintptr(nextfd), O_CLOEXEC)
} else {
_, _, err1 = rawSyscall(dupTrampoline, uintptr(pipe), uintptr(nextfd), 0)
if err1 != 0 {
goto childerror
}
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_fcntl_trampoline), uintptr(nextfd), F_SETFD, FD_CLOEXEC)
}
if err1 != 0 {
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++
}
if runtime.GOOS == "openbsd" {
_, _, err1 = rawSyscall(dupTrampoline, uintptr(fd[i]), uintptr(nextfd), O_CLOEXEC)
} else {
_, _, err1 = rawSyscall(dupTrampoline, uintptr(fd[i]), uintptr(nextfd), 0)
if err1 != 0 {
goto childerror
}
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_fcntl_trampoline), uintptr(nextfd), F_SETFD, FD_CLOEXEC)
}
if err1 != 0 {
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(abi.FuncPCABI0(libc_close_trampoline), uintptr(i), 0, 0)
continue
}
if fd[i] == i {
// dup2(i, i) won't clear close-on-exec flag on Linux,
// probably not elsewhere either.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_fcntl_trampoline), uintptr(fd[i]), F_SETFD, 0)
if err1 != 0 {
goto childerror
}
continue
}
// The new fd is created NOT close-on-exec,
// which is exactly what we want.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_dup2_trampoline), uintptr(fd[i]), uintptr(i), 0)
if err1 != 0 {
goto childerror
}
}
// By convention, we don't close-on-exec the fds we are
// started with, so if len(fd) < 3, close 0, 1, 2 as needed.
// Programs that know they inherit fds >= 3 will need
// to set them close-on-exec.
for i = len(fd); i < 3; i++ {
rawSyscall(abi.FuncPCABI0(libc_close_trampoline), uintptr(i), 0, 0)
}
// Detach fd 0 from tty
if sys.Noctty {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_ioctl_trampoline), 0, uintptr(TIOCNOTTY), 0)
if err1 != 0 {
goto childerror
}
}
// Set the controlling TTY to Ctty
if sys.Setctty {
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_ioctl_trampoline), uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
if err1 != 0 {
goto childerror
}
}
// Restore original rlimit.
if rlimOK && rlim.Cur != 0 {
rawSyscall(abi.FuncPCABI0(libc_setrlimit_trampoline), uintptr(RLIMIT_NOFILE), uintptr(unsafe.Pointer(&rlim)), 0)
}
// Time to exec.
_, _, err1 = rawSyscall(abi.FuncPCABI0(libc_execve_trampoline),
uintptr(unsafe.Pointer(argv0)),
uintptr(unsafe.Pointer(&argv[0])),
uintptr(unsafe.Pointer(&envv[0])))
childerror:
// send error code on pipe
rawSyscall(abi.FuncPCABI0(libc_write_trampoline), uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
for {
rawSyscall(abi.FuncPCABI0(libc_exit_trampoline), 253, 0, 0)
}
*/
panic("todo: syscall.forkAndExecInChild")
}

View File

@@ -0,0 +1,283 @@
// 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.
//go:build unix
// Fork, exec, wait, etc.
package syscall
import (
"sync"
)
// ForkLock is used to synchronize creation of new file descriptors
// with fork.
//
// We want the child in a fork/exec sequence to inherit only the
// file descriptors we intend. To do that, we mark all file
// descriptors close-on-exec and then, in the child, explicitly
// unmark the ones we want the exec'ed program to keep.
// Unix doesn't make this easy: there is, in general, no way to
// allocate a new file descriptor close-on-exec. Instead you
// have to allocate the descriptor and then mark it close-on-exec.
// If a fork happens between those two events, the child's exec
// will inherit an unwanted file descriptor.
//
// This lock solves that race: the create new fd/mark close-on-exec
// operation is done holding ForkLock for reading, and the fork itself
// is done holding ForkLock for writing. At least, that's the idea.
// There are some complications.
//
// Some system calls that create new file descriptors can block
// for arbitrarily long times: open on a hung NFS server or named
// pipe, accept on a socket, and so on. We can't reasonably grab
// the lock across those operations.
//
// It is worse to inherit some file descriptors than others.
// If a non-malicious child accidentally inherits an open ordinary file,
// that's not a big deal. On the other hand, if a long-lived child
// accidentally inherits the write end of a pipe, then the reader
// of that pipe will not see EOF until that child exits, potentially
// causing the parent program to hang. This is a common problem
// in threaded C programs that use popen.
//
// Luckily, the file descriptors that are most important not to
// inherit are not the ones that can take an arbitrarily long time
// to create: pipe returns instantly, and the net package uses
// non-blocking I/O to accept on a listening socket.
// The rules for which file descriptor-creating operations use the
// ForkLock are as follows:
//
// - Pipe. Use pipe2 if available. Otherwise, does not block,
// so use ForkLock.
// - Socket. Use SOCK_CLOEXEC if available. Otherwise, does not
// block, so use ForkLock.
// - Open. Use O_CLOEXEC if available. Otherwise, may block,
// so live with the race.
// - Dup. Use F_DUPFD_CLOEXEC or dup3 if available. Otherwise,
// does not block, so use ForkLock.
var ForkLock sync.RWMutex
func CloseOnExec(fd int) {
/* TODO(xsw):
fcntl(fd, F_SETFD, FD_CLOEXEC)
*/
panic("todo: syscall.CloseOnExec")
}
func SetNonblock(fd int, nonblocking bool) (err error) {
/* TODO(xsw):
flag, err := fcntl(fd, F_GETFL, 0)
if err != nil {
return err
}
if nonblocking {
flag |= O_NONBLOCK
} else {
flag &^= O_NONBLOCK
}
_, err = fcntl(fd, F_SETFL, flag)
return err
*/
panic("todo: syscall.SetNonblock")
}
// Credential holds user and group identities to be assumed
// by a child process started by StartProcess.
type Credential struct {
Uid uint32 // User ID.
Gid uint32 // Group ID.
Groups []uint32 // Supplementary group IDs.
NoSetGroups bool // If true, don't set supplementary groups
}
// ProcAttr holds attributes that will be applied to a new process started
// by StartProcess.
type ProcAttr struct {
Dir string // Current working directory.
Env []string // Environment.
Files []uintptr // File descriptors.
Sys *SysProcAttr
}
var zeroProcAttr ProcAttr
var zeroSysProcAttr SysProcAttr
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
/* TODO(xsw):
var p [2]int
var n int
var err1 Errno
var wstatus WaitStatus
if attr == nil {
attr = &zeroProcAttr
}
sys := attr.Sys
if sys == nil {
sys = &zeroSysProcAttr
}
// 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
}
envvp, err := SlicePtrFromStrings(attr.Env)
if err != nil {
return 0, err
}
if (runtime.GOOS == "freebsd" || runtime.GOOS == "dragonfly") && len(argv) > 0 && len(argv[0]) > len(argv0) {
argvp[0] = argv0p
}
var chroot *byte
if sys.Chroot != "" {
chroot, err = BytePtrFromString(sys.Chroot)
if err != nil {
return 0, err
}
}
var dir *byte
if attr.Dir != "" {
dir, err = BytePtrFromString(attr.Dir)
if err != nil {
return 0, err
}
}
// Both Setctty and Foreground use the Ctty field,
// but they give it slightly different meanings.
if sys.Setctty && sys.Foreground {
return 0, errorspkg.New("both Setctty and Foreground set in SysProcAttr")
}
if sys.Setctty && sys.Ctty >= len(attr.Files) {
return 0, errorspkg.New("Setctty set but Ctty not valid in child")
}
acquireForkLock()
// Allocate child status pipe close on exec.
if err = forkExecPipe(p[:]); err != nil {
releaseForkLock()
return 0, err
}
// Kick off child.
pid, err1 = forkAndExecInChild(argv0p, argvp, envvp, chroot, dir, attr, sys, p[1])
if err1 != 0 {
Close(p[0])
Close(p[1])
releaseForkLock()
return 0, Errno(err1)
}
releaseForkLock()
// Read child error status from pipe.
Close(p[1])
for {
n, err = readlen(p[0], (*byte)(unsafe.Pointer(&err1)), int(unsafe.Sizeof(err1)))
if err != EINTR {
break
}
}
Close(p[0])
if err != nil || n != 0 {
if n == int(unsafe.Sizeof(err1)) {
err = Errno(err1)
}
if err == nil {
err = EPIPE
}
// Child failed; wait for it to exit, to make sure
// the zombies don't accumulate.
_, err1 := Wait4(pid, &wstatus, 0, nil)
for err1 == EINTR {
_, err1 = Wait4(pid, &wstatus, 0, nil)
}
return 0, err
}
// Read got EOF, so pipe closed on exec, so exec succeeded.
return pid, nil
*/
panic("todo: syscall.forkExec")
}
// 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)
}
// 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)
return pid, 0, err
}
/* TODO(xsw):
// Implemented in runtime package.
func runtime_BeforeExec()
func runtime_AfterExec()
// execveLibc is non-nil on OS using libc syscall, set to execve in exec_libc.go; this
// avoids a build dependency for other platforms.
var execveLibc func(path uintptr, argv uintptr, envp uintptr) Errno
var execveDarwin func(path *byte, argv **byte, envp **byte) error
var execveOpenBSD func(path *byte, argv **byte, envp **byte) error
*/
// Exec invokes the execve(2) system call.
func Exec(argv0 string, argv []string, envv []string) (err error) {
/* TODO(xsw):
argv0p, err := BytePtrFromString(argv0)
if err != nil {
return err
}
argvp, err := SlicePtrFromStrings(argv)
if err != nil {
return err
}
envvp, err := SlicePtrFromStrings(envv)
if err != nil {
return err
}
runtime_BeforeExec()
rlim, rlimOK := origRlimitNofile.Load().(Rlimit)
if rlimOK && rlim.Cur != 0 {
Setrlimit(RLIMIT_NOFILE, &rlim)
}
var err1 error
if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" || runtime.GOOS == "aix" {
// RawSyscall should never be used on Solaris, illumos, or AIX.
err1 = execveLibc(
uintptr(unsafe.Pointer(argv0p)),
uintptr(unsafe.Pointer(&argvp[0])),
uintptr(unsafe.Pointer(&envvp[0])))
} else if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
// Similarly on Darwin.
err1 = execveDarwin(argv0p, &argvp[0], &envvp[0])
} else if runtime.GOOS == "openbsd" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
// Similarly on OpenBSD.
err1 = execveOpenBSD(argv0p, &argvp[0], &envvp[0])
} else {
_, _, err1 = RawSyscall(SYS_EXECVE,
uintptr(unsafe.Pointer(argv0p)),
uintptr(unsafe.Pointer(&argvp[0])),
uintptr(unsafe.Pointer(&envvp[0])))
}
runtime_AfterExec()
return err1
*/
panic("todo: syscall.Exec")
}