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/godebug" 10 "internal/syscall/windows" 11 "sync" 12 "syscall" 13 "time" 14 "unsafe" 15) 16 17// A fileStat is the implementation of FileInfo returned by Stat and Lstat. 18type fileStat struct { 19 name string 20 21 // from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx 22 FileAttributes uint32 23 CreationTime syscall.Filetime 24 LastAccessTime syscall.Filetime 25 LastWriteTime syscall.Filetime 26 FileSizeHigh uint32 27 FileSizeLow uint32 28 29 // from Win32finddata and GetFileInformationByHandleEx 30 ReparseTag uint32 31 32 // what syscall.GetFileType returns 33 filetype uint32 34 35 // used to implement SameFile 36 sync.Mutex 37 path string 38 vol uint32 39 idxhi uint32 40 idxlo uint32 41 appendNameToPath bool 42} 43 44// newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle 45// to gather all required information about the file handle h. 46func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) { 47 var d syscall.ByHandleFileInformation 48 err = syscall.GetFileInformationByHandle(h, &d) 49 if err != nil { 50 return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} 51 } 52 53 var reparseTag uint32 54 if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 55 var ti windows.FILE_ATTRIBUTE_TAG_INFO 56 err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) 57 if err != nil { 58 return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} 59 } 60 reparseTag = ti.ReparseTag 61 } 62 63 return &fileStat{ 64 name: filepathlite.Base(path), 65 FileAttributes: d.FileAttributes, 66 CreationTime: d.CreationTime, 67 LastAccessTime: d.LastAccessTime, 68 LastWriteTime: d.LastWriteTime, 69 FileSizeHigh: d.FileSizeHigh, 70 FileSizeLow: d.FileSizeLow, 71 vol: d.VolumeSerialNumber, 72 idxhi: d.FileIndexHigh, 73 idxlo: d.FileIndexLow, 74 ReparseTag: reparseTag, 75 // fileStat.path is used by os.SameFile to decide if it needs 76 // to fetch vol, idxhi and idxlo. But these are already set, 77 // so set fileStat.path to "" to prevent os.SameFile doing it again. 78 }, nil 79} 80 81// newFileStatFromWin32FileAttributeData copies all required information 82// from syscall.Win32FileAttributeData d into the newly created fileStat. 83func newFileStatFromWin32FileAttributeData(d *syscall.Win32FileAttributeData) *fileStat { 84 return &fileStat{ 85 FileAttributes: d.FileAttributes, 86 CreationTime: d.CreationTime, 87 LastAccessTime: d.LastAccessTime, 88 LastWriteTime: d.LastWriteTime, 89 FileSizeHigh: d.FileSizeHigh, 90 FileSizeLow: d.FileSizeLow, 91 } 92} 93 94// newFileStatFromFileIDBothDirInfo copies all required information 95// from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat. 96func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat { 97 // The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct. 98 // FileAttributes can contain any file attributes that is currently set on the file, 99 // not just the ones documented. 100 // EaSize contains the reparse tag if the file is a reparse point. 101 return &fileStat{ 102 FileAttributes: d.FileAttributes, 103 CreationTime: d.CreationTime, 104 LastAccessTime: d.LastAccessTime, 105 LastWriteTime: d.LastWriteTime, 106 FileSizeHigh: uint32(d.EndOfFile >> 32), 107 FileSizeLow: uint32(d.EndOfFile), 108 ReparseTag: d.EaSize, 109 idxhi: uint32(d.FileID >> 32), 110 idxlo: uint32(d.FileID), 111 } 112} 113 114// newFileStatFromFileFullDirInfo copies all required information 115// from windows.FILE_FULL_DIR_INFO d into the newly created fileStat. 116func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat { 117 return &fileStat{ 118 FileAttributes: d.FileAttributes, 119 CreationTime: d.CreationTime, 120 LastAccessTime: d.LastAccessTime, 121 LastWriteTime: d.LastWriteTime, 122 FileSizeHigh: uint32(d.EndOfFile >> 32), 123 FileSizeLow: uint32(d.EndOfFile), 124 ReparseTag: d.EaSize, 125 } 126} 127 128// newFileStatFromWin32finddata copies all required information 129// from syscall.Win32finddata d into the newly created fileStat. 130func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { 131 fs := &fileStat{ 132 FileAttributes: d.FileAttributes, 133 CreationTime: d.CreationTime, 134 LastAccessTime: d.LastAccessTime, 135 LastWriteTime: d.LastWriteTime, 136 FileSizeHigh: d.FileSizeHigh, 137 FileSizeLow: d.FileSizeLow, 138 } 139 if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 140 // Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw: 141 // “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT 142 // attribute, this member specifies the reparse point tag. Otherwise, this 143 // value is undefined and should not be used.” 144 fs.ReparseTag = d.Reserved0 145 } 146 return fs 147} 148 149// isReparseTagNameSurrogate determines whether a tag's associated 150// reparse point is a surrogate for another named entity (for example, a mounted folder). 151// 152// See https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-isreparsetagnamesurrogate 153// and https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags. 154func (fs *fileStat) isReparseTagNameSurrogate() bool { 155 // True for IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT. 156 return fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && fs.ReparseTag&0x20000000 != 0 157} 158 159func (fs *fileStat) Size() int64 { 160 return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) 161} 162 163var winsymlink = godebug.New("winsymlink") 164 165func (fs *fileStat) Mode() FileMode { 166 m := fs.mode() 167 if winsymlink.Value() == "0" { 168 old := fs.modePreGo1_23() 169 if old != m { 170 winsymlink.IncNonDefault() 171 m = old 172 } 173 } 174 return m 175} 176 177func (fs *fileStat) mode() (m FileMode) { 178 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { 179 m |= 0444 180 } else { 181 m |= 0666 182 } 183 184 // Windows reports the FILE_ATTRIBUTE_DIRECTORY bit for reparse points 185 // that refer to directories, such as symlinks and mount points. 186 // However, we follow symlink POSIX semantics and do not set the mode bits. 187 // This allows users to walk directories without following links 188 // by just calling "fi, err := os.Lstat(name); err == nil && fi.IsDir()". 189 // Note that POSIX only defines the semantics for symlinks, not for 190 // mount points or other surrogate reparse points, but we treat them 191 // the same way for consistency. Also, mount points can contain infinite 192 // loops, so it is not safe to walk them without special handling. 193 if !fs.isReparseTagNameSurrogate() { 194 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 195 m |= ModeDir | 0111 196 } 197 198 switch fs.filetype { 199 case syscall.FILE_TYPE_PIPE: 200 m |= ModeNamedPipe 201 case syscall.FILE_TYPE_CHAR: 202 m |= ModeDevice | ModeCharDevice 203 } 204 } 205 206 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 207 switch fs.ReparseTag { 208 case syscall.IO_REPARSE_TAG_SYMLINK: 209 m |= ModeSymlink 210 case windows.IO_REPARSE_TAG_AF_UNIX: 211 m |= ModeSocket 212 case windows.IO_REPARSE_TAG_DEDUP: 213 // If the Data Deduplication service is enabled on Windows Server, its 214 // Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP 215 // whenever that job runs. 216 // 217 // However, DEDUP reparse points remain similar in most respects to 218 // regular files: they continue to support random-access reads and writes 219 // of persistent data, and they shouldn't add unexpected latency or 220 // unavailability in the way that a network filesystem might. 221 // 222 // Go programs may use ModeIrregular to filter out unusual files (such as 223 // raw device files on Linux, POSIX FIFO special files, and so on), so 224 // to avoid files changing unpredictably from regular to irregular we will 225 // consider DEDUP files to be close enough to regular to treat as such. 226 default: 227 m |= ModeIrregular 228 } 229 } 230 return 231} 232 233// modePreGo1_23 returns the FileMode for the fileStat, using the pre-Go 1.23 234// logic for determining the file mode. 235// The logic is subtle and not well-documented, so it is better to keep it 236// separate from the new logic. 237func (fs *fileStat) modePreGo1_23() (m FileMode) { 238 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { 239 m |= 0444 240 } else { 241 m |= 0666 242 } 243 if fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK || 244 fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT { 245 return m | ModeSymlink 246 } 247 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 248 m |= ModeDir | 0111 249 } 250 switch fs.filetype { 251 case syscall.FILE_TYPE_PIPE: 252 m |= ModeNamedPipe 253 case syscall.FILE_TYPE_CHAR: 254 m |= ModeDevice | ModeCharDevice 255 } 256 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 257 if fs.ReparseTag == windows.IO_REPARSE_TAG_AF_UNIX { 258 m |= ModeSocket 259 } 260 if m&ModeType == 0 { 261 if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP { 262 // See comment in fs.Mode. 263 } else { 264 m |= ModeIrregular 265 } 266 } 267 } 268 return m 269} 270 271func (fs *fileStat) ModTime() time.Time { 272 return time.Unix(0, fs.LastWriteTime.Nanoseconds()) 273} 274 275// Sys returns syscall.Win32FileAttributeData for file fs. 276func (fs *fileStat) Sys() any { 277 return &syscall.Win32FileAttributeData{ 278 FileAttributes: fs.FileAttributes, 279 CreationTime: fs.CreationTime, 280 LastAccessTime: fs.LastAccessTime, 281 LastWriteTime: fs.LastWriteTime, 282 FileSizeHigh: fs.FileSizeHigh, 283 FileSizeLow: fs.FileSizeLow, 284 } 285} 286 287func (fs *fileStat) loadFileId() error { 288 fs.Lock() 289 defer fs.Unlock() 290 if fs.path == "" { 291 // already done 292 return nil 293 } 294 var path string 295 if fs.appendNameToPath { 296 path = fixLongPath(fs.path + `\` + fs.name) 297 } else { 298 path = fs.path 299 } 300 pathp, err := syscall.UTF16PtrFromString(path) 301 if err != nil { 302 return err 303 } 304 305 // Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations, 306 // “Applications that use the CreateFile function should specify the 307 // FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse 308 // point.” 309 // 310 // And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew, 311 // “If the file is not a reparse point, then this flag is ignored.” 312 // 313 // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want 314 // information about the reparse point itself. 315 // 316 // If the file is a symlink, the symlink target should have already been 317 // resolved when the fileStat was created, so we don't need to worry about 318 // resolving symlink reparse points again here. 319 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT) 320 321 h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) 322 if err != nil { 323 return err 324 } 325 defer syscall.CloseHandle(h) 326 var i syscall.ByHandleFileInformation 327 err = syscall.GetFileInformationByHandle(h, &i) 328 if err != nil { 329 return err 330 } 331 fs.path = "" 332 fs.vol = i.VolumeSerialNumber 333 fs.idxhi = i.FileIndexHigh 334 fs.idxlo = i.FileIndexLow 335 return nil 336} 337 338// saveInfoFromPath saves full path of the file to be used by os.SameFile later, 339// and set name from path. 340func (fs *fileStat) saveInfoFromPath(path string) error { 341 fs.path = path 342 if !filepathlite.IsAbs(fs.path) { 343 var err error 344 fs.path, err = syscall.FullPath(fs.path) 345 if err != nil { 346 return &PathError{Op: "FullPath", Path: path, Err: err} 347 } 348 } 349 fs.name = filepathlite.Base(path) 350 return nil 351} 352 353func sameFile(fs1, fs2 *fileStat) bool { 354 e := fs1.loadFileId() 355 if e != nil { 356 return false 357 } 358 e = fs2.loadFileId() 359 if e != nil { 360 return false 361 } 362 return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo 363} 364 365// For testing. 366func atime(fi FileInfo) time.Time { 367 return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) 368} 369