-
Notifications
You must be signed in to change notification settings - Fork 955
os/StartProcess #4377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
os/StartProcess #4377
Changes from 23 commits
4cecb75
53bedeb
8fd1ba4
1faa457
8494457
c9703df
b33e04f
44bc700
c037cea
e30e603
f975bbf
7f7559c
fb71295
6c6f254
fdf44d8
be48fa5
9b8dd29
6d74b9d
61267ad
70bfa96
5878a4b
706477a
664c581
94e8652
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
leongross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: with this check, the previous check is unnecessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this was left behind and got merged 🙂 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it wasn't a blocker for me. It's not a bug, just code that could have been cleaner. |
||
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 { | ||
leongross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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{}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also add a negative test, to make sure that running Can you also add some tests that verify that unsupported features like |
||
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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm | ||
|
||
package os | ||
leongross marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
} |
This file was deleted.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file needs to be removed. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//go:build linux && !baremetal && !tinygo.wasm | ||
|
||
// arm64 does not have a fork syscall, so ignore it for now | ||
// TODO: add support for arm64 with clone or use musl implementation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment needs to be updated or removed since it is outdated. |
||
|
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
//go:build linux && !baremetal && !darwin && !tinygo.wasm && aarch64 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this file can be removed entirely now, since these things are implemented in osexec.go? |
||
|
||
// arm64 does not have a fork syscall, so ignore it for now | ||
// TODO: add support for arm64 with clone or use musl implementation | ||
|
||
package os | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
func fork() (pid int, err error) { | ||
return 0, errors.New("fork not supported on aarch64") | ||
} | ||
|
||
func execve(pathname string, argv []string, envv []string) (err error) { | ||
return 0, errors.New("execve not supported on aarch64") | ||
} |
Uh oh!
There was an error while loading. Please reload this page.