diff options
Diffstat (limited to 'internal/acl')
| -rw-r--r-- | internal/acl/acl.go | 99 | ||||
| -rw-r--r-- | internal/acl/acl_test.go | 200 | ||||
| -rw-r--r-- | internal/acl/libacl-helper.c | 175 | ||||
| -rw-r--r-- | internal/acl/libacl-helper.go | 40 | ||||
| -rw-r--r-- | internal/acl/libacl-helper.h | 21 | ||||
| -rw-r--r-- | internal/acl/libacl-helper_test.go | 47 | ||||
| -rw-r--r-- | internal/acl/perms.go | 26 | ||||
| -rw-r--r-- | internal/acl/perms_test.go | 20 |
8 files changed, 387 insertions, 241 deletions
diff --git a/internal/acl/acl.go b/internal/acl/acl.go index 366ad533..d5b53095 100644 --- a/internal/acl/acl.go +++ b/internal/acl/acl.go @@ -8,26 +8,103 @@ package acl */ import "C" +import ( + "os" + + "hakurei.app/ext" +) + +// Perm represents acl_perm_t. type Perm C.acl_perm_t const ( - Read Perm = C.ACL_READ - Write Perm = C.ACL_WRITE + // Read represents ACL_READ. + Read Perm = C.ACL_READ + // Write represents ACL_WRITE. + Write Perm = C.ACL_WRITE + // Execute represents ACL_EXECUTE. Execute Perm = C.ACL_EXECUTE ) +// Status is the outcome of an ACL update operation. +type Status = C.enum_hakurei_acl_status + +// An Error describes an error encountered during [Update]. +type Error struct { + // Pathname of the file to update ACL for. + Path string + // Function that returned the error. + Op Status + // Underlying errno. + Err error +} + +// Unwrap returns an [os.PathError] describing e. +func (e Error) Unwrap() error { + v := os.PathError{Path: e.Path, Err: e.Err} + switch e.Op { + case C.HAKUREI_ACL_OK: + return os.ErrInvalid + + case C.HAKUREI_ACL_GET_FILE: + v.Op = "acl_get_file" + case C.HAKUREI_ACL_GET_TAG_TYPE: + v.Op = "acl_get_tag_type" + case C.HAKUREI_ACL_GET_QUALIFIER: + v.Op = "acl_get_qualifier" + case C.HAKUREI_ACL_DELETE_ENTRY: + v.Op = "acl_delete_entry" + case C.HAKUREI_ACL_CREATE_ENTRY: + v.Op = "acl_create_entry" + case C.HAKUREI_ACL_GET_PERMSET: + v.Op = "acl_get_permset" + case C.HAKUREI_ACL_ADD_PERM: + v.Op = "acl_add_perm" + case C.HAKUREI_ACL_SET_TAG_TYPE: + v.Op = "acl_set_tag_type" + case C.HAKUREI_ACL_SET_QUALIFIER: + v.Op = "acl_set_qualifier" + case C.HAKUREI_ACL_CALC_MASK: + v.Op = "acl_calc_mask" + case C.HAKUREI_ACL_VALID: + v.Op = "acl_valid" + case C.HAKUREI_ACL_SET_FILE: + v.Op = "acl_set_file" + case C.HAKUREI_ACL_GET_PERM: + v.Op = "acl_get_perm" + case C.HAKUREI_ACL_DUP: + v.Op = "acl_dup" + + default: // unreachable + v.Op = "setfacl" + } + return &v +} + +// Error returns the message of the resulting [os.PathError]. +func (e Error) Error() string { + return e.Unwrap().Error() +} + // 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] +func Update(name string, uid int, perms Perm) error { + f, _err := os.OpenFile(name, ext.O_PATH, 0) + if _err != nil { + return _err } - r, err := C.hakurei_acl_update_file_by_uid( - C.CString(name), + r, err := C.hakurei_update_acl( + C.int(f.Fd()), C.uid_t(uid), - (*C.acl_perm_t)(p), - C.size_t(len(perms)), + C.acl_perm_t(perms), ) - return newAclPathError(name, int(r), err) + + if _err = f.Close(); _err != nil { + return _err + } + + if r == C.HAKUREI_ACL_OK { + return nil + } + return &Error{name, r, err} } diff --git a/internal/acl/acl_test.go b/internal/acl/acl_test.go index 60c4b702..14f0ca18 100644 --- a/internal/acl/acl_test.go +++ b/internal/acl/acl_test.go @@ -18,93 +18,131 @@ import ( "hakurei.app/internal/info" ) -const testFileName = "acl.test" - var ( uid = os.Geteuid() - cred = int32(os.Geteuid()) + cred = int32(uid) ) +// testFile returns the pathname of a newly created test file. +func testFile(t *testing.T) string { + name := filepath.Join(t.TempDir(), "f") + f, err := os.Create(name) + if err != nil { + t.Fatal(err) + } else if err = f.Close(); err != nil { + t.Fatal(err) + } + return name +} + func TestUpdate(t *testing.T) { if info.CanDegrade { name := filepath.Join(t.TempDir(), "check-degrade") if err := os.WriteFile(name, nil, 0); err != nil { t.Fatal(err) } - if err := acl.Update(name, os.Geteuid()); errors.Is(err, syscall.ENOTSUP) { + if err := acl.Update(name, uid, 0); errors.Is(err, syscall.ENOTSUP) { t.Skip(err) } } - testFilePath := filepath.Join(t.TempDir(), testFileName) + t.Parallel() - 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) - } + pathname := testFile(t) + cur := getfacl(t, pathname) + if len(cur) != 3 { + t.Fatalf("unexpected test file acl length %d", len(cur)) } - defer func() { - if err := os.Remove(testFilePath); err != nil { - t.Fatalf("Remove: error = %v", err) - } - }() - cur := getfacl(t, testFilePath) + if err := acl.Update(pathname, uid, 0); err != nil { + t.Fatalf("(clear) Update: error = %v", err) + } + if cur = getfacl(t, pathname); len(cur) != 4 { + t.Fatalf("(clear) Update: %v", cur) + } - t.Run("default entry count", func(t *testing.T) { - if len(cur) != 3 { - t.Fatalf("unexpected test file acl length %d", len(cur)) - } - }) + if err := acl.Update(pathname, uid, 0); err != nil { + t.Fatalf("(clear*) Update: error = %v", err) + } + if val := getfacl(t, pathname); !reflect.DeepEqual(val, cur) { + t.Fatalf("(clear*) Update: %v, want %v", val, 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) - } - }) + testUpdate(t, "r--", "", cur, acl.Read, true) + testUpdate(t, "-w-", "", cur, acl.Write, true) + testUpdate(t, "--x", "", cur, acl.Execute, true) - 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, "-wx", "", cur, + acl.Write|acl.Execute, + true, + ) + testUpdate( + t, "r-x", "", cur, + acl.Read|acl.Execute, + true, + ) + testUpdate( + t, "rw-", "", cur, + acl.Read|acl.Write, + true, + ) + testUpdate( + t, "rwx", "", cur, + acl.Read|acl.Write|acl.Execute, + true, + ) - 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) + for i := range 16 { + testUpdate( + t, "r-x", " "+strconv.Itoa(i), cur, + acl.Read|acl.Execute, + false, + ) + } + + testUpdate( + t, "rwx", " cleanup", cur, + acl.Read|acl.Write|acl.Execute, + true, + ) } -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) - } - }) +func testUpdate( + t *testing.T, + name, suffix string, + cur []*getFACLResp, + perms acl.Perm, + cleanup bool, +) { + t.Run(name+suffix, func(t *testing.T) { + t.Parallel() - if err := acl.Update(testFilePath, uid, perms...); err != nil { + pathname := testFile(t) + if cleanup { + t.Cleanup(func() { + if err := acl.Update(pathname, uid, 0); err != nil { + t.Fatalf("Update: error = %v", err) + } + if v := getfacl(t, pathname); !reflect.DeepEqual(v, cur) { + t.Fatalf("Update: %v, want %v", v, cur) + } + }) + } + + if err := acl.Update(pathname, uid, perms); err != nil { t.Fatalf("Update: error = %v", err) } - r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred) + r := respByCred(getfacl(t, pathname), fAclTypeUser, cred) if r == nil { t.Fatalf("Update did not add an ACL entry") } - if !r.equals(fAclTypeUser, cred, val) { + + if !r.equals(&getFACLResp{ + typ: fAclTypeUser, + cred: cred, + val: perms, + }) { t.Fatalf("Update(%s) = %s", name, r) } }) @@ -113,31 +151,24 @@ func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val type ( getFAclInvocation struct { cmd *exec.Cmd - val []*getFAclResp + val []*getFACLResp pe []error } - getFAclResp struct { + getFACLResp struct { typ fAclType cred int32 - val fAclPerm + val acl.Perm raw []byte } - fAclPerm uintptr fAclType uint8 ) const fAclBufSize = 16 const ( - fAclPermRead fAclPerm = 1 << iota - fAclPermWrite - fAclPermExecute -) - -const ( fAclTypeUser fAclType = iota fAclTypeGroup fAclTypeMask @@ -149,7 +180,13 @@ func (c *getFAclInvocation) run(name string) error { panic("attempted to run twice") } - c.cmd = exec.Command("getfacl", "--omit-header", "--absolute-names", "--numeric", name) + c.cmd = exec.Command( + "getfacl", + "--omit-header", + "--absolute-names", + "--numeric", + name, + ) c.cmd.Stderr = os.Stderr scanErr := make(chan error, 1) @@ -166,18 +203,17 @@ func (c *getFAclInvocation) run(name string) error { return errors.Join(<-scanErr, c.cmd.Wait()) } -func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { - c.val = make([]*getFAclResp, 0, 4+fAclBufSize) +func (c *getFAclInvocation) parse(r io.Reader, scanErr chan error) { + c.val = make([]*getFACLResp, 0, 4+fAclBufSize) - s := bufio.NewScanner(pipe) + s := bufio.NewScanner(r) for s.Scan() { fields := bytes.SplitN(s.Bytes(), []byte{':'}, 3) if len(fields) != 3 { continue } - resp := getFAclResp{} - + var resp getFACLResp switch string(fields[0]) { case "user": resp.typ = fAclTypeUser @@ -213,7 +249,7 @@ func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { } else { switch fields[2][0] { case 'r': - resp.val |= fAclPermRead + resp.val |= acl.Read case '-': default: c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][0])) @@ -221,7 +257,7 @@ func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { } switch fields[2][1] { case 'w': - resp.val |= fAclPermWrite + resp.val |= acl.Write case '-': default: c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][1])) @@ -229,7 +265,7 @@ func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { } switch fields[2][2] { case 'x': - resp.val |= fAclPermExecute + resp.val |= acl.Execute case '-': default: c.pe = append(c.pe, fmt.Errorf("invalid perm %v", fields[2][2])) @@ -244,7 +280,7 @@ func (c *getFAclInvocation) parse(pipe io.Reader, scanErr chan error) { scanErr <- s.Err() } -func (r *getFAclResp) String() string { +func (r *getFACLResp) String() string { if r.raw != nil && len(r.raw) > 0 { return string(r.raw) } @@ -252,11 +288,11 @@ func (r *getFAclResp) String() string { 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 (r *getFACLResp) equals(v *getFACLResp) bool { + return r.typ == v.typ && r.cred == v.cred && r.val == v.val } -func getfacl(t *testing.T, name string) []*getFAclResp { +func getfacl(t *testing.T, name string) []*getFACLResp { c := new(getFAclInvocation) if err := c.run(name); err != nil { t.Fatalf("getfacl: error = %v", err) @@ -267,7 +303,7 @@ func getfacl(t *testing.T, name string) []*getFAclResp { return c.val } -func respByCred(v []*getFAclResp, typ fAclType, cred int32) *getFAclResp { +func respByCred(v []*getFACLResp, typ fAclType, cred int32) *getFACLResp { j := -1 for i, r := range v { if r.typ == typ && r.cred == cred { diff --git a/internal/acl/libacl-helper.c b/internal/acl/libacl-helper.c index 259dfb2b..c56e6436 100644 --- a/internal/acl/libacl-helper.c +++ b/internal/acl/libacl-helper.c @@ -1,90 +1,133 @@ +#define _GNU_SOURCE /* Definition of AT_* constants */ + #include "libacl-helper.h" #include <acl/libacl.h> #include <stdbool.h> #include <stdlib.h> +#include <fcntl.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; +enum hakurei_acl_status hakurei_update_acl(int fd, uid_t uid, acl_perm_t perms) +{ + enum hakurei_acl_status ret; + + bool v; + int i; + acl_t acl, acl_d = NULL; + acl_entry_t entry; + acl_tag_t tag_type; + void *qualifier_p; + acl_permset_t permset; + acl_perm_t has_perms; + + ret = HAKUREI_ACL_GET_FILE; + acl = acl_get_file_at(fd, "", AT_EMPTY_PATH, 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 = HAKUREI_ACL_GET_TAG_TYPE; + if (acl_get_tag_type(entry, &tag_type) != 0) + goto out; + if (tag_type != ACL_USER) + continue; + + ret = HAKUREI_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 = HAKUREI_ACL_GET_PERMSET; + if (acl_get_permset(entry, &permset) != 0) + goto out; + + if (perms == 0) + goto delete; - ret = -1; /* acl_get_file */ - acl = acl_get_file(path_p, ACL_TYPE_ACCESS); - if (acl == NULL) - goto out; + /* this is quite ugly, but the standard makes it impossible to + * directly obtain these bits */ + has_perms = 0; +#define COPY_PERM_BIT(perm) \ + do { \ + ret = HAKUREI_ACL_GET_PERM; \ + i = acl_get_perm(permset, perm); \ + if (i < 0) \ + goto out; \ + if (i > 0) \ + has_perms |= perm; \ + } while (0) + COPY_PERM_BIT(ACL_READ); + COPY_PERM_BIT(ACL_WRITE); + COPY_PERM_BIT(ACL_EXECUTE); - /* 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; + if (perms == has_perms) { + ret = HAKUREI_ACL_DUP; + acl_d = acl_dup(acl); + if (acl_d == NULL) + goto out; - 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); + ret = HAKUREI_ACL_CALC_MASK; + if (acl_calc_mask(&acl_d) != 0) + goto out; - if (!v) - continue; + ret = HAKUREI_ACL_OK; + if (acl_cmp(acl, acl_d) == 0) + goto out; + } - ret = -4; /* acl_delete_entry */ - if (acl_delete_entry(acl, entry) != 0) - goto out; - } +delete: + ret = HAKUREI_ACL_DELETE_ENTRY; + if (acl_delete_entry(acl, entry) != 0) + goto out; + } - if (plen == 0) - goto set; + if (perms == 0) + goto set; - ret = -5; /* acl_create_entry */ - if (acl_create_entry(&acl, &entry) != 0) - goto out; + ret = HAKUREI_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 = HAKUREI_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 = HAKUREI_ACL_ADD_PERM; + if (acl_add_perm(permset, perms) != 0) + goto out; - ret = -8; /* acl_set_tag_type */ - if (acl_set_tag_type(entry, ACL_USER) != 0) - goto out; + ret = HAKUREI_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; + ret = HAKUREI_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 = HAKUREI_ACL_CALC_MASK; + if (acl_calc_mask(&acl) != 0) + goto out; - ret = -11; /* acl_valid */ - if (acl_valid(acl) != 0) - goto out; + ret = HAKUREI_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; + ret = HAKUREI_ACL_SET_FILE; + if (acl_set_file_at(fd, "", AT_EMPTY_PATH, ACL_TYPE_ACCESS, acl) == 0) + ret = HAKUREI_ACL_OK; out: - free((void *)path_p); - if (acl != NULL) - acl_free((void *)acl); - return ret; + if (acl != NULL) + acl_free((void *)acl); + if (acl_d != NULL) + acl_free((void *)acl_d); + return ret; } diff --git a/internal/acl/libacl-helper.go b/internal/acl/libacl-helper.go deleted file mode 100644 index 22575296..00000000 --- a/internal/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/internal/acl/libacl-helper.h b/internal/acl/libacl-helper.h index b86eb170..111fac4a 100644 --- a/internal/acl/libacl-helper.h +++ b/internal/acl/libacl-helper.h @@ -1,4 +1,21 @@ #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); +enum hakurei_acl_status { + HAKUREI_ACL_OK, + HAKUREI_ACL_GET_FILE, + HAKUREI_ACL_GET_TAG_TYPE, + HAKUREI_ACL_GET_QUALIFIER, + HAKUREI_ACL_DELETE_ENTRY, + HAKUREI_ACL_CREATE_ENTRY, + HAKUREI_ACL_GET_PERMSET, + HAKUREI_ACL_ADD_PERM, + HAKUREI_ACL_SET_TAG_TYPE, + HAKUREI_ACL_SET_QUALIFIER, + HAKUREI_ACL_CALC_MASK, + HAKUREI_ACL_VALID, + HAKUREI_ACL_SET_FILE, + HAKUREI_ACL_GET_PERM, + HAKUREI_ACL_DUP, +}; + +enum hakurei_acl_status hakurei_update_acl(int fd, uid_t uid, acl_perm_t perms); diff --git a/internal/acl/libacl-helper_test.go b/internal/acl/libacl-helper_test.go index 13d23a3c..05c43639 100644 --- a/internal/acl/libacl-helper_test.go +++ b/internal/acl/libacl-helper_test.go @@ -1,4 +1,4 @@ -package acl +package acl_test import ( "os" @@ -7,9 +7,12 @@ import ( "testing" "hakurei.app/container" + "hakurei.app/internal/acl" ) -func TestNewAclPathError(t *testing.T) { +func TestError(t *testing.T) { + t.Parallel() + testCases := []struct { name string path string @@ -17,43 +20,51 @@ func TestNewAclPathError(t *testing.T) { err error want error }{ - {"nil", container.Nonexistent, 0, syscall.ENOTRECOVERABLE, nil}, + {"nil", container.Nonexistent, 0, syscall.ENOTRECOVERABLE, os.ErrInvalid}, - {"acl_get_file", container.Nonexistent, -1, syscall.ENOTRECOVERABLE, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"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, + {"acl_set_file", container.Nonexistent, 12, syscall.ENOTRECOVERABLE, &os.PathError{Op: "acl_set_file", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, + {"acl_get_perm", container.Nonexistent, 13, syscall.ENOTRECOVERABLE, + &os.PathError{Op: "acl_get_perm", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, + {"acl_dup", container.Nonexistent, 14, syscall.ENOTRECOVERABLE, + &os.PathError{Op: "acl_dup", Path: container.Nonexistent, Err: syscall.ENOTRECOVERABLE}}, - {"acl", container.Nonexistent, -13, syscall.ENOTRECOVERABLE, + {"acl", container.Nonexistent, 15, 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) + err := acl.Error{ + Path: tc.path, + Op: acl.Status(tc.r), + Err: tc.err, + }.Unwrap() if !reflect.DeepEqual(err, tc.want) { - t.Errorf("newAclPathError: %v, want %v", err, tc.want) + t.Errorf("Unwrap: %v, want %v", err, tc.want) } }) } diff --git a/internal/acl/perms.go b/internal/acl/perms.go index fbc99e82..829b134f 100644 --- a/internal/acl/perms.go +++ b/internal/acl/perms.go @@ -1,18 +1,18 @@ package acl -type Perms []Perm +import "unsafe" -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' - } +// String returns a text representation of perms. +func (perms Perm) String() string { + buf := []byte{'-', '-', '-'} + if perms&Read != 0 { + buf[0] = 'r' } - return string(s) + if perms&Write != 0 { + buf[1] = 'w' + } + if perms&Execute != 0 { + buf[2] = 'x' + } + return unsafe.String(unsafe.SliceData(buf), len(buf)) } diff --git a/internal/acl/perms_test.go b/internal/acl/perms_test.go index 3920046d..9fa24dc8 100644 --- a/internal/acl/perms_test.go +++ b/internal/acl/perms_test.go @@ -7,18 +7,20 @@ import ( ) func TestPerms(t *testing.T) { + t.Parallel() + testCases := []struct { name string - perms acl.Perms + perms acl.Perm }{ - {"---", 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}}, + {"---", 0}, + {"r--", acl.Read}, + {"-w-", acl.Write}, + {"--x", acl.Execute}, + {"rw-", acl.Read | acl.Write}, + {"r-x", acl.Read | acl.Execute}, + {"-wx", acl.Write | acl.Execute}, + {"rwx", acl.Read | acl.Write | acl.Execute}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { |
