Source file src/internal/execabs/execabs.go

     1  // Copyright 2021 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 execabs is a drop-in replacement for os/exec
     6  // that requires PATH lookups to find absolute paths.
     7  // That is, execabs.Command("cmd") runs the same PATH lookup
     8  // as exec.Command("cmd"), but if the result is a path
     9  // which is relative, the Run and Start methods will report
    10  // an error instead of running the executable.
    11  package execabs
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"reflect"
    19  	"unsafe"
    20  )
    21  
    22  var ErrNotFound = exec.ErrNotFound
    23  
    24  type (
    25  	Cmd       = exec.Cmd
    26  	Error     = exec.Error
    27  	ExitError = exec.ExitError
    28  )
    29  
    30  func relError(file, path string) error {
    31  	return fmt.Errorf("%s resolves to executable relative to current directory (.%c%s)", file, filepath.Separator, path)
    32  }
    33  
    34  func LookPath(file string) (string, error) {
    35  	path, err := exec.LookPath(file)
    36  	if err != nil {
    37  		return "", err
    38  	}
    39  	if filepath.Base(file) == file && !filepath.IsAbs(path) {
    40  		return "", relError(file, path)
    41  	}
    42  	return path, nil
    43  }
    44  
    45  func fixCmd(name string, cmd *exec.Cmd) {
    46  	if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
    47  		// exec.Command was called with a bare binary name and
    48  		// exec.LookPath returned a path which is not absolute.
    49  		// Set cmd.lookPathErr and clear cmd.Path so that it
    50  		// cannot be run.
    51  		lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
    52  		if *lookPathErr == nil {
    53  			*lookPathErr = relError(name, cmd.Path)
    54  		}
    55  		cmd.Path = ""
    56  	}
    57  }
    58  
    59  func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
    60  	cmd := exec.CommandContext(ctx, name, arg...)
    61  	fixCmd(name, cmd)
    62  	return cmd
    63  
    64  }
    65  
    66  func Command(name string, arg ...string) *exec.Cmd {
    67  	cmd := exec.Command(name, arg...)
    68  	fixCmd(name, cmd)
    69  	return cmd
    70  }
    71  

View as plain text