mirror of
https://github.com/golang/go
synced 2024-10-14 11:53:56 +00:00
os,syscall: File.Stat to use file handle for directories on Windows
Updates syscall.Open to support opening directories via CreateFileW. CreateFileW handles are more versatile than FindFirstFile handles. They can be used in Win32 APIs like GetFileInformationByHandle and SetFilePointerEx, which are needed by some Go APIs. Fixes #52747 Fixes #36019 Change-Id: I26a00cef9844fb4abeeb18d2f9d854162a146651 Reviewed-on: https://go-review.googlesource.com/c/go/+/405275 Reviewed-by: Roland Shoemaker <roland@golang.org> Reviewed-by: Patrik Nyblom <pnyb@google.com> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Bryan Mills <bcmills@google.com> Run-TryBot: Quim Muntal <quimmuntal@gmail.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
dc6b7c86df
commit
0f0aa5d8a6
|
@ -268,7 +268,6 @@ const (
|
||||||
kindNet fileKind = iota
|
kindNet fileKind = iota
|
||||||
kindFile
|
kindFile
|
||||||
kindConsole
|
kindConsole
|
||||||
kindDir
|
|
||||||
kindPipe
|
kindPipe
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -286,12 +285,10 @@ func (fd *FD) Init(net string, pollable bool) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch net {
|
switch net {
|
||||||
case "file":
|
case "file", "dir":
|
||||||
fd.kind = kindFile
|
fd.kind = kindFile
|
||||||
case "console":
|
case "console":
|
||||||
fd.kind = kindConsole
|
fd.kind = kindConsole
|
||||||
case "dir":
|
|
||||||
fd.kind = kindDir
|
|
||||||
case "pipe":
|
case "pipe":
|
||||||
fd.kind = kindPipe
|
fd.kind = kindPipe
|
||||||
case "tcp", "tcp4", "tcp6",
|
case "tcp", "tcp4", "tcp6",
|
||||||
|
@ -371,8 +368,6 @@ func (fd *FD) destroy() error {
|
||||||
case kindNet:
|
case kindNet:
|
||||||
// The net package uses the CloseFunc variable for testing.
|
// The net package uses the CloseFunc variable for testing.
|
||||||
err = CloseFunc(fd.Sysfd)
|
err = CloseFunc(fd.Sysfd)
|
||||||
case kindDir:
|
|
||||||
err = syscall.FindClose(fd.Sysfd)
|
|
||||||
default:
|
default:
|
||||||
err = syscall.CloseHandle(fd.Sysfd)
|
err = syscall.CloseHandle(fd.Sysfd)
|
||||||
}
|
}
|
||||||
|
@ -1008,15 +1003,6 @@ func (fd *FD) Seek(offset int64, whence int) (int64, error) {
|
||||||
return syscall.Seek(fd.Sysfd, offset, whence)
|
return syscall.Seek(fd.Sysfd, offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindNextFile wraps syscall.FindNextFile.
|
|
||||||
func (fd *FD) FindNextFile(data *syscall.Win32finddata) error {
|
|
||||||
if err := fd.incref(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fd.decref()
|
|
||||||
return syscall.FindNextFile(fd.Sysfd, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fchmod updates syscall.ByHandleFileInformation.Fileattributes when needed.
|
// Fchmod updates syscall.ByHandleFileInformation.Fileattributes when needed.
|
||||||
func (fd *FD) Fchmod(mode uint32) error {
|
func (fd *FD) Fchmod(mode uint32) error {
|
||||||
if err := fd.incref(); err != nil {
|
if err := fd.incref(); err != nil {
|
||||||
|
|
|
@ -11,8 +11,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
|
||||||
if !file.isdir() {
|
// If this file has no dirinfo, create one.
|
||||||
return nil, nil, nil, &PathError{Op: "readdir", Path: file.name, Err: syscall.ENOTDIR}
|
needdata := true
|
||||||
|
if file.dirinfo == nil {
|
||||||
|
needdata = false
|
||||||
|
file.dirinfo, err = openDir(file.name)
|
||||||
|
if err != nil {
|
||||||
|
err = &PathError{Op: "readdir", Path: file.name, Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wantAll := n <= 0
|
wantAll := n <= 0
|
||||||
if wantAll {
|
if wantAll {
|
||||||
|
@ -20,8 +27,8 @@ func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []Di
|
||||||
}
|
}
|
||||||
d := &file.dirinfo.data
|
d := &file.dirinfo.data
|
||||||
for n != 0 && !file.dirinfo.isempty {
|
for n != 0 && !file.dirinfo.isempty {
|
||||||
if file.dirinfo.needdata {
|
if needdata {
|
||||||
e := file.pfd.FindNextFile(d)
|
e := syscall.FindNextFile(file.dirinfo.h, d)
|
||||||
runtime.KeepAlive(file)
|
runtime.KeepAlive(file)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
if e == syscall.ERROR_NO_MORE_FILES {
|
if e == syscall.ERROR_NO_MORE_FILES {
|
||||||
|
@ -32,7 +39,7 @@ func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []Di
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.dirinfo.needdata = true
|
needdata = true
|
||||||
name := syscall.UTF16ToString(d.FileName[0:])
|
name := syscall.UTF16ToString(d.FileName[0:])
|
||||||
if name == "." || name == ".." { // Useless names
|
if name == "." || name == ".." { // Useless names
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -225,10 +225,6 @@ func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
|
||||||
// relative to the current offset, and 2 means relative to the end.
|
// relative to the current offset, and 2 means relative to the end.
|
||||||
// It returns the new offset and an error, if any.
|
// It returns the new offset and an error, if any.
|
||||||
// The behavior of Seek on a file opened with O_APPEND is not specified.
|
// The behavior of Seek on a file opened with O_APPEND is not specified.
|
||||||
//
|
|
||||||
// If f is a directory, the behavior of Seek varies by operating
|
|
||||||
// system; you can seek to the beginning of the directory on Unix-like
|
|
||||||
// operating systems, but not on Windows.
|
|
||||||
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
|
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
|
||||||
if err := f.checkValid("seek"); err != nil {
|
if err := f.checkValid("seek"); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
|
@ -86,10 +86,14 @@ func NewFile(fd uintptr, name string) *File {
|
||||||
|
|
||||||
// Auxiliary information if the File describes a directory
|
// Auxiliary information if the File describes a directory
|
||||||
type dirInfo struct {
|
type dirInfo struct {
|
||||||
data syscall.Win32finddata
|
h syscall.Handle // search handle created with FindFirstFile
|
||||||
needdata bool
|
data syscall.Win32finddata
|
||||||
path string
|
path string
|
||||||
isempty bool // set if FindFirstFile returns ERROR_FILE_NOT_FOUND
|
isempty bool // set if FindFirstFile returns ERROR_FILE_NOT_FOUND
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dirInfo) close() error {
|
||||||
|
return syscall.FindClose(d.h)
|
||||||
}
|
}
|
||||||
|
|
||||||
func epipecheck(file *File, e error) {
|
func epipecheck(file *File, e error) {
|
||||||
|
@ -99,17 +103,7 @@ func epipecheck(file *File, e error) {
|
||||||
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
|
// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
|
||||||
const DevNull = "NUL"
|
const DevNull = "NUL"
|
||||||
|
|
||||||
func (f *file) isdir() bool { return f != nil && f.dirinfo != nil }
|
func openDir(name string) (d *dirInfo, e error) {
|
||||||
|
|
||||||
func openFile(name string, flag int, perm FileMode) (file *File, err error) {
|
|
||||||
r, e := syscall.Open(fixLongPath(name), flag|syscall.O_CLOEXEC, syscallMode(perm))
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
return newFile(r, name, "file"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openDir(name string) (file *File, err error) {
|
|
||||||
var mask string
|
var mask string
|
||||||
|
|
||||||
path := fixLongPath(name)
|
path := fixLongPath(name)
|
||||||
|
@ -130,25 +124,27 @@ func openDir(name string) (file *File, err error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
d := new(dirInfo)
|
d = new(dirInfo)
|
||||||
r, e := syscall.FindFirstFile(maskp, &d.data)
|
d.h, e = syscall.FindFirstFile(maskp, &d.data)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
// FindFirstFile returns ERROR_FILE_NOT_FOUND when
|
// FindFirstFile returns ERROR_FILE_NOT_FOUND when
|
||||||
// no matching files can be found. Then, if directory
|
// no matching files can be found. Then, if directory
|
||||||
// exists, we should proceed.
|
// exists, we should proceed.
|
||||||
if e != syscall.ERROR_FILE_NOT_FOUND {
|
// If FindFirstFile failed because name does not point
|
||||||
return nil, e
|
// to a directory, we should return ENOTDIR.
|
||||||
}
|
|
||||||
var fa syscall.Win32FileAttributeData
|
var fa syscall.Win32FileAttributeData
|
||||||
pathp, e := syscall.UTF16PtrFromString(path)
|
pathp, e1 := syscall.UTF16PtrFromString(path)
|
||||||
if e != nil {
|
if e1 != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
e = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
|
e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
|
||||||
if e != nil {
|
if e1 != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
|
if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
|
||||||
|
return nil, syscall.ENOTDIR
|
||||||
|
}
|
||||||
|
if e != syscall.ERROR_FILE_NOT_FOUND {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
d.isempty = true
|
d.isempty = true
|
||||||
|
@ -157,12 +153,11 @@ func openDir(name string) (file *File, err error) {
|
||||||
if !isAbs(d.path) {
|
if !isAbs(d.path) {
|
||||||
d.path, e = syscall.FullPath(d.path)
|
d.path, e = syscall.FullPath(d.path)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
d.close()
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f := newFile(r, name, "dir")
|
return d, nil
|
||||||
f.dirinfo = d
|
|
||||||
return f, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// openFileNolog is the Windows implementation of OpenFile.
|
// openFileNolog is the Windows implementation of OpenFile.
|
||||||
|
@ -170,28 +165,36 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
||||||
}
|
}
|
||||||
r, errf := openFile(name, flag, perm)
|
path := fixLongPath(name)
|
||||||
if errf == nil {
|
r, e := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm))
|
||||||
return r, nil
|
if e != nil {
|
||||||
}
|
// We should return EISDIR when we are trying to open a directory with write access.
|
||||||
r, errd := openDir(name)
|
if e == syscall.ERROR_ACCESS_DENIED && (flag&O_WRONLY != 0 || flag&O_RDWR != 0) {
|
||||||
if errd == nil {
|
pathp, e1 := syscall.UTF16PtrFromString(path)
|
||||||
if flag&O_WRONLY != 0 || flag&O_RDWR != 0 {
|
if e1 == nil {
|
||||||
r.Close()
|
var fa syscall.Win32FileAttributeData
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: syscall.EISDIR}
|
e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
|
||||||
|
if e1 == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
e = syscall.EISDIR
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r, nil
|
return nil, &PathError{Op: "open", Path: name, Err: e}
|
||||||
}
|
}
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: errf}
|
f, e := newFile(r, name, "file"), nil
|
||||||
|
if e != nil {
|
||||||
|
return nil, &PathError{Op: "open", Path: name, Err: e}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (file *file) close() error {
|
func (file *file) close() error {
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
}
|
}
|
||||||
if file.isdir() && file.dirinfo.isempty {
|
if file.dirinfo != nil {
|
||||||
// "special" empty directories
|
file.dirinfo.close()
|
||||||
return nil
|
file.dirinfo = nil
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if e := file.pfd.Close(); e != nil {
|
if e := file.pfd.Close(); e != nil {
|
||||||
|
@ -211,6 +214,12 @@ func (file *file) close() error {
|
||||||
// relative to the current offset, and 2 means relative to the end.
|
// relative to the current offset, and 2 means relative to the end.
|
||||||
// It returns the new offset and an error, if any.
|
// It returns the new offset and an error, if any.
|
||||||
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
|
||||||
|
if f.dirinfo != nil {
|
||||||
|
// Free cached dirinfo, so we allocate a new one if we
|
||||||
|
// access this file as a directory again. See #35767 and #37161.
|
||||||
|
f.dirinfo.close()
|
||||||
|
f.dirinfo = nil
|
||||||
|
}
|
||||||
ret, err = f.pfd.Seek(offset, whence)
|
ret, err = f.pfd.Seek(offset, whence)
|
||||||
runtime.KeepAlive(f)
|
runtime.KeepAlive(f)
|
||||||
return ret, err
|
return ret, err
|
||||||
|
|
|
@ -2564,9 +2564,6 @@ func TestUserHomeDir(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDirSeek(t *testing.T) {
|
func TestDirSeek(t *testing.T) {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
testenv.SkipFlaky(t, 36019)
|
|
||||||
}
|
|
||||||
wd, err := Getwd()
|
wd, err := Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -1252,3 +1252,28 @@ func TestWindowsReadlink(t *testing.T) {
|
||||||
mklink(t, "relfilelink", "file")
|
mklink(t, "relfilelink", "file")
|
||||||
testReadlink(t, "relfilelink", "file")
|
testReadlink(t, "relfilelink", "file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenDirTOCTOU(t *testing.T) {
|
||||||
|
// Check opened directories can't be renamed until the handle is closed.
|
||||||
|
// See issue 52747.
|
||||||
|
tmpdir := t.TempDir()
|
||||||
|
dir := filepath.Join(tmpdir, "dir")
|
||||||
|
if err := os.Mkdir(dir, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f, err := os.Open(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
newpath := filepath.Join(tmpdir, "dir1")
|
||||||
|
err = os.Rename(dir, newpath)
|
||||||
|
if err == nil || !errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
|
||||||
|
f.Close()
|
||||||
|
t.Fatalf("Rename(%q, %q) = %v; want windows.ERROR_SHARING_VIOLATION", dir, newpath, err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
err = os.Rename(dir, newpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,10 +16,6 @@ func (file *File) Stat() (FileInfo, error) {
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return nil, ErrInvalid
|
return nil, ErrInvalid
|
||||||
}
|
}
|
||||||
if file.isdir() {
|
|
||||||
// I don't know any better way to do that for directory
|
|
||||||
return Stat(file.dirinfo.path)
|
|
||||||
}
|
|
||||||
return statHandle(file.name, file.pfd.Sysfd)
|
return statHandle(file.name, file.pfd.Sysfd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -371,8 +371,11 @@ func Open(path string, mode int, perm uint32) (fd Handle, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
h, e := CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
if createmode == OPEN_EXISTING && access == GENERIC_READ {
|
||||||
return h, e
|
// Necessary for opening directory handles.
|
||||||
|
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
||||||
|
}
|
||||||
|
return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Read(fd Handle, p []byte) (n int, err error) {
|
func Read(fd Handle, p []byte) (n int, err error) {
|
||||||
|
|
|
@ -15,6 +15,28 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestOpen_Dir(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
h, err := syscall.Open(dir, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open failed: %v", err)
|
||||||
|
}
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
h, err = syscall.Open(dir, syscall.O_RDONLY|syscall.O_TRUNC, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Open should have failed")
|
||||||
|
} else {
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
}
|
||||||
|
h, err = syscall.Open(dir, syscall.O_RDONLY|syscall.O_CREAT, 0)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Open should have failed")
|
||||||
|
} else {
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWin32finddata(t *testing.T) {
|
func TestWin32finddata(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue