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 }