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

View as plain text