diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 33445e79f9..50081b2eac 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -166,6 +166,19 @@ func Chown(name string, uid, gid int) error { return nil } +// Lchown 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 itself. +// If there is an error, it will be of type [*PathError]. +// +// If there is an error, it will be of type *PathError. +func Lchown(name string, uid, gid int) error { + e := ignoringEINTR(func() error { return syscall.Lchown(name, uid, gid) }) + if e != nil { + return &PathError{Op: "lchown", 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. diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index ad151abb03..b9c43038db 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -12,6 +12,7 @@ import ( "errors" "io/fs" . "os" + "path/filepath" "runtime" "testing" ) @@ -70,3 +71,38 @@ func TestChownErr(t *testing.T) { } } } + +func TestLchownErr(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("TestLchown", t) + defer Remove(f.Name()) + defer f.Close() + link := filepath.Join(TempDir(), "TestLchownLink") + _ = Symlink(f.Name(), link) + defer Remove(link) + + // EACCES + if err := Lchown(link, TEST_UID_ROOT, TEST_GID_ROOT); err != nil { + errCmp := fs.PathError{Op: "lchown", Path: link, Err: errors.New("operation not permitted")} + if errors.Is(err, &errCmp) { + t.Fatalf("lchown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", link, TEST_UID_ROOT, TEST_GID_ROOT, err) + } + } + + // ENOENT + if err := Chown("invalid", Geteuid(), Getgid()); err != nil { + errCmp := fs.PathError{Op: "lchown", 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'", link, Geteuid(), Getegid(), err) + } + } +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 86c756383e..4eea3ef5dd 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -172,6 +172,15 @@ func Chown(path string, uid, gid int) (err error) { return } +func Lchown(path string, uid, gid int) (err error) { + data := cstring(path) + fail := int(libc_lchown(&data[0], uid, gid)) + if fail < 0 { + err = getErrno() + } + return +} + func Fork() (err error) { fail := int(libc_fork()) if fail < 0 { @@ -368,6 +377,11 @@ func libc_chmod(pathname *byte, mode uint32) int32 //export chown func libc_chown(pathname *byte, owner, group int) int32 +// int lchown(const char *pathname, uid_t owner, gid_t group); +// +//export lchown +func libc_lchown(pathname *byte, owner, group int) int32 + // int mkdir(const char *pathname, mode_t mode); // //export mkdir