]> Cypherpunks repositories - gostls13.git/commitdiff
os,internal/syscall/windows: support O_* flags in Root.OpenFile
authorqmuntal <quimmuntal@gmail.com>
Wed, 26 Nov 2025 11:11:50 +0000 (12:11 +0100)
committerGopher Robot <gobot@golang.org>
Wed, 26 Nov 2025 19:56:54 +0000 (11:56 -0800)
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>
src/internal/syscall/windows/at_windows.go
src/internal/syscall/windows/types_windows.go
src/os/root_windows.go
src/os/root_windows_test.go
src/syscall/types_windows.go

index b7ca8433c2a87a65010390a30df92980c7b56896..de733c523cd7a1c1822c06d841e88f2f1fe385a7 100644 (file)
@@ -5,6 +5,7 @@
 package windows
 
 import (
+       "internal/oserror"
        "runtime"
        "structs"
        "syscall"
@@ -26,7 +27,6 @@ const (
 // 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
 )
 
@@ -36,24 +36,54 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
        }
 
        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,
@@ -82,14 +112,13 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
        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.
@@ -121,7 +150,7 @@ func Openat(dirfd syscall.Handle, name string, flag uint64, perm uint32) (_ sysc
                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,
        )
index 49daf9b31b1ff701f27c79bd6fd85cf57f748f6c..568f94624c441611a0276b19a5da972c86b850e2 100644 (file)
@@ -210,6 +210,7 @@ const (
        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
@@ -286,3 +287,17 @@ const VER_NT_WORKSTATION = 0x0000001
 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
index 381e33fc0e527674f0c6a01c5a2747268233e009..da995e6d7f5c3b096405ae26fb90b3523a7b695b 100644 (file)
@@ -134,8 +134,8 @@ func rootOpenFileNolog(root *Root, name string, flag int, perm FileMode) (*File,
        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) {
@@ -213,7 +213,7 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, 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
                }
@@ -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
        //
        // 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
@@ -301,7 +301,7 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error {
        //                 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
        }
@@ -382,7 +382,7 @@ func linkat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newname
 }
 
 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
        }
@@ -391,7 +391,7 @@ func readlinkat(dirfd syscall.Handle, name string) (string, 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 {
                return 0, err
        }
index 47643f98d103bd31d9fdc52ef7d2deddcde6ae88..31d54f257163d818f8febfd0d4686a184b3f2b89 100644 (file)
@@ -9,6 +9,7 @@ package os_test
 import (
        "errors"
        "fmt"
+       "internal/strconv"
        "internal/syscall/windows"
        "os"
        "path/filepath"
@@ -247,3 +248,89 @@ func TestRootOpenFileTruncateNamedPipe(t *testing.T) {
        }
        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()
+}
index 3c6d18a8509e97d99ad121b24d3290e9de0d7090..1bffb2769d281cd701c017c2d97f1ebb6d0a5dd8 100644 (file)
@@ -54,7 +54,6 @@ const (
        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
 )