github.com/AndrienkoAleksandr/go@v0.0.19/src/os/types_windows.go (about) 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 "internal/syscall/windows" 9 "sync" 10 "syscall" 11 "time" 12 "unsafe" 13 ) 14 15 // A fileStat is the implementation of FileInfo returned by Stat and Lstat. 16 type fileStat struct { 17 name string 18 19 // from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx 20 FileAttributes uint32 21 CreationTime syscall.Filetime 22 LastAccessTime syscall.Filetime 23 LastWriteTime syscall.Filetime 24 FileSizeHigh uint32 25 FileSizeLow uint32 26 27 // from Win32finddata and GetFileInformationByHandleEx 28 ReparseTag uint32 29 30 // what syscall.GetFileType returns 31 filetype uint32 32 33 // used to implement SameFile 34 sync.Mutex 35 path string 36 vol uint32 37 idxhi uint32 38 idxlo uint32 39 } 40 41 // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle 42 // to gather all required information about the file handle h. 43 func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) { 44 var d syscall.ByHandleFileInformation 45 err = syscall.GetFileInformationByHandle(h, &d) 46 if err != nil { 47 return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} 48 } 49 50 var ti windows.FILE_ATTRIBUTE_TAG_INFO 51 err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) 52 if err != nil { 53 if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER { 54 // It appears calling GetFileInformationByHandleEx with 55 // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with 56 // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that 57 // instance to indicate no symlinks are possible. 58 ti.ReparseTag = 0 59 } else { 60 return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} 61 } 62 } 63 64 return &fileStat{ 65 name: basename(path), 66 FileAttributes: d.FileAttributes, 67 CreationTime: d.CreationTime, 68 LastAccessTime: d.LastAccessTime, 69 LastWriteTime: d.LastWriteTime, 70 FileSizeHigh: d.FileSizeHigh, 71 FileSizeLow: d.FileSizeLow, 72 vol: d.VolumeSerialNumber, 73 idxhi: d.FileIndexHigh, 74 idxlo: d.FileIndexLow, 75 ReparseTag: ti.ReparseTag, 76 // fileStat.path is used by os.SameFile to decide if it needs 77 // to fetch vol, idxhi and idxlo. But these are already set, 78 // so set fileStat.path to "" to prevent os.SameFile doing it again. 79 }, nil 80 } 81 82 // newFileStatFromFileIDBothDirInfo copies all required information 83 // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat. 84 func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat { 85 // The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct. 86 // FileAttributes can contain any file attributes that is currently set on the file, 87 // not just the ones documented. 88 // EaSize contains the reparse tag if the file is a reparse point. 89 return &fileStat{ 90 FileAttributes: d.FileAttributes, 91 CreationTime: d.CreationTime, 92 LastAccessTime: d.LastAccessTime, 93 LastWriteTime: d.LastWriteTime, 94 FileSizeHigh: uint32(d.EndOfFile >> 32), 95 FileSizeLow: uint32(d.EndOfFile), 96 ReparseTag: d.EaSize, 97 idxhi: uint32(d.FileID >> 32), 98 idxlo: uint32(d.FileID), 99 } 100 } 101 102 // newFileStatFromWin32finddata copies all required information 103 // from syscall.Win32finddata d into the newly created fileStat. 104 func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { 105 fs := &fileStat{ 106 FileAttributes: d.FileAttributes, 107 CreationTime: d.CreationTime, 108 LastAccessTime: d.LastAccessTime, 109 LastWriteTime: d.LastWriteTime, 110 FileSizeHigh: d.FileSizeHigh, 111 FileSizeLow: d.FileSizeLow, 112 } 113 if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 114 // Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw: 115 // “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT 116 // attribute, this member specifies the reparse point tag. Otherwise, this 117 // value is undefined and should not be used.” 118 fs.ReparseTag = d.Reserved0 119 } 120 return fs 121 } 122 123 func (fs *fileStat) isSymlink() bool { 124 // As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as 125 // symlinks because otherwise certain directory junction tests in the 126 // path/filepath package would fail. 127 // 128 // However, 129 // https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions 130 // seems to suggest that directory junctions should be treated like hard 131 // links, not symlinks. 132 // 133 // TODO(bcmills): Get more input from Microsoft on what the behavior ought to 134 // be for MOUNT_POINT reparse points. 135 136 return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK || 137 fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT 138 } 139 140 func (fs *fileStat) Size() int64 { 141 return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) 142 } 143 144 func (fs *fileStat) Mode() (m FileMode) { 145 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { 146 m |= 0444 147 } else { 148 m |= 0666 149 } 150 if fs.isSymlink() { 151 return m | ModeSymlink 152 } 153 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 154 m |= ModeDir | 0111 155 } 156 switch fs.filetype { 157 case syscall.FILE_TYPE_PIPE: 158 m |= ModeNamedPipe 159 case syscall.FILE_TYPE_CHAR: 160 m |= ModeDevice | ModeCharDevice 161 } 162 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 { 163 m |= ModeIrregular 164 } 165 return m 166 } 167 168 func (fs *fileStat) ModTime() time.Time { 169 return time.Unix(0, fs.LastWriteTime.Nanoseconds()) 170 } 171 172 // Sys returns syscall.Win32FileAttributeData for file fs. 173 func (fs *fileStat) Sys() any { 174 return &syscall.Win32FileAttributeData{ 175 FileAttributes: fs.FileAttributes, 176 CreationTime: fs.CreationTime, 177 LastAccessTime: fs.LastAccessTime, 178 LastWriteTime: fs.LastWriteTime, 179 FileSizeHigh: fs.FileSizeHigh, 180 FileSizeLow: fs.FileSizeLow, 181 } 182 } 183 184 func (fs *fileStat) loadFileId() error { 185 fs.Lock() 186 defer fs.Unlock() 187 if fs.path == "" { 188 // already done 189 return nil 190 } 191 pathp, err := syscall.UTF16PtrFromString(fs.path) 192 if err != nil { 193 return err 194 } 195 196 // Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations, 197 // “Applications that use the CreateFile function should specify the 198 // FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse 199 // point.” 200 // 201 // And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew, 202 // “If the file is not a reparse point, then this flag is ignored.” 203 // 204 // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want 205 // information about the reparse point itself. 206 // 207 // If the file is a symlink, the symlink target should have already been 208 // resolved when the fileStat was created, so we don't need to worry about 209 // resolving symlink reparse points again here. 210 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT) 211 212 h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) 213 if err != nil { 214 return err 215 } 216 defer syscall.CloseHandle(h) 217 var i syscall.ByHandleFileInformation 218 err = syscall.GetFileInformationByHandle(h, &i) 219 if err != nil { 220 return err 221 } 222 fs.path = "" 223 fs.vol = i.VolumeSerialNumber 224 fs.idxhi = i.FileIndexHigh 225 fs.idxlo = i.FileIndexLow 226 return nil 227 } 228 229 // saveInfoFromPath saves full path of the file to be used by os.SameFile later, 230 // and set name from path. 231 func (fs *fileStat) saveInfoFromPath(path string) error { 232 fs.path = path 233 if !isAbs(fs.path) { 234 var err error 235 fs.path, err = syscall.FullPath(fs.path) 236 if err != nil { 237 return &PathError{Op: "FullPath", Path: path, Err: err} 238 } 239 } 240 fs.name = basename(path) 241 return nil 242 } 243 244 func sameFile(fs1, fs2 *fileStat) bool { 245 e := fs1.loadFileId() 246 if e != nil { 247 return false 248 } 249 e = fs2.loadFileId() 250 if e != nil { 251 return false 252 } 253 return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo 254 } 255 256 // For testing. 257 func atime(fi FileInfo) time.Time { 258 return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) 259 }