mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-22 21:39:01 +00:00
os: implement StartProcess
Signed-off-by: leongross <leon.gross@9elements.com>
This commit is contained in:
@@ -941,6 +941,7 @@ endif
|
|||||||
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
||||||
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
||||||
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
||||||
|
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||||
|
|||||||
@@ -136,12 +136,20 @@ var libMusl = Library{
|
|||||||
"thread/*.c",
|
"thread/*.c",
|
||||||
"time/*.c",
|
"time/*.c",
|
||||||
"unistd/*.c",
|
"unistd/*.c",
|
||||||
|
"process/*.c",
|
||||||
}
|
}
|
||||||
|
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
// These files need to be added to the start for some reason.
|
// These files need to be added to the start for some reason.
|
||||||
globs = append([]string{"thread/arm/*.c"}, globs...)
|
globs = append([]string{"thread/arm/*.c"}, globs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if arch != "aarch64" && arch != "mips" {
|
||||||
|
//aarch64 and mips have no architecture specific code, either they
|
||||||
|
// are not supported or don't need any?
|
||||||
|
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
|
||||||
|
}
|
||||||
|
|
||||||
var sources []string
|
var sources []string
|
||||||
seenSources := map[string]struct{}{}
|
seenSources := map[string]struct{}{}
|
||||||
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
|
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
|
||||||
|
|||||||
+13
-1
@@ -5,6 +5,12 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplementedDir = errors.New("directory setting not implemented")
|
||||||
|
ErrNotImplementedSys = errors.New("sys setting not implemented")
|
||||||
|
ErrNotImplementedFiles = errors.New("files setting not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
type Signal interface {
|
type Signal interface {
|
||||||
String() string
|
String() string
|
||||||
Signal() // to distinguish from other Stringers
|
Signal() // to distinguish from other Stringers
|
||||||
@@ -47,6 +53,10 @@ func (p *ProcessState) Sys() interface{} {
|
|||||||
return nil // TODO
|
return nil // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ProcessState) Exited() bool {
|
||||||
|
return false // TODO
|
||||||
|
}
|
||||||
|
|
||||||
// ExitCode returns the exit code of the exited process, or -1
|
// ExitCode returns the exit code of the exited process, or -1
|
||||||
// if the process hasn't exited or was terminated by a signal.
|
// if the process hasn't exited or was terminated by a signal.
|
||||||
func (p *ProcessState) ExitCode() int {
|
func (p *ProcessState) ExitCode() int {
|
||||||
@@ -57,8 +67,10 @@ type Process struct {
|
|||||||
Pid int
|
Pid int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartProcess starts a new process with the program, arguments and attributes specified by name, argv and attr.
|
||||||
|
// Arguments to the process (os.Args) are passed via argv.
|
||||||
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
|
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) {
|
func (p *Process) Wait() (*ProcessState, error) {
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// 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 linux && !baremetal && !tinygo.wasm
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The only signal values guaranteed to be present in the os package on all
|
||||||
|
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
|
||||||
|
// the process to exit). On Windows, sending os.Interrupt to a process with
|
||||||
|
// os.Process.Signal is not implemented; it will return an error instead of
|
||||||
|
// sending a signal.
|
||||||
|
var (
|
||||||
|
Interrupt Signal = syscall.SIGINT
|
||||||
|
Kill Signal = syscall.SIGKILL
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keep compatible with golang and always succeed and return new proc with pid on Linux.
|
||||||
|
func findProcess(pid int) (*Process, error) {
|
||||||
|
return &Process{Pid: pid}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) release() error {
|
||||||
|
// NOOP for unix.
|
||||||
|
p.Pid = -1
|
||||||
|
// no need for a finalizer anymore
|
||||||
|
runtime.SetFinalizer(p, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls.
|
||||||
|
// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process.
|
||||||
|
// It thereby replaces the newly created process with the specified command and arguments.
|
||||||
|
// Differences to upstream golang implementation (https://cs.opensource.google/go/go/+/master:src/syscall/exec_unix.go;l=143):
|
||||||
|
// * No setting of Process Attributes
|
||||||
|
// * Ignoring Ctty
|
||||||
|
// * No ForkLocking (might be introduced by #4273)
|
||||||
|
// * No parent-child communication via pipes (TODO)
|
||||||
|
// * No waiting for crashes child processes to prohibit zombie process accumulation / Wait status checking (TODO)
|
||||||
|
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
|
||||||
|
if argv == nil {
|
||||||
|
return 0, errors.New("exec: no argv")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(argv) == 0 {
|
||||||
|
return 0, errors.New("exec: no argv")
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr == nil {
|
||||||
|
attr = new(ProcAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := fork()
|
||||||
|
pid = int(p)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// else code runs in child, which then should exec the new process
|
||||||
|
err = execve(argv0, argv, attr.Env)
|
||||||
|
if err != nil {
|
||||||
|
// exec failed
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// 3. TODO: use pipes to communicate back child status
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Golang, the idiomatic way to create a new process is to use the StartProcess function.
|
||||||
|
// Since the Model of operating system processes in tinygo differs from the one in Golang, we need to implement the StartProcess function differently.
|
||||||
|
// The startProcess function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls.
|
||||||
|
// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process.
|
||||||
|
// It thereby replaces the newly created process with the specified command and arguments.
|
||||||
|
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
|
||||||
|
if attr != nil {
|
||||||
|
if attr.Dir != "" {
|
||||||
|
return nil, ErrNotImplementedDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr.Sys != nil {
|
||||||
|
return nil, ErrNotImplementedSys
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(attr.Files) != 0 {
|
||||||
|
return nil, ErrNotImplementedFiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := forkExec(name, argv, attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return findProcess(pid)
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
//go:build linux && !baremetal && !tinygo.wasm
|
||||||
|
|
||||||
|
package os_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
. "os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test the functionality of the forkExec function, which is used to fork and exec a new process.
|
||||||
|
// This test is not run on Windows, as forkExec is not supported on Windows.
|
||||||
|
// This test is not run on Plan 9, as forkExec is not supported on Plan 9.
|
||||||
|
func TestForkExec(t *testing.T) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
t.Logf("skipping test on %s", runtime.GOOS)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{})
|
||||||
|
if !errors.Is(err, nil) {
|
||||||
|
t.Fatalf("forkExec failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc == nil {
|
||||||
|
t.Fatalf("proc is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc.Pid == 0 {
|
||||||
|
t.Fatalf("forkExec failed: new process has pid 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForkExecErrNotExist(t *testing.T) {
|
||||||
|
proc, err := StartProcess("invalid", []string{"invalid"}, &ProcAttr{})
|
||||||
|
if !errors.Is(err, ErrNotExist) {
|
||||||
|
t.Fatalf("wanted ErrNotExist, got %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc != nil {
|
||||||
|
t.Fatalf("wanted nil, got %v\n", proc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForkExecProcDir(t *testing.T) {
|
||||||
|
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Dir: "dir"})
|
||||||
|
if !errors.Is(err, ErrNotImplementedDir) {
|
||||||
|
t.Fatalf("wanted ErrNotImplementedDir, got %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc != nil {
|
||||||
|
t.Fatalf("wanted nil, got %v\n", proc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForkExecProcSys(t *testing.T) {
|
||||||
|
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Sys: &syscall.SysProcAttr{}})
|
||||||
|
if !errors.Is(err, ErrNotImplementedSys) {
|
||||||
|
t.Fatalf("wanted ErrNotImplementedSys, got %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc != nil {
|
||||||
|
t.Fatalf("wanted nil, got %v\n", proc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForkExecProcFiles(t *testing.T) {
|
||||||
|
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Files: []*File{}})
|
||||||
|
if !errors.Is(err, ErrNotImplementedFiles) {
|
||||||
|
t.Fatalf("wanted ErrNotImplementedFiles, got %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc != nil {
|
||||||
|
t.Fatalf("wanted nil, got %v\n", proc)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
var (
|
||||||
|
Interrupt Signal = syscall.SIGINT
|
||||||
|
Kill Signal = syscall.SIGKILL
|
||||||
|
)
|
||||||
|
|
||||||
|
func findProcess(pid int) (*Process, error) {
|
||||||
|
return &Process{Pid: pid}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Process) release() error {
|
||||||
|
p.Pid = -1
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func forkExec(_ string, _ []string, _ *ProcAttr) (pid int, err error) {
|
||||||
|
return 0, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func startProcess(_ string, _ []string, _ *ProcAttr) (proc *Process, err error) {
|
||||||
|
return &Process{Pid: 0}, ErrNotImplemented
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
// 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 aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || wasip2 || windows
|
|
||||||
|
|
||||||
package os
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The only signal values guaranteed to be present in the os package on all
|
|
||||||
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
|
|
||||||
// the process to exit). On Windows, sending os.Interrupt to a process with
|
|
||||||
// os.Process.Signal is not implemented; it will return an error instead of
|
|
||||||
// sending a signal.
|
|
||||||
var (
|
|
||||||
Interrupt Signal = syscall.SIGINT
|
|
||||||
Kill Signal = syscall.SIGKILL
|
|
||||||
)
|
|
||||||
|
|
||||||
// Keep compatible with golang and always succeed and return new proc with pid on Linux.
|
|
||||||
func findProcess(pid int) (*Process, error) {
|
|
||||||
return &Process{Pid: pid}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Process) release() error {
|
|
||||||
// NOOP for unix.
|
|
||||||
p.Pid = -1
|
|
||||||
// no need for a finalizer anymore
|
|
||||||
runtime.SetFinalizer(p, nil)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
//go:build linux && !baremetal && !tinygo.wasm
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fork() (pid int32, err error) {
|
||||||
|
pid = libc_fork()
|
||||||
|
if pid != 0 {
|
||||||
|
if errno := *libc_errno(); errno != 0 {
|
||||||
|
err = syscall.Errno(*libc_errno())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the golang standard library does not expose interfaces for execve and fork, so we define them here the same way via the libc wrapper
|
||||||
|
func execve(pathname string, argv []string, envv []string) error {
|
||||||
|
argv0 := cstring(pathname)
|
||||||
|
|
||||||
|
// transform argv and envv into the format expected by execve
|
||||||
|
argv1 := make([]*byte, len(argv)+1)
|
||||||
|
for i, arg := range argv {
|
||||||
|
argv1[i] = &cstring(arg)[0]
|
||||||
|
}
|
||||||
|
argv1[len(argv)] = nil
|
||||||
|
|
||||||
|
env1 := make([]*byte, len(envv)+1)
|
||||||
|
for i, env := range envv {
|
||||||
|
env1[i] = &cstring(env)[0]
|
||||||
|
}
|
||||||
|
env1[len(envv)] = nil
|
||||||
|
|
||||||
|
ret, _, err := syscall.Syscall(syscall.SYS_EXECVE, uintptr(unsafe.Pointer(&argv0[0])), uintptr(unsafe.Pointer(&argv1[0])), uintptr(unsafe.Pointer(&env1[0])))
|
||||||
|
if int(ret) != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cstring(s string) []byte {
|
||||||
|
data := make([]byte, len(s)+1)
|
||||||
|
copy(data, s)
|
||||||
|
// final byte should be zero from the initial allocation
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
//export fork
|
||||||
|
func libc_fork() int32
|
||||||
|
|
||||||
|
// Internal musl function to get the C errno pointer.
|
||||||
|
//
|
||||||
|
//export __errno_location
|
||||||
|
func libc_errno() *int32
|
||||||
@@ -233,6 +233,11 @@ func (w WaitStatus) Continued() bool { return false }
|
|||||||
func (w WaitStatus) StopSignal() Signal { return 0 }
|
func (w WaitStatus) StopSignal() Signal { return 0 }
|
||||||
func (w WaitStatus) TrapCause() int { return 0 }
|
func (w WaitStatus) TrapCause() int { return 0 }
|
||||||
|
|
||||||
|
// since rusage is quite a big struct and we stub it out anyway no need to define it here
|
||||||
|
func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, err error) {
|
||||||
|
return 0, ENOSYS // TODO
|
||||||
|
}
|
||||||
|
|
||||||
func Getenv(key string) (value string, found bool) {
|
func Getenv(key string) (value string, found bool) {
|
||||||
data := cstring(key)
|
data := cstring(key)
|
||||||
raw := libc_getenv(&data[0])
|
raw := libc_getenv(&data[0])
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build linux || unix
|
||||||
|
|
||||||
package syscall
|
package syscall
|
||||||
|
|
||||||
func Exec(argv0 string, argv []string, envv []string) (err error)
|
func Exec(argv0 string, argv []string, envv []string) (err error)
|
||||||
|
|||||||
Reference in New Issue
Block a user