patch os: File
This commit is contained in:
@@ -217,6 +217,7 @@ func setStickyBit(name string) error {
|
|||||||
}
|
}
|
||||||
return Chmod(name, fi.Mode()|ModeSticky)
|
return Chmod(name, fi.Mode()|ModeSticky)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Open opens the named file for reading. If successful, methods on
|
// Open opens the named file for reading. If successful, methods on
|
||||||
// the returned file can be used for reading; the associated file
|
// the returned file can be used for reading; the associated file
|
||||||
@@ -242,7 +243,6 @@ func Create(name string) (*File, error) {
|
|||||||
// methods on the returned File can be used for I/O.
|
// methods on the returned File can be used for I/O.
|
||||||
// If there is an error, it will be of type *PathError.
|
// If there is an error, it will be of type *PathError.
|
||||||
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
||||||
testlog.Open(name)
|
|
||||||
f, err := openFileNolog(name, flag, perm)
|
f, err := openFileNolog(name, flag, perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -252,6 +252,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
|||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// lstat is overridden in tests.
|
// lstat is overridden in tests.
|
||||||
var lstat = Lstat
|
var lstat = Lstat
|
||||||
|
|
||||||
|
|||||||
199
internal/lib/os/file_unix.go
Normal file
199
internal/lib/os/file_unix.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build unix || (js && wasm) || wasip1
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fd returns the integer Unix file descriptor referencing the open file.
|
||||||
|
// If f is closed, the file descriptor becomes invalid.
|
||||||
|
// If f is garbage collected, a finalizer may close the file descriptor,
|
||||||
|
// making it invalid; see runtime.SetFinalizer for more information on when
|
||||||
|
// a finalizer might be run. On Unix systems this will cause the SetDeadline
|
||||||
|
// methods to stop working.
|
||||||
|
// Because file descriptors can be reused, the returned file descriptor may
|
||||||
|
// only be closed through the Close method of f, or by its finalizer during
|
||||||
|
// garbage collection. Otherwise, during garbage collection the finalizer
|
||||||
|
// may close an unrelated file descriptor with the same (reused) number.
|
||||||
|
//
|
||||||
|
// As an alternative, see the f.SyscallConn method.
|
||||||
|
func (f *File) Fd() uintptr {
|
||||||
|
if f == nil {
|
||||||
|
return ^(uintptr(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.fd
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFile returns a new File with the given file descriptor and
|
||||||
|
// name. The returned value will be nil if fd is not a valid file
|
||||||
|
// descriptor. On Unix systems, if the file descriptor is in
|
||||||
|
// non-blocking mode, NewFile will attempt to return a pollable File
|
||||||
|
// (one for which the SetDeadline methods work).
|
||||||
|
//
|
||||||
|
// After passing it to NewFile, fd may become invalid under the same
|
||||||
|
// conditions described in the comments of the Fd method, and the same
|
||||||
|
// constraints apply.
|
||||||
|
func NewFile(fd uintptr, name string) *File {
|
||||||
|
return &File{fd: fd, name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(xsw):
|
||||||
|
// NewFile returns a new File with the given file descriptor and
|
||||||
|
// name. The returned value will be nil if fd is not a valid file
|
||||||
|
// descriptor. On Unix systems, if the file descriptor is in
|
||||||
|
// non-blocking mode, NewFile will attempt to return a pollable File
|
||||||
|
// (one for which the SetDeadline methods work).
|
||||||
|
//
|
||||||
|
// After passing it to NewFile, fd may become invalid under the same
|
||||||
|
// conditions described in the comments of the Fd method, and the same
|
||||||
|
// constraints apply.
|
||||||
|
func NewFile(fd uintptr, name string) *File {
|
||||||
|
fdi := int(fd)
|
||||||
|
if fdi < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := kindNewFile
|
||||||
|
appendMode := false
|
||||||
|
if flags, err := unix.Fcntl(fdi, syscall.F_GETFL, 0); err == nil {
|
||||||
|
if unix.HasNonblockFlag(flags) {
|
||||||
|
kind = kindNonBlock
|
||||||
|
}
|
||||||
|
appendMode = flags&syscall.O_APPEND != 0
|
||||||
|
}
|
||||||
|
f := newFile(fdi, name, kind)
|
||||||
|
f.appendMode = appendMode
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// net_newUnixFile is a hidden entry point called by net.conn.File.
|
||||||
|
// This is used so that a nonblocking network connection will become
|
||||||
|
// blocking if code calls the Fd method. We don't want that for direct
|
||||||
|
// calls to NewFile: passing a nonblocking descriptor to NewFile should
|
||||||
|
// remain nonblocking if you get it back using Fd. But for net.conn.File
|
||||||
|
// the call to NewFile is hidden from the user. Historically in that case
|
||||||
|
// the Fd method has returned a blocking descriptor, and we want to
|
||||||
|
// retain that behavior because existing code expects it and depends on it.
|
||||||
|
//
|
||||||
|
//-go:linkname net_newUnixFile net.newUnixFile
|
||||||
|
func net_newUnixFile(fd int, name string) *File {
|
||||||
|
if fd < 0 {
|
||||||
|
panic("invalid FD")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := newFile(fd, name, kindNonBlock)
|
||||||
|
f.nonblock = true // tell Fd to return blocking descriptor
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// newFileKind describes the kind of file to newFile.
|
||||||
|
type newFileKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// kindNewFile means that the descriptor was passed to us via NewFile.
|
||||||
|
kindNewFile newFileKind = iota
|
||||||
|
// kindOpenFile means that the descriptor was opened using
|
||||||
|
// Open, Create, or OpenFile (without O_NONBLOCK).
|
||||||
|
kindOpenFile
|
||||||
|
// kindPipe means that the descriptor was opened using Pipe.
|
||||||
|
kindPipe
|
||||||
|
// kindNonBlock means that the descriptor is already in
|
||||||
|
// non-blocking mode.
|
||||||
|
kindNonBlock
|
||||||
|
// kindNoPoll means that we should not put the descriptor into
|
||||||
|
// non-blocking mode, because we know it is not a pipe or FIFO.
|
||||||
|
// Used by openFdAt for directories.
|
||||||
|
kindNoPoll
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(xsw):
|
||||||
|
// func sigpipe() // implemented in package runtime
|
||||||
|
|
||||||
|
// epipecheck raises SIGPIPE if we get an EPIPE error on standard
|
||||||
|
// output or standard error. See the SIGPIPE docs in os/signal, and
|
||||||
|
// issue 11845.
|
||||||
|
func epipecheck(file *File, e error) {
|
||||||
|
/* TODO(xsw):
|
||||||
|
if e == syscall.EPIPE && file.stdoutOrErr {
|
||||||
|
sigpipe()
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
panic("todo: os.epipecheck")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DevNull is the name of the operating system's “null device.”
|
||||||
|
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
|
||||||
|
const DevNull = "/dev/null"
|
||||||
|
|
||||||
|
// openFileNolog is the Unix implementation of OpenFile.
|
||||||
|
// Changes here should be reflected in openFdAt, if relevant.
|
||||||
|
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
||||||
|
panic("todo: os.openFileNolog")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempDir() string {
|
||||||
|
dir := Getenv("TMPDIR")
|
||||||
|
if dir == "" {
|
||||||
|
if runtime.GOOS == "android" {
|
||||||
|
dir = "/data/local/tmp"
|
||||||
|
} else {
|
||||||
|
dir = "/tmp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
type unixDirent struct {
|
||||||
|
parent string
|
||||||
|
name string
|
||||||
|
typ FileMode
|
||||||
|
info FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *unixDirent) Name() string { return d.name }
|
||||||
|
func (d *unixDirent) IsDir() bool { return d.typ.IsDir() }
|
||||||
|
func (d *unixDirent) Type() FileMode { return d.typ }
|
||||||
|
|
||||||
|
func (d *unixDirent) Info() (FileInfo, error) {
|
||||||
|
/* TODO(xsw):
|
||||||
|
if d.info != nil {
|
||||||
|
return d.info, nil
|
||||||
|
}
|
||||||
|
return lstat(d.parent + "/" + d.name)
|
||||||
|
*/
|
||||||
|
panic("todo: os.unixDirent.Info")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *unixDirent) String() string {
|
||||||
|
return fs.FormatDirEntry(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(xsw):
|
||||||
|
func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) {
|
||||||
|
ude := &unixDirent{
|
||||||
|
parent: parent,
|
||||||
|
name: name,
|
||||||
|
typ: typ,
|
||||||
|
}
|
||||||
|
if typ != ^FileMode(0) && !testingForceReadDirLstat {
|
||||||
|
return ude, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := lstat(parent + "/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ude.typ = info.Mode().Type()
|
||||||
|
ude.info = info
|
||||||
|
return ude, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -202,7 +202,6 @@ func Getwd() (dir string, err error) {
|
|||||||
// func Hostname() (name string, err error)
|
// func Hostname() (name string, err error)
|
||||||
// func IsExist(err error) bool
|
// func IsExist(err error) bool
|
||||||
// func IsNotExist(err error) bool
|
// func IsNotExist(err error) bool
|
||||||
// func IsPathSeparator(c uint8) bool
|
|
||||||
// func IsPermission(err error) bool
|
// func IsPermission(err error) bool
|
||||||
// func IsTimeout(err error) bool
|
// func IsTimeout(err error) bool
|
||||||
|
|
||||||
|
|||||||
19
internal/lib/os/path_plan9.go
Normal file
19
internal/lib/os/path_plan9.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2011 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
|
||||||
|
|
||||||
|
const (
|
||||||
|
PathSeparator = '/' // OS-specific path separator
|
||||||
|
PathListSeparator = '\000' // OS-specific path list separator
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsPathSeparator reports whether c is a directory separator character.
|
||||||
|
func IsPathSeparator(c uint8) bool {
|
||||||
|
return PathSeparator == c
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
return p
|
||||||
|
}
|
||||||
75
internal/lib/os/path_unix.go
Normal file
75
internal/lib/os/path_unix.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2011 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.
|
||||||
|
|
||||||
|
//go:build unix || (js && wasm) || wasip1
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
const (
|
||||||
|
PathSeparator = '/' // OS-specific path separator
|
||||||
|
PathListSeparator = ':' // OS-specific path list separator
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsPathSeparator reports whether c is a directory separator character.
|
||||||
|
func IsPathSeparator(c uint8) bool {
|
||||||
|
return PathSeparator == c
|
||||||
|
}
|
||||||
|
|
||||||
|
// basename removes trailing slashes and the leading directory name from path name.
|
||||||
|
func basename(name string) string {
|
||||||
|
i := len(name) - 1
|
||||||
|
// Remove trailing slashes
|
||||||
|
for ; i > 0 && name[i] == '/'; i-- {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
// Remove leading directory name
|
||||||
|
for i--; i >= 0; i-- {
|
||||||
|
if name[i] == '/' {
|
||||||
|
name = name[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitPath returns the base name and parent directory.
|
||||||
|
func splitPath(path string) (string, string) {
|
||||||
|
// if no better parent is found, the path is relative from "here"
|
||||||
|
dirname := "."
|
||||||
|
|
||||||
|
// Remove all but one leading slash.
|
||||||
|
for len(path) > 1 && path[0] == '/' && path[1] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
i := len(path) - 1
|
||||||
|
|
||||||
|
// Remove trailing slashes.
|
||||||
|
for ; i > 0 && path[i] == '/'; i-- {
|
||||||
|
path = path[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no slashes in path, base is path
|
||||||
|
basename := path
|
||||||
|
|
||||||
|
// Remove leading directory path
|
||||||
|
for i--; i >= 0; i-- {
|
||||||
|
if path[i] == '/' {
|
||||||
|
if i == 0 {
|
||||||
|
dirname = path[:1]
|
||||||
|
} else {
|
||||||
|
dirname = path[:i]
|
||||||
|
}
|
||||||
|
basename = path[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirname, basename
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
return p
|
||||||
|
}
|
||||||
227
internal/lib/os/path_windows.go
Normal file
227
internal/lib/os/path_windows.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
// Copyright 2011 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
|
||||||
|
|
||||||
|
const (
|
||||||
|
PathSeparator = '\\' // OS-specific path separator
|
||||||
|
PathListSeparator = ';' // OS-specific path list separator
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsPathSeparator reports whether c is a directory separator character.
|
||||||
|
func IsPathSeparator(c uint8) bool {
|
||||||
|
// NOTE: Windows accepts / as path separator.
|
||||||
|
return c == '\\' || c == '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// basename removes trailing slashes and the leading
|
||||||
|
// directory name and drive letter from path name.
|
||||||
|
func basename(name string) string {
|
||||||
|
// Remove drive letter
|
||||||
|
if len(name) == 2 && name[1] == ':' {
|
||||||
|
name = "."
|
||||||
|
} else if len(name) > 2 && name[1] == ':' {
|
||||||
|
name = name[2:]
|
||||||
|
}
|
||||||
|
i := len(name) - 1
|
||||||
|
// Remove trailing slashes
|
||||||
|
for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- {
|
||||||
|
name = name[:i]
|
||||||
|
}
|
||||||
|
// Remove leading directory name
|
||||||
|
for i--; i >= 0; i-- {
|
||||||
|
if name[i] == '/' || name[i] == '\\' {
|
||||||
|
name = name[i+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromSlash(path string) string {
|
||||||
|
// Replace each '/' with '\\' if present
|
||||||
|
var pathbuf []byte
|
||||||
|
var lastSlash int
|
||||||
|
for i, b := range path {
|
||||||
|
if b == '/' {
|
||||||
|
if pathbuf == nil {
|
||||||
|
pathbuf = make([]byte, len(path))
|
||||||
|
}
|
||||||
|
copy(pathbuf[lastSlash:], path[lastSlash:i])
|
||||||
|
pathbuf[i] = '\\'
|
||||||
|
lastSlash = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pathbuf == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(pathbuf[lastSlash:], path[lastSlash:])
|
||||||
|
return string(pathbuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirname(path string) string {
|
||||||
|
vol := volumeName(path)
|
||||||
|
i := len(path) - 1
|
||||||
|
for i >= len(vol) && !IsPathSeparator(path[i]) {
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
dir := path[len(vol) : i+1]
|
||||||
|
last := len(dir) - 1
|
||||||
|
if last > 0 && IsPathSeparator(dir[last]) {
|
||||||
|
dir = dir[:last]
|
||||||
|
}
|
||||||
|
if dir == "" {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
return vol + dir
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is set via go:linkname on runtime.canUseLongPaths, and is true when the OS
|
||||||
|
// supports opting into proper long path handling without the need for fixups.
|
||||||
|
var canUseLongPaths bool
|
||||||
|
|
||||||
|
// fixLongPath returns the extended-length (\\?\-prefixed) form of
|
||||||
|
// path when needed, in order to avoid the default 260 character file
|
||||||
|
// path limit imposed by Windows. If path is not easily converted to
|
||||||
|
// the extended-length form (for example, if path is a relative path
|
||||||
|
// or contains .. elements), or is short enough, fixLongPath returns
|
||||||
|
// path unmodified.
|
||||||
|
//
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
|
||||||
|
func fixLongPath(path string) string {
|
||||||
|
if canUseLongPaths {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
// 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixRootDirectory fixes a reference to a drive's root directory to
|
||||||
|
// have the required trailing slash.
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
if len(p) == len(`\\?\c:`) {
|
||||||
|
if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
|
||||||
|
return p + `\`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
19
internal/lib/os/stat.go
Normal file
19
internal/lib/os/stat.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2017 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
|
||||||
|
|
||||||
|
// Stat returns a FileInfo describing the named file.
|
||||||
|
// If there is an error, it will be of type *PathError.
|
||||||
|
func Stat(name string) (FileInfo, error) {
|
||||||
|
return statNolog(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat returns a FileInfo describing the named file.
|
||||||
|
// If the file is a symbolic link, the returned FileInfo
|
||||||
|
// describes the symbolic link. Lstat makes no attempt to follow the link.
|
||||||
|
// If there is an error, it will be of type *PathError.
|
||||||
|
func Lstat(name string) (FileInfo, error) {
|
||||||
|
return lstatNolog(name)
|
||||||
|
}
|
||||||
42
internal/lib/os/stat_darwin.go
Normal file
42
internal/lib/os/stat_darwin.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// 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 (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fillFileStatFromSys(fs *fileStat, name string) {
|
||||||
|
fs.name = basename(name)
|
||||||
|
fs.size = fs.sys.Size
|
||||||
|
fs.modTime = time.Unix(fs.sys.Mtimespec.Unix())
|
||||||
|
fs.mode = FileMode(fs.sys.Mode & 0777)
|
||||||
|
switch fs.sys.Mode & syscall.S_IFMT {
|
||||||
|
case syscall.S_IFBLK, syscall.S_IFWHT:
|
||||||
|
fs.mode |= ModeDevice
|
||||||
|
case syscall.S_IFCHR:
|
||||||
|
fs.mode |= ModeDevice | ModeCharDevice
|
||||||
|
case syscall.S_IFDIR:
|
||||||
|
fs.mode |= ModeDir
|
||||||
|
case syscall.S_IFIFO:
|
||||||
|
fs.mode |= ModeNamedPipe
|
||||||
|
case syscall.S_IFLNK:
|
||||||
|
fs.mode |= ModeSymlink
|
||||||
|
case syscall.S_IFREG:
|
||||||
|
// nothing to do
|
||||||
|
case syscall.S_IFSOCK:
|
||||||
|
fs.mode |= ModeSocket
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISGID != 0 {
|
||||||
|
fs.mode |= ModeSetgid
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISUID != 0 {
|
||||||
|
fs.mode |= ModeSetuid
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISVTX != 0 {
|
||||||
|
fs.mode |= ModeSticky
|
||||||
|
}
|
||||||
|
}
|
||||||
42
internal/lib/os/stat_linux.go
Normal file
42
internal/lib/os/stat_linux.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// 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 (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fillFileStatFromSys(fs *fileStat, name string) {
|
||||||
|
fs.name = basename(name)
|
||||||
|
fs.size = fs.sys.Size
|
||||||
|
fs.modTime = time.Unix(fs.sys.Mtim.Unix())
|
||||||
|
fs.mode = FileMode(fs.sys.Mode & 0777)
|
||||||
|
switch fs.sys.Mode & syscall.S_IFMT {
|
||||||
|
case syscall.S_IFBLK:
|
||||||
|
fs.mode |= ModeDevice
|
||||||
|
case syscall.S_IFCHR:
|
||||||
|
fs.mode |= ModeDevice | ModeCharDevice
|
||||||
|
case syscall.S_IFDIR:
|
||||||
|
fs.mode |= ModeDir
|
||||||
|
case syscall.S_IFIFO:
|
||||||
|
fs.mode |= ModeNamedPipe
|
||||||
|
case syscall.S_IFLNK:
|
||||||
|
fs.mode |= ModeSymlink
|
||||||
|
case syscall.S_IFREG:
|
||||||
|
// nothing to do
|
||||||
|
case syscall.S_IFSOCK:
|
||||||
|
fs.mode |= ModeSocket
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISGID != 0 {
|
||||||
|
fs.mode |= ModeSetgid
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISUID != 0 {
|
||||||
|
fs.mode |= ModeSetuid
|
||||||
|
}
|
||||||
|
if fs.sys.Mode&syscall.S_ISVTX != 0 {
|
||||||
|
fs.mode |= ModeSticky
|
||||||
|
}
|
||||||
|
}
|
||||||
57
internal/lib/os/stat_unix.go
Normal file
57
internal/lib/os/stat_unix.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
//go:build unix || (js && wasm) || wasip1
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
// Stat returns the FileInfo structure describing file.
|
||||||
|
// If there is an error, it will be of type *PathError.
|
||||||
|
func (f *File) Stat() (FileInfo, error) {
|
||||||
|
/* TODO(xsw):
|
||||||
|
if f == nil {
|
||||||
|
return nil, ErrInvalid
|
||||||
|
}
|
||||||
|
var fs fileStat
|
||||||
|
err := os.Fstat(c.Int(f.fd), &fs.sys)
|
||||||
|
if err != 0 {
|
||||||
|
return nil, &PathError{Op: "stat", Path: f.name, Err: syscall.Errno(err)}
|
||||||
|
}
|
||||||
|
fillFileStatFromSys(&fs, f.name)
|
||||||
|
return &fs, nil
|
||||||
|
*/
|
||||||
|
panic("todo: os.File.Stat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// statNolog stats a file with no test logging.
|
||||||
|
func statNolog(name string) (FileInfo, error) {
|
||||||
|
/* TODO(xsw):
|
||||||
|
var fs fileStat
|
||||||
|
err := ignoringEINTR(func() error {
|
||||||
|
return syscall.Stat(name, &fs.sys)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, &PathError{Op: "stat", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
fillFileStatFromSys(&fs, name)
|
||||||
|
return &fs, nil
|
||||||
|
*/
|
||||||
|
panic("todo: os.statNolog")
|
||||||
|
}
|
||||||
|
|
||||||
|
// lstatNolog lstats a file with no test logging.
|
||||||
|
func lstatNolog(name string) (FileInfo, error) {
|
||||||
|
/* TODO(xsw):
|
||||||
|
var fs fileStat
|
||||||
|
err := ignoringEINTR(func() error {
|
||||||
|
return syscall.Lstat(name, &fs.sys)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, &PathError{Op: "lstat", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
fillFileStatFromSys(&fs, name)
|
||||||
|
return &fs, nil
|
||||||
|
*/
|
||||||
|
panic("todo: os.lstatNolog")
|
||||||
|
}
|
||||||
@@ -22,19 +22,7 @@ type File struct {
|
|||||||
fd uintptr
|
fd uintptr
|
||||||
name string
|
name string
|
||||||
appendMode bool
|
appendMode bool
|
||||||
}
|
nonblock bool
|
||||||
|
|
||||||
// NewFile returns a new File with the given file descriptor and
|
|
||||||
// name. The returned value will be nil if fd is not a valid file
|
|
||||||
// descriptor. On Unix systems, if the file descriptor is in
|
|
||||||
// non-blocking mode, NewFile will attempt to return a pollable File
|
|
||||||
// (one for which the SetDeadline methods work).
|
|
||||||
//
|
|
||||||
// After passing it to NewFile, fd may become invalid under the same
|
|
||||||
// conditions described in the comments of the Fd method, and the same
|
|
||||||
// constraints apply.
|
|
||||||
func NewFile(fd uintptr, name string) *File {
|
|
||||||
return &File{fd: fd, name: name}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write writes len(b) bytes to the File.
|
// write writes len(b) bytes to the File.
|
||||||
@@ -118,10 +106,8 @@ const (
|
|||||||
ModePerm = fs.ModePerm // Unix permission bits, 0o777
|
ModePerm = fs.ModePerm // Unix permission bits, 0o777
|
||||||
)
|
)
|
||||||
|
|
||||||
/* TODO(xsw):
|
|
||||||
func (fs *fileStat) Name() string { return fs.name }
|
func (fs *fileStat) Name() string { return fs.name }
|
||||||
func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
|
func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
|
||||||
*/
|
|
||||||
|
|
||||||
// SameFile reports whether fi1 and fi2 describe the same file.
|
// SameFile reports whether fi1 and fi2 describe the same file.
|
||||||
// For example, on Unix this means that the device and inode fields
|
// For example, on Unix this means that the device and inode fields
|
||||||
@@ -130,13 +116,10 @@ func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
|
|||||||
// SameFile only applies to results returned by this package's Stat.
|
// SameFile only applies to results returned by this package's Stat.
|
||||||
// It returns false in other cases.
|
// It returns false in other cases.
|
||||||
func SameFile(fi1, fi2 FileInfo) bool {
|
func SameFile(fi1, fi2 FileInfo) bool {
|
||||||
/*
|
fs1, ok1 := fi1.(*fileStat)
|
||||||
fs1, ok1 := fi1.(*fileStat)
|
fs2, ok2 := fi2.(*fileStat)
|
||||||
fs2, ok2 := fi2.(*fileStat)
|
if !ok1 || !ok2 {
|
||||||
if !ok1 || !ok2 {
|
return false
|
||||||
return false
|
}
|
||||||
}
|
return sameFile(fs1, fs2)
|
||||||
return sameFile(fs1, fs2)
|
|
||||||
*/
|
|
||||||
panic("todo")
|
|
||||||
}
|
}
|
||||||
|
|||||||
30
internal/lib/os/types_plan9.go
Normal file
30
internal/lib/os/types_plan9.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
|
||||||
|
type fileStat struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode FileMode
|
||||||
|
modTime time.Time
|
||||||
|
sys any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileStat) Size() int64 { return fs.size }
|
||||||
|
func (fs *fileStat) Mode() FileMode { return fs.mode }
|
||||||
|
func (fs *fileStat) ModTime() time.Time { return fs.modTime }
|
||||||
|
func (fs *fileStat) Sys() any { return fs.sys }
|
||||||
|
|
||||||
|
func sameFile(fs1, fs2 *fileStat) bool {
|
||||||
|
a := fs1.sys.(*syscall.Dir)
|
||||||
|
b := fs2.sys.(*syscall.Dir)
|
||||||
|
return a.Qid.Path == b.Qid.Path && a.Type == b.Type && a.Dev == b.Dev
|
||||||
|
}
|
||||||
30
internal/lib/os/types_unix.go
Normal file
30
internal/lib/os/types_unix.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !windows && !plan9
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
|
||||||
|
type fileStat struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode FileMode
|
||||||
|
modTime time.Time
|
||||||
|
sys syscall.Stat_t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileStat) Size() int64 { return fs.size }
|
||||||
|
func (fs *fileStat) Mode() FileMode { return fs.mode }
|
||||||
|
func (fs *fileStat) ModTime() time.Time { return fs.modTime }
|
||||||
|
func (fs *fileStat) Sys() any { return &fs.sys }
|
||||||
|
|
||||||
|
func sameFile(fs1, fs2 *fileStat) bool {
|
||||||
|
return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
|
||||||
|
}
|
||||||
241
internal/lib/os/types_windows.go
Normal file
241
internal/lib/os/types_windows.go
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// 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
|
||||||
|
ReparseTag 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{Op: "GetFileInformationByHandle", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ti windows.FILE_ATTRIBUTE_TAG_INFO
|
||||||
|
err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
|
||||||
|
if err != nil {
|
||||||
|
if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
|
||||||
|
// It appears calling GetFileInformationByHandleEx with
|
||||||
|
// FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
|
||||||
|
// ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
|
||||||
|
// instance to indicate no symlinks are possible.
|
||||||
|
ti.ReparseTag = 0
|
||||||
|
} else {
|
||||||
|
return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: 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,
|
||||||
|
ReparseTag: ti.ReparseTag,
|
||||||
|
// 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 {
|
||||||
|
fs := &fileStat{
|
||||||
|
FileAttributes: d.FileAttributes,
|
||||||
|
CreationTime: d.CreationTime,
|
||||||
|
LastAccessTime: d.LastAccessTime,
|
||||||
|
LastWriteTime: d.LastWriteTime,
|
||||||
|
FileSizeHigh: d.FileSizeHigh,
|
||||||
|
FileSizeLow: d.FileSizeLow,
|
||||||
|
}
|
||||||
|
if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||||
|
// Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
|
||||||
|
// “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
|
||||||
|
// attribute, this member specifies the reparse point tag. Otherwise, this
|
||||||
|
// value is undefined and should not be used.”
|
||||||
|
fs.ReparseTag = d.Reserved0
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fileStat) isSymlink() bool {
|
||||||
|
// As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as
|
||||||
|
// symlinks because otherwise certain directory junction tests in the
|
||||||
|
// path/filepath package would fail.
|
||||||
|
//
|
||||||
|
// However,
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
|
||||||
|
// seems to suggest that directory junctions should be treated like hard
|
||||||
|
// links, not symlinks.
|
||||||
|
//
|
||||||
|
// TODO(bcmills): Get more input from Microsoft on what the behavior ought to
|
||||||
|
// be for MOUNT_POINT reparse points.
|
||||||
|
|
||||||
|
return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
|
||||||
|
fs.ReparseTag == 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.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
|
||||||
|
}
|
||||||
|
if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 {
|
||||||
|
m |= ModeIrregular
|
||||||
|
}
|
||||||
|
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() any {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
|
||||||
|
// “Applications that use the CreateFile function should specify the
|
||||||
|
// FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
|
||||||
|
// point.”
|
||||||
|
//
|
||||||
|
// And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
|
||||||
|
// “If the file is not a reparse point, then this flag is ignored.”
|
||||||
|
//
|
||||||
|
// So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
|
||||||
|
// information about the reparse point itself.
|
||||||
|
//
|
||||||
|
// If the file is a symlink, the symlink target should have already been
|
||||||
|
// resolved when the fileStat was created, so we don't need to worry about
|
||||||
|
// resolving symlink reparse points again here.
|
||||||
|
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
|
||||||
|
|
||||||
|
h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveInfoFromPath saves full path of the file to be used by os.SameFile later,
|
||||||
|
// and set name from path.
|
||||||
|
func (fs *fileStat) saveInfoFromPath(path string) error {
|
||||||
|
fs.path = path
|
||||||
|
if !isAbs(fs.path) {
|
||||||
|
var err error
|
||||||
|
fs.path, err = syscall.FullPath(fs.path)
|
||||||
|
if err != nil {
|
||||||
|
return &PathError{Op: "FullPath", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fs.name = basename(path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user