aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2024-10-10 09:57:50 -0700
committerDamien Neil <dneil@google.com>2024-10-11 21:56:46 +0000
commit86a1a994ff522a7236e6744e40dfbc33d0d6bd88 (patch)
tree1a28ab70f10c220abd44df46b8ae9767ae5d1ac6 /src
parent1c6288f7e1005a1217859c20e4892a9f2cfd8091 (diff)
downloadgo-86a1a994ff522a7236e6744e40dfbc33d0d6bd88.tar.xz
internal/syscall/windows: add Openat, Mkdirat
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>
Diffstat (limited to 'src')
-rw-r--r--src/internal/syscall/windows/at_windows.go168
-rw-r--r--src/internal/syscall/windows/syscall_windows.go10
-rw-r--r--src/internal/syscall/windows/types_windows.go22
-rw-r--r--src/syscall/types_windows.go26
4 files changed, 213 insertions, 13 deletions
diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go
new file mode 100644
index 0000000000..17a8c592f9
--- /dev/null
+++ b/src/internal/syscall/windows/at_windows.go
@@ -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
+}
diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go
index 2849376dd1..924f4951e7 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -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
diff --git a/src/internal/syscall/windows/types_windows.go b/src/internal/syscall/windows/types_windows.go
index 0421c3a35a..514feafae4 100644
--- a/src/internal/syscall/windows/types_windows.go
+++ b/src/internal/syscall/windows/types_windows.go
@@ -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
diff --git a/src/syscall/types_windows.go b/src/syscall/types_windows.go
index 6743675b95..eb1ba06ce6 100644
--- a/src/syscall/types_windows.go
+++ b/src/syscall/types_windows.go
@@ -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 (