Skip to content

Commit

Permalink
add minified StartProcess implementation (hacky)
Browse files Browse the repository at this point in the history
Signed-off-by: leongross <[email protected]>
  • Loading branch information
leongross committed Jul 9, 2024
1 parent 2d5a8d4 commit e12c2de
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/os/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ type Process struct {
Pid int
}

// Create a lightweight implementation of execve / syscall.StartProcess
// Do not save all the proc info as golang does but make some sanity checks
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
return nil, &PathError{Op: "fork/exec", Path: name, Err: ErrNotImplemented}
return startProcess(name, argv, attr)
}

func (p *Process) Wait() (*ProcessState, error) {
Expand Down Expand Up @@ -92,3 +94,7 @@ func (p *Process) Release() error {
func FindProcess(pid int) (*Process, error) {
return findProcess(pid)
}

func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
return forkExec(argv0, argv, attr)
}
53 changes: 53 additions & 0 deletions src/os/exec_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package os
import (
"runtime"
"syscall"
"unsafe"
)

// The only signal values guaranteed to be present in the os package on all
Expand All @@ -33,3 +34,55 @@ func (p *Process) release() error {
runtime.SetFinalizer(p, nil)
return nil
}

// Combination of fork and exec, careful to be thread safe.

// https://cs.opensource.google/go/go/+/master:src/syscall/exec_unix.go;l=143?q=forkExec&ss=go%2Fgo
// losely inspired by the golang implementation
// This is a hacky fire-and forget implementation without setting any attributes, using pipes or checking for errors
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
// Convert args to C form.
var (
ret uintptr
)

argv0p, err := syscall.BytePtrFromString(argv0)
if err != nil {
return 0, err
}
argvp, err := syscall.SlicePtrFromStrings(argv)
if err != nil {
return 0, err
}
envvp, err := syscall.SlicePtrFromStrings(attr.Env)
if err != nil {
return 0, err
}

// pid, _, _ = syscall.Syscall6(syscall.SYS_FORK, 0, 0, 0, 0, 0, 0)
// 1. fork
ret, _, _ = syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
if ret != 0 {
// parent
return int(ret), nil
} else {
// 2. exec
ret, _, _ = syscall.Syscall6(syscall.SYS_EXECVE, uintptr(unsafe.Pointer(argv0p)), uintptr(unsafe.Pointer(&argvp[0])), uintptr(unsafe.Pointer(&envvp[0])), 0, 0, 0)
if ret != 0 {
// exec failed
syscall.Exit(1)
}
// 3. TODO: use pipes to communicate back child status
return int(ret), nil
}
}

// in regular go this is where the forkExec thingy comes in play
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
pid, err := ForkExec(name, argv, attr)
if err != nil {
return nil, err
}

return findProcess(pid)
}
32 changes: 32 additions & 0 deletions src/syscall/syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package syscall
import (
"errors"
"sync/atomic"
"internal/bytealg"

"github.com/tinygo-org/tinygo/src/internal/bytealg"
)

const (
Expand All @@ -11,6 +14,11 @@ const (
AF_INET6 = 0xa
)

const (
// #define EINVAL 22 /* Invalid argument */
EINVAL = 22
)

func Exit(code int)

type Rlimit struct {
Expand All @@ -26,3 +34,27 @@ var origRlimitNofile atomic.Value // of Rlimit
func Setrlimit(resource int, rlim *Rlimit) error {
return errors.New("Setrlimit not implemented")
}

// ByteSliceFromString returns a NUL-terminated slice of bytes
// containing the text of s. If s contains a NUL byte at any
// location, it returns (nil, [EINVAL]).
// https://cs.opensource.google/go/go/+/master:src/syscall/syscall.go;l=45;drc=94982a07825aec711f11c97283e99e467838d616
func ByteSliceFromString(s string) ([]byte, error) {
if bytealg.IndexByteString(s, 0) != -1 {
return nil, errors.New("contains NUL")
}
a := make([]byte, len(s)+1)
copy(a, s)
return a, nil
}

// BytePtrFromString returns a pointer to a NUL-terminated array of
// bytes containing the text of s. If s contains a NUL byte at any
// location, it returns (nil, [EINVAL]).
func BytePtrFromString(s string) (*byte, error) {
a, err := ByteSliceFromString(s)
if err != nil {
return nil, err
}
return &a[0], nil
}

0 comments on commit e12c2de

Please sign in to comment.