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)
}
}
}