diff --git a/system/acl/acl_getfacl_test.go b/system/acl/acl_getfacl_test.go deleted file mode 100644 index c20fade..0000000 --- a/system/acl/acl_getfacl_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package acl_test - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "os/exec" - "strconv" -) - -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 -} diff --git a/system/acl/acl_test.go b/system/acl/acl_test.go index 250dcd2..c6c3e56 100644 --- a/system/acl/acl_test.go +++ b/system/acl/acl_test.go @@ -1,10 +1,16 @@ package acl_test import ( + "bufio" + "bytes" "errors" + "fmt" + "io" "os" + "os/exec" "path" "reflect" + "strconv" "testing" "hakurei.app/system/acl" @@ -17,7 +23,7 @@ var ( cred = int32(os.Geteuid()) ) -func TestUpdatePerm(t *testing.T) { +func TestUpdate(t *testing.T) { if os.Getenv("GO_TEST_SKIP_ACL") == "1" { t.Log("acl test skipped") t.SkipNow() @@ -48,19 +54,19 @@ func TestUpdatePerm(t *testing.T) { t.Run("default clear mask", func(t *testing.T) { if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("UpdatePerm: error = %v", err) + t.Fatalf("Update: error = %v", err) } if cur = getfacl(t, testFilePath); len(cur) != 4 { - t.Fatalf("UpdatePerm: %v", cur) + t.Fatalf("Update: %v", cur) } }) t.Run("default clear consistency", func(t *testing.T) { if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("UpdatePerm: error = %v", err) + t.Fatalf("Update: error = %v", err) } if val := getfacl(t, testFilePath); !reflect.DeepEqual(val, cur) { - t.Fatalf("UpdatePerm: %v, want %v", val, cur) + t.Fatalf("Update: %v, want %v", val, cur) } }) @@ -77,26 +83,171 @@ func testUpdate(t *testing.T, testFilePath, name string, cur []*getFAclResp, val t.Run(name, func(t *testing.T) { t.Cleanup(func() { if err := acl.Update(testFilePath, uid); err != nil { - t.Fatalf("UpdatePerm: error = %v", err) + t.Fatalf("Update: error = %v", err) } if v := getfacl(t, testFilePath); !reflect.DeepEqual(v, cur) { - t.Fatalf("UpdatePerm: %v, want %v", v, cur) + t.Fatalf("Update: %v, want %v", v, cur) } }) if err := acl.Update(testFilePath, uid, perms...); err != nil { - t.Fatalf("UpdatePerm: error = %v", err) + t.Fatalf("Update: error = %v", err) } r := respByCred(getfacl(t, testFilePath), fAclTypeUser, cred) if r == nil { - t.Fatalf("UpdatePerm did not add an ACL entry") + t.Fatalf("Update did not add an ACL entry") } if !r.equals(fAclTypeUser, cred, val) { - t.Fatalf("UpdatePerm(%s) = %s", name, r) + 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 { diff --git a/system/acl/perms_test.go b/system/acl/perms_test.go new file mode 100644 index 0000000..16b282a --- /dev/null +++ b/system/acl/perms_test.go @@ -0,0 +1,30 @@ +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) + } + }) + } +}