| // 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} | |
| } | |
| 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, | |
| // 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, | |
| } | |
| } | |
| // newFileStatFromGetFileAttributesExOrFindFirstFile calls GetFileAttributesEx | |
| // and FindFirstFile to gather all required information about the provided file path pathp. | |
| func newFileStatFromGetFileAttributesExOrFindFirstFile(path string, pathp *uint16) (*fileStat, error) { | |
| // As suggested by Microsoft, use GetFileAttributes() to acquire the file information, | |
| // and if it's a reparse point use FindFirstFile() to get the tag: | |
| // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363940(v=vs.85).aspx | |
| // Notice that always calling FindFirstFile can create performance problems | |
| // (https://golang.org/issues/19922#issuecomment-300031421) | |
| var fa syscall.Win32FileAttributeData | |
| err := syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) | |
| if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 { | |
| // Not a symlink. | |
| return &fileStat{ | |
| FileAttributes: fa.FileAttributes, | |
| CreationTime: fa.CreationTime, | |
| LastAccessTime: fa.LastAccessTime, | |
| LastWriteTime: fa.LastWriteTime, | |
| FileSizeHigh: fa.FileSizeHigh, | |
| FileSizeLow: fa.FileSizeLow, | |
| }, nil | |
| } | |
| // GetFileAttributesEx returns ERROR_INVALID_NAME if called | |
| // for invalid file name like "*.txt". Do not attempt to call | |
| // FindFirstFile with "*.txt", because FindFirstFile will | |
| // succeed. So just return ERROR_INVALID_NAME instead. | |
| // see https://golang.org/issue/24999 for details. | |
| if errno, _ := err.(syscall.Errno); errno == windows.ERROR_INVALID_NAME { | |
| return nil, &PathError{"GetFileAttributesEx", path, err} | |
| } | |
| // We might have symlink here. But some directories also have | |
| // FileAttributes FILE_ATTRIBUTE_REPARSE_POINT bit set. | |
| // For example, OneDrive directory is like that | |
| // (see golang.org/issue/22579 for details). | |
| // So use FindFirstFile instead to distinguish directories like | |
| // OneDrive from real symlinks (see instructions described at | |
| // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ | |
| // and in particular bits about using both FileAttributes and | |
| // Reserved0 fields). | |
| var fd syscall.Win32finddata | |
| sh, err := syscall.FindFirstFile(pathp, &fd) | |
| if err != nil { | |
| return nil, &PathError{"FindFirstFile", path, err} | |
| } | |
| syscall.FindClose(sh) | |
| return newFileStatFromWin32finddata(&fd), nil | |
| } | |
| func (fs *fileStat) updatePathAndName(name string) error { | |
| fs.path = name | |
| if !isAbs(fs.path) { | |
| var err error | |
| fs.path, err = syscall.FullPath(fs.path) | |
| if err != nil { | |
| return &PathError{"FullPath", name, err} | |
| } | |
| } | |
| fs.name = basename(name) | |
| return nil | |
| } | |
| 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 | |
| } | |
| h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 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()) | |
| } |