github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 ) 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 // 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 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 // 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. 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 // TestExec is for issue #41702. 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 // 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. 317 func 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. 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 // 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 }