lin
2025-06-05 ed3dd9d3e7519a82bb871d5eedb24a2fa0c91f47
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
 
package os
 
import (
   "internal/syscall/windows"
   "sync"
   "syscall"
   "time"
   "unsafe"
)
 
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
   name string
 
   // from ByHandleFileInformation, Win32FileAttributeData and Win32finddata
   FileAttributes uint32
   CreationTime   syscall.Filetime
   LastAccessTime syscall.Filetime
   LastWriteTime  syscall.Filetime
   FileSizeHigh   uint32
   FileSizeLow    uint32
 
   // from Win32finddata
   Reserved0 uint32
 
   // what syscall.GetFileType returns
   filetype uint32
 
   // used to implement SameFile
   sync.Mutex
   path             string
   vol              uint32
   idxhi            uint32
   idxlo            uint32
   appendNameToPath bool
}
 
// newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
// to gather all required information about the file handle h.
func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
   var d syscall.ByHandleFileInformation
   err = syscall.GetFileInformationByHandle(h, &d)
   if err != nil {
       return nil, &PathError{"GetFileInformationByHandle", path, err}
   }
 
   var ti windows.FILE_ATTRIBUTE_TAG_INFO
   err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
   if err != nil {
       if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
           // It appears calling GetFileInformationByHandleEx with
           // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
           // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
           // instance to indicate no symlinks are possible.
           ti.ReparseTag = 0
       } else {
           return nil, &PathError{"GetFileInformationByHandleEx", path, err}
       }
   }
 
   return &fileStat{
       name:           basename(path),
       FileAttributes: d.FileAttributes,
       CreationTime:   d.CreationTime,
       LastAccessTime: d.LastAccessTime,
       LastWriteTime:  d.LastWriteTime,
       FileSizeHigh:   d.FileSizeHigh,
       FileSizeLow:    d.FileSizeLow,
       vol:            d.VolumeSerialNumber,
       idxhi:          d.FileIndexHigh,
       idxlo:          d.FileIndexLow,
       Reserved0:      ti.ReparseTag,
       // fileStat.path is used by os.SameFile to decide if it needs
       // to fetch vol, idxhi and idxlo. But these are already set,
       // so set fileStat.path to "" to prevent os.SameFile doing it again.
   }, nil
}
 
// newFileStatFromWin32finddata copies all required information
// from syscall.Win32finddata d into the newly created fileStat.
func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
   return &fileStat{
       FileAttributes: d.FileAttributes,
       CreationTime:   d.CreationTime,
       LastAccessTime: d.LastAccessTime,
       LastWriteTime:  d.LastWriteTime,
       FileSizeHigh:   d.FileSizeHigh,
       FileSizeLow:    d.FileSizeLow,
       Reserved0:      d.Reserved0,
   }
}
 
func (fs *fileStat) isSymlink() bool {
   // Use instructions described at
   // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
   // to recognize whether it's a symlink.
   if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
       return false
   }
   return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK ||
       fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT
}
 
func (fs *fileStat) Size() int64 {
   return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
}
 
func (fs *fileStat) Mode() (m FileMode) {
   if fs == &devNullStat {
       return ModeDevice | ModeCharDevice | 0666
   }
   if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
       m |= 0444
   } else {
       m |= 0666
   }
   if fs.isSymlink() {
       return m | ModeSymlink
   }
   if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
       m |= ModeDir | 0111
   }
   switch fs.filetype {
   case syscall.FILE_TYPE_PIPE:
       m |= ModeNamedPipe
   case syscall.FILE_TYPE_CHAR:
       m |= ModeDevice | ModeCharDevice
   }
   return m
}
 
func (fs *fileStat) ModTime() time.Time {
   return time.Unix(0, fs.LastWriteTime.Nanoseconds())
}
 
// Sys returns syscall.Win32FileAttributeData for file fs.
func (fs *fileStat) Sys() interface{} {
   return &syscall.Win32FileAttributeData{
       FileAttributes: fs.FileAttributes,
       CreationTime:   fs.CreationTime,
       LastAccessTime: fs.LastAccessTime,
       LastWriteTime:  fs.LastWriteTime,
       FileSizeHigh:   fs.FileSizeHigh,
       FileSizeLow:    fs.FileSizeLow,
   }
}
 
func (fs *fileStat) loadFileId() error {
   fs.Lock()
   defer fs.Unlock()
   if fs.path == "" {
       // already done
       return nil
   }
   var path string
   if fs.appendNameToPath {
       path = fs.path + `\` + fs.name
   } else {
       path = fs.path
   }
   pathp, err := syscall.UTF16PtrFromString(path)
   if err != nil {
       return err
   }
   attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
   if fs.isSymlink() {
       // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
       // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
       attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
   }
   h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
   if err != nil {
       return err
   }
   defer syscall.CloseHandle(h)
   var i syscall.ByHandleFileInformation
   err = syscall.GetFileInformationByHandle(h, &i)
   if err != nil {
       return err
   }
   fs.path = ""
   fs.vol = i.VolumeSerialNumber
   fs.idxhi = i.FileIndexHigh
   fs.idxlo = i.FileIndexLow
   return nil
}
 
// devNullStat is fileStat structure describing DevNull file ("NUL").
var devNullStat = fileStat{
   name: DevNull,
   // hopefully this will work for SameFile
   vol:   0,
   idxhi: 0,
   idxlo: 0,
}
 
func sameFile(fs1, fs2 *fileStat) bool {
   e := fs1.loadFileId()
   if e != nil {
       return false
   }
   e = fs2.loadFileId()
   if e != nil {
       return false
   }
   return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
}
 
// For testing.
func atime(fi FileInfo) time.Time {
   return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
}