mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
os,syscall: pass file flags to CreateFile on Windows
Add support for FILE_FLAG_* constants in the flag argument of os.OpenFile and syscall.Open on Windows. Passing invalid flags will result in an error. Updates #73676 Change-Id: Ie215a3dd14f0d74141533f0a07865a02a67a3846 Reviewed-on: https://go-review.googlesource.com/c/go/+/699415 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
53515fb0a9
commit
252c901125
7 changed files with 131 additions and 31 deletions
4
doc/next/6-stdlib/99-minor/os/73676.md
Normal file
4
doc/next/6-stdlib/99-minor/os/73676.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
On Windows, the [OpenFile] `flag` parameter can now contain any combination of
|
||||||
|
Windows-specific file flags, such as `FILE_FLAG_OVERLAPPED` and
|
||||||
|
`FILE_FLAG_SEQUENTIAL_SCAN`, for control of file or device caching behavior,
|
||||||
|
access modes, and other special-purpose flags.
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsNonblock returns whether the file descriptor fd is opened
|
// IsNonblock returns whether the file descriptor fd is opened
|
||||||
// in non-blocking mode, that is, the [syscall.FILE_FLAG_OVERLAPPED] flag
|
// in non-blocking mode, that is, the [windows.O_FILE_FLAG_OVERLAPPED] flag
|
||||||
// was set when the file was opened.
|
// was set when the file was opened.
|
||||||
func IsNonblock(fd syscall.Handle) (nonblocking bool, err error) {
|
func IsNonblock(fd syscall.Handle) (nonblocking bool, err error) {
|
||||||
var info FILE_MODE_INFORMATION
|
var info FILE_MODE_INFORMATION
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,22 @@ type SECURITY_QUALITY_OF_SERVICE struct {
|
||||||
EffectiveOnly byte
|
EffectiveOnly byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File flags for [os.OpenFile]. The O_ prefix is used to indicate
|
||||||
|
// that these flags are specific to the OpenFile function.
|
||||||
|
const (
|
||||||
|
O_FILE_FLAG_OPEN_NO_RECALL = 0x00100000
|
||||||
|
O_FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||||
|
O_FILE_FLAG_SESSION_AWARE = 0x00800000
|
||||||
|
O_FILE_FLAG_POSIX_SEMANTICS = 0x01000000
|
||||||
|
O_FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
||||||
|
O_FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
|
||||||
|
O_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
|
||||||
|
O_FILE_FLAG_RANDOM_ACCESS = 0x10000000
|
||||||
|
O_FILE_FLAG_NO_BUFFERING = 0x20000000
|
||||||
|
O_FILE_FLAG_OVERLAPPED = 0x40000000
|
||||||
|
O_FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CreateDisposition flags for NtCreateFile and NtCreateNamedPipeFile.
|
// CreateDisposition flags for NtCreateFile and NtCreateNamedPipeFile.
|
||||||
FILE_SUPERSEDE = 0x00000000
|
FILE_SUPERSEDE = 0x00000000
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &PathError{Op: "open", Path: name, Err: err}
|
return nil, &PathError{Op: "open", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
// syscall.Open always returns a non-blocking handle.
|
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
|
||||||
return newFile(r, name, "file", false), nil
|
return newFile(r, name, "file", nonblocking), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openDirNolog(name string) (*File, error) {
|
func openDirNolog(name string) (*File, error) {
|
||||||
|
|
|
||||||
|
|
@ -1571,15 +1571,10 @@ func TestReadWriteFileOverlapped(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
name := filepath.Join(t.TempDir(), "test.txt")
|
name := filepath.Join(t.TempDir(), "test.txt")
|
||||||
wname, err := syscall.UTF16PtrFromString(name)
|
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|windows.O_FILE_FLAG_OVERLAPPED, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := syscall.CreateFile(wname, syscall.GENERIC_ALL, 0, nil, syscall.CREATE_NEW, syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED, 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
f := os.NewFile(uintptr(h), name)
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
data := []byte("test")
|
data := []byte("test")
|
||||||
|
|
@ -1655,22 +1650,14 @@ func TestStdinOverlappedPipe(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFileOverlapped(t testing.TB, name string, overlapped bool) *os.File {
|
func newFileOverlapped(t testing.TB, name string, overlapped bool) *os.File {
|
||||||
namep, err := syscall.UTF16PtrFromString(name)
|
flags := os.O_RDWR | os.O_CREATE
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
flags := syscall.FILE_ATTRIBUTE_NORMAL
|
|
||||||
if overlapped {
|
if overlapped {
|
||||||
flags |= syscall.FILE_FLAG_OVERLAPPED
|
flags |= windows.O_FILE_FLAG_OVERLAPPED
|
||||||
}
|
}
|
||||||
h, err := syscall.CreateFile(namep,
|
f, err := os.OpenFile(name, flags, 0666)
|
||||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
|
||||||
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
|
|
||||||
nil, syscall.OPEN_ALWAYS, uint32(flags), 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
f := os.NewFile(uintptr(h), name)
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -1706,7 +1693,7 @@ func newPipe(t testing.TB, name string, message, overlapped bool) *os.File {
|
||||||
// Create the read handle.
|
// Create the read handle.
|
||||||
flags := windows.PIPE_ACCESS_DUPLEX
|
flags := windows.PIPE_ACCESS_DUPLEX
|
||||||
if overlapped {
|
if overlapped {
|
||||||
flags |= syscall.FILE_FLAG_OVERLAPPED
|
flags |= windows.O_FILE_FLAG_OVERLAPPED
|
||||||
}
|
}
|
||||||
typ := windows.PIPE_TYPE_BYTE | windows.PIPE_READMODE_BYTE
|
typ := windows.PIPE_TYPE_BYTE | windows.PIPE_READMODE_BYTE
|
||||||
if message {
|
if message {
|
||||||
|
|
@ -1888,21 +1875,13 @@ func TestFileOverlappedReadAtVolume(t *testing.T) {
|
||||||
// See https://go.dev/issues/74951.
|
// See https://go.dev/issues/74951.
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
name := `\\.\` + filepath.VolumeName(t.TempDir())
|
name := `\\.\` + filepath.VolumeName(t.TempDir())
|
||||||
namep, err := syscall.UTF16PtrFromString(name)
|
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|windows.O_FILE_FLAG_OVERLAPPED, 0666)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
h, err := syscall.CreateFile(namep,
|
|
||||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
|
||||||
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
|
|
||||||
nil, syscall.OPEN_ALWAYS, syscall.FILE_FLAG_OVERLAPPED, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||||
t.Skip("skipping test: access denied")
|
t.Skip("skipping test: access denied")
|
||||||
}
|
}
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
f := os.NewFile(uintptr(h), name)
|
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
var buf [0]byte
|
var buf [0]byte
|
||||||
|
|
@ -2209,3 +2188,71 @@ func TestSplitPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenFileFlags(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
t.Parallel()
|
||||||
|
f, err := os.OpenFile(filepath.Join(t.TempDir(), "test.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 TestOpenFileDeleteOnClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
name := filepath.Join(t.TempDir(), "test.txt")
|
||||||
|
f, err := os.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(name); !errors.Is(err, os.ErrNotExist) {
|
||||||
|
t.Errorf("expected file to be deleted, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOpenFileFlagInvalid(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// 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 := os.OpenFile(filepath.Join(t.TempDir(), "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()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,16 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
|
||||||
if perm&S_IWRITE == 0 {
|
if perm&S_IWRITE == 0 {
|
||||||
attrs = FILE_ATTRIBUTE_READONLY
|
attrs = FILE_ATTRIBUTE_READONLY
|
||||||
}
|
}
|
||||||
|
// fileFlags contains the high 12 bits of flag.
|
||||||
|
// This bit range can be used by the caller to specify the file flags
|
||||||
|
// passed to CreateFile. It is an error to use if the bits can't be
|
||||||
|
// mapped to the supported FILE_FLAG_* constants.
|
||||||
|
if fileFlags := uint32(flag) & fileFlagsMask; fileFlags&^validFileFlagsMask == 0 {
|
||||||
|
attrs |= fileFlags
|
||||||
|
} else {
|
||||||
|
return InvalidHandle, oserror.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
switch accessFlags {
|
switch accessFlags {
|
||||||
case O_WRONLY, O_RDWR:
|
case O_WRONLY, O_RDWR:
|
||||||
// Unix doesn't allow opening a directory with O_WRONLY
|
// Unix doesn't allow opening a directory with O_WRONLY
|
||||||
|
|
@ -414,7 +424,6 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
|
||||||
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
attrs |= FILE_FLAG_BACKUP_SEMANTICS
|
||||||
}
|
}
|
||||||
if flag&O_SYNC != 0 {
|
if flag&O_SYNC != 0 {
|
||||||
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
|
||||||
attrs |= _FILE_FLAG_WRITE_THROUGH
|
attrs |= _FILE_FLAG_WRITE_THROUGH
|
||||||
}
|
}
|
||||||
// We don't use CREATE_ALWAYS, because when opening a file with
|
// We don't use CREATE_ALWAYS, because when opening a file with
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,20 @@ var signals = [...]string{
|
||||||
15: "terminated",
|
15: "terminated",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileFlagsMask = 0xFFF00000
|
||||||
|
|
||||||
|
const validFileFlagsMask = FILE_FLAG_OPEN_REPARSE_POINT |
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS |
|
||||||
|
FILE_FLAG_OVERLAPPED |
|
||||||
|
_FILE_FLAG_OPEN_NO_RECALL |
|
||||||
|
_FILE_FLAG_SESSION_AWARE |
|
||||||
|
_FILE_FLAG_POSIX_SEMANTICS |
|
||||||
|
_FILE_FLAG_DELETE_ON_CLOSE |
|
||||||
|
_FILE_FLAG_SEQUENTIAL_SCAN |
|
||||||
|
_FILE_FLAG_NO_BUFFERING |
|
||||||
|
_FILE_FLAG_RANDOM_ACCESS |
|
||||||
|
_FILE_FLAG_WRITE_THROUGH
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GENERIC_READ = 0x80000000
|
GENERIC_READ = 0x80000000
|
||||||
GENERIC_WRITE = 0x40000000
|
GENERIC_WRITE = 0x40000000
|
||||||
|
|
@ -119,9 +133,19 @@ const (
|
||||||
OPEN_ALWAYS = 4
|
OPEN_ALWAYS = 4
|
||||||
TRUNCATE_EXISTING = 5
|
TRUNCATE_EXISTING = 5
|
||||||
|
|
||||||
|
// The following flags are supported by [Open]
|
||||||
|
// and exported in [golang.org/x/sys/windows].
|
||||||
|
_FILE_FLAG_OPEN_NO_RECALL = 0x00100000
|
||||||
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
|
||||||
|
_FILE_FLAG_SESSION_AWARE = 0x00800000
|
||||||
|
_FILE_FLAG_POSIX_SEMANTICS = 0x01000000
|
||||||
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
|
||||||
|
_FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
|
||||||
|
_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
|
||||||
|
_FILE_FLAG_RANDOM_ACCESS = 0x10000000
|
||||||
|
_FILE_FLAG_NO_BUFFERING = 0x20000000
|
||||||
FILE_FLAG_OVERLAPPED = 0x40000000
|
FILE_FLAG_OVERLAPPED = 0x40000000
|
||||||
|
_FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||||
|
|
||||||
HANDLE_FLAG_INHERIT = 0x00000001
|
HANDLE_FLAG_INHERIT = 0x00000001
|
||||||
STARTF_USESTDHANDLES = 0x00000100
|
STARTF_USESTDHANDLES = 0x00000100
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue