Skip to content

Commit

Permalink
os/Chown (#4213)
Browse files Browse the repository at this point in the history
src/os, src/syscall: add os.Chown
  • Loading branch information
leongross authored Jul 21, 2024
1 parent 4f908f4 commit 3e2230e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/os/file_anyos.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ func Chmod(name string, mode FileMode) error {
return nil
}

// Chown changes the numeric uid and gid of the named file.
// If the file is a symbolic link, it changes the uid and gid of the link's target.
// A uid or gid of -1 means to not change that value.
// If there is an error, it will be of type *PathError.
func Chown(name string, uid, gid int) error {
e := ignoringEINTR(func() error {
return syscall.Chown(name, uid, gid)
})
if e != nil {
return &PathError{Op: "chown", Path: name, Err: e}
}
return nil
}

// ignoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
Expand Down
41 changes: 41 additions & 0 deletions src/os/os_chmod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
package os_test

import (
"errors"
"io/fs"
. "os"
"runtime"
"testing"
)

func TestChmod(t *testing.T) {
// Chmod
f := newFile("TestChmod", t)
defer Remove(f.Name())
defer f.Close()
Expand All @@ -28,4 +31,42 @@ func TestChmod(t *testing.T) {
t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err)
}
checkMode(t, f.Name(), fm)

}

// Since testing syscalls requires a static, predictable environment that has to be controlled
// by the CI, we don't test for success but for failures and verify that the error messages are as expected.
// EACCES is returned when the user does not have the required permissions to change the ownership of the file
// ENOENT is returned when the file does not exist
// ENOTDIR is returned when the file is not a directory
func TestChownErr(t *testing.T) {
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
t.Log("skipping on " + runtime.GOOS)
return
}

var (
TEST_UID_ROOT = 0
TEST_GID_ROOT = 0
)

f := newFile("TestChown", t)
defer Remove(f.Name())
defer f.Close()

// EACCES
if err := Chown(f.Name(), TEST_UID_ROOT, TEST_GID_ROOT); err != nil {
errCmp := fs.PathError{Op: "chown", Path: f.Name(), Err: errors.New("operation not permitted")}
if errors.Is(err, &errCmp) {
t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", f.Name(), TEST_UID_ROOT, TEST_GID_ROOT, err)
}
}

// ENOENT
if err := Chown("invalid", Geteuid(), Getgid()); err != nil {
errCmp := fs.PathError{Op: "chown", Path: "invalid", Err: errors.New("no such file or directory")}
if errors.Is(err, &errCmp) {
t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'no such file or directory'", f.Name(), Geteuid(), Getegid(), err)
}
}
}
14 changes: 14 additions & 0 deletions src/syscall/syscall_libc.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,15 @@ func Unlink(path string) (err error) {
return
}

func Chown(path string, uid, gid int) (err error) {
data := cstring(path)
fail := int(libc_chown(&data[0], uid, gid))
if fail < 0 {
err = getErrno()
}
return
}

func Faccessat(dirfd int, path string, mode uint32, flags int) (err error)

func Kill(pid int, sig Signal) (err error) {
Expand Down Expand Up @@ -357,6 +366,11 @@ func libc_chdir(pathname *byte) int32
//export chmod
func libc_chmod(pathname *byte, mode uint32) int32

// int chown(const char *pathname, uid_t owner, gid_t group);
//
//export chown
func libc_chown(pathname *byte, owner, group int) int32

// int mkdir(const char *pathname, mode_t mode);
//
//export mkdir
Expand Down

0 comments on commit 3e2230e

Please sign in to comment.