Commit 37f390aa authored by Alex Brainman's avatar Alex Brainman

os: use GetFileAttributesEx to implement Stat on windows

Fixes #2129.

R=rsc
CC=golang-dev
https://golang.org/cl/4934049
parent 919cb2ec
......@@ -21,39 +21,6 @@ func epipecheck(file *File, e int) {
}
}
// Stat returns a FileInfo structure describing the named file and an error, if any.
// If name names a valid symbolic link, the returned FileInfo describes
// the file pointed at by the link and has fi.FollowedSymlink set to true.
// If name names an invalid symbolic link, the returned FileInfo describes
// the link itself and has fi.FollowedSymlink set to false.
func Stat(name string) (fi *FileInfo, err Error) {
var lstat, stat syscall.Stat_t
e := syscall.Lstat(name, &lstat)
if iserror(e) {
return nil, &PathError{"stat", name, Errno(e)}
}
statp := &lstat
if lstat.Mode&syscall.S_IFMT == syscall.S_IFLNK {
e := syscall.Stat(name, &stat)
if !iserror(e) {
statp = &stat
}
}
return fileInfoFromStat(name, new(FileInfo), &lstat, statp), nil
}
// Lstat returns the FileInfo structure describing the named file and an
// error, if any. If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow the link.
func Lstat(name string) (fi *FileInfo, err Error) {
var stat syscall.Stat_t
e := syscall.Lstat(name, &stat)
if iserror(e) {
return nil, &PathError{"lstat", name, Errno(e)}
}
return fileInfoFromStat(name, new(FileInfo), &stat, &stat), nil
}
// Remove removes the named file or directory.
func Remove(name string) Error {
// System call interface forces us to know
......
......@@ -94,6 +94,39 @@ func (file *File) Stat() (fi *FileInfo, err Error) {
return fileInfoFromStat(file.name, new(FileInfo), &stat, &stat), nil
}
// Stat returns a FileInfo structure describing the named file and an error, if any.
// If name names a valid symbolic link, the returned FileInfo describes
// the file pointed at by the link and has fi.FollowedSymlink set to true.
// If name names an invalid symbolic link, the returned FileInfo describes
// the link itself and has fi.FollowedSymlink set to false.
func Stat(name string) (fi *FileInfo, err Error) {
var lstat, stat syscall.Stat_t
e := syscall.Lstat(name, &lstat)
if iserror(e) {
return nil, &PathError{"stat", name, Errno(e)}
}
statp := &lstat
if lstat.Mode&syscall.S_IFMT == syscall.S_IFLNK {
e := syscall.Stat(name, &stat)
if !iserror(e) {
statp = &stat
}
}
return fileInfoFromStat(name, new(FileInfo), &lstat, statp), nil
}
// Lstat returns the FileInfo structure describing the named file and an
// error, if any. If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow the link.
func Lstat(name string) (fi *FileInfo, err Error) {
var stat syscall.Stat_t
e := syscall.Lstat(name, &stat)
if iserror(e) {
return nil, &PathError{"lstat", name, Errno(e)}
}
return fileInfoFromStat(name, new(FileInfo), &stat, &stat), nil
}
// Readdir reads the contents of the directory associated with file and
// returns an array of up to n FileInfo structures, as would be returned
// by Lstat, in directory order. Subsequent calls on the same file will yield
......
......@@ -39,8 +39,8 @@ func NewFile(fd syscall.Handle, name string) *File {
// Auxiliary information if the File describes a directory
type dirInfo struct {
stat syscall.Stat_t
usefirststat bool
data syscall.Win32finddata
needdata bool
}
const DevNull = "NUL"
......@@ -64,12 +64,11 @@ func openFile(name string, flag int, perm uint32) (file *File, err Error) {
func openDir(name string) (file *File, err Error) {
d := new(dirInfo)
r, e := syscall.FindFirstFile(syscall.StringToUTF16Ptr(name+"\\*"), &d.stat.Windata)
r, e := syscall.FindFirstFile(syscall.StringToUTF16Ptr(name+`\*`), &d.data)
if e != 0 {
return nil, &PathError{"open", name, Errno(e)}
}
f := NewFile(r, name)
d.usefirststat = true
f.dirinfo = d
return f, nil
}
......@@ -128,28 +127,6 @@ func (file *File) Close() Error {
return err
}
func (file *File) statFile(name string) (fi *FileInfo, err Error) {
var stat syscall.ByHandleFileInformation
e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &stat)
if e != 0 {
return nil, &PathError{"stat", file.name, Errno(e)}
}
return fileInfoFromByHandleInfo(new(FileInfo), file.name, &stat), nil
}
// Stat returns the FileInfo structure describing file.
// It returns the FileInfo and an error, if any.
func (file *File) Stat() (fi *FileInfo, err Error) {
if file == nil || file.fd < 0 {
return nil, EINVAL
}
if file.isdir() {
// I don't know any better way to do that for directory
return Stat(file.name)
}
return file.statFile(file.name)
}
// Readdir reads the contents of the directory associated with file and
// returns an array of up to n FileInfo structures, as would be returned
// by Lstat, in directory order. Subsequent calls on the same file will yield
......@@ -172,7 +149,6 @@ func (file *File) Readdir(n int) (fi []FileInfo, err Error) {
if !file.isdir() {
return nil, &PathError{"Readdir", file.name, ENOTDIR}
}
di := file.dirinfo
wantAll := n <= 0
size := n
if wantAll {
......@@ -180,11 +156,10 @@ func (file *File) Readdir(n int) (fi []FileInfo, err Error) {
size = 100
}
fi = make([]FileInfo, 0, size) // Empty with room to grow.
d := &file.dirinfo.data
for n != 0 {
if di.usefirststat {
di.usefirststat = false
} else {
e := syscall.FindNextFile(syscall.Handle(file.fd), &di.stat.Windata)
if file.dirinfo.needdata {
e := syscall.FindNextFile(syscall.Handle(file.fd), d)
if e != 0 {
if e == syscall.ERROR_NO_MORE_FILES {
break
......@@ -198,7 +173,8 @@ func (file *File) Readdir(n int) (fi []FileInfo, err Error) {
}
}
var f FileInfo
fileInfoFromWin32finddata(&f, &di.stat.Windata)
setFileInfo(&f, string(syscall.UTF16ToString(d.FileName[0:])), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime)
file.dirinfo.needdata = true
if f.Name == "." || f.Name == ".." { // Useless names
continue
}
......
......@@ -4,24 +4,76 @@
package os
import "syscall"
import (
"unsafe"
"syscall"
)
func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo {
return fileInfoFromWin32finddata(fi, &stat.Windata)
// Stat returns the FileInfo structure describing file.
// It returns the FileInfo and an error, if any.
func (file *File) Stat() (fi *FileInfo, err Error) {
if file == nil || file.fd < 0 {
return nil, EINVAL
}
if file.isdir() {
// I don't know any better way to do that for directory
return Stat(file.name)
}
var d syscall.ByHandleFileInformation
e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &d)
if e != 0 {
return nil, &PathError{"GetFileInformationByHandle", file.name, Errno(e)}
}
return setFileInfo(new(FileInfo), basename(file.name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil
}
func fileInfoFromWin32finddata(fi *FileInfo, d *syscall.Win32finddata) *FileInfo {
return setFileInfo(fi, string(syscall.UTF16ToString(d.FileName[0:])), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime)
// Stat returns a FileInfo structure describing the named file and an error, if any.
// If name names a valid symbolic link, the returned FileInfo describes
// the file pointed at by the link and has fi.FollowedSymlink set to true.
// If name names an invalid symbolic link, the returned FileInfo describes
// the link itself and has fi.FollowedSymlink set to false.
func Stat(name string) (fi *FileInfo, err Error) {
if len(name) == 0 {
return nil, &PathError{"Stat", name, Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
var d syscall.Win32FileAttributeData
e := syscall.GetFileAttributesEx(syscall.StringToUTF16Ptr(name), syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&d)))
if e != 0 {
return nil, &PathError{"GetFileAttributesEx", name, Errno(e)}
}
return setFileInfo(new(FileInfo), basename(name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil
}
func fileInfoFromByHandleInfo(fi *FileInfo, name string, d *syscall.ByHandleFileInformation) *FileInfo {
for i := len(name) - 1; i >= 0; i-- {
// Lstat returns the FileInfo structure describing the named file and an
// error, if any. If the file is a symbolic link, the returned FileInfo
// describes the symbolic link. Lstat makes no attempt to follow the link.
func Lstat(name string) (fi *FileInfo, err Error) {
// No links on Windows
return Stat(name)
}
// 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 setFileInfo(fi, name, d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime)
return name
}
func setFileInfo(fi *FileInfo, name string, fa, sizehi, sizelo uint32, ctime, atime, wtime syscall.Filetime) *FileInfo {
......
......@@ -207,6 +207,7 @@ func NewCallback(fn interface{}) uintptr
//sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (errno int)
//sys GetFileAttributes(name *uint16) (attrs uint32, errno int) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW
//sys SetFileAttributes(name *uint16, attrs uint32) (errno int) = kernel32.SetFileAttributesW
//sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (errno int) = kernel32.GetFileAttributesExW
//sys GetCommandLine() (cmd *uint16) = kernel32.GetCommandLineW
//sys CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, errno int) [failretval==nil] = shell32.CommandLineToArgvW
//sys LocalFree(hmem Handle) (handle Handle, errno int) [failretval!=0]
......@@ -354,39 +355,6 @@ func getStdHandle(h int) (fd Handle) {
return r
}
func Stat(path string, stat *Stat_t) (errno int) {
if len(path) == 0 {
return ERROR_PATH_NOT_FOUND
}
// Remove trailing slash.
if path[len(path)-1] == '/' || path[len(path)-1] == '\\' {
// Check if we're given root directory ("\" or "c:\").
if len(path) == 1 || (len(path) == 3 && path[1] == ':') {
// TODO(brainman): Perhaps should fetch other fields, not just FileAttributes.
stat.Windata = Win32finddata{}
a, e := GetFileAttributes(StringToUTF16Ptr(path))
if e != 0 {
return e
}
stat.Windata.FileAttributes = a
return 0
}
path = path[:len(path)-1]
}
h, e := FindFirstFile(StringToUTF16Ptr(path), &stat.Windata)
if e != 0 {
return e
}
defer FindClose(h)
stat.Mode = 0
return 0
}
func Lstat(path string, stat *Stat_t) (errno int) {
// no links on windows, just call Stat
return Stat(path, stat)
}
const ImplementsGetwd = true
func Getwd() (wd string, errno int) {
......
......@@ -66,6 +66,7 @@ var (
procSetFileTime = modkernel32.NewProc("SetFileTime")
procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW")
procSetFileAttributesW = modkernel32.NewProc("SetFileAttributesW")
procGetFileAttributesExW = modkernel32.NewProc("GetFileAttributesExW")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW")
procLocalFree = modkernel32.NewProc("LocalFree")
......@@ -142,7 +143,8 @@ func FreeLibrary(handle Handle) (errno int) {
}
func GetProcAddress(module Handle, procname string) (proc uintptr, errno int) {
proc, _, e1 := Syscall(procGetProcAddress.Addr(), 2, uintptr(module), uintptr(unsafe.Pointer(StringBytePtr(procname))), 0)
r0, _, e1 := Syscall(procGetProcAddress.Addr(), 2, uintptr(module), uintptr(unsafe.Pointer(StringBytePtr(procname))), 0)
proc = uintptr(r0)
if proc == 0 {
if e1 != 0 {
errno = int(e1)
......@@ -847,6 +849,20 @@ func SetFileAttributes(name *uint16, attrs uint32) (errno int) {
return
}
func GetFileAttributesEx(name *uint16, level uint32, info *byte) (errno int) {
r1, _, e1 := Syscall(procGetFileAttributesExW.Addr(), 3, uintptr(unsafe.Pointer(name)), uintptr(level), uintptr(unsafe.Pointer(info)))
if int(r1) == 0 {
if e1 != 0 {
errno = int(e1)
} else {
errno = EINVAL
}
} else {
errno = 0
}
return
}
func GetCommandLine() (cmd *uint16) {
r0, _, _ := Syscall(procGetCommandLineW.Addr(), 0, 0, 0, 0)
cmd = (*uint16)(unsafe.Pointer(r0))
......
......@@ -66,6 +66,7 @@ var (
procSetFileTime = modkernel32.NewProc("SetFileTime")
procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW")
procSetFileAttributesW = modkernel32.NewProc("SetFileAttributesW")
procGetFileAttributesExW = modkernel32.NewProc("GetFileAttributesExW")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW")
procLocalFree = modkernel32.NewProc("LocalFree")
......@@ -142,7 +143,8 @@ func FreeLibrary(handle Handle) (errno int) {
}
func GetProcAddress(module Handle, procname string) (proc uintptr, errno int) {
proc, _, e1 := Syscall(procGetProcAddress.Addr(), 2, uintptr(module), uintptr(unsafe.Pointer(StringBytePtr(procname))), 0)
r0, _, e1 := Syscall(procGetProcAddress.Addr(), 2, uintptr(module), uintptr(unsafe.Pointer(StringBytePtr(procname))), 0)
proc = uintptr(r0)
if proc == 0 {
if e1 != 0 {
errno = int(e1)
......@@ -847,6 +849,20 @@ func SetFileAttributes(name *uint16, attrs uint32) (errno int) {
return
}
func GetFileAttributesEx(name *uint16, level uint32, info *byte) (errno int) {
r1, _, e1 := Syscall(procGetFileAttributesExW.Addr(), 3, uintptr(unsafe.Pointer(name)), uintptr(level), uintptr(unsafe.Pointer(info)))
if int(r1) == 0 {
if e1 != 0 {
errno = int(e1)
} else {
errno = EINVAL
}
} else {
errno = 0
}
return
}
func GetCommandLine() (cmd *uint16) {
r0, _, _ := Syscall(procGetCommandLineW.Addr(), 0, 0, 0, 0)
cmd = (*uint16)(unsafe.Pointer(r0))
......
......@@ -244,6 +244,20 @@ type ByHandleFileInformation struct {
FileIndexLow uint32
}
const (
GetFileExInfoStandard = 0
GetFileExMaxInfoLevel = 1
)
type Win32FileAttributeData struct {
FileAttributes uint32
CreationTime Filetime
LastAccessTime Filetime
LastWriteTime Filetime
FileSizeHigh uint32
FileSizeLow uint32
}
// ShowWindow constants
const (
// winuser.h
......@@ -291,12 +305,6 @@ type ProcessInformation struct {
ThreadId uint32
}
// Invented values to support what package os expects.
type Stat_t struct {
Windata Win32finddata
Mode uint32
}
type Systemtime struct {
Year uint16
Month uint16
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment