golang.org/x/sys@v0.20.1-0.20240517151509-673e0f94c16d/execabs/execabs.go (about)

     1  // Copyright 2020 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  //
    12  // See https://blog.golang.org/path-security for more information
    13  // about when it may be necessary or appropriate to use this package.
    14  package execabs
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os/exec"
    20  	"path/filepath"
    21  	"reflect"
    22  	"unsafe"
    23  )
    24  
    25  // ErrNotFound is the error resulting if a path search failed to find an executable file.
    26  // It is an alias for exec.ErrNotFound.
    27  var ErrNotFound = exec.ErrNotFound
    28  
    29  // Cmd represents an external command being prepared or run.
    30  // It is an alias for exec.Cmd.
    31  type Cmd = exec.Cmd
    32  
    33  // Error is returned by LookPath when it fails to classify a file as an executable.
    34  // It is an alias for exec.Error.
    35  type Error = exec.Error
    36  
    37  // An ExitError reports an unsuccessful exit by a command.
    38  // It is an alias for exec.ExitError.
    39  type ExitError = exec.ExitError
    40  
    41  func relError(file, path string) error {
    42  	return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
    43  }
    44  
    45  // LookPath searches for an executable named file in the directories
    46  // named by the PATH environment variable. If file contains a slash,
    47  // it is tried directly and the PATH is not consulted. The result will be
    48  // an absolute path.
    49  //
    50  // LookPath differs from exec.LookPath in its handling of PATH lookups,
    51  // which are used for file names without slashes. If exec.LookPath's
    52  // PATH lookup would have returned an executable from the current directory,
    53  // LookPath instead returns an error.
    54  func LookPath(file string) (string, error) {
    55  	path, err := exec.LookPath(file)
    56  	if err != nil && !isGo119ErrDot(err) {
    57  		return "", err
    58  	}
    59  	if filepath.Base(file) == file && !filepath.IsAbs(path) {
    60  		return "", relError(file, path)
    61  	}
    62  	return path, nil
    63  }
    64  
    65  func fixCmd(name string, cmd *exec.Cmd) {
    66  	if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) && !isGo119ErrFieldSet(cmd) {
    67  		// exec.Command was called with a bare binary name and
    68  		// exec.LookPath returned a path which is not absolute.
    69  		// Set cmd.lookPathErr and clear cmd.Path so that it
    70  		// cannot be run.
    71  		lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
    72  		if *lookPathErr == nil {
    73  			*lookPathErr = relError(name, cmd.Path)
    74  		}
    75  		cmd.Path = ""
    76  	}
    77  }
    78  
    79  // CommandContext is like Command but includes a context.
    80  //
    81  // The provided context is used to kill the process (by calling os.Process.Kill)
    82  // if the context becomes done before the command completes on its own.
    83  func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
    84  	cmd := exec.CommandContext(ctx, name, arg...)
    85  	fixCmd(name, cmd)
    86  	return cmd
    87  
    88  }
    89  
    90  // Command returns the Cmd struct to execute the named program with the given arguments.
    91  // See exec.Command for most details.
    92  //
    93  // Command differs from exec.Command in its handling of PATH lookups,
    94  // which are used when the program name contains no slashes.
    95  // If exec.Command would have returned an exec.Cmd configured to run an
    96  // executable from the current directory, Command instead
    97  // returns an exec.Cmd that will return an error from Start or Run.
    98  func Command(name string, arg ...string) *exec.Cmd {
    99  	cmd := exec.Command(name, arg...)
   100  	fixCmd(name, cmd)
   101  	return cmd
   102  }