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.
257 lines
6.5 KiB
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 ""
|
|
}
|