• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7package syscall_test
8
9import (
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
24type command struct {
25	pipe io.WriteCloser
26	proc *exec.Cmd
27	test *testing.T
28}
29
30func (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
41func (c *command) Start() {
42	if err := c.proc.Start(); err != nil {
43		c.test.Fatal(err)
44	}
45}
46
47func (c *command) Stop() {
48	c.pipe.Close()
49	if err := c.proc.Wait(); err != nil {
50		c.test.Fatal(err)
51	}
52}
53
54func 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
66func parent() (pid, pgrp int) {
67	return syscall.Getpid(), syscall.Getpgrp()
68}
69
70func 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
89func 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
113func 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
168func 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
219func 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.
276func 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.
304func 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.
317func 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.
346func 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
384func 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