From 86a1a994ff522a7236e6744e40dfbc33d0d6bd88 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Thu, 10 Oct 2024 09:57:50 -0700 Subject: [PATCH] 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 Reviewed-by: Quim Muntal LUCI-TryBot-Result: Go LUCI --- src/internal/syscall/windows/at_windows.go | 168 ++++++++++++++++++ .../syscall/windows/syscall_windows.go | 10 ++ src/internal/syscall/windows/types_windows.go | 22 ++- src/syscall/types_windows.go | 26 +-- 4 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 src/internal/syscall/windows/at_windows.go 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 ( -- 2.45.2