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/testpty"
18 "os"
19 "os/exec"
20 "os/signal"
21 "runtime"
22 "strconv"
23 "syscall"
24 "testing"
25 "time"
26 "unsafe"
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(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 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
132 defer cancel()
133 cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
134 cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=1")
135 cmd.Stdin = os.Stdin
136 cmd.Stdout = os.Stdout
137 cmd.Stderr = os.Stderr
138 cmd.ExtraFiles = []*os.File{procTTY, controlW}
139 cmd.SysProcAttr = &syscall.SysProcAttr{
140 Setsid: true,
141 Setctty: true,
142 Ctty: ptyFD,
143 }
144
145 if err := cmd.Start(); err != nil {
146 t.Fatal(err)
147 }
148
149 if err := procTTY.Close(); err != nil {
150 t.Errorf("closing procTTY: %v", err)
151 }
152
153 if err := controlW.Close(); err != nil {
154 t.Errorf("closing controlW: %v", err)
155 }
156
157
158 b := make([]byte, 8)
159 n, err := controlR.Read(b)
160 if err != nil {
161 t.Fatalf("error reading child pid: %v\n", err)
162 }
163 if n != 8 {
164 t.Fatalf("unexpected short read n = %d\n", n)
165 }
166 pid := binary.LittleEndian.Uint64(b[:])
167 process, err := os.FindProcess(int(pid))
168 if err != nil {
169 t.Fatalf("unable to find child process: %v", err)
170 }
171
172
173
174 b = make([]byte, 1)
175 _, err = pty.Read(b)
176 if err != nil {
177 t.Fatalf("error reading from child: %v", err)
178 }
179
180
181
182
183
184 time.Sleep(pause)
185
186 t.Logf("Sending ^Z...")
187
188
189 if _, err := pty.Write([]byte{26}); err != nil {
190 t.Fatalf("writing ^Z to pty: %v", err)
191 }
192
193
194 if _, err := controlR.Read(b); err != nil {
195 t.Fatalf("error reading readiness: %v", err)
196 }
197
198 t.Logf("Sending SIGCONT...")
199
200
201 if err := process.Signal(syscall.SIGCONT); err != nil {
202 t.Fatalf("Signal(SIGCONT) got err %v want nil", err)
203 }
204
205
206
207 if _, err := pty.Write([]byte{'\n'}); err != nil {
208 t.Fatalf("writing %q to pty: %v", "\n", err)
209 }
210
211 t.Logf("Waiting for exit...")
212
213 if err = cmd.Wait(); err != nil {
214 t.Errorf("subprogram failed: %v", err)
215 }
216 }
217
218
219 func runSessionLeader(pause time.Duration) {
220
221
222
223
224
225
226
227
228
229
230
231
232
233 signal.Ignore(syscall.SIGTTOU)
234
235 pty := os.NewFile(ptyFD, "pty")
236 controlW := os.NewFile(controlFD, "control-pipe")
237
238
239 ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
240 defer cancel()
241 cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
242 cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=2")
243 cmd.Stdin = os.Stdin
244 cmd.Stdout = os.Stdout
245 cmd.Stderr = os.Stderr
246 cmd.ExtraFiles = []*os.File{pty}
247 cmd.SysProcAttr = &syscall.SysProcAttr{
248 Foreground: true,
249 Ctty: ptyFD,
250 }
251 if err := cmd.Start(); err != nil {
252 fmt.Fprintf(os.Stderr, "error starting second subprocess: %v\n", err)
253 os.Exit(1)
254 }
255
256 fn := func() error {
257 var b [8]byte
258 binary.LittleEndian.PutUint64(b[:], uint64(cmd.Process.Pid))
259 _, err := controlW.Write(b[:])
260 if err != nil {
261 return fmt.Errorf("error writing child pid: %w", err)
262 }
263
264
265 var status syscall.WaitStatus
266 var errno syscall.Errno
267 for {
268 _, _, errno = syscall.Syscall6(syscall.SYS_WAIT4, uintptr(cmd.Process.Pid), uintptr(unsafe.Pointer(&status)), syscall.WUNTRACED, 0, 0, 0)
269 if errno != syscall.EINTR {
270 break
271 }
272 }
273 if errno != 0 {
274 return fmt.Errorf("error waiting for stop: %w", errno)
275 }
276
277 if !status.Stopped() {
278 return fmt.Errorf("unexpected wait status: %v", status)
279 }
280
281
282 pgrp := int32(syscall.Getpgrp())
283 _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pgrp)))
284 if errno != 0 {
285 return fmt.Errorf("error setting tty process group: %w", errno)
286 }
287
288
289
290 time.Sleep(pause)
291
292
293 pid := int32(cmd.Process.Pid)
294 _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pid)))
295 if errno != 0 {
296 return fmt.Errorf("error setting tty process group back: %w", errno)
297 }
298
299
300
301 if _, err := controlW.Write(b[:1]); err != nil {
302 return fmt.Errorf("error writing readiness: %w", err)
303 }
304
305 return nil
306 }
307
308 err := fn()
309 if err != nil {
310 fmt.Fprintf(os.Stderr, "session leader error: %v\n", err)
311 cmd.Process.Kill()
312
313 }
314
315 werr := cmd.Wait()
316 if werr != nil {
317 fmt.Fprintf(os.Stderr, "error running second subprocess: %v\n", err)
318 }
319
320 if err != nil || werr != nil {
321 os.Exit(1)
322 }
323
324 os.Exit(0)
325 }
326
327
328 func runStoppingChild() {
329 pty := os.NewFile(ptyFD, "pty")
330
331 var b [1]byte
332 if _, err := pty.Write(b[:]); err != nil {
333 fmt.Fprintf(os.Stderr, "error writing byte to PTY: %v\n", err)
334 os.Exit(1)
335 }
336
337 _, err := pty.Read(b[:])
338 if err != nil {
339 fmt.Fprintln(os.Stderr, err)
340 os.Exit(1)
341 }
342 if b[0] == '\n' {
343
344 fmt.Println("read newline")
345 } else {
346 fmt.Fprintf(os.Stderr, "read 1 unexpected byte: %q\n", b)
347 os.Exit(1)
348 }
349 os.Exit(0)
350 }
351
View as plain text