Source file src/syscall/exec_unix_test.go

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build unix
     6  
     7  package syscall_test
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"internal/testenv"
    13  	"io"
    14  	"math/rand"
    15  	"os"
    16  	"os/exec"
    17  	"os/signal"
    18  	"strconv"
    19  	"syscall"
    20  	"testing"
    21  	"time"
    22  	"unsafe"
    23  )
    24  
    25  type command struct {
    26  	pipe io.WriteCloser
    27  	proc *exec.Cmd
    28  	test *testing.T
    29  }
    30  
    31  func (c *command) Info() (pid, pgrp int) {
    32  	pid = c.proc.Process.Pid
    33  
    34  	pgrp, err := syscall.Getpgid(pid)
    35  	if err != nil {
    36  		c.test.Fatal(err)
    37  	}
    38  
    39  	return
    40  }
    41  
    42  func (c *command) Start() {
    43  	if err := c.proc.Start(); err != nil {
    44  		c.test.Fatal(err)
    45  	}
    46  }
    47  
    48  func (c *command) Stop() {
    49  	c.pipe.Close()
    50  	if err := c.proc.Wait(); err != nil {
    51  		c.test.Fatal(err)
    52  	}
    53  }
    54  
    55  func create(t *testing.T) *command {
    56  	testenv.MustHaveExec(t)
    57  
    58  	proc := exec.Command("cat")
    59  	stdin, err := proc.StdinPipe()
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  
    64  	return &command{stdin, proc, t}
    65  }
    66  
    67  func parent() (pid, pgrp int) {
    68  	return syscall.Getpid(), syscall.Getpgrp()
    69  }
    70  
    71  func TestZeroSysProcAttr(t *testing.T) {
    72  	ppid, ppgrp := parent()
    73  
    74  	cmd := create(t)
    75  
    76  	cmd.Start()
    77  	defer cmd.Stop()
    78  
    79  	cpid, cpgrp := cmd.Info()
    80  
    81  	if cpid == ppid {
    82  		t.Fatalf("Parent and child have the same process ID")
    83  	}
    84  
    85  	if cpgrp != ppgrp {
    86  		t.Fatalf("Child is not in parent's process group")
    87  	}
    88  }
    89  
    90  func TestSetpgid(t *testing.T) {
    91  	ppid, ppgrp := parent()
    92  
    93  	cmd := create(t)
    94  
    95  	cmd.proc.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    96  	cmd.Start()
    97  	defer cmd.Stop()
    98  
    99  	cpid, cpgrp := cmd.Info()
   100  
   101  	if cpid == ppid {
   102  		t.Fatalf("Parent and child have the same process ID")
   103  	}
   104  
   105  	if cpgrp == ppgrp {
   106  		t.Fatalf("Parent and child are in the same process group")
   107  	}
   108  
   109  	if cpid != cpgrp {
   110  		t.Fatalf("Child's process group is not the child's process ID")
   111  	}
   112  }
   113  
   114  func TestPgid(t *testing.T) {
   115  	ppid, ppgrp := parent()
   116  
   117  	cmd1 := create(t)
   118  
   119  	cmd1.proc.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
   120  	cmd1.Start()
   121  	defer cmd1.Stop()
   122  
   123  	cpid1, cpgrp1 := cmd1.Info()
   124  
   125  	if cpid1 == ppid {
   126  		t.Fatalf("Parent and child 1 have the same process ID")
   127  	}
   128  
   129  	if cpgrp1 == ppgrp {
   130  		t.Fatalf("Parent and child 1 are in the same process group")
   131  	}
   132  
   133  	if cpid1 != cpgrp1 {
   134  		t.Fatalf("Child 1's process group is not its process ID")
   135  	}
   136  
   137  	cmd2 := create(t)
   138  
   139  	cmd2.proc.SysProcAttr = &syscall.SysProcAttr{
   140  		Setpgid: true,
   141  		Pgid:    cpgrp1,
   142  	}
   143  	cmd2.Start()
   144  	defer cmd2.Stop()
   145  
   146  	cpid2, cpgrp2 := cmd2.Info()
   147  
   148  	if cpid2 == ppid {
   149  		t.Fatalf("Parent and child 2 have the same process ID")
   150  	}
   151  
   152  	if cpgrp2 == ppgrp {
   153  		t.Fatalf("Parent and child 2 are in the same process group")
   154  	}
   155  
   156  	if cpid2 == cpgrp2 {
   157  		t.Fatalf("Child 2's process group is its process ID")
   158  	}
   159  
   160  	if cpid1 == cpid2 {
   161  		t.Fatalf("Child 1 and 2 have the same process ID")
   162  	}
   163  
   164  	if cpgrp1 != cpgrp2 {
   165  		t.Fatalf("Child 1 and 2 are not in the same process group")
   166  	}
   167  }
   168  
   169  func TestForeground(t *testing.T) {
   170  	signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
   171  	defer signal.Reset()
   172  
   173  	tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
   174  	if err != nil {
   175  		t.Skipf("Can't test Foreground. Couldn't open /dev/tty: %s", err)
   176  	}
   177  	defer tty.Close()
   178  
   179  	// This should really be pid_t, however _C_int (aka int32) is generally
   180  	// equivalent.
   181  	fpgrp := int32(0)
   182  
   183  	errno := syscall.IoctlPtr(tty.Fd(), syscall.TIOCGPGRP, unsafe.Pointer(&fpgrp))
   184  	if errno != 0 {
   185  		t.Fatalf("TIOCGPGRP failed with error code: %s", errno)
   186  	}
   187  
   188  	if fpgrp == 0 {
   189  		t.Fatalf("Foreground process group is zero")
   190  	}
   191  
   192  	ppid, ppgrp := parent()
   193  
   194  	cmd := create(t)
   195  
   196  	cmd.proc.SysProcAttr = &syscall.SysProcAttr{
   197  		Ctty:       int(tty.Fd()),
   198  		Foreground: true,
   199  	}
   200  	cmd.Start()
   201  
   202  	cpid, cpgrp := cmd.Info()
   203  
   204  	if cpid == ppid {
   205  		t.Fatalf("Parent and child have the same process ID")
   206  	}
   207  
   208  	if cpgrp == ppgrp {
   209  		t.Fatalf("Parent and child are in the same process group")
   210  	}
   211  
   212  	if cpid != cpgrp {
   213  		t.Fatalf("Child's process group is not the child's process ID")
   214  	}
   215  
   216  	cmd.Stop()
   217  
   218  	// This call fails on darwin/arm64. The failure doesn't matter, though.
   219  	// This is just best effort.
   220  	syscall.IoctlPtr(tty.Fd(), syscall.TIOCSPGRP, unsafe.Pointer(&fpgrp))
   221  }
   222  
   223  func TestForegroundSignal(t *testing.T) {
   224  	tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
   225  	if err != nil {
   226  		t.Skipf("couldn't open /dev/tty: %s", err)
   227  	}
   228  	defer tty.Close()
   229  
   230  	// This should really be pid_t, however _C_int (aka int32) is generally
   231  	// equivalent.
   232  	fpgrp := int32(0)
   233  
   234  	errno := syscall.IoctlPtr(tty.Fd(), syscall.TIOCGPGRP, unsafe.Pointer(&fpgrp))
   235  	if errno != 0 {
   236  		t.Fatalf("TIOCGPGRP failed with error code: %s", errno)
   237  	}
   238  
   239  	if fpgrp == 0 {
   240  		t.Fatalf("Foreground process group is zero")
   241  	}
   242  
   243  	defer func() {
   244  		signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
   245  		syscall.IoctlPtr(tty.Fd(), syscall.TIOCSPGRP, unsafe.Pointer(&fpgrp))
   246  		signal.Reset()
   247  	}()
   248  
   249  	ch1 := make(chan os.Signal, 1)
   250  	ch2 := make(chan bool)
   251  
   252  	signal.Notify(ch1, syscall.SIGTTIN, syscall.SIGTTOU)
   253  	defer signal.Stop(ch1)
   254  
   255  	cmd := create(t)
   256  
   257  	go func() {
   258  		cmd.proc.SysProcAttr = &syscall.SysProcAttr{
   259  			Ctty:       int(tty.Fd()),
   260  			Foreground: true,
   261  		}
   262  		cmd.Start()
   263  		cmd.Stop()
   264  		close(ch2)
   265  	}()
   266  
   267  	timer := time.NewTimer(30 * time.Second)
   268  	defer timer.Stop()
   269  	for {
   270  		select {
   271  		case sig := <-ch1:
   272  			t.Errorf("unexpected signal %v", sig)
   273  		case <-ch2:
   274  			// Success.
   275  			return
   276  		case <-timer.C:
   277  			t.Fatal("timed out waiting for child process")
   278  		}
   279  	}
   280  }
   281  
   282  // Test a couple of cases that SysProcAttr can't handle. Issue 29458.
   283  func TestInvalidExec(t *testing.T) {
   284  	t.Parallel()
   285  	t.Run("SetCtty-Foreground", func(t *testing.T) {
   286  		t.Parallel()
   287  		cmd := create(t)
   288  		cmd.proc.SysProcAttr = &syscall.SysProcAttr{
   289  			Setctty:    true,
   290  			Foreground: true,
   291  			Ctty:       0,
   292  		}
   293  		if err := cmd.proc.Start(); err == nil {
   294  			t.Error("expected error setting both SetCtty and Foreground")
   295  		}
   296  	})
   297  	t.Run("invalid-Ctty", func(t *testing.T) {
   298  		t.Parallel()
   299  		cmd := create(t)
   300  		cmd.proc.SysProcAttr = &syscall.SysProcAttr{
   301  			Setctty: true,
   302  			Ctty:    3,
   303  		}
   304  		if err := cmd.proc.Start(); err == nil {
   305  			t.Error("expected error with invalid Ctty value")
   306  		}
   307  	})
   308  }
   309  
   310  // TestExec is for issue #41702.
   311  func TestExec(t *testing.T) {
   312  	testenv.MustHaveExec(t)
   313  	cmd := exec.Command(os.Args[0], "-test.run=^TestExecHelper$")
   314  	cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=2")
   315  	o, err := cmd.CombinedOutput()
   316  	if err != nil {
   317  		t.Errorf("%s\n%v", o, err)
   318  	}
   319  }
   320  
   321  // TestExecHelper is used by TestExec. It does nothing by itself.
   322  // In testing on macOS 10.14, this used to fail with
   323  // "signal: illegal instruction" more than half the time.
   324  func TestExecHelper(t *testing.T) {
   325  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "2" {
   326  		return
   327  	}
   328  
   329  	// We don't have to worry about restoring these values.
   330  	// We are in a child process that only runs this test,
   331  	// and we are going to call syscall.Exec anyhow.
   332  	os.Setenv("GO_WANT_HELPER_PROCESS", "3")
   333  
   334  	stop := time.Now().Add(time.Second)
   335  	for i := 0; i < 100; i++ {
   336  		go func(i int) {
   337  			r := rand.New(rand.NewSource(int64(i)))
   338  			for time.Now().Before(stop) {
   339  				r.Uint64()
   340  			}
   341  		}(i)
   342  	}
   343  
   344  	time.Sleep(10 * time.Millisecond)
   345  
   346  	argv := []string{os.Args[0], "-test.run=^TestExecHelper$"}
   347  	syscall.Exec(os.Args[0], argv, os.Environ())
   348  
   349  	t.Error("syscall.Exec returned")
   350  }
   351  
   352  // Test that rlimit values are restored by exec.
   353  func TestRlimitRestored(t *testing.T) {
   354  	if os.Getenv("GO_WANT_HELPER_PROCESS") != "" {
   355  		fmt.Println(syscall.OrigRlimitNofile().Cur)
   356  		os.Exit(0)
   357  	}
   358  
   359  	orig := syscall.OrigRlimitNofile()
   360  	if orig == nil {
   361  		t.Skip("skipping test because rlimit not adjusted at startup")
   362  	}
   363  
   364  	executable, err := os.Executable()
   365  	if err != nil {
   366  		executable = os.Args[0]
   367  	}
   368  
   369  	cmd := testenv.Command(t, executable, "-test.run=^TestRlimitRestored$")
   370  	cmd = testenv.CleanCmdEnv(cmd)
   371  	cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
   372  
   373  	out, err := cmd.CombinedOutput()
   374  	if len(out) > 0 {
   375  		t.Logf("%s", out)
   376  	}
   377  	if err != nil {
   378  		t.Fatalf("subprocess failed: %v", err)
   379  	}
   380  	s := string(bytes.TrimSpace(out))
   381  	v, err := strconv.ParseUint(s, 10, 64)
   382  	if err != nil {
   383  		t.Fatalf("could not parse %q as number: %v", s, v)
   384  	}
   385  
   386  	if v != uint64(orig.Cur) {
   387  		t.Errorf("exec rlimit = %d, want %d", v, orig)
   388  	}
   389  }
   390  
   391  func TestForkExecNilArgv(t *testing.T) {
   392  	defer func() {
   393  		if p := recover(); p != nil {
   394  			t.Fatal("forkExec panicked")
   395  		}
   396  	}()
   397  
   398  	// We don't really care what the result of forkExec is, just that it doesn't
   399  	// panic, so we choose something we know won't actually spawn a process (probably).
   400  	syscall.ForkExec("/dev/null", nil, nil)
   401  }
   402  

View as plain text