Source file
src/syscall/exec_unix_test.go
1
2
3
4
5
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
215
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
268 return
269 case <-timer.C:
270 t.Fatal("timed out waiting for child process")
271 }
272 }
273 }
274
275
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
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
315
316
317 func TestExecHelper(t *testing.T) {
318 if os.Getenv("GO_WANT_HELPER_PROCESS") != "2" {
319 return
320 }
321
322
323
324
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
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
392
393 syscall.ForkExec("/dev/null", nil, nil)
394 }
395
View as plain text