mirror of
https://github.com/golang/go
synced 2024-11-18 12:04:57 -07:00
os: reimplement windows os.Stat
Currently windows Stat uses combination of Lstat and Readlink to walk symlinks until it reaches file or directory. Windows Readlink is implemented via Windows DeviceIoControl(FSCTL_GET_REPARSE_POINT, ...) call, but that call does not work on network shares or inside of Docker container (see issues #18555 ad #19922 for details). But Raymond Chen suggests different approach: https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ - he suggests to use Windows I/O manager to dereferences the symbolic link. This appears to work for all normal symlinks, but also for network shares and inside of Docker container. This CL implements described procedure. I also had to adjust TestStatSymlinkLoop, because the test is expecting Stat to return syscall.ELOOP for symlink with a loop. But new Stat returns Windows error of ERROR_CANT_RESOLVE_FILENAME = 1921 instead. I could map ERROR_CANT_RESOLVE_FILENAME into syscall.ELOOP, but I suspect the former is broader than later. And ERROR_CANT_RESOLVE_FILENAME message text of "The name of the file cannot be resolved by the system." sounds fine to me. Fixes #10935 Fixes #18555 Fixes #19922 Change-Id: I979636064cdbdb9c7c840cf8ae73fe2c24499879 Reviewed-on: https://go-review.googlesource.com/41834 Reviewed-by: Harshavardhana <hrshvardhana@gmail.com> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
e94b9d4143
commit
5300362172
@ -667,8 +667,8 @@ func TestStatSymlinkLoop(t *testing.T) {
|
||||
defer os.Remove("x")
|
||||
|
||||
_, err = os.Stat("x")
|
||||
if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.ELOOP {
|
||||
t.Errorf("expected *PathError with ELOOP, got %T: %v\n", err, err)
|
||||
if _, ok := err.(*os.PathError); !ok {
|
||||
t.Errorf("expected *PathError, got %T: %v\n", err, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,32 +61,85 @@ func (file *File) Stat() (FileInfo, error) {
|
||||
// Stat returns a FileInfo structure describing the named file.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func Stat(name string) (FileInfo, error) {
|
||||
var fi FileInfo
|
||||
var err error
|
||||
link := name
|
||||
for i := 0; i < 255; i++ {
|
||||
fi, err = Lstat(link)
|
||||
if len(name) == 0 {
|
||||
return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
|
||||
}
|
||||
if name == DevNull {
|
||||
return &devNullStat, nil
|
||||
}
|
||||
namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
|
||||
if err != nil {
|
||||
return nil, &PathError{"Stat", name, err}
|
||||
}
|
||||
|
||||
// Use Windows I/O manager to dereferences the symbolic link, as per
|
||||
// https://blogs.msdn.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 {
|
||||
if err == windows.ERROR_SHARING_VIOLATION {
|
||||
// try FindFirstFile now that CreateFile failed
|
||||
return statWithFindFirstFile(name, namep)
|
||||
}
|
||||
return nil, &PathError{"CreateFile", name, err}
|
||||
}
|
||||
defer syscall.CloseHandle(h)
|
||||
|
||||
var d syscall.ByHandleFileInformation
|
||||
err = syscall.GetFileInformationByHandle(h, &d)
|
||||
if err != nil {
|
||||
return nil, &PathError{"GetFileInformationByHandle", name, err}
|
||||
}
|
||||
return &fileStat{
|
||||
name: basename(name),
|
||||
sys: syscall.Win32FileAttributeData{
|
||||
FileAttributes: d.FileAttributes,
|
||||
CreationTime: d.CreationTime,
|
||||
LastAccessTime: d.LastAccessTime,
|
||||
LastWriteTime: d.LastWriteTime,
|
||||
FileSizeHigh: d.FileSizeHigh,
|
||||
FileSizeLow: d.FileSizeLow,
|
||||
},
|
||||
vol: d.VolumeSerialNumber,
|
||||
idxhi: d.FileIndexHigh,
|
||||
idxlo: d.FileIndexLow,
|
||||
// fileStat.path is used by os.SameFile to decide, if it needs
|
||||
// to fetch vol, idxhi and idxlo. But these are already set,
|
||||
// so set fileStat.path to "" to prevent os.SameFile doing it again.
|
||||
// Also do not set fileStat.filetype, because it is only used for
|
||||
// console and stdin/stdout. But you cannot call os.Stat for these.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// statWithFindFirstFile is used by Stat to handle special case of stating
|
||||
// c:\pagefile.sys. We might discovered other files need similar treatment.
|
||||
func statWithFindFirstFile(name string, namep *uint16) (FileInfo, error) {
|
||||
var fd syscall.Win32finddata
|
||||
h, err := syscall.FindFirstFile(namep, &fd)
|
||||
if err != nil {
|
||||
return nil, &PathError{"FindFirstFile", name, err}
|
||||
}
|
||||
syscall.FindClose(h)
|
||||
|
||||
fullpath := name
|
||||
if !isAbs(fullpath) {
|
||||
fullpath, err = syscall.FullPath(fullpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fi.Mode()&ModeSymlink == 0 {
|
||||
fi.(*fileStat).name = basename(name)
|
||||
return fi, nil
|
||||
}
|
||||
newlink, err := Readlink(link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch {
|
||||
case isAbs(newlink):
|
||||
link = newlink
|
||||
case len(newlink) > 0 && IsPathSeparator(newlink[0]):
|
||||
link = volumeName(link) + newlink
|
||||
default:
|
||||
link = dirname(link) + `\` + newlink
|
||||
return nil, &PathError{"FullPath", name, err}
|
||||
}
|
||||
}
|
||||
return nil, &PathError{"Stat", name, syscall.ELOOP}
|
||||
return &fileStat{
|
||||
name: basename(name),
|
||||
path: fullpath,
|
||||
sys: syscall.Win32FileAttributeData{
|
||||
FileAttributes: fd.FileAttributes,
|
||||
CreationTime: fd.CreationTime,
|
||||
LastAccessTime: fd.LastAccessTime,
|
||||
LastWriteTime: fd.LastWriteTime,
|
||||
FileSizeHigh: fd.FileSizeHigh,
|
||||
FileSizeLow: fd.FileSizeLow,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Lstat returns the FileInfo structure describing the named file.
|
||||
@ -111,19 +164,7 @@ func Lstat(name string) (FileInfo, error) {
|
||||
return nil, &PathError{"GetFileAttributesEx", name, e}
|
||||
}
|
||||
// try FindFirstFile now that GetFileAttributesEx failed
|
||||
var fd syscall.Win32finddata
|
||||
h, e2 := syscall.FindFirstFile(namep, &fd)
|
||||
if e2 != nil {
|
||||
return nil, &PathError{"FindFirstFile", name, e}
|
||||
}
|
||||
syscall.FindClose(h)
|
||||
|
||||
fs.sys.FileAttributes = fd.FileAttributes
|
||||
fs.sys.CreationTime = fd.CreationTime
|
||||
fs.sys.LastAccessTime = fd.LastAccessTime
|
||||
fs.sys.LastWriteTime = fd.LastWriteTime
|
||||
fs.sys.FileSizeHigh = fd.FileSizeHigh
|
||||
fs.sys.FileSizeLow = fd.FileSizeLow
|
||||
return statWithFindFirstFile(name, namep)
|
||||
}
|
||||
fs.path = name
|
||||
if !isAbs(fs.path) {
|
||||
|
Loading…
Reference in New Issue
Block a user