Source file src/os/pidfd_linux_test.go

     1  // Copyright 2023 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 os_test
     6  
     7  import (
     8  	"errors"
     9  	"internal/syscall/unix"
    10  	"internal/testenv"
    11  	"os"
    12  	"os/exec"
    13  	"syscall"
    14  	"testing"
    15  	"time"
    16  )
    17  
    18  func TestFindProcessViaPidfd(t *testing.T) {
    19  	testenv.MustHaveGoBuild(t)
    20  	t.Parallel()
    21  
    22  	if err := os.CheckPidfdOnce(); err != nil {
    23  		// Non-pidfd code paths tested in exec_unix_test.go.
    24  		t.Skipf("skipping: pidfd not available: %v", err)
    25  	}
    26  
    27  	p, err := os.StartProcess(testenv.GoToolPath(t), []string{"go"}, &os.ProcAttr{})
    28  	if err != nil {
    29  		t.Fatalf("starting test process: %v", err)
    30  	}
    31  	p.Wait()
    32  
    33  	// Use pid of a non-existing process.
    34  	proc, err := os.FindProcess(p.Pid)
    35  	// FindProcess should never return errors on Unix.
    36  	if err != nil {
    37  		t.Fatalf("FindProcess: got error %v, want <nil>", err)
    38  	}
    39  	// FindProcess should never return nil Process.
    40  	if proc == nil {
    41  		t.Fatal("FindProcess: got nil, want non-nil")
    42  	}
    43  	if proc.Status() != os.StatusDone {
    44  		t.Fatalf("got process status: %v, want %d", proc.Status(), os.StatusDone)
    45  	}
    46  
    47  	// Check that all Process' public methods work as expected with
    48  	// "done" Process.
    49  	if err := proc.Kill(); err != os.ErrProcessDone {
    50  		t.Errorf("Kill: got %v, want %v", err, os.ErrProcessDone)
    51  	}
    52  	if err := proc.Signal(os.Kill); err != os.ErrProcessDone {
    53  		t.Errorf("Signal: got %v, want %v", err, os.ErrProcessDone)
    54  	}
    55  	if _, err := proc.Wait(); !errors.Is(err, syscall.ECHILD) {
    56  		t.Errorf("Wait: got %v, want %v", err, os.ErrProcessDone)
    57  	}
    58  	// Release never returns errors on Unix.
    59  	if err := proc.Release(); err != nil {
    60  		t.Fatalf("Release: got %v, want <nil>", err)
    61  	}
    62  }
    63  
    64  func TestStartProcessWithPidfd(t *testing.T) {
    65  	testenv.MustHaveGoBuild(t)
    66  	t.Parallel()
    67  
    68  	if err := os.CheckPidfdOnce(); err != nil {
    69  		// Non-pidfd code paths tested in exec_unix_test.go.
    70  		t.Skipf("skipping: pidfd not available: %v", err)
    71  	}
    72  
    73  	var pidfd int
    74  	p, err := os.StartProcess(testenv.GoToolPath(t), []string{"go"}, &os.ProcAttr{
    75  		Sys: &syscall.SysProcAttr{
    76  			PidFD: &pidfd,
    77  		},
    78  	})
    79  	if err != nil {
    80  		t.Fatalf("starting test process: %v", err)
    81  	}
    82  	defer syscall.Close(pidfd)
    83  
    84  	if _, err := p.Wait(); err != nil {
    85  		t.Fatalf("Wait: got %v, want <nil>", err)
    86  	}
    87  
    88  	// Check the pidfd is still valid
    89  	err = unix.PidFDSendSignal(uintptr(pidfd), syscall.Signal(0))
    90  	if !errors.Is(err, syscall.ESRCH) {
    91  		t.Errorf("SendSignal: got %v, want %v", err, syscall.ESRCH)
    92  	}
    93  }
    94  
    95  // Issue #69284
    96  func TestPidfdLeak(t *testing.T) {
    97  	exe := testenv.Executable(t)
    98  
    99  	// Find the next 10 descriptors.
   100  	// We need to get more than one descriptor in practice;
   101  	// the pidfd winds up not being the next descriptor.
   102  	const count = 10
   103  	want := make([]int, count)
   104  	for i := range count {
   105  		var err error
   106  		want[i], err = syscall.Open(exe, syscall.O_RDONLY, 0)
   107  		if err != nil {
   108  			t.Fatal(err)
   109  		}
   110  	}
   111  
   112  	// Close the descriptors.
   113  	for _, d := range want {
   114  		syscall.Close(d)
   115  	}
   116  
   117  	// Start a process 10 times.
   118  	for range 10 {
   119  		// For testing purposes this has to be an absolute path.
   120  		// Otherwise we will fail finding the executable
   121  		// and won't start a process at all.
   122  		cmd := exec.Command("/noSuchExecutable")
   123  		cmd.Run()
   124  	}
   125  
   126  	// Open the next 10 descriptors again.
   127  	got := make([]int, count)
   128  	for i := range count {
   129  		var err error
   130  		got[i], err = syscall.Open(exe, syscall.O_RDONLY, 0)
   131  		if err != nil {
   132  			t.Fatal(err)
   133  		}
   134  	}
   135  
   136  	// Close the descriptors
   137  	for _, d := range got {
   138  		syscall.Close(d)
   139  	}
   140  
   141  	t.Logf("got %v", got)
   142  	t.Logf("want %v", want)
   143  
   144  	// Allow some slack for runtime epoll descriptors and the like.
   145  	if got[count-1] > want[count-1]+5 {
   146  		t.Errorf("got descriptor %d, want %d", got[count-1], want[count-1])
   147  	}
   148  }
   149  
   150  func TestProcessWithHandleLinux(t *testing.T) {
   151  	t.Parallel()
   152  	havePidfd := os.CheckPidfdOnce() == nil
   153  
   154  	const envVar = "OSTEST_PROCESS_WITH_HANDLE"
   155  	if os.Getenv(envVar) != "" {
   156  		time.Sleep(1 * time.Minute)
   157  		return
   158  	}
   159  
   160  	cmd := testenv.CommandContext(t, t.Context(), testenv.Executable(t), "-test.run=^"+t.Name()+"$")
   161  	cmd = testenv.CleanCmdEnv(cmd)
   162  	cmd.Env = append(cmd.Env, envVar+"=1")
   163  	if err := cmd.Start(); err != nil {
   164  		t.Fatal(err)
   165  	}
   166  	defer func() {
   167  		cmd.Process.Kill()
   168  		cmd.Wait()
   169  	}()
   170  
   171  	const sig = syscall.SIGINT
   172  	called := false
   173  	err := cmd.Process.WithHandle(func(pidfd uintptr) {
   174  		called = true
   175  		// Check the provided pidfd is valid, and terminate the child.
   176  		err := unix.PidFDSendSignal(pidfd, sig)
   177  		if err != nil {
   178  			t.Errorf("PidFDSendSignal: got error %v, want nil", err)
   179  		}
   180  	})
   181  	// If pidfd is not supported, WithHandle should fail.
   182  	if !havePidfd && err == nil {
   183  		t.Fatal("WithHandle: got nil, want error")
   184  	}
   185  	// If pidfd is supported, WithHandle should succeed.
   186  	if havePidfd && err != nil {
   187  		t.Fatalf("WithHandle: got error %v, want nil", err)
   188  	}
   189  	// If pidfd is supported, function should have been called, and vice versa.
   190  	if havePidfd != called {
   191  		t.Fatalf("WithHandle: havePidfd is %v, but called is %v", havePidfd, called)
   192  	}
   193  	// If pidfd is supported, wait on the child process to check it worked as intended.
   194  	if called {
   195  		err := cmd.Wait()
   196  		if err == nil {
   197  			t.Fatal("Wait: want error, got nil")
   198  		}
   199  		st := cmd.ProcessState.Sys().(syscall.WaitStatus)
   200  		if !st.Signaled() {
   201  			t.Fatal("ProcessState: want Signaled, got", err)
   202  		}
   203  		if gotSig := st.Signal(); sig != gotSig {
   204  			t.Fatalf("ProcessState.Signal: want %v, got %v", sig, gotSig)
   205  		}
   206  		// Finally, check that WithHandle now returns ErrProcessDone.
   207  		called = false
   208  		err = cmd.Process.WithHandle(func(_ uintptr) {
   209  			called = true
   210  		})
   211  		if err != os.ErrProcessDone {
   212  			t.Fatalf("WithHandle: want os.ErrProcessDone, got %v", err)
   213  		}
   214  		if called {
   215  			t.Fatal("called: want false, got true")
   216  		}
   217  	}
   218  }
   219  

View as plain text