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>
package windows
import (
+ "internal/oserror"
"runtime"
"structs"
"syscall"
// to avoid overlap.
const (
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
)
}
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) {
case syscall.O_RDONLY:
// FILE_GENERIC_READ includes FILE_LIST_DIRECTORY.
- access = FILE_GENERIC_READ
+ access |= FILE_GENERIC_READ
case syscall.O_WRONLY:
- access = FILE_GENERIC_WRITE
+ access |= FILE_GENERIC_WRITE
options |= FILE_NON_DIRECTORY_FILE
case syscall.O_RDWR:
- access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
+ access |= FILE_GENERIC_READ | FILE_GENERIC_WRITE
options |= FILE_NON_DIRECTORY_FILE
default:
// Stat opens files without requesting read or write permissions,
// but we still need to request SYNCHRONIZE.
- access = SYNCHRONIZE
+ access |= SYNCHRONIZE
}
if flag&syscall.O_CREAT != 0 {
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 {
access |= FILE_APPEND_DATA
// Remove FILE_WRITE_DATA access unless O_TRUNC is set,
if flag&syscall.O_CLOEXEC == 0 {
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 {
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
// a file with FILE_ATTRIBUTE_READONLY these will replace an existing
// file with a new, read-only one.
fileAttrs,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
disposition,
- FILE_SYNCHRONOUS_IO_NONALERT|FILE_OPEN_FOR_BACKUP_INTENT|options,
+ FILE_OPEN_FOR_BACKUP_INTENT|options,
nil,
0,
)
FILE_NO_COMPRESSION = 0x00008000
FILE_OPEN_REQUIRING_OPLOCK = 0x00010000
FILE_DISALLOW_EXCLUSIVE = 0x00020000
+ FILE_SESSION_AWARE = 0x00040000
FILE_RESERVE_OPFILTER = 0x00100000
FILE_OPEN_REPARSE_POINT = 0x00200000
FILE_OPEN_NO_RECALL = 0x00400000
type MemoryBasicInformation = windows.MemoryBasicInformation
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
if err != nil {
return nil, &PathError{Op: "openat", Path: name, Err: err}
}
- // openat always returns a non-blocking handle.
- return newFile(fd, joinPath(root.Name(), name), kindOpenFile, false), nil
+ nonblocking := flag&windows.O_FILE_FLAG_OVERLAPPED != 0
+ return newFile(fd, joinPath(root.Name(), name), kindOpenFile, nonblocking), nil
}
func openat(dirfd syscall.Handle, name string, flag uint64, perm FileMode) (syscall.Handle, error) {
lstat = false
}
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 {
return nil, err
}
// This may or may not be the desired behavior: https://go.dev/issue/71492
//
// 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.
//
// If we want to change this in the future, pass O_NOFOLLOW_ANY instead
// 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 {
return err
}
}
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 {
return "", err
}
}
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 {
return 0, err
}
import (
"errors"
"fmt"
+ "internal/strconv"
"internal/syscall/windows"
"os"
"path/filepath"
}
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()
+}
o_DIRECTORY = 0x04000
O_CLOEXEC = 0x80000
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
)