mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os,internal/syscall/windows: support O_* flags in Root.OpenFile
These file flags are supported by os.OpenFile since CL 699415. Closes #73676 Change-Id: Ib37102a565f538d394d2a94bd605d6c6004f3028 Reviewed-on: https://go-review.googlesource.com/c/go/+/724621 Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
parent
e0a4dffb0c
commit
b194f5d24a
5 changed files with 148 additions and 18 deletions
|
|
@ -5,6 +5,7 @@
|
||||||
package windows
|
package windows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"internal/oserror"
|
||||||
"runtime"
|
"runtime"
|
||||||
"structs"
|
"structs"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
@ -26,7 +27,6 @@ const (
|
||||||
// to avoid overlap.
|
// to avoid overlap.
|
||||||
const (
|
const (
|
||||||
O_NOFOLLOW_ANY = 0x200000000 // disallow symlinks anywhere in the path
|
O_NOFOLLOW_ANY = 0x200000000 // disallow symlinks anywhere in the path
|
||||||
O_OPEN_REPARSE = 0x400000000 // FILE_OPEN_REPARSE_POINT, used by Lstat
|
|
||||||
O_WRITE_ATTRS = 0x800000000 // FILE_WRITE_ATTRIBUTES, used by Chmod
|
O_WRITE_ATTRS = 0x800000000 // FILE_WRITE_ATTRIBUTES, used by Chmod
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -36,24 +36,54 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
|
||||||
}
|
}
|
||||||
|
|
||||||
var access, options uint32
|
var access, options uint32
|
||||||
|
// Map Win32 file flags to NT create options.
|
||||||
|
fileFlags := uint32(flag) & FileFlagsMask
|
||||||
|
if fileFlags&^ValidFileFlagsMask != 0 {
|
||||||
|
return syscall.InvalidHandle, oserror.ErrInvalid
|
||||||
|
}
|
||||||
|
if fileFlags&O_FILE_FLAG_OVERLAPPED == 0 {
|
||||||
|
options |= FILE_SYNCHRONOUS_IO_NONALERT
|
||||||
|
}
|
||||||
|
if fileFlags&O_FILE_FLAG_DELETE_ON_CLOSE != 0 {
|
||||||
|
access |= DELETE
|
||||||
|
}
|
||||||
|
setOptionFlag := func(ntFlag, win32Flag uint32) {
|
||||||
|
if fileFlags&win32Flag != 0 {
|
||||||
|
options |= ntFlag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOptionFlag(FILE_NO_INTERMEDIATE_BUFFERING, O_FILE_FLAG_NO_BUFFERING)
|
||||||
|
setOptionFlag(FILE_WRITE_THROUGH, O_FILE_FLAG_WRITE_THROUGH)
|
||||||
|
setOptionFlag(FILE_SEQUENTIAL_ONLY, O_FILE_FLAG_SEQUENTIAL_SCAN)
|
||||||
|
setOptionFlag(FILE_RANDOM_ACCESS, O_FILE_FLAG_RANDOM_ACCESS)
|
||||||
|
setOptionFlag(FILE_OPEN_FOR_BACKUP_INTENT, O_FILE_FLAG_BACKUP_SEMANTICS)
|
||||||
|
setOptionFlag(FILE_SESSION_AWARE, O_FILE_FLAG_SESSION_AWARE)
|
||||||
|
setOptionFlag(FILE_DELETE_ON_CLOSE, O_FILE_FLAG_DELETE_ON_CLOSE)
|
||||||
|
setOptionFlag(FILE_OPEN_NO_RECALL, O_FILE_FLAG_OPEN_NO_RECALL)
|
||||||
|
setOptionFlag(FILE_OPEN_REPARSE_POINT, O_FILE_FLAG_OPEN_REPARSE_POINT)
|
||||||
|
|
||||||
switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
switch flag & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
||||||
case syscall.O_RDONLY:
|
case syscall.O_RDONLY:
|
||||||
// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
|
// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
|
||||||
access = FILE_GENERIC_READ
|
access |= FILE_GENERIC_READ
|
||||||
case syscall.O_WRONLY:
|
case syscall.O_WRONLY:
|
||||||
access = FILE_GENERIC_WRITE
|
access |= FILE_GENERIC_WRITE
|
||||||
options |= FILE_NON_DIRECTORY_FILE
|
options |= FILE_NON_DIRECTORY_FILE
|
||||||
case syscall.O_RDWR:
|
case syscall.O_RDWR:
|
||||||
access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
|
access |= FILE_GENERIC_READ | FILE_GENERIC_WRITE
|
||||||
options |= FILE_NON_DIRECTORY_FILE
|
options |= FILE_NON_DIRECTORY_FILE
|
||||||
default:
|
default:
|
||||||
// Stat opens files without requesting read or write permissions,
|
// Stat opens files without requesting read or write permissions,
|
||||||
// but we still need to request SYNCHRONIZE.
|
// but we still need to request SYNCHRONIZE.
|
||||||
access = SYNCHRONIZE
|
access |= SYNCHRONIZE
|
||||||
}
|
}
|
||||||
if flag&syscall.O_CREAT != 0 {
|
if flag&syscall.O_CREAT != 0 {
|
||||||
access |= FILE_GENERIC_WRITE
|
access |= FILE_GENERIC_WRITE
|
||||||
}
|
}
|
||||||
|
if fileFlags&O_FILE_FLAG_NO_BUFFERING != 0 {
|
||||||
|
// Disable buffering implies no implicit append access.
|
||||||
|
access &^= FILE_APPEND_DATA
|
||||||
|
}
|
||||||
if flag&syscall.O_APPEND != 0 {
|
if flag&syscall.O_APPEND != 0 {
|
||||||
access |= FILE_APPEND_DATA
|
access |= FILE_APPEND_DATA
|
||||||
// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
|
// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
|
||||||
|
|
@ -82,14 +112,13 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
|
||||||
if flag&syscall.O_CLOEXEC == 0 {
|
if flag&syscall.O_CLOEXEC == 0 {
|
||||||
objAttrs.Attributes |= OBJ_INHERIT
|
objAttrs.Attributes |= OBJ_INHERIT
|
||||||
}
|
}
|
||||||
|
if fileFlags&O_FILE_FLAG_POSIX_SEMANTICS == 0 {
|
||||||
|
objAttrs.Attributes |= OBJ_CASE_INSENSITIVE
|
||||||
|
}
|
||||||
if err := objAttrs.init(dirfd, name); err != nil {
|
if err := objAttrs.init(dirfd, name); err != nil {
|
||||||
return syscall.InvalidHandle, err
|
return syscall.InvalidHandle, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if flag&O_OPEN_REPARSE != 0 {
|
|
||||||
options |= FILE_OPEN_REPARSE_POINT
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
|
// We don't use FILE_OVERWRITE/FILE_OVERWRITE_IF, because when opening
|
||||||
// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
|
// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
|
||||||
// file with a new, read-only one.
|
// file with a new, read-only one.
|
||||||
|
|
@ -121,7 +150,7 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
|
||||||
fileAttrs,
|
fileAttrs,
|
||||||
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
||||||
disposition,
|
disposition,
|
||||||
FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT|options,
|
FILE_OPEN_FOR_BACKUP_INTENT|options,
|
||||||
nil,
|
nil,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@ const (
|
||||||
FILE_NO_COMPRESSION = 0x00008000
|
FILE_NO_COMPRESSION = 0x00008000
|
||||||
FILE_OPEN_REQUIRING_OPLOCK = 0x00010000
|
FILE_OPEN_REQUIRING_OPLOCK = 0x00010000
|
||||||
FILE_DISALLOW_EXCLUSIVE = 0x00020000
|
FILE_DISALLOW_EXCLUSIVE = 0x00020000
|
||||||
|
FILE_SESSION_AWARE = 0x00040000
|
||||||
FILE_RESERVE_OPFILTER = 0x00100000
|
FILE_RESERVE_OPFILTER = 0x00100000
|
||||||
FILE_OPEN_REPARSE_POINT = 0x00200000
|
FILE_OPEN_REPARSE_POINT = 0x00200000
|
||||||
FILE_OPEN_NO_RECALL = 0x00400000
|
FILE_OPEN_NO_RECALL = 0x00400000
|
||||||
|
|
@ -286,3 +287,17 @@ const VER_NT_WORKSTATION = 0x0000001
|
||||||
type MemoryBasicInformation = windows.MemoryBasicInformation
|
type MemoryBasicInformation = windows.MemoryBasicInformation
|
||||||
|
|
||||||
type Context = windows.Context
|
type Context = windows.Context
|
||||||
|
|
||||||
|
const FileFlagsMask = 0xFFF00000
|
||||||
|
|
||||||
|
const ValidFileFlagsMask = O_FILE_FLAG_OPEN_REPARSE_POINT |
|
||||||
|
O_FILE_FLAG_BACKUP_SEMANTICS |
|
||||||
|
O_FILE_FLAG_OVERLAPPED |
|
||||||
|
O_FILE_FLAG_OPEN_NO_RECALL |
|
||||||
|
O_FILE_FLAG_SESSION_AWARE |
|
||||||
|
O_FILE_FLAG_POSIX_SEMANTICS |
|
||||||
|
O_FILE_FLAG_DELETE_ON_CLOSE |
|
||||||
|
O_FILE_FLAG_SEQUENTIAL_SCAN |
|
||||||
|
O_FILE_FLAG_NO_BUFFERING |
|
||||||
|
O_FILE_FLAG_RANDOM_ACCESS |
|
||||||
|
O_FILE_FLAG_WRITE_THROUGH
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,8 @@ func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &PathError{Op: "openat", Path: name, Err: err}
|
return nil, &PathError{Op: "openat", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
// openat always returns a non-blocking handle.
|
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
|
||||||
return newFile(fd, joinPath(root.Name(), name), kindOpenFile, false), nil
|
return newFile(fd, joinPath(root.Name(), name), kindOpenFile, nonblocking), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) {
|
func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) {
|
||||||
|
|
@ -213,7 +213,7 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
|
||||||
lstat = false
|
lstat = false
|
||||||
}
|
}
|
||||||
fi, err := doInRoot(r, name, nil, func(parent syscall.Handle, n string) (FileInfo, error) {
|
fi, err := doInRoot(r, name, nil, func(parent syscall.Handle, n string) (FileInfo, error) {
|
||||||
fd, err := openat(parent, n, windows.O_OPEN_REPARSE, 0)
|
fd, err := openat(parent, n, windows.O_FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +290,7 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error {
|
||||||
// This may or may not be the desired behavior: https://go.dev/issue/71492
|
// This may or may not be the desired behavior: https://go.dev/issue/71492
|
||||||
//
|
//
|
||||||
// For now, be consistent with os.Symlink.
|
// For now, be consistent with os.Symlink.
|
||||||
// Passing O_OPEN_REPARSE causes us to open the named file itself,
|
// Passing O_FILE_FLAG_OPEN_REPARSE_POINT causes us to open the named file itself,
|
||||||
// not any file that it links to.
|
// not any file that it links to.
|
||||||
//
|
//
|
||||||
// If we want to change this in the future, pass O_NOFOLLOW_ANY instead
|
// If we want to change this in the future, pass O_NOFOLLOW_ANY instead
|
||||||
|
|
@ -301,7 +301,7 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error {
|
||||||
// return errSymlink(link)
|
// return errSymlink(link)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
h, err := windows.Openat(parent, name, syscall.O_CLOEXEC|windows.O_OPEN_REPARSE|windows.O_WRITE_ATTRS, 0)
|
h, err := windows.Openat(parent, name, syscall.O_CLOEXEC|windows.O_FILE_FLAG_OPEN_REPARSE_POINT|windows.O_WRITE_ATTRS, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -382,7 +382,7 @@ func linkat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newname
|
||||||
}
|
}
|
||||||
|
|
||||||
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
||||||
fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0)
|
fd, err := openat(dirfd, name, windows.O_FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +391,7 @@ func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func modeAt(parent syscall.Handle, name string) (FileMode, error) {
|
func modeAt(parent syscall.Handle, name string) (FileMode, error) {
|
||||||
fd, err := openat(parent, name, windows.O_OPEN_REPARSE|windows.O_DIRECTORY, 0)
|
fd, err := openat(parent, name, windows.O_FILE_FLAG_OPEN_REPARSE_POINT|windows.O_DIRECTORY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ package os_test
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"internal/strconv"
|
||||||
"internal/syscall/windows"
|
"internal/syscall/windows"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -247,3 +248,89 @@ func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootOpenFileFlags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
root, err := os.OpenRoot(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
// The only way to retrieve some of the flags passed in CreateFile
|
||||||
|
// is using NtQueryInformationFile, which returns the file flags
|
||||||
|
// NT equivalent. Note that FILE_SYNCHRONOUS_IO_NONALERT is always
|
||||||
|
// set when FILE_FLAG_OVERLAPPED is not passed.
|
||||||
|
// The flags that can't be retrieved using NtQueryInformationFile won't
|
||||||
|
// be tested in here, but we at least know that the logic to handle them is correct.
|
||||||
|
tests := []struct {
|
||||||
|
flag uint32
|
||||||
|
wantMode uint32
|
||||||
|
}{
|
||||||
|
{0, windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
||||||
|
{windows.O_FILE_FLAG_OVERLAPPED, 0},
|
||||||
|
{windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
||||||
|
{windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING},
|
||||||
|
{windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
||||||
|
{windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
f, err := root.OpenFile(strconv.Itoa(i)+".txt", syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
var info windows.FILE_MODE_INFORMATION
|
||||||
|
if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{},
|
||||||
|
unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if info.Mode != tt.wantMode {
|
||||||
|
t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootOpenFileDeleteOnClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir := t.TempDir()
|
||||||
|
root, err := os.OpenRoot(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
const name = "test.txt"
|
||||||
|
f, err := root.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// The file should be deleted after closing.
|
||||||
|
if _, err := os.Stat(filepath.Join(dir, name)); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Errorf("expected file to be deleted, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRootOpenFileFlagInvalid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
dir := t.TempDir()
|
||||||
|
root, err := os.OpenRoot(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
// invalidFileFlag is the only value in the file flag range that is not supported,
|
||||||
|
// as it is not defined in the Windows API.
|
||||||
|
const invalidFileFlag = 0x00400000
|
||||||
|
f, err := root.OpenFile("test.txt", syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666)
|
||||||
|
if !errors.Is(err, os.ErrInvalid) {
|
||||||
|
t.Fatalf("expected os.ErrInvalid, got %v", err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,6 @@ const (
|
||||||
o_DIRECTORY = 0x04000
|
o_DIRECTORY = 0x04000
|
||||||
O_CLOEXEC = 0x80000
|
O_CLOEXEC = 0x80000
|
||||||
o_NOFOLLOW_ANY = 0x200000000 // used by internal/syscall/windows
|
o_NOFOLLOW_ANY = 0x200000000 // used by internal/syscall/windows
|
||||||
o_OPEN_REPARSE = 0x400000000 // used by internal/syscall/windows
|
|
||||||
o_WRITE_ATTRS = 0x800000000 // used by internal/syscall/windows
|
o_WRITE_ATTRS = 0x800000000 // used by internal/syscall/windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue