257 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package usn
 | 
						|
 | 
						|
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 ""
 | 
						|
}
 |