github.com/brass-software/os@v0.0.0-20240129060254-960f457a5dea/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 appendNameToPath bool 40 } 41 42 // newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle 43 // to gather all required information about the file handle h. 44 func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) { 45 var d syscall.ByHandleFileInformation 46 err = syscall.GetFileInformationByHandle(h, &d) 47 if err != nil { 48 return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} 49 } 50 51 var ti windows.FILE_ATTRIBUTE_TAG_INFO 52 err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti))) 53 if err != nil { 54 if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER { 55 // It appears calling GetFileInformationByHandleEx with 56 // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with 57 // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that 58 // instance to indicate no symlinks are possible. 59 ti.ReparseTag = 0 60 } else { 61 return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} 62 } 63 } 64 65 return &fileStat{ 66 name: basename(path), 67 FileAttributes: d.FileAttributes, 68 CreationTime: d.CreationTime, 69 LastAccessTime: d.LastAccessTime, 70 LastWriteTime: d.LastWriteTime, 71 FileSizeHigh: d.FileSizeHigh, 72 FileSizeLow: d.FileSizeLow, 73 vol: d.VolumeSerialNumber, 74 idxhi: d.FileIndexHigh, 75 idxlo: d.FileIndexLow, 76 ReparseTag: ti.ReparseTag, 77 // fileStat.path is used by os.SameFile to decide if it needs 78 // to fetch vol, idxhi and idxlo. But these are already set, 79 // so set fileStat.path to "" to prevent os.SameFile doing it again. 80 }, nil 81 } 82 83 // newFileStatFromFileIDBothDirInfo copies all required information 84 // from windows.FILE_ID_BOTH_DIR_INFO d into the newly created fileStat. 85 func newFileStatFromFileIDBothDirInfo(d *windows.FILE_ID_BOTH_DIR_INFO) *fileStat { 86 // The FILE_ID_BOTH_DIR_INFO MSDN documentations isn't completely correct. 87 // FileAttributes can contain any file attributes that is currently set on the file, 88 // not just the ones documented. 89 // EaSize contains the reparse tag if the file is a reparse point. 90 return &fileStat{ 91 FileAttributes: d.FileAttributes, 92 CreationTime: d.CreationTime, 93 LastAccessTime: d.LastAccessTime, 94 LastWriteTime: d.LastWriteTime, 95 FileSizeHigh: uint32(d.EndOfFile >> 32), 96 FileSizeLow: uint32(d.EndOfFile), 97 ReparseTag: d.EaSize, 98 idxhi: uint32(d.FileID >> 32), 99 idxlo: uint32(d.FileID), 100 } 101 } 102 103 // newFileStatFromFileFullDirInfo copies all required information 104 // from windows.FILE_FULL_DIR_INFO d into the newly created fileStat. 105 func newFileStatFromFileFullDirInfo(d *windows.FILE_FULL_DIR_INFO) *fileStat { 106 return &fileStat{ 107 FileAttributes: d.FileAttributes, 108 CreationTime: d.CreationTime, 109 LastAccessTime: d.LastAccessTime, 110 LastWriteTime: d.LastWriteTime, 111 FileSizeHigh: uint32(d.EndOfFile >> 32), 112 FileSizeLow: uint32(d.EndOfFile), 113 ReparseTag: d.EaSize, 114 } 115 } 116 117 // newFileStatFromWin32finddata copies all required information 118 // from syscall.Win32finddata d into the newly created fileStat. 119 func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { 120 fs := &fileStat{ 121 FileAttributes: d.FileAttributes, 122 CreationTime: d.CreationTime, 123 LastAccessTime: d.LastAccessTime, 124 LastWriteTime: d.LastWriteTime, 125 FileSizeHigh: d.FileSizeHigh, 126 FileSizeLow: d.FileSizeLow, 127 } 128 if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 129 // Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw: 130 // “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT 131 // attribute, this member specifies the reparse point tag. Otherwise, this 132 // value is undefined and should not be used.” 133 fs.ReparseTag = d.Reserved0 134 } 135 return fs 136 } 137 138 // isReparseTagNameSurrogate determines whether a tag's associated 139 // reparse point is a surrogate for another named entity (for example, a mounted folder). 140 // 141 // See https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-isreparsetagnamesurrogate 142 // and https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-point-tags. 143 func (fs *fileStat) isReparseTagNameSurrogate() bool { 144 // True for IO_REPARSE_TAG_SYMLINK and IO_REPARSE_TAG_MOUNT_POINT. 145 return fs.ReparseTag&0x20000000 != 0 146 } 147 148 func (fs *fileStat) isSymlink() bool { 149 // As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as 150 // symlinks because otherwise certain directory junction tests in the 151 // path/filepath package would fail. 152 // 153 // However, 154 // https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions 155 // seems to suggest that directory junctions should be treated like hard 156 // links, not symlinks. 157 // 158 // TODO(bcmills): Get more input from Microsoft on what the behavior ought to 159 // be for MOUNT_POINT reparse points. 160 161 return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK || 162 fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT 163 } 164 165 func (fs *fileStat) Size() int64 { 166 return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) 167 } 168 169 func (fs *fileStat) Mode() (m FileMode) { 170 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { 171 m |= 0444 172 } else { 173 m |= 0666 174 } 175 if fs.isSymlink() { 176 return m | ModeSymlink 177 } 178 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 179 m |= ModeDir | 0111 180 } 181 switch fs.filetype { 182 case syscall.FILE_TYPE_PIPE: 183 m |= ModeNamedPipe 184 case syscall.FILE_TYPE_CHAR: 185 m |= ModeDevice | ModeCharDevice 186 } 187 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 { 188 if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP { 189 // If the Data Deduplication service is enabled on Windows Server, its 190 // Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP 191 // whenever that job runs. 192 // 193 // However, DEDUP reparse points remain similar in most respects to 194 // regular files: they continue to support random-access reads and writes 195 // of persistent data, and they shouldn't add unexpected latency or 196 // unavailability in the way that a network filesystem might. 197 // 198 // Go programs may use ModeIrregular to filter out unusual files (such as 199 // raw device files on Linux, POSIX FIFO special files, and so on), so 200 // to avoid files changing unpredictably from regular to irregular we will 201 // consider DEDUP files to be close enough to regular to treat as such. 202 } else { 203 m |= ModeIrregular 204 } 205 } 206 return m 207 } 208 209 func (fs *fileStat) ModTime() time.Time { 210 return time.Unix(0, fs.LastWriteTime.Nanoseconds()) 211 } 212 213 // Sys returns syscall.Win32FileAttributeData for file fs. 214 func (fs *fileStat) Sys() any { 215 return &syscall.Win32FileAttributeData{ 216 FileAttributes: fs.FileAttributes, 217 CreationTime: fs.CreationTime, 218 LastAccessTime: fs.LastAccessTime, 219 LastWriteTime: fs.LastWriteTime, 220 FileSizeHigh: fs.FileSizeHigh, 221 FileSizeLow: fs.FileSizeLow, 222 } 223 } 224 225 func (fs *fileStat) loadFileId() error { 226 fs.Lock() 227 defer fs.Unlock() 228 if fs.path == "" { 229 // already done 230 return nil 231 } 232 var path string 233 if fs.appendNameToPath { 234 path = fixLongPath(fs.path + `\` + fs.name) 235 } else { 236 path = fs.path 237 } 238 pathp, err := syscall.UTF16PtrFromString(path) 239 if err != nil { 240 return err 241 } 242 243 // Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations, 244 // “Applications that use the CreateFile function should specify the 245 // FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse 246 // point.” 247 // 248 // And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew, 249 // “If the file is not a reparse point, then this flag is ignored.” 250 // 251 // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want 252 // information about the reparse point itself. 253 // 254 // If the file is a symlink, the symlink target should have already been 255 // resolved when the fileStat was created, so we don't need to worry about 256 // resolving symlink reparse points again here. 257 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT) 258 259 h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) 260 if err != nil { 261 return err 262 } 263 defer syscall.CloseHandle(h) 264 var i syscall.ByHandleFileInformation 265 err = syscall.GetFileInformationByHandle(h, &i) 266 if err != nil { 267 return err 268 } 269 fs.path = "" 270 fs.vol = i.VolumeSerialNumber 271 fs.idxhi = i.FileIndexHigh 272 fs.idxlo = i.FileIndexLow 273 return nil 274 } 275 276 // saveInfoFromPath saves full path of the file to be used by os.SameFile later, 277 // and set name from path. 278 func (fs *fileStat) saveInfoFromPath(path string) error { 279 fs.path = path 280 if !isAbs(fs.path) { 281 var err error 282 fs.path, err = syscall.FullPath(fs.path) 283 if err != nil { 284 return &PathError{Op: "FullPath", Path: path, Err: err} 285 } 286 } 287 fs.name = basename(path) 288 return nil 289 } 290 291 func sameFile(fs1, fs2 *fileStat) bool { 292 e := fs1.loadFileId() 293 if e != nil { 294 return false 295 } 296 e = fs2.loadFileId() 297 if e != nil { 298 return false 299 } 300 return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo 301 } 302 303 // For testing. 304 func atime(fi FileInfo) time.Time { 305 return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) 306 }