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