Skip to content

Commit 252c901

Browse files
committed
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 <[email protected]> Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 53515fb commit 252c901

File tree

7 files changed

+131
-31
lines changed

7 files changed

+131
-31
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
On Windows, the [OpenFile] `flag` parameter can now contain any combination of
2+
Windows-specific file flags, such as `FILE_FLAG_OVERLAPPED` and
3+
`FILE_FLAG_SEQUENTIAL_SCAN`, for control of file or device caching behavior,
4+
access modes, and other special-purpose flags.

src/internal/syscall/windows/nonblocking_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// IsNonblock returns whether the file descriptor fd is opened
13-
// in non-blocking mode, that is, the [syscall.FILE_FLAG_OVERLAPPED] flag
13+
// in non-blocking mode, that is, the [windows.O_FILE_FLAG_OVERLAPPED] flag
1414
// was set when the file was opened.
1515
func IsNonblock(fd syscall.Handle) (nonblocking bool, err error) {
1616
var info FILE_MODE_INFORMATION

src/internal/syscall/windows/types_windows.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,22 @@ type SECURITY_QUALITY_OF_SERVICE struct {
164164
EffectiveOnly byte
165165
}
166166

167+
// File flags for [os.OpenFile]. The O_ prefix is used to indicate
168+
// that these flags are specific to the OpenFile function.
169+
const (
170+
O_FILE_FLAG_OPEN_NO_RECALL = 0x00100000
171+
O_FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
172+
O_FILE_FLAG_SESSION_AWARE = 0x00800000
173+
O_FILE_FLAG_POSIX_SEMANTICS = 0x01000000
174+
O_FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
175+
O_FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
176+
O_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
177+
O_FILE_FLAG_RANDOM_ACCESS = 0x10000000
178+
O_FILE_FLAG_NO_BUFFERING = 0x20000000
179+
O_FILE_FLAG_OVERLAPPED = 0x40000000
180+
O_FILE_FLAG_WRITE_THROUGH = 0x80000000
181+
)
182+
167183
const (
168184
// CreateDisposition flags for NtCreateFile and NtCreateNamedPipeFile.
169185
FILE_SUPERSEDE = 0x00000000

src/os/file_windows.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
120120
if err != nil {
121121
return nil, &PathError{Op: "open", Path: name, Err: err}
122122
}
123-
// syscall.Open always returns a non-blocking handle.
124-
return newFile(r, name, "file", false), nil
123+
nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
124+
return newFile(r, name, "file", nonblocking), nil
125125
}
126126

127127
func openDirNolog(name string) (*File, error) {

src/os/os_windows_test.go

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,15 +1571,10 @@ func TestReadWriteFileOverlapped(t *testing.T) {
15711571
t.Parallel()
15721572

15731573
name := filepath.Join(t.TempDir(), "test.txt")
1574-
wname, err := syscall.UTF16PtrFromString(name)
1575-
if err != nil {
1576-
t.Fatal(err)
1577-
}
1578-
h, err := syscall.CreateFile(wname, syscall.GENERIC_ALL, 0, nil, syscall.CREATE_NEW, syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED, 0)
1574+
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|windows.O_FILE_FLAG_OVERLAPPED, 0666)
15791575
if err != nil {
15801576
t.Fatal(err)
15811577
}
1582-
f := os.NewFile(uintptr(h), name)
15831578
defer f.Close()
15841579

15851580
data := []byte("test")
@@ -1655,22 +1650,14 @@ func TestStdinOverlappedPipe(t *testing.T) {
16551650
}
16561651

16571652
func newFileOverlapped(t testing.TB, name string, overlapped bool) *os.File {
1658-
namep, err := syscall.UTF16PtrFromString(name)
1659-
if err != nil {
1660-
t.Fatal(err)
1661-
}
1662-
flags := syscall.FILE_ATTRIBUTE_NORMAL
1653+
flags := os.O_RDWR | os.O_CREATE
16631654
if overlapped {
1664-
flags |= syscall.FILE_FLAG_OVERLAPPED
1655+
flags |= windows.O_FILE_FLAG_OVERLAPPED
16651656
}
1666-
h, err := syscall.CreateFile(namep,
1667-
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
1668-
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
1669-
nil, syscall.OPEN_ALWAYS, uint32(flags), 0)
1657+
f, err := os.OpenFile(name, flags, 0666)
16701658
if err != nil {
16711659
t.Fatal(err)
16721660
}
1673-
f := os.NewFile(uintptr(h), name)
16741661
t.Cleanup(func() {
16751662
if err := f.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
16761663
t.Fatal(err)
@@ -1706,7 +1693,7 @@ func newPipe(t testing.TB, name string, message, overlapped bool) *os.File {
17061693
// Create the read handle.
17071694
flags := windows.PIPE_ACCESS_DUPLEX
17081695
if overlapped {
1709-
flags |= syscall.FILE_FLAG_OVERLAPPED
1696+
flags |= windows.O_FILE_FLAG_OVERLAPPED
17101697
}
17111698
typ := windows.PIPE_TYPE_BYTE | windows.PIPE_READMODE_BYTE
17121699
if message {
@@ -1888,21 +1875,13 @@ func TestFileOverlappedReadAtVolume(t *testing.T) {
18881875
// See https://go.dev/issues/74951.
18891876
t.Parallel()
18901877
name := `\\.\` + filepath.VolumeName(t.TempDir())
1891-
namep, err := syscall.UTF16PtrFromString(name)
1892-
if err != nil {
1893-
t.Fatal(err)
1894-
}
1895-
h, err := syscall.CreateFile(namep,
1896-
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
1897-
syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_READ,
1898-
nil, syscall.OPEN_ALWAYS, syscall.FILE_FLAG_OVERLAPPED, 0)
1878+
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|windows.O_FILE_FLAG_OVERLAPPED, 0666)
18991879
if err != nil {
19001880
if errors.Is(err, syscall.ERROR_ACCESS_DENIED) {
19011881
t.Skip("skipping test: access denied")
19021882
}
19031883
t.Fatal(err)
19041884
}
1905-
f := os.NewFile(uintptr(h), name)
19061885
defer f.Close()
19071886

19081887
var buf [0]byte
@@ -2209,3 +2188,71 @@ func TestSplitPath(t *testing.T) {
22092188
}
22102189
}
22112190
}
2191+
2192+
func TestOpenFileFlags(t *testing.T) {
2193+
t.Parallel()
2194+
2195+
// The only way to retrieve some of the flags passed in CreateFile
2196+
// is using NtQueryInformationFile, which returns the file flags
2197+
// NT equivalent. Note that FILE_SYNCHRONOUS_IO_NONALERT is always
2198+
// set when FILE_FLAG_OVERLAPPED is not passed.
2199+
// The flags that can't be retrieved using NtQueryInformationFile won't
2200+
// be tested in here, but we at least know that the logic to handle them is correct.
2201+
tests := []struct {
2202+
flag uint32
2203+
wantMode uint32
2204+
}{
2205+
{0, windows.FILE_SYNCHRONOUS_IO_NONALERT},
2206+
{windows.O_FILE_FLAG_OVERLAPPED, 0},
2207+
{windows.O_FILE_FLAG_NO_BUFFERING, windows.FILE_NO_INTERMEDIATE_BUFFERING | windows.FILE_SYNCHRONOUS_IO_NONALERT},
2208+
{windows.O_FILE_FLAG_NO_BUFFERING | windows.O_FILE_FLAG_OVERLAPPED, windows.FILE_NO_INTERMEDIATE_BUFFERING},
2209+
{windows.O_FILE_FLAG_SEQUENTIAL_SCAN, windows.FILE_SEQUENTIAL_ONLY | windows.FILE_SYNCHRONOUS_IO_NONALERT},
2210+
{windows.O_FILE_FLAG_WRITE_THROUGH, windows.FILE_WRITE_THROUGH | windows.FILE_SYNCHRONOUS_IO_NONALERT},
2211+
}
2212+
for i, tt := range tests {
2213+
t.Run(strconv.Itoa(i), func(t *testing.T) {
2214+
t.Parallel()
2215+
f, err := os.OpenFile(filepath.Join(t.TempDir(), "test.txt"), syscall.O_RDWR|syscall.O_CREAT|int(tt.flag), 0666)
2216+
if err != nil {
2217+
t.Fatal(err)
2218+
}
2219+
defer f.Close()
2220+
var info windows.FILE_MODE_INFORMATION
2221+
if err := windows.NtQueryInformationFile(syscall.Handle(f.Fd()), &windows.IO_STATUS_BLOCK{},
2222+
unsafe.Pointer(&info), uint32(unsafe.Sizeof(info)), windows.FileModeInformation); err != nil {
2223+
t.Fatal(err)
2224+
}
2225+
if info.Mode != tt.wantMode {
2226+
t.Errorf("file mode = 0x%x; want 0x%x", info.Mode, tt.wantMode)
2227+
}
2228+
})
2229+
}
2230+
}
2231+
2232+
func TestOpenFileDeleteOnClose(t *testing.T) {
2233+
t.Parallel()
2234+
name := filepath.Join(t.TempDir(), "test.txt")
2235+
f, err := os.OpenFile(name, syscall.O_RDWR|syscall.O_CREAT|windows.O_FILE_FLAG_DELETE_ON_CLOSE, 0666)
2236+
if err != nil {
2237+
t.Fatal(err)
2238+
}
2239+
if err := f.Close(); err != nil {
2240+
t.Fatal(err)
2241+
}
2242+
// The file should be deleted after closing.
2243+
if _, err := os.Stat(name); !errors.Is(err, os.ErrNotExist) {
2244+
t.Errorf("expected file to be deleted, got %v", err)
2245+
}
2246+
}
2247+
2248+
func TestOpenFileFlagInvalid(t *testing.T) {
2249+
t.Parallel()
2250+
// invalidFileFlag is the only value in the file flag range that is not supported,
2251+
// as it is not defined in the Windows API.
2252+
const invalidFileFlag = 0x00400000
2253+
f, err := os.OpenFile(filepath.Join(t.TempDir(), "test.txt"), syscall.O_RDWR|syscall.O_CREAT|invalidFileFlag, 0666)
2254+
if !errors.Is(err, os.ErrInvalid) {
2255+
t.Fatalf("expected os.ErrInvalid, got %v", err)
2256+
}
2257+
f.Close()
2258+
}

src/syscall/syscall_windows.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,16 @@ func Open(name string, flag int, perm uint32) (fd Handle, err error) {
401401
if perm&S_IWRITE == 0 {
402402
attrs = FILE_ATTRIBUTE_READONLY
403403
}
404+
// fileFlags contains the high 12 bits of flag.
405+
// This bit range can be used by the caller to specify the file flags
406+
// passed to CreateFile. It is an error to use if the bits can't be
407+
// mapped to the supported FILE_FLAG_* constants.
408+
if fileFlags := uint32(flag) & fileFlagsMask; fileFlags&^validFileFlagsMask == 0 {
409+
attrs |= fileFlags
410+
} else {
411+
return InvalidHandle, oserror.ErrInvalid
412+
}
413+
404414
switch accessFlags {
405415
case O_WRONLY, O_RDWR:
406416
// 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) {
414424
attrs |= FILE_FLAG_BACKUP_SEMANTICS
415425
}
416426
if flag&O_SYNC != 0 {
417-
const _FILE_FLAG_WRITE_THROUGH = 0x80000000
418427
attrs |= _FILE_FLAG_WRITE_THROUGH
419428
}
420429
// We don't use CREATE_ALWAYS, because when opening a file with

src/syscall/types_windows.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ var signals = [...]string{
8989
15: "terminated",
9090
}
9191

92+
const fileFlagsMask = 0xFFF00000
93+
94+
const validFileFlagsMask = FILE_FLAG_OPEN_REPARSE_POINT |
95+
FILE_FLAG_BACKUP_SEMANTICS |
96+
FILE_FLAG_OVERLAPPED |
97+
_FILE_FLAG_OPEN_NO_RECALL |
98+
_FILE_FLAG_SESSION_AWARE |
99+
_FILE_FLAG_POSIX_SEMANTICS |
100+
_FILE_FLAG_DELETE_ON_CLOSE |
101+
_FILE_FLAG_SEQUENTIAL_SCAN |
102+
_FILE_FLAG_NO_BUFFERING |
103+
_FILE_FLAG_RANDOM_ACCESS |
104+
_FILE_FLAG_WRITE_THROUGH
105+
92106
const (
93107
GENERIC_READ = 0x80000000
94108
GENERIC_WRITE = 0x40000000
@@ -119,9 +133,19 @@ const (
119133
OPEN_ALWAYS = 4
120134
TRUNCATE_EXISTING = 5
121135

136+
// The following flags are supported by [Open]
137+
// and exported in [golang.org/x/sys/windows].
138+
_FILE_FLAG_OPEN_NO_RECALL = 0x00100000
122139
FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
140+
_FILE_FLAG_SESSION_AWARE = 0x00800000
141+
_FILE_FLAG_POSIX_SEMANTICS = 0x01000000
123142
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
143+
_FILE_FLAG_DELETE_ON_CLOSE = 0x04000000
144+
_FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000
145+
_FILE_FLAG_RANDOM_ACCESS = 0x10000000
146+
_FILE_FLAG_NO_BUFFERING = 0x20000000
124147
FILE_FLAG_OVERLAPPED = 0x40000000
148+
_FILE_FLAG_WRITE_THROUGH = 0x80000000
125149

126150
HANDLE_FLAG_INHERIT = 0x00000001
127151
STARTF_USESTDHANDLES = 0x00000100

0 commit comments

Comments
 (0)