Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions internal/vfs/internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If something is a junction, maybe we don't even need to stat it and just say that it's a dir? (I don't believe junctions/reparse points can be files, in which case I could rename funcs too)

addToResult(entry.Name(), stat.Mode())
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/vfs/osvfs/os.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}

Expand Down
7 changes: 7 additions & 0 deletions internal/vfs/osvfs/symlink_other.go
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions internal/vfs/osvfs/symlink_windows.go
Original file line number Diff line number Diff line change
@@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to prepend \\?\ to automatically handle long filepaths? https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesexa

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, crap, converting those is a whole thing that is hard and not available to us outside stdlib. I'd need to do something like I did in realpath

func realpath(path string) (string, error) {
	var h windows.Handle
	if len(path) < 248 {
		var err error
		h, err = openMetadata(path)
		if err != nil {
			return "", err
		}
		defer windows.CloseHandle(h) //nolint:errcheck
	} else {
		// For long paths, defer to os.Open to run the path through fixLongPath.
		f, err := os.Open(path)
		if err != nil {
			return "", err
		}
		defer f.Close()

		// Works on directories too since https://go.dev/cl/405275.
		h = windows.Handle(f.Fd())
	}

Guess I'll wait on things until I figure that out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't block this on it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty easy to hit this on the use cases where it matters, e.g. pnpm ones, so I should definitely check it in afraid

pathUTF16,
syscall.GetFileExInfoStandard,
(*byte)(unsafe.Pointer(&data)),
)
if err != nil {
return false
}

return data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0
}