os, syscall: implement Stat and Lstat

File.Stat is left as a stub for now.

Tests are a bit stubbed down because os.ReadDir, os.Symlink, and t.TempDir are not yet (fully) implemented.
TODO: reimport tests from upstream as those materialize.
Этот коммит содержится в:
Dan Kegel 2021-11-28 08:30:55 -08:00 коммит произвёл Ron Evans
родитель 5127b9d65b
коммит ec9fd3fb38
17 изменённых файлов: 1118 добавлений и 25 удалений

12
src/os/export_windows_test.go Обычный файл
Просмотреть файл

@ -0,0 +1,12 @@
// 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.
package os
// Export for testing.
var (
FixLongPath = fixLongPath
CanUseLongPaths = canUseLongPaths
)

Просмотреть файл

@ -171,11 +171,6 @@ func (f *File) Stat() (FileInfo, error) {
return nil, &PathError{"stat", f.name, ErrNotImplemented}
}
// Sync is a stub, not yet implemented
func (f *File) Sync() error {
return ErrNotImplemented
}
func (f *File) SyscallConn() (syscall.RawConn, error) {
return nil, ErrNotImplemented
}
@ -190,16 +185,6 @@ func (f *File) Truncate(size int64) error {
return &PathError{"truncate", f.name, ErrNotImplemented}
}
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
}
// PathError records an error and the operation and file path that caused it.
// TODO: PathError moved to io/fs in go 1.16 and left an alias in os/errors.go.
// Do the same once we drop support for go 1.15.
@ -245,16 +230,6 @@ const (
O_TRUNC int = syscall.O_TRUNC
)
// Stat is a stub, not yet implemented
func Stat(name string) (FileInfo, error) {
return nil, &PathError{"stat", name, ErrNotImplemented}
}
// Lstat is a stub, not yet implemented
func Lstat(name string) (FileInfo, error) {
return nil, &PathError{"lstat", name, ErrNotImplemented}
}
func Getwd() (string, error) {
return syscall.Getwd()
}

Просмотреть файл

@ -50,3 +50,19 @@ func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) {
}
return
}
// ignoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
// Also #20400 and #36644 are issues in which a signal handler is
// installed without setting SA_RESTART. None of these are the common case,
// but there are enough of them that it seems that we can't avoid
// an EINTR loop.
func ignoringEINTR(fn func() error) error {
for {
err := fn()
if err != syscall.EINTR {
return err
}
}
}

Просмотреть файл

@ -56,3 +56,21 @@ func tempDir() string {
func (f unixFileHandle) ReadAt(b []byte, offset int64) (n int, err error) {
return -1, ErrNotImplemented
}
// isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows.
// True is returned if name is 'NUL' whatever the case.
func isWindowsNulName(name string) bool {
if len(name) != 3 {
return false
}
if name[0] != 'n' && name[0] != 'N' {
return false
}
if name[1] != 'u' && name[1] != 'U' {
return false
}
if name[2] != 'l' && name[2] != 'L' {
return false
}
return true
}

35
src/os/path_unix.go Обычный файл
Просмотреть файл

@ -0,0 +1,35 @@
// +build darwin linux,!baremetal
// 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 {
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
}

216
src/os/path_windows.go Обычный файл
Просмотреть файл

@ -0,0 +1,216 @@
// 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 accept / 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])
}

21
src/os/stat.go Обычный файл
Просмотреть файл

@ -0,0 +1,21 @@
// +build !baremetal,!js
// 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)
}

51
src/os/stat_darwin.go Обычный файл
Просмотреть файл

@ -0,0 +1,51 @@
// 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 = timespecToTime(fs.sys.Mtim)
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
}
}
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
// For testing.
func atime(fi FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

53
src/os/stat_linux.go Обычный файл
Просмотреть файл

@ -0,0 +1,53 @@
// +build linux,!baremetal
// 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 = timespecToTime(fs.sys.Mtim)
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
}
}
func timespecToTime(ts syscall.Timespec) time.Time {
return time.Unix(int64(ts.Sec), int64(ts.Nsec))
}
// For testing.
func atime(fi FileInfo) time.Time {
return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
}

96
src/os/stat_test.go Обычный файл
Просмотреть файл

@ -0,0 +1,96 @@
// Copyright 2018 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_test
import (
"io/fs"
"os"
"path/filepath"
"testing"
)
// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) {
// TODO: revert to upstream test once fstat and readdir are implemented
// test os.Stat
sfi, err := os.Stat(path)
if err != nil {
t.Error(err)
return
}
statCheck(t, path, sfi)
// test os.Lstat
lsfi, err := os.Lstat(path)
if err != nil {
t.Error(err)
return
}
lstatCheck(t, path, lsfi)
if isLink {
if os.SameFile(sfi, lsfi) {
t.Errorf("stat and lstat of %q should not be the same", path)
}
} else {
if !os.SameFile(sfi, lsfi) {
t.Errorf("stat and lstat of %q should be the same", path)
}
}
}
// testIsDir verifies that fi refers to directory.
func testIsDir(t *testing.T, path string, fi fs.FileInfo) {
t.Helper()
if !fi.IsDir() {
t.Errorf("%q should be a directory", path)
}
if fi.Mode()&fs.ModeSymlink != 0 {
t.Errorf("%q should not be a symlink", path)
}
}
// testIsFile verifies that fi refers to file.
func testIsFile(t *testing.T, path string, fi fs.FileInfo) {
t.Helper()
if fi.IsDir() {
t.Errorf("%q should not be a directory", path)
}
if fi.Mode()&fs.ModeSymlink != 0 {
t.Errorf("%q should not be a symlink", path)
}
}
func testDirStats(t *testing.T, path string) {
testStatAndLstat(t, path, false, testIsDir, testIsDir)
}
func testFileStats(t *testing.T, path string) {
testStatAndLstat(t, path, false, testIsFile, testIsFile)
}
func TestDirAndSymlinkStats(t *testing.T) {
// TODO: revert to upstream test once symlinks and t.TempDir are implemented
tmpdir := os.TempDir()
dir := filepath.Join(tmpdir, "dir")
os.Remove(dir)
if err := os.Mkdir(dir, 0777); err != nil {
t.Fatal(err)
return
}
testDirStats(t, dir)
}
func TestFileAndSymlinkStats(t *testing.T) {
// TODO: revert to upstream test once symlinks and t.TempDir are implemented
tmpdir := os.TempDir()
file := filepath.Join(tmpdir, "file")
if err := os.WriteFile(file, []byte("abcdefg"), 0644); err != nil {
t.Fatal(err)
return
}
testFileStats(t, file)
}

42
src/os/stat_unix.go Обычный файл
Просмотреть файл

@ -0,0 +1,42 @@
// +build darwin linux,!baremetal
// 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.
package os
import (
"syscall"
)
// Sync is a stub, not yet implemented
func (f *File) Sync() error {
return ErrNotImplemented
}
// statNolog stats a file with no test logging.
func statNolog(name string) (FileInfo, error) {
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
}
// lstatNolog lstats a file with no test logging.
func lstatNolog(name string) (FileInfo, error) {
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
}

89
src/os/stat_windows.go Обычный файл
Просмотреть файл

@ -0,0 +1,89 @@
// 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"
"syscall"
"unsafe"
)
// Sync is a stub, not yet implemented
func (f *File) Sync() error {
return ErrNotImplemented
}
// stat implements both Stat and Lstat of a file.
func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) {
if len(name) == 0 {
return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
if isWindowsNulName(name) {
return &devNullStat, nil
}
namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
if err != nil {
return nil, &PathError{Op: funcname, Path: name, Err: err}
}
// Try GetFileAttributesEx first, because it is faster than CreateFile.
// See https://golang.org/issues/19922#issuecomment-300031421 for details.
var fa syscall.Win32FileAttributeData
err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
// Not a symlink.
fs := &fileStat{
FileAttributes: fa.FileAttributes,
CreationTime: fa.CreationTime,
LastAccessTime: fa.LastAccessTime,
LastWriteTime: fa.LastWriteTime,
FileSizeHigh: fa.FileSizeHigh,
FileSizeLow: fa.FileSizeLow,
}
if err := fs.saveInfoFromPath(name); err != nil {
return nil, err
}
return fs, nil
}
// GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
// files, like c:\pagefile.sys. Use FindFirstFile for such files.
if err == windows.ERROR_SHARING_VIOLATION {
var fd syscall.Win32finddata
sh, err := syscall.FindFirstFile(namep, &fd)
if err != nil {
return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
}
syscall.FindClose(sh)
fs := newFileStatFromWin32finddata(&fd)
if err := fs.saveInfoFromPath(name); err != nil {
return nil, err
}
return fs, nil
}
// Finally use CreateFile.
h, err := syscall.CreateFile(namep, 0, 0, nil,
syscall.OPEN_EXISTING, createFileAttrs, 0)
if err != nil {
return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
}
defer syscall.CloseHandle(h)
return newFileStatFromGetFileInformationByHandle(name, h)
}
// statNolog implements Stat for Windows.
func statNolog(name string) (FileInfo, error) {
return stat("Stat", name, syscall.FILE_FLAG_BACKUP_SEMANTICS)
}
// lstatNolog implements Lstat for Windows.
func lstatNolog(name string) (FileInfo, error) {
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
return stat("Lstat", name, attrs)
}

25
src/os/types.go Обычный файл
Просмотреть файл

@ -0,0 +1,25 @@
// +build !baremetal,!js
// 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
func (fs *fileStat) Name() string { return fs.name }
func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
// SameFile reports whether fi1 and fi2 describe the same file.
// For example, on Unix this means that the device and inode fields
// of the two underlying structures are identical; on other systems
// the decision may be based on the path names.
// SameFile only applies to results returned by this package's Stat.
// It returns false in other cases.
func SameFile(fi1, fi2 FileInfo) bool {
fs1, ok1 := fi1.(*fileStat)
fs2, ok2 := fi2.(*fileStat)
if !ok1 || !ok2 {
return false
}
return sameFile(fs1, fs2)
}

30
src/os/types_unix.go Обычный файл
Просмотреть файл

@ -0,0 +1,30 @@
// +build darwin linux,!baremetal
// 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 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() interface{} { return &fs.sys }
func sameFile(fs1, fs2 *fileStat) bool {
return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
}

231
src/os/types_windows.go Обычный файл
Просмотреть файл

@ -0,0 +1,231 @@
// 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
Reserved0 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,
Reserved0: 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 {
return &fileStat{
FileAttributes: d.FileAttributes,
CreationTime: d.CreationTime,
LastAccessTime: d.LastAccessTime,
LastWriteTime: d.LastWriteTime,
FileSizeHigh: d.FileSizeHigh,
FileSizeLow: d.FileSizeLow,
Reserved0: d.Reserved0,
}
}
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 FileMode) {
if fs == &devNullStat {
return ModeDevice | ModeCharDevice | 0666
}
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
}
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,
}
}
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
}
attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
if fs.isSymlink() {
// Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
// See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
attrs |= 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
}
// 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 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
}
// For testing.
func atime(fi FileInfo) time.Time {
return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
}

Просмотреть файл

@ -2,6 +2,10 @@
package syscall
import (
"unsafe"
)
// This file defines errno and constants to match the darwin libsystem ABI.
// Values have been copied from src/syscall/zerrors_darwin_amd64.go.
@ -97,3 +101,93 @@ const (
MAP_ANON = 0x1000 // allocated from memory, swap space
MAP_ANONYMOUS = MAP_ANON
)
type Timespec struct {
Sec int64
Nsec int64
}
// Go chose Linux's field names for Stat_t, see https://github.com/golang/go/issues/31735
type Stat_t struct {
Dev int32
Mode uint16
Nlink uint16
Ino uint64
Uid uint32
Gid uint32
Rdev int32
Pad_cgo_0 [4]byte
Atim Timespec
Mtim Timespec
Ctim Timespec
Btim Timespec
Size int64
Blocks int64
Blksize int32
Flags uint32
Gen uint32
Lspare int32
Qspare [2]int64
}
// Source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/_types/_s_ifmt.h
const (
S_IEXEC = 0x40
S_IFBLK = 0x6000
S_IFCHR = 0x2000
S_IFDIR = 0x4000
S_IFIFO = 0x1000
S_IFLNK = 0xa000
S_IFMT = 0xf000
S_IFREG = 0x8000
S_IFSOCK = 0xc000
S_IFWHT = 0xe000
S_IREAD = 0x100
S_IRGRP = 0x20
S_IROTH = 0x4
S_IRUSR = 0x100
S_IRWXG = 0x38
S_IRWXO = 0x7
S_IRWXU = 0x1c0
S_ISGID = 0x400
S_ISTXT = 0x200
S_ISUID = 0x800
S_ISVTX = 0x200
S_IWGRP = 0x10
S_IWOTH = 0x2
S_IWRITE = 0x80
S_IWUSR = 0x80
S_IXGRP = 0x8
S_IXOTH = 0x1
S_IXUSR = 0x40
)
func Stat(path string, p *Stat_t) (err error) {
data := cstring(path)
n := libc_stat(&data[0], unsafe.Pointer(p))
if n < 0 {
err = getErrno()
}
return
}
func Lstat(path string, p *Stat_t) (err error) {
data := cstring(path)
n := libc_lstat(&data[0], unsafe.Pointer(p))
if n < 0 {
err = getErrno()
}
return
}
// The odd $INODE64 suffix is an Apple compatibility feature, see https://assert.cc/posts/darwin_use_64_bit_inode_vs_ctypes/
// Without it, you get the old, smaller struct stat from mac os 10.2 or so.
// int stat(const char *path, struct stat * buf);
//export stat$INODE64
func libc_stat(pathname *byte, ptr unsafe.Pointer) int32
// int lstat(const char *path, struct stat * buf);
//export lstat$INODE64
func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32

Просмотреть файл

@ -2,6 +2,10 @@
package syscall
import (
"unsafe"
)
// https://github.com/WebAssembly/wasi-libc/blob/main/expected/wasm32-wasi/predefined-macros.txt
type Signal int
@ -139,3 +143,88 @@ const (
EXDEV Errno = 75 /* Cross-device link */
ENOTCAPABLE Errno = 76 /* Extension: Capabilities insufficient. */
)
// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h
type Timespec struct {
Sec int32
Nsec int64
}
// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_stat.h
// https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__typedef_ino_t.h
// etc.
// Go chose Linux's field names for Stat_t, see https://github.com/golang/go/issues/31735
type Stat_t struct {
Dev uint64
Ino uint64
Nlink uint64
Mode uint32
Uid uint32
Gid uint32
Pad_cgo_0 [4]byte
Rdev uint64
Size int64
Blksize int32
Blocks int64
Atim Timespec
Mtim Timespec
Ctim Timespec
Qspare [3]int64
}
// https://github.com/WebAssembly/wasi-libc/blob/main/libc-top-half/musl/include/sys/stat.h
const (
S_IFBLK = 0x6000
S_IFCHR = 0x2000
S_IFDIR = 0x4000
S_IFIFO = 0x1000
S_IFLNK = 0xa000
S_IFMT = 0xf000
S_IFREG = 0x8000
S_IFSOCK = 0xc000
S_IREAD = 0x100
S_IRGRP = 0x20
S_IROTH = 0x4
S_IRUSR = 0x100
S_IRWXG = 0x38
S_IRWXO = 0x7
S_IRWXU = 0x1c0
S_ISGID = 0x400
S_ISUID = 0x800
S_ISVTX = 0x200
S_IWGRP = 0x10
S_IWOTH = 0x2
S_IWRITE = 0x80
S_IWUSR = 0x80
S_IXGRP = 0x8
S_IXOTH = 0x1
S_IXUSR = 0x40
)
func Stat(path string, p *Stat_t) (err error) {
data := cstring(path)
n := libc_stat(&data[0], unsafe.Pointer(p))
if n < 0 {
err = getErrno()
}
return
}
func Lstat(path string, p *Stat_t) (err error) {
data := cstring(path)
n := libc_lstat(&data[0], unsafe.Pointer(p))
if n < 0 {
err = getErrno()
}
return
}
// int stat(const char *path, struct stat * buf);
//export stat
func libc_stat(pathname *byte, ptr unsafe.Pointer) int32
// int lstat(const char *path, struct stat * buf);
//export lstat
func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32