github.com/AndrienkoAleksandr/go@v0.0.19/src/os/exec/lp_windows.go (about)

     1  // Copyright 2010 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 exec
     6  
     7  import (
     8  	"errors"
     9  	"io/fs"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"syscall"
    14  )
    15  
    16  // ErrNotFound is the error resulting if a path search failed to find an executable file.
    17  var ErrNotFound = errors.New("executable file not found in %PATH%")
    18  
    19  func chkStat(file string) error {
    20  	d, err := os.Stat(file)
    21  	if err != nil {
    22  		return err
    23  	}
    24  	if d.IsDir() {
    25  		return fs.ErrPermission
    26  	}
    27  	return nil
    28  }
    29  
    30  func hasExt(file string) bool {
    31  	i := strings.LastIndex(file, ".")
    32  	if i < 0 {
    33  		return false
    34  	}
    35  	return strings.LastIndexAny(file, `:\/`) < i
    36  }
    37  
    38  func findExecutable(file string, exts []string) (string, error) {
    39  	if len(exts) == 0 {
    40  		return file, chkStat(file)
    41  	}
    42  	if hasExt(file) {
    43  		if chkStat(file) == nil {
    44  			return file, nil
    45  		}
    46  	}
    47  	for _, e := range exts {
    48  		if f := file + e; chkStat(f) == nil {
    49  			return f, nil
    50  		}
    51  	}
    52  	return "", fs.ErrNotExist
    53  }
    54  
    55  // LookPath searches for an executable named file in the
    56  // directories named by the PATH environment variable.
    57  // LookPath also uses PATHEXT environment variable to match
    58  // a suitable candidate.
    59  // If file contains a slash, it is tried directly and the PATH is not consulted.
    60  // Otherwise, on success, the result is an absolute path.
    61  //
    62  // In older versions of Go, LookPath could return a path relative to the current directory.
    63  // As of Go 1.19, LookPath will instead return that path along with an error satisfying
    64  // errors.Is(err, ErrDot). See the package documentation for more details.
    65  func LookPath(file string) (string, error) {
    66  	var exts []string
    67  	x := os.Getenv(`PATHEXT`)
    68  	if x != "" {
    69  		for _, e := range strings.Split(strings.ToLower(x), `;`) {
    70  			if e == "" {
    71  				continue
    72  			}
    73  			if e[0] != '.' {
    74  				e = "." + e
    75  			}
    76  			exts = append(exts, e)
    77  		}
    78  	} else {
    79  		exts = []string{".com", ".exe", ".bat", ".cmd"}
    80  	}
    81  
    82  	if strings.ContainsAny(file, `:\/`) {
    83  		f, err := findExecutable(file, exts)
    84  		if err == nil {
    85  			return f, nil
    86  		}
    87  		return "", &Error{file, err}
    88  	}
    89  
    90  	// On Windows, creating the NoDefaultCurrentDirectoryInExePath
    91  	// environment variable (with any value or no value!) signals that
    92  	// path lookups should skip the current directory.
    93  	// In theory we are supposed to call NeedCurrentDirectoryForExePathW
    94  	// "as the registry location of this environment variable can change"
    95  	// but that seems exceedingly unlikely: it would break all users who
    96  	// have configured their environment this way!
    97  	// https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-needcurrentdirectoryforexepathw
    98  	// See also go.dev/issue/43947.
    99  	var (
   100  		dotf   string
   101  		dotErr error
   102  	)
   103  	if _, found := syscall.Getenv("NoDefaultCurrentDirectoryInExePath"); !found {
   104  		if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
   105  			if execerrdot.Value() == "0" {
   106  				execerrdot.IncNonDefault()
   107  				return f, nil
   108  			}
   109  			dotf, dotErr = f, &Error{file, ErrDot}
   110  		}
   111  	}
   112  
   113  	path := os.Getenv("path")
   114  	for _, dir := range filepath.SplitList(path) {
   115  		if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
   116  			if dotErr != nil {
   117  				// https://go.dev/issue/53536: if we resolved a relative path implicitly,
   118  				// and it is the same executable that would be resolved from the explicit %PATH%,
   119  				// prefer the explicit name for the executable (and, likely, no error) instead
   120  				// of the equivalent implicit name with ErrDot.
   121  				//
   122  				// Otherwise, return the ErrDot for the implicit path as soon as we find
   123  				// out that the explicit one doesn't match.
   124  				dotfi, dotfiErr := os.Lstat(dotf)
   125  				fi, fiErr := os.Lstat(f)
   126  				if dotfiErr != nil || fiErr != nil || !os.SameFile(dotfi, fi) {
   127  					return dotf, dotErr
   128  				}
   129  			}
   130  
   131  			if !filepath.IsAbs(f) {
   132  				if execerrdot.Value() != "0" {
   133  					return f, &Error{file, ErrDot}
   134  				}
   135  				execerrdot.IncNonDefault()
   136  			}
   137  			return f, nil
   138  		}
   139  	}
   140  
   141  	if dotErr != nil {
   142  		return dotf, dotErr
   143  	}
   144  	return "", &Error{file, ErrNotFound}
   145  }