Source file src/os/exec/exec_posix_test.go

     1  // Copyright 2017 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 exec_test
     8  
     9  import (
    10  	"fmt"
    11  	"internal/testenv"
    12  	"io"
    13  	"os"
    14  	"os/user"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"syscall"
    21  	"testing"
    22  	"time"
    23  )
    24  
    25  func init() {
    26  	registerHelperCommand("pwd", cmdPwd)
    27  }
    28  
    29  func cmdPwd(...string) {
    30  	pwd, err := os.Getwd()
    31  	if err != nil {
    32  		fmt.Fprintln(os.Stderr, err)
    33  		os.Exit(1)
    34  	}
    35  	fmt.Println(pwd)
    36  }
    37  
    38  func TestCredentialNoSetGroups(t *testing.T) {
    39  	if runtime.GOOS == "android" {
    40  		maySkipHelperCommand("echo")
    41  		t.Skip("unsupported on Android")
    42  	}
    43  	t.Parallel()
    44  
    45  	u, err := user.Current()
    46  	if err != nil {
    47  		t.Fatalf("error getting current user: %v", err)
    48  	}
    49  
    50  	uid, err := strconv.Atoi(u.Uid)
    51  	if err != nil {
    52  		t.Fatalf("error converting Uid=%s to integer: %v", u.Uid, err)
    53  	}
    54  
    55  	gid, err := strconv.Atoi(u.Gid)
    56  	if err != nil {
    57  		t.Fatalf("error converting Gid=%s to integer: %v", u.Gid, err)
    58  	}
    59  
    60  	// If NoSetGroups is true, setgroups isn't called and cmd.Run should succeed
    61  	cmd := helperCommand(t, "echo", "foo")
    62  	cmd.SysProcAttr = &syscall.SysProcAttr{
    63  		Credential: &syscall.Credential{
    64  			Uid:         uint32(uid),
    65  			Gid:         uint32(gid),
    66  			NoSetGroups: true,
    67  		},
    68  	}
    69  
    70  	if err = cmd.Run(); err != nil {
    71  		t.Errorf("Failed to run command: %v", err)
    72  	}
    73  }
    74  
    75  // For issue #19314: make sure that SIGSTOP does not cause the process
    76  // to appear done.
    77  func TestWaitid(t *testing.T) {
    78  	t.Parallel()
    79  
    80  	cmd := helperCommand(t, "pipetest")
    81  	stdin, err := cmd.StdinPipe()
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	stdout, err := cmd.StdoutPipe()
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  	if err := cmd.Start(); err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	// Wait for the child process to come up and register any signal handlers.
    94  	const msg = "O:ping\n"
    95  	if _, err := io.WriteString(stdin, msg); err != nil {
    96  		t.Fatal(err)
    97  	}
    98  	buf := make([]byte, len(msg))
    99  	if _, err := io.ReadFull(stdout, buf); err != nil {
   100  		t.Fatal(err)
   101  	}
   102  	// Now leave the pipes open so that the process will hang until we close stdin.
   103  
   104  	if err := cmd.Process.Signal(syscall.SIGSTOP); err != nil {
   105  		cmd.Process.Kill()
   106  		t.Fatal(err)
   107  	}
   108  
   109  	ch := make(chan error)
   110  	go func() {
   111  		ch <- cmd.Wait()
   112  	}()
   113  
   114  	// Give a little time for Wait to block on waiting for the process.
   115  	// (This is just to give some time to trigger the bug; it should not be
   116  	// necessary for the test to pass.)
   117  	if testing.Short() {
   118  		time.Sleep(1 * time.Millisecond)
   119  	} else {
   120  		time.Sleep(10 * time.Millisecond)
   121  	}
   122  
   123  	// This call to Signal should succeed because the process still exists.
   124  	// (Prior to the fix for #19314, this would fail with os.ErrProcessDone
   125  	// or an equivalent error.)
   126  	if err := cmd.Process.Signal(syscall.SIGCONT); err != nil {
   127  		t.Error(err)
   128  		syscall.Kill(cmd.Process.Pid, syscall.SIGCONT)
   129  	}
   130  
   131  	// The SIGCONT should allow the process to wake up, notice that stdin
   132  	// is closed, and exit successfully.
   133  	stdin.Close()
   134  	err = <-ch
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  }
   139  
   140  // https://go.dev/issue/50599: if Env is not set explicitly, setting Dir should
   141  // implicitly update PWD to the correct path, and Environ should list the
   142  // updated value.
   143  func TestImplicitPWD(t *testing.T) {
   144  	t.Parallel()
   145  
   146  	cwd, err := os.Getwd()
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  
   151  	cases := []struct {
   152  		name string
   153  		dir  string
   154  		want string
   155  	}{
   156  		{"empty", "", cwd},
   157  		{"dot", ".", cwd},
   158  		{"dotdot", "..", filepath.Dir(cwd)},
   159  		{"PWD", cwd, cwd},
   160  		{"PWDdotdot", cwd + string(filepath.Separator) + "..", filepath.Dir(cwd)},
   161  	}
   162  
   163  	for _, tc := range cases {
   164  		tc := tc
   165  		t.Run(tc.name, func(t *testing.T) {
   166  			t.Parallel()
   167  
   168  			cmd := helperCommand(t, "pwd")
   169  			if cmd.Env != nil {
   170  				t.Fatalf("test requires helperCommand not to set Env field")
   171  			}
   172  			cmd.Dir = tc.dir
   173  
   174  			var pwds []string
   175  			for _, kv := range cmd.Environ() {
   176  				if strings.HasPrefix(kv, "PWD=") {
   177  					pwds = append(pwds, strings.TrimPrefix(kv, "PWD="))
   178  				}
   179  			}
   180  
   181  			wantPWDs := []string{tc.want}
   182  			if tc.dir == "" {
   183  				if _, ok := os.LookupEnv("PWD"); !ok {
   184  					wantPWDs = nil
   185  				}
   186  			}
   187  			if !reflect.DeepEqual(pwds, wantPWDs) {
   188  				t.Errorf("PWD entries in cmd.Environ():\n\t%s\nwant:\n\t%s", strings.Join(pwds, "\n\t"), strings.Join(wantPWDs, "\n\t"))
   189  			}
   190  
   191  			cmd.Stderr = new(strings.Builder)
   192  			out, err := cmd.Output()
   193  			if err != nil {
   194  				t.Fatalf("%v:\n%s", err, cmd.Stderr)
   195  			}
   196  			got := strings.Trim(string(out), "\r\n")
   197  			t.Logf("in\n\t%s\n`pwd` reported\n\t%s", tc.dir, got)
   198  			if got != tc.want {
   199  				t.Errorf("want\n\t%s", tc.want)
   200  			}
   201  		})
   202  	}
   203  }
   204  
   205  // However, if cmd.Env is set explicitly, setting Dir should not override it.
   206  // (This checks that the implementation for https://go.dev/issue/50599 doesn't
   207  // break existing users who may have explicitly mismatched the PWD variable.)
   208  func TestExplicitPWD(t *testing.T) {
   209  	t.Parallel()
   210  
   211  	maySkipHelperCommand("pwd")
   212  	testenv.MustHaveSymlink(t)
   213  
   214  	cwd, err := os.Getwd()
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	link := filepath.Join(t.TempDir(), "link")
   220  	if err := os.Symlink(cwd, link); err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	// Now link is another equally-valid name for cwd. If we set Dir to one and
   225  	// PWD to the other, the subprocess should report the PWD version.
   226  	cases := []struct {
   227  		name string
   228  		dir  string
   229  		pwd  string
   230  	}{
   231  		{name: "original PWD", pwd: cwd},
   232  		{name: "link PWD", pwd: link},
   233  		{name: "in link with original PWD", dir: link, pwd: cwd},
   234  		{name: "in dir with link PWD", dir: cwd, pwd: link},
   235  		// Ideally we would also like to test what happens if we set PWD to
   236  		// something totally bogus (or the empty string), but then we would have no
   237  		// idea what output the subprocess should actually produce: cwd itself may
   238  		// contain symlinks preserved from the PWD value in the test's environment.
   239  	}
   240  	for _, tc := range cases {
   241  		tc := tc
   242  		t.Run(tc.name, func(t *testing.T) {
   243  			t.Parallel()
   244  
   245  			cmd := helperCommand(t, "pwd")
   246  			// This is intentionally opposite to the usual order of setting cmd.Dir
   247  			// and then calling cmd.Environ. Here, we *want* PWD not to match cmd.Dir,
   248  			// so we don't care whether cmd.Dir is reflected in cmd.Environ.
   249  			cmd.Env = append(cmd.Environ(), "PWD="+tc.pwd)
   250  			cmd.Dir = tc.dir
   251  
   252  			var pwds []string
   253  			for _, kv := range cmd.Environ() {
   254  				if strings.HasPrefix(kv, "PWD=") {
   255  					pwds = append(pwds, strings.TrimPrefix(kv, "PWD="))
   256  				}
   257  			}
   258  
   259  			wantPWDs := []string{tc.pwd}
   260  			if !reflect.DeepEqual(pwds, wantPWDs) {
   261  				t.Errorf("PWD entries in cmd.Environ():\n\t%s\nwant:\n\t%s", strings.Join(pwds, "\n\t"), strings.Join(wantPWDs, "\n\t"))
   262  			}
   263  
   264  			cmd.Stderr = new(strings.Builder)
   265  			out, err := cmd.Output()
   266  			if err != nil {
   267  				t.Fatalf("%v:\n%s", err, cmd.Stderr)
   268  			}
   269  			got := strings.Trim(string(out), "\r\n")
   270  			t.Logf("in\n\t%s\nwith PWD=%s\nsubprocess os.Getwd() reported\n\t%s", tc.dir, tc.pwd, got)
   271  			if got != tc.pwd {
   272  				t.Errorf("want\n\t%s", tc.pwd)
   273  			}
   274  		})
   275  	}
   276  }
   277  

View as plain text