diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
commit | 82539ad8d59729fb45b0bb0edda8f2bddb719eb1 (patch) | |
tree | 58f0b58e6f44f0e04d4a6373132cf426fa835fa7 /src/syscall/exec_unix_test.go | |
parent | Initial commit. (diff) | |
download | golang-1.17-upstream.tar.xz golang-1.17-upstream.zip |
Adding upstream version 1.17.13.upstream/1.17.13upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/syscall/exec_unix_test.go')
-rw-r--r-- | src/syscall/exec_unix_test.go | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/src/syscall/exec_unix_test.go b/src/syscall/exec_unix_test.go new file mode 100644 index 0000000..866671b --- /dev/null +++ b/src/syscall/exec_unix_test.go @@ -0,0 +1,350 @@ +// 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 aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package syscall_test + +import ( + "internal/testenv" + "io" + "math/rand" + "os" + "os/exec" + "os/signal" + "runtime" + "syscall" + "testing" + "time" + "unsafe" +) + +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() + + // This should really be pid_t, however _C_int (aka int32) is generally + // equivalent. + fpgrp := int32(0) + + errno := syscall.Ioctl(tty.Fd(), syscall.TIOCGPGRP, uintptr(unsafe.Pointer(&fpgrp))) + if errno != 0 { + t.Fatalf("TIOCGPGRP failed with error code: %s", errno) + } + + if fpgrp == 0 { + t.Fatalf("Foreground process group is zero") + } + + ppid, ppgrp := parent() + + cmd := create(t) + + cmd.proc.SysProcAttr = &syscall.SysProcAttr{ + Ctty: int(tty.Fd()), + 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.Ioctl(tty.Fd(), syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&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() + + // This should really be pid_t, however _C_int (aka int32) is generally + // equivalent. + fpgrp := int32(0) + + errno := syscall.Ioctl(tty.Fd(), syscall.TIOCGPGRP, uintptr(unsafe.Pointer(&fpgrp))) + if errno != 0 { + t.Fatalf("TIOCGPGRP failed with error code: %s", errno) + } + + if fpgrp == 0 { + t.Fatalf("Foreground process group is zero") + } + + defer func() { + signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU) + syscall.Ioctl(tty.Fd(), syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&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: int(tty.Fd()), + 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. + runtime.GOMAXPROCS(50) + 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") +} |