aboutsummaryrefslogtreecommitdiff
path: root/src/internal
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
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')
-rw-r--r--src/internal/syscall/unix/asm_darwin.s1
-rw-r--r--src/internal/syscall/unix/asm_openbsd.s2
-rw-r--r--src/internal/syscall/unix/at.go19
-rw-r--r--src/internal/syscall/unix/at_aix.go1
-rw-r--r--src/internal/syscall/unix/at_darwin.go26
-rw-r--r--src/internal/syscall/unix/at_libc.go24
-rw-r--r--src/internal/syscall/unix/at_openbsd.go26
-rw-r--r--src/internal/syscall/unix/at_solaris.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_dragonfly.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_freebsd.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_linux.go1
-rw-r--r--src/internal/syscall/unix/at_sysnum_netbsd.go1
-rw-r--r--src/internal/syscall/unix/at_wasip1.go33
-rw-r--r--src/internal/syscall/windows/at_windows.go175
14 files changed, 303 insertions, 9 deletions
diff --git a/src/internal/syscall/unix/asm_darwin.s b/src/internal/syscall/unix/asm_darwin.s
index 79d384c941..9803c7260f 100644
--- a/src/internal/syscall/unix/asm_darwin.s
+++ b/src/internal/syscall/unix/asm_darwin.s
@@ -29,3 +29,4 @@ TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchmodat(SB)
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0; JMP libc_renameat(SB)
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_linkat(SB)
+TEXT ·libc_symlinkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_symlinkat(SB)
diff --git a/src/internal/syscall/unix/asm_openbsd.s b/src/internal/syscall/unix/asm_openbsd.s
index 481dd7d700..d7c230555c 100644
--- a/src/internal/syscall/unix/asm_openbsd.s
+++ b/src/internal/syscall/unix/asm_openbsd.s
@@ -22,3 +22,5 @@ TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0
JMP libc_renameat(SB)
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0
JMP libc_linkat(SB)
+TEXT ·libc_symlinkat_trampoline(SB),NOSPLIT,$0-0
+ JMP libc_symlinkat(SB)
diff --git a/src/internal/syscall/unix/at.go b/src/internal/syscall/unix/at.go
index 4549a07f8c..96272afc7b 100644
--- a/src/internal/syscall/unix/at.go
+++ b/src/internal/syscall/unix/at.go
@@ -158,3 +158,22 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
}
return nil
}
+
+func Symlinkat(oldpath string, newdirfd int, newpath string) error {
+ oldp, err := syscall.BytePtrFromString(oldpath)
+ if err != nil {
+ return err
+ }
+ newp, err := syscall.BytePtrFromString(newpath)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall.Syscall(symlinkatTrap,
+ uintptr(unsafe.Pointer(oldp)),
+ uintptr(newdirfd),
+ uintptr(unsafe.Pointer(newp)))
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_aix.go b/src/internal/syscall/unix/at_aix.go
index 573554927e..8bf7b4dd81 100644
--- a/src/internal/syscall/unix/at_aix.go
+++ b/src/internal/syscall/unix/at_aix.go
@@ -10,6 +10,7 @@ package unix
//go:cgo_import_dynamic libc_linkat linkat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_openat openat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_renameat renameat "libc.a/shr_64.o"
+//go:cgo_import_dynamic libc_symlinkat symlinkat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.a/shr_64.o"
diff --git a/src/internal/syscall/unix/at_darwin.go b/src/internal/syscall/unix/at_darwin.go
index 61437672ee..c74c827626 100644
--- a/src/internal/syscall/unix/at_darwin.go
+++ b/src/internal/syscall/unix/at_darwin.go
@@ -154,3 +154,29 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
}
return nil
}
+
+func libc_symlinkat_trampoline()
+
+//go:cgo_import_dynamic libc_symlinkat symlinkat "/usr/lib/libSystem.B.dylib"
+
+func Symlinkat(oldpath string, newdirfd int, newpath string) error {
+ oldp, err := syscall.BytePtrFromString(oldpath)
+ if err != nil {
+ return err
+ }
+ newp, err := syscall.BytePtrFromString(newpath)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_symlinkat_trampoline),
+ uintptr(unsafe.Pointer(oldp)),
+ uintptr(newdirfd),
+ uintptr(unsafe.Pointer(newp)),
+ 0,
+ 0,
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_libc.go b/src/internal/syscall/unix/at_libc.go
index b32d3bba39..5c64b34d48 100644
--- a/src/internal/syscall/unix/at_libc.go
+++ b/src/internal/syscall/unix/at_libc.go
@@ -20,6 +20,7 @@ import (
//go:linkname procFchownat libc_fchownat
//go:linkname procRenameat libc_renameat
//go:linkname procLinkat libc_linkat
+//go:linkname procSymlinkat libc_symlinkat
var (
procFstatat,
@@ -30,7 +31,8 @@ var (
procFchmodat,
procFchownat,
procRenameat,
- procLinkat uintptr
+ procLinkat,
+ procSymlinkat uintptr
)
func Unlinkat(dirfd int, path string, flags int) error {
@@ -208,3 +210,23 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
}
return nil
}
+
+func Symlinkat(oldpath string, newdirfd int, newpath string) error {
+ oldp, err := syscall.BytePtrFromString(oldpath)
+ if err != nil {
+ return err
+ }
+ newp, err := syscall.BytePtrFromString(newpath)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall6(uintptr(unsafe.Pointer(&procSymlinkat)), 3,
+ uintptr(unsafe.Pointer(oldp)),
+ uintptr(newdirfd),
+ uintptr(unsafe.Pointer(newp)),
+ 0, 0, 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_openbsd.go b/src/internal/syscall/unix/at_openbsd.go
index 2a433930f3..0fd5e90e5c 100644
--- a/src/internal/syscall/unix/at_openbsd.go
+++ b/src/internal/syscall/unix/at_openbsd.go
@@ -145,3 +145,29 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
}
return nil
}
+
+func libc_symlinkat_trampoline()
+
+//go:cgo_import_dynamic libc_symlinkat symlinkat "libc.so"
+
+func Symlinkat(oldpath string, newdirfd int, newpath string) error {
+ oldp, err := syscall.BytePtrFromString(oldpath)
+ if err != nil {
+ return err
+ }
+ newp, err := syscall.BytePtrFromString(newpath)
+ if err != nil {
+ return err
+ }
+ _, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_symlinkat_trampoline),
+ uintptr(unsafe.Pointer(oldp)),
+ uintptr(newdirfd),
+ uintptr(unsafe.Pointer(newp)),
+ 0,
+ 0,
+ 0)
+ if errno != 0 {
+ return errno
+ }
+ return nil
+}
diff --git a/src/internal/syscall/unix/at_solaris.go b/src/internal/syscall/unix/at_solaris.go
index abfda15688..5d69ae5bee 100644
--- a/src/internal/syscall/unix/at_solaris.go
+++ b/src/internal/syscall/unix/at_solaris.go
@@ -19,6 +19,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
//go:cgo_import_dynamic libc_linkat linkat "libc.so"
//go:cgo_import_dynamic libc_openat openat "libc.so"
//go:cgo_import_dynamic libc_renameat renameat "libc.so"
+//go:cgo_import_dynamic libc_symlinkat symlinkat "libc.so"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.so"
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.so"
diff --git a/src/internal/syscall/unix/at_sysnum_dragonfly.go b/src/internal/syscall/unix/at_sysnum_dragonfly.go
index 3ba2c54152..9728b969c4 100644
--- a/src/internal/syscall/unix/at_sysnum_dragonfly.go
+++ b/src/internal/syscall/unix/at_sysnum_dragonfly.go
@@ -16,6 +16,7 @@ const (
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
renameatTrap uintptr = syscall.SYS_RENAMEAT
linkatTrap uintptr = syscall.SYS_LINKAT
+ symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
AT_EACCESS = 0x4
AT_FDCWD = 0xfffafdcd
diff --git a/src/internal/syscall/unix/at_sysnum_freebsd.go b/src/internal/syscall/unix/at_sysnum_freebsd.go
index 032b8b5276..c1fdcabf41 100644
--- a/src/internal/syscall/unix/at_sysnum_freebsd.go
+++ b/src/internal/syscall/unix/at_sysnum_freebsd.go
@@ -23,4 +23,5 @@ const (
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
renameatTrap uintptr = syscall.SYS_RENAMEAT
linkatTrap uintptr = syscall.SYS_LINKAT
+ symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
)
diff --git a/src/internal/syscall/unix/at_sysnum_linux.go b/src/internal/syscall/unix/at_sysnum_linux.go
index 6b8bebff2a..bb7f244fe2 100644
--- a/src/internal/syscall/unix/at_sysnum_linux.go
+++ b/src/internal/syscall/unix/at_sysnum_linux.go
@@ -14,6 +14,7 @@ const (
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
linkatTrap uintptr = syscall.SYS_LINKAT
+ symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
)
const (
diff --git a/src/internal/syscall/unix/at_sysnum_netbsd.go b/src/internal/syscall/unix/at_sysnum_netbsd.go
index 01e10ddd59..b59b5e0cf9 100644
--- a/src/internal/syscall/unix/at_sysnum_netbsd.go
+++ b/src/internal/syscall/unix/at_sysnum_netbsd.go
@@ -16,6 +16,7 @@ const (
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
renameatTrap uintptr = syscall.SYS_RENAMEAT
linkatTrap uintptr = syscall.SYS_LINKAT
+ symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
)
const (
diff --git a/src/internal/syscall/unix/at_wasip1.go b/src/internal/syscall/unix/at_wasip1.go
index 72537caf1e..dfbb365f2a 100644
--- a/src/internal/syscall/unix/at_wasip1.go
+++ b/src/internal/syscall/unix/at_wasip1.go
@@ -101,6 +101,10 @@ func Mkdirat(dirfd int, path string, mode uint32) error {
))
}
+//go:wasmimport wasi_snapshot_preview1 path_create_directory
+//go:noescape
+func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
+
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
// WASI preview 1 doesn't support changing file modes.
return syscall.ENOSYS
@@ -111,10 +115,6 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
return syscall.ENOSYS
}
-//go:wasmimport wasi_snapshot_preview1 path_rename
-//go:noescape
-func path_rename(oldFd int32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
-
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
if oldpath == "" || newpath == "" {
return syscall.EINVAL
@@ -129,9 +129,9 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
))
}
-//go:wasmimport wasi_snapshot_preview1 path_link
+//go:wasmimport wasi_snapshot_preview1 path_rename
//go:noescape
-func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
+func path_rename(oldFd int32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) error {
if oldpath == "" || newpath == "" {
@@ -148,9 +148,26 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
))
}
-//go:wasmimport wasi_snapshot_preview1 path_create_directory
+//go:wasmimport wasi_snapshot_preview1 path_link
//go:noescape
-func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
+func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
+
+func Symlinkat(oldpath string, newdirfd int, newpath string) error {
+ if oldpath == "" || newpath == "" {
+ return syscall.EINVAL
+ }
+ return errnoErr(path_symlink(
+ unsafe.StringData(oldpath),
+ size(len(oldpath)),
+ int32(newdirfd),
+ unsafe.StringData(newpath),
+ size(len(newpath)),
+ ))
+}
+
+//go:wasmimport wasi_snapshot_preview1 path_symlink
+//go:noescape
+func path_symlink(oldPath *byte, oldPathLen size, fd int32, newPath *byte, newPathLen size) syscall.Errno
func errnoErr(errno syscall.Errno) error {
if errno == 0 {
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()
+}