]> Cypherpunks repositories - gostls13.git/commitdiff
os: add Root.Chown
authorDamien Neil <dneil@google.com>
Mon, 10 Feb 2025 23:35:17 +0000 (15:35 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 13 Feb 2025 23:29:56 +0000 (15:29 -0800)
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>
21 files changed:
api/next/67002.txt
doc/next/6-stdlib/99-minor/os/67002.md
src/internal/syscall/unix/asm_darwin.s
src/internal/syscall/unix/asm_openbsd.s
src/internal/syscall/unix/at.go
src/internal/syscall/unix/at_aix.go
src/internal/syscall/unix/at_darwin.go
src/internal/syscall/unix/at_libc.go
src/internal/syscall/unix/at_openbsd.go
src/internal/syscall/unix/at_solaris.go
src/internal/syscall/unix/at_sysnum_dragonfly.go
src/internal/syscall/unix/at_sysnum_freebsd.go
src/internal/syscall/unix/at_sysnum_linux.go
src/internal/syscall/unix/at_sysnum_netbsd.go
src/internal/syscall/unix/at_wasip1.go
src/os/root.go
src/os/root_noopenat.go
src/os/root_openat.go
src/os/root_unix.go
src/os/root_unix_test.go [new file with mode: 0644]
src/os/root_windows.go

index 06119c0e754589d5d76e79b19bb2351484a58f27..216d3a3afeb606496cf5e8fd3071aa64aa1a8641 100644 (file)
@@ -1 +1,2 @@
 pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
+pkg os, method (*Root) Chown(string, int, int) error #67002
index a0751c30e2fec30693e6d962523a767ce493d12b..04ff6d5de0fec0fc77d8314676fed8bb6f36b63b 100644 (file)
@@ -1,3 +1,4 @@
 The [os.Root] type supports the following additional methods:
 
   * [os.Root.Chmod]
+  * [os.Root.Chown]
index de6e01ee4a4fe3c70140fc392903e4e86cfea69b..0f28cd1e393bc735545e7134919b971f219453bb 100644 (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)
index 306ef4664d57d84b131b47c7fc8f62156ad4b7dd..b804a52714c3b4a58f2109636b78ab09c8189fa2 100644 (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)
index 2a29dd6a5a3ac777d622998f48f3310d7c374606..794f8ace14f20d559c461526fdc5cc5bb9dbc704 100644 (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
+}
index e679efc34459d8f3b7164f98d3b336b608a06c77..aa188cdb76f6bfc7c4b66548113296845c82b8b6 100644 (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"
index 759b0943f558c38451ffdb4451473d7dc4991264..75d7b455691859f30fa512bebdf4245bae2f84cf 100644 (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
+}
index f88e09d31db417e49df6575ce02d69a8e23c7781..137e0e093651eda7aad18871fe0ca398d74417f5 100644 (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
+}
index 26ca70322b7f150db44c363a890197181eefb2ae..771cb063e0cf4001cce4b198f8f8340a5948b9dc 100644 (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
+}
index a4910f1003aaae07a720c0b7ba83f606f3839e1b..f84e8e35daa11ca9e4362ee8f94f812fb076cbe1 100644 (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"
index 84c60c47b8ac5c81cf76b4a0ca4b65e3d42b5bf6..1e89e97f388077f51e10065e26843f3e80b3c18a 100644 (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
index 22ff4e7e895c5834a2c8436519ced9f6dae36fba..59a8c2ce5a21c10d93ffff1165a7e825d17eb90c 100644 (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
 )
index 8fba319cab7dbdbe1366ac0e4f65d5f2b7c2e4ea..35cc4307e92f3dde94c678d05a211a9671054149 100644 (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 (
index f2b7a4f9ebfe8a387e523d6a221ce5f99664c4e4..bb946b6581d25314e124db4243ac3244c06ba7dd 100644 (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 (
index 7289317110f8552698f5ac07c82651c0dccbccc7..3fdc95436c2dc130100fb176c006e7836de837dd 100644 (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
index cd26144ab7410f2556e07601f8f96a1cd21d5a9a..fd3b603ed8380530678bb3e3d0624efd80e7aa43 100644 (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 {
index 819486f289f397440c26f7528841b5ffc97d99fe..919e78c777825d3a60a70f7af051c35a7028345f 100644 (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}
index d98d2e36752f4bfa036950090d2166d64438d067..65d3eacf4df98bfcaaaeb5e571f95595858aa09b 100644 (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)
index 06da8da15ec0d2dea4a8549d52f4dca6562d6c64..76d6b74eb7364bd85d1dd08b225842f2d80aee4f 100644 (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))
diff --git a/src/os/root_unix_test.go b/src/os/root_unix_test.go
new file mode 100644 (file)
index 0000000..280efc6
--- /dev/null
@@ -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
+               })
+       }
+}
index 9b57d5648e695645ebd8314038604e596e10dd07..4f391cb2a7d9fd54740d7e451ecd7a66f9300a20 100644 (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))
 }