aboutsummaryrefslogtreecommitdiff
path: root/src/internal/syscall/windows
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-03-25 10:31:00 -0700
committerGopher Robot <gobot@golang.org>2025-03-28 11:02:40 -0700
commit26fdb07d4ce58885305283ba18960f582f4eaa73 (patch)
tree6291830ac80f793d1979ce338377f4556c86247a /src/internal/syscall/windows
parent656b5b3abe25d026725edff49edbdaa9862c9d77 (diff)
downloadgo-26fdb07d4ce58885305283ba18960f582f4eaa73.tar.xz
os: add Root.Symlink
For #67002 Change-Id: Ia1637b61eae49e97e1d07f058ad2390e74cd3403 Reviewed-on: https://go-review.googlesource.com/c/go/+/660635 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Quim Muntal <quimmuntal@gmail.com> Auto-Submit: Damien Neil <dneil@google.com>
Diffstat (limited to 'src/internal/syscall/windows')
-rw-r--r--src/internal/syscall/windows/at_windows.go175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/internal/syscall/windows/at_windows.go b/src/internal/syscall/windows/at_windows.go
index 4b939d46ab..f04de276b9 100644
--- a/src/internal/syscall/windows/at_windows.go
+++ b/src/internal/syscall/windows/at_windows.go
@@ -5,6 +5,8 @@
package windows
import (
+ "runtime"
+ "structs"
"syscall"
"unsafe"
)
@@ -376,3 +378,176 @@ func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, ne
}
return err
}
+
+// SymlinkatFlags configure Symlinkat.
+//
+// Symbolic links have two properties: They may be directory or file links,
+// and they may be absolute or relative.
+//
+// The Windows API defines flags describing these properties
+// (SYMBOLIC_LINK_FLAG_DIRECTORY and SYMLINK_FLAG_RELATIVE),
+// but the flags are passed to different system calls and
+// do not have distinct values, so we define our own enumeration
+// that permits expressing both.
+type SymlinkatFlags uint
+
+const (
+ SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota)
+ SYMLINKAT_RELATIVE
+)
+
+func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
+ // Temporarily acquire symlink-creating privileges if possible.
+ // This is the behavior of CreateSymbolicLinkW.
+ //
+ // (When passed the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag,
+ // CreateSymbolicLinkW ignores errors in acquiring privileges, as we do here.)
+ return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error {
+ return symlinkat(oldname, newdirfd, newname, flags)
+ })
+}
+
+func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
+ oldnameu16, err := syscall.UTF16FromString(oldname)
+ if err != nil {
+ return err
+ }
+ oldnameu16 = oldnameu16[:len(oldnameu16)-1] // trim off terminal NUL
+
+ var options uint32
+ if flags&SYMLINKAT_DIRECTORY != 0 {
+ options |= FILE_DIRECTORY_FILE
+ } else {
+ options |= FILE_NON_DIRECTORY_FILE
+ }
+
+ objAttrs := &OBJECT_ATTRIBUTES{}
+ if err := objAttrs.init(newdirfd, newname); err != nil {
+ return err
+ }
+ var h syscall.Handle
+ err = NtCreateFile(
+ &h,
+ SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE,
+ objAttrs,
+ &IO_STATUS_BLOCK{},
+ nil,
+ syscall.FILE_ATTRIBUTE_NORMAL,
+ 0,
+ FILE_CREATE,
+ FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
+ 0,
+ 0,
+ )
+ if err != nil {
+ return ntCreateFileError(err, 0)
+ }
+ defer syscall.CloseHandle(h)
+
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
+ type reparseDataBufferT struct {
+ _ structs.HostLayout
+
+ ReparseTag uint32
+ ReparseDataLength uint16
+ Reserved uint16
+
+ SubstituteNameOffset uint16
+ SubstituteNameLength uint16
+ PrintNameOffset uint16
+ PrintNameLength uint16
+ Flags uint32
+ }
+
+ const (
+ headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset))
+ bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{}))
+ )
+
+ // Data buffer containing a SymbolicLinkReparseBuffer followed by the link target.
+ rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16)))
+
+ rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0]))
+ rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
+ rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize)
+ rdb.SubstituteNameOffset = 0
+ rdb.SubstituteNameLength = uint16(2 * len(oldnameu16))
+ rdb.PrintNameOffset = 0
+ rdb.PrintNameLength = rdb.SubstituteNameLength
+ if flags&SYMLINKAT_RELATIVE != 0 {
+ rdb.Flags = SYMLINK_FLAG_RELATIVE
+ }
+
+ namebuf := rdbbuf[bufferSize:]
+ copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16)))
+
+ err = syscall.DeviceIoControl(
+ h,
+ FSCTL_SET_REPARSE_POINT,
+ &rdbbuf[0],
+ uint32(len(rdbbuf)),
+ nil,
+ 0,
+ nil,
+ nil)
+ if err != nil {
+ // Creating the symlink has failed, so try to remove the file.
+ const FileDispositionInformation = 13
+ NtSetInformationFile(
+ h,
+ &IO_STATUS_BLOCK{},
+ uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
+ DeleteFile: true,
+ })),
+ uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
+ FileDispositionInformation,
+ )
+ return err
+ }
+
+ return nil
+}
+
+// withPrivilege temporariliy acquires the named privilege and runs f.
+// If the privilege cannot be acquired it runs f anyway,
+// which should fail with an appropriate error.
+func withPrivilege(privilege string, f func() error) error {
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := ImpersonateSelf(SecurityImpersonation)
+ if err != nil {
+ return f()
+ }
+ defer RevertToSelf()
+
+ curThread, err := GetCurrentThread()
+ if err != nil {
+ return f()
+ }
+ var token syscall.Token
+ err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token)
+ if err != nil {
+ return f()
+ }
+ defer syscall.CloseHandle(syscall.Handle(token))
+
+ privStr, err := syscall.UTF16PtrFromString(privilege)
+ if err != nil {
+ return f()
+ }
+ var tokenPriv TOKEN_PRIVILEGES
+ err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid)
+ if err != nil {
+ return f()
+ }
+
+ tokenPriv.PrivilegeCount = 1
+ tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
+ err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil)
+ if err != nil {
+ return f()
+ }
+
+ return f()
+}