Source file src/os/stat_windows.go

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os
     6  
     7  import (
     8  	"errors"
     9  	"internal/filepathlite"
    10  	"internal/syscall/windows"
    11  	"syscall"
    12  	"unsafe"
    13  )
    14  
    15  // Stat returns the [FileInfo] structure describing file.
    16  // If there is an error, it will be of type [*PathError].
    17  func (file *File) Stat() (FileInfo, error) {
    18  	if file == nil {
    19  		return nil, ErrInvalid
    20  	}
    21  	return statHandle(file.name, file.pfd.Sysfd)
    22  }
    23  
    24  // stat implements both Stat and Lstat of a file.
    25  func stat(funcname, name string, followSurrogates bool) (FileInfo, error) {
    26  	if len(name) == 0 {
    27  		return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
    28  	}
    29  	namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
    30  	if err != nil {
    31  		return nil, &PathError{Op: funcname, Path: name, Err: err}
    32  	}
    33  
    34  	// Try GetFileAttributesEx first, because it is faster than CreateFile.
    35  	// See https://golang.org/issues/19922#issuecomment-300031421 for details.
    36  	var fa syscall.Win32FileAttributeData
    37  	err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
    38  	if errors.Is(err, ErrNotExist) {
    39  		return nil, &PathError{Op: "GetFileAttributesEx", Path: name, Err: err}
    40  	}
    41  	if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
    42  		// Not a surrogate for another named entity, because it isn't any kind of reparse point.
    43  		// The information we got from GetFileAttributesEx is good enough for now.
    44  		fs := newFileStatFromWin32FileAttributeData(&fa)
    45  		if err := fs.saveInfoFromPath(name); err != nil {
    46  			return nil, err
    47  		}
    48  		return fs, nil
    49  	}
    50  
    51  	// GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
    52  	// files like c:\pagefile.sys. Use FindFirstFile for such files.
    53  	if err == windows.ERROR_SHARING_VIOLATION {
    54  		var fd syscall.Win32finddata
    55  		sh, err := syscall.FindFirstFile(namep, &fd)
    56  		if err != nil {
    57  			return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
    58  		}
    59  		syscall.FindClose(sh)
    60  		if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
    61  			// Not a surrogate for another named entity. FindFirstFile is good enough.
    62  			fs := newFileStatFromWin32finddata(&fd)
    63  			if err := fs.saveInfoFromPath(name); err != nil {
    64  				return nil, err
    65  			}
    66  			return fs, nil
    67  		}
    68  	}
    69  
    70  	// Use CreateFile to determine whether the file is a name surrogate and, if so,
    71  	// save information about the link target.
    72  	// Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
    73  	// even if name refers to a directory.
    74  	var flags uint32 = syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT
    75  	h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, flags, 0)
    76  
    77  	if err == windows.ERROR_INVALID_PARAMETER {
    78  		// Console handles, like "\\.\con", require generic read access. See
    79  		// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#consoles.
    80  		// We haven't set it previously because it is normally not required
    81  		// to read attributes and some files may not allow it.
    82  		h, err = syscall.CreateFile(namep, syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING, flags, 0)
    83  	}
    84  	if err != nil {
    85  		// Since CreateFile failed, we can't determine whether name refers to a
    86  		// name surrogate, or some other kind of reparse point. Since we can't return a
    87  		// FileInfo with a known-accurate Mode, we must return an error.
    88  		return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
    89  	}
    90  
    91  	fi, err := statHandle(name, h)
    92  	syscall.CloseHandle(h)
    93  	if err == nil && followSurrogates && fi.(*fileStat).isReparseTagNameSurrogate() {
    94  		// To obtain information about the link target, we reopen the file without
    95  		// FILE_FLAG_OPEN_REPARSE_POINT and examine the resulting handle.
    96  		// (See https://devblogs.microsoft.com/oldnewthing/20100212-00/?p=14963.)
    97  		h, err = syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
    98  		if err != nil {
    99  			// name refers to a symlink, but we couldn't resolve the symlink target.
   100  			return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
   101  		}
   102  		defer syscall.CloseHandle(h)
   103  		return statHandle(name, h)
   104  	}
   105  	return fi, err
   106  }
   107  
   108  func statHandle(name string, h syscall.Handle) (FileInfo, error) {
   109  	ft, err := syscall.GetFileType(h)
   110  	if err != nil {
   111  		return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
   112  	}
   113  	switch ft {
   114  	case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
   115  		return &fileStat{name: filepathlite.Base(name), filetype: ft}, nil
   116  	}
   117  	fs, err := newFileStatFromGetFileInformationByHandle(name, h)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	fs.filetype = ft
   122  	return fs, nil
   123  }
   124  
   125  // statNolog implements Stat for Windows.
   126  func statNolog(name string) (FileInfo, error) {
   127  	return stat("Stat", name, true)
   128  }
   129  
   130  // lstatNolog implements Lstat for Windows.
   131  func lstatNolog(name string) (FileInfo, error) {
   132  	followSurrogates := false
   133  	if name != "" && IsPathSeparator(name[len(name)-1]) {
   134  		// We try to implement POSIX semantics for Lstat path resolution
   135  		// (per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12):
   136  		// symlinks before the last separator in the path must be resolved. Since
   137  		// the last separator in this case follows the last path element, we should
   138  		// follow symlinks in the last path element.
   139  		followSurrogates = true
   140  	}
   141  	return stat("Lstat", name, followSurrogates)
   142  }
   143  

View as plain text