]> Cypherpunks repositories - gostls13.git/commitdiff
internal/syscall/windows: add Openat, Mkdirat
authorDamien Neil <dneil@google.com>
Thu, 10 Oct 2024 16:57:50 +0000 (09:57 -0700)
committerDamien Neil <dneil@google.com>
Fri, 11 Oct 2024 21:56:46 +0000 (21:56 +0000)
Windows versions of openat and mkdirat,
implemented using NtCreateFile.

For #67002

Change-Id: If43b1c1069733e5c45f7d45a69699fec30187308
Reviewed-on: https://go-review.googlesource.com/c/go/+/619435
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/internal/syscall/windows/at_windows.go [new file with mode: 0644]
src/internal/syscall/windows/syscall_windows.go
src/internal/syscall/windows/types_windows.go
src/syscall/types_windows.go

diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go
new file mode 100644 (file)
index 0000000..17a8c59
--- /dev/null
@@ -0,0 +1,168 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package windows
+
+import (
+       "syscall"
+)
+
+// Openat flags not supported by syscall.Open.
+//
+// These are invented values.
+//
+// When adding a new flag here, add an unexported version to
+// the set of invented O_ values in syscall/types_windows.go
+// to avoid overlap.
+const (
+       O_DIRECTORY    = 0x100000   // target must be a directory
+       O_NOFOLLOW_ANY = 0x20000000 // disallow symlinks anywhere in the path
+)
+
+func Openat(dirfd syscall.Handle, name string, flag int, perm uint32) (_ syscall.Handle, e1 error) {
+       if len(name) == 0 {
+               return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
+       }
+
+       var access, options uint32
+       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
+       case syscall.O_WRONLY:
+               access = FILE_GENERIC_WRITE
+               options |= FILE_NON_DIRECTORY_FILE
+       case syscall.O_RDWR:
+               access = FILE_GENERIC_READ | FILE_GENERIC_WRITE
+               options |= FILE_NON_DIRECTORY_FILE
+       }
+       if flag&syscall.O_CREAT != 0 {
+               access |= FILE_GENERIC_WRITE
+       }
+       if flag&syscall.O_APPEND != 0 {
+               access &^= FILE_WRITE_DATA
+               access |= FILE_APPEND_DATA
+       }
+       if flag&O_DIRECTORY != 0 {
+               options |= FILE_DIRECTORY_FILE
+               access |= FILE_LIST_DIRECTORY
+       }
+       if flag&syscall.O_SYNC != 0 {
+               options |= FILE_WRITE_THROUGH
+       }
+       // Allow File.Stat.
+       access |= STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | FILE_READ_EA
+
+       objAttrs := &OBJECT_ATTRIBUTES{}
+       if flag&O_NOFOLLOW_ANY != 0 {
+               objAttrs.Attributes |= OBJ_DONT_REPARSE
+       }
+       if flag&syscall.O_CLOEXEC == 0 {
+               objAttrs.Attributes |= OBJ_INHERIT
+       }
+       if err := objAttrs.init(dirfd, name); err != nil {
+               return syscall.InvalidHandle, err
+       }
+
+       // 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.
+       //
+       // Instead, we ftruncate the file after opening when O_TRUNC is set.
+       var disposition uint32
+       switch {
+       case flag&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
+               disposition = FILE_CREATE
+       case flag&syscall.O_CREAT == syscall.O_CREAT:
+               disposition = FILE_OPEN_IF
+       default:
+               disposition = FILE_OPEN
+       }
+
+       fileAttrs := uint32(FILE_ATTRIBUTE_NORMAL)
+       if perm&syscall.S_IWRITE == 0 {
+               fileAttrs = FILE_ATTRIBUTE_READONLY
+       }
+
+       var h syscall.Handle
+       err := NtCreateFile(
+               &h,
+               SYNCHRONIZE|access,
+               objAttrs,
+               &IO_STATUS_BLOCK{},
+               nil,
+               fileAttrs,
+               FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+               disposition,
+               FILE_SYNCHRONOUS_IO_NONALERT|options,
+               0,
+               0,
+       )
+       if err != nil {
+               return h, ntCreateFileError(err, flag)
+       }
+
+       if flag&syscall.O_TRUNC != 0 {
+               err = syscall.Ftruncate(h, 0)
+               if err != nil {
+                       syscall.CloseHandle(h)
+                       return syscall.InvalidHandle, err
+               }
+       }
+
+       return h, nil
+}
+
+// ntCreateFileError maps error returns from NTCreateFile to user-visible errors.
+func ntCreateFileError(err error, flag int) error {
+       s, ok := err.(NTStatus)
+       if !ok {
+               // Shouldn't really be possible, NtCreateFile always returns NTStatus.
+               return err
+       }
+       switch s {
+       case STATUS_REPARSE_POINT_ENCOUNTERED:
+               return syscall.ELOOP
+       case STATUS_NOT_A_DIRECTORY:
+               // ENOTDIR is the errno returned by open when O_DIRECTORY is specified
+               // and the target is not a directory.
+               //
+               // NtCreateFile can return STATUS_NOT_A_DIRECTORY under other circumstances,
+               // such as when opening "file/" where "file" is not a directory.
+               // (This might be Windows version dependent.)
+               //
+               // Only map STATUS_NOT_A_DIRECTORY to ENOTDIR when O_DIRECTORY is specified.
+               if flag&O_DIRECTORY != 0 {
+                       return syscall.ENOTDIR
+               }
+       case STATUS_FILE_IS_A_DIRECTORY:
+               return syscall.EISDIR
+       }
+       return s.Errno()
+}
+
+func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
+       objAttrs := &OBJECT_ATTRIBUTES{}
+       if err := objAttrs.init(dirfd, name); err != nil {
+               return err
+       }
+       var h syscall.Handle
+       err := NtCreateFile(
+               &h,
+               FILE_GENERIC_READ,
+               objAttrs,
+               &IO_STATUS_BLOCK{},
+               nil,
+               syscall.FILE_ATTRIBUTE_NORMAL,
+               syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
+               FILE_CREATE,
+               FILE_DIRECTORY_FILE,
+               0,
+               0,
+       )
+       if err != nil {
+               return ntCreateFileError(err, 0)
+       }
+       return nil
+}
index 2849376dd1947c71291238214b1965816d0908c6..924f4951e71e59d213fa4384d8267ddea20d7d7f 100644 (file)
@@ -517,6 +517,16 @@ func (s NTStatus) Error() string {
        return s.Errno().Error()
 }
 
+// x/sys/windows/mkerrors.bash can generate a complete list of NTStatus codes.
+//
+// At the moment, we only need a couple, so just put them here manually.
+// If this list starts getting long, we should consider generating the full set.
+const (
+       STATUS_FILE_IS_A_DIRECTORY       NTStatus = 0xC00000BA
+       STATUS_NOT_A_DIRECTORY           NTStatus = 0xC0000103
+       STATUS_REPARSE_POINT_ENCOUNTERED NTStatus = 0xC000050B
+)
+
 // NT Native APIs
 //sys   NtCreateFile(handle *syscall.Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, allocationSize *int64, attributes uint32, share uint32, disposition uint32, options uint32, eabuffer uintptr, ealength uint32) (ntstatus error) = ntdll.NtCreateFile
 //sys   rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) (ret syscall.Errno) = ntdll.RtlNtStatusToDosErrorNoTeb
index 0421c3a35a1700bd1294023dee57d847abbc0930..514feafae44dcf82dfbac4aebb03ec5abd7352f3 100644 (file)
@@ -4,7 +4,10 @@
 
 package windows
 
-import "syscall"
+import (
+       "syscall"
+       "unsafe"
+)
 
 // Socket related.
 const (
@@ -105,6 +108,23 @@ type OBJECT_ATTRIBUTES struct {
        SecurityQoS        *SECURITY_QUALITY_OF_SERVICE
 }
 
+// init sets o's RootDirectory, ObjectName, and Length.
+func (o *OBJECT_ATTRIBUTES) init(root syscall.Handle, name string) error {
+       if name == "." {
+               name = ""
+       }
+       objectName, err := NewNTUnicodeString(name)
+       if err != nil {
+               return err
+       }
+       o.ObjectName = objectName
+       if root != syscall.InvalidHandle {
+               o.RootDirectory = root
+       }
+       o.Length = uint32(unsafe.Sizeof(*o))
+       return nil
+}
+
 // Values for the Attributes member of OBJECT_ATTRIBUTES.
 const (
        OBJ_INHERIT                       = 0x00000002
index 6743675b95909bdcc4ff033909d2663202ee15f3..eb1ba06ce667035d05a3300dc47c5853be46fed0 100644 (file)
@@ -34,18 +34,20 @@ const (
 
 const (
        // Invented values to support what package os expects.
-       O_RDONLY   = 0x00000
-       O_WRONLY   = 0x00001
-       O_RDWR     = 0x00002
-       O_CREAT    = 0x00040
-       O_EXCL     = 0x00080
-       O_NOCTTY   = 0x00100
-       O_TRUNC    = 0x00200
-       O_NONBLOCK = 0x00800
-       O_APPEND   = 0x00400
-       O_SYNC     = 0x01000
-       O_ASYNC    = 0x02000
-       O_CLOEXEC  = 0x80000
+       O_RDONLY       = 0x00000
+       O_WRONLY       = 0x00001
+       O_RDWR         = 0x00002
+       O_CREAT        = 0x00040
+       O_EXCL         = 0x00080
+       O_NOCTTY       = 0x00100
+       O_TRUNC        = 0x00200
+       O_NONBLOCK     = 0x00800
+       O_APPEND       = 0x00400
+       O_SYNC         = 0x01000
+       O_ASYNC        = 0x02000
+       O_CLOEXEC      = 0x80000
+       o_DIRECTORY    = 0x100000   // used by internal/syscall/windows
+       o_NOFOLLOW_ANY = 0x20000000 // used by internal/syscall/windows
 )
 
 const (