os: add Root.Chown

For #67002

Change-Id: I546537618cbe32217fa72264d49db2b1a1d3b6db
Reviewed-on: https://go-review.googlesource.com/c/go/+/648295
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Damien Neil 2025-02-10 15:35:17 -08:00 committed by Gopher Robot
parent 187fd2698d
commit 807a51b391
21 changed files with 224 additions and 1 deletions

View File

@ -1 +1,2 @@
pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
pkg os, method (*Root) Chown(string, int, int) error #67002

View File

@ -1,3 +1,4 @@
The [os.Root] type supports the following additional methods:
* [os.Root.Chmod]
* [os.Root.Chown]

View File

@ -26,3 +26,4 @@ TEXT ·libc_faccessat_trampoline(SB),NOSPLIT,$0-0; JMP libc_faccessat(SB)
TEXT ·libc_readlinkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_readlinkat(SB)
TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0; JMP libc_mkdirat(SB)
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchmodat(SB)
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)

View File

@ -16,3 +16,5 @@ TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0
JMP libc_mkdirat(SB)
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0
JMP libc_fchmodat(SB)
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0
JMP libc_fchownat(SB)

View File

@ -96,3 +96,21 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
p, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
_, _, errno := syscall.Syscall6(fchownatTrap,
uintptr(dirfd),
uintptr(unsafe.Pointer(p)),
uintptr(uid),
uintptr(gid),
uintptr(flags),
0)
if errno != 0 {
return errno
}
return nil
}

View File

@ -5,6 +5,7 @@
package unix
//go:cgo_import_dynamic libc_fchmodat fchmodat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_fchownat fchownat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_fstatat fstatat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_openat openat "libc.a/shr_64.o"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"

View File

@ -80,3 +80,25 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
func libc_fchownat_trampoline()
//go:cgo_import_dynamic libc_fchownat fchownat "/usr/lib/libSystem.B.dylib"
func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
p, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_fchownat_trampoline),
uintptr(dirfd),
uintptr(unsafe.Pointer(p)),
uintptr(uid),
uintptr(gid),
uintptr(flags),
0)
if errno != 0 {
return errno
}
return nil
}

View File

@ -17,6 +17,7 @@ import (
//go:linkname procReadlinkat libc_readlinkat
//go:linkname procMkdirat libc_mkdirat
//go:linkname procFchmodat libc_fchmodat
//go:linkname procFchownat libc_chownat
var (
procFstatat,
@ -24,7 +25,8 @@ var (
procUnlinkat,
procReadlinkat,
procMkdirat,
procFchmodat uintptr
procFchmodat,
procFchownat uintptr
)
func Unlinkat(dirfd int, path string, flags int) error {
@ -126,3 +128,21 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
p, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
_, _, errno := syscall6(uintptr(unsafe.Pointer(&procFchownat)), 4,
uintptr(dirfd),
uintptr(unsafe.Pointer(p)),
uintptr(uid),
uintptr(gid),
uintptr(flags),
0)
if errno != 0 {
return errno
}
return nil
}

View File

@ -71,3 +71,25 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
}
return nil
}
//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
func libc_fchownat_trampoline()
func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
p, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_fchmodat_trampoline),
uintptr(dirfd),
uintptr(unsafe.Pointer(p)),
uintptr(uid),
uintptr(gid),
uintptr(flags),
0)
if errno != 0 {
return errno
}
return nil
}

View File

@ -14,6 +14,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
//go:cgo_import_dynamic libc_faccessat faccessat "libc.so"
//go:cgo_import_dynamic libc_fchmodat fchmodat "libc.so"
//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
//go:cgo_import_dynamic libc_openat openat "libc.so"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"

View File

@ -13,6 +13,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
AT_EACCESS = 0x4
AT_FDCWD = 0xfffafdcd

View File

@ -20,4 +20,5 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)

View File

@ -12,6 +12,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)
const (

View File

@ -13,6 +13,7 @@ const (
readlinkatTrap uintptr = syscall.SYS_READLINKAT
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
)
const (

View File

@ -106,6 +106,11 @@ func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
return syscall.ENOSYS
}
func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
// WASI preview 1 doesn't support changing file ownership.
return syscall.ENOSYS
}
//go:wasmimport wasi_snapshot_preview1 path_create_directory
//go:noescape
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno

View File

@ -151,6 +151,12 @@ func (r *Root) Mkdir(name string, perm FileMode) error {
return rootMkdir(r, name, perm)
}
// Chown changes the numeric uid and gid of the named file in the root.
// See [Chown] for more details.
func (r *Root) Chown(name string, uid, gid int) error {
return rootChown(r, name, uid, gid)
}
// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func (r *Root) Remove(name string) error {

View File

@ -105,6 +105,16 @@ func rootChmod(r *Root, name string, mode FileMode) error {
return nil
}
func rootChown(r *Root, name string, uid, gid int) error {
if err := checkPathEscapes(r, name); err != nil {
return &PathError{Op: "chownat", Path: name, Err: err}
}
if err := Chown(joinPath(r.root.name, name), uid, gid); err != nil {
return &PathError{Op: "chownat", Path: name, Err: underlyingError(err)}
}
return nil
}
func rootMkdir(r *Root, name string, perm FileMode) error {
if err := checkPathEscapes(r, name); err != nil {
return &PathError{Op: "mkdirat", Path: name, Err: err}

View File

@ -77,6 +77,16 @@ func rootChmod(r *Root, name string, mode FileMode) error {
return nil
}
func rootChown(r *Root, name string, uid, gid int) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, chownat(parent, name, uid, gid)
})
if err != nil {
return &PathError{Op: "chownat", Path: name, Err: err}
}
return err
}
func rootMkdir(r *Root, name string, perm FileMode) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, mkdirat(parent, name, perm)

View File

@ -157,6 +157,14 @@ func chmodat(parent int, name string, mode FileMode) error {
})
}
func chownat(parent int, name string, uid, gid int) error {
return afterResolvingSymlink(parent, name, func() error {
return ignoringEINTR(func() error {
return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
})
})
}
func mkdirat(fd int, name string, perm FileMode) error {
return ignoringEINTR(func() error {
return unix.Mkdirat(fd, name, syscallMode(perm))

87
src/os/root_unix_test.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2025 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.
//go:build unix || (js && wasm) || wasip1
package os_test
import (
"fmt"
"os"
"runtime"
"syscall"
"testing"
)
func TestRootChown(t *testing.T) {
if runtime.GOOS == "wasip1" {
t.Skip("Chown not supported on " + runtime.GOOS)
}
// Look up the current default uid/gid.
f := newFile(t)
dir, err := f.Stat()
if err != nil {
t.Fatal(err)
}
sys := dir.Sys().(*syscall.Stat_t)
groups, err := os.Getgroups()
if err != nil {
t.Fatalf("getgroups: %v", err)
}
groups = append(groups, os.Getgid())
for _, test := range rootTestCases {
test.run(t, func(t *testing.T, target string, root *os.Root) {
if target != "" {
if err := os.WriteFile(target, nil, 0o666); err != nil {
t.Fatal(err)
}
}
for _, gid := range groups {
err := root.Chown(test.open, -1, gid)
if errEndsTest(t, err, test.wantError, "root.Chown(%q, -1, %v)", test.open, gid) {
return
}
checkUidGid(t, target, int(sys.Uid), gid)
}
})
}
}
func TestRootConsistencyChown(t *testing.T) {
if runtime.GOOS == "wasip1" {
t.Skip("Chown not supported on " + runtime.GOOS)
}
groups, err := os.Getgroups()
if err != nil {
t.Fatalf("getgroups: %v", err)
}
var gid int
if len(groups) == 0 {
gid = os.Getgid()
} else {
gid = groups[0]
}
for _, test := range rootConsistencyTestCases {
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
chown := os.Chown
lstat := os.Lstat
if r != nil {
chown = r.Chown
lstat = r.Lstat
}
err := chown(path, -1, gid)
if err != nil {
return "", err
}
fi, err := lstat(path)
if err != nil {
return "", err
}
sys := fi.Sys().(*syscall.Stat_t)
return fmt.Sprintf("%v %v", sys.Uid, sys.Gid), nil
})
}
}

View File

@ -276,6 +276,10 @@ func chmodat(parent syscall.Handle, name string, mode FileMode) error {
return windows.SetFileInformationByHandle(h, windows.FileBasicInfo, unsafe.Pointer(&fbi), uint32(unsafe.Sizeof(fbi)))
}
func chownat(parent syscall.Handle, name string, uid, gid int) error {
return syscall.EWINDOWS // matches syscall.Chown
}
func mkdirat(dirfd syscall.Handle, name string, perm FileMode) error {
return windows.Mkdirat(dirfd, name, syscallMode(perm))
}