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
|
||||
// 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.
|
||||
func IsNonblock(fd syscall.Handle) (nonblocking bool, err error) {
|
||||
var info FILE_MODE_INFORMATION
|
||||
|
|
|
|||
|
|
@ -164,6 +164,22 @@ type SECURITY_QUALITY_OF_SERVICE struct {
|
|||
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 (
|
||||
// CreateDisposition flags for NtCreateFile and NtCreateNamedPipeFile.
|
||||
FILE_SUPERSEDE = 0x00000000
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
|
|||
if err != nil {
|
||||
return nil, &PathError{Op: "open", Path: name, Err: err}
|
||||
}
|
||||
// syscall.Open always returns a non-blocking handle.
|
||||
return newFile(r, name, "file", false), nil
|
||||
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
|
||||
return newFile(r, name, "file", nonblocking), nil
|
||||
}
|
||||
|
||||
func openDirNolog(name string) (*File, error) {
|
||||
|
|
|
|||
|
|
@ -1571,15 +1571,10 @@ func TestReadWriteFileOverlapped(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
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 {
|
||||
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()
|
||||
|
||||
data := []byte("test")
|
||||
|
|
@ -1655,22 +1650,14 @@ func TestStdinOverlappedPipe(t *testing.T) {
|
|||
}
|
||||
|
||||
func newFileOverlapped(t testing.TB, name string, overlapped bool) *os.File {
|
||||
namep, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
flags := syscall.FILE_ATTRIBUTE_NORMAL
|
||||
flags := os.O_RDWR | os.O_CREATE
|
||||
if overlapped {
|
||||
flags |= syscall.FILE_FLAG_OVERLAPPED
|
||||
flags |= windows.O_FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
h, err := syscall.CreateFile(namep,
|
||||
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
|
||||
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
|
||||
nil, syscall.OPEN_ALWAYS, uint32(flags), 0)
|
||||
f, err := os.OpenFile(name, flags, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := os.NewFile(uintptr(h), name)
|
||||
t.Cleanup(func() {
|
||||
if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||
t.Fatal(err)
|
||||
|
|
@ -1706,7 +1693,7 @@ func newPipe(t testing.TB, name string, message, overlapped bool) *os.File {
|
|||
// Create the read handle.
|
||||
flags := windows.PIPE_ACCESS_DUPLEX
|
||||
if overlapped {
|
||||
flags |= syscall.FILE_FLAG_OVERLAPPED
|
||||
flags |= windows.O_FILE_FLAG_OVERLAPPED
|
||||
}
|
||||
typ := windows.PIPE_TYPE_BYTE | windows.PIPE_READMODE_BYTE
|
||||
if message {
|
||||
|
|
@ -1888,21 +1875,13 @@ func TestFileOverlappedReadAtVolume(t *testing.T) {
|
|||
// See https://go.dev/issues/74951.
|
||||
t.Parallel()
|
||||
name := `\\.\` + filepath.VolumeName(t.TempDir())
|
||||
namep, err := syscall.UTF16PtrFromString(name)
|
||||
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)
|
||||
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|windows.O_FILE_FLAG_OVERLAPPED, 0666)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
|
||||
t.Skip("skipping test: access denied")
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := os.NewFile(uintptr(h), name)
|
||||
defer f.Close()
|
||||
|
||||
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 {
|
||||
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 {
|
||||
case O_WRONLY, O_RDWR:
|
||||
// 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
|
||||
}
|
||||
if flag&O_SYNC != 0 {
|
||||
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
|
||||
attrs |= _FILE_FLAG_WRITE_THROUGH
|
||||
}
|
||||
// We don't use CREATE_ALWAYS, because when opening a file with
|
||||
|
|
|
|||
|
|
@ -89,6 +89,20 @@ var signals = [...]string{
|
|||
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 (
|
||||
GENERIC_READ = 0x80000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
|
|
@ -119,9 +133,19 @@ const (
|
|||
OPEN_ALWAYS = 4
|
||||
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_SESSION_AWARE = 0x00800000
|
||||
_FILE_FLAG_POSIX_SEMANTICS = 0x01000000
|
||||
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_WRITE_THROUGH = 0x80000000
|
||||
|
||||
HANDLE_FLAG_INHERIT = 0x00000001
|
||||
STARTF_USESTDHANDLES = 0x00000100
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue