aboutsummaryrefslogtreecommitdiff
path: root/src/os/stat_windows.go
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2023-01-04 17:23:46 -0500
committerGopher Robot <gobot@golang.org>2023-01-23 22:46:55 +0000
commit3e44b7d07a7b3c6233eb1bf4cf3cb00a0b85adec (patch)
tree7d404b995c7cba691854cd313f993e0e38987c50 /src/os/stat_windows.go
parent2423370136d4b1915d06bb1aaacbedaa900bc5c7 (diff)
downloadgo-3e44b7d07a7b3c6233eb1bf4cf3cb00a0b85adec.tar.xz
os: treat non-symlink reparse points as irregular files
Prior to this change (as of CL 143578), our stat function attempted to resolve all reparse points as if they were symlinks. This results in an additional call to CreateFile when statting a symlink file: we use CreateFile once to obtain the reparse tag and check whether the file is actually a symlink, and if it is we call CreateFile again without FILE_FLAG_OPEN_REPARSE_POINT to stat the link target. Fortunately, since symlinks are rare on Windows that overhead shouldn't be a big deal in practice. Fixes #42919. Change-Id: If453930c6e98040cd6525ac4aea60a84498c9579 Reviewed-on: https://go-review.googlesource.com/c/go/+/460595 Reviewed-by: Quim Muntal <quimmuntal@gmail.com> Run-TryBot: Bryan Mills <bcmills@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Cherry Mui <cherryyz@google.com> Auto-Submit: Bryan Mills <bcmills@google.com>
Diffstat (limited to 'src/os/stat_windows.go')
-rw-r--r--src/os/stat_windows.go77
1 files changed, 49 insertions, 28 deletions
diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go
index 8747c19888..7ac9f7b860 100644
--- a/src/os/stat_windows.go
+++ b/src/os/stat_windows.go
@@ -20,7 +20,7 @@ func (file *File) Stat() (FileInfo, error) {
}
// stat implements both Stat and Lstat of a file.
-func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
+func stat(funcname, name string, followSymlinks bool) (FileInfo, error) {
if len(name) == 0 {
return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
@@ -33,8 +33,29 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
// See https://golang.org/issues/19922#issuecomment-300031421 for details.
var fa syscall.Win32FileAttributeData
err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+
+ // GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
+ // files like c:\pagefile.sys. Use FindFirstFile for such files.
+ if err == windows.ERROR_SHARING_VIOLATION {
+ var fd syscall.Win32finddata
+ sh, err := syscall.FindFirstFile(namep, &fd)
+ if err != nil {
+ return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
+ }
+ syscall.FindClose(sh)
+ if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ // Not a symlink or mount point. FindFirstFile is good enough.
+ fs := newFileStatFromWin32finddata(&fd)
+ if err := fs.saveInfoFromPath(name); err != nil {
+ return nil, err
+ }
+ return fs, nil
+ }
+ }
+
if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
- // Not a symlink.
+ // The file is definitely not a symlink, because it isn't any kind of reparse point.
+ // The information we got from GetFileAttributesEx is good enough for now.
fs := &fileStat{
FileAttributes: fa.FileAttributes,
CreationTime: fa.CreationTime,
@@ -48,30 +69,34 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
}
return fs, nil
}
- // GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
- // files, like c:\pagefile.sys. Use FindFirstFile for such files.
- if err == windows.ERROR_SHARING_VIOLATION {
- var fd syscall.Win32finddata
- sh, err := syscall.FindFirstFile(namep, &fd)
- if err != nil {
- return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
- }
- syscall.FindClose(sh)
- fs := newFileStatFromWin32finddata(&fd)
- if err := fs.saveInfoFromPath(name); err != nil {
- return nil, err
- }
- return fs, nil
- }
- // Finally use CreateFile.
- h, err := syscall.CreateFile(namep, 0, 0, nil,
- syscall.OPEN_EXISTING, createFileAttrs, 0)
+ // Use CreateFile to determine whether the file is a symlink and, if so,
+ // save information about the link target.
+ // Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
+ // even if name refers to a directory.
+ h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
if err != nil {
+ // Since CreateFile failed, we can't determine whether name refers to a
+ // symlink, or some other kind of reparse point. Since we can't return a
+ // FileInfo with a known-accurate Mode, we must return an error.
return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
}
- defer syscall.CloseHandle(h)
- return statHandle(name, h)
+
+ fi, err := statHandle(name, h)
+ syscall.CloseHandle(h)
+ if err == nil && followSymlinks && fi.(*fileStat).isSymlink() {
+ // To obtain information about the link target, we reopen the file without
+ // FILE_FLAG_OPEN_REPARSE_POINT and examine the resulting handle.
+ // (See https://devblogs.microsoft.com/oldnewthing/20100212-00/?p=14963.)
+ h, err = syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if err != nil {
+ // name refers to a symlink, but we couldn't resolve the symlink target.
+ return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
+ }
+ defer syscall.CloseHandle(h)
+ return statHandle(name, h)
+ }
+ return fi, err
}
func statHandle(name string, h syscall.Handle) (FileInfo, error) {
@@ -93,14 +118,10 @@ func statHandle(name string, h syscall.Handle) (FileInfo, error) {
// statNolog implements Stat for Windows.
func statNolog(name string) (FileInfo, error) {
- return stat("Stat", name, syscall.FILE_FLAG_BACKUP_SEMANTICS)
+ return stat("Stat", name, true)
}
// lstatNolog implements Lstat for Windows.
func lstatNolog(name string) (FileInfo, error) {
- attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
- // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
- // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
- attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
- return stat("Lstat", name, attrs)
+ return stat("Lstat", name, false)
}