aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/system
diff options
context:
space:
mode:
Diffstat (limited to 'internal/system')
-rw-r--r--internal/system/acl.go17
-rw-r--r--internal/system/acl_test.go100
-rw-r--r--internal/system/dispatcher.go6
-rw-r--r--internal/system/dispatcher_test.go19
-rw-r--r--internal/system/mkdir.go40
-rw-r--r--internal/system/mkdir_test.go86
-rw-r--r--internal/system/pipewire.go2
-rw-r--r--internal/system/pipewire_test.go2
-rw-r--r--internal/system/system.go53
-rw-r--r--internal/system/system_test.go74
-rw-r--r--internal/system/wayland.go2
-rw-r--r--internal/system/wayland_test.go4
12 files changed, 242 insertions, 163 deletions
diff --git a/internal/system/acl.go b/internal/system/acl.go
index e91f4eb7..7d34fa62 100644
--- a/internal/system/acl.go
+++ b/internal/system/acl.go
@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"os"
- "slices"
"hakurei.app/check"
"hakurei.app/hst"
@@ -12,14 +11,14 @@ import (
)
// UpdatePerm calls UpdatePermType with the [Process] criteria.
-func (sys *I) UpdatePerm(path *check.Absolute, perms ...acl.Perm) *I {
- sys.UpdatePermType(Process, path, perms...)
+func (sys *I) UpdatePerm(path *check.Absolute, perms acl.Perm) *I {
+ sys.UpdatePermType(Process, path, perms)
return sys
}
// UpdatePermType maintains [acl.Perms] on a file until its [hst.Enablements] is
// no longer satisfied.
-func (sys *I) UpdatePermType(et hst.Enablements, path *check.Absolute, perms ...acl.Perm) *I {
+func (sys *I) UpdatePermType(et hst.Enablements, path *check.Absolute, perms acl.Perm) *I {
sys.ops = append(sys.ops, &aclUpdateOp{et, path.String(), perms})
return sys
}
@@ -28,20 +27,20 @@ func (sys *I) UpdatePermType(et hst.Enablements, path *check.Absolute, perms ...
type aclUpdateOp struct {
et hst.Enablements
path string
- perms acl.Perms
+ perms acl.Perm
}
func (a *aclUpdateOp) Type() hst.Enablements { return a.et }
func (a *aclUpdateOp) apply(sys *I) error {
sys.msg.Verbose("applying ACL", a)
- return newOpError("acl", sys.aclUpdate(a.path, sys.uid, a.perms...), false)
+ return newOpError("acl", sys.aclUpdate(a.path, sys.uid, a.perms), false)
}
func (a *aclUpdateOp) revert(sys *I, ec *Criteria) error {
if ec.hasType(a.Type()) {
sys.msg.Verbose("stripping ACL", a)
- err := sys.aclUpdate(a.path, sys.uid)
+ err := sys.aclUpdate(a.path, sys.uid, 0)
if errors.Is(err, os.ErrNotExist) {
// the ACL is effectively stripped if the file no longer exists
sys.msg.Verbosef("target of ACL %s no longer exists", a)
@@ -57,9 +56,7 @@ func (a *aclUpdateOp) revert(sys *I, ec *Criteria) error {
func (a *aclUpdateOp) Is(o Op) bool {
target, ok := o.(*aclUpdateOp)
return ok && a != nil && target != nil &&
- a.et == target.et &&
- a.path == target.path &&
- slices.Equal(a.perms, target.perms)
+ *a == *target
}
func (a *aclUpdateOp) Path() string { return a.path }
diff --git a/internal/system/acl_test.go b/internal/system/acl_test.go
index 01f9d9b1..a340e234 100644
--- a/internal/system/acl_test.go
+++ b/internal/system/acl_test.go
@@ -15,45 +15,45 @@ func TestACLUpdateOp(t *testing.T) {
checkOpBehaviour(t, 0, []opBehaviourTestCase{
{"apply aclUpdate", 0xbeef, 0xff,
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(1)),
+ &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, stub.UniqueError(1)),
}, &OpError{Op: "acl", Err: stub.UniqueError(1)}, nil, nil},
{"revert aclUpdate", 0xbeef, 0xff,
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, ([]acl.Perm)(nil)}, nil, stub.UniqueError(0)),
+ call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Perm(0)}, nil, stub.UniqueError(0)),
}, &OpError{Op: "acl", Err: stub.UniqueError(0), Revert: true}},
{"success revert skip", 0xbeef, Process,
- &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ &aclUpdateOp{User, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{User, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"skipping ACL", &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"skipping ACL", &aclUpdateOp{User, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
}, nil},
{"success revert aclUpdate ENOENT", 0xbeef, 0xff,
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, ([]acl.Perm)(nil)}, nil, &os.PathError{Op: "acl_get_file", Path: "/proc/nonexistent", Err: syscall.ENOENT}),
- call("verbosef", stub.ExpectArgs{"target of ACL %s no longer exists", []any{&aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Perm(0)}, nil, &os.PathError{Op: "acl_get_file", Path: "/proc/nonexistent", Err: syscall.ENOENT}),
+ call("verbosef", stub.ExpectArgs{"target of ACL %s no longer exists", []any{&aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
}, nil},
{"success", 0xbeef, 0xff,
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"applying ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{acl.Read, acl.Write, acl.Execute}}}}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, ([]acl.Perm)(nil)}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"stripping ACL", &aclUpdateOp{Process, "/proc/nonexistent", acl.Read | acl.Write | acl.Execute}}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/proc/nonexistent", 0xbeef, acl.Perm(0)}, nil, nil),
}, nil},
})
@@ -63,28 +63,28 @@ func TestACLUpdateOp(t *testing.T) {
func(_ *testing.T, sys *I) {
sys.
UpdatePerm(m("/run/user/1971/hakurei"), acl.Execute).
- UpdatePerm(m("/tmp/hakurei.0/tmpdir/150"), acl.Read, acl.Write, acl.Execute)
+ UpdatePerm(m("/tmp/hakurei.0/tmpdir/150"), acl.Read|acl.Write|acl.Execute)
}, []Op{
- &aclUpdateOp{Process, "/run/user/1971/hakurei", []acl.Perm{acl.Execute}},
- &aclUpdateOp{Process, "/tmp/hakurei.0/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
+ &aclUpdateOp{Process, "/run/user/1971/hakurei", acl.Execute},
+ &aclUpdateOp{Process, "/tmp/hakurei.0/tmpdir/150", acl.Read | acl.Write | acl.Execute},
}, stub.Expect{}},
{"tmpdirp", 0xbeef, func(_ *testing.T, sys *I) {
sys.UpdatePermType(User, m("/tmp/hakurei.0/tmpdir"), acl.Execute)
}, []Op{
- &aclUpdateOp{User, "/tmp/hakurei.0/tmpdir", []acl.Perm{acl.Execute}},
+ &aclUpdateOp{User, "/tmp/hakurei.0/tmpdir", acl.Execute},
}, stub.Expect{}},
{"tmpdir", 0xbeef, func(_ *testing.T, sys *I) {
- sys.UpdatePermType(User, m("/tmp/hakurei.0/tmpdir/150"), acl.Read, acl.Write, acl.Execute)
+ sys.UpdatePermType(User, m("/tmp/hakurei.0/tmpdir/150"), acl.Read|acl.Write|acl.Execute)
}, []Op{
- &aclUpdateOp{User, "/tmp/hakurei.0/tmpdir/150", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
+ &aclUpdateOp{User, "/tmp/hakurei.0/tmpdir/150", acl.Read | acl.Write | acl.Execute},
}, stub.Expect{}},
{"share", 0xbeef, func(_ *testing.T, sys *I) {
sys.UpdatePermType(Process, m("/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5"), acl.Execute)
}, []Op{
- &aclUpdateOp{Process, "/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5", []acl.Perm{acl.Execute}},
+ &aclUpdateOp{Process, "/run/user/1971/hakurei/fcb8a12f7c482d183ade8288c3de78b5", acl.Execute},
}, stub.Expect{}},
{"passwd", 0xbeef, func(_ *testing.T, sys *I) {
@@ -92,14 +92,14 @@ func TestACLUpdateOp(t *testing.T) {
UpdatePermType(Process, m("/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/passwd"), acl.Read).
UpdatePermType(Process, m("/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/group"), acl.Read)
}, []Op{
- &aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/passwd", []acl.Perm{acl.Read}},
- &aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/group", []acl.Perm{acl.Read}},
+ &aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/passwd", acl.Read},
+ &aclUpdateOp{Process, "/tmp/hakurei.0/fcb8a12f7c482d183ade8288c3de78b5/group", acl.Read},
}, stub.Expect{}},
{"wayland", 0xbeef, func(_ *testing.T, sys *I) {
- sys.UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read, acl.Write, acl.Execute)
+ sys.UpdatePermType(hst.EWayland, m("/run/user/1971/wayland-0"), acl.Read|acl.Write|acl.Execute)
}, []Op{
- &aclUpdateOp{hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
+ &aclUpdateOp{hst.EWayland, "/run/user/1971/wayland-0", acl.Read | acl.Write | acl.Execute},
}, stub.Expect{}},
})
@@ -110,75 +110,75 @@ func TestACLUpdateOp(t *testing.T) {
{"et differs",
&aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, &aclUpdateOp{
hst.EX11, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, false},
{"path differs", &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-1",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, false},
{"perms differs", &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write},
+ acl.Read | acl.Write,
}, false},
{"equals", &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, &aclUpdateOp{
hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
+ acl.Read | acl.Write | acl.Execute,
}, true},
})
checkOpMeta(t, []opMetaTestCase{
{"clear",
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{}},
+ &aclUpdateOp{Process, "/proc/nonexistent", 0},
Process, "/proc/nonexistent",
`--- type: process path: "/proc/nonexistent"`},
{"read",
- &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0", []acl.Perm{acl.Read}},
+ &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0", acl.Read},
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0",
`r-- type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0"`},
{"write",
- &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1", []acl.Perm{acl.Write}},
+ &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1", acl.Write},
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1",
`-w- type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/1"`},
{"execute",
- &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2", []acl.Perm{acl.Execute}},
+ &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2", acl.Execute},
User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2",
`--x type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2"`},
{"wayland",
- &aclUpdateOp{hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}},
+ &aclUpdateOp{hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", acl.Read | acl.Write},
hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland",
`rw- type: wayland path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland"`},
{"x11",
- &aclUpdateOp{hst.EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}},
+ &aclUpdateOp{hst.EX11, "/tmp/.X11-unix/X0", acl.Read | acl.Execute},
hst.EX11, "/tmp/.X11-unix/X0",
`r-x type: x11 path: "/tmp/.X11-unix/X0"`},
{"dbus",
- &aclUpdateOp{hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}},
+ &aclUpdateOp{hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", acl.Write | acl.Execute},
hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus",
`-wx type: dbus path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus"`},
{"pulseaudio",
- &aclUpdateOp{hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
+ &aclUpdateOp{hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", acl.Read | acl.Write | acl.Execute},
hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse",
`rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`},
})
diff --git a/internal/system/dispatcher.go b/internal/system/dispatcher.go
index 8f70b0c0..5a222176 100644
--- a/internal/system/dispatcher.go
+++ b/internal/system/dispatcher.go
@@ -50,7 +50,7 @@ type syscallDispatcher interface {
println(v ...any)
// aclUpdate provides [acl.Update].
- aclUpdate(name string, uid int, perms ...acl.Perm) error
+ aclUpdate(name string, uid int, perms acl.Perm) error
// waylandNew provides [wayland.New].
waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (io.Closer, error)
@@ -85,8 +85,8 @@ func (k direct) remove(name string) error { return os.Remove(na
func (k direct) println(v ...any) { log.Println(v...) }
-func (k direct) aclUpdate(name string, uid int, perms ...acl.Perm) error {
- return acl.Update(name, uid, perms...)
+func (k direct) aclUpdate(name string, uid int, perms acl.Perm) error {
+ return acl.Update(name, uid, perms)
}
func (k direct) waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (io.Closer, error) {
diff --git a/internal/system/dispatcher_test.go b/internal/system/dispatcher_test.go
index 3bf14e75..bfb8df72 100644
--- a/internal/system/dispatcher_test.go
+++ b/internal/system/dispatcher_test.go
@@ -2,11 +2,13 @@ package system
import (
"io"
+ "io/fs"
"log"
"os"
"reflect"
"slices"
"testing"
+ "time"
"unsafe"
"hakurei.app/check"
@@ -206,7 +208,7 @@ func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
// InternalNew initialises [I] with a stub syscallDispatcher.
func InternalNew(t *testing.T, want stub.Expect, uid int) (*I, *stub.Stub[syscallDispatcher]) {
k := &kstub{stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, want)}
- sys := New(t.Context(), k, uid)
+ sys := New(t.Context(), k, uid, nil)
sys.syscallDispatcher = k
return sys, k.Stub
}
@@ -215,6 +217,17 @@ type kstub struct{ *stub.Stub[syscallDispatcher] }
func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }
+// modeFi implements the Mode method of [os.FileInfo].
+type modeFi os.FileMode
+
+func (modeFi) Name() string { panic("attempting to call Name") }
+func (modeFi) Size() int64 { panic("attempting to call Size") }
+func (modeFi) ModTime() time.Time { panic("attempting to call ModTime") }
+func (modeFi) Sys() any { panic("attempting to call Sys") }
+
+func (fi modeFi) Mode() fs.FileMode { return fs.FileMode(fi) }
+func (fi modeFi) IsDir() bool { return fs.FileMode(fi).IsDir() }
+
func (k *kstub) stat(name string) (fi os.FileInfo, err error) {
k.Helper()
expect := k.Expects("stat")
@@ -280,7 +293,7 @@ func (k *kstub) println(v ...any) {
}
}
-func (k *kstub) aclUpdate(name string, uid int, perms ...acl.Perm) error {
+func (k *kstub) aclUpdate(name string, uid int, perms acl.Perm) error {
k.Helper()
expect := k.Expects("aclUpdate")
@@ -293,7 +306,7 @@ func (k *kstub) aclUpdate(name string, uid int, perms ...acl.Perm) error {
return expect.Error(
stub.CheckArgReflect(k.Stub, "name", nameVal, 0),
stub.CheckArg(k.Stub, "uid", uid, 1),
- stub.CheckArgReflect(k.Stub, "perms", perms, 2))
+ stub.CheckArg(k.Stub, "perms", perms, 2))
}
func (k *kstub) waylandNew(displayPath, bindPath *check.Absolute, appID, instanceID string) (io.Closer, error) {
diff --git a/internal/system/mkdir.go b/internal/system/mkdir.go
index 81b89d58..ecbfc876 100644
--- a/internal/system/mkdir.go
+++ b/internal/system/mkdir.go
@@ -4,21 +4,33 @@ import (
"errors"
"fmt"
"os"
+ "syscall"
"hakurei.app/check"
"hakurei.app/hst"
)
// Ensure ensures the existence of a directory.
-func (sys *I) Ensure(name *check.Absolute, perm os.FileMode) *I {
- sys.ops = append(sys.ops, &mkdirOp{User, name.String(), perm, false})
+func (sys *I) Ensure(name *check.Absolute, perm os.FileMode, skipChmod bool) *I {
+ sys.ops = append(sys.ops, &mkdirOp{
+ et: User,
+ path: name.String(),
+ perm: perm,
+ skipChmod: skipChmod,
+ })
return sys
}
// Ephemeral ensures the existence of a directory until its [hst.Enablements] is
// no longer satisfied.
-func (sys *I) Ephemeral(et hst.Enablements, name *check.Absolute, perm os.FileMode) *I {
- sys.ops = append(sys.ops, &mkdirOp{et, name.String(), perm, true})
+func (sys *I) Ephemeral(et hst.Enablements, name *check.Absolute, perm os.FileMode, skipChmod bool) *I {
+ sys.ops = append(sys.ops, &mkdirOp{
+ et: et,
+ path: name.String(),
+ perm: perm,
+ ephemeral: true,
+ skipChmod: skipChmod,
+ })
return sys
}
@@ -28,6 +40,7 @@ type mkdirOp struct {
path string
perm os.FileMode
ephemeral bool
+ skipChmod bool
}
func (m *mkdirOp) Type() hst.Enablements { return m.et }
@@ -35,12 +48,21 @@ func (m *mkdirOp) Type() hst.Enablements { return m.et }
func (m *mkdirOp) apply(sys *I) error {
sys.msg.Verbose("ensuring directory", m)
- if err := sys.mkdir(m.path, m.perm); err != nil {
- if !errors.Is(err, os.ErrExist) {
- return newOpError("mkdir", err, false)
+ if fi, err := sys.stat(m.path); err == nil {
+ if !fi.IsDir() {
+ return newOpError("mkdir", syscall.ENOTDIR, false)
+ }
+ if !m.skipChmod && fi.Mode().Perm() != m.perm {
+ // directory exists, ensure mode
+ return newOpError("mkdir", sys.chmod(m.path, m.perm), false)
}
- // directory exists, ensure mode
- return newOpError("mkdir", sys.chmod(m.path, m.perm), false)
+ return nil
+ } else if !errors.Is(err, os.ErrNotExist) {
+ return newOpError("mkdir", err, false)
+ }
+
+ if err := sys.mkdir(m.path, m.perm); err != nil {
+ return newOpError("mkdir", err, false)
} else {
return nil
}
diff --git a/internal/system/mkdir_test.go b/internal/system/mkdir_test.go
index f226a48d..ea79d5b0 100644
--- a/internal/system/mkdir_test.go
+++ b/internal/system/mkdir_test.go
@@ -2,6 +2,7 @@ package system
import (
"os"
+ "syscall"
"testing"
"hakurei.app/internal/stub"
@@ -11,63 +12,78 @@ func TestMkdirOp(t *testing.T) {
t.Parallel()
checkOpBehaviour(t, 0, []opBehaviourTestCase{
- {"mkdir", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil),
+ {"mkdir", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, stub.UniqueError(2)),
}, &OpError{Op: "mkdir", Err: stub.UniqueError(2)}, nil, nil},
- {"chmod", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil),
- call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, os.ErrExist),
+ {"not directory", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, modeFi(0700), nil),
+ }, &OpError{Op: "mkdir", Err: syscall.ENOTDIR}, nil, nil},
+
+ {"chmod", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, modeFi(os.ModeDir|0700), nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, stub.UniqueError(1)),
}, &OpError{Op: "mkdir", Err: stub.UniqueError(1)}, nil, nil},
- {"remove", 0xbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ {"remove", 0xbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(0)),
}, &OpError{Op: "mkdir", Err: stub.UniqueError(0), Revert: true}},
- {"success exist chmod", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil),
- call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, os.ErrExist),
+ {"success exist chmod", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, modeFi(os.ModeDir|0700), nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
}, nil, nil, nil},
- {"success ensure", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false}}}, nil, nil),
+ {"success exist", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, modeFi(os.ModeDir|0711), nil),
+ }, nil, nil, nil},
+
+ {"success ensure", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, false, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
}, nil, nil, nil},
- {"success skip", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ {"success skip", 0xbeef, 0xff, &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"skipping ephemeral directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"skipping ephemeral directory", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
}, nil},
- {"success", 0xbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ {"success", 0xbeef, 0xff, &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}, []stub.Call{
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
}, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
}, nil},
})
checkOpsBuilder(t, "EnsureEphemeral", []opsBuilderTestCase{
{"ensure", 0xcafe, func(_ *testing.T, sys *I) {
- sys.Ensure(m("/tmp/hakurei.0"), 0700)
+ sys.Ensure(m("/tmp/hakurei.0"), 0700, false)
}, []Op{
- &mkdirOp{User, "/tmp/hakurei.0", 0700, false},
+ &mkdirOp{User, "/tmp/hakurei.0", 0700, false, false},
}, stub.Expect{}},
{"ephemeral", 0xcafe, func(_ *testing.T, sys *I) {
- sys.Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711)
+ sys.Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711, false)
}, []Op{
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false},
}, stub.Expect{}},
})
@@ -76,37 +92,37 @@ func TestMkdirOp(t *testing.T) {
{"zero", new(mkdirOp), new(mkdirOp), true},
{"ephemeral differs",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false, false},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
false},
{"perm differs",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0701, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0701, true, false},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
false},
{"path differs",
- &mkdirOp{Process, "/tmp/hakurei.1/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ &mkdirOp{Process, "/tmp/hakurei.1/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
false},
{"et differs",
- &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
false},
{"equals",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
+ &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
true},
})
checkOpMeta(t, []opMetaTestCase{
- {"ensure", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false},
+ {"ensure", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false, false},
User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9",
`mode: -rwx------ type: ensure path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`},
- {"ephemeral", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
+ {"ephemeral", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true, false},
User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9",
`mode: -rwx------ type: user path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`},
})
diff --git a/internal/system/pipewire.go b/internal/system/pipewire.go
index c2d7b396..427013a4 100644
--- a/internal/system/pipewire.go
+++ b/internal/system/pipewire.go
@@ -73,7 +73,7 @@ func (p *pipewireOp) apply(sys *I) (err error) {
return newOpError("pipewire", err, false)
}
- if err = sys.aclUpdate(p.dst.String(), sys.uid, acl.Read, acl.Write, acl.Execute); err != nil {
+ if err = sys.aclUpdate(p.dst.String(), sys.uid, acl.Read|acl.Write|acl.Execute); err != nil {
if closeErr := p.scc.Close(); closeErr != nil {
return newOpError("pipewire", errors.Join(err, closeErr), false)
}
diff --git a/internal/system/pipewire_test.go b/internal/system/pipewire_test.go
index f0ca0b34..5953681e 100644
--- a/internal/system/pipewire_test.go
+++ b/internal/system/pipewire_test.go
@@ -423,7 +423,7 @@ func TestPipeWireOp(t *testing.T) {
call("verbosef", stub.ExpectArgs{"pipewire pathname socket on %q", []any{ignoreValue{}}}, nil, nil),
call("chmod", stub.ExpectArgs{ignoreValue{}, os.FileMode(0)}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{ignoreValue{}, 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{ignoreValue{}, 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up pipewire socket on %q", []any{ignoreValue{}}}, nil, nil),
}, nil},
diff --git a/internal/system/system.go b/internal/system/system.go
index 1462f818..340e23ec 100644
--- a/internal/system/system.go
+++ b/internal/system/system.go
@@ -6,7 +6,9 @@ import (
"errors"
"strings"
+ "hakurei.app/check"
"hakurei.app/hst"
+ "hakurei.app/internal/lockedfile"
"hakurei.app/message"
)
@@ -67,14 +69,6 @@ func TypeString(e hst.Enablements) string {
}
}
-// New returns the address of a new [I] targeting uid.
-func New(ctx context.Context, msg message.Msg, uid int) (sys *I) {
- if ctx == nil || msg == nil || uid < 0 {
- panic("invalid call to New")
- }
- return &I{ctx: ctx, msg: msg, uid: uid, syscallDispatcher: direct{}}
-}
-
// An I provides deferred operating system interaction. [I] must not be copied.
// Methods of [I] must not be used concurrently.
type I struct {
@@ -84,15 +78,32 @@ type I struct {
ops []Op
ctx context.Context
- // the behaviour of Commit is only defined for up to one call
- committed bool
- // the behaviour of Revert is only defined for up to one call
- reverted bool
+ // The behaviour of Commit and Revert is only defined for at most one call.
+ committed, reverted bool
+ // Optional Commit and Revert big lock.
+ mu *lockedfile.Mutex
msg message.Msg
syscallDispatcher
}
+// New returns the address of a new [I] targeting uid.
+func New(
+ ctx context.Context,
+ msg message.Msg,
+ uid int,
+ lock *check.Absolute,
+) (sys *I) {
+ if ctx == nil || msg == nil || uid < 0 {
+ panic("invalid call to New")
+ }
+ v := I{ctx: ctx, msg: msg, uid: uid, syscallDispatcher: direct{}}
+ if lock != nil {
+ v.mu = lockedfile.MutexAt(lock.String())
+ }
+ return &v
+}
+
func (sys *I) UID() int { return sys.uid }
// Equal returns whether all [Op] instances held by sys matches that of target.
@@ -118,9 +129,18 @@ func (sys *I) Commit() error {
if sys.committed {
panic("attempting to commit twice")
}
+
+ if sys.mu != nil {
+ if unlock, err := sys.mu.Lock(); err != nil {
+ return err
+ } else {
+ defer unlock()
+ }
+ }
sys.committed = true
- sp := New(sys.ctx, sys.msg, sys.uid)
+ sp := New(sys.ctx, sys.msg, sys.uid, nil)
+ // lock is still held on revert
sp.syscallDispatcher = sys.syscallDispatcher
sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
defer func() {
@@ -153,6 +173,13 @@ func (sys *I) Revert(ec *Criteria) error {
if sys.reverted {
panic("attempting to revert twice")
}
+ if sys.mu != nil {
+ if unlock, err := sys.mu.Lock(); err != nil {
+ return err
+ } else {
+ defer unlock()
+ }
+ }
sys.reverted = true
// collect errors
diff --git a/internal/system/system_test.go b/internal/system/system_test.go
index b5dec992..8d863826 100644
--- a/internal/system/system_test.go
+++ b/internal/system/system_test.go
@@ -82,7 +82,7 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want)
}
}()
- New(nil, message.New(nil), 0)
+ New(nil, message.New(nil), 0, nil)
})
t.Run("msg", func(t *testing.T) {
@@ -92,7 +92,7 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want)
}
}()
- New(t.Context(), nil, 0)
+ New(t.Context(), nil, 0, nil)
})
t.Run("uid", func(t *testing.T) {
@@ -102,11 +102,11 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want)
}
}()
- New(t.Context(), message.New(nil), -1)
+ New(t.Context(), message.New(nil), -1, nil)
})
})
- sys := New(t.Context(), message.New(nil), 0xbeef)
+ sys := New(t.Context(), message.New(nil), 0xbeef, nil)
if sys.ctx == nil {
t.Error("New: ctx = nil")
}
@@ -125,53 +125,53 @@ func TestEqual(t *testing.T) {
want bool
}{
{"simple UID",
- New(t.Context(), message.New(nil), 150),
- New(t.Context(), message.New(nil), 150),
+ New(t.Context(), message.New(nil), 150, nil),
+ New(t.Context(), message.New(nil), 150, nil),
true},
{"simple UID differ",
- New(t.Context(), message.New(nil), 150),
- New(t.Context(), message.New(nil), 151),
+ New(t.Context(), message.New(nil), 150, nil),
+ New(t.Context(), message.New(nil), 151, nil),
false},
{"simple UID nil",
- New(t.Context(), message.New(nil), 150),
+ New(t.Context(), message.New(nil), 150, nil),
nil,
false},
{"op length mismatch",
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos"),
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
+ Ensure(m("/run"), 0755, false),
false},
{"op value mismatch",
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0644),
- New(t.Context(), message.New(nil), 150).
+ Ensure(m("/run"), 0644, false),
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
+ Ensure(m("/run"), 0755, false),
false},
{"op type mismatch",
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
Wayland(m("/proc/nonexistent/dst"), m("/proc/nonexistent/src"), "\x00", "\x00"),
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
+ Ensure(m("/run"), 0755, false),
false},
{"op equals",
- New(t.Context(), message.New(nil), 150).
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- New(t.Context(), message.New(nil), 150).
+ Ensure(m("/run"), 0755, false),
+ New(t.Context(), message.New(nil), 150, nil).
ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
+ Ensure(m("/run"), 0755, false),
true},
}
@@ -200,46 +200,49 @@ func TestCommitRevert(t *testing.T) {
}{
{"apply xhost partial mkdir", func(sys *I) {
sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
+ Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711, false).
ChangeHosts("chronos")
}, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(2)),
call("verbosef", stub.ExpectArgs{"commit faulted after %d ops, rolling back partial commit", []any{1}}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(3)),
call("println", stub.ExpectArgs{[]any{"cannot revert mkdir: unique error 3 injected by the test suite"}}, nil, nil),
}, &OpError{Op: "xhost", Err: stub.UniqueError(2)}, nil, nil},
{"apply xhost", func(sys *I) {
sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
+ Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711, false).
ChangeHosts("chronos")
}, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(2)),
call("verbosef", stub.ExpectArgs{"commit faulted after %d ops, rolling back partial commit", []any{1}}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
}, &OpError{Op: "xhost", Err: stub.UniqueError(2)}, nil, nil},
{"revert multi", func(sys *I) {
sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
+ Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711, false).
ChangeHosts("chronos")
}, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"deleting entry %s from X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeDelete), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(1)),
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(0)),
}, errors.Join(
&OpError{Op: "xhost", Err: stub.UniqueError(1), Revert: true},
@@ -247,17 +250,18 @@ func TestCommitRevert(t *testing.T) {
{"success", func(sys *I) {
sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
+ Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711, false).
ChangeHosts("chronos")
}, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
+ call("stat", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, os.ErrNotExist),
call("mkdir", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", os.FileMode(0711)}, nil, nil),
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"deleting entry %s from X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeDelete), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
+ call("verbose", stub.ExpectArgs{[]any{"destroying ephemeral directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true, false}}}, nil, nil),
call("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
}, nil},
}
diff --git a/internal/system/wayland.go b/internal/system/wayland.go
index c22c3fdf..e86c3159 100644
--- a/internal/system/wayland.go
+++ b/internal/system/wayland.go
@@ -42,7 +42,7 @@ func (w *waylandOp) apply(sys *I) (err error) {
return newOpError("wayland", err, false)
}
- if err = sys.aclUpdate(w.dst.String(), sys.uid, acl.Read, acl.Write, acl.Execute); err != nil {
+ if err = sys.aclUpdate(w.dst.String(), sys.uid, acl.Read|acl.Write|acl.Execute); err != nil {
if closeErr := w.ctx.Close(); closeErr != nil {
return newOpError("wayland", errors.Join(err, closeErr), false)
}
diff --git a/internal/system/wayland_test.go b/internal/system/wayland_test.go
index 76e6cc93..a9d29378 100644
--- a/internal/system/wayland_test.go
+++ b/internal/system/wayland_test.go
@@ -32,7 +32,7 @@ func TestWaylandOp(t *testing.T) {
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, stub.UniqueError(2)),
+ call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, stub.UniqueError(2)),
}, &OpError{Op: "wayland", Err: stub.UniqueError(2)}, nil, nil},
{"success", 0xbeef, 0xff, &waylandOp{nil,
@@ -44,7 +44,7 @@ func TestWaylandOp(t *testing.T) {
call("waylandNew", stub.ExpectArgs{m("/run/user/1971/wayland-0"), m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}, nil, nil),
call("verbosef", stub.ExpectArgs{"wayland pathname socket on %q via %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/1971/wayland-0")}}, nil, nil),
call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, nil),
- call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, []acl.Perm{acl.Read, acl.Write, acl.Execute}}, nil, nil),
+ call("aclUpdate", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", 0xbeef, acl.Read | acl.Write | acl.Execute}, nil, nil),
}, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"hanging up wayland socket on %q", []any{m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland")}}, nil, nil),
}, nil},