Skip to content

Commit

Permalink
os: add Root.Chown
Browse files Browse the repository at this point in the history
For golang#67002

Change-Id: I546537618cbe32217fa72264d49db2b1a1d3b6db
Reviewed-on: https://go-review.googlesource.com/c/go/+/648295
LUCI-TryBot-Result: Go LUCI <[email protected]>
Auto-Submit: Damien Neil <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
  • Loading branch information
neild authored and gopherbot committed Feb 13, 2025
1 parent 187fd26 commit 807a51b
Show file tree
Hide file tree
Showing 21 changed files with 224 additions and 1 deletion.
1 change: 1 addition & 0 deletions api/next/67002.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
pkg os, method (*Root) Chown(string, int, int) error #67002
1 change: 1 addition & 0 deletions doc/next/6-stdlib/99-minor/os/67002.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
The [os.Root] type supports the following additional methods:

* [os.Root.Chmod]
* [os.Root.Chown]
1 change: 1 addition & 0 deletions src/internal/syscall/unix/asm_darwin.s
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 2 additions & 0 deletions src/internal/syscall/unix/asm_openbsd.s
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 18 additions & 0 deletions src/internal/syscall/unix/at.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_aix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 22 additions & 0 deletions src/internal/syscall/unix/at_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
22 changes: 21 additions & 1 deletion src/internal/syscall/unix/at_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ import (
//go:linkname procReadlinkat libc_readlinkat
//go:linkname procMkdirat libc_mkdirat
//go:linkname procFchmodat libc_fchmodat
//go:linkname procFchownat libc_chownat

var (
procFstatat,
procOpenat,
procUnlinkat,
procReadlinkat,
procMkdirat,
procFchmodat uintptr
procFchmodat,
procFchownat uintptr
)

func Unlinkat(dirfd int, path string, flags int) error {
Expand Down Expand Up @@ -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
}
22 changes: 22 additions & 0 deletions src/internal/syscall/unix/at_openbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_solaris.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_sysnum_dragonfly.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_sysnum_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_sysnum_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
1 change: 1 addition & 0 deletions src/internal/syscall/unix/at_sysnum_netbsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
5 changes: 5 additions & 0 deletions src/internal/syscall/unix/at_wasip1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/os/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions src/os/root_noopenat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
10 changes: 10 additions & 0 deletions src/os/root_openat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/os/root_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
87 changes: 87 additions & 0 deletions src/os/root_unix_test.go
Original file line number Diff line number Diff line change
@@ -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
})
}
}
4 changes: 4 additions & 0 deletions src/os/root_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down

0 comments on commit 807a51b

Please sign in to comment.