feat: Implement Lchown function for changing file ownership (#5161)

* feat: Implement Lchown function for changing file ownership

- Added Lchown function to change the numeric uid and gid of a file or symbolic link.
- Included error handling to return *PathError for failed operations.
- Added unit tests for Lchown to verify behavior on different error scenarios.

* fix: fix chmod tests
This commit is contained in:
Itxaka
2026-01-10 09:31:22 +01:00
committed by GitHub
parent 743bf45e00
commit ca36fba7e4
3 changed files with 63 additions and 0 deletions
+13
View File
@@ -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.
+36
View File
@@ -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)
}
}
}
+14
View File
@@ -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