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 package syscall_test 6 7 import ( 8 "bufio" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "os/signal" 15 "path/filepath" 16 "syscall" 17 "testing" 18 "time" 19 ) 20 21 func TestMain(m *testing.M) { 22 if os.Getenv("GO_DEATHSIG_PARENT") == "1" { 23 deathSignalParent() 24 } else if os.Getenv("GO_DEATHSIG_CHILD") == "1" { 25 deathSignalChild() 26 } 27 28 os.Exit(m.Run()) 29 } 30 31 func TestLinuxDeathSignal(t *testing.T) { 32 if os.Getuid() != 0 { 33 t.Skip("skipping root only test") 34 } 35 36 // Copy the test binary to a location that a non-root user can read/execute 37 // after we drop privileges 38 tempDir, err := ioutil.TempDir("", "TestDeathSignal") 39 if err != nil { 40 t.Fatalf("cannot create temporary directory: %v", err) 41 } 42 defer os.RemoveAll(tempDir) 43 os.Chmod(tempDir, 0755) 44 45 tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0])) 46 47 src, err := os.Open(os.Args[0]) 48 if err != nil { 49 t.Fatalf("cannot open binary %q, %v", os.Args[0], err) 50 } 51 defer src.Close() 52 53 dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755) 54 if err != nil { 55 t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err) 56 } 57 if _, err := io.Copy(dst, src); err != nil { 58 t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err) 59 } 60 err = dst.Close() 61 if err != nil { 62 t.Fatalf("failed to close test binary %q, %v", tmpBinary, err) 63 } 64 65 cmd := exec.Command(tmpBinary) 66 cmd.Env = []string{"GO_DEATHSIG_PARENT=1"} 67 chldStdin, err := cmd.StdinPipe() 68 if err != nil { 69 t.Fatal("failed to create new stdin pipe: %v", err) 70 } 71 chldStdout, err := cmd.StdoutPipe() 72 if err != nil { 73 t.Fatal("failed to create new stdout pipe: %v", err) 74 } 75 cmd.Stderr = os.Stderr 76 77 err = cmd.Start() 78 defer cmd.Wait() 79 if err != nil { 80 t.Fatalf("failed to start first child process: %v", err) 81 } 82 83 chldPipe := bufio.NewReader(chldStdout) 84 85 if got, err := chldPipe.ReadString('\n'); got == "start\n" { 86 syscall.Kill(cmd.Process.Pid, syscall.SIGTERM) 87 88 go func() { 89 time.Sleep(5 * time.Second) 90 chldStdin.Close() 91 }() 92 93 want := "ok\n" 94 if got, err = chldPipe.ReadString('\n'); got != want { 95 t.Fatalf("expected %q, received %q, %v", want, got, err) 96 } 97 } else { 98 t.Fatalf("did not receive start from child, received %q, %v", got, err) 99 } 100 } 101 102 func deathSignalParent() { 103 cmd := exec.Command(os.Args[0]) 104 cmd.Env = []string{"GO_DEATHSIG_CHILD=1"} 105 cmd.Stdin = os.Stdin 106 cmd.Stdout = os.Stdout 107 attrs := syscall.SysProcAttr{ 108 Pdeathsig: syscall.SIGUSR1, 109 // UID/GID 99 is the user/group "nobody" on RHEL/Fedora and is 110 // unused on Ubuntu 111 Credential: &syscall.Credential{Uid: 99, Gid: 99}, 112 } 113 cmd.SysProcAttr = &attrs 114 115 err := cmd.Start() 116 if err != nil { 117 fmt.Fprintf(os.Stderr, "death signal parent error: %v\n") 118 os.Exit(1) 119 } 120 cmd.Wait() 121 os.Exit(0) 122 } 123 124 func deathSignalChild() { 125 c := make(chan os.Signal, 1) 126 signal.Notify(c, syscall.SIGUSR1) 127 go func() { 128 <-c 129 fmt.Println("ok") 130 os.Exit(0) 131 }() 132 fmt.Println("start") 133 134 buf := make([]byte, 32) 135 os.Stdin.Read(buf) 136 137 // We expected to be signaled before stdin closed 138 fmt.Println("not ok") 139 os.Exit(1) 140 } 141