Skip to content

add os.StartProcess #4323

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ TEST_PACKAGES_FAST = \
net/http/internal/ascii \
net/mail \
os \
os/signal \
path \
reflect \
sync \
Expand Down
6 changes: 5 additions & 1 deletion src/os/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ func (p *ProcessState) Sys() interface{} {
return nil // TODO
}

func (p *ProcessState) Exited() bool {
return false // TODO
}

// ExitCode returns the exit code of the exited process, or -1
// if the process hasn't exited or was terminated by a signal.
func (p *ProcessState) ExitCode() int {
Expand All @@ -58,7 +62,7 @@ type Process struct {
}

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
110 changes: 110 additions & 0 deletions src/os/exec_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 posix || aix || linux || dragonfly || freebsd || (js && wasm) || netbsd || openbsd || solaris || wasip1
// +build posix aix linux dragonfly freebsd js,wasm netbsd openbsd solaris wasip1

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) {
var (
ret uintptr
)

if len(argv) == 0 {
return 0, errors.New("exec: no argv")
}

if attr == nil {
attr = new(ProcAttr)
}

argv0p, err := syscall.BytePtrFromString(argv0)
if err != nil {
return 0, err
}
argvp, err := syscall.SlicePtrFromStrings(argv)
if err != nil {
return 0, err
}
envp, err := syscall.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
}

ret = syscall.Fork()
if int(ret) < 0 {
return 0, errors.New("fork failed")
}

if int(ret) != 0 {
// if fd == 0 code runs in parent
return int(ret), nil
} else {
// else code runs in child, which then should exec the new process
ret = syscall.Execve(argv0, argv, envp)
if int(ret) != 0 {
// exec failed
return int(ret), errors.New("exec failed")
}
// 3. TODO: use pipes to communicate back child status
return int(ret), 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) {
pid, err := forkExec(name, argv, attr)
if err != nil {
return nil, err
}

return findProcess(pid)
}
29 changes: 29 additions & 0 deletions src/os/exec_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package os_test

import (
. "os"
"runtime"
"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("echo", []string{"echo", "hello", "world"}, &ProcAttr{})
if err != nil {
t.Fatalf("forkExec failed: %v", err)
return
}

if proc.Pid == 0 {
t.Fatalf("forkExec failed: new process has pid 0")
}

t.Logf("forkExec succeeded: new process has pid %d", proc)
}
21 changes: 21 additions & 0 deletions src/os/exec_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris
// +build !aix,!android,!freebsd,!linux,!netbsd,!openbsd,!plan9,!solaris

package os

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
}
35 changes: 0 additions & 35 deletions src/os/exec_posix.go

This file was deleted.

54 changes: 54 additions & 0 deletions src/os/signal/signal_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2012 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 !plan9 && !windows && !js && !wasm && !wasip1 && !wasip2
// +build !plan9,!windows,!js,!wasm,!wasip1,!wasip2

package signal

import (
"os"
"syscall"
)

const (
numSig = 65 // max across all systems
)

type Signal interface {
String() string
Signal() // to distinguish from other Stringers
}

// Defined by the runtime package and used by syscall/sigqueue.go.
func signal_disable(uint32)
func signal_enable(uint32)
func signal_ignore(uint32)
func signal_ignored(uint32) bool
func signal_recv() uint32

func signum(sig os.Signal) int {
switch sig := sig.(type) {
case syscall.Signal:
i := int(sig)
if i < 0 || i >= numSig {
return -1
}
return i
default:
return -1
}
}

func enableSignal(sig int) {
signal_enable(uint32(sig))
}

func disableSignal(sig int) {
signal_disable(uint32(sig))
}

func ignoreSignal(sig int) {
signal_ignore(uint32(sig))
}
30 changes: 30 additions & 0 deletions src/os/signal/signal_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build !plan9 && !windows && !js && !wasm && !wasip1 && !wasip2
// +build !plan9,!windows,!js,!wasm,!wasip1,!wasip2

package signal

import (
"runtime"
"testing"
)

// The file sigqueue.go contains preliminary stubs for signal handling.
// Since tinygo ultimately lacks support for signals, these stubs are
// placeholders for future work.
// There might be userland applications that rely on these functions
// from the upstream go package os/signal that we want to enable
// building and linking.

func TestSignalHandling(t *testing.T) {
if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" {
t.Skip()
}

// This test is a placeholder for future work.
// It is here to ensure that the stubs in sigqueue.go
// are correctly linked and can be called from userland
// applications.
enableSignal(0)
disableSignal(0)
ignoreSignal(0)
}
26 changes: 26 additions & 0 deletions src/runtime/sigqueue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 !plan9
// +build !plan9

package runtime

// Stub definitions copied from upstream golang
// TODO: implement a minimal functional version of these functions

// go: linkname os/signal.signal_disable signal_disable
func signal_disable(_ uint32) {}

// go: linkname os/signal.signal_enable signal_enable
func signal_enable(_ uint32) {}

// go: linkname os/signal.signal_ignore signal_ignore
func signal_ignore(_ uint32) {}

// go: linkname os/signal.signal_ignored signal_ignored
func signal_ignored(_ uint32) bool { return true }

// go: linkname os/signal.signal_recv signal_recv
func signal_recv() uint32 { return 0 }
7 changes: 7 additions & 0 deletions src/syscall/exec_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build unix

package syscall

// Implemented in runtime package.
func runtime_BeforeExec()
func runtime_AfterExec()
Loading
Loading