mirror of https://go.googlesource.com/go
139 lines
5.2 KiB
Go
139 lines
5.2 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package os
|
|
|
|
import (
|
|
"internal/filepathlite"
|
|
"internal/syscall/windows"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
// Stat returns the [FileInfo] structure describing file.
|
|
// If there is an error, it will be of type [*PathError].
|
|
func (file *File) Stat() (FileInfo, error) {
|
|
if file == nil {
|
|
return nil, ErrInvalid
|
|
}
|
|
return statHandle(file.name, file.pfd.Sysfd)
|
|
}
|
|
|
|
// stat implements both Stat and Lstat of a file.
|
|
func stat(funcname, name string, followSurrogates bool) (FileInfo, error) {
|
|
if len(name) == 0 {
|
|
return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
|
|
}
|
|
namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
|
|
if err != nil {
|
|
return nil, &PathError{Op: funcname, Path: name, Err: 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 surrogate for another named entity, because it isn't any kind of reparse point.
|
|
// The information we got from GetFileAttributesEx is good enough for now.
|
|
fs := newFileStatFromWin32FileAttributeData(&fa)
|
|
if err := fs.saveInfoFromPath(name); err != nil {
|
|
return nil, err
|
|
}
|
|
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)
|
|
if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
|
// Not a surrogate for another named entity. FindFirstFile is good enough.
|
|
fs := newFileStatFromWin32finddata(&fd)
|
|
if err := fs.saveInfoFromPath(name); err != nil {
|
|
return nil, err
|
|
}
|
|
return fs, nil
|
|
}
|
|
}
|
|
|
|
// Use CreateFile to determine whether the file is a name surrogate 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.
|
|
var flags uint32 = syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT
|
|
h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, flags, 0)
|
|
|
|
if err == windows.ERROR_INVALID_PARAMETER {
|
|
// Console handles, like "\\.\con", require generic read access. See
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#consoles.
|
|
// We haven't set it previously because it is normally not required
|
|
// to read attributes and some files may not allow it.
|
|
h, err = syscall.CreateFile(namep, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, flags, 0)
|
|
}
|
|
if err != nil {
|
|
// Since CreateFile failed, we can't determine whether name refers to a
|
|
// name surrogate, 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}
|
|
}
|
|
|
|
fi, err := statHandle(name, h)
|
|
syscall.CloseHandle(h)
|
|
if err == nil && followSurrogates && fi.(*fileStat).isReparseTagNameSurrogate() {
|
|
// 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) {
|
|
ft, err := syscall.GetFileType(h)
|
|
if err != nil {
|
|
return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
|
|
}
|
|
switch ft {
|
|
case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
|
|
return &fileStat{name: filepathlite.Base(name), filetype: ft}, nil
|
|
}
|
|
fs, err := newFileStatFromGetFileInformationByHandle(name, h)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fs.filetype = ft
|
|
return fs, err
|
|
}
|
|
|
|
// statNolog implements Stat for Windows.
|
|
func statNolog(name string) (FileInfo, error) {
|
|
return stat("Stat", name, true)
|
|
}
|
|
|
|
// lstatNolog implements Lstat for Windows.
|
|
func lstatNolog(name string) (FileInfo, error) {
|
|
followSurrogates := false
|
|
if name != "" && IsPathSeparator(name[len(name)-1]) {
|
|
// We try to implement POSIX semantics for Lstat path resolution
|
|
// (per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12):
|
|
// symlinks before the last separator in the path must be resolved. Since
|
|
// the last separator in this case follows the last path element, we should
|
|
// follow symlinks in the last path element.
|
|
followSurrogates = true
|
|
}
|
|
return stat("Lstat", name, followSurrogates)
|
|
}
|