mirror of https://go.googlesource.com/go
395 lines
7.9 KiB
Go
395 lines
7.9 KiB
Go
// Copyright 2015 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
|
|
|
|
package syscall_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"internal/testenv"
|
|
"io"
|
|
"math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"strconv"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type command struct {
|
|
pipe io.WriteCloser
|
|
proc *exec.Cmd
|
|
test *testing.T
|
|
}
|
|
|
|
func (c *command) Info() (pid, pgrp int) {
|
|
pid = c.proc.Process.Pid
|
|
|
|
pgrp, err := syscall.Getpgid(pid)
|
|
if err != nil {
|
|
c.test.Fatal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *command) Start() {
|
|
if err := c.proc.Start(); err != nil {
|
|
c.test.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (c *command) Stop() {
|
|
c.pipe.Close()
|
|
if err := c.proc.Wait(); err != nil {
|
|
c.test.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func create(t *testing.T) *command {
|
|
testenv.MustHaveExec(t)
|
|
|
|
proc := exec.Command("cat")
|
|
stdin, err := proc.StdinPipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return &command{stdin, proc, t}
|
|
}
|
|
|
|
func parent() (pid, pgrp int) {
|
|
return syscall.Getpid(), syscall.Getpgrp()
|
|
}
|
|
|
|
func TestZeroSysProcAttr(t *testing.T) {
|
|
ppid, ppgrp := parent()
|
|
|
|
cmd := create(t)
|
|
|
|
cmd.Start()
|
|
defer cmd.Stop()
|
|
|
|
cpid, cpgrp := cmd.Info()
|
|
|
|
if cpid == ppid {
|
|
t.Fatalf("Parent and child have the same process ID")
|
|
}
|
|
|
|
if cpgrp != ppgrp {
|
|
t.Fatalf("Child is not in parent's process group")
|
|
}
|
|
}
|
|
|
|
func TestSetpgid(t *testing.T) {
|
|
ppid, ppgrp := parent()
|
|
|
|
cmd := create(t)
|
|
|
|
cmd.proc.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
cmd.Start()
|
|
defer cmd.Stop()
|
|
|
|
cpid, cpgrp := cmd.Info()
|
|
|
|
if cpid == ppid {
|
|
t.Fatalf("Parent and child have the same process ID")
|
|
}
|
|
|
|
if cpgrp == ppgrp {
|
|
t.Fatalf("Parent and child are in the same process group")
|
|
}
|
|
|
|
if cpid != cpgrp {
|
|
t.Fatalf("Child's process group is not the child's process ID")
|
|
}
|
|
}
|
|
|
|
func TestPgid(t *testing.T) {
|
|
ppid, ppgrp := parent()
|
|
|
|
cmd1 := create(t)
|
|
|
|
cmd1.proc.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
cmd1.Start()
|
|
defer cmd1.Stop()
|
|
|
|
cpid1, cpgrp1 := cmd1.Info()
|
|
|
|
if cpid1 == ppid {
|
|
t.Fatalf("Parent and child 1 have the same process ID")
|
|
}
|
|
|
|
if cpgrp1 == ppgrp {
|
|
t.Fatalf("Parent and child 1 are in the same process group")
|
|
}
|
|
|
|
if cpid1 != cpgrp1 {
|
|
t.Fatalf("Child 1's process group is not its process ID")
|
|
}
|
|
|
|
cmd2 := create(t)
|
|
|
|
cmd2.proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Setpgid: true,
|
|
Pgid: cpgrp1,
|
|
}
|
|
cmd2.Start()
|
|
defer cmd2.Stop()
|
|
|
|
cpid2, cpgrp2 := cmd2.Info()
|
|
|
|
if cpid2 == ppid {
|
|
t.Fatalf("Parent and child 2 have the same process ID")
|
|
}
|
|
|
|
if cpgrp2 == ppgrp {
|
|
t.Fatalf("Parent and child 2 are in the same process group")
|
|
}
|
|
|
|
if cpid2 == cpgrp2 {
|
|
t.Fatalf("Child 2's process group is its process ID")
|
|
}
|
|
|
|
if cpid1 == cpid2 {
|
|
t.Fatalf("Child 1 and 2 have the same process ID")
|
|
}
|
|
|
|
if cpgrp1 != cpgrp2 {
|
|
t.Fatalf("Child 1 and 2 are not in the same process group")
|
|
}
|
|
}
|
|
|
|
func TestForeground(t *testing.T) {
|
|
signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
|
|
defer signal.Reset()
|
|
|
|
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
|
if err != nil {
|
|
t.Skipf("Can't test Foreground. Couldn't open /dev/tty: %s", err)
|
|
}
|
|
defer tty.Close()
|
|
|
|
ttyFD := int(tty.Fd())
|
|
|
|
fpgrp, err := syscall.Tcgetpgrp(ttyFD)
|
|
if err != nil {
|
|
t.Fatalf("Tcgetpgrp failed: %v", err)
|
|
}
|
|
if fpgrp == 0 {
|
|
t.Fatalf("Foreground process group is zero")
|
|
}
|
|
|
|
ppid, ppgrp := parent()
|
|
|
|
cmd := create(t)
|
|
|
|
cmd.proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Ctty: ttyFD,
|
|
Foreground: true,
|
|
}
|
|
cmd.Start()
|
|
|
|
cpid, cpgrp := cmd.Info()
|
|
|
|
if cpid == ppid {
|
|
t.Fatalf("Parent and child have the same process ID")
|
|
}
|
|
|
|
if cpgrp == ppgrp {
|
|
t.Fatalf("Parent and child are in the same process group")
|
|
}
|
|
|
|
if cpid != cpgrp {
|
|
t.Fatalf("Child's process group is not the child's process ID")
|
|
}
|
|
|
|
cmd.Stop()
|
|
|
|
// This call fails on darwin/arm64. The failure doesn't matter, though.
|
|
// This is just best effort.
|
|
syscall.Tcsetpgrp(ttyFD, fpgrp)
|
|
}
|
|
|
|
func TestForegroundSignal(t *testing.T) {
|
|
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
|
if err != nil {
|
|
t.Skipf("couldn't open /dev/tty: %s", err)
|
|
}
|
|
defer tty.Close()
|
|
|
|
ttyFD := int(tty.Fd())
|
|
|
|
fpgrp, err := syscall.Tcgetpgrp(ttyFD)
|
|
if err != nil {
|
|
t.Fatalf("Tcgetpgrp failed: %v", err)
|
|
}
|
|
if fpgrp == 0 {
|
|
t.Fatalf("Foreground process group is zero")
|
|
}
|
|
|
|
defer func() {
|
|
signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
|
|
syscall.Tcsetpgrp(ttyFD, fpgrp)
|
|
signal.Reset()
|
|
}()
|
|
|
|
ch1 := make(chan os.Signal, 1)
|
|
ch2 := make(chan bool)
|
|
|
|
signal.Notify(ch1, syscall.SIGTTIN, syscall.SIGTTOU)
|
|
defer signal.Stop(ch1)
|
|
|
|
cmd := create(t)
|
|
|
|
go func() {
|
|
cmd.proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Ctty: ttyFD,
|
|
Foreground: true,
|
|
}
|
|
cmd.Start()
|
|
cmd.Stop()
|
|
close(ch2)
|
|
}()
|
|
|
|
timer := time.NewTimer(30 * time.Second)
|
|
defer timer.Stop()
|
|
for {
|
|
select {
|
|
case sig := <-ch1:
|
|
t.Errorf("unexpected signal %v", sig)
|
|
case <-ch2:
|
|
// Success.
|
|
return
|
|
case <-timer.C:
|
|
t.Fatal("timed out waiting for child process")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test a couple of cases that SysProcAttr can't handle. Issue 29458.
|
|
func TestInvalidExec(t *testing.T) {
|
|
t.Parallel()
|
|
t.Run("SetCtty-Foreground", func(t *testing.T) {
|
|
t.Parallel()
|
|
cmd := create(t)
|
|
cmd.proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Setctty: true,
|
|
Foreground: true,
|
|
Ctty: 0,
|
|
}
|
|
if err := cmd.proc.Start(); err == nil {
|
|
t.Error("expected error setting both SetCtty and Foreground")
|
|
}
|
|
})
|
|
t.Run("invalid-Ctty", func(t *testing.T) {
|
|
t.Parallel()
|
|
cmd := create(t)
|
|
cmd.proc.SysProcAttr = &syscall.SysProcAttr{
|
|
Setctty: true,
|
|
Ctty: 3,
|
|
}
|
|
if err := cmd.proc.Start(); err == nil {
|
|
t.Error("expected error with invalid Ctty value")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestExec is for issue #41702.
|
|
func TestExec(t *testing.T) {
|
|
testenv.MustHaveExec(t)
|
|
cmd := exec.Command(os.Args[0], "-test.run=^TestExecHelper$")
|
|
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=2")
|
|
o, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Errorf("%s\n%v", o, err)
|
|
}
|
|
}
|
|
|
|
// TestExecHelper is used by TestExec. It does nothing by itself.
|
|
// In testing on macOS 10.14, this used to fail with
|
|
// "signal: illegal instruction" more than half the time.
|
|
func TestExecHelper(t *testing.T) {
|
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "2" {
|
|
return
|
|
}
|
|
|
|
// We don't have to worry about restoring these values.
|
|
// We are in a child process that only runs this test,
|
|
// and we are going to call syscall.Exec anyhow.
|
|
os.Setenv("GO_WANT_HELPER_PROCESS", "3")
|
|
|
|
stop := time.Now().Add(time.Second)
|
|
for i := 0; i < 100; i++ {
|
|
go func(i int) {
|
|
r := rand.New(rand.NewSource(int64(i)))
|
|
for time.Now().Before(stop) {
|
|
r.Uint64()
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
|
|
argv := []string{os.Args[0], "-test.run=^TestExecHelper$"}
|
|
syscall.Exec(os.Args[0], argv, os.Environ())
|
|
|
|
t.Error("syscall.Exec returned")
|
|
}
|
|
|
|
// Test that rlimit values are restored by exec.
|
|
func TestRlimitRestored(t *testing.T) {
|
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "" {
|
|
fmt.Println(syscall.OrigRlimitNofile().Cur)
|
|
os.Exit(0)
|
|
}
|
|
|
|
orig := syscall.OrigRlimitNofile()
|
|
if orig == nil {
|
|
t.Skip("skipping test because rlimit not adjusted at startup")
|
|
}
|
|
|
|
executable, err := os.Executable()
|
|
if err != nil {
|
|
executable = os.Args[0]
|
|
}
|
|
|
|
cmd := testenv.Command(t, executable, "-test.run=^TestRlimitRestored$")
|
|
cmd = testenv.CleanCmdEnv(cmd)
|
|
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
|
|
|
|
out, err := cmd.CombinedOutput()
|
|
if len(out) > 0 {
|
|
t.Logf("%s", out)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("subprocess failed: %v", err)
|
|
}
|
|
s := string(bytes.TrimSpace(out))
|
|
v, err := strconv.ParseUint(s, 10, 64)
|
|
if err != nil {
|
|
t.Fatalf("could not parse %q as number: %v", s, v)
|
|
}
|
|
|
|
if v != uint64(orig.Cur) {
|
|
t.Errorf("exec rlimit = %d, want %d", v, orig)
|
|
}
|
|
}
|
|
|
|
func TestForkExecNilArgv(t *testing.T) {
|
|
defer func() {
|
|
if p := recover(); p != nil {
|
|
t.Fatal("forkExec panicked")
|
|
}
|
|
}()
|
|
|
|
// We don't really care what the result of forkExec is, just that it doesn't
|
|
// panic, so we choose something we know won't actually spawn a process (probably).
|
|
syscall.ForkExec("/dev/null", nil, nil)
|
|
}
|