diff --git a/discovery.go b/discovery.go index 1901f59..95e0d04 100644 --- a/discovery.go +++ b/discovery.go @@ -191,7 +191,7 @@ func (s *PHPStore) discoverPHPViaPHP(dir, binName string) *Version { } php = filepath.Clean(php) var err error - php, err = filepath.EvalSymlinks(php) + php, err = evalSymlinks(php) if err != nil { s.log(" %s is not a valid symlink", php) return nil @@ -332,7 +332,7 @@ func (s *PHPStore) pathDirectories(configDir string) []string { seen := make(map[string]bool) for _, dir := range filepath.SplitList(path) { dir = strings.Replace(dir, "%%USERPROFILE%%", user, 1) - edir, err := filepath.EvalSymlinks(dir) + edir, err := evalSymlinks(dir) if err != nil { continue } diff --git a/fsutils_others.go b/fsutils_others.go new file mode 100644 index 0000000..c48c73c --- /dev/null +++ b/fsutils_others.go @@ -0,0 +1,7 @@ +//go:build !windows + +package phpstore + +import "path/filepath" + +var evalSymlinks = filepath.EvalSymlinks diff --git a/fsutils_windows.go b/fsutils_windows.go new file mode 100644 index 0000000..dc15bb2 --- /dev/null +++ b/fsutils_windows.go @@ -0,0 +1,39 @@ +package phpstore + +import ( + "errors" + "os" + "path/filepath" + "syscall" +) + +// Taken from https://github.com/golangci/golangci-lint/blob/main/pkg/fsutils/fsutils_windows.go + +// This is a workaround for the behavior of [filepath.EvalSymlinks], +// which fails with [syscall.ENOTDIR] if the specified path contains a junction on Windows. +// Junctions can occur, for example, when a volume is mounted as a subdirectory inside another drive. +// This can usually happen when using the Dev Drives feature and replacing existing directories. +// See: https://github.com/golang/go/issues/40180 +// +// Since [syscall.ENOTDIR] is only returned when calling [filepath.EvalSymlinks] on Windows +// if part of the presented path is a junction and nothing before was a symlink, +// we simply treat this as NOT symlink, +// because a symlink over the junction makes no sense at all. +func evalSymlinks(path string) (string, error) { + resolved, err := filepath.EvalSymlinks(path) + if err == nil { + return resolved, nil + } + + if !errors.Is(err, syscall.ENOTDIR) { + return "", err + } + + _, err = os.Stat(path) + if err != nil { + return "", err + } + + // If exists, we make the path cleaned before being used + return filepath.Abs(path), nil +} diff --git a/store.go b/store.go index dd0411e..80fd3c8 100644 --- a/store.go +++ b/store.go @@ -238,7 +238,7 @@ func (s *PHPStore) loadVersions() { // addVersion ensures that all versions are unique in the store func (s *PHPStore) addVersion(version *Version) int { idx, ok := s.seen[version.PHPPath] - sl, _ := filepath.EvalSymlinks(version.PHPPath) + sl, _ := evalSymlinks(version.PHPPath) // double-check to see if that's not just a symlink to another existing version if !ok && sl != "" { idx, ok = s.seen[sl] diff --git a/version.go b/version.go index 06f99a4..8dbc195 100644 --- a/version.go +++ b/version.go @@ -185,35 +185,35 @@ func (v *Version) setServer(fpm, cgi, phpconfig, phpize, phpdbg string) string { msg := fmt.Sprintf(" Found PHP: %s", v.PHPPath) fpm = filepath.Clean(fpm) if _, err := os.Stat(fpm); err == nil { - if fpm, err := filepath.EvalSymlinks(fpm); err == nil { + if fpm, err := evalSymlinks(fpm); err == nil { v.FPMPath = fpm msg += fmt.Sprintf(", with FPM: %s", fpm) } } cgi = filepath.Clean(cgi) if _, err := os.Stat(cgi); err == nil { - if cgi, err := filepath.EvalSymlinks(cgi); err == nil { + if cgi, err := evalSymlinks(cgi); err == nil { v.CGIPath = cgi msg += fmt.Sprintf(", with CGI: %s", cgi) } } phpconfig = filepath.Clean(phpconfig) if _, err := os.Stat(phpconfig); err == nil { - if phpconfig, err := filepath.EvalSymlinks(phpconfig); err == nil { + if phpconfig, err := evalSymlinks(phpconfig); err == nil { v.PHPConfigPath = phpconfig msg += fmt.Sprintf(", with php-config: %s", phpconfig) } } phpize = filepath.Clean(phpize) if _, err := os.Stat(phpize); err == nil { - if phpize, err := filepath.EvalSymlinks(phpize); err == nil { + if phpize, err := evalSymlinks(phpize); err == nil { v.PHPizePath = phpize msg += fmt.Sprintf(", with phpize: %s", phpize) } } phpdbg = filepath.Clean(phpdbg) if _, err := os.Stat(phpdbg); err == nil { - if phpdbg, err := filepath.EvalSymlinks(phpdbg); err == nil { + if phpdbg, err := evalSymlinks(phpdbg); err == nil { v.PHPdbgPath = phpdbg msg += fmt.Sprintf(", with phpdbg: %s", phpdbg) }