]> Cypherpunks repositories - gostls13.git/commitdiff
os: add Root.Lchown
authorDamien Neil <dneil@google.com>
Fri, 14 Feb 2025 00:38:08 +0000 (16:38 -0800)
committerGopher Robot <gobot@golang.org>
Wed, 19 Mar 2025 17:48:54 +0000 (10:48 -0700)
For #67002

Change-Id: I1bbf18838a1dd2281a2b6e56fc8a58ef70007adc
Reviewed-on: https://go-review.googlesource.com/c/go/+/649536
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>
api/next/67002.txt
doc/next/6-stdlib/99-minor/os/67002.md
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
src/os/root_windows.go

index 2e6b6fe6628af9d4ec28a9f18cdba8bafad08774..f7c3530f5941bfa25e4652664f6c1b484cb2558b 100644 (file)
@@ -1,3 +1,4 @@
 pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
 pkg os, method (*Root) Chown(string, int, int) error #67002
 pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
+pkg os, method (*Root) Lchown(string, int, int) error #67002
index 0f82fb31e6d2a3e34ec71f9e25d589f3e23db450..629ea55ac0cc6da15661ff258f5156535b3fe539 100644 (file)
@@ -3,3 +3,4 @@ The [os.Root] type supports the following additional methods:
   * [os.Root.Chmod]
   * [os.Root.Chown]
   * [os.Root.Chtimes]
+  * [os.Root.Lchown]
index bcabb496bcb9026e4a69539ebd278c3cf00f8e5d..27bd871c176b37bea00073726d401f8fe3d7834e 100644 (file)
@@ -159,6 +159,12 @@ func (r *Root) Chown(name string, uid, gid int) error {
        return rootChown(r, name, uid, gid)
 }
 
+// Lchown changes the numeric uid and gid of the named file in the root.
+// See [Lchown] for more details.
+func (r *Root) Lchown(name string, uid, gid int) error {
+       return rootLchown(r, name, uid, gid)
+}
+
 // Chtimes changes the access and modification times of the named file in the root.
 // See [Chtimes] for more details.
 func (r *Root) Chtimes(name string, atime time.Time, mtime time.Time) error {
index 186a382df3c9c51baeaf9f9fd35d9c4e327e7351..46dd79473921dbbe305c29f295cfa4a687448773 100644 (file)
@@ -116,6 +116,16 @@ func rootChown(r *Root, name string, uid, gid int) error {
        return nil
 }
 
+func rootLchown(r *Root, name string, uid, gid int) error {
+       if err := checkPathEscapesLstat(r, name); err != nil {
+               return &PathError{Op: "lchownat", Path: name, Err: err}
+       }
+       if err := Lchown(joinPath(r.root.name, name), uid, gid); err != nil {
+               return &PathError{Op: "lchownat", Path: name, Err: underlyingError(err)}
+       }
+       return nil
+}
+
 func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
        if err := checkPathEscapes(r, name); err != nil {
                return &PathError{Op: "chtimesat", Path: name, Err: err}
index e28b192f4cdbd16e6dfb7c2a6cdcf30deef07161..e47004e8eac68c06cfc30f66f1eaa7c2560e2ed1 100644 (file)
@@ -88,6 +88,16 @@ func rootChown(r *Root, name string, uid, gid int) error {
        return nil
 }
 
+func rootLchown(r *Root, name string, uid, gid int) error {
+       _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
+               return struct{}{}, lchownat(parent, name, uid, gid)
+       })
+       if err != nil {
+               return &PathError{Op: "lchownat", Path: name, Err: err}
+       }
+       return err
+}
+
 func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
        _, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
                return struct{}{}, chtimesat(parent, name, atime, mtime)
index 884c1a38d951d6d359ae38b1ef30b3d4a5e9a1e2..a5ca10b0cd8ad51faa7152508b5dd31f4106cd72 100644 (file)
@@ -166,6 +166,12 @@ func chownat(parent int, name string, uid, gid int) error {
        })
 }
 
+func lchownat(parent int, name string, uid, gid int) error {
+       return ignoringEINTR(func() error {
+               return unix.Fchownat(parent, name, uid, gid, unix.AT_SYMLINK_NOFOLLOW)
+       })
+}
+
 func chtimesat(parent int, name string, atime time.Time, mtime time.Time) error {
        return afterResolvingSymlink(parent, name, func() error {
                return ignoringEINTR(func() error {
index 280efc68751ed38504437e9975a4d330e48da8c0..0562af1f5ee84c9a3a54cb36e0b3b689169d1434 100644 (file)
@@ -9,6 +9,7 @@ package os_test
 import (
        "fmt"
        "os"
+       "path/filepath"
        "runtime"
        "syscall"
        "testing"
@@ -50,6 +51,46 @@ func TestRootChown(t *testing.T) {
        }
 }
 
+func TestRootLchown(t *testing.T) {
+       if runtime.GOOS == "wasip1" {
+               t.Skip("Lchown 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) {
+                       wantError := test.wantError
+                       if test.ltarget != "" {
+                               wantError = false
+                               target = filepath.Join(root.Name(), test.ltarget)
+                       } else if target != "" {
+                               if err := os.WriteFile(target, nil, 0o666); err != nil {
+                                       t.Fatal(err)
+                               }
+                       }
+                       for _, gid := range groups {
+                               err := root.Lchown(test.open, -1, gid)
+                               if errEndsTest(t, err, wantError, "root.Lchown(%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)
@@ -85,3 +126,39 @@ func TestRootConsistencyChown(t *testing.T) {
                })
        }
 }
+
+func TestRootConsistencyLchown(t *testing.T) {
+       if runtime.GOOS == "wasip1" {
+               t.Skip("Lchown 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) {
+                       lchown := os.Lchown
+                       lstat := os.Lstat
+                       if r != nil {
+                               lchown = r.Lchown
+                               lstat = r.Lstat
+                       }
+                       err := lchown(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 eed81ea51b85bd76cee76970936577a55c83167e..c56946d0d5534e947a3bae3ead8db9e303f44b1c 100644 (file)
@@ -281,6 +281,10 @@ func chownat(parent syscall.Handle, name string, uid, gid int) error {
        return syscall.EWINDOWS // matches syscall.Chown
 }
 
+func lchownat(parent syscall.Handle, name string, uid, gid int) error {
+       return syscall.EWINDOWS // matches syscall.Lchown
+}
+
 func mkdirat(dirfd syscall.Handle, name string, perm FileMode) error {
        return windows.Mkdirat(dirfd, name, syscallMode(perm))
 }