acl: implement Update in C
All checks were successful
Test / Create distribution (push) Successful in 18s
Test / Run NixOS test (push) Successful in 46s

The original implementation was effectively just writing C in Go.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-02-17 20:37:10 +09:00
parent 7e69893264
commit e85be67fd9
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
3 changed files with 88 additions and 177 deletions

69
acl/acl-update.c Normal file
View File

@ -0,0 +1,69 @@
#include "acl-update.h"
#include <stdlib.h>
#include <stdbool.h>
#include <sys/acl.h>
#include <acl/libacl.h>
int f_acl_update_file_by_uid(const char *path_p, uid_t uid, acl_perm_t *perms, size_t plen) {
int ret = -1;
bool v;
int i;
acl_t acl;
acl_entry_t entry;
acl_tag_t tag_type;
void *qualifier_p;
acl_permset_t permset;
acl = acl_get_file(path_p, ACL_TYPE_ACCESS);
if (acl == NULL)
goto out;
// prune entries by uid
for (i = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); i == 1; i = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) {
if (acl_get_tag_type(entry, &tag_type) != 0)
return -1;
if (tag_type != ACL_USER)
continue;
qualifier_p = acl_get_qualifier(entry);
if (qualifier_p == NULL)
return -1;
v = *(uid_t *)qualifier_p == uid;
acl_free(qualifier_p);
if (!v)
continue;
acl_delete_entry(acl, entry);
}
if (plen == 0)
goto set;
if (acl_create_entry(&acl, &entry) != 0)
goto out;
if (acl_get_permset(entry, &permset) != 0)
goto out;
for (i = 0; i < plen; i++) {
if (acl_add_perm(permset, perms[i]) != 0)
goto out;
}
if (acl_set_tag_type(entry, ACL_USER) != 0)
goto out;
if (acl_set_qualifier(entry, (void *)&uid) != 0)
goto out;
set:
if (acl_calc_mask(&acl) != 0)
goto out;
if (acl_valid(acl) != 0)
goto out;
if (acl_set_file(path_p, ACL_TYPE_ACCESS, acl) == 0)
ret = 0;
out:
free((void *)path_p);
if (acl != NULL)
acl_free((void *)acl);
return ret;
}

3
acl/acl-update.h Normal file
View File

@ -0,0 +1,3 @@
#include <sys/acl.h>
int f_acl_update_file_by_uid(const char *path_p, uid_t uid, acl_perm_t *perms, size_t plen);

View File

@ -1,197 +1,36 @@
// Package acl implements simple ACL manipulation via libacl.
package acl
import (
"errors"
"runtime"
"syscall"
"unsafe"
)
/*
#cgo linux pkg-config: --static libacl
#include <stdlib.h>
#include <sys/acl.h>
#include <acl/libacl.h>
static acl_t _go_acl_get_file(const char *path_p, acl_type_t type) {
acl_t acl = acl_get_file(path_p, type);
free((void *)path_p);
return acl;
}
static int _go_acl_set_file(const char *path_p, acl_type_t type, acl_t acl) {
if (acl_valid(acl) != 0) {
return -1;
}
int ret = acl_set_file(path_p, type, acl);
free((void *)path_p);
return ret;
}
#include "acl-update.h"
*/
import "C"
func getFile(name string, t C.acl_type_t) (*ACL, error) {
a, err := C._go_acl_get_file(C.CString(name), t)
if errors.Is(err, syscall.ENODATA) {
err = nil
}
return newACL(a), err
}
func (acl *ACL) setFile(name string, t C.acl_type_t) error {
_, err := C._go_acl_set_file(C.CString(name), t, acl.acl)
return err
}
func newACL(a C.acl_t) *ACL {
acl := &ACL{a}
runtime.SetFinalizer(acl, (*ACL).free)
return acl
}
type ACL struct {
acl C.acl_t
}
func (acl *ACL) free() {
C.acl_free(unsafe.Pointer(acl.acl))
// no need for a finalizer anymore
runtime.SetFinalizer(acl, nil)
}
type Perm C.acl_perm_t
const (
Read = C.ACL_READ
Write = C.ACL_WRITE
Execute = C.ACL_EXECUTE
TypeDefault = C.ACL_TYPE_DEFAULT
TypeAccess = C.ACL_TYPE_ACCESS
UndefinedTag = C.ACL_UNDEFINED_TAG
UserObj = C.ACL_USER_OBJ
User = C.ACL_USER
GroupObj = C.ACL_GROUP_OBJ
Group = C.ACL_GROUP
Mask = C.ACL_MASK
Other = C.ACL_OTHER
Read Perm = C.ACL_READ
Write Perm = C.ACL_WRITE
Execute Perm = C.ACL_EXECUTE
)
type (
Perm C.acl_perm_t
)
func (acl *ACL) removeEntry(tt C.acl_tag_t, tq int) error {
var e C.acl_entry_t
// get first entry
if r, err := C.acl_get_entry(acl.acl, C.ACL_FIRST_ENTRY, &e); err != nil {
return err
} else if r == 0 {
// return on acl with no entries
return nil
}
for {
if r, err := C.acl_get_entry(acl.acl, C.ACL_NEXT_ENTRY, &e); err != nil {
return err
} else if r == 0 {
// return on drained acl
return nil
}
var (
q int
t C.acl_tag_t
)
// get current entry tag type
if _, err := C.acl_get_tag_type(e, &t); err != nil {
return err
}
// get current entry qualifier
if rq, err := C.acl_get_qualifier(e); err != nil {
// neither ACL_USER nor ACL_GROUP
if errors.Is(err, syscall.EINVAL) {
continue
}
return err
} else {
q = *(*int)(rq)
C.acl_free(rq)
}
// delete on match
if t == tt && q == tq {
_, err := C.acl_delete_entry(acl.acl, e)
return err
}
}
}
// Update replaces ACL_USER entry with qualifier uid.
func Update(name string, uid int, perms ...Perm) error {
// read acl from file
a, err := getFile(name, TypeAccess)
if err != nil {
return err
}
// free acl on return if get is successful
defer a.free()
// remove existing entry
if err = a.removeEntry(User, uid); err != nil {
return err
}
// create new entry if perms are passed
var p *Perm
if len(perms) > 0 {
// create new acl entry
var e C.acl_entry_t
if _, err = C.acl_create_entry(&a.acl, &e); err != nil {
return err
}
// get perm set of new entry
var p C.acl_permset_t
if _, err = C.acl_get_permset(e, &p); err != nil {
return err
}
// add target perms
for _, perm := range perms {
if _, err = C.acl_add_perm(p, C.acl_perm_t(perm)); err != nil {
return err
}
}
// set perm set to new entry
if _, err = C.acl_set_permset(e, p); err != nil {
return err
}
// set user tag to new entry
if _, err = C.acl_set_tag_type(e, User); err != nil {
return err
}
// set qualifier (uid) to new entry
if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil {
return err
}
p = &perms[0]
}
// calculate mask after update
if _, err = C.acl_calc_mask(&a.acl); err != nil {
return err
r, err := C.f_acl_update_file_by_uid(
C.CString(name),
C.uid_t(uid),
(*C.acl_perm_t)(p),
C.size_t(len(perms)),
)
if r == 0 {
return nil
}
// write acl to file
return a.setFile(name, TypeAccess)
return err
}