Skip to content

Commit e409a77

Browse files
committed
Enforced Windows symbolic link detection consistency with Go.
We were previously using an incorrect (or at least not completely correct) symbolic link detection mechanism that differed from that of the Go standard library. On Windows, where we use the os package to drive some of our filesystem implementation, this led to spurious symbolic link reports. This commit unifies Mutagen's symbolic link definition with that of the Go standard library. Note that the Go standard library still has an open issue regarding symbolic link classification with WSL symbolic links (golang/go#42184), so we inherit that for now, but we should be able to adjust our definition once that issue is resolved. Fixes #248. Signed-off-by: Jacob Howard <[email protected]>
1 parent 71e4639 commit e409a77

File tree

3 files changed

+62
-36
lines changed

3 files changed

+62
-36
lines changed

pkg/filesystem/directory_windows.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"path/filepath"
66
"strings"
77
"syscall"
8+
"unsafe"
89

910
"github.com/pkg/errors"
1011

@@ -13,6 +14,8 @@ import (
1314
aclapi "github.com/hectane/go-acl/api"
1415

1516
osvendor "github.com/mutagen-io/mutagen/pkg/filesystem/internal/third_party/os"
17+
18+
fssyscall "github.com/mutagen-io/mutagen/pkg/filesystem/internal/syscall"
1619
)
1720

1821
// ensureValidName verifies that the provided name does not reference the
@@ -243,27 +246,26 @@ func (d *Directory) openHandle(name string, wantDirectory bool) (string, windows
243246
return "", 0, errors.Wrap(err, "unable to open path")
244247
}
245248

246-
// Query file metadata.
247-
var metadata windows.ByHandleFileInformation
248-
if err := windows.GetFileInformationByHandle(handle, &metadata); err != nil {
249+
// Query attribute metadata.
250+
var attributes fssyscall.FileAttributeTagInfo
251+
if err := windows.GetFileInformationByHandleEx(
252+
handle,
253+
windows.FileAttributeTagInfo,
254+
(*byte)(unsafe.Pointer(&attributes)),
255+
uint32(unsafe.Sizeof(attributes)),
256+
); err != nil {
249257
windows.CloseHandle(handle)
250-
return "", 0, errors.Wrap(err, "unable to query file metadata")
251-
}
252-
253-
// Verify that the handle does not represent a symbolic link and that the
254-
// type coincides with what we want. Note that FILE_ATTRIBUTE_REPARSE_POINT
255-
// can be or'd with FILE_ATTRIBUTE_DIRECTORY (since symbolic links are
256-
// "typed" on Windows), so we have to explicitly exclude reparse points
257-
// before checking types.
258-
//
259-
// TODO: Are there additional attributes upon which we should reject here?
260-
// The Go os.File implementation doesn't seem to for normal os.Open
261-
// operations, so I guess we don't need to either, but we should keep the
262-
// option in mind.
263-
if metadata.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
258+
return "", 0, errors.Wrap(err, "unable to query attribute metadata")
259+
}
260+
261+
// Verify that the attribute metadata doesn't indicate a symbolic link.
262+
if attributes.IsSymbolicLink() {
264263
windows.CloseHandle(handle)
265264
return "", 0, errors.New("path pointed to symbolic link")
266-
} else if wantDirectory && metadata.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 {
265+
}
266+
267+
// Verify that the handle corresponds to a directory (if requested).
268+
if wantDirectory && attributes.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 {
267269
windows.CloseHandle(handle)
268270
return "", 0, errors.New("path pointed to non-directory location")
269271
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package syscall
2+
3+
import (
4+
"golang.org/x/sys/windows"
5+
)
6+
7+
// FileAttributeTagInfo is the Go representation of FILE_ATTRIBUTE_TAG_INFO.
8+
type FileAttributeTagInfo struct {
9+
// FileAttributes are the file attributes.
10+
FileAttributes uint32
11+
// ReparseTag is the file reparse tag.
12+
ReparseTag uint32
13+
}
14+
15+
// IsSymbolicLink returns whether or not the file attributes indicate a symbolic
16+
// link. This method must match the implementation of os.fileStat.isSymlink in
17+
// the Go standard library in order for code in the filesystem package to work
18+
// correctly (since it partially relies on os package functionality on Windows
19+
// and will see inconsistent classification behavior otherwise).
20+
func (i *FileAttributeTagInfo) IsSymbolicLink() bool {
21+
if i.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
22+
return false
23+
}
24+
// TODO: Update this definition once golang/go#42184 is resolved.
25+
return i.ReparseTag == windows.IO_REPARSE_TAG_SYMLINK ||
26+
i.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
27+
}

pkg/filesystem/open_windows.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import (
44
"io"
55
"os"
66
"path/filepath"
7+
"unsafe"
78

89
"github.com/pkg/errors"
910

1011
"golang.org/x/sys/windows"
1112

1213
osvendor "github.com/mutagen-io/mutagen/pkg/filesystem/internal/third_party/os"
14+
15+
fssyscall "github.com/mutagen-io/mutagen/pkg/filesystem/internal/syscall"
1316
)
1417

1518
// Open opens a filesystem path for traversal and operations. It will return
@@ -64,32 +67,26 @@ func Open(path string, allowSymbolicLinkLeaf bool) (io.Closer, *Metadata, error)
6467
return nil, nil, errors.Wrap(err, "unable to open path")
6568
}
6669

67-
// Query raw file metadata.
68-
var rawMetadata windows.ByHandleFileInformation
69-
if err := windows.GetFileInformationByHandle(handle, &rawMetadata); err != nil {
70+
// Query attribute metadata.
71+
var attributes fssyscall.FileAttributeTagInfo
72+
if err := windows.GetFileInformationByHandleEx(
73+
handle,
74+
windows.FileAttributeTagInfo,
75+
(*byte)(unsafe.Pointer(&attributes)),
76+
uint32(unsafe.Sizeof(attributes)),
77+
); err != nil {
7078
windows.CloseHandle(handle)
71-
return nil, nil, errors.Wrap(err, "unable to query file metadata")
79+
return nil, nil, errors.Wrap(err, "unable to query attribute metadata")
7280
}
7381

74-
// Verify that the handle does not represent a symbolic link. Even if we
75-
// allow symbolic links in the leaf position of the path, we should not end
76-
// up with one (it should resolve).
77-
//
78-
// Note that FILE_ATTRIBUTE_REPARSE_POINT can be or'd with
79-
// FILE_ATTRIBUTE_DIRECTORY (since symbolic links are "typed" on Windows),
80-
// so we have to explicitly exclude reparse points before checking types.
81-
//
82-
// TODO: Are there additional attributes upon which we should reject here?
83-
// The Go os.File implementation doesn't seem to for normal os.Open
84-
// operations, so I guess we don't need to either, but we should keep the
85-
// option in mind.
86-
if rawMetadata.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
82+
// Verify that the attribute metadata doesn't indicate a symbolic link.
83+
if attributes.IsSymbolicLink() {
8784
windows.CloseHandle(handle)
8885
return nil, nil, ErrUnsupportedOpenType
8986
}
9087

9188
// Determine whether or not this is a directory.
92-
isDirectory := rawMetadata.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0
89+
isDirectory := attributes.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0
9390

9491
// Handle os.File creation based on type.
9592
var file *os.File

0 commit comments

Comments
 (0)