Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 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 ( |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 8 | "internal/syscall/windows" |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 9 | "sync" |
| 10 | "syscall" |
| 11 | "time" |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 12 | "unsafe" |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 13 | ) |
| 14 | |
| 15 | // A fileStat is the implementation of FileInfo returned by Stat and Lstat. |
| 16 | type fileStat struct { |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 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 |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 32 | |
| 33 | // used to implement SameFile |
| 34 | sync.Mutex |
Dan Willemsen | c78f714 | 2017-07-26 13:08:14 -0700 | [diff] [blame] | 35 | path string |
| 36 | vol uint32 |
| 37 | idxhi uint32 |
| 38 | idxlo uint32 |
| 39 | appendNameToPath bool |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 40 | } |
| 41 | |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 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 | } |
Colin Cross | 1371fe4 | 2019-03-19 21:08:48 -0700 | [diff] [blame] | 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{"GetFileInformationByHandleEx", path, err} |
| 62 | } |
| 63 | } |
| 64 | |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 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, |
Colin Cross | 1371fe4 | 2019-03-19 21:08:48 -0700 | [diff] [blame] | 76 | Reserved0: ti.ReparseTag, |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 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 | // newFileStatFromWin32finddata copies all required information |
| 84 | // from syscall.Win32finddata d into the newly created fileStat. |
| 85 | func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat { |
| 86 | return &fileStat{ |
| 87 | FileAttributes: d.FileAttributes, |
| 88 | CreationTime: d.CreationTime, |
| 89 | LastAccessTime: d.LastAccessTime, |
| 90 | LastWriteTime: d.LastWriteTime, |
| 91 | FileSizeHigh: d.FileSizeHigh, |
| 92 | FileSizeLow: d.FileSizeLow, |
| 93 | Reserved0: d.Reserved0, |
| 94 | } |
| 95 | } |
| 96 | |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 97 | func (fs *fileStat) isSymlink() bool { |
| 98 | // Use instructions described at |
| 99 | // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ |
| 100 | // to recognize whether it's a symlink. |
| 101 | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { |
| 102 | return false |
| 103 | } |
| 104 | return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK || |
| 105 | fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT |
| 106 | } |
| 107 | |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 108 | func (fs *fileStat) Size() int64 { |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 109 | return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow) |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | func (fs *fileStat) Mode() (m FileMode) { |
| 113 | if fs == &devNullStat { |
| 114 | return ModeDevice | ModeCharDevice | 0666 |
| 115 | } |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 116 | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 117 | m |= 0444 |
| 118 | } else { |
| 119 | m |= 0666 |
| 120 | } |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 121 | if fs.isSymlink() { |
Dan Willemsen | c78f714 | 2017-07-26 13:08:14 -0700 | [diff] [blame] | 122 | return m | ModeSymlink |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 123 | } |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 124 | if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { |
Dan Willemsen | c78f714 | 2017-07-26 13:08:14 -0700 | [diff] [blame] | 125 | m |= ModeDir | 0111 |
| 126 | } |
| 127 | switch fs.filetype { |
| 128 | case syscall.FILE_TYPE_PIPE: |
Dan Willemsen | 0c15709 | 2016-07-08 13:57:52 -0700 | [diff] [blame] | 129 | m |= ModeNamedPipe |
Dan Willemsen | c78f714 | 2017-07-26 13:08:14 -0700 | [diff] [blame] | 130 | case syscall.FILE_TYPE_CHAR: |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 131 | m |= ModeDevice | ModeCharDevice |
Dan Willemsen | 0c15709 | 2016-07-08 13:57:52 -0700 | [diff] [blame] | 132 | } |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 133 | return m |
| 134 | } |
| 135 | |
| 136 | func (fs *fileStat) ModTime() time.Time { |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 137 | return time.Unix(0, fs.LastWriteTime.Nanoseconds()) |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 138 | } |
| 139 | |
| 140 | // Sys returns syscall.Win32FileAttributeData for file fs. |
Dan Willemsen | f3f2eb6 | 2018-08-28 11:28:58 -0700 | [diff] [blame] | 141 | func (fs *fileStat) Sys() interface{} { |
| 142 | return &syscall.Win32FileAttributeData{ |
| 143 | FileAttributes: fs.FileAttributes, |
| 144 | CreationTime: fs.CreationTime, |
| 145 | LastAccessTime: fs.LastAccessTime, |
| 146 | LastWriteTime: fs.LastWriteTime, |
| 147 | FileSizeHigh: fs.FileSizeHigh, |
| 148 | FileSizeLow: fs.FileSizeLow, |
| 149 | } |
| 150 | } |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 151 | |
| 152 | func (fs *fileStat) loadFileId() error { |
| 153 | fs.Lock() |
| 154 | defer fs.Unlock() |
| 155 | if fs.path == "" { |
| 156 | // already done |
| 157 | return nil |
| 158 | } |
Dan Willemsen | c78f714 | 2017-07-26 13:08:14 -0700 | [diff] [blame] | 159 | var path string |
| 160 | if fs.appendNameToPath { |
| 161 | path = fs.path + `\` + fs.name |
| 162 | } else { |
| 163 | path = fs.path |
| 164 | } |
| 165 | pathp, err := syscall.UTF16PtrFromString(path) |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 166 | if err != nil { |
| 167 | return err |
| 168 | } |
Colin Cross | 1371fe4 | 2019-03-19 21:08:48 -0700 | [diff] [blame] | 169 | attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) |
| 170 | if fs.isSymlink() { |
| 171 | // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink. |
| 172 | // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted |
| 173 | attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT |
| 174 | } |
| 175 | h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0) |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 176 | if err != nil { |
| 177 | return err |
| 178 | } |
| 179 | defer syscall.CloseHandle(h) |
| 180 | var i syscall.ByHandleFileInformation |
Dan Willemsen | 0c15709 | 2016-07-08 13:57:52 -0700 | [diff] [blame] | 181 | err = syscall.GetFileInformationByHandle(h, &i) |
Colin Cross | 7bb052a | 2015-02-03 12:59:37 -0800 | [diff] [blame] | 182 | if err != nil { |
| 183 | return err |
| 184 | } |
| 185 | fs.path = "" |
| 186 | fs.vol = i.VolumeSerialNumber |
| 187 | fs.idxhi = i.FileIndexHigh |
| 188 | fs.idxlo = i.FileIndexLow |
| 189 | return nil |
| 190 | } |
| 191 | |
| 192 | // devNullStat is fileStat structure describing DevNull file ("NUL"). |
| 193 | var devNullStat = fileStat{ |
| 194 | name: DevNull, |
| 195 | // hopefully this will work for SameFile |
| 196 | vol: 0, |
| 197 | idxhi: 0, |
| 198 | idxlo: 0, |
| 199 | } |
| 200 | |
| 201 | func sameFile(fs1, fs2 *fileStat) bool { |
| 202 | e := fs1.loadFileId() |
| 203 | if e != nil { |
| 204 | return false |
| 205 | } |
| 206 | e = fs2.loadFileId() |
| 207 | if e != nil { |
| 208 | return false |
| 209 | } |
| 210 | return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo |
| 211 | } |
| 212 | |
| 213 | // For testing. |
| 214 | func atime(fi FileInfo) time.Time { |
| 215 | return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds()) |
| 216 | } |