You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wincmd/filestats_windows.go

257 lines
6.5 KiB
Go

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 ""
}