]> Cypherpunks.ru repositories - gostls13.git/blob - src/os/types_windows.go
os: report IO_REPARSE_TAG_DEDUP files as regular in Stat and Lstat
[gostls13.git] / src / os / types_windows.go
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 }