aboutsummaryrefslogtreecommitdiff
path: root/src/os/stat_windows.go
diff options
context:
space:
mode:
authorAlex Brainman <alex.brainman@gmail.com>2018-10-20 16:30:57 +1100
committerAlex Brainman <alex.brainman@gmail.com>2018-11-02 07:30:03 +0000
commitf10815898c0732e2e6cdb697d6f95f33f8650b4e (patch)
treecd6140743719a8c6c9066c6866ec70c46fc4f342 /src/os/stat_windows.go
parentd154ef60a0c88be98c70bbe1c5735fb7b1f45250 (diff)
downloadgo-f10815898c0732e2e6cdb697d6f95f33f8650b4e.tar.xz
os: use CreateFile for Stat of symlinks
Stat uses Windows FindFirstFile + CreateFile to gather symlink information - FindFirstFile determines if file is a symlink, and then CreateFile follows symlink to capture target details. Lstat only uses FindFirstFile. This CL replaces current approach with just a call to CreateFile. Lstat uses FILE_FLAG_OPEN_REPARSE_POINT flag, that instructs CreateFile not to follow symlink. Other than that both Stat and Lstat look the same now. New code is simpler. CreateFile + GetFileInformationByHandle (unlike FindFirstFile) does not report reparse tag of a file. I tried to ignore reparse tag altogether. And it works for symlinks and mount points. Unfortunately (see https://github.com/moby/moby/issues/37026), files on deduped disk volumes are reported with FILE_ATTRIBUTE_REPARSE_POINT attribute set and reparse tag set to IO_REPARSE_TAG_DEDUP. So, if we ignore reparse tag, Lstat interprets deduped volume files as symlinks. That is incorrect. So I had to add GetFileInformationByHandleEx call to gather reparse tag after calling CreateFile and GetFileInformationByHandle. Fixes #27225 Fixes #27515 Change-Id: If60233bcf18836c147597cc17450d82f3f88c623 Reviewed-on: https://go-review.googlesource.com/c/143578 Run-TryBot: Alex Brainman <alex.brainman@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Kirill Kolyshkin <kolyshkin@gmail.com>
Diffstat (limited to 'src/os/stat_windows.go')
-rw-r--r--src/os/stat_windows.go85
1 files changed, 52 insertions, 33 deletions
diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go
index 19cc0cf6b7..f4700f5818 100644
--- a/src/os/stat_windows.go
+++ b/src/os/stat_windows.go
@@ -5,7 +5,9 @@
package os
import (
+ "internal/syscall/windows"
"syscall"
+ "unsafe"
)
// isNulName returns true if name is NUL file name.
@@ -58,33 +60,59 @@ func (file *File) Stat() (FileInfo, error) {
return fs, err
}
-// statNolog implements Stat for Windows.
-func statNolog(name string) (FileInfo, error) {
+// stat implements both Stat and Lstat of a file.
+func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
if len(name) == 0 {
- return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
+ return nil, &PathError{funcname, name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
if isNulName(name) {
return &devNullStat, nil
}
namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
if err != nil {
- return nil, &PathError{"Stat", name, err}
+ return nil, &PathError{funcname, name, err}
}
- fs, err := newFileStatFromGetFileAttributesExOrFindFirstFile(name, namep)
- if err != nil {
- return nil, err
+
+ // Try GetFileAttributesEx first, because it is faster than CreateFile.
+ // See https://golang.org/issues/19922#issuecomment-300031421 for details.
+ var fa syscall.Win32FileAttributeData
+ err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ // Not a symlink.
+ fs := &fileStat{
+ path: name,
+ FileAttributes: fa.FileAttributes,
+ CreationTime: fa.CreationTime,
+ LastAccessTime: fa.LastAccessTime,
+ LastWriteTime: fa.LastWriteTime,
+ FileSizeHigh: fa.FileSizeHigh,
+ FileSizeLow: fa.FileSizeLow,
+ }
+ // Gather full path to be used by os.SameFile later.
+ if !isAbs(fs.path) {
+ fs.path, err = syscall.FullPath(fs.path)
+ if err != nil {
+ return nil, &PathError{"FullPath", name, err}
+ }
+ }
+ fs.name = basename(name)
+ return fs, nil
}
- if !fs.isSymlink() {
- err = fs.updatePathAndName(name)
+ // 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, err
+ return nil, &PathError{"FindFirstFile", name, err}
}
- return fs, nil
+ syscall.FindClose(sh)
+ return newFileStatFromWin32finddata(&fd), nil
}
- // Use Windows I/O manager to dereference the symbolic link, as per
- // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
+
+ // Finally use CreateFile.
h, err := syscall.CreateFile(namep, 0, 0, nil,
- syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ syscall.OPEN_EXISTING, createFileAttrs, 0)
if err != nil {
return nil, &PathError{"CreateFile", name, err}
}
@@ -93,25 +121,16 @@ func statNolog(name string) (FileInfo, error) {
return newFileStatFromGetFileInformationByHandle(name, h)
}
+// statNolog implements Stat for Windows.
+func statNolog(name string) (FileInfo, error) {
+ return stat("Stat", name, syscall.FILE_FLAG_BACKUP_SEMANTICS)
+}
+
// lstatNolog implements Lstat for Windows.
func lstatNolog(name string) (FileInfo, error) {
- if len(name) == 0 {
- return nil, &PathError{"Lstat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
- }
- if isNulName(name) {
- return &devNullStat, nil
- }
- namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
- if err != nil {
- return nil, &PathError{"Lstat", name, err}
- }
- fs, err := newFileStatFromGetFileAttributesExOrFindFirstFile(name, namep)
- if err != nil {
- return nil, err
- }
- err = fs.updatePathAndName(name)
- if err != nil {
- return nil, err
- }
- return fs, nil
+ 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)
}