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