diff --git a/internal/vfs/internal/internal.go b/internal/vfs/internal/internal.go index c761e20388..5376a91182 100644 --- a/internal/vfs/internal/internal.go +++ b/internal/vfs/internal/internal.go @@ -13,8 +13,8 @@ import ( ) type Common struct { - RootFor func(root string) fs.FS - Realpath func(path string) string + RootFor func(root string) fs.FS + IsSymlinkOrJunction func(path string) bool } func RootLength(p string) int { @@ -93,12 +93,12 @@ func (vfs *Common) GetAccessibleEntries(path string) (result vfs.Entries) { continue } - if entryType&fs.ModeIrregular != 0 && vfs.Realpath != nil { - // Could be a Windows junction. Try Realpath. - // TODO(jakebailey): use syscall.Win32FileAttributeData instead + if entryType&fs.ModeIrregular != 0 && vfs.IsSymlinkOrJunction != nil { + // Could be a Windows junction or other reparse point. + // Check using the OS-specific helper. fullPath := path + "/" + entry.Name() - if realpath := vfs.Realpath(fullPath); fullPath != realpath { - if stat := vfs.Stat(realpath); stat != nil { + if vfs.IsSymlinkOrJunction(fullPath) { + if stat := vfs.Stat(fullPath); stat != nil { addToResult(entry.Name(), stat.Mode()) } } diff --git a/internal/vfs/osvfs/os.go b/internal/vfs/osvfs/os.go index d811d6ff71..fd37954693 100644 --- a/internal/vfs/osvfs/os.go +++ b/internal/vfs/osvfs/os.go @@ -21,8 +21,8 @@ func FS() vfs.FS { var osVFS vfs.FS = &osFS{ common: internal.Common{ - RootFor: os.DirFS, - Realpath: osFSRealpath, + RootFor: os.DirFS, + IsSymlinkOrJunction: isSymlinkOrJunction, }, } diff --git a/internal/vfs/osvfs/symlink_other.go b/internal/vfs/osvfs/symlink_other.go new file mode 100644 index 0000000000..799c60e22f --- /dev/null +++ b/internal/vfs/osvfs/symlink_other.go @@ -0,0 +1,7 @@ +//go:build !windows + +package osvfs + +// On Unix-like systems, symlinks are already properly detected by the +// fs.ModeSymlink bit in the directory entry type, so this check is not needed. +var isSymlinkOrJunction func(path string) bool diff --git a/internal/vfs/osvfs/symlink_windows.go b/internal/vfs/osvfs/symlink_windows.go new file mode 100644 index 0000000000..66581fccce --- /dev/null +++ b/internal/vfs/osvfs/symlink_windows.go @@ -0,0 +1,27 @@ +package osvfs + +import ( + "syscall" + "unsafe" +) + +// isSymlinkOrJunction checks if the given path is a symlink or junction point +// on Windows by checking the FILE_ATTRIBUTE_REPARSE_POINT attribute. +func isSymlinkOrJunction(path string) bool { + pathUTF16, err := syscall.UTF16PtrFromString(path) + if err != nil { + return false + } + + var data syscall.Win32FileAttributeData + err = syscall.GetFileAttributesEx( + pathUTF16, + syscall.GetFileExInfoStandard, + (*byte)(unsafe.Pointer(&data)), + ) + if err != nil { + return false + } + + return data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 +}