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.
8 "internal/syscall/windows"
15 // A fileStat is the implementation of FileInfo returned by Stat and Lstat.
16 type fileStat struct {
19 // from ByHandleFileInformation, Win32FileAttributeData, Win32finddata, and GetFileInformationByHandleEx
21 CreationTime syscall.Filetime
22 LastAccessTime syscall.Filetime
23 LastWriteTime syscall.Filetime
27 // from Win32finddata and GetFileInformationByHandleEx
30 // what syscall.GetFileType returns
33 // used to implement SameFile
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)
48 return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err}
51 var ti windows.FILE_ATTRIBUTE_TAG_INFO
52 err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
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.
61 return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err}
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.
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.
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),
98 idxhi: uint32(d.FileID >> 32),
99 idxlo: uint32(d.FileID),
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 {
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,
117 // newFileStatFromWin32finddata copies all required information
118 // from syscall.Win32finddata d into the newly created fileStat.
119 func newFileStatFromWin32finddata(d *syscall.Win32finddata) *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,
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
138 // isReparseTagNameSurrogate determines whether a tag's associated
139 // reparse point is a surrogate for another named entity (for example, a mounted folder).
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
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.
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.
158 // TODO(bcmills): Get more input from Microsoft on what the behavior ought to
159 // be for MOUNT_POINT reparse points.
161 return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
162 fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
165 func (fs *fileStat) Size() int64 {
166 return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
169 func (fs *fileStat) Mode() (m FileMode) {
170 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
176 return m | ModeSymlink
178 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
182 case syscall.FILE_TYPE_PIPE:
184 case syscall.FILE_TYPE_CHAR:
185 m |= ModeDevice | ModeCharDevice
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.
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.
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.
209 func (fs *fileStat) ModTime() time.Time {
210 return time.Unix(0, fs.LastWriteTime.Nanoseconds())
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,
225 func (fs *fileStat) loadFileId() error {
233 if fs.appendNameToPath {
234 path = fixLongPath(fs.path + `\` + fs.name)
238 pathp, err := syscall.UTF16PtrFromString(path)
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
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.”
251 // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
252 // information about the reparse point itself.
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)
259 h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
263 defer syscall.CloseHandle(h)
264 var i syscall.ByHandleFileInformation
265 err = syscall.GetFileInformationByHandle(h, &i)
270 fs.vol = i.VolumeSerialNumber
271 fs.idxhi = i.FileIndexHigh
272 fs.idxlo = i.FileIndexLow
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 {
282 fs.path, err = syscall.FullPath(fs.path)
284 return &PathError{Op: "FullPath", Path: path, Err: err}
287 fs.name = basename(path)
291 func sameFile(fs1, fs2 *fileStat) bool {
292 e := fs1.loadFileId()
300 return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
304 func atime(fi FileInfo) time.Time {
305 return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())