Source file
src/os/signal/signal_cgo_test.go
1
2
3
4
5
6
7
8
9
10
11 package signal_test
12
13 import (
14 "context"
15 "encoding/binary"
16 "fmt"
17 "internal/syscall/unix"
18 "internal/testenv"
19 "internal/testpty"
20 "os"
21 "os/signal"
22 "runtime"
23 "strconv"
24 "syscall"
25 "testing"
26 "time"
27 )
28
29 const (
30 ptyFD = 3
31 controlFD = 4
32 )
33
34
35
36
37
38
39
40
41
42 func TestTerminalSignal(t *testing.T) {
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 if runtime.GOOS == "dragonfly" {
79 t.Skip("skipping: wait hangs on dragonfly; see https://go.dev/issue/56132")
80 }
81
82 scale := 1
83 if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
84 if sc, err := strconv.Atoi(s); err == nil {
85 scale = sc
86 }
87 }
88 pause := time.Duration(scale) * 10 * time.Millisecond
89
90 lvl := os.Getenv("GO_TEST_TERMINAL_SIGNALS")
91 switch lvl {
92 case "":
93
94 break
95 case "1":
96 runSessionLeader(t, pause)
97 panic("unreachable")
98 case "2":
99 runStoppingChild()
100 panic("unreachable")
101 default:
102 fmt.Fprintf(os.Stderr, "unknown subprocess level %s\n", lvl)
103 os.Exit(1)
104 }
105
106 t.Parallel()
107
108 pty, procTTYName, err := testpty.Open()
109 if err != nil {
110 ptyErr := err.(*testpty.PtyError)
111 if ptyErr.FuncName == "posix_openpt" && ptyErr.Errno == syscall.EACCES {
112 t.Skip("posix_openpt failed with EACCES, assuming chroot and skipping")
113 }
114 t.Fatal(err)
115 }
116 defer pty.Close()
117 procTTY, err := os.OpenFile(procTTYName, os.O_RDWR, 0)
118 if err != nil {
119 t.Fatal(err)
120 }
121 defer procTTY.Close()
122
123
124
125
126 controlR, controlW, err := os.Pipe()
127 if err != nil {
128 t.Fatal(err)
129 }
130
131 var (
132 ctx = context.Background()
133 cmdArgs = []string{"-test.run=^TestTerminalSignal$"}
134 )
135 if deadline, ok := t.Deadline(); ok {
136 d := time.Until(deadline)
137 var cancel context.CancelFunc
138 ctx, cancel = context.WithTimeout(ctx, d)
139 t.Cleanup(cancel)
140
141
142
143 cmdArgs = append(cmdArgs, fmt.Sprintf("-test.timeout=%v", d*5/4))
144 }
145
146 cmd := testenv.CommandContext(t, ctx, os.Args[0], cmdArgs...)
147 cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=1")
148 cmd.Stdin = os.Stdin
149 cmd.Stdout = os.Stdout
150 cmd.Stderr = os.Stderr
151 cmd.ExtraFiles = []*os.File{procTTY, controlW}
152 cmd.SysProcAttr = &syscall.SysProcAttr{
153 Setsid: true,
154 Setctty: true,
155 Ctty: ptyFD,
156 }
157
158 if err := cmd.Start(); err != nil {
159 t.Fatal(err)
160 }
161
162 if err := procTTY.Close(); err != nil {
163 t.Errorf("closing procTTY: %v", err)
164 }
165
166 if err := controlW.Close(); err != nil {
167 t.Errorf("closing controlW: %v", err)
168 }
169
170
171 b := make([]byte, 8)
172 n, err := controlR.Read(b)
173 if err != nil {
174 t.Fatalf("error reading child pid: %v\n", err)
175 }
176 if n != 8 {
177 t.Fatalf("unexpected short read n = %d\n", n)
178 }
179 pid := binary.LittleEndian.Uint64(b[:])
180 process, err := os.FindProcess(int(pid))
181 if err != nil {
182 t.Fatalf("unable to find child process: %v", err)
183 }
184
185
186
187 b = make([]byte, 1)
188 _, err = pty.Read(b)
189 if err != nil {
190 t.Fatalf("error reading from child: %v", err)
191 }
192
193
194
195
196
197 time.Sleep(pause)
198
199 t.Logf("Sending ^Z...")
200
201
202 if _, err := pty.Write([]byte{26}); err != nil {
203 t.Fatalf("writing ^Z to pty: %v", err)
204 }
205
206
207 if _, err := controlR.Read(b); err != nil {
208 t.Fatalf("error reading readiness: %v", err)
209 }
210
211 t.Logf("Sending SIGCONT...")
212
213
214 if err := process.Signal(syscall.SIGCONT); err != nil {
215 t.Fatalf("Signal(SIGCONT) got err %v want nil", err)
216 }
217
218
219
220 if _, err := pty.Write([]byte{'\n'}); err != nil {
221 t.Fatalf("writing %q to pty: %v", "\n", err)
222 }
223
224 t.Logf("Waiting for exit...")
225
226 if err = cmd.Wait(); err != nil {
227 t.Errorf("subprogram failed: %v", err)
228 }
229 }
230
231
232 func runSessionLeader(t *testing.T, pause time.Duration) {
233
234
235
236
237
238
239
240
241
242
243
244
245
246 signal.Ignore(syscall.SIGTTOU)
247
248 pty := os.NewFile(ptyFD, "pty")
249 controlW := os.NewFile(controlFD, "control-pipe")
250
251 var (
252 ctx = context.Background()
253 cmdArgs = []string{"-test.run=^TestTerminalSignal$"}
254 )
255 if deadline, ok := t.Deadline(); ok {
256 d := time.Until(deadline)
257 var cancel context.CancelFunc
258 ctx, cancel = context.WithTimeout(ctx, d)
259 t.Cleanup(cancel)
260
261
262
263 cmdArgs = append(cmdArgs, fmt.Sprintf("-test.timeout=%v", d*5/4))
264 }
265
266 cmd := testenv.CommandContext(t, ctx, os.Args[0], cmdArgs...)
267 cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=2")
268 cmd.Stdin = os.Stdin
269 cmd.Stdout = os.Stdout
270 cmd.Stderr = os.Stderr
271 cmd.ExtraFiles = []*os.File{pty}
272 cmd.SysProcAttr = &syscall.SysProcAttr{
273 Foreground: true,
274 Ctty: ptyFD,
275 }
276 if err := cmd.Start(); err != nil {
277 fmt.Fprintf(os.Stderr, "error starting second subprocess: %v\n", err)
278 os.Exit(1)
279 }
280
281 fn := func() error {
282 var b [8]byte
283 binary.LittleEndian.PutUint64(b[:], uint64(cmd.Process.Pid))
284 _, err := controlW.Write(b[:])
285 if err != nil {
286 return fmt.Errorf("error writing child pid: %w", err)
287 }
288
289
290 var status syscall.WaitStatus
291 for {
292 _, err = syscall.Wait4(cmd.Process.Pid, &status, syscall.WUNTRACED, nil)
293 if err != syscall.EINTR {
294 break
295 }
296 }
297 if err != nil {
298 return fmt.Errorf("error waiting for stop: %w", err)
299 }
300
301 if !status.Stopped() {
302 return fmt.Errorf("unexpected wait status: %v", status)
303 }
304
305
306 pgrp := int32(syscall.Getpgrp())
307 if err := unix.Tcsetpgrp(ptyFD, pgrp); err != nil {
308 return fmt.Errorf("error setting tty process group: %w", err)
309 }
310
311
312
313 time.Sleep(pause)
314
315
316 pid := int32(cmd.Process.Pid)
317 if err := unix.Tcsetpgrp(ptyFD, pid); err != nil {
318 return fmt.Errorf("error setting tty process group back: %w", err)
319 }
320
321
322
323 if _, err := controlW.Write(b[:1]); err != nil {
324 return fmt.Errorf("error writing readiness: %w", err)
325 }
326
327 return nil
328 }
329
330 err := fn()
331 if err != nil {
332 fmt.Fprintf(os.Stderr, "session leader error: %v\n", err)
333 cmd.Process.Kill()
334
335 }
336
337 werr := cmd.Wait()
338 if werr != nil {
339 fmt.Fprintf(os.Stderr, "error running second subprocess: %v\n", err)
340 }
341
342 if err != nil || werr != nil {
343 os.Exit(1)
344 }
345
346 os.Exit(0)
347 }
348
349
350 func runStoppingChild() {
351 pty := os.NewFile(ptyFD, "pty")
352
353 var b [1]byte
354 if _, err := pty.Write(b[:]); err != nil {
355 fmt.Fprintf(os.Stderr, "error writing byte to PTY: %v\n", err)
356 os.Exit(1)
357 }
358
359 _, err := pty.Read(b[:])
360 if err != nil {
361 fmt.Fprintln(os.Stderr, err)
362 os.Exit(1)
363 }
364 if b[0] == '\n' {
365
366 fmt.Println("read newline")
367 } else {
368 fmt.Fprintf(os.Stderr, "read 1 unexpected byte: %q\n", b)
369 os.Exit(1)
370 }
371 os.Exit(0)
372 }
373
View as plain text