diff options
Diffstat (limited to 'system/acl')
| -rw-r--r-- | system/acl/acl.go | 33 | ||||
| -rw-r--r-- | system/acl/acl_test.go | 276 | ||||
| -rw-r--r-- | system/acl/deprecated.go | 25 | ||||
| -rw-r--r-- | system/acl/libacl-helper.c | 90 | ||||
| -rw-r--r-- | system/acl/libacl-helper.go | 40 | ||||
| -rw-r--r-- | system/acl/libacl-helper.h | 4 | ||||
| -rw-r--r-- | system/acl/libacl-helper_test.go | 60 | ||||
| -rw-r--r-- | system/acl/perms.go | 18 | ||||
| -rw-r--r-- | system/acl/perms_test.go | 30 |
9 files changed, 25 insertions, 551 deletions
diff --git a/system/acl/acl.go b/system/acl/acl.go deleted file mode 100644 index 366ad533..00000000 --- a/system/acl/acl.go +++ /dev/null @@ -1,33 +0,0 @@ -// Package acl implements simple ACL manipulation via libacl. -package acl - -/* -#cgo linux pkg-config: --static libacl - -#include "libacl-helper.h" -*/ -import "C" - -type Perm C.acl_perm_t - -const ( - Read Perm = C.ACL_READ - Write Perm = C.ACL_WRITE - Execute Perm = C.ACL_EXECUTE -) - -// Update replaces ACL_USER entry with qualifier uid. -func Update(name string, uid int, perms ...Perm) error { - var p *Perm - if len(perms) > 0 { - p = &perms[0] - } - - r, err := C.hakurei_acl_update_file_by_uid( - C.CString(name), - C.uid_t(uid), - (*C.acl_perm_t)(p), - C.size_t(len(perms)), - ) - return newAclPathError(name, int(r), err) -} diff --git a/system/acl/acl_test.go b/system/acl/acl_test.go deleted file mode 100644 index c6c3e562..00000000 --- a/system/acl/acl_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package acl_test - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os" - "os/exec" - "path" - "reflect" - "strconv" - "testing" - - "hakurei.app/system/acl" -) - -const testFileName = "acl.test" - -var ( - uid = os.Geteuid() - cred = int32(os.Geteuid()) -) - -func TestUpdate(t *testing.T) { - if os.Getenv("GO_TEST_SKIP_ACL") == "1" { - t.Log("acl test skipped") - t.SkipNow() - } - - testFilePath := path.Join(t.TempDir(), testFileName) - - if f, err := os.Create(testFilePath); err != nil { - t.Fatalf("Create: error = %v", err) - } else { - if err = f.Close(); err != nil { - t.Fatalf("Close: error = %v", err) - } - } - defer func() { - if err := os.Remove(testFilePath); err != nil { - t.Fatalf("Remove: error = %v", err) - } - }() - - cur := getfacl(t, testFilePath) - - t.Run("default entry count", func(t *testing.T) { - if len(cur) != 3 { - t.Fatalf("unexpected test file acl length %d", len(cur)) - } - }) - - t.Run("default clear mask", func(t *testing.T) { - if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("Update: error = %v", err) - } - if cur = getfacl(t, testFilePath); len(cur) != 4 { - t.Fatalf("Update: %v", cur) - } - }) - - t.Run("default clear consistency", func(t *testing.T) { - if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("Update: error = %v", err) - } - if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) { - t.Fatalf("Update: %v, want %v", val, cur) - } - }) - - testUpdate(t, testFilePath, "r--", cur, fAclPermRead, acl.Read) - testUpdate(t, testFilePath, "-w-", cur, fAclPermWrite, acl.Write) - testUpdate(t, testFilePath, "--x", cur, fAclPermExecute, acl.Execute) - testUpdate(t, testFilePath, "-wx", cur, fAclPermWrite|fAclPermExecute, acl.Write, acl.Execute) - testUpdate(t, testFilePath, "r-x", cur, fAclPermRead|fAclPermExecute, acl.Read, acl.Execute) - testUpdate(t, testFilePath, "rw-", cur, fAclPermRead|fAclPermWrite, acl.Read, acl.Write) - testUpdate(t, testFilePath, "rwx", cur, fAclPermRead|fAclPermWrite|fAclPermExecute, acl.Read, acl.Write, acl.Execute) -} - -func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val fAclPerm, perms ...acl.Perm) { - t.Run(name, func(t *testing.T) { - t.Cleanup(func() { - if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("Update: error = %v", err) - } - if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) { - t.Fatalf("Update: %v, want %v", v, cur) - } - }) - - if err := acl.Update(testFilePath, uid, perms...); err != nil { - t.Fatalf("Update: error = %v", err) - } - r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred) - if r == nil { - t.Fatalf("Update did not add an ACL entry") - } - if !r.equals(fAclTypeUser, cred, val) { - t.Fatalf("Update(%s) = %s", name, r) - } - }) -} - -type ( - getFAclInvocation struct { - cmd *exec.Cmd - val []*getFAclResp - pe []error - } - - getFAclResp struct { - typ fAclType - cred int32 - val fAclPerm - - raw []byte - } - - fAclPerm uintptr - fAclType uint8 -) - -const fAclBufSize = 16 - -const ( - fAclPermRead fAclPerm = 1 << iota - fAclPermWrite - fAclPermExecute -) - -const ( - fAclTypeUser fAclType = iota - fAclTypeGroup - fAclTypeMask - fAclTypeOther -) - -func (c *getFAclInvocation) run(name string) error { - if c.cmd != nil { - panic("attempted to run twice") - } - - c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name) - - scanErr := make(chan error, 1) - if p, err := c.cmd.StdoutPipe(); err != nil { - return err - } else { - go c.parse(p, scanErr) - } - - if err := c.cmd.Start(); err != nil { - return err - } - - return errors.Join(<-scanErr, c.cmd.Wait()) -} - -func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { - c.val = make([]*getFAclResp, 0, 4+fAclBufSize) - - s := bufio.NewScanner(pipe) - for s.Scan() { - fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3) - if len(fields) != 3 { - continue - } - - resp := getFAclResp{} - - switch string(fields[0]) { - case "user": - resp.typ = fAclTypeUser - case "group": - resp.typ = fAclTypeGroup - case "mask": - resp.typ = fAclTypeMask - case "other": - resp.typ = fAclTypeOther - default: - c.pe = append(c.pe, fmt.Errorf("unknown type %s", string(fields[0]))) - continue - } - - if len(fields[1]) == 0 { - resp.cred = -1 - } else { - if cred, err := strconv.Atoi(string(fields[1])); err != nil { - c.pe = append(c.pe, err) - continue - } else { - resp.cred = int32(cred) - if resp.cred < 0 { - c.pe = append(c.pe, fmt.Errorf("credential %d out of range", resp.cred)) - continue - } - } - } - - if len(fields[2]) != 3 { - c.pe = append(c.pe, fmt.Errorf("invalid perm length %d", len(fields[2]))) - continue - } else { - switch fields[2][0] { - case 'r': - resp.val |= fAclPermRead - case '-': - default: - c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0])) - continue - } - switch fields[2][1] { - case 'w': - resp.val |= fAclPermWrite - case '-': - default: - c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1])) - continue - } - switch fields[2][2] { - case 'x': - resp.val |= fAclPermExecute - case '-': - default: - c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2])) - continue - } - } - - resp.raw = make([]byte, len(s.Bytes())) - copy(resp.raw, s.Bytes()) - c.val = append(c.val, &resp) - } - scanErr <- s.Err() -} - -func (r *getFAclResp) String() string { - if r.raw != nil && len(r.raw) > 0 { - return string(r.raw) - } - - return "(user-initialised resp value)" -} - -func (r *getFAclResp) equals(typ fAclType, cred int32, val fAclPerm) bool { - return r.typ == typ && r.cred == cred && r.val == val -} - -func getfacl(t *testing.T, name string) []*getFAclResp { - c := new(getFAclInvocation) - if err := c.run(name); err != nil { - t.Fatalf("getfacl: error = %v", err) - } - if len(c.pe) != 0 { - t.Errorf("errors encountered parsing getfacl output\n%s", errors.Join(c.pe...).Error()) - } - return c.val -} - -func respByCred(v []*getFAclResp, typ fAclType, cred int32) *getFAclResp { - j := -1 - for i, r := range v { - if r.typ == typ && r.cred == cred { - if j != -1 { - panic("invalid acl") - } - j = i - } - } - if j == -1 { - return nil - } - return v[j] -} diff --git a/system/acl/deprecated.go b/system/acl/deprecated.go new file mode 100644 index 00000000..4119057b --- /dev/null +++ b/system/acl/deprecated.go @@ -0,0 +1,25 @@ +// Package acl exposes the internal/system/acl package. +// +// Deprecated: This package will be removed in 0.4. +package acl + +import ( + _ "unsafe" // for go:linkname + + "hakurei.app/internal/system/acl" +) + +type Perm = acl.Perm + +const ( + Read = acl.Read + Write = acl.Write + Execute = acl.Execute +) + +// Update replaces ACL_USER entry with qualifier uid. +// +//go:linkname Update hakurei.app/internal/system/acl.Update +func Update(name string, uid int, perms ...Perm) error + +type Perms = acl.Perms diff --git a/system/acl/libacl-helper.c b/system/acl/libacl-helper.c deleted file mode 100644 index d1df8167..00000000 --- a/system/acl/libacl-helper.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "libacl-helper.h" -#include <acl/libacl.h> -#include <stdbool.h> -#include <stdlib.h> -#include <sys/acl.h> - -int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid, - acl_perm_t *perms, size_t plen) { - int ret; - bool v; - int i; - acl_t acl; - acl_entry_t entry; - acl_tag_t tag_type; - void *qualifier_p; - acl_permset_t permset; - - ret = -1; /* acl_get_file */ - 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)) { - ret = -2; /* acl_get_tag_type */ - if (acl_get_tag_type(entry, &tag_type) != 0) - goto out; - if (tag_type != ACL_USER) - continue; - - ret = -3; /* acl_get_qualifier */ - qualifier_p = acl_get_qualifier(entry); - if (qualifier_p == NULL) - goto out; - v = *(uid_t *)qualifier_p == uid; - acl_free(qualifier_p); - - if (!v) - continue; - - ret = -4; /* acl_delete_entry */ - if (acl_delete_entry(acl, entry) != 0) - goto out; - } - - if (plen == 0) - goto set; - - ret = -5; /* acl_create_entry */ - if (acl_create_entry(&acl, &entry) != 0) - goto out; - - ret = -6; /* acl_get_permset */ - if (acl_get_permset(entry, &permset) != 0) - goto out; - - ret = -7; /* acl_add_perm */ - for (i = 0; i < plen; i++) { - if (acl_add_perm(permset, perms[i]) != 0) - goto out; - } - - ret = -8; /* acl_set_tag_type */ - if (acl_set_tag_type(entry, ACL_USER) != 0) - goto out; - - ret = -9; /* acl_set_qualifier */ - if (acl_set_qualifier(entry, (void *)&uid) != 0) - goto out; - -set: - ret = -10; /* acl_calc_mask */ - if (acl_calc_mask(&acl) != 0) - goto out; - - ret = -11; /* acl_valid */ - if (acl_valid(acl) != 0) - goto out; - - ret = -12; /* acl_set_file */ - 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; -} diff --git a/system/acl/libacl-helper.go b/system/acl/libacl-helper.go deleted file mode 100644 index 22575296..00000000 --- a/system/acl/libacl-helper.go +++ /dev/null @@ -1,40 +0,0 @@ -package acl - -import "os" - -func newAclPathError(name string, r int, err error) error { - pathError := &os.PathError{Path: name, Err: err} - switch r { - case 0: - return nil - - case -1: - pathError.Op = "acl_get_file" - case -2: - pathError.Op = "acl_get_tag_type" - case -3: - pathError.Op = "acl_get_qualifier" - case -4: - pathError.Op = "acl_delete_entry" - case -5: - pathError.Op = "acl_create_entry" - case -6: - pathError.Op = "acl_get_permset" - case -7: - pathError.Op = "acl_add_perm" - case -8: - pathError.Op = "acl_set_tag_type" - case -9: - pathError.Op = "acl_set_qualifier" - case -10: - pathError.Op = "acl_calc_mask" - case -11: - pathError.Op = "acl_valid" - case -12: - pathError.Op = "acl_set_file" - - default: // unreachable - pathError.Op = "setfacl" - } - return pathError -} diff --git a/system/acl/libacl-helper.h b/system/acl/libacl-helper.h deleted file mode 100644 index b86eb170..00000000 --- a/system/acl/libacl-helper.h +++ /dev/null @@ -1,4 +0,0 @@ -#include <sys/acl.h> - -int hakurei_acl_update_file_by_uid(const char *path_p, uid_t uid, - acl_perm_t *perms, size_t plen); diff --git a/system/acl/libacl-helper_test.go b/system/acl/libacl-helper_test.go deleted file mode 100644 index 13d23a3c..00000000 --- a/system/acl/libacl-helper_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package acl - -import ( - "os" - "reflect" - "syscall" - "testing" - - "hakurei.app/container" -) - -func TestNewAclPathError(t *testing.T) { - testCases := []struct { - name string - path string - r int - err error - want error - }{ - {"nil", container.Nonexistent, 0, syscall.ENOTRECOVERABLE, nil}, - - {"acl_get_file", container.Nonexistent, -1, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_get_file", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_get_tag_type", container.Nonexistent, -2, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_get_tag_type", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_get_qualifier", container.Nonexistent, -3, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_get_qualifier", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_delete_entry", container.Nonexistent, -4, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_delete_entry", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_create_entry", container.Nonexistent, -5, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_create_entry", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_get_permset", container.Nonexistent, -6, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_get_permset", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_add_perm", container.Nonexistent, -7, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_add_perm", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_set_tag_type", container.Nonexistent, -8, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_set_tag_type", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_set_qualifier", container.Nonexistent, -9, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_set_qualifier", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_calc_mask", container.Nonexistent, -10, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_calc_mask", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_valid", container.Nonexistent, -11, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_valid", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl_set_file", container.Nonexistent, -12, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "acl_set_file", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - - {"acl", container.Nonexistent, -13, syscall.ENOTRECOVERABLE, - &os.PathError{Op: "setfacl", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"invalid", container.Nonexistent, -0xdead, nil, - &os.PathError{Op: "setfacl", Path: container.Nonexistent}}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := newAclPathError(tc.path, tc.r, tc.err) - if !reflect.DeepEqual(err, tc.want) { - t.Errorf("newAclPathError: %v, want %v", err, tc.want) - } - }) - } -} diff --git a/system/acl/perms.go b/system/acl/perms.go deleted file mode 100644 index fbc99e82..00000000 --- a/system/acl/perms.go +++ /dev/null @@ -1,18 +0,0 @@ -package acl - -type Perms []Perm - -func (ps Perms) String() string { - var s = []byte("---") - for _, p := range ps { - switch p { - case Read: - s[0] = 'r' - case Write: - s[1] = 'w' - case Execute: - s[2] = 'x' - } - } - return string(s) -} diff --git a/system/acl/perms_test.go b/system/acl/perms_test.go deleted file mode 100644 index 16b282a7..00000000 --- a/system/acl/perms_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package acl_test - -import ( - "testing" - - "hakurei.app/system/acl" -) - -func TestPerms(t *testing.T) { - testCases := []struct { - name string - perms acl.Perms - }{ - {"---", acl.Perms{}}, - {"r--", acl.Perms{acl.Read}}, - {"-w-", acl.Perms{acl.Write}}, - {"--x", acl.Perms{acl.Execute}}, - {"rw-", acl.Perms{acl.Read, acl.Read, acl.Write}}, - {"r-x", acl.Perms{acl.Read, acl.Execute, acl.Execute}}, - {"-wx", acl.Perms{acl.Write, acl.Write, acl.Execute, acl.Execute}}, - {"rwx", acl.Perms{acl.Read, acl.Write, acl.Execute}}, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - if got := tc.perms.String(); got != tc.name { - t.Errorf("String: %q, want %q", got, tc.name) - } - }) - } -} |
