github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/os/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 and Win32finddata 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 28 Reserved0 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{"GetFileInformationByHandle", path, err} 49 } 50 return &fileStat{ 51 name: basename(path), 52 FileAttributes: d.FileAttributes, 53 CreationTime: d.CreationTime, 54 LastAccessTime: d.LastAccessTime, 55 LastWriteTime: d.LastWriteTime, 56 FileSizeHigh: d.FileSizeHigh, 57 FileSizeLow: d.FileSizeLow, 58 vol: d.VolumeSerialNumber, 59 idxhi: d.FileIndexHigh, 60 idxlo: d.FileIndexLow, 61 // fileStat.path is used by os.SameFile to decide if it needs 62 // to fetch vol, idxhi and idxlo. But these are already set, 63 // so set fileStat.path to "" to prevent os.SameFile doing it again. 64 }, nil 65 } 66 67 // newFileStatFromWin32finddata copies all required information 68 // from syscall.Win32finddata d into the newly created fileStat. 69 func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { 70 return &fileStat{ 71 FileAttributes: d.FileAttributes, 72 CreationTime: d.CreationTime, 73 LastAccessTime: d.LastAccessTime, 74 LastWriteTime: d.LastWriteTime, 75 FileSizeHigh: d.FileSizeHigh, 76 FileSizeLow: d.FileSizeLow, 77 Reserved0: d.Reserved0, 78 } 79 } 80 81 // newFileStatFromGetFileAttributesExOrFindFirstFile calls GetFileAttributesEx 82 // and FindFirstFile to gather all required information about the provided file path pathp. 83 func newFileStatFromGetFileAttributesExOrFindFirstFile(path string, pathp *uint16) (*fileStat, error) { 84 // As suggested by Microsoft, use GetFileAttributes() to acquire the file information, 85 // and if it's a reparse point use FindFirstFile() to get the tag: 86 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363940(v=vs.85).aspx 87 // Notice that always calling FindFirstFile can create performance problems 88 // (https://golang.org/issues/19922#issuecomment-300031421) 89 var fa syscall.Win32FileAttributeData 90 err := syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) 91 if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { 92 // Not a symlink. 93 return &fileStat{ 94 FileAttributes: fa.FileAttributes, 95 CreationTime: fa.CreationTime, 96 LastAccessTime: fa.LastAccessTime, 97 LastWriteTime: fa.LastWriteTime, 98 FileSizeHigh: fa.FileSizeHigh, 99 FileSizeLow: fa.FileSizeLow, 100 }, nil 101 } 102 // GetFileAttributesEx returns ERROR_INVALID_NAME if called 103 // for invalid file name like "*.txt". Do not attempt to call 104 // FindFirstFile with "*.txt", because FindFirstFile will 105 // succeed. So just return ERROR_INVALID_NAME instead. 106 // see https://golang.org/issue/24999 for details. 107 if errno, _ := err.(syscall.Errno); errno == windows.ERROR_INVALID_NAME { 108 return nil, &PathError{"GetFileAttributesEx", path, err} 109 } 110 // We might have symlink here. But some directories also have 111 // FileAttributes FILE_ATTRIBUTE_REPARSE_POINT bit set. 112 // For example, OneDrive directory is like that 113 // (see golang.org/issue/22579 for details). 114 // So use FindFirstFile instead to distinguish directories like 115 // OneDrive from real symlinks (see instructions described at 116 // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ 117 // and in particular bits about using both FileAttributes and 118 // Reserved0 fields). 119 var fd syscall.Win32finddata 120 sh, err := syscall.FindFirstFile(pathp, &fd) 121 if err != nil { 122 return nil, &PathError{"FindFirstFile", path, err} 123 } 124 syscall.FindClose(sh) 125 126 return newFileStatFromWin32finddata(&fd), nil 127 } 128 129 func (fs *fileStat) updatePathAndName(name string) error { 130 fs.path = name 131 if !isAbs(fs.path) { 132 var err error 133 fs.path, err = syscall.FullPath(fs.path) 134 if err != nil { 135 return &PathError{"FullPath", name, err} 136 } 137 } 138 fs.name = basename(name) 139 return nil 140 } 141 142 func (fs *fileStat) isSymlink() bool { 143 // Use instructions described at 144 // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ 145 // to recognize whether it's a symlink. 146 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { 147 return false 148 } 149 return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK || 150 fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT 151 } 152 153 func (fs *fileStat) Size() int64 { 154 return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) 155 } 156 157 func (fs *fileStat) Mode() (m FileMode) { 158 if fs == &devNullStat { 159 return ModeDevice | ModeCharDevice | 0666 160 } 161 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { 162 m |= 0444 163 } else { 164 m |= 0666 165 } 166 if fs.isSymlink() { 167 return m | ModeSymlink 168 } 169 if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 170 m |= ModeDir | 0111 171 } 172 switch fs.filetype { 173 case syscall.FILE_TYPE_PIPE: 174 m |= ModeNamedPipe 175 case syscall.FILE_TYPE_CHAR: 176 m |= ModeDevice | ModeCharDevice 177 } 178 return m 179 } 180 181 func (fs *fileStat) ModTime() time.Time { 182 return time.Unix(0, fs.LastWriteTime.Nanoseconds()) 183 } 184 185 // Sys returns syscall.Win32FileAttributeData for file fs. 186 func (fs *fileStat) Sys() interface{} { 187 return &syscall.Win32FileAttributeData{ 188 FileAttributes: fs.FileAttributes, 189 CreationTime: fs.CreationTime, 190 LastAccessTime: fs.LastAccessTime, 191 LastWriteTime: fs.LastWriteTime, 192 FileSizeHigh: fs.FileSizeHigh, 193 FileSizeLow: fs.FileSizeLow, 194 } 195 } 196 197 func (fs *fileStat) loadFileId() error { 198 fs.Lock() 199 defer fs.Unlock() 200 if fs.path == "" { 201 // already done 202 return nil 203 } 204 var path string 205 if fs.appendNameToPath { 206 path = fs.path + `\` + fs.name 207 } else { 208 path = fs.path 209 } 210 pathp, err := syscall.UTF16PtrFromString(path) 211 if err != nil { 212 return err 213 } 214 attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) 215 if fs.isSymlink() { 216 // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. 217 // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted 218 attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT 219 } 220 h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) 221 if err != nil { 222 return err 223 } 224 defer syscall.CloseHandle(h) 225 var i syscall.ByHandleFileInformation 226 err = syscall.GetFileInformationByHandle(h, &i) 227 if err != nil { 228 return err 229 } 230 fs.path = "" 231 fs.vol = i.VolumeSerialNumber 232 fs.idxhi = i.FileIndexHigh 233 fs.idxlo = i.FileIndexLow 234 return nil 235 } 236 237 // devNullStat is fileStat structure describing DevNull file ("NUL"). 238 var devNullStat = fileStat{ 239 name: DevNull, 240 // hopefully this will work for SameFile 241 vol: 0, 242 idxhi: 0, 243 idxlo: 0, 244 } 245 246 func sameFile(fs1, fs2 *fileStat) bool { 247 e := fs1.loadFileId() 248 if e != nil { 249 return false 250 } 251 e = fs2.loadFileId() 252 if e != nil { 253 return false 254 } 255 return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo 256 } 257 258 // For testing. 259 func atime(fi FileInfo) time.Time { 260 return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) 261 }