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