blob: f8f09df749bc09e6ef5b95fe5e72225bfcd7fa9f [file] [log] [blame] [edit]
// 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"
"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.
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.Cur == 0 {
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)
}
}