package wincmd import ( "b612.me/win32api" "golang.org/x/sys/windows" "os" "sync" "syscall" "time" ) const DevNull = "NUL" // 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 } // newFileStatFromWin32finddata copies all required information // from syscall.Win32finddata d into the newly created fileStat. func newFileStatFromInformation(d *syscall.ByHandleFileInformation, name string, path string) FileStat { return FileStat{ name: name, path: path, FileAttributes: d.FileAttributes, CreationTime: d.CreationTime, LastAccessTime: d.LastAccessTime, LastWriteTime: d.LastWriteTime, FileSizeHigh: d.FileSizeHigh, FileSizeLow: d.FileSizeLow, } } func (fs *FileStat) Name() string { return fs.name } func (fs *FileStat) IsDir() bool { return fs.FileAttributes&win32api.FILE_ATTRIBUTE_DIRECTORY != 0 } 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 os.FileMode) { if fs == &devNullStat { return os.ModeDevice | os.ModeCharDevice | 0666 } if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 { m |= 0444 } else { m |= 0666 } if fs.isSymlink() { return m | os.ModeSymlink } if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { m |= os.ModeDir | 0111 } switch fs.filetype { case syscall.FILE_TYPE_PIPE: m |= os.ModeNamedPipe case syscall.FILE_TYPE_CHAR: m |= os.ModeDevice | os.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, } } // saveInfoFromPath saves full path of the file to be used by os.SameFile later, // and set name from path. // 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 fixLongPath(path string) string { // Do nothing (and don't allocate) if the path is "short". // Empirically (at least on the Windows Server 2013 builder), // the kernel is arbitrarily okay with < 248 bytes. That // matches what the docs above say: // "When using an API to create a directory, the specified // path cannot be so long that you cannot append an 8.3 file // name (that is, the directory name cannot exceed MAX_PATH // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. // // The MSDN docs appear to say that a normal path that is 248 bytes long // will work; empirically the path must be less then 248 bytes long. if len(path) < 248 { // Don't fix. (This is how Go 1.7 and earlier worked, // not automatically generating the \\?\ form) return path } // The extended form begins with \\?\, as in // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. // The extended form disables evaluation of . and .. path // elements and disables the interpretation of / as equivalent // to \. The conversion here rewrites / to \ and elides // . elements as well as trailing or duplicate separators. For // simplicity it avoids the conversion entirely for relative // paths or paths containing .. elements. For now, // \\server\share paths are not converted to // \\?\UNC\server\share paths because the rules for doing so // are less well-specified. if len(path) >= 2 && path[:2] == `\\` { // Don't canonicalize UNC paths. return path } if !isAbs(path) { // Relative path return path } const prefix = `\\?` pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) copy(pathbuf, prefix) n := len(path) r, w := 0, len(prefix) for r < n { switch { case IsPathSeparator(path[r]): // empty block r++ case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])): // /./ r++ case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])): // /../ is currently unhandled return path default: pathbuf[w] = '\\' w++ for ; r < n && !IsPathSeparator(path[r]); r++ { pathbuf[w] = path[r] w++ } } } // A drive's root directory needs a trailing \ if w == len(`\\?\c:`) { pathbuf[w] = '\\' w++ } return string(pathbuf[:w]) } func IsPathSeparator(c uint8) bool { // NOTE: Windows accept / as path separator. return c == '\\' || c == '/' } func isAbs(path string) (b bool) { v := volumeName(path) if v == "" { return false } path = path[len(v):] if path == "" { return false } return IsPathSeparator(path[0]) } func volumeName(path string) (v string) { if len(path) < 2 { return "" } // with drive letter c := path[0] if path[1] == ':' && ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') { return path[:2] } // is it UNC if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && !IsPathSeparator(path[2]) && path[2] != '.' { // first, leading `\\` and next shouldn't be `\`. its server name. for n := 3; n < l-1; n++ { // second, next '\' shouldn't be repeated. if IsPathSeparator(path[n]) { n++ // third, following something characters. its share name. if !IsPathSeparator(path[n]) { if path[n] == '.' { break } for ; n < l; n++ { if IsPathSeparator(path[n]) { break } } return path[:n] } break } } } return "" }