aboutsummaryrefslogtreecommitdiffhomepage
path: root/system
diff options
context:
space:
mode:
Diffstat (limited to 'system')
-rw-r--r--system/acl.go69
-rw-r--r--system/acl/acl.go33
-rw-r--r--system/acl/acl_test.go276
-rw-r--r--system/acl/deprecated.go25
-rw-r--r--system/acl/libacl-helper.c90
-rw-r--r--system/acl/libacl-helper.go40
-rw-r--r--system/acl/libacl-helper.h4
-rw-r--r--system/acl/libacl-helper_test.go60
-rw-r--r--system/acl/perms.go18
-rw-r--r--system/acl/perms_test.go30
-rw-r--r--system/acl_test.go185
-rw-r--r--system/dbus.go207
-rw-r--r--system/dbus/address.go193
-rw-r--r--system/dbus/address_escape_test.go59
-rw-r--r--system/dbus/address_test.go123
-rw-r--r--system/dbus/config.go71
-rw-r--r--system/dbus/config_test.go124
-rw-r--r--system/dbus/dbus.go69
-rw-r--r--system/dbus/dbus_test.go170
-rw-r--r--system/dbus/deprecated.go115
-rw-r--r--system/dbus/export_test.go15
-rw-r--r--system/dbus/proc.go188
-rw-r--r--system/dbus/proc_test.go11
-rw-r--r--system/dbus/proxy.go105
-rw-r--r--system/dbus/samples_test.go214
-rw-r--r--system/dbus/testdata/dev.vencord.Vesktop.json18
-rw-r--r--system/dbus/testdata/org.chromium.Chromium+.json9
-rw-r--r--system/dbus/testdata/org.chromium.Chromium.json24
-rw-r--r--system/dbus/testdata/uk.gensokyo.CrashTestDummy.json21
-rw-r--r--system/dbus_test.go674
-rw-r--r--system/deprecated.go50
-rw-r--r--system/dispatcher.go89
-rw-r--r--system/dispatcher_test.go379
-rw-r--r--system/internal/xcb/changehosts.go17
-rw-r--r--system/internal/xcb/xcb.go127
-rw-r--r--system/link.go50
-rw-r--r--system/link_test.go87
-rw-r--r--system/mkdir.go76
-rw-r--r--system/mkdir_test.go113
-rw-r--r--system/output.go88
-rw-r--r--system/output_test.go140
-rw-r--r--system/system.go177
-rw-r--r--system/system_test.go331
-rw-r--r--system/wayland.go90
-rw-r--r--system/wayland/conn.go123
-rw-r--r--system/wayland/deprecated.go (renamed from system/wayland/consts.go)16
-rw-r--r--system/wayland/security-context-v1-protocol.c74
-rw-r--r--system/wayland/security-context-v1-protocol.h392
-rw-r--r--system/wayland/wayland-client-helper.c96
-rw-r--r--system/wayland/wayland-client-helper.h4
-rw-r--r--system/wayland/wayland.go36
-rw-r--r--system/wayland_test.go303
-rw-r--r--system/xhost.go38
-rw-r--r--system/xhost_test.go60
54 files changed, 204 insertions, 5992 deletions
diff --git a/system/acl.go b/system/acl.go
deleted file mode 100644
index c89a4780..00000000
--- a/system/acl.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package system
-
-import (
- "errors"
- "fmt"
- "os"
- "slices"
-
- "hakurei.app/container/check"
- "hakurei.app/hst"
- "hakurei.app/system/acl"
-)
-
-// UpdatePerm calls UpdatePermType with the [Process] criteria.
-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 [Enablement] is no longer satisfied.
-func (sys *I) UpdatePermType(et hst.Enablement, path *check.Absolute, perms ...acl.Perm) *I {
- sys.ops = append(sys.ops, &aclUpdateOp{et, path.String(), perms})
- return sys
-}
-
-// aclUpdateOp implements [I.UpdatePermType].
-type aclUpdateOp struct {
- et hst.Enablement
- path string
- perms acl.Perms
-}
-
-func (a *aclUpdateOp) Type() hst.Enablement { 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)
-}
-
-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)
- 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)
- err = nil
- }
- return newOpError("acl", err, true)
- } else {
- sys.msg.Verbose("skipping ACL", a)
- return nil
- }
-}
-
-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)
-}
-
-func (a *aclUpdateOp) Path() string { return a.path }
-
-func (a *aclUpdateOp) String() string {
- return fmt.Sprintf("%s type: %s path: %q",
- a.perms, TypeString(a.et), a.path)
-}
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)
- }
- })
- }
-}
diff --git a/system/acl_test.go b/system/acl_test.go
deleted file mode 100644
index 2202d957..00000000
--- a/system/acl_test.go
+++ /dev/null
@@ -1,185 +0,0 @@
-package system
-
-import (
- "os"
- "syscall"
- "testing"
-
- "hakurei.app/container/stub"
- "hakurei.app/hst"
- "hakurei.app/system/acl"
-)
-
-func TestACLUpdateOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []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)),
- }, &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),
- }, 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)),
- }, &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),
- }, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"skipping ACL", &aclUpdateOp{User, "/proc/nonexistent", []acl.Perm{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),
- }, 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),
- }, 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),
- }, 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),
- }, nil},
- })
-
- checkOpsBuilder(t, "UpdatePermType", []opsBuilderTestCase{
- {"simple",
- 0xbeef,
- 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)
- }, []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}},
- }, 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}},
- }, stub.Expect{}},
-
- {"tmpdir", 0xbeef, func(_ *testing.T, sys *I) {
- 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}},
- }, 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}},
- }, stub.Expect{}},
-
- {"passwd", 0xbeef, func(_ *testing.T, sys *I) {
- sys.
- 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}},
- }, 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)
- }, []Op{
- &aclUpdateOp{hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
- }, stub.Expect{}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"nil", (*aclUpdateOp)(nil), (*aclUpdateOp)(nil), false},
- {"zero", new(aclUpdateOp), new(aclUpdateOp), true},
-
- {"et differs",
- &aclUpdateOp{
- hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write, acl.Execute},
- }, &aclUpdateOp{
- hst.EX11, "/run/user/1971/wayland-0",
- []acl.Perm{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},
- }, &aclUpdateOp{
- hst.EWayland, "/run/user/1971/wayland-1",
- []acl.Perm{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},
- }, &aclUpdateOp{
- hst.EWayland, "/run/user/1971/wayland-0",
- []acl.Perm{acl.Read, acl.Write},
- }, false},
-
- {"equals", &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.Perm{acl.Read, acl.Write, acl.Execute},
- }, true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"clear",
- &aclUpdateOp{Process, "/proc/nonexistent", []acl.Perm{}},
- Process, "/proc/nonexistent",
- `--- type: process path: "/proc/nonexistent"`},
-
- {"read",
- &aclUpdateOp{User, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/0", []acl.Perm{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}},
- 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}},
- 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}},
- 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}},
- 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}},
- 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}},
- hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse",
- `rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`},
- })
-}
diff --git a/system/dbus.go b/system/dbus.go
deleted file mode 100644
index 424f9dfa..00000000
--- a/system/dbus.go
+++ /dev/null
@@ -1,207 +0,0 @@
-package system
-
-import (
- "bytes"
- "context"
- "errors"
- "fmt"
- "log"
- "reflect"
- "strings"
- "sync"
- "syscall"
-
- "hakurei.app/container"
- "hakurei.app/hst"
- "hakurei.app/system/dbus"
-)
-
-// ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil.
-var ErrDBusConfig = errors.New("dbus config not supplied")
-
-// MustProxyDBus calls ProxyDBus and panics if an error is returned.
-func (sys *I) MustProxyDBus(
- session, system *hst.BusConfig,
- sessionBus, systemBus dbus.ProxyPair,
-) *I {
- if err := sys.ProxyDBus(session, system, sessionBus, systemBus); err != nil {
- panic(err.Error())
- } else {
- return sys
- }
-}
-
-// ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via [dbus] and terminates it on revert.
-// This [Op] is always [Process] scoped.
-func (sys *I) ProxyDBus(
- session, system *hst.BusConfig,
- sessionBus, systemBus dbus.ProxyPair,
-) error {
- d := new(dbusProxyOp)
-
- // session bus is required as otherwise this is effectively a very expensive noop
- if session == nil {
- return newOpErrorMessage("dbus", ErrDBusConfig,
- "attempted to create message bus proxy args without session bus config", false)
- }
-
- // system bus is optional
- d.system = system != nil
-
- d.out = &linePrefixWriter{println: log.Println, prefix: "(dbus) ", buf: new(strings.Builder)}
- if final, err := sys.dbusFinalise(sessionBus, systemBus, session, system); err != nil {
- if errors.Is(err, syscall.EINVAL) {
- return newOpErrorMessage("dbus", err,
- "message bus proxy configuration contains NUL byte", false)
- }
- return newOpErrorMessage("dbus", err,
- fmt.Sprintf("cannot finalise message bus proxy: %v", err), false)
- } else {
- if sys.msg.IsVerbose() {
- sys.msg.Verbose("session bus proxy:", dbus.Args(session, sessionBus))
- if system != nil {
- sys.msg.Verbose("system bus proxy:", dbus.Args(system, systemBus))
- }
-
- // this calls the argsWt String method
- sys.msg.Verbose("message bus proxy final args:", final.WriterTo)
- }
-
- d.final = final
- }
-
- sys.ops = append(sys.ops, d)
- return nil
-}
-
-// dbusProxyOp implements [I.ProxyDBus].
-type dbusProxyOp struct {
- proxy *dbus.Proxy // populated during apply
-
- final *dbus.Final
- out *linePrefixWriter
- // whether system bus proxy is enabled
- system bool
-}
-
-func (d *dbusProxyOp) Type() hst.Enablement { return Process }
-
-func (d *dbusProxyOp) apply(sys *I) error {
- sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
- if d.system {
- sys.msg.Verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
- }
-
- d.proxy = dbus.New(sys.ctx, sys.msg, d.final, d.out)
- if err := sys.dbusProxyStart(d.proxy); err != nil {
- d.out.Dump()
- return newOpErrorMessage("dbus", err,
- fmt.Sprintf("cannot start message bus proxy: %v", err), false)
- }
- sys.msg.Verbose("starting message bus proxy", d.proxy)
- return nil
-}
-
-func (d *dbusProxyOp) revert(sys *I, _ *Criteria) error {
- // criteria ignored here since dbus is always process-scoped
- sys.msg.Verbose("terminating message bus proxy")
- sys.dbusProxyClose(d.proxy)
-
- exitMessage := "message bus proxy exit"
- defer func() { sys.msg.Verbose(exitMessage) }()
-
- if d.out != nil {
- d.out.Dump()
- }
-
- err := sys.dbusProxyWait(d.proxy)
- if errors.Is(err, context.Canceled) {
- exitMessage = "message bus proxy canceled upstream"
- err = nil
- }
- return newOpErrorMessage("dbus", err,
- fmt.Sprintf("message bus proxy error: %v", err), true)
-}
-
-func (d *dbusProxyOp) Is(o Op) bool {
- target, ok := o.(*dbusProxyOp)
- return ok && d != nil && target != nil &&
- d.system == target.system &&
- d.final != nil && target.final != nil &&
- d.final.Session == target.final.Session &&
- d.final.System == target.final.System &&
- dbus.EqualAddrEntries(d.final.SessionUpstream, target.final.SessionUpstream) &&
- dbus.EqualAddrEntries(d.final.SystemUpstream, target.final.SystemUpstream) &&
- reflect.DeepEqual(d.final.WriterTo, target.final.WriterTo)
-}
-
-func (d *dbusProxyOp) Path() string { return container.Nonexistent }
-func (d *dbusProxyOp) String() string { return d.proxy.String() }
-
-const (
- // lpwSizeThreshold is the threshold of bytes written to linePrefixWriter which,
- // if reached or exceeded, causes linePrefixWriter to drop all future writes.
- lpwSizeThreshold = 1 << 24
-)
-
-// linePrefixWriter calls println with a prefix for every line written.
-type linePrefixWriter struct {
- prefix string
- println func(v ...any)
-
- n int
- msg []string
- buf *strings.Builder
-
- mu sync.RWMutex
-}
-
-func (s *linePrefixWriter) Write(p []byte) (n int, err error) {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.write(p, 0)
-}
-
-func (s *linePrefixWriter) write(p []byte, a int) (int, error) {
- if s.n >= lpwSizeThreshold {
- if len(p) == 0 {
- return a, nil
- }
- return a, syscall.ENOMEM
- }
-
- if i := bytes.IndexByte(p, '\n'); i == -1 {
- n, _ := s.buf.Write(p)
- s.n += n
- return a + n, nil
- } else {
- n, _ := s.buf.Write(p[:i])
- s.n += n + 1
-
- v := s.buf.String()
- if strings.HasPrefix(v, "init: ") {
- s.n -= len(v) + 1
- // pass through container init messages
- s.println(s.prefix + v)
- } else {
- s.msg = append(s.msg, v)
- }
-
- s.buf.Reset()
- return s.write(p[i+1:], a+n+1)
- }
-}
-
-func (s *linePrefixWriter) Dump() {
- s.mu.RLock()
- for _, m := range s.msg {
- s.println(s.prefix + m)
- }
- if s.buf != nil && s.buf.Len() != 0 {
- s.println("*" + s.prefix + s.buf.String())
- }
- if s.n >= lpwSizeThreshold {
- s.println("+" + s.prefix + "write threshold reached, output may be incomplete")
- }
- s.mu.RUnlock()
-}
diff --git a/system/dbus/address.go b/system/dbus/address.go
deleted file mode 100644
index edee7e20..00000000
--- a/system/dbus/address.go
+++ /dev/null
@@ -1,193 +0,0 @@
-package dbus
-
-import (
- "bytes"
- "encoding/hex"
- "errors"
- "fmt"
- "slices"
-)
-
-type AddrEntry struct {
- Method string `json:"method"`
- Values [][2]string `json:"values"`
-}
-
-// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
-func EqualAddrEntries(entries, target []AddrEntry) bool {
- return slices.EqualFunc(entries, target, func(a AddrEntry, b AddrEntry) bool {
- return a.Method == b.Method && slices.Equal(a.Values, b.Values)
- })
-}
-
-// Parse parses D-Bus address according to
-// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
-func Parse(addr []byte) ([]AddrEntry, error) {
- // Look for a semicolon
- address := bytes.Split(bytes.TrimSuffix(addr, []byte{';'}), []byte{';'})
-
- // Allocate for entries
- v := make([]AddrEntry, len(address))
-
- for i, s := range address {
- var pairs [][]byte
-
- // Look for the colon :
- if method, list, ok := bytes.Cut(s, []byte{':'}); !ok {
- return v, &BadAddressError{ErrNoColon, i, s, -1, nil}
- } else {
- pairs = bytes.Split(list, []byte{','})
- v[i].Method = string(method)
- v[i].Values = make([][2]string, len(pairs))
- }
-
- for j, pair := range pairs {
- key, value, ok := bytes.Cut(pair, []byte{'='})
- if !ok {
- return v, &BadAddressError{ErrBadPairSep, i, s, j, pair}
- }
- if len(key) == 0 {
- return v, &BadAddressError{ErrBadPairKey, i, s, j, pair}
- }
- if len(value) == 0 {
- return v, &BadAddressError{ErrBadPairVal, i, s, j, pair}
- }
- v[i].Values[j][0] = string(key)
-
- if val, errno := unescapeValue(value); errno != errSuccess {
- return v, &BadAddressError{errno, i, s, j, pair}
- } else {
- v[i].Values[j][1] = string(val)
- }
- }
- }
-
- return v, nil
-}
-
-func unescapeValue(v []byte) (val []byte, errno ParseError) {
- if l := len(v) - (bytes.Count(v, []byte{'%'}) * 2); l < 0 {
- errno = ErrBadValLength
- return
- } else {
- val = make([]byte, l)
- }
-
- var i, skip int
- for iu, b := range v {
- if skip > 0 {
- skip--
- continue
- }
-
- if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
- goto opt
- } else if b >= '0' && b <= '9' { // 0-9
- goto opt
- } else if b >= 'A' && b <= 'Z' { // A-Z
- goto opt
- } else if b >= 'a' && b <= 'z' { // a-z
- goto opt
- }
-
- if b != '%' {
- errno = ErrBadValByte
- break
- }
-
- skip += 2
- if iu+2 >= len(v) {
- errno = ErrBadValHexLength
- break
- }
- if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
- if errors.As(err, new(hex.InvalidByteError)) {
- errno = ErrBadValHexByte
- break
- }
- // unreachable
- panic(err.Error())
- } else if c != 1 {
- // unreachable
- panic(fmt.Sprintf("invalid decode length %d", c))
- }
- i++
- continue
-
- opt:
- val[i] = b
- i++
- }
-
- return
-}
-
-type ParseError uint8
-
-func (e ParseError) Error() string {
- switch e {
- case errSuccess:
- panic("attempted to return success as error")
- case ErrNoColon:
- return "address does not contain a colon"
- case ErrBadPairSep:
- return "'=' character not found"
- case ErrBadPairKey:
- return "'=' character has no key preceding it"
- case ErrBadPairVal:
- return "'=' character has no value following it"
- case ErrBadValLength:
- return "unescaped value has impossible length"
- case ErrBadValByte:
- return "in D-Bus address, characters other than [-0-9A-Za-z_/.\\*] should have been escaped"
- case ErrBadValHexLength:
- return "in D-Bus address, percent character was not followed by two hex digits"
- case ErrBadValHexByte:
- return "in D-Bus address, percent character was followed by characters other than hex digits"
-
- default:
- return fmt.Sprintf("parse error %d", e)
- }
-}
-
-const (
- errSuccess ParseError = iota
- ErrNoColon
- ErrBadPairSep
- ErrBadPairKey
- ErrBadPairVal
- ErrBadValLength
- ErrBadValByte
- ErrBadValHexLength
- ErrBadValHexByte
-)
-
-type BadAddressError struct {
- // error type
- Type ParseError
-
- // bad entry position
- EntryPos int
- // bad entry value
- EntryVal []byte
-
- // bad pair position
- PairPos int
- // bad pair value
- PairVal []byte
-}
-
-func (a *BadAddressError) Is(err error) bool {
- var b *BadAddressError
- return errors.As(err, &b) && a.Type == b.Type &&
- a.EntryPos == b.EntryPos && slices.Equal(a.EntryVal, b.EntryVal) &&
- a.PairPos == b.PairPos && slices.Equal(a.PairVal, b.PairVal)
-}
-
-func (a *BadAddressError) Error() string {
- return a.Type.Error()
-}
-
-func (a *BadAddressError) Unwrap() error {
- return a.Type
-}
diff --git a/system/dbus/address_escape_test.go b/system/dbus/address_escape_test.go
deleted file mode 100644
index 4ef48f8b..00000000
--- a/system/dbus/address_escape_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package dbus
-
-import (
- "testing"
-)
-
-func TestUnescapeValue(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- value string
- want string
- wantErr ParseError
- }{
- // upstream test cases
- {value: "abcde", want: "abcde"},
- {value: "", want: ""},
- {value: "%20%20", want: " "},
- {value: "%24", want: "$"},
- {value: "%25", want: "%"},
- {value: "abc%24", want: "abc$"},
- {value: "%24abc", want: "$abc"},
- {value: "abc%24abc", want: "abc$abc"},
- {value: "/", want: "/"},
- {value: "-", want: "-"},
- {value: "_", want: "_"},
- {value: "A", want: "A"},
- {value: "I", want: "I"},
- {value: "Z", want: "Z"},
- {value: "a", want: "a"},
- {value: "i", want: "i"},
- {value: "z", want: "z"},
- /* Bug: https://bugs.freedesktop.org/show_bug.cgi?id=53499 */
- {value: "%c3%b6", want: "\xc3\xb6"},
-
- {value: "%a", wantErr: ErrBadValHexLength},
- {value: "%q", wantErr: ErrBadValHexLength},
- {value: "%az", wantErr: ErrBadValHexByte},
- {value: "%%", wantErr: ErrBadValLength},
- {value: "%$$", wantErr: ErrBadValHexByte},
- {value: "abc%a", wantErr: ErrBadValHexLength},
- {value: "%axyz", wantErr: ErrBadValHexByte},
- {value: "%", wantErr: ErrBadValLength},
- {value: "$", wantErr: ErrBadValByte},
- {value: " ", wantErr: ErrBadValByte},
- }
-
- for _, tc := range testCases {
- t.Run("unescape "+tc.value, func(t *testing.T) {
- t.Parallel()
-
- if got, errno := unescapeValue([]byte(tc.value)); errno != tc.wantErr {
- t.Errorf("unescapeValue() errno = %v, wantErr %v", errno, tc.wantErr)
- } else if tc.wantErr == errSuccess && string(got) != tc.want {
- t.Errorf("unescapeValue() = %q, want %q", got, tc.want)
- }
- })
- }
-}
diff --git a/system/dbus/address_test.go b/system/dbus/address_test.go
deleted file mode 100644
index 5de90535..00000000
--- a/system/dbus/address_test.go
+++ /dev/null
@@ -1,123 +0,0 @@
-package dbus_test
-
-import (
- "errors"
- "reflect"
- "testing"
-
- "hakurei.app/system/dbus"
-)
-
-func TestParse(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- addr string
- want []dbus.AddrEntry
- wantErr error
- }{
- {
- name: "simple session unix",
- addr: "unix:path=/run/user/1971/bus",
- want: []dbus.AddrEntry{{
- Method: "unix",
- Values: [][2]string{{"path", "/run/user/1971/bus"}},
- }},
- },
- {
- name: "simple upper escape",
- addr: "debug:name=Test,cat=cute,escaped=%c3%b6",
- want: []dbus.AddrEntry{{
- Method: "debug",
- Values: [][2]string{
- {"name", "Test"},
- {"cat", "cute"},
- {"escaped", "\xc3\xb6"},
- },
- }},
- },
- {
- name: "simple bad escape",
- addr: "debug:name=%",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadValLength,
- EntryPos: 0, EntryVal: []byte("debug:name=%"), PairPos: 0, PairVal: []byte("name=%")},
- },
-
- // upstream test cases
- {
- name: "full address success",
- addr: "unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
- want: []dbus.AddrEntry{
- {Method: "unix", Values: [][2]string{{"path", "/tmp/foo"}}},
- {Method: "debug", Values: [][2]string{{"name", "test"}, {"sliff", "sloff"}}},
- },
- },
- {
- name: "empty address",
- addr: "",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
- EntryVal: []byte{}, PairPos: -1},
- },
- {
- name: "no body",
- addr: "foo",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
- EntryPos: 0, EntryVal: []byte("foo"), PairPos: -1},
- },
- {
- name: "no pair separator",
- addr: "foo:bar",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
- EntryPos: 0, EntryVal: []byte("foo:bar"), PairPos: 0, PairVal: []byte("bar")},
- },
- {
- name: "no pair separator multi pair",
- addr: "foo:bar,baz",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
- EntryPos: 0, EntryVal: []byte("foo:bar,baz"), PairPos: 0, PairVal: []byte("bar")},
- },
- {
- name: "no pair separator single valid pair",
- addr: "foo:bar=foo,baz",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
- EntryPos: 0, EntryVal: []byte("foo:bar=foo,baz"), PairPos: 1, PairVal: []byte("baz")},
- },
- {
- name: "no body single valid address",
- addr: "foo:bar=foo;baz",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
- EntryPos: 1, EntryVal: []byte("baz"), PairPos: -1},
- },
- {
- name: "no key",
- addr: "foo:=foo",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairKey,
- EntryPos: 0, EntryVal: []byte("foo:=foo"), PairPos: 0, PairVal: []byte("=foo")},
- },
- {
- name: "no value",
- addr: "foo:foo=",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairVal,
- EntryPos: 0, EntryVal: []byte("foo:foo="), PairPos: 0, PairVal: []byte("foo=")},
- },
- {
- name: "no pair separator single valid pair trailing",
- addr: "foo:foo,bar=baz",
- wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
- EntryPos: 0, EntryVal: []byte("foo:foo,bar=baz"), PairPos: 0, PairVal: []byte("foo")},
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- if got, err := dbus.Parse([]byte(tc.addr)); !errors.Is(err, tc.wantErr) {
- t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
- } else if tc.wantErr == nil && !reflect.DeepEqual(got, tc.want) {
- t.Errorf("Parse() = %#v, want %#v", got, tc.want)
- }
- })
- }
-}
diff --git a/system/dbus/config.go b/system/dbus/config.go
deleted file mode 100644
index 2a09ee90..00000000
--- a/system/dbus/config.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package dbus
-
-import (
- "hakurei.app/hst"
-)
-
-// ProxyPair is an upstream dbus address and a downstream socket path.
-type ProxyPair [2]string
-
-// Args returns the xdg-dbus-proxy arguments equivalent of [hst.BusConfig].
-func Args(c *hst.BusConfig, bus ProxyPair) (args []string) {
- argc := 2 + len(c.See) + len(c.Talk) + len(c.Own) + len(c.Call) + len(c.Broadcast)
- if c.Log {
- argc++
- }
- if c.Filter {
- argc++
- }
-
- args = make([]string, 0, argc)
- args = append(args, bus[0], bus[1])
- if c.Filter {
- args = append(args, "--filter")
- }
- for _, name := range c.See {
- args = append(args, "--see="+name)
- }
- for _, name := range c.Talk {
- args = append(args, "--talk="+name)
- }
- for _, name := range c.Own {
- args = append(args, "--own="+name)
- }
- for name, rule := range c.Call {
- args = append(args, "--call="+name+"="+rule)
- }
- for name, rule := range c.Broadcast {
- args = append(args, "--broadcast="+name+"="+rule)
- }
- if c.Log {
- args = append(args, "--log")
- }
-
- return
-}
-
-// NewConfig returns the address of a new [hst.BusConfig] with optional defaults.
-func NewConfig(id string, defaults, mpris bool) *hst.BusConfig {
- c := hst.BusConfig{
- Call: make(map[string]string),
- Broadcast: make(map[string]string),
-
- Filter: true,
- }
-
- if defaults {
- c.Talk = []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"}
-
- c.Call["org.freedesktop.portal.*"] = "*"
- c.Broadcast["org.freedesktop.portal.*"] = "@/org/freedesktop/portal/*"
-
- if id != "" {
- c.Own = []string{id + ".*"}
- if mpris {
- c.Own = append(c.Own, "org.mpris.MediaPlayer2."+id+".*")
- }
- }
- }
-
- return &c
-}
diff --git a/system/dbus/config_test.go b/system/dbus/config_test.go
deleted file mode 100644
index 644c766f..00000000
--- a/system/dbus/config_test.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package dbus_test
-
-import (
- "reflect"
- "slices"
- "strings"
- "testing"
-
- "hakurei.app/hst"
- "hakurei.app/system/dbus"
-)
-
-func TestConfigArgs(t *testing.T) {
- t.Parallel()
-
- for _, tc := range testCasesExt {
- if tc.wantErr {
- // args does not check for nulls
- continue
- }
-
- t.Run("build arguments for "+tc.id, func(t *testing.T) {
- t.Parallel()
-
- if got := dbus.Args(tc.c, tc.bus); !slices.Equal(got, tc.want) {
- t.Errorf("Args: %v, want %v", got, tc.want)
- }
- })
- }
-}
-
-func TestNewConfig(t *testing.T) {
- t.Parallel()
- ids := [...]string{"org.chromium.Chromium", "dev.vencord.Vesktop"}
-
- type newTestCase struct {
- id string
- args [2]bool
- want *hst.BusConfig
- }
-
- // populate tests from IDs in generic tests
- tcs := make([]newTestCase, 0, (len(ids)+1)*4)
- // tests for defaults without id
- tcs = append(tcs,
- newTestCase{"", [2]bool{false, false}, &hst.BusConfig{
- Call: make(map[string]string),
- Broadcast: make(map[string]string),
- Filter: true,
- }},
- newTestCase{"", [2]bool{false, true}, &hst.BusConfig{
- Call: make(map[string]string),
- Broadcast: make(map[string]string),
- Filter: true,
- }},
- newTestCase{"", [2]bool{true, false}, &hst.BusConfig{
- Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Filter: true,
- }},
- newTestCase{"", [2]bool{true, true}, &hst.BusConfig{
- Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Filter: true,
- }},
- )
- for _, id := range ids {
- tcs = append(tcs,
- newTestCase{id, [2]bool{false, false}, &hst.BusConfig{
- Call: make(map[string]string),
- Broadcast: make(map[string]string),
- Filter: true,
- }},
- newTestCase{id, [2]bool{false, true}, &hst.BusConfig{
- Call: make(map[string]string),
- Broadcast: make(map[string]string),
- Filter: true,
- }},
- newTestCase{id, [2]bool{true, false}, &hst.BusConfig{
- Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
- Own: []string{id + ".*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Filter: true,
- }},
- newTestCase{id, [2]bool{true, true}, &hst.BusConfig{
- Talk: []string{"org.freedesktop.DBus", "org.freedesktop.Notifications"},
- Own: []string{id + ".*", "org.mpris.MediaPlayer2." + id + ".*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Filter: true,
- }},
- )
- }
-
- for _, tc := range tcs {
- name := new(strings.Builder)
- name.WriteString("create new configuration struct")
-
- if tc.args[0] {
- name.WriteString(" with builtin defaults")
- if tc.args[1] {
- name.WriteString(" (mpris)")
- }
- }
-
- if tc.id != "" {
- name.WriteString(" for application ID ")
- name.WriteString(tc.id)
- }
-
- t.Run(name.String(), func(t *testing.T) {
- t.Parallel()
-
- if gotC := dbus.NewConfig(tc.id, tc.args[0], tc.args[1]); !reflect.DeepEqual(gotC, tc.want) {
- t.Errorf("NewConfig(%q, %t, %t) = %v, want %v",
- tc.id, tc.args[0], tc.args[1],
- gotC, tc.want)
- }
- })
- }
-}
diff --git a/system/dbus/dbus.go b/system/dbus/dbus.go
deleted file mode 100644
index 9ea89f51..00000000
--- a/system/dbus/dbus.go
+++ /dev/null
@@ -1,69 +0,0 @@
-// Package dbus wraps xdg-dbus-proxy and implements configuration and sandboxing of the underlying helper process.
-package dbus
-
-import (
- "fmt"
- "os"
- "sync"
-)
-
-const (
- /*
- SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
-
- If that variable is not set, applications may also try to read the address from the X Window System root window property _DBUS_SESSION_BUS_ADDRESS.
- The root window property must have type STRING. The environment variable should have precedence over the root window property.
-
- The address of the login session message bus is given in the DBUS_SESSION_BUS_ADDRESS environment variable.
- If DBUS_SESSION_BUS_ADDRESS is not set, or if it's set to the string "autolaunch:",
- the system should use platform-specific methods of locating a running D-Bus session server,
- or starting one if a running instance cannot be found.
- Note that this mechanism is not recommended for attempting to determine if a daemon is running.
- It is inherently racy to attempt to make this determination, since the bus daemon may be started just before or just after the determination is made.
- Therefore, it is recommended that applications do not try to make this determination for their functionality purposes, and instead they should attempt to start the server.
-
- This package diverges from the specification, as the caller is unlikely to be an X client, or be in a position to autolaunch a dbus server.
- So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.
- */
- SessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
-
- /*
- SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
-
- If that variable is not set, applications should try to connect to the well-known address unix:path=/var/run/dbus/system_bus_socket.
- Implementations of the well-known system bus should listen on an address that will result in that connection being successful.
- */
- SystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
-
- // FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
- FallbackSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
-)
-
-var (
- address [2]string
- addressOnce sync.Once
-)
-
-// Address returns the session and system bus addresses copied from environment,
-// or appropriate fallback values if they are not set.
-func Address() (session, system string) {
- addressOnce.Do(func() {
- // resolve upstream session bus address
- if addr, ok := os.LookupEnv(SessionBusAddress); !ok {
- // fall back to default format
- address[0] = fmt.Sprintf("unix:path=/run/user/%d/bus", os.Getuid())
- } else {
- address[0] = addr
- }
-
- // resolve upstream system bus address
- if addr, ok := os.LookupEnv(SystemBusAddress); !ok {
- // fall back to default hardcoded value
- address[1] = FallbackSystemBusAddress
- } else {
- address[1] = addr
- }
- })
-
- return address[0], address[1]
-}
diff --git a/system/dbus/dbus_test.go b/system/dbus/dbus_test.go
deleted file mode 100644
index 3a9af578..00000000
--- a/system/dbus/dbus_test.go
+++ /dev/null
@@ -1,170 +0,0 @@
-package dbus_test
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "hakurei.app/internal/helper"
- "hakurei.app/message"
- "hakurei.app/system/dbus"
-)
-
-func TestFinalise(t *testing.T) {
- if _, err := dbus.Finalise(dbus.ProxyPair{}, dbus.ProxyPair{}, nil, nil); !errors.Is(err, syscall.EBADE) {
- t.Errorf("Finalise: error = %v, want %v",
- err, syscall.EBADE)
- }
-
- for id, tc := range testCasePairs {
- t.Run("create final for "+id, func(t *testing.T) {
- var wt io.WriterTo
- if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); (errors.Is(err, syscall.EINVAL)) != tc[0].wantErr {
- t.Errorf("Finalise: error = %v, wantErr %v",
- err, tc[0].wantErr)
- return
- } else {
- wt = v
- }
-
- // rest of the tests happen for sealed instances
- if tc[0].wantErr {
- return
- }
-
- // build null-terminated string from wanted args
- want := new(strings.Builder)
- args := append(tc[0].want, tc[1].want...)
- for _, arg := range args {
- want.WriteString(arg)
- want.WriteByte(0)
- }
-
- got := new(strings.Builder)
- if _, err := wt.WriteTo(got); err != nil {
- t.Errorf("WriteTo: error = %v", err)
- }
-
- if want.String() != got.String() {
- t.Errorf("Seal: %q, want %q",
- got.String(), want.String())
- }
- })
- }
-}
-
-func TestProxyStartWaitCloseString(t *testing.T) {
- t.Run("sandbox", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, true) })
- t.Run("direct", func(t *testing.T) { testProxyFinaliseStartWaitCloseString(t, false) })
-}
-
-const (
- stubProxyTimeout = 5 * time.Second
-)
-
-func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
- {
- oldWaitDelay := helper.WaitDelay
- helper.WaitDelay = 16 * time.Second
- t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
- }
-
- {
- proxyName := dbus.ProxyName
- dbus.ProxyName = os.Args[0]
- t.Cleanup(func() { dbus.ProxyName = proxyName })
- }
-
- var p *dbus.Proxy
-
- t.Run("string for nil proxy", func(t *testing.T) {
- want := "(invalid dbus proxy)"
- if got := p.String(); got != want {
- t.Errorf("String: %q, want %q",
- got, want)
- }
- })
-
- t.Run("invalid start", func(t *testing.T) {
- if !useSandbox {
- p = dbus.NewDirect(t.Context(), message.New(nil), nil, nil)
- } else {
- p = dbus.New(t.Context(), message.New(nil), nil, nil)
- }
-
- if err := p.Start(); !errors.Is(err, syscall.ENOTRECOVERABLE) {
- t.Errorf("Start: error = %q, wantErr %q", err, syscall.ENOTRECOVERABLE)
- return
- }
- })
-
- for id, tc := range testCasePairs {
- // this test does not test errors
- if tc[0].wantErr {
- continue
- }
-
- t.Run("proxy for "+id, func(t *testing.T) {
- var final *dbus.Final
- t.Run("finalise", func(t *testing.T) {
- if v, err := dbus.Finalise(tc[0].bus, tc[1].bus, tc[0].c, tc[1].c); err != nil {
- t.Errorf("Finalise: error = %v, wantErr %v", err, tc[0].wantErr)
- return
- } else {
- final = v
- }
- })
-
- ctx, cancel := context.WithTimeout(t.Context(), stubProxyTimeout)
- defer cancel()
- output := new(strings.Builder)
- if !useSandbox {
- p = dbus.NewDirect(ctx, message.New(nil), final, output)
- } else {
- p = dbus.New(ctx, message.New(nil), final, output)
- }
-
- { // check invalid wait behaviour
- wantErr := "dbus: not started"
- if err := p.Wait(); err == nil || err.Error() != wantErr {
- t.Errorf("Wait: error = %v, wantErr %v", err, wantErr)
- }
- }
-
- { // check string behaviour
- want := "(unused dbus proxy)"
- if got := p.String(); got != want {
- t.Errorf("String: %q, want %q", got, want)
- return
- }
- }
-
- if err := p.Start(); err != nil {
- t.Fatalf("Start: error = %v", err)
- }
-
- { // check running string behaviour
- wantSubstr := fmt.Sprintf("%s --args=3 --fd=4", os.Args[0])
- if useSandbox {
- wantSubstr = `argv: ["xdg-dbus-proxy" "--args=3" "--fd=4"], filter: true, rules: 0, flags: 0x1, presets: 0xf`
- }
- if got := p.String(); !strings.Contains(got, wantSubstr) {
- t.Errorf("String: %q, want %q",
- got, wantSubstr)
- return
- }
- }
-
- p.Close()
- if err := p.Wait(); err != nil {
- t.Errorf("Wait: error = %v\noutput: %s", err, output.String())
- }
- })
- }
-}
diff --git a/system/dbus/deprecated.go b/system/dbus/deprecated.go
new file mode 100644
index 00000000..67a309c2
--- /dev/null
+++ b/system/dbus/deprecated.go
@@ -0,0 +1,115 @@
+// Package dbus exposes the internal/system/dbus package.
+//
+// Deprecated: This package will be removed in 0.4.
+package dbus
+
+import (
+ "context"
+ "io"
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/hst"
+ "hakurei.app/internal/system/dbus"
+ "hakurei.app/message"
+)
+
+type AddrEntry = dbus.AddrEntry
+
+// EqualAddrEntries returns whether two slices of [AddrEntry] are equal.
+//
+//go:linkname EqualAddrEntries hakurei.app/internal/system/dbus.EqualAddrEntries
+func EqualAddrEntries(entries, target []AddrEntry) bool
+
+// Parse parses D-Bus address according to
+// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
+//
+//go:linkname Parse hakurei.app/internal/system/dbus.Parse
+func Parse(addr []byte) ([]AddrEntry, error)
+
+type ParseError = dbus.ParseError
+
+const (
+ ErrNoColon = dbus.ErrNoColon
+ ErrBadPairSep = dbus.ErrBadPairSep
+ ErrBadPairKey = dbus.ErrBadPairKey
+ ErrBadPairVal = dbus.ErrBadPairVal
+ ErrBadValLength = dbus.ErrBadValLength
+ ErrBadValByte = dbus.ErrBadValByte
+ ErrBadValHexLength = dbus.ErrBadValHexLength
+ ErrBadValHexByte = dbus.ErrBadValHexByte
+)
+
+type BadAddressError = dbus.BadAddressError
+
+// ProxyPair is an upstream dbus address and a downstream socket path.
+type ProxyPair = dbus.ProxyPair
+
+// Args returns the xdg-dbus-proxy arguments equivalent of [hst.BusConfig].
+//
+//go:linkname Args hakurei.app/internal/system/dbus.Args
+func Args(c *hst.BusConfig, bus ProxyPair) (args []string)
+
+// NewConfig returns the address of a new [hst.BusConfig] with optional defaults.
+//
+//go:linkname NewConfig hakurei.app/internal/system/dbus.NewConfig
+func NewConfig(id string, defaults, mpris bool) *hst.BusConfig
+
+const (
+ /*
+ SessionBusAddress is the name of the environment variable where the address of the login session message bus is given in.
+
+ If that variable is not set, applications may also try to read the address from the X Window System root window property _DBUS_SESSION_BUS_ADDRESS.
+ The root window property must have type STRING. The environment variable should have precedence over the root window property.
+
+ The address of the login session message bus is given in the DBUS_SESSION_BUS_ADDRESS environment variable.
+ If DBUS_SESSION_BUS_ADDRESS is not set, or if it's set to the string "autolaunch:",
+ the system should use platform-specific methods of locating a running D-Bus session server,
+ or starting one if a running instance cannot be found.
+ Note that this mechanism is not recommended for attempting to determine if a daemon is running.
+ It is inherently racy to attempt to make this determination, since the bus daemon may be started just before or just after the determination is made.
+ Therefore, it is recommended that applications do not try to make this determination for their functionality purposes, and instead they should attempt to start the server.
+
+ This package diverges from the specification, as the caller is unlikely to be an X client, or be in a position to autolaunch a dbus server.
+ So a fallback address with a socket located in the well-known default XDG_RUNTIME_DIR formatting is used.
+ */
+ SessionBusAddress = dbus.SessionBusAddress
+
+ /*
+ SystemBusAddress is the name of the environment variable where the address of the system message bus is given in.
+
+ If that variable is not set, applications should try to connect to the well-known address unix:path=/var/run/dbus/system_bus_socket.
+ Implementations of the well-known system bus should listen on an address that will result in that connection being successful.
+ */
+ SystemBusAddress = dbus.SystemBusAddress
+
+ // FallbackSystemBusAddress is used when [SystemBusAddress] is not set.
+ FallbackSystemBusAddress = dbus.FallbackSystemBusAddress
+)
+
+// Address returns the session and system bus addresses copied from environment,
+// or appropriate fallback values if they are not set.
+//
+//go:linkname Address hakurei.app/internal/system/dbus.Address
+func Address() (session, system string)
+
+// ProxyName is the file name or path to the proxy program.
+// Overriding ProxyName will only affect Proxy instance created after the change.
+//
+//go:linkname ProxyName hakurei.app/internal/system/dbus.ProxyName
+var ProxyName string
+
+// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
+type Proxy = dbus.Proxy
+
+// Final describes the outcome of a proxy configuration.
+type Final = dbus.Final
+
+// Finalise creates a checked argument writer for [Proxy].
+//
+//go:linkname Finalise hakurei.app/internal/system/dbus.Finalise
+func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (final *Final, err error)
+
+// New returns a new instance of [Proxy].
+//
+//go:linkname New hakurei.app/internal/system/dbus.New
+func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy
diff --git a/system/dbus/export_test.go b/system/dbus/export_test.go
deleted file mode 100644
index bc24f3f3..00000000
--- a/system/dbus/export_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package dbus
-
-import (
- "context"
- "io"
-
- "hakurei.app/message"
-)
-
-// NewDirect returns a new instance of [Proxy] with its sandbox disabled.
-func NewDirect(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy {
- p := New(ctx, msg, final, output)
- p.useSandbox = false
- return p
-}
diff --git a/system/dbus/proc.go b/system/dbus/proc.go
deleted file mode 100644
index 0bd9c6b3..00000000
--- a/system/dbus/proc.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package dbus
-
-import (
- "context"
- "errors"
- "os"
- "os/exec"
- "strconv"
- "syscall"
-
- "hakurei.app/container"
- "hakurei.app/container/check"
- "hakurei.app/container/seccomp"
- "hakurei.app/container/std"
- "hakurei.app/internal/helper"
- "hakurei.app/ldd"
-)
-
-// Start starts and configures a D-Bus proxy process.
-func (p *Proxy) Start() error {
- if p.final == nil || p.final.WriterTo == nil {
- return syscall.ENOTRECOVERABLE
- }
-
- p.mu.Lock()
- defer p.mu.Unlock()
- p.pmu.Lock()
- defer p.pmu.Unlock()
-
- if p.cancel != nil || p.cause != nil {
- return errors.New("dbus: already started")
- }
-
- ctx, cancel := context.WithCancelCause(p.ctx)
-
- if !p.useSandbox {
- p.helper = helper.NewDirect(ctx, p.name, p.final, true, argF, func(cmd *exec.Cmd) {
- if p.output != nil {
- cmd.Stdout, cmd.Stderr = p.output, p.output
- }
- cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
- cmd.Env = make([]string, 0)
- }, nil)
- } else {
- var toolPath *check.Absolute
- if a, err := check.NewAbs(p.name); err != nil {
- if p.name, err = exec.LookPath(p.name); err != nil {
- return err
- } else if toolPath, err = check.NewAbs(p.name); err != nil {
- return err
- }
- } else {
- toolPath = a
- }
-
- var libPaths []*check.Absolute
- if entries, err := ldd.Exec(ctx, p.msg, toolPath.String()); err != nil {
- return err
- } else {
- libPaths = ldd.Path(entries)
- }
-
- p.helper = helper.New(
- ctx, p.msg, toolPath, "xdg-dbus-proxy",
- p.final, true,
- argF, func(z *container.Container) {
- z.SeccompFlags |= seccomp.AllowMultiarch
- z.SeccompPresets |= std.PresetStrict
- z.Hostname = "hakurei-dbus"
- if p.output != nil {
- z.Stdout, z.Stderr = p.output, p.output
- }
-
- // these lib paths are unpredictable, so mount them first so they cannot cover anything
- for _, name := range libPaths {
- z.Bind(name, name, 0)
- }
-
- // upstream bus directories
- upstreamPaths := make([]*check.Absolute, 0, 2)
- for _, addr := range [][]AddrEntry{p.final.SessionUpstream, p.final.SystemUpstream} {
- for _, ent := range addr {
- if ent.Method != "unix" {
- continue
- }
- for _, pair := range ent.Values {
- if pair[0] != "path" {
- continue
- }
- if a, err := check.NewAbs(pair[1]); err != nil {
- continue
- } else {
- upstreamPaths = append(upstreamPaths, a.Dir())
- }
- }
- }
- }
- check.SortAbs(upstreamPaths)
- upstreamPaths = check.CompactAbs(upstreamPaths)
- for _, name := range upstreamPaths {
- z.Bind(name, name, 0)
- }
- z.HostNet = len(upstreamPaths) == 0
- z.HostAbstract = z.HostNet
-
- // parent directories of bind paths
- sockDirPaths := make([]*check.Absolute, 0, 2)
- if a, err := check.NewAbs(p.final.Session[1]); err == nil {
- sockDirPaths = append(sockDirPaths, a.Dir())
- }
- if a, err := check.NewAbs(p.final.System[1]); err == nil {
- sockDirPaths = append(sockDirPaths, a.Dir())
- }
- check.SortAbs(sockDirPaths)
- sockDirPaths = check.CompactAbs(sockDirPaths)
- for _, name := range sockDirPaths {
- z.Bind(name, name, std.BindWritable)
- }
-
- // xdg-dbus-proxy bin path
- binPath := toolPath.Dir()
- z.Bind(binPath, binPath, 0)
- }, nil)
- }
-
- if err := p.helper.Start(); err != nil {
- cancel(err)
- p.helper = nil
- return err
- }
-
- p.cancel, p.cause = cancel, func() error { return context.Cause(ctx) }
- return nil
-}
-
-var proxyClosed = errors.New("proxy closed")
-
-// Wait blocks until xdg-dbus-proxy exits and releases resources.
-func (p *Proxy) Wait() error {
- p.mu.RLock()
- defer p.mu.RUnlock()
-
- p.pmu.RLock()
- if p.helper == nil || p.cancel == nil || p.cause == nil {
- p.pmu.RUnlock()
- return errors.New("dbus: not started")
- }
-
- var errs [3]error
-
- errs[0] = p.helper.Wait()
- if errors.Is(errs[0], context.Canceled) &&
- errors.Is(p.cause(), proxyClosed) {
- errs[0] = nil
- }
- p.pmu.RUnlock()
-
- // ensure socket removal so ephemeral directory is empty at revert
- if err := os.Remove(p.final.Session[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
- errs[1] = err
- }
- if p.final.System[1] != "" {
- if err := os.Remove(p.final.System[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
- errs[2] = err
- }
- }
-
- return errors.Join(errs[:]...)
-}
-
-// Close cancels the context passed to the helper instance attached to xdg-dbus-proxy.
-func (p *Proxy) Close() {
- p.pmu.Lock()
- defer p.pmu.Unlock()
-
- if p.cancel == nil {
- panic("dbus: not started")
- }
- p.cancel(proxyClosed)
-}
-
-func argF(argsFd, statFd int) []string {
- if statFd == -1 {
- return []string{"--args=" + strconv.Itoa(argsFd)}
- } else {
- return []string{"--args=" + strconv.Itoa(argsFd), "--fd=" + strconv.Itoa(statFd)}
- }
-}
diff --git a/system/dbus/proc_test.go b/system/dbus/proc_test.go
deleted file mode 100644
index 544856ed..00000000
--- a/system/dbus/proc_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package dbus_test
-
-import (
- "os"
- "testing"
-
- "hakurei.app/container"
- "hakurei.app/internal/helper"
-)
-
-func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }
diff --git a/system/dbus/proxy.go b/system/dbus/proxy.go
deleted file mode 100644
index 9de49c41..00000000
--- a/system/dbus/proxy.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package dbus
-
-import (
- "context"
- "io"
- "sync"
- "syscall"
-
- "hakurei.app/hst"
- "hakurei.app/internal/helper"
- "hakurei.app/message"
-)
-
-// ProxyName is the file name or path to the proxy program.
-// Overriding ProxyName will only affect Proxy instance created after the change.
-var ProxyName = "xdg-dbus-proxy"
-
-// Proxy holds the state of a xdg-dbus-proxy process, and should never be copied.
-type Proxy struct {
- helper helper.Helper
- ctx context.Context
- msg message.Msg
-
- cancel context.CancelCauseFunc
- cause func() error
-
- final *Final
- output io.Writer
- useSandbox bool
-
- name string
-
- mu, pmu sync.RWMutex
-}
-
-func (p *Proxy) String() string {
- if p == nil {
- return "(invalid dbus proxy)"
- }
-
- p.mu.RLock()
- defer p.mu.RUnlock()
-
- if p.helper != nil {
- return p.helper.String()
- }
-
- return "(unused dbus proxy)"
-}
-
-// Final describes the outcome of a proxy configuration.
-type Final struct {
- Session, System ProxyPair
- // parsed upstream address
- SessionUpstream, SystemUpstream []AddrEntry
- io.WriterTo
-}
-
-// Finalise creates a checked argument writer for [Proxy].
-func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (final *Final, err error) {
- if session == nil && system == nil {
- return nil, syscall.EBADE
- }
-
- var args []string
- if session != nil {
- if err = session.CheckInterfaces("session"); err != nil {
- return
- }
- args = append(args, Args(session, sessionBus)...)
- }
- if system != nil {
- if err = system.CheckInterfaces("system"); err != nil {
- return
- }
- args = append(args, Args(system, systemBus)...)
- }
-
- final = &Final{Session: sessionBus, System: systemBus}
-
- final.WriterTo, err = helper.NewCheckedArgs(args...)
- if err != nil {
- return
- }
-
- if session != nil {
- final.SessionUpstream, err = Parse([]byte(final.Session[0]))
- if err != nil {
- return
- }
- }
- if system != nil {
- final.SystemUpstream, err = Parse([]byte(final.System[0]))
- if err != nil {
- return
- }
- }
-
- return
-}
-
-// New returns a new instance of [Proxy].
-func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy {
- return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true}
-}
diff --git a/system/dbus/samples_test.go b/system/dbus/samples_test.go
deleted file mode 100644
index 7db3d6b9..00000000
--- a/system/dbus/samples_test.go
+++ /dev/null
@@ -1,214 +0,0 @@
-package dbus_test
-
-import (
- "hakurei.app/hst"
-)
-
-const (
- sampleHostPath = "/tmp/bus"
- sampleHostAddr = "unix:path=" + sampleHostPath
- sampleBindPath = "/tmp/proxied_bus"
-)
-
-var samples = []dbusTestCase{
- {
- "org.chromium.Chromium", &hst.BusConfig{
- See: nil,
- Talk: []string{"org.freedesktop.Notifications", "org.freedesktop.FileManager1", "org.freedesktop.ScreenSaver",
- "org.freedesktop.secrets", "org.kde.kwalletd5", "org.kde.kwalletd6", "org.gnome.SessionManager"},
- Own: []string{"org.chromium.Chromium.*", "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
- "org.mpris.MediaPlayer2.chromium.*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Log: false,
- Filter: true,
- }, false, false,
- [2]string{sampleHostAddr, sampleBindPath},
- []string{
- sampleHostAddr,
- sampleBindPath,
- "--filter",
- "--talk=org.freedesktop.Notifications",
- "--talk=org.freedesktop.FileManager1",
- "--talk=org.freedesktop.ScreenSaver",
- "--talk=org.freedesktop.secrets",
- "--talk=org.kde.kwalletd5",
- "--talk=org.kde.kwalletd6",
- "--talk=org.gnome.SessionManager",
- "--own=org.chromium.Chromium.*",
- "--own=org.mpris.MediaPlayer2.org.chromium.Chromium.*",
- "--own=org.mpris.MediaPlayer2.chromium.*",
- "--call=org.freedesktop.portal.*=*",
- "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
- },
- },
- {
- "org.chromium.Chromium+", &hst.BusConfig{
- See: nil,
- Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
- Own: nil,
- Call: nil,
- Broadcast: nil,
- Log: false,
- Filter: true,
- }, false, false,
- [2]string{sampleHostAddr, sampleBindPath},
- []string{
- sampleHostAddr,
- sampleBindPath,
- "--filter",
- "--talk=org.bluez",
- "--talk=org.freedesktop.Avahi",
- "--talk=org.freedesktop.UPower",
- },
- },
-
- {
- "dev.vencord.Vesktop", &hst.BusConfig{
- See: nil,
- Talk: []string{"org.freedesktop.Notifications", "org.kde.StatusNotifierWatcher"},
- Own: []string{"dev.vencord.Vesktop.*", "org.mpris.MediaPlayer2.dev.vencord.Vesktop.*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Log: false,
- Filter: true,
- }, false, false,
- [2]string{sampleHostAddr, sampleBindPath},
- []string{
- sampleHostAddr,
- sampleBindPath,
- "--filter",
- "--talk=org.freedesktop.Notifications",
- "--talk=org.kde.StatusNotifierWatcher",
- "--own=dev.vencord.Vesktop.*",
- "--own=org.mpris.MediaPlayer2.dev.vencord.Vesktop.*",
- "--call=org.freedesktop.portal.*=*",
- "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*"},
- },
-
- {
- "uk.gensokyo.CrashTestDummy", &hst.BusConfig{
- See: []string{"uk.gensokyo.CrashTestDummy1"},
- Talk: []string{"org.freedesktop.Notifications"},
- Own: []string{"uk.gensokyo.CrashTestDummy.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Log: true,
- Filter: true,
- }, false, false,
- [2]string{sampleHostAddr, sampleBindPath},
- []string{
- sampleHostAddr,
- sampleBindPath,
- "--filter",
- "--see=uk.gensokyo.CrashTestDummy1",
- "--talk=org.freedesktop.Notifications",
- "--own=uk.gensokyo.CrashTestDummy.*",
- "--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*",
- "--call=org.freedesktop.portal.*=*",
- "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
- "--log"},
- },
- {
- "uk.gensokyo.CrashTestDummy1", &hst.BusConfig{
- See: []string{"uk.gensokyo.CrashTestDummy"},
- Talk: []string{"org.freedesktop.Notifications"},
- Own: []string{"uk.gensokyo.CrashTestDummy1.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*"},
- Call: map[string]string{"org.freedesktop.portal.*": "*"},
- Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
- Log: true,
- Filter: true,
- }, false, true,
- [2]string{sampleHostAddr, sampleBindPath},
- []string{
- sampleHostAddr,
- sampleBindPath,
- "--filter",
- "--see=uk.gensokyo.CrashTestDummy",
- "--talk=org.freedesktop.Notifications",
- "--own=uk.gensokyo.CrashTestDummy1.*",
- "--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*",
- "--call=org.freedesktop.portal.*=*",
- "--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
- "--log"},
- },
-}
-
-type dbusTestCase struct {
- id string
- c *hst.BusConfig
- wantErr bool
- wantErrF bool
- bus [2]string
- want []string
-}
-
-var (
- testCasesExt = func() []dbusTestCase {
- testCases := make([]dbusTestCase, len(samples)*2)
- for i := range samples {
- testCases[i] = samples[i]
-
- fi := &testCases[len(samples)+i]
- *fi = samples[i]
-
- // create null-injected test cases
- fi.wantErr = true
- injectNulls := func(t *[]string) {
- f := make([]string, len(*t))
- for i := range f {
- f[i] = "\x00" + (*t)[i] + "\x00"
- }
- *t = f
- }
-
- fi.c = new(hst.BusConfig)
- *fi.c = *samples[i].c
- injectNulls(&fi.c.See)
- injectNulls(&fi.c.Talk)
- injectNulls(&fi.c.Own)
- }
- return testCases
- }()
-
- testCasePairs = func() map[string][2]dbusTestCase {
- // enumerate test case pairs
- var pc int
- for _, tc := range samples {
- if tc.id != "" {
- pc++
- }
- }
- pairs := make(map[string][2]dbusTestCase, pc)
- for i, tc := range testCasesExt {
- if tc.id == "" {
- continue
- }
-
- // skip already enumerated system bus test
- if tc.id[len(tc.id)-1] == '+' {
- continue
- }
-
- ftp := [2]dbusTestCase{tc}
-
- // system proxy tests always place directly after its user counterpart with id ending in +
- if i+1 < len(testCasesExt) && testCasesExt[i+1].id[len(testCasesExt[i+1].id)-1] == '+' {
- // attach system bus config
- ftp[1] = testCasesExt[i+1]
-
- // check for misplaced/mismatching tests
- if ftp[0].wantErr != ftp[1].wantErr || ftp[0].id+"+" != ftp[1].id {
- panic("mismatching session/system pairing")
- }
- }
-
- k := tc.id
- if tc.wantErr {
- k = "malformed_" + k
- }
- pairs[k] = ftp
- }
- return pairs
- }()
-)
diff --git a/system/dbus/testdata/dev.vencord.Vesktop.json b/system/dbus/testdata/dev.vencord.Vesktop.json
deleted file mode 100644
index 05259774..00000000
--- a/system/dbus/testdata/dev.vencord.Vesktop.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "talk":[
- "org.freedesktop.Notifications",
- "org.kde.StatusNotifierWatcher"
- ],
- "own":[
- "dev.vencord.Vesktop.*",
- "org.mpris.MediaPlayer2.dev.vencord.Vesktop.*"
- ],
- "call":{
- "org.freedesktop.portal.*":"*"
- },
- "broadcast":{
- "org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
- },
-
- "filter":true
-}
diff --git a/system/dbus/testdata/org.chromium.Chromium+.json b/system/dbus/testdata/org.chromium.Chromium+.json
deleted file mode 100644
index 0a27d719..00000000
--- a/system/dbus/testdata/org.chromium.Chromium+.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "talk":[
- "org.bluez",
- "org.freedesktop.Avahi",
- "org.freedesktop.UPower"
- ],
-
- "filter":true
-} \ No newline at end of file
diff --git a/system/dbus/testdata/org.chromium.Chromium.json b/system/dbus/testdata/org.chromium.Chromium.json
deleted file mode 100644
index be027f39..00000000
--- a/system/dbus/testdata/org.chromium.Chromium.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "talk":[
- "org.freedesktop.Notifications",
- "org.freedesktop.FileManager1",
- "org.freedesktop.ScreenSaver",
- "org.freedesktop.secrets",
- "org.kde.kwalletd5",
- "org.kde.kwalletd6",
- "org.gnome.SessionManager"
- ],
- "own":[
- "org.chromium.Chromium.*",
- "org.mpris.MediaPlayer2.org.chromium.Chromium.*",
- "org.mpris.MediaPlayer2.chromium.*"
- ],
- "call":{
- "org.freedesktop.portal.*":"*"
- },
- "broadcast":{
- "org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
- },
-
- "filter":true
-}
diff --git a/system/dbus/testdata/uk.gensokyo.CrashTestDummy.json b/system/dbus/testdata/uk.gensokyo.CrashTestDummy.json
deleted file mode 100644
index 43272f67..00000000
--- a/system/dbus/testdata/uk.gensokyo.CrashTestDummy.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "see": [
- "uk.gensokyo.CrashTestDummy1"
- ],
- "talk":[
- "org.freedesktop.Notifications"
- ],
- "own":[
- "uk.gensokyo.CrashTestDummy.*",
- "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"
- ],
- "call":{
- "org.freedesktop.portal.*":"*"
- },
- "broadcast":{
- "org.freedesktop.portal.*":"@/org/freedesktop/portal/*"
- },
-
- "log": true,
- "filter":true
-}
diff --git a/system/dbus_test.go b/system/dbus_test.go
deleted file mode 100644
index eedb2335..00000000
--- a/system/dbus_test.go
+++ /dev/null
@@ -1,674 +0,0 @@
-package system
-
-import (
- "context"
- "reflect"
- "slices"
- "strconv"
- "strings"
- "syscall"
- "testing"
-
- "hakurei.app/container/stub"
- "hakurei.app/hst"
- "hakurei.app/internal/helper"
- "hakurei.app/system/dbus"
-)
-
-func TestDBusProxyOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []opBehaviourTestCase{
- {"dbusProxyStart", 0xdead, 0xff, &dbusProxyOp{
- final: dbusNewFinalSample(4),
- out: new(linePrefixWriter), // panics on write
- system: true,
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"session bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "unix:path=/run/user/1000/bus"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"system bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", "unix:path=/run/dbus/system_bus_socket"}}, nil, nil),
- call("dbusProxyStart", stub.ExpectArgs{dbusNewFinalSample(4)}, nil, stub.UniqueError(2)),
- }, &OpError{
- Op: "dbus", Err: stub.UniqueError(2),
- Msg: "cannot start message bus proxy: unique error 2 injected by the test suite",
- }, nil, nil},
-
- {"dbusProxyWait", 0xdead, 0xff, &dbusProxyOp{
- final: dbusNewFinalSample(3),
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"session bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "unix:path=/run/user/1000/bus"}}, nil, nil),
- call("dbusProxyStart", stub.ExpectArgs{dbusNewFinalSample(3)}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"starting message bus proxy", ignoreValue{}}}, nil, nil),
- }, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"terminating message bus proxy"}}, nil, nil),
- call("dbusProxyClose", stub.ExpectArgs{dbusNewFinalSample(3)}, nil, nil),
- call("dbusProxyWait", stub.ExpectArgs{dbusNewFinalSample(3)}, nil, stub.UniqueError(1)),
- call("verbose", stub.ExpectArgs{[]any{"message bus proxy exit"}}, nil, nil),
- }, &OpError{
- Op: "dbus", Err: stub.UniqueError(1), Revert: true,
- Msg: "message bus proxy error: unique error 1 injected by the test suite",
- }},
-
- {"success dbusProxyWait cancel", 0xdead, 0xff, &dbusProxyOp{
- final: dbusNewFinalSample(2),
- system: true,
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"session bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "unix:path=/run/user/1000/bus"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"system bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", "unix:path=/run/dbus/system_bus_socket"}}, nil, nil),
- call("dbusProxyStart", stub.ExpectArgs{dbusNewFinalSample(2)}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"starting message bus proxy", ignoreValue{}}}, nil, nil),
- }, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"terminating message bus proxy"}}, nil, nil),
- call("dbusProxyClose", stub.ExpectArgs{dbusNewFinalSample(2)}, nil, nil),
- call("dbusProxyWait", stub.ExpectArgs{dbusNewFinalSample(2)}, nil, context.Canceled),
- call("verbose", stub.ExpectArgs{[]any{"message bus proxy canceled upstream"}}, nil, nil),
- }, nil},
-
- {"success", 0xdead, 0xff, &dbusProxyOp{
- final: dbusNewFinalSample(1),
- system: true,
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"session bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "unix:path=/run/user/1000/bus"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"system bus proxy on %q for upstream %q", []any{"/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", "unix:path=/run/dbus/system_bus_socket"}}, nil, nil),
- call("dbusProxyStart", stub.ExpectArgs{dbusNewFinalSample(1)}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"starting message bus proxy", ignoreValue{}}}, nil, nil),
- }, nil, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"terminating message bus proxy"}}, nil, nil),
- call("dbusProxyClose", stub.ExpectArgs{dbusNewFinalSample(1)}, nil, nil),
- call("dbusProxyWait", stub.ExpectArgs{dbusNewFinalSample(1)}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"message bus proxy exit"}}, nil, nil),
- }, nil},
- })
-
- checkOpsBuilder(t, "ProxyDBus", []opsBuilderTestCase{
- {"nil session", 0xcafe, func(t *testing.T, sys *I) {
- wantErr := &OpError{
- Op: "dbus", Err: ErrDBusConfig,
- Msg: "attempted to create message bus proxy args without session bus config",
- }
- if err := sys.ProxyDBus(nil, new(hst.BusConfig), dbus.ProxyPair{}, dbus.ProxyPair{}); !reflect.DeepEqual(err, wantErr) {
- t.Errorf("ProxyDBus: error = %v, want %v", err, wantErr)
- }
- }, nil, stub.Expect{}},
-
- {"dbusFinalise NUL", 0xcafe, func(_ *testing.T, sys *I) {
- defer func() {
- want := "message bus proxy configuration contains NUL byte"
- if r := recover(); r != want {
- t.Errorf("MustProxyDBus: panic = %v, want %v", r, want)
- }
- }()
-
- sys.MustProxyDBus(
- &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"session\x00"}, Filter: true,
- }, &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"system\x00"}, Filter: true,
- }, dbus.ProxyPair{
- "unix:path=/run/user/1000/bus",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
- }, dbus.ProxyPair{
- "unix:path=/run/dbus/system_bus_socket",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
- })
- }, nil, stub.Expect{Calls: []stub.Call{
- call("dbusFinalise", stub.ExpectArgs{
- dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
- dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
- &hst.BusConfig{Talk: []string{"session\x00"}, Filter: true},
- &hst.BusConfig{Talk: []string{"system\x00"}, Filter: true},
- }, (*dbus.Final)(nil), syscall.EINVAL),
- }}},
-
- {"dbusFinalise", 0xcafe, func(_ *testing.T, sys *I) {
- wantErr := &OpError{
- Op: "dbus", Err: stub.UniqueError(0),
- Msg: "cannot finalise message bus proxy: unique error 0 injected by the test suite",
- }
- if err := sys.ProxyDBus(
- &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"session\x00"}, Filter: true,
- }, &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"system\x00"}, Filter: true,
- }, dbus.ProxyPair{
- "unix:path=/run/user/1000/bus",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
- }, dbus.ProxyPair{
- "unix:path=/run/dbus/system_bus_socket",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
- }); !reflect.DeepEqual(err, wantErr) {
- t.Errorf("ProxyDBus: error = %v", err)
- }
- }, nil, stub.Expect{Calls: []stub.Call{
- call("dbusFinalise", stub.ExpectArgs{
- dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
- dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
- &hst.BusConfig{Talk: []string{"session\x00"}, Filter: true},
- &hst.BusConfig{Talk: []string{"system\x00"}, Filter: true},
- }, (*dbus.Final)(nil), stub.UniqueError(0)),
- }}},
-
- {"full", 0xcafe, func(_ *testing.T, sys *I) {
- sys.MustProxyDBus(
- &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"session\x00"}, Filter: true,
- }, &hst.BusConfig{
- // use impossible value here as an implicit assert that it goes through the stub
- Talk: []string{"system\x00"}, Filter: true,
- }, dbus.ProxyPair{
- "unix:path=/run/user/1000/bus",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus",
- }, dbus.ProxyPair{
- "unix:path=/run/dbus/system_bus_socket",
- "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket",
- })
- }, []Op{
- &dbusProxyOp{
- final: dbusNewFinalSample(0),
- system: true,
- },
- }, stub.Expect{Calls: []stub.Call{
- call("dbusFinalise", stub.ExpectArgs{
- dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
- dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
- &hst.BusConfig{Talk: []string{"session\x00"}, Filter: true},
- &hst.BusConfig{Talk: []string{"system\x00"}, Filter: true},
- }, dbusNewFinalSample(0), nil),
- call("isVerbose", stub.ExpectArgs{}, true, nil),
- call("verbose", stub.ExpectArgs{[]any{"session bus proxy:", []string{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", "--filter", "--talk=session\x00"}}}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"system bus proxy:", []string{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", "--filter", "--talk=system\x00"}}}, nil, nil),
- call("verbose", stub.ExpectArgs{[]any{"message bus proxy final args:", helper.MustNewCheckedArgs("unique", "value", "0", "injected", "by", "the", "test", "suite")}}, nil, nil),
- }}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"nil", (*dbusProxyOp)(nil), (*dbusProxyOp)(nil), false},
- {"zero", new(dbusProxyOp), new(dbusProxyOp), false},
-
- {"system differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: false,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"wt differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1001/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"final system upstream differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket\x00"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"final session upstream differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1001/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"final system differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.1/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"final session differs", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1001/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, false},
-
- {"equals", &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, &dbusProxyOp{final: &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"unix", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs(
- "--filter", "unix:path=/run/user/1000/bus", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/bus",
- "--filter", "unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/b186c281d9e83a39afdc66d964ef99c6/system_bus_socket",
- ),
- }, system: true,
- }, true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"dbus", new(dbusProxyOp),
- Process, "/proc/nonexistent",
- "(invalid dbus proxy)"},
- })
-}
-
-func dbusNewFinalSample(v int) *dbus.Final {
- return &dbus.Final{
- Session: dbus.ProxyPair{"unix:path=/run/user/1000/bus", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus"},
- System: dbus.ProxyPair{"unix:path=/run/dbus/system_bus_socket", "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"},
-
- SessionUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/user/1000/bus"}}}},
- SystemUpstream: []dbus.AddrEntry{{Method: "unix", Values: [][2]string{{"path", "/run/dbus/system_bus_socket"}}}},
-
- WriterTo: helper.MustNewCheckedArgs("unique", "value", strconv.Itoa(v), "injected", "by", "the", "test", "suite"),
- }
-}
-
-func TestLinePrefixWriter(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- prefix string
- f func(w func(s string))
- wantErr []error
- wantPt []string
- want []string
- wantExt []string
- wantBuf string
- }{
- {"nop", "(nop) ", func(func(string)) {}, nil, nil, nil, nil, ""},
-
- {"partial", "(partial) ", func(w func(string)) {
- w("C-65533: -> ")
- }, nil, nil, nil, []string{
- "*(partial) C-65533: -> ",
- }, "C-65533: -> "},
-
- {"break", "(break) ", func(w func(string)) {
- w("C-65533: -> ")
- w("org.freedesktop.DBus fake ListNames\n")
- }, nil, nil, []string{
- "C-65533: -> org.freedesktop.DBus fake ListNames",
- }, nil, ""},
-
- {"break pt", "(break pt) ", func(w func(string)) {
- w("init: ")
- w("received setup parameters\n")
- }, nil, []string{
- "init: received setup parameters",
- }, nil, nil, ""},
-
- {"threshold", "(threshold) ", func(w func(s string)) {
- w(string(make([]byte, lpwSizeThreshold)))
- w("\n")
- }, []error{nil, syscall.ENOMEM}, nil, nil, []string{
- "*(threshold) " + string(make([]byte, lpwSizeThreshold)),
- "+(threshold) write threshold reached, output may be incomplete",
- }, string(make([]byte, lpwSizeThreshold))},
-
- {"threshold multi", "(threshold multi) ", func(w func(s string)) {
- w(":3\n")
- w(string(make([]byte, lpwSizeThreshold-3)))
- w("\n")
- }, []error{nil, nil, syscall.ENOMEM}, nil, []string{
- ":3",
- }, []string{
- "*(threshold multi) " + string(make([]byte, lpwSizeThreshold-3)),
- "+(threshold multi) write threshold reached, output may be incomplete",
- }, string(make([]byte, lpwSizeThreshold-3))},
-
- {"threshold multi partial", "(threshold multi partial) ", func(w func(s string)) {
- w(":3\n")
- w(string(make([]byte, lpwSizeThreshold-2)))
- w("dropped\n")
- }, []error{nil, nil, syscall.ENOMEM}, nil, []string{
- ":3",
- }, []string{
- "*(threshold multi partial) " + string(make([]byte, lpwSizeThreshold-2)),
- "+(threshold multi partial) write threshold reached, output may be incomplete",
- }, string(make([]byte, lpwSizeThreshold-2))},
-
- {"threshold exact", "(threshold exact) ", func(w func(s string)) {
- w(string(make([]byte, lpwSizeThreshold-1)))
- w("\n")
- }, nil, nil, []string{
- string(make([]byte, lpwSizeThreshold-1)),
- }, []string{
- "+(threshold exact) write threshold reached, output may be incomplete",
- }, ""},
-
- {"sample", "(dbus) ", func(w func(s string)) {
- w("init: received setup parameters\n")
- w(`init: mounting "/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" on "/sysroot/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" on "/sysroot/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" on "/sysroot/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" on "/sysroot/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" on "/sysroot/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" on "/sysroot/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" on "/sysroot/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" flags 0x4005` + "\n")
- w(`init: mounting "/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib64" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" on "/sysroot/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib64" flags 0x4005` + "\n")
- w(`init: mounting "/run/user/1000" flags 0x0` + "\n")
- w(`init: resolved "/host/run/user/1000" on "/sysroot/run/user/1000" flags 0x4005` + "\n")
- w(`init: mounting "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" flags 0x2` + "\n")
- w(`init: resolved "/host/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" on "/sysroot/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" flags 0x4004` + "\n")
- w(`init: mounting "/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" flags 0x0` + "\n")
- w(`init: resolved "/host/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" on "/sysroot/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" flags 0x4005` + "\n")
- w("init: resolving presets 0xf\n")
- w("init: 68 filter rules loaded\n")
- w("init: starting initial program /nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin/xdg-dbus-proxy\n")
- w("C1: -> org.freedesktop.DBus call org.freedesktop.DBus.Hello at /org/freedesktop/DBus\n")
- w("C-65536: -> org.freedesktop.DBus fake wildcarded AddMatch for org.freedesktop.portal\n")
- w("C-65535: -> org.freedesktop.DBus fake AddMatch for org.freedesktop.Notifications\n")
- w("C-65534: -> org.freedesktop.DBus fake GetNameOwner for org.freedesktop.Notifications\n")
- w("C-65533: -> org.freedesktop.DBus fake ListNames\n")
- w("B1: <- org.freedesktop.DBus return from C1\n")
- w("B2: <- org.freedesktop.DBus signal org.freedesktop.DBus.NameAcquired at /org/freedesktop/DBus\n")
- w("B3: <- org.freedesktop.DBus return from C-65536\n")
- w("*SKIPPED*\n")
- w("B4: <- org.freedesktop.DBus return from C-65535\n")
- w("*SKIPPED*\n")
- w("B5: <- org.freedesktop.DBus return error org.freedesktop.DBus.Error.NameHasNoOwner from C-65534\n")
- w("*SKIPPED*\n")
- w("B6: <- org.freedesktop.DBus return from C-65533\n")
- w("C-65532: -> org.freedesktop.DBus fake GetNameOwner for org.freedesktop.DBus\n")
- w("*SKIPPED*\n")
- w("B7: <- org.freedesktop.DBus return from C-65532\n")
- w("*SKIPPED*\n")
- w("C2: -> org.freedesktop.DBus call org.freedesktop.DBus.AddMatch at /org/freedesktop/DBus\n")
- w("C3: -> org.freedesktop.DBus call org.freedesktop.DBus.GetNameOwner at /org/freedesktop/DBus\n")
- w("C4: -> org.freedesktop.DBus call org.freedesktop.DBus.AddMatch at /org/freedesktop/DBus\n")
- w("C5: -> org.freedesktop.DBus call org.freedesktop.DBus.StartServiceByName at /org/freedesktop/DBus\n")
- w("B8: <- org.freedesktop.DBus return from C2\n")
- w("B9: <- org.freedesktop.DBus return error org.freedesktop.DBus.Error.NameHasNoOwner from C3\n")
- w("B10: <- org.freedesktop.DBus return from C4\n")
- w("B12: <- org.freedesktop.DBus signal org.freedesktop.DBus.NameOwnerChanged at /org/freedesktop/DBus\n")
- w("B11: <- org.freedesktop.DBus return from C5\n")
- w("C6: -> org.freedesktop.DBus call org.freedesktop.DBus.GetNameOwner at /org/freedesktop/DBus\n")
- w("B13: <- org.freedesktop.DBus return from C6\n")
- w("C7: -> :1.4 call org.freedesktop.Notifications.GetServerInformation at /org/freedesktop/Notifications\n")
- w("B4: <- :1.4 return from C7\n")
- w("C8: -> :1.4 call org.freedesktop.Notifications.GetServerInformation at /org/freedesktop/Notifications\n")
- w("B5: <- :1.4 return from C8\n")
- w("C9: -> :1.4 call org.freedesktop.Notifications.Notify at /org/freedesktop/Notifications\n")
- w("B6: <- :1.4 return from C9\n")
- w("C10: -> org.freedesktop.DBus call org.freedesktop.DBus.RemoveMatch at /org/freedesktop/DBus\n")
- w("C11: -> org.freedesktop.DBus call org.freedesktop.DBus.RemoveMatch at /org/freedesktop/DBus\n")
- w("B14: <- org.freedesktop.DBus return from C10\n")
- w("B15: <- org.freedesktop.DBus return from C11\n")
- w("init: initial process exited with code 0\n")
- }, nil, []string{
- "init: received setup parameters",
- `init: mounting "/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" flags 0x0`,
- `init: resolved "/host/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" on "/sysroot/nix/store/5gml2l2cj28yvyfyzblzjy1laqpxmyzd-libselinux-3.8.1/lib" flags 0x4005`,
- `init: mounting "/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" flags 0x0`,
- `init: resolved "/host/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" on "/sysroot/nix/store/bcs094l67dlbqf7idxxbljp293zms9mh-util-linux-minimal-2.41-lib/lib" flags 0x4005`,
- `init: mounting "/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" flags 0x0`,
- `init: resolved "/host/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" on "/sysroot/nix/store/jl19fdc7gdxqz9a1s368r9d15vpirnqy-zlib-1.3.1/lib" flags 0x4005`,
- `init: mounting "/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" flags 0x0`,
- `init: resolved "/host/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" on "/sysroot/nix/store/rnn29mhynsa4ncmk0fkcrdr29n0j20l4-libffi-3.4.8/lib" flags 0x4005`,
- `init: mounting "/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" flags 0x0`,
- `init: resolved "/host/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" on "/sysroot/nix/store/vvp8hlss3d5q6hn0cifq04jrpnp6bini-pcre2-10.44/lib" flags 0x4005`,
- `init: mounting "/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" flags 0x0`,
- `init: resolved "/host/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" on "/sysroot/nix/store/y3nxdc2x8hwivppzgx5hkrhacsh87l21-glib-2.84.3/lib" flags 0x4005`,
- `init: mounting "/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" flags 0x0`,
- `init: resolved "/host/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" on "/sysroot/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" flags 0x4005`,
- `init: mounting "/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib64" flags 0x0`,
- `init: resolved "/host/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib" on "/sysroot/nix/store/zdpby3l6azi78sl83cpad2qjpfj25aqx-glibc-2.40-66/lib64" flags 0x4005`,
- `init: mounting "/run/user/1000" flags 0x0`,
- `init: resolved "/host/run/user/1000" on "/sysroot/run/user/1000" flags 0x4005`,
- `init: mounting "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" flags 0x2`,
- `init: resolved "/host/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" on "/sysroot/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f" flags 0x4004`,
- `init: mounting "/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" flags 0x0`,
- `init: resolved "/host/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" on "/sysroot/nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin" flags 0x4005`,
- "init: resolving presets 0xf",
- "init: 68 filter rules loaded",
- "init: starting initial program /nix/store/d2divmq2d897amikcwpdx7zrbpddxxcl-xdg-dbus-proxy-0.1.6/bin/xdg-dbus-proxy",
-
- "init: initial process exited with code 0",
- }, []string{
- "C1: -> org.freedesktop.DBus call org.freedesktop.DBus.Hello at /org/freedesktop/DBus",
- "C-65536: -> org.freedesktop.DBus fake wildcarded AddMatch for org.freedesktop.portal",
- "C-65535: -> org.freedesktop.DBus fake AddMatch for org.freedesktop.Notifications",
- "C-65534: -> org.freedesktop.DBus fake GetNameOwner for org.freedesktop.Notifications",
- "C-65533: -> org.freedesktop.DBus fake ListNames",
- "B1: <- org.freedesktop.DBus return from C1",
- "B2: <- org.freedesktop.DBus signal org.freedesktop.DBus.NameAcquired at /org/freedesktop/DBus",
- "B3: <- org.freedesktop.DBus return from C-65536",
- "*SKIPPED*",
- "B4: <- org.freedesktop.DBus return from C-65535",
- "*SKIPPED*",
- "B5: <- org.freedesktop.DBus return error org.freedesktop.DBus.Error.NameHasNoOwner from C-65534",
- "*SKIPPED*",
- "B6: <- org.freedesktop.DBus return from C-65533",
- "C-65532: -> org.freedesktop.DBus fake GetNameOwner for org.freedesktop.DBus",
- "*SKIPPED*",
- "B7: <- org.freedesktop.DBus return from C-65532",
- "*SKIPPED*",
- "C2: -> org.freedesktop.DBus call org.freedesktop.DBus.AddMatch at /org/freedesktop/DBus",
- "C3: -> org.freedesktop.DBus call org.freedesktop.DBus.GetNameOwner at /org/freedesktop/DBus",
- "C4: -> org.freedesktop.DBus call org.freedesktop.DBus.AddMatch at /org/freedesktop/DBus",
- "C5: -> org.freedesktop.DBus call org.freedesktop.DBus.StartServiceByName at /org/freedesktop/DBus",
- "B8: <- org.freedesktop.DBus return from C2",
- "B9: <- org.freedesktop.DBus return error org.freedesktop.DBus.Error.NameHasNoOwner from C3",
- "B10: <- org.freedesktop.DBus return from C4",
- "B12: <- org.freedesktop.DBus signal org.freedesktop.DBus.NameOwnerChanged at /org/freedesktop/DBus",
- "B11: <- org.freedesktop.DBus return from C5",
- "C6: -> org.freedesktop.DBus call org.freedesktop.DBus.GetNameOwner at /org/freedesktop/DBus",
- "B13: <- org.freedesktop.DBus return from C6",
- "C7: -> :1.4 call org.freedesktop.Notifications.GetServerInformation at /org/freedesktop/Notifications",
- "B4: <- :1.4 return from C7",
- "C8: -> :1.4 call org.freedesktop.Notifications.GetServerInformation at /org/freedesktop/Notifications",
- "B5: <- :1.4 return from C8",
- "C9: -> :1.4 call org.freedesktop.Notifications.Notify at /org/freedesktop/Notifications",
- "B6: <- :1.4 return from C9",
- "C10: -> org.freedesktop.DBus call org.freedesktop.DBus.RemoveMatch at /org/freedesktop/DBus",
- "C11: -> org.freedesktop.DBus call org.freedesktop.DBus.RemoveMatch at /org/freedesktop/DBus",
- "B14: <- org.freedesktop.DBus return from C10",
- "B15: <- org.freedesktop.DBus return from C11",
- }, nil, ""},
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- gotPt := make([]string, 0, len(tc.wantPt))
- out := &linePrefixWriter{
- prefix: tc.prefix,
- println: func(v ...any) {
- if len(v) != 1 {
- t.Fatalf("invalid call to println: %#v", v)
- }
- gotPt = append(gotPt, v[0].(string))
- },
- buf: new(strings.Builder),
- }
-
- var pos int
- tc.f(func(s string) {
- _, err := out.Write([]byte(s))
- if tc.wantErr != nil {
- if !reflect.DeepEqual(err, tc.wantErr[pos]) {
- t.Fatalf("Write: error = %v, want %v", err, tc.wantErr[pos])
- }
- } else if err != nil {
- t.Fatalf("Write: unexpected error: %v", err)
- return
- }
- pos++
- })
-
- if !slices.Equal(out.msg, tc.want) {
- t.Errorf("msg: %#v, want %#v", out.msg, tc.want)
- }
-
- if out.buf.String() != tc.wantBuf {
- t.Errorf("buf: %q, want %q", out.buf, tc.wantBuf)
- }
-
- wantPt := make([]string, len(tc.wantPt))
- for i, m := range tc.wantPt {
- wantPt[i] = tc.prefix + m
- }
- if !slices.Equal(gotPt, wantPt) {
- t.Errorf("passthrough: %#v, want %#v", gotPt, wantPt)
- }
-
- wantDump := make([]string, len(tc.want)+len(tc.wantExt))
- for i, want := range tc.want {
- wantDump[i] = tc.prefix + want
- }
- for i, want := range tc.wantExt {
- wantDump[len(tc.want)+i] = want
- }
- t.Run("dump", func(t *testing.T) {
- got := make([]string, 0, len(wantDump))
- out.println = func(v ...any) {
- if len(v) != 1 {
- t.Fatalf("Dump: invalid call to println: %#v", v)
- }
- got = append(got, v[0].(string))
- }
- out.Dump()
-
- if !slices.Equal(got, wantDump) {
- t.Errorf("Dump: %#v, want %#v", got, wantDump)
- }
- })
- })
- }
-}
diff --git a/system/deprecated.go b/system/deprecated.go
new file mode 100644
index 00000000..3f0a423d
--- /dev/null
+++ b/system/deprecated.go
@@ -0,0 +1,50 @@
+// Package system exposes the internal/system package.
+//
+// Deprecated: This package will be removed in 0.4.
+package system
+
+import (
+ "context"
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/hst"
+ "hakurei.app/internal/system"
+ "hakurei.app/message"
+)
+
+// ErrDBusConfig is returned when a required hst.BusConfig argument is nil.
+//
+//go:linkname ErrDBusConfig hakurei.app/internal/system.ErrDBusConfig
+var ErrDBusConfig error
+
+// OpError is returned by [I.Commit] and [I.Revert].
+type OpError = system.OpError
+
+const (
+ // User type is reverted at final instance exit.
+ User = system.User
+ // Process type is unconditionally reverted on exit.
+ Process = system.Process
+
+ CM = system.CM
+)
+
+// Criteria specifies types of Op to revert.
+type Criteria = system.Criteria
+
+// Op is a reversible system operation.
+type Op = system.Op
+
+// TypeString extends [Enablement.String] to support [User] and [Process].
+//
+//go:linkname TypeString hakurei.app/internal/system.TypeString
+func TypeString(e hst.Enablement) string
+
+// New returns the address of a new [I] targeting uid.
+//
+//go:linkname New hakurei.app/internal/system.New
+func New(ctx context.Context, msg message.Msg, uid int) (sys *I)
+
+// An I provides deferred operating system interaction. [I] must not be copied.
+// Methods of [I] must not be used concurrently.
+type I = system.I
diff --git a/system/dispatcher.go b/system/dispatcher.go
deleted file mode 100644
index cdc95072..00000000
--- a/system/dispatcher.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package system
-
-import (
- "io"
- "io/fs"
- "log"
- "os"
-
- "hakurei.app/hst"
- "hakurei.app/system/acl"
- "hakurei.app/system/dbus"
- "hakurei.app/system/internal/xcb"
-)
-
-type osFile interface {
- Name() string
- io.Writer
- fs.File
-}
-
-// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
-// syscallDispatcher is embedded in [I], so all methods must be unexported.
-type syscallDispatcher interface {
- // new starts a goroutine with a new instance of syscallDispatcher.
- // A syscallDispatcher must never be used in any goroutine other than the one owning it,
- // just synchronising access is not enough, as this is for test instrumentation.
- new(f func(k syscallDispatcher))
-
- // stat provides os.Stat.
- stat(name string) (os.FileInfo, error)
- // open provides [os.Open].
- open(name string) (osFile, error)
- // mkdir provides os.Mkdir.
- mkdir(name string, perm os.FileMode) error
- // chmod provides os.Chmod.
- chmod(name string, mode os.FileMode) error
- // link provides os.Link.
- link(oldname, newname string) error
- // remove provides os.Remove.
- remove(name string) error
-
- // println provides [log.Println].
- println(v ...any)
-
- // aclUpdate provides [acl.Update].
- aclUpdate(name string, uid int, perms ...acl.Perm) error
-
- // xcbChangeHosts provides [xcb.ChangeHosts].
- xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error
-
- // dbusFinalise provides [dbus.Finalise].
- dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error)
- // dbusProxyStart provides the Start method of [dbus.Proxy].
- dbusProxyStart(proxy *dbus.Proxy) error
- // dbusProxyClose provides the Close method of [dbus.Proxy].
- dbusProxyClose(proxy *dbus.Proxy)
- // dbusProxyWait provides the Wait method of [dbus.Proxy].
- dbusProxyWait(proxy *dbus.Proxy) error
-}
-
-// direct implements syscallDispatcher on the current kernel.
-type direct struct{}
-
-func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
-
-func (k direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
-func (k direct) open(name string) (osFile, error) { return os.Open(name) }
-func (k direct) mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
-func (k direct) chmod(name string, mode os.FileMode) error { return os.Chmod(name, mode) }
-func (k direct) link(oldname, newname string) error { return os.Link(oldname, newname) }
-func (k direct) remove(name string) error { return os.Remove(name) }
-
-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) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
- return xcb.ChangeHosts(mode, family, address)
-}
-
-func (k direct) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error) {
- return dbus.Finalise(sessionBus, systemBus, session, system)
-}
-
-func (k direct) dbusProxyStart(proxy *dbus.Proxy) error { return proxy.Start() }
-func (k direct) dbusProxyClose(proxy *dbus.Proxy) { proxy.Close() }
-func (k direct) dbusProxyWait(proxy *dbus.Proxy) error { return proxy.Wait() }
diff --git a/system/dispatcher_test.go b/system/dispatcher_test.go
deleted file mode 100644
index fd6e5d09..00000000
--- a/system/dispatcher_test.go
+++ /dev/null
@@ -1,379 +0,0 @@
-package system
-
-import (
- "log"
- "os"
- "reflect"
- "slices"
- "testing"
- "unsafe"
-
- "hakurei.app/container/stub"
- "hakurei.app/hst"
- "hakurei.app/system/acl"
- "hakurei.app/system/dbus"
- "hakurei.app/system/internal/xcb"
-)
-
-// call initialises a [stub.Call].
-// This keeps composites analysis happy without making the test cases too bloated.
-func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
- return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
-}
-
-type opBehaviourTestCase struct {
- name string
- uid int
- ec hst.Enablement
- op Op
-
- apply []stub.Call
- wantErrApply error
-
- revert []stub.Call
- wantErrRevert error
-}
-
-func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
- t.Helper()
-
- t.Run("behaviour", func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- var ec *Criteria
- if tc.ec != 0xff {
- ec = (*Criteria)(&tc.ec)
- }
-
- sys, s := InternalNew(t, stub.Expect{Calls: slices.Concat(tc.apply, []stub.Call{{Name: stub.CallSeparator}}, tc.revert)}, tc.uid)
- defer stub.HandleExit(t)
- errApply := tc.op.apply(sys)
- s.Expects(stub.CallSeparator)
- if !reflect.DeepEqual(errApply, tc.wantErrApply) {
- t.Errorf("apply: error = %v, want %v", errApply, tc.wantErrApply)
- }
- if errApply != nil {
- goto out
- }
-
- if err := tc.op.revert(sys, ec); !reflect.DeepEqual(err, tc.wantErrRevert) {
- t.Errorf("revert: error = %v, want %v", err, tc.wantErrRevert)
- }
-
- out:
- s.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
- count := s.Pos() - 1 // separator
- if count < len(tc.apply) {
- t.Errorf("apply: %d calls, want %d", count, len(tc.apply))
- } else {
- t.Errorf("revert: %d calls, want %d", count-len(tc.apply), len(tc.revert))
- }
- })
- })
- }
- })
-}
-
-type opsBuilderTestCase struct {
- name string
- uid int
- f func(t *testing.T, sys *I)
- want []Op
- exp stub.Expect
-}
-
-func checkOpsBuilder(t *testing.T, fname string, testCases []opsBuilderTestCase) {
- t.Helper()
-
- t.Run("build", func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- sys, s := InternalNew(t, tc.exp, tc.uid)
- defer stub.HandleExit(t)
- tc.f(t, sys)
- s.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
- t.Helper()
-
- t.Errorf("%s: %d calls, want %d", fname, s.Pos(), s.Len())
- })
- if !slices.EqualFunc(sys.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
- t.Errorf("ops: %#v, want %#v", sys.ops, tc.want)
- }
- })
- }
- })
-}
-
-type opIsTestCase struct {
- name string
- op, v Op
- want bool
-}
-
-func checkOpIs(t *testing.T, testCases []opIsTestCase) {
- t.Helper()
-
- t.Run("is", func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- if got := tc.op.Is(tc.v); got != tc.want {
- t.Errorf("Is: %v, want %v", got, tc.want)
- }
- })
- }
- })
-}
-
-type opMetaTestCase struct {
- name string
- op Op
-
- wantType hst.Enablement
- wantPath string
- wantString string
-}
-
-func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
- t.Helper()
-
- t.Run("meta", func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Helper()
- t.Parallel()
-
- t.Run("type", func(t *testing.T) {
- t.Helper()
-
- if got := tc.op.Type(); got != tc.wantType {
- t.Errorf("Type: %q, want %q", got, tc.wantType)
- }
- })
-
- t.Run("path", func(t *testing.T) {
- t.Helper()
-
- if got := tc.op.Path(); got != tc.wantPath {
- t.Errorf("Path: %q, want %q", got, tc.wantPath)
- }
- })
-
- t.Run("string", func(t *testing.T) {
- t.Helper()
-
- if got := tc.op.String(); got != tc.wantString {
- t.Errorf("String: %s, want %s", got, tc.wantString)
- }
- })
- })
- }
- })
-}
-
-// 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.syscallDispatcher = k
- return sys, k.Stub
-}
-
-type kstub struct{ *stub.Stub[syscallDispatcher] }
-
-func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }
-
-func (k *kstub) stat(name string) (fi os.FileInfo, err error) {
- k.Helper()
- expect := k.Expects("stat")
- err = expect.Error(
- stub.CheckArg(k.Stub, "name", name, 0))
- if err == nil {
- fi = expect.Ret.(os.FileInfo)
- }
- return
-}
-
-func (k *kstub) open(name string) (f osFile, err error) {
- k.Helper()
- expect := k.Expects("open")
- err = expect.Error(
- stub.CheckArg(k.Stub, "name", name, 0))
- if err == nil {
- f = expect.Ret.(osFile)
- }
- return
-}
-
-func (k *kstub) mkdir(name string, perm os.FileMode) error {
- k.Helper()
- return k.Expects("mkdir").Error(
- stub.CheckArg(k.Stub, "name", name, 0),
- stub.CheckArg(k.Stub, "perm", perm, 1))
-}
-
-func (k *kstub) chmod(name string, mode os.FileMode) error {
- k.Helper()
- return k.Expects("chmod").Error(
- stub.CheckArg(k.Stub, "name", name, 0),
- stub.CheckArg(k.Stub, "mode", mode, 1))
-}
-
-func (k *kstub) link(oldname, newname string) error {
- k.Helper()
- return k.Expects("link").Error(
- stub.CheckArg(k.Stub, "oldname", oldname, 0),
- stub.CheckArg(k.Stub, "newname", newname, 1))
-}
-
-func (k *kstub) remove(name string) error {
- k.Helper()
- return k.Expects("remove").Error(
- stub.CheckArg(k.Stub, "name", name, 0))
-}
-
-func (k *kstub) println(v ...any) {
- k.Helper()
- k.Expects("println")
- if !stub.CheckArgReflect(k.Stub, "v", v, 0) {
- k.FailNow()
- }
-}
-
-func (k *kstub) aclUpdate(name string, uid int, perms ...acl.Perm) error {
- k.Helper()
- return k.Expects("aclUpdate").Error(
- stub.CheckArg(k.Stub, "name", name, 0),
- stub.CheckArg(k.Stub, "uid", uid, 1),
- stub.CheckArgReflect(k.Stub, "perms", perms, 2))
-}
-
-func (k *kstub) xcbChangeHosts(mode xcb.HostMode, family xcb.Family, address string) error {
- k.Helper()
- return k.Expects("xcbChangeHosts").Error(
- stub.CheckArg(k.Stub, "mode", mode, 0),
- stub.CheckArg(k.Stub, "family", family, 1),
- stub.CheckArg(k.Stub, "address", address, 2))
-}
-
-func (k *kstub) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *hst.BusConfig) (final *dbus.Final, err error) {
- k.Helper()
- expect := k.Expects("dbusFinalise")
-
- final = expect.Ret.(*dbus.Final)
- err = expect.Error(
- stub.CheckArg(k.Stub, "sessionBus", sessionBus, 0),
- stub.CheckArg(k.Stub, "systemBus", systemBus, 1),
- stub.CheckArgReflect(k.Stub, "session", session, 2),
- stub.CheckArgReflect(k.Stub, "system", system, 3))
- if err != nil {
- final = nil
- }
- return
-}
-
-func (k *kstub) dbusProxyStart(proxy *dbus.Proxy) error {
- k.Helper()
- return k.dbusProxySCW(k.Expects("dbusProxyStart"), proxy)
-}
-func (k *kstub) dbusProxyClose(proxy *dbus.Proxy) {
- k.Helper()
- if k.dbusProxySCW(k.Expects("dbusProxyClose"), proxy) != nil {
- k.Fail()
- }
-}
-func (k *kstub) dbusProxyWait(proxy *dbus.Proxy) error {
- k.Helper()
- return k.dbusProxySCW(k.Expects("dbusProxyWait"), proxy)
-}
-func (k *kstub) dbusProxySCW(expect *stub.Call, proxy *dbus.Proxy) error {
- k.Helper()
- v := reflect.ValueOf(proxy).Elem()
-
- if ctxV := v.FieldByName("ctx"); ctxV.IsNil() {
- k.Errorf("proxy: ctx = %s", ctxV.String())
- return os.ErrInvalid
- }
-
- finalV := v.FieldByName("final")
- if gotFinal := reflect.NewAt(finalV.Type(), unsafe.Pointer(finalV.UnsafeAddr())).Elem().Interface().(*dbus.Final); !reflect.DeepEqual(gotFinal, expect.Args[0]) {
- k.Errorf("proxy: final = %#v, want %#v", gotFinal, expect.Args[0])
- return os.ErrInvalid
- }
-
- outputV := v.FieldByName("output")
- if _, ok := reflect.NewAt(outputV.Type(), unsafe.Pointer(outputV.UnsafeAddr())).Elem().Interface().(*linePrefixWriter); !ok {
- k.Errorf("proxy: output = %s", outputV.String())
- return os.ErrInvalid
- }
-
- return expect.Err
-}
-
-func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
-
-func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
-func (k *kstub) SwapVerbose(verbose bool) bool {
- k.Helper()
- expect := k.Expects("swapVerbose")
- if expect.Error(
- stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
- k.FailNow()
- }
- return expect.Ret.(bool)
-}
-
-// ignoreValue marks a value to be ignored by the test suite.
-type ignoreValue struct{}
-
-func (k *kstub) Verbose(v ...any) {
- k.Helper()
- expect := k.Expects("verbose")
-
- // translate ignores in v
- if want, ok := expect.Args[0].([]any); ok && len(v) == len(want) {
- for i, a := range want {
- if _, ok = a.(ignoreValue); ok {
- v[i] = ignoreValue{}
- }
- }
- }
-
- if expect.Error(
- stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
- k.FailNow()
- }
-}
-
-func (k *kstub) Verbosef(format string, v ...any) {
- k.Helper()
- if k.Expects("verbosef").Error(
- stub.CheckArg(k.Stub, "format", format, 0),
- stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
- k.FailNow()
- }
-}
-
-func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
-func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
-func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }
diff --git a/system/internal/xcb/changehosts.go b/system/internal/xcb/changehosts.go
deleted file mode 100644
index 8b05614b..00000000
--- a/system/internal/xcb/changehosts.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package xcb
-
-import "errors"
-
-var ErrChangeHosts = errors.New("xcb_change_hosts() failed")
-
-func ChangeHosts(mode HostMode, family Family, address string) error {
- conn := new(connection)
- if err := conn.connect(); err != nil {
- conn.disconnect()
- return err
- } else {
- defer conn.disconnect()
- }
-
- return conn.changeHostsChecked(mode, family, address)
-}
diff --git a/system/internal/xcb/xcb.go b/system/internal/xcb/xcb.go
deleted file mode 100644
index 3dc62c3f..00000000
--- a/system/internal/xcb/xcb.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Package xcb implements X11 ChangeHosts via libxcb.
-package xcb
-
-import (
- "runtime"
- "unsafe"
-)
-
-/*
-#cgo linux pkg-config: --static xcb
-
-#include <stdlib.h>
-#include <xcb/xcb.h>
-
-static int hakurei_xcb_change_hosts_checked(xcb_connection_t *c,
- uint8_t mode, uint8_t family,
- uint16_t address_len, const uint8_t *address) {
- int ret;
- xcb_generic_error_t *e;
- xcb_void_cookie_t cookie;
-
- cookie = xcb_change_hosts_checked(c, mode, family, address_len, address);
- free((void *)address);
-
- ret = xcb_connection_has_error(c);
- if (ret != 0)
- return ret;
-
- e = xcb_request_check(c, cookie);
- if (e != NULL) {
- // don't want to deal with xcb errors
- free((void *)e);
- ret = -1;
- }
-
- return ret;
-}
-*/
-import "C"
-
-const (
- HostModeInsert = C.XCB_HOST_MODE_INSERT
- HostModeDelete = C.XCB_HOST_MODE_DELETE
-
- FamilyInternet = C.XCB_FAMILY_INTERNET
- FamilyDecnet = C.XCB_FAMILY_DECNET
- FamilyChaos = C.XCB_FAMILY_CHAOS
- FamilyServerInterpreted = C.XCB_FAMILY_SERVER_INTERPRETED
- FamilyInternet6 = C.XCB_FAMILY_INTERNET_6
-)
-
-type (
- HostMode = C.xcb_host_mode_t
- Family = C.xcb_family_t
-)
-
-func (conn *connection) changeHostsChecked(mode HostMode, family Family, address string) error {
- ret := C.hakurei_xcb_change_hosts_checked(
- conn.c,
- C.uint8_t(mode),
- C.uint8_t(family),
- C.uint16_t(len(address)),
- (*C.uint8_t)(unsafe.Pointer(C.CString(address))),
- )
- switch ret {
- case 0:
- return nil
- case -1:
- return ErrChangeHosts
- default:
- return ConnectionError(ret)
- }
-}
-
-type connection struct{ c *C.xcb_connection_t }
-
-func (conn *connection) connect() error {
- conn.c = C.xcb_connect(nil, nil)
- runtime.SetFinalizer(conn, (*connection).disconnect)
- return conn.hasError()
-}
-
-func (conn *connection) hasError() error {
- ret := C.xcb_connection_has_error(conn.c)
- if ret == 0 {
- return nil
- }
- return ConnectionError(ret)
-}
-
-func (conn *connection) disconnect() {
- C.xcb_disconnect(conn.c)
-
- // no need for a finalizer anymore
- runtime.SetFinalizer(conn, nil)
-}
-
-const (
- ConnError ConnectionError = C.XCB_CONN_ERROR
- ConnClosedExtNotSupported ConnectionError = C.XCB_CONN_CLOSED_EXT_NOTSUPPORTED
- ConnClosedMemInsufficient ConnectionError = C.XCB_CONN_CLOSED_MEM_INSUFFICIENT
- ConnClosedReqLenExceed ConnectionError = C.XCB_CONN_CLOSED_REQ_LEN_EXCEED
- ConnClosedParseErr ConnectionError = C.XCB_CONN_CLOSED_PARSE_ERR
- ConnClosedInvalidScreen ConnectionError = C.XCB_CONN_CLOSED_INVALID_SCREEN
-)
-
-// ConnectionError represents an error returned by xcb_connection_has_error.
-type ConnectionError int
-
-func (ce ConnectionError) Error() string {
- switch ce {
- case ConnError:
- return "connection error"
- case ConnClosedExtNotSupported:
- return "extension not supported"
- case ConnClosedMemInsufficient:
- return "memory not available"
- case ConnClosedReqLenExceed:
- return "request length exceeded"
- case ConnClosedParseErr:
- return "invalid display string"
- case ConnClosedInvalidScreen:
- return "server has no screen matching display"
- default:
- return "generic X11 failure"
- }
-}
diff --git a/system/link.go b/system/link.go
deleted file mode 100644
index a13adaae..00000000
--- a/system/link.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package system
-
-import (
- "fmt"
-
- "hakurei.app/container/check"
- "hakurei.app/hst"
-)
-
-// Link calls LinkFileType with the [Process] criteria.
-func (sys *I) Link(oldname, newname *check.Absolute) *I {
- return sys.LinkFileType(Process, oldname, newname)
-}
-
-// LinkFileType maintains a hardlink until its [Enablement] is no longer satisfied.
-func (sys *I) LinkFileType(et hst.Enablement, oldname, newname *check.Absolute) *I {
- sys.ops = append(sys.ops, &hardlinkOp{et, newname.String(), oldname.String()})
- return sys
-}
-
-// hardlinkOp implements [I.LinkFileType].
-type hardlinkOp struct {
- et hst.Enablement
- dst, src string
-}
-
-func (l *hardlinkOp) Type() hst.Enablement { return l.et }
-
-func (l *hardlinkOp) apply(sys *I) error {
- sys.msg.Verbose("linking", l)
- return newOpError("hardlink", sys.link(l.src, l.dst), false)
-}
-
-func (l *hardlinkOp) revert(sys *I, ec *Criteria) error {
- if ec.hasType(l.Type()) {
- sys.msg.Verbosef("removing hard link %q", l.dst)
- return newOpError("hardlink", sys.remove(l.dst), true)
- } else {
- sys.msg.Verbosef("skipping hard link %q", l.dst)
- return nil
- }
-}
-
-func (l *hardlinkOp) Is(o Op) bool {
- target, ok := o.(*hardlinkOp)
- return ok && l != nil && target != nil && *l == *target
-}
-
-func (l *hardlinkOp) Path() string { return l.src }
-func (l *hardlinkOp) String() string { return fmt.Sprintf("%q from %q", l.dst, l.src) }
diff --git a/system/link_test.go b/system/link_test.go
deleted file mode 100644
index d719931f..00000000
--- a/system/link_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package system
-
-import (
- "testing"
-
- "hakurei.app/container/stub"
- "hakurei.app/hst"
-)
-
-func TestHardlinkOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []opBehaviourTestCase{
- {"link", 0xbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
- call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(1)),
- }, &OpError{Op: "hardlink", Err: stub.UniqueError(1)}, nil, nil},
-
- {"remove", 0xbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
- call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
- call("remove", stub.ExpectArgs{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(0)),
- }, &OpError{Op: "hardlink", Err: stub.UniqueError(0), Revert: true}},
-
- {"success skip", 0xbeef, hst.EWayland | hst.EX11, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
- call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"skipping hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
- }, nil},
-
- {"success", 0xbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
- call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
- call("remove", stub.ExpectArgs{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
- }, nil},
- })
-
- checkOpsBuilder(t, "LinkFileType", []opsBuilderTestCase{
- {"type", 0xcafe, func(_ *testing.T, sys *I) {
- sys.LinkFileType(User, m("/run/user/1000/pulse/native"), m("/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"))
- }, []Op{
- &hardlinkOp{User, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- }, stub.Expect{}},
-
- {"link", 0xcafe, func(_ *testing.T, sys *I) {
- sys.Link(m("/run/user/1000/pulse/native"), m("/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"))
- }, []Op{
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- }, stub.Expect{}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"nil", (*hardlinkOp)(nil), (*hardlinkOp)(nil), false},
- {"zero", new(hardlinkOp), new(hardlinkOp), true},
-
- {"src differs",
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse"},
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- false},
-
- {"dst differs",
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6", "/run/user/1000/pulse/native"},
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- false},
-
- {"et differs",
- &hardlinkOp{User, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- false},
-
- {"equals",
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"link", &hardlinkOp{Process, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"},
- Process, "/run/user/1000/pulse/native",
- `"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse" from "/run/user/1000/pulse/native"`},
- })
-}
diff --git a/system/mkdir.go b/system/mkdir.go
deleted file mode 100644
index f375579c..00000000
--- a/system/mkdir.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package system
-
-import (
- "errors"
- "fmt"
- "os"
-
- "hakurei.app/container/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})
- return sys
-}
-
-// Ephemeral ensures the existence of a directory until its [Enablement] is no longer satisfied.
-func (sys *I) Ephemeral(et hst.Enablement, name *check.Absolute, perm os.FileMode) *I {
- sys.ops = append(sys.ops, &mkdirOp{et, name.String(), perm, true})
- return sys
-}
-
-// mkdirOp implements [I.Ensure] and [I.Ephemeral].
-type mkdirOp struct {
- et hst.Enablement
- path string
- perm os.FileMode
- ephemeral bool
-}
-
-func (m *mkdirOp) Type() hst.Enablement { 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)
- }
- // directory exists, ensure mode
- return newOpError("mkdir", sys.chmod(m.path, m.perm), false)
- } else {
- return nil
- }
-}
-
-func (m *mkdirOp) revert(sys *I, ec *Criteria) error {
- if !m.ephemeral {
- // skip non-ephemeral dir and do not log anything
- return nil
- }
-
- if ec.hasType(m.Type()) {
- sys.msg.Verbose("destroying ephemeral directory", m)
- return newOpError("mkdir", sys.remove(m.path), true)
- } else {
- sys.msg.Verbose("skipping ephemeral directory", m)
- return nil
- }
-}
-
-func (m *mkdirOp) Is(o Op) bool {
- target, ok := o.(*mkdirOp)
- return ok && m != nil && target != nil && *m == *target
-}
-
-func (m *mkdirOp) Path() string { return m.path }
-
-func (m *mkdirOp) String() string {
- t := "ensure"
- if m.ephemeral {
- t = TypeString(m.Type())
- }
- return fmt.Sprintf("mode: %s type: %s path: %q", m.perm.String(), t, m.path)
-}
diff --git a/system/mkdir_test.go b/system/mkdir_test.go
deleted file mode 100644
index e8ffde2a..00000000
--- a/system/mkdir_test.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package system
-
-import (
- "os"
- "testing"
-
- "hakurei.app/container/stub"
-)
-
-func TestMkdirOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []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),
- 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),
- 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),
- 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("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),
- 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),
- 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),
- 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),
- }, 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),
- 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("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)
- }, []Op{
- &mkdirOp{User, "/tmp/hakurei.0", 0700, false},
- }, stub.Expect{}},
-
- {"ephemeral", 0xcafe, func(_ *testing.T, sys *I) {
- sys.Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711)
- }, []Op{
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true},
- }, stub.Expect{}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"nil", (*mkdirOp)(nil), (*mkdirOp)(nil), false},
- {"zero", new(mkdirOp), new(mkdirOp), true},
-
- {"ephemeral differs",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- false},
-
- {"perm differs",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0701, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- false},
-
- {"path differs",
- &mkdirOp{Process, "/tmp/hakurei.1/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- false},
-
- {"et differs",
- &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- false},
-
- {"equals",
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"ensure", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, false},
- User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9",
- `mode: -rwx------ type: ensure path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`},
-
- {"ephemeral", &mkdirOp{User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0700, true},
- User, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9",
- `mode: -rwx------ type: user path: "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"`},
- })
-}
diff --git a/system/output.go b/system/output.go
deleted file mode 100644
index 71acace4..00000000
--- a/system/output.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package system
-
-import (
- "errors"
- "net"
- "os"
-
- "hakurei.app/container"
- "hakurei.app/message"
-)
-
-// OpError is returned by [I.Commit] and [I.Revert].
-type OpError struct {
- Op string
- Err error
- Msg string
- Revert bool
-}
-
-func (e *OpError) Unwrap() error { return e.Err }
-func (e *OpError) Error() string {
- if e.Msg != "" {
- return e.Msg
- }
-
- switch {
- case errors.As(e.Err, new(*os.PathError)),
- errors.As(e.Err, new(*os.LinkError)),
- errors.As(e.Err, new(*net.OpError)),
- errors.As(e.Err, new(*container.StartError)):
- return e.Err.Error()
-
- default:
- if !e.Revert {
- return "apply " + e.Op + ": " + e.Err.Error()
- } else {
- return "revert " + e.Op + ": " + e.Err.Error()
- }
- }
-}
-
-func (e *OpError) Message() string {
- switch {
- case e.Msg != "":
- return e.Error()
-
- default:
- return "cannot " + e.Error()
- }
-}
-
-// newOpError returns an [OpError] without a message string.
-func newOpError(op string, err error, revert bool) error {
- if err == nil {
- return nil
- }
- return &OpError{op, err, "", revert}
-}
-
-// newOpErrorMessage returns an [OpError] with an overriding message string.
-func newOpErrorMessage(op string, err error, message string, revert bool) error {
- if err == nil {
- return nil
- }
- return &OpError{op, err, message, revert}
-}
-
-func printJoinedError(println func(v ...any), fallback string, err error) {
- var joinErr interface {
- Unwrap() []error
- error
- }
- if !errors.As(err, &joinErr) {
- if m, ok := message.GetMessage(err); ok {
- println(m)
- } else {
- println(fallback, err)
- }
- } else {
- for _, err = range joinErr.Unwrap() {
- if m, ok := message.GetMessage(err); ok {
- println(m)
- } else {
- println(err.Error())
- }
- }
- }
-}
diff --git a/system/output_test.go b/system/output_test.go
deleted file mode 100644
index c0cbfdfe..00000000
--- a/system/output_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package system
-
-import (
- "errors"
- "net"
- "os"
- "reflect"
- "syscall"
- "testing"
-
- "hakurei.app/container"
- "hakurei.app/message"
-)
-
-func TestOpError(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- err error
- s string
- is error
- isF error
- msg string
- }{
- {"message", newOpErrorMessage("dbus", ErrDBusConfig,
- "attempted to create message bus proxy args without session bus config", false),
- "attempted to create message bus proxy args without session bus config",
- ErrDBusConfig, syscall.ENOTRECOVERABLE,
- "attempted to create message bus proxy args without session bus config"},
-
- {"apply", newOpError("tmpfile", syscall.EBADE, false),
- "apply tmpfile: invalid exchange",
- syscall.EBADE, syscall.EBADF,
- "cannot apply tmpfile: invalid exchange"},
-
- {"revert", newOpError("wayland", syscall.EBADF, true),
- "revert wayland: bad file descriptor",
- syscall.EBADF, syscall.EBADE,
- "cannot revert wayland: bad file descriptor"},
-
- {"path", newOpError("tmpfile", &os.PathError{Op: "stat", Path: "/run/dbus", Err: syscall.EISDIR}, false),
- "stat /run/dbus: is a directory",
- syscall.EISDIR, syscall.ENOTDIR,
- "cannot stat /run/dbus: is a directory"},
-
- {"net", newOpError("wayland", &net.OpError{Op: "dial", Net: "unix", Addr: &net.UnixAddr{Name: "/run/user/1000/wayland-1", Net: "unix"}, Err: syscall.ENOENT}, false),
- "dial unix /run/user/1000/wayland-1: no such file or directory",
- syscall.ENOENT, syscall.EPERM,
- "cannot dial unix /run/user/1000/wayland-1: no such file or directory"},
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- t.Run("error", func(t *testing.T) {
- if got := tc.err.Error(); got != tc.s {
- t.Errorf("Error: %q, want %q", got, tc.s)
- }
- })
-
- t.Run("is", func(t *testing.T) {
- if !errors.Is(tc.err, tc.is) {
- t.Error("Is: unexpected false")
- }
- if errors.Is(tc.err, tc.isF) {
- t.Error("Is: unexpected true")
- }
- })
-
- t.Run("msg", func(t *testing.T) {
- if got, ok := message.GetMessage(tc.err); !ok {
- if tc.msg != "" {
- t.Errorf("GetMessage: err does not implement MessageError")
- }
- return
- } else if got != tc.msg {
- t.Errorf("GetMessage: %q, want %q", got, tc.msg)
- }
- })
- })
- }
-
- t.Run("new", func(t *testing.T) {
- if err := newOpError("check", nil, false); err != nil {
- t.Errorf("newOpError: %v", err)
- }
- if err := newOpErrorMessage("check", nil, "", false); err != nil {
- t.Errorf("newOpErrorMessage: %v", err)
- }
- })
-}
-
-func TestPrintJoinedError(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- err error
- want [][]any
- }{
- {"nil", nil, [][]any{{"not a joined error:", nil}}},
- {"single", errors.Join(syscall.EINVAL), [][]any{{"invalid argument"}}},
-
- {"unwrapped", syscall.EINVAL, [][]any{{"not a joined error:", syscall.EINVAL}}},
- {"unwrapped message", &OpError{
- Op: "meow",
- Err: syscall.EBADFD,
- }, [][]any{
- {"cannot apply meow: file descriptor in bad state"},
- }},
-
- {"many", errors.Join(syscall.ENOTRECOVERABLE, syscall.ETIMEDOUT, syscall.EBADFD), [][]any{
- {"state not recoverable"},
- {"connection timed out"},
- {"file descriptor in bad state"},
- }},
- {"many message", errors.Join(
- &container.StartError{Step: "meow", Err: syscall.ENOMEM},
- &os.PathError{Op: "meow", Path: "/proc/nonexistent", Err: syscall.ENOSYS},
- &os.LinkError{Op: "link", Old: "/etc", New: "/proc/nonexistent", Err: syscall.ENOENT},
- &OpError{Op: "meow", Err: syscall.ENODEV, Revert: true},
- ), [][]any{
- {"cannot meow: cannot allocate memory"},
- {"meow /proc/nonexistent: function not implemented"},
- {"link /etc /proc/nonexistent: no such file or directory"},
- {"cannot revert meow: no such device"},
- }},
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- var got [][]any
- printJoinedError(func(v ...any) { got = append(got, v) }, "not a joined error:", tc.err)
- if !reflect.DeepEqual(got, tc.want) {
- t.Errorf("printJoinedError: %#v, want %#v", got, tc.want)
- }
- })
- }
-}
diff --git a/system/system.go b/system/system.go
deleted file mode 100644
index cadaab04..00000000
--- a/system/system.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Package system provides helpers to apply and revert groups of operations to the system.
-package system
-
-import (
- "context"
- "errors"
- "strings"
-
- "hakurei.app/hst"
- "hakurei.app/message"
-)
-
-const (
- // User type is reverted at final instance exit.
- User = hst.EM << iota
- // Process type is unconditionally reverted on exit.
- Process
-
- CM
-)
-
-// Criteria specifies types of Op to revert.
-type Criteria hst.Enablement
-
-func (ec *Criteria) hasType(t hst.Enablement) bool {
- // nil criteria: revert everything except User
- if ec == nil {
- return t != User
- }
-
- return hst.Enablement(*ec)&t != 0
-}
-
-// Op is a reversible system operation.
-type Op interface {
- // Type returns [Op]'s enablement type, for matching a revert criteria.
- Type() hst.Enablement
-
- apply(sys *I) error
- revert(sys *I, ec *Criteria) error
-
- Is(o Op) bool
- Path() string
- String() string
-}
-
-// TypeString extends [Enablement.String] to support [User] and [Process].
-func TypeString(e hst.Enablement) string {
- switch e {
- case User:
- return "user"
- case Process:
- return "process"
- default:
- buf := new(strings.Builder)
- buf.Grow(48)
- if v := e &^ User &^ Process; v != 0 {
- buf.WriteString(v.String())
- }
-
- for i := User; i < CM; i <<= 1 {
- if e&i != 0 {
- buf.WriteString(", " + TypeString(i))
- }
- }
- return strings.TrimPrefix(buf.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 {
- _ noCopy
-
- uid int
- 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
-
- msg message.Msg
- syscallDispatcher
-}
-
-func (sys *I) UID() int { return sys.uid }
-
-// Equal returns whether all [Op] instances held by sys matches that of target.
-func (sys *I) Equal(target *I) bool {
- if sys == nil || target == nil || sys.uid != target.uid || len(sys.ops) != len(target.ops) {
- return false
- }
-
- for i, o := range sys.ops {
- if !o.Is(target.ops[i]) {
- return false
- }
- }
-
- return true
-}
-
-// Commit applies all [Op] held by [I] and reverts all successful [Op] on first error encountered.
-// Commit must not be called more than once.
-func (sys *I) Commit() error {
- if sys.committed {
- panic("attempting to commit twice")
- }
- sys.committed = true
-
- sp := New(sys.ctx, sys.msg, sys.uid)
- sp.syscallDispatcher = sys.syscallDispatcher
- sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
- defer func() {
- // sp is set to nil when all ops are applied
- if sp != nil {
- // rollback partial commit
- sys.msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
- if err := sp.Revert(nil); err != nil {
- printJoinedError(sys.println, "cannot revert partial commit:", err)
- }
- }
- }()
-
- for _, o := range sys.ops {
- if err := o.apply(sys); err != nil {
- return err
- } else {
- // register partial commit
- sp.ops = append(sp.ops, o)
- }
- }
-
- // disarm partial commit rollback
- sp = nil
- return nil
-}
-
-// Revert reverts all [Op] meeting [Criteria] held by [I].
-func (sys *I) Revert(ec *Criteria) error {
- if sys.reverted {
- panic("attempting to revert twice")
- }
- sys.reverted = true
-
- // collect errors
- errs := make([]error, len(sys.ops))
- for i := range sys.ops {
- errs[i] = sys.ops[len(sys.ops)-i-1].revert(sys, ec)
- }
-
- // errors.Join filters nils
- return errors.Join(errs...)
-}
-
-// noCopy may be added to structs which must not be copied
-// after the first use.
-//
-// See https://golang.org/issues/8005#issuecomment-190753527
-// for details.
-//
-// Note that it must not be embedded, due to the Lock and Unlock methods.
-type noCopy struct{}
-
-// Lock is a no-op used by -copylocks checker from `go vet`.
-func (*noCopy) Lock() {}
-func (*noCopy) Unlock() {}
diff --git a/system/system_test.go b/system/system_test.go
deleted file mode 100644
index 83df9dee..00000000
--- a/system/system_test.go
+++ /dev/null
@@ -1,331 +0,0 @@
-package system
-
-import (
- "errors"
- "os"
- "reflect"
- "slices"
- "strconv"
- "testing"
-
- "hakurei.app/container/check"
- "hakurei.app/container/stub"
- "hakurei.app/hst"
- "hakurei.app/message"
- "hakurei.app/system/internal/xcb"
-)
-
-func TestCriteria(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- ec, t hst.Enablement
- want bool
- }{
- {"nil", 0xff, hst.EWayland, true},
- {"nil user", 0xff, User, false},
- {"all", hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse | User | Process, Process, true},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- var criteria *Criteria
- if tc.ec != 0xff {
- criteria = (*Criteria)(&tc.ec)
- }
- if got := criteria.hasType(tc.t); got != tc.want {
- t.Errorf("hasType: got %v, want %v",
- got, tc.want)
- }
- })
- }
-}
-
-func TestTypeString(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- e hst.Enablement
- want string
- }{
- {hst.EWayland, hst.EWayland.String()},
- {hst.EX11, hst.EX11.String()},
- {hst.EDBus, hst.EDBus.String()},
- {hst.EPulse, hst.EPulse.String()},
- {User, "user"},
- {Process, "process"},
- {User | Process, "user, process"},
- {hst.EWayland | User | Process, "wayland, user, process"},
- {hst.EX11 | Process, "x11, process"},
- }
-
- for _, tc := range testCases {
- t.Run("label type string "+strconv.Itoa(int(tc.e)), func(t *testing.T) {
- t.Parallel()
- if got := TypeString(tc.e); got != tc.want {
- t.Errorf("TypeString: %q, want %q", got, tc.want)
- }
- })
- }
-}
-
-func TestNew(t *testing.T) {
- t.Parallel()
-
- t.Run("panic", func(t *testing.T) {
- t.Run("ctx", func(t *testing.T) {
- defer func() {
- want := "invalid call to New"
- if r := recover(); r != want {
- t.Errorf("recover: %v, want %v", r, want)
- }
- }()
- New(nil, message.New(nil), 0)
- })
-
- t.Run("msg", func(t *testing.T) {
- defer func() {
- want := "invalid call to New"
- if r := recover(); r != want {
- t.Errorf("recover: %v, want %v", r, want)
- }
- }()
- New(t.Context(), nil, 0)
- })
-
- t.Run("uid", func(t *testing.T) {
- defer func() {
- want := "invalid call to New"
- if r := recover(); r != want {
- t.Errorf("recover: %v, want %v", r, want)
- }
- }()
- New(t.Context(), message.New(nil), -1)
- })
- })
-
- sys := New(t.Context(), message.New(nil), 0xbeef)
- if sys.ctx == nil {
- t.Error("New: ctx = nil")
- }
- if got := sys.UID(); got != 0xbeef {
- t.Errorf("UID: %d", got)
- }
-}
-
-func TestEqual(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- sys *I
- v *I
- want bool
- }{
- {"simple UID",
- New(t.Context(), message.New(nil), 150),
- New(t.Context(), message.New(nil), 150),
- true},
-
- {"simple UID differ",
- New(t.Context(), message.New(nil), 150),
- New(t.Context(), message.New(nil), 151),
- false},
-
- {"simple UID nil",
- New(t.Context(), message.New(nil), 150),
- nil,
- false},
-
- {"op length mismatch",
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos"),
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- false},
-
- {"op value mismatch",
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0644),
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- false},
-
- {"op type mismatch",
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Wayland(m("/proc/nonexistent/dst"), m("/proc/nonexistent/src"), "\x00", "\x00"),
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- false},
-
- {"op equals",
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- New(t.Context(), message.New(nil), 150).
- ChangeHosts("chronos").
- Ensure(m("/run"), 0755),
- true},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if tc.sys.Equal(tc.v) != tc.want {
- t.Errorf("Equal: %v, want %v", !tc.want, tc.want)
- }
- })
- }
-}
-
-func TestCommitRevert(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- name string
- f func(sys *I)
- ec hst.Enablement
-
- commit []stub.Call
- wantErrCommit error
-
- revert []stub.Call
- wantErrRevert error
- }{
- {"apply xhost partial mkdir", func(sys *I) {
- sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
- ChangeHosts("chronos")
- }, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
- 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("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).
- ChangeHosts("chronos")
- }, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
- 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("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).
- ChangeHosts("chronos")
- }, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
- 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("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, stub.UniqueError(0)),
- }, errors.Join(
- &OpError{Op: "xhost", Err: stub.UniqueError(1), Revert: true},
- &OpError{Op: "mkdir", Err: stub.UniqueError(0), Revert: true})},
-
- {"success", func(sys *I) {
- sys.
- Ephemeral(Process, m("/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"), 0711).
- ChangeHosts("chronos")
- }, 0xff, []stub.Call{
- call("verbose", stub.ExpectArgs{[]any{"ensuring directory", &mkdirOp{Process, "/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9", 0711, true}}}, nil, nil),
- 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("remove", stub.ExpectArgs{"/tmp/hakurei.0/f2f3bcd492d0266438fa9bf164fe90d9"}, nil, nil),
- }, nil},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
-
- var ec *Criteria
- if tc.ec != 0xff {
- ec = (*Criteria)(&tc.ec)
- }
-
- sys, s := InternalNew(t, stub.Expect{Calls: slices.Concat(tc.commit, []stub.Call{{Name: stub.CallSeparator}}, tc.revert)}, 0xbad)
- defer stub.HandleExit(t)
- tc.f(sys)
- errCommit := sys.Commit()
- s.Expects(stub.CallSeparator)
- if !reflect.DeepEqual(errCommit, tc.wantErrCommit) {
- t.Errorf("Commit: error = %v, want %v", errCommit, tc.wantErrCommit)
- }
- if errCommit != nil {
- goto out
- }
-
- if err := sys.Revert(ec); !reflect.DeepEqual(err, tc.wantErrRevert) {
- t.Errorf("Revert: error = %v, want %v", err, tc.wantErrRevert)
- }
-
- out:
- s.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
- count := s.Pos() - 1 // separator
- if count < len(tc.commit) {
- t.Errorf("Commit: %d calls, want %d", count, len(tc.commit))
- } else {
- t.Errorf("Revert: %d calls, want %d", count-len(tc.commit), len(tc.revert))
- }
- })
- })
- }
-
- t.Run("panic", func(t *testing.T) {
- t.Run("committed", func(t *testing.T) {
- defer func() {
- want := "attempting to commit twice"
- if r := recover(); r != want {
- t.Errorf("Commit: panic = %v, want %v", r, want)
- }
- }()
- _ = (&I{committed: true}).Commit()
- })
-
- t.Run("reverted", func(t *testing.T) {
- defer func() {
- want := "attempting to revert twice"
- if r := recover(); r != want {
- t.Errorf("Revert: panic = %v, want %v", r, want)
- }
- }()
- _ = (&I{reverted: true}).Revert(nil)
- })
- })
-}
-
-func TestNop(t *testing.T) {
- // these do nothing
- new(noCopy).Unlock()
- new(noCopy).Lock()
-}
-
-func m(pathname string) *check.Absolute { return check.MustAbs(pathname) }
diff --git a/system/wayland.go b/system/wayland.go
deleted file mode 100644
index bb144d8c..00000000
--- a/system/wayland.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package system
-
-import (
- "errors"
- "fmt"
- "os"
-
- "hakurei.app/container/check"
- "hakurei.app/hst"
- "hakurei.app/system/acl"
- "hakurei.app/system/wayland"
-)
-
-type waylandConn interface {
- Attach(p string) (err error)
- Bind(pathname, appID, instanceID string) (*os.File, error)
- Close() error
-}
-
-// Wayland maintains a wayland socket with security-context-v1 attached via [wayland].
-// The socket stops accepting connections once the pipe referred to by sync is closed.
-// The socket is pathname only and is destroyed on revert.
-func (sys *I) Wayland(dst, src *check.Absolute, appID, instanceID string) *I {
- sys.ops = append(sys.ops, &waylandOp{nil,
- dst.String(), src.String(),
- appID, instanceID,
- new(wayland.Conn)})
- return sys
-}
-
-// waylandOp implements [I.Wayland].
-type waylandOp struct {
- sync *os.File
- dst, src string
- appID, instanceID string
-
- conn waylandConn
-}
-
-func (w *waylandOp) Type() hst.Enablement { return Process }
-
-func (w *waylandOp) apply(sys *I) error {
- if err := w.conn.Attach(w.src); err != nil {
- return newOpError("wayland", err, false)
- } else {
- sys.msg.Verbosef("wayland attached on %q", w.src)
- }
-
- if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
- return newOpError("wayland", err, false)
- } else {
- w.sync = sp
- sys.msg.Verbosef("wayland listening on %q", w.dst)
- if err = sys.chmod(w.dst, 0); err != nil {
- return newOpError("wayland", err, false)
- }
- return newOpError("wayland", sys.aclUpdate(w.dst, sys.uid, acl.Read, acl.Write, acl.Execute), false)
- }
-}
-
-func (w *waylandOp) revert(sys *I, _ *Criteria) error {
- var (
- hangupErr error
- closeErr error
- removeErr error
- )
-
- sys.msg.Verbosef("detaching from wayland on %q", w.src)
- if w.sync != nil {
- hangupErr = w.sync.Close()
- }
- closeErr = w.conn.Close()
-
- sys.msg.Verbosef("removing wayland socket on %q", w.dst)
- if err := sys.remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
- removeErr = err
- }
-
- return newOpError("wayland", errors.Join(hangupErr, closeErr, removeErr), true)
-}
-
-func (w *waylandOp) Is(o Op) bool {
- target, ok := o.(*waylandOp)
- return ok && w != nil && target != nil &&
- w.dst == target.dst && w.src == target.src &&
- w.appID == target.appID && w.instanceID == target.instanceID
-}
-
-func (w *waylandOp) Path() string { return w.dst }
-func (w *waylandOp) String() string { return fmt.Sprintf("wayland socket at %q", w.dst) }
diff --git a/system/wayland/conn.go b/system/wayland/conn.go
deleted file mode 100644
index 86e0a288..00000000
--- a/system/wayland/conn.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Package wayland implements Wayland security_context_v1 protocol.
-package wayland
-
-import (
- "errors"
- "net"
- "os"
- "runtime"
- "sync"
- "syscall"
-)
-
-// Conn represents a connection to the wayland display server.
-type Conn struct {
- conn *net.UnixConn
-
- done chan struct{}
- doneOnce sync.Once
-
- mu sync.Mutex
-}
-
-// Attach connects Conn to a wayland socket.
-func (c *Conn) Attach(p string) (err error) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- if c.conn != nil {
- return errors.New("socket already attached")
- }
-
- c.conn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: p, Net: "unix"})
- return
-}
-
-// Close releases resources and closes the connection to the wayland compositor.
-func (c *Conn) Close() error {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- if c.done == nil {
- return errors.New("no socket bound")
- }
-
- c.doneOnce.Do(func() {
- c.done <- struct{}{}
- <-c.done
- })
-
- // closed by wayland
- runtime.SetFinalizer(c.conn, nil)
- return nil
-}
-
-// Bind binds the new socket to pathname.
-func (c *Conn) Bind(pathname, appID, instanceID string) (*os.File, error) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- if c.conn == nil {
- return nil, errors.New("socket not attached")
- }
- if c.done != nil {
- return nil, errors.New("socket already bound")
- }
-
- if rc, err := c.conn.SyscallConn(); err != nil {
- // unreachable
- return nil, err
- } else {
- c.done = make(chan struct{})
- return bindRawConn(c.done, rc, pathname, appID, instanceID)
- }
-}
-
-func bindRawConn(done chan struct{}, rc syscall.RawConn, p, appID, instanceID string) (*os.File, error) {
- var syncPipe [2]*os.File
-
- if r, w, err := os.Pipe(); err != nil {
- return nil, err
- } else {
- syncPipe[0] = r
- syncPipe[1] = w
- }
-
- setupDone := make(chan error, 1) // does not block with c.done
-
- go func() {
- if err := rc.Control(func(fd uintptr) {
- // prevent runtime from closing the read end of sync fd
- runtime.SetFinalizer(syncPipe[0], nil)
-
- // allow the Bind method to return after setup
- setupDone <- bind(fd, p, appID, instanceID, syncPipe[0].Fd())
- close(setupDone)
-
- // keep socket alive until done is requested
- <-done
- runtime.KeepAlive(syncPipe[1])
- }); err != nil {
- setupDone <- err
- }
-
- // notify Close that rc.Control has returned
- close(done)
- }()
-
- // return write end of the pipe
- return syncPipe[1], <-setupDone
-}
-
-func bind(fd uintptr, p, appID, instanceID string, syncFd uintptr) error {
- // ensure p is available
- if f, err := os.Create(p); err != nil {
- return err
- } else if err = f.Close(); err != nil {
- return err
- } else if err = os.Remove(p); err != nil {
- return err
- }
-
- return bindWaylandFd(p, fd, appID, instanceID, syncFd)
-}
diff --git a/system/wayland/consts.go b/system/wayland/deprecated.go
index 672e6392..bf364d9a 100644
--- a/system/wayland/consts.go
+++ b/system/wayland/deprecated.go
@@ -1,5 +1,17 @@
+// Package wayland exposes the internal/system/wayland package.
+//
+// Deprecated: This package will be removed in 0.4.
package wayland
+import (
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/internal/system/wayland"
+)
+
+// Conn represents a connection to the wayland display server.
+type Conn = wayland.Conn
+
const (
// WaylandDisplay contains the name of the server socket
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
@@ -7,9 +19,9 @@ const (
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171)
// or used as-is if absolute
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176).
- WaylandDisplay = "WAYLAND_DISPLAY"
+ WaylandDisplay = wayland.Display
// FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149).
- FallbackName = "wayland-0"
+ FallbackName = wayland.FallbackName
)
diff --git a/system/wayland/security-context-v1-protocol.c b/system/wayland/security-context-v1-protocol.c
deleted file mode 100644
index 5b15b1b4..00000000
--- a/system/wayland/security-context-v1-protocol.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/* Generated by wayland-scanner 1.23.1 */
-
-/*
- * Copyright © 2021 Simon Ser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- */
-
-#include <stdbool.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include "wayland-util.h"
-
-#ifndef __has_attribute
-# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
-#endif
-
-#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
-#define WL_PRIVATE __attribute__ ((visibility("hidden")))
-#else
-#define WL_PRIVATE
-#endif
-
-extern const struct wl_interface wp_security_context_v1_interface;
-
-static const struct wl_interface *security_context_v1_types[] = {
- NULL,
- &wp_security_context_v1_interface,
- NULL,
- NULL,
-};
-
-static const struct wl_message wp_security_context_manager_v1_requests[] = {
- { "destroy", "", security_context_v1_types + 0 },
- { "create_listener", "nhh", security_context_v1_types + 1 },
-};
-
-WL_PRIVATE const struct wl_interface wp_security_context_manager_v1_interface = {
- "wp_security_context_manager_v1", 1,
- 2, wp_security_context_manager_v1_requests,
- 0, NULL,
-};
-
-static const struct wl_message wp_security_context_v1_requests[] = {
- { "destroy", "", security_context_v1_types + 0 },
- { "set_sandbox_engine", "s", security_context_v1_types + 0 },
- { "set_app_id", "s", security_context_v1_types + 0 },
- { "set_instance_id", "s", security_context_v1_types + 0 },
- { "commit", "", security_context_v1_types + 0 },
-};
-
-WL_PRIVATE const struct wl_interface wp_security_context_v1_interface = {
- "wp_security_context_v1", 1,
- 5, wp_security_context_v1_requests,
- 0, NULL,
-};
-
diff --git a/system/wayland/security-context-v1-protocol.h b/system/wayland/security-context-v1-protocol.h
deleted file mode 100644
index f973619e..00000000
--- a/system/wayland/security-context-v1-protocol.h
+++ /dev/null
@@ -1,392 +0,0 @@
-/* Generated by wayland-scanner 1.23.1 */
-
-#ifndef SECURITY_CONTEXT_V1_CLIENT_PROTOCOL_H
-#define SECURITY_CONTEXT_V1_CLIENT_PROTOCOL_H
-
-#include <stdint.h>
-#include <stddef.h>
-#include "wayland-client.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @page page_security_context_v1 The security_context_v1 protocol
- * @section page_ifaces_security_context_v1 Interfaces
- * - @subpage page_iface_wp_security_context_manager_v1 - client security context manager
- * - @subpage page_iface_wp_security_context_v1 - client security context
- * @section page_copyright_security_context_v1 Copyright
- * <pre>
- *
- * Copyright © 2021 Simon Ser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a
- * copy of this software and associated documentation files (the "Software"),
- * to deal in the Software without restriction, including without limitation
- * the rights to use, copy, modify, merge, publish, distribute, sublicense,
- * and/or sell copies of the Software, and to permit persons to whom the
- * Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice (including the next
- * paragraph) shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- * DEALINGS IN THE SOFTWARE.
- * </pre>
- */
-struct wp_security_context_manager_v1;
-struct wp_security_context_v1;
-
-#ifndef WP_SECURITY_CONTEXT_MANAGER_V1_INTERFACE
-#define WP_SECURITY_CONTEXT_MANAGER_V1_INTERFACE
-/**
- * @page page_iface_wp_security_context_manager_v1 wp_security_context_manager_v1
- * @section page_iface_wp_security_context_manager_v1_desc Description
- *
- * This interface allows a client to register a new Wayland connection to
- * the compositor and attach a security context to it.
- *
- * This is intended to be used by sandboxes. Sandbox engines attach a
- * security context to all connections coming from inside the sandbox. The
- * compositor can then restrict the features that the sandboxed connections
- * can use.
- *
- * Compositors should forbid nesting multiple security contexts by not
- * exposing wp_security_context_manager_v1 global to clients with a security
- * context attached, or by sending the nested protocol error. Nested
- * security contexts are dangerous because they can potentially allow
- * privilege escalation of a sandboxed client.
- *
- * Warning! The protocol described in this file is currently in the testing
- * phase. Backward compatible changes may be added together with the
- * corresponding interface version bump. Backward incompatible changes can
- * only be done by creating a new major version of the extension.
- * @section page_iface_wp_security_context_manager_v1_api API
- * See @ref iface_wp_security_context_manager_v1.
- */
-/**
- * @defgroup iface_wp_security_context_manager_v1 The wp_security_context_manager_v1 interface
- *
- * This interface allows a client to register a new Wayland connection to
- * the compositor and attach a security context to it.
- *
- * This is intended to be used by sandboxes. Sandbox engines attach a
- * security context to all connections coming from inside the sandbox. The
- * compositor can then restrict the features that the sandboxed connections
- * can use.
- *
- * Compositors should forbid nesting multiple security contexts by not
- * exposing wp_security_context_manager_v1 global to clients with a security
- * context attached, or by sending the nested protocol error. Nested
- * security contexts are dangerous because they can potentially allow
- * privilege escalation of a sandboxed client.
- *
- * Warning! The protocol described in this file is currently in the testing
- * phase. Backward compatible changes may be added together with the
- * corresponding interface version bump. Backward incompatible changes can
- * only be done by creating a new major version of the extension.
- */
-extern const struct wl_interface wp_security_context_manager_v1_interface;
-#endif
-#ifndef WP_SECURITY_CONTEXT_V1_INTERFACE
-#define WP_SECURITY_CONTEXT_V1_INTERFACE
-/**
- * @page page_iface_wp_security_context_v1 wp_security_context_v1
- * @section page_iface_wp_security_context_v1_desc Description
- *
- * The security context allows a client to register a new client and attach
- * security context metadata to the connections.
- *
- * When both are set, the combination of the application ID and the sandbox
- * engine must uniquely identify an application. The same application ID
- * will be used across instances (e.g. if the application is restarted, or
- * if the application is started multiple times).
- *
- * When both are set, the combination of the instance ID and the sandbox
- * engine must uniquely identify a running instance of an application.
- * @section page_iface_wp_security_context_v1_api API
- * See @ref iface_wp_security_context_v1.
- */
-/**
- * @defgroup iface_wp_security_context_v1 The wp_security_context_v1 interface
- *
- * The security context allows a client to register a new client and attach
- * security context metadata to the connections.
- *
- * When both are set, the combination of the application ID and the sandbox
- * engine must uniquely identify an application. The same application ID
- * will be used across instances (e.g. if the application is restarted, or
- * if the application is started multiple times).
- *
- * When both are set, the combination of the instance ID and the sandbox
- * engine must uniquely identify a running instance of an application.
- */
-extern const struct wl_interface wp_security_context_v1_interface;
-#endif
-
-#ifndef WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_ENUM
-#define WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_ENUM
-enum wp_security_context_manager_v1_error {
- /**
- * listening socket FD is invalid
- */
- WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_INVALID_LISTEN_FD = 1,
- /**
- * nested security contexts are forbidden
- */
- WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_NESTED = 2,
-};
-#endif /* WP_SECURITY_CONTEXT_MANAGER_V1_ERROR_ENUM */
-
-#define WP_SECURITY_CONTEXT_MANAGER_V1_DESTROY 0
-#define WP_SECURITY_CONTEXT_MANAGER_V1_CREATE_LISTENER 1
-
-
-/**
- * @ingroup iface_wp_security_context_manager_v1
- */
-#define WP_SECURITY_CONTEXT_MANAGER_V1_DESTROY_SINCE_VERSION 1
-/**
- * @ingroup iface_wp_security_context_manager_v1
- */
-#define WP_SECURITY_CONTEXT_MANAGER_V1_CREATE_LISTENER_SINCE_VERSION 1
-
-/** @ingroup iface_wp_security_context_manager_v1 */
-static inline void
-wp_security_context_manager_v1_set_user_data(struct wp_security_context_manager_v1 *wp_security_context_manager_v1, void *user_data)
-{
- wl_proxy_set_user_data((struct wl_proxy *) wp_security_context_manager_v1, user_data);
-}
-
-/** @ingroup iface_wp_security_context_manager_v1 */
-static inline void *
-wp_security_context_manager_v1_get_user_data(struct wp_security_context_manager_v1 *wp_security_context_manager_v1)
-{
- return wl_proxy_get_user_data((struct wl_proxy *) wp_security_context_manager_v1);
-}
-
-static inline uint32_t
-wp_security_context_manager_v1_get_version(struct wp_security_context_manager_v1 *wp_security_context_manager_v1)
-{
- return wl_proxy_get_version((struct wl_proxy *) wp_security_context_manager_v1);
-}
-
-/**
- * @ingroup iface_wp_security_context_manager_v1
- *
- * Destroy the manager. This doesn't destroy objects created with the
- * manager.
- */
-static inline void
-wp_security_context_manager_v1_destroy(struct wp_security_context_manager_v1 *wp_security_context_manager_v1)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_manager_v1,
- WP_SECURITY_CONTEXT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_manager_v1), WL_MARSHAL_FLAG_DESTROY);
-}
-
-/**
- * @ingroup iface_wp_security_context_manager_v1
- *
- * Creates a new security context with a socket listening FD.
- *
- * The compositor will accept new client connections on listen_fd.
- * listen_fd must be ready to accept new connections when this request is
- * sent by the client. In other words, the client must call bind(2) and
- * listen(2) before sending the FD.
- *
- * close_fd is a FD that will signal hangup when the compositor should stop
- * accepting new connections on listen_fd.
- *
- * The compositor must continue to accept connections on listen_fd when
- * the Wayland client which created the security context disconnects.
- *
- * After sending this request, closing listen_fd and close_fd remains the
- * only valid operation on them.
- */
-static inline struct wp_security_context_v1 *
-wp_security_context_manager_v1_create_listener(struct wp_security_context_manager_v1 *wp_security_context_manager_v1, int32_t listen_fd, int32_t close_fd)
-{
- struct wl_proxy *id;
-
- id = wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_manager_v1,
- WP_SECURITY_CONTEXT_MANAGER_V1_CREATE_LISTENER, &wp_security_context_v1_interface, wl_proxy_get_version((struct wl_proxy *) wp_security_context_manager_v1), 0, NULL, listen_fd, close_fd);
-
- return (struct wp_security_context_v1 *) id;
-}
-
-#ifndef WP_SECURITY_CONTEXT_V1_ERROR_ENUM
-#define WP_SECURITY_CONTEXT_V1_ERROR_ENUM
-enum wp_security_context_v1_error {
- /**
- * security context has already been committed
- */
- WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED = 1,
- /**
- * metadata has already been set
- */
- WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET = 2,
- /**
- * metadata is invalid
- */
- WP_SECURITY_CONTEXT_V1_ERROR_INVALID_METADATA = 3,
-};
-#endif /* WP_SECURITY_CONTEXT_V1_ERROR_ENUM */
-
-#define WP_SECURITY_CONTEXT_V1_DESTROY 0
-#define WP_SECURITY_CONTEXT_V1_SET_SANDBOX_ENGINE 1
-#define WP_SECURITY_CONTEXT_V1_SET_APP_ID 2
-#define WP_SECURITY_CONTEXT_V1_SET_INSTANCE_ID 3
-#define WP_SECURITY_CONTEXT_V1_COMMIT 4
-
-
-/**
- * @ingroup iface_wp_security_context_v1
- */
-#define WP_SECURITY_CONTEXT_V1_DESTROY_SINCE_VERSION 1
-/**
- * @ingroup iface_wp_security_context_v1
- */
-#define WP_SECURITY_CONTEXT_V1_SET_SANDBOX_ENGINE_SINCE_VERSION 1
-/**
- * @ingroup iface_wp_security_context_v1
- */
-#define WP_SECURITY_CONTEXT_V1_SET_APP_ID_SINCE_VERSION 1
-/**
- * @ingroup iface_wp_security_context_v1
- */
-#define WP_SECURITY_CONTEXT_V1_SET_INSTANCE_ID_SINCE_VERSION 1
-/**
- * @ingroup iface_wp_security_context_v1
- */
-#define WP_SECURITY_CONTEXT_V1_COMMIT_SINCE_VERSION 1
-
-/** @ingroup iface_wp_security_context_v1 */
-static inline void
-wp_security_context_v1_set_user_data(struct wp_security_context_v1 *wp_security_context_v1, void *user_data)
-{
- wl_proxy_set_user_data((struct wl_proxy *) wp_security_context_v1, user_data);
-}
-
-/** @ingroup iface_wp_security_context_v1 */
-static inline void *
-wp_security_context_v1_get_user_data(struct wp_security_context_v1 *wp_security_context_v1)
-{
- return wl_proxy_get_user_data((struct wl_proxy *) wp_security_context_v1);
-}
-
-static inline uint32_t
-wp_security_context_v1_get_version(struct wp_security_context_v1 *wp_security_context_v1)
-{
- return wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1);
-}
-
-/**
- * @ingroup iface_wp_security_context_v1
- *
- * Destroy the security context object.
- */
-static inline void
-wp_security_context_v1_destroy(struct wp_security_context_v1 *wp_security_context_v1)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_v1,
- WP_SECURITY_CONTEXT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1), WL_MARSHAL_FLAG_DESTROY);
-}
-
-/**
- * @ingroup iface_wp_security_context_v1
- *
- * Attach a unique sandbox engine name to the security context. The name
- * should follow the reverse-DNS style (e.g. "org.flatpak").
- *
- * A list of well-known engines is maintained at:
- * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/security-context/engines.md
- *
- * It is a protocol error to call this request twice. The already_set
- * error is sent in this case.
- */
-static inline void
-wp_security_context_v1_set_sandbox_engine(struct wp_security_context_v1 *wp_security_context_v1, const char *name)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_v1,
- WP_SECURITY_CONTEXT_V1_SET_SANDBOX_ENGINE, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1), 0, name);
-}
-
-/**
- * @ingroup iface_wp_security_context_v1
- *
- * Attach an application ID to the security context.
- *
- * The application ID is an opaque, sandbox-specific identifier for an
- * application. See the well-known engines document for more details:
- * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/security-context/engines.md
- *
- * The compositor may use the application ID to group clients belonging to
- * the same security context application.
- *
- * Whether this request is optional or not depends on the sandbox engine used.
- *
- * It is a protocol error to call this request twice. The already_set
- * error is sent in this case.
- */
-static inline void
-wp_security_context_v1_set_app_id(struct wp_security_context_v1 *wp_security_context_v1, const char *app_id)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_v1,
- WP_SECURITY_CONTEXT_V1_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1), 0, app_id);
-}
-
-/**
- * @ingroup iface_wp_security_context_v1
- *
- * Attach an instance ID to the security context.
- *
- * The instance ID is an opaque, sandbox-specific identifier for a running
- * instance of an application. See the well-known engines document for
- * more details:
- * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/security-context/engines.md
- *
- * Whether this request is optional or not depends on the sandbox engine used.
- *
- * It is a protocol error to call this request twice. The already_set
- * error is sent in this case.
- */
-static inline void
-wp_security_context_v1_set_instance_id(struct wp_security_context_v1 *wp_security_context_v1, const char *instance_id)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_v1,
- WP_SECURITY_CONTEXT_V1_SET_INSTANCE_ID, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1), 0, instance_id);
-}
-
-/**
- * @ingroup iface_wp_security_context_v1
- *
- * Atomically register the new client and attach the security context
- * metadata.
- *
- * If the provided metadata is inconsistent or does not match with out of
- * band metadata (see
- * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/security-context/engines.md),
- * the invalid_metadata error may be sent eventually.
- *
- * It's a protocol error to send any request other than "destroy" after
- * this request. In this case, the already_used error is sent.
- */
-static inline void
-wp_security_context_v1_commit(struct wp_security_context_v1 *wp_security_context_v1)
-{
- wl_proxy_marshal_flags((struct wl_proxy *) wp_security_context_v1,
- WP_SECURITY_CONTEXT_V1_COMMIT, NULL, wl_proxy_get_version((struct wl_proxy *) wp_security_context_v1), 0);
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/system/wayland/wayland-client-helper.c b/system/wayland/wayland-client-helper.c
deleted file mode 100644
index 5a2c9a01..00000000
--- a/system/wayland/wayland-client-helper.c
+++ /dev/null
@@ -1,96 +0,0 @@
-#include "wayland-client-helper.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include "security-context-v1-protocol.h"
-#include <wayland-client.h>
-
-static void registry_handle_global(void *data, struct wl_registry *registry,
- uint32_t name, const char *interface,
- uint32_t version) {
- struct wp_security_context_manager_v1 **out = data;
-
- if (strcmp(interface, wp_security_context_manager_v1_interface.name) == 0)
- *out = wl_registry_bind(registry, name,
- &wp_security_context_manager_v1_interface, 1);
-}
-
-static void registry_handle_global_remove(void *data,
- struct wl_registry *registry,
- uint32_t name) {} /* no-op */
-
-static const struct wl_registry_listener registry_listener = {
- .global = registry_handle_global,
- .global_remove = registry_handle_global_remove,
-};
-
-int32_t hakurei_bind_wayland_fd(char *socket_path, int fd, const char *app_id,
- const char *instance_id, int sync_fd) {
- int32_t res = 0; /* refer to resErr for corresponding Go error */
-
- struct wl_display *display;
- display = wl_display_connect_to_fd(fd);
- if (!display) {
- res = 1;
- goto out;
- };
-
- struct wl_registry *registry;
- registry = wl_display_get_registry(display);
-
- struct wp_security_context_manager_v1 *security_context_manager = NULL;
- wl_registry_add_listener(registry, &registry_listener,
- &security_context_manager);
- int ret;
- ret = wl_display_roundtrip(display);
- wl_registry_destroy(registry);
- if (ret < 0)
- goto out;
-
- if (!security_context_manager) {
- res = 2;
- goto out;
- }
-
- int listen_fd = -1;
- listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (listen_fd < 0)
- goto out;
-
- struct sockaddr_un sockaddr = {0};
- sockaddr.sun_family = AF_UNIX;
- snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", socket_path);
- if (bind(listen_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != 0)
- goto out;
-
- if (listen(listen_fd, 0) != 0)
- goto out;
-
- struct wp_security_context_v1 *security_context;
- security_context = wp_security_context_manager_v1_create_listener(
- security_context_manager, listen_fd, sync_fd);
- wp_security_context_v1_set_sandbox_engine(security_context, "app.hakurei");
- wp_security_context_v1_set_app_id(security_context, app_id);
- wp_security_context_v1_set_instance_id(security_context, instance_id);
- wp_security_context_v1_commit(security_context);
- wp_security_context_v1_destroy(security_context);
- if (wl_display_roundtrip(display) < 0)
- goto out;
-
-out:
- if (listen_fd >= 0)
- close(listen_fd);
- if (security_context_manager)
- wp_security_context_manager_v1_destroy(security_context_manager);
- if (display)
- wl_display_disconnect(display);
-
- free((void *)socket_path);
- free((void *)app_id);
- free((void *)instance_id);
- return res;
-}
diff --git a/system/wayland/wayland-client-helper.h b/system/wayland/wayland-client-helper.h
deleted file mode 100644
index eae8936d..00000000
--- a/system/wayland/wayland-client-helper.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#include <stdint.h>
-
-int32_t hakurei_bind_wayland_fd(char *socket_path, int fd, const char *app_id,
- const char *instance_id, int sync_fd); \ No newline at end of file
diff --git a/system/wayland/wayland.go b/system/wayland/wayland.go
deleted file mode 100644
index 2aa1fe15..00000000
--- a/system/wayland/wayland.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package wayland
-
-//go:generate sh -c "wayland-scanner client-header `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.h"
-//go:generate sh -c "wayland-scanner private-code `pkg-config --variable=datarootdir wayland-protocols`/wayland-protocols/staging/security-context/security-context-v1.xml security-context-v1-protocol.c"
-
-/*
-#cgo linux pkg-config: --static wayland-client
-#cgo freebsd openbsd LDFLAGS: -lwayland-client
-
-#include "wayland-client-helper.h"
-*/
-import "C"
-import (
- "errors"
- "strings"
-)
-
-var (
- ErrContainsNull = errors.New("string contains null character")
-)
-
-var resErr = [...]error{
- 0: nil,
- 1: errors.New("wl_display_connect_to_fd() failed"),
- 2: errors.New("wp_security_context_v1 not available"),
-}
-
-func bindWaylandFd(socketPath string, fd uintptr, appID, instanceID string, syncFd uintptr) error {
- if hasNull(appID) || hasNull(instanceID) {
- return ErrContainsNull
- }
- res := C.hakurei_bind_wayland_fd(C.CString(socketPath), C.int(fd), C.CString(appID), C.CString(instanceID), C.int(syncFd))
- return resErr[int32(res)]
-}
-
-func hasNull(s string) bool { return strings.IndexByte(s, '\x00') > -1 }
diff --git a/system/wayland_test.go b/system/wayland_test.go
deleted file mode 100644
index 6805b0c2..00000000
--- a/system/wayland_test.go
+++ /dev/null
@@ -1,303 +0,0 @@
-package system
-
-import (
- "errors"
- "os"
- "testing"
-
- "hakurei.app/container/stub"
- "hakurei.app/system/acl"
- "hakurei.app/system/wayland"
-)
-
-type stubWaylandConn struct {
- t *testing.T
-
- wantAttach string
- attachErr error
- attached bool
-
- wantBind [3]string
- bindErr error
- bound bool
-
- closeErr error
- closed bool
-}
-
-func (conn *stubWaylandConn) Attach(p string) (err error) {
- conn.t.Helper()
-
- if conn.attached {
- conn.t.Fatal("Attach called twice")
- }
- conn.attached = true
-
- err = conn.attachErr
- if p != conn.wantAttach {
- conn.t.Errorf("Attach: p = %q, want %q", p, conn.wantAttach)
- err = stub.ErrCheck
- }
- return
-}
-
-func (conn *stubWaylandConn) Bind(pathname, appID, instanceID string) (*os.File, error) {
- conn.t.Helper()
-
- if !conn.attached {
- conn.t.Fatal("Bind called before Attach")
- }
-
- if conn.bound {
- conn.t.Fatal("Bind called twice")
- }
- conn.bound = true
-
- if pathname != conn.wantBind[0] {
- conn.t.Errorf("Attach: pathname = %q, want %q", pathname, conn.wantBind[0])
- return nil, stub.ErrCheck
- }
- if appID != conn.wantBind[1] {
- conn.t.Errorf("Attach: appID = %q, want %q", appID, conn.wantBind[1])
- return nil, stub.ErrCheck
- }
- if instanceID != conn.wantBind[2] {
- conn.t.Errorf("Attach: instanceID = %q, want %q", instanceID, conn.wantBind[2])
- return nil, stub.ErrCheck
- }
- return nil, conn.bindErr
-}
-
-func (conn *stubWaylandConn) Close() error {
- conn.t.Helper()
-
- if !conn.attached {
- conn.t.Fatal("Close called before Attach")
- }
- if !conn.bound {
- conn.t.Fatal("Close called before Bind")
- }
-
- if conn.closed {
- conn.t.Fatal("Close called twice")
- }
- conn.closed = true
- return conn.closeErr
-}
-
-func TestWaylandOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []opBehaviourTestCase{
- {"attach", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"},
- attachErr: stub.UniqueError(5)},
- }, nil, &OpError{Op: "wayland", Err: stub.UniqueError(5)}, nil, nil},
-
- {"bind", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"},
- bindErr: stub.UniqueError(4)},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- }, &OpError{Op: "wayland", Err: stub.UniqueError(4)}, nil, nil},
-
- {"chmod", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"wayland listening on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, nil, nil),
- call("chmod", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", os.FileMode(0)}, nil, stub.UniqueError(3)),
- }, &OpError{Op: "wayland", Err: stub.UniqueError(3)}, nil, nil},
-
- {"aclUpdate", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"wayland listening on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, 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)),
- }, &OpError{Op: "wayland", Err: stub.UniqueError(2)}, nil, nil},
-
- {"remove", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"wayland listening on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, 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),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"detaching from wayland on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"removing wayland socket on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, nil, nil),
- call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, stub.UniqueError(1)),
- }, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(1)), Revert: true}},
-
- {"close", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"},
- closeErr: stub.UniqueError(0)},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"wayland listening on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, 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),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"detaching from wayland on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"removing wayland socket on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, nil, nil),
- call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, nil),
- }, &OpError{Op: "wayland", Err: errors.Join(stub.UniqueError(0)), Revert: true}},
-
- {"success", 0xbeef, 0xff, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- &stubWaylandConn{t: t, wantAttach: "/run/user/1971/wayland-0", wantBind: [3]string{
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c"}},
- }, []stub.Call{
- call("verbosef", stub.ExpectArgs{"wayland attached on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"wayland listening on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, 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),
- }, nil, []stub.Call{
- call("verbosef", stub.ExpectArgs{"detaching from wayland on %q", []any{"/run/user/1971/wayland-0"}}, nil, nil),
- call("verbosef", stub.ExpectArgs{"removing wayland socket on %q", []any{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}}, nil, nil),
- call("remove", stub.ExpectArgs{"/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"}, nil, nil),
- }, nil},
- })
-
- checkOpsBuilder(t, "Wayland", []opsBuilderTestCase{
- {"chromium", 0xcafe, func(_ *testing.T, sys *I) {
- sys.Wayland(
- m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"),
- m("/run/user/1971/wayland-0"),
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- )
- }, []Op{&waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }}, stub.Expect{}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"dst differs", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7d/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, false},
-
- {"src differs", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-1",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, false},
-
- {"appID differs", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, false},
-
- {"instanceID differs", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7d",
- new(wayland.Conn),
- }, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, false},
-
- {"equals", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"chromium", &waylandOp{nil,
- "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- "/run/user/1971/wayland-0",
- "org.chromium.Chromium",
- "ebf083d1b175911782d413369b64ce7c",
- new(wayland.Conn),
- }, Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland",
- `wayland socket at "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"`},
- })
-}
diff --git a/system/xhost.go b/system/xhost.go
deleted file mode 100644
index 9a547f43..00000000
--- a/system/xhost.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package system
-
-import (
- "hakurei.app/hst"
- "hakurei.app/system/internal/xcb"
-)
-
-// ChangeHosts inserts the target user into X11 hosts and deletes it once its [Enablement] is no longer satisfied.
-func (sys *I) ChangeHosts(username string) *I {
- sys.ops = append(sys.ops, xhostOp(username))
- return sys
-}
-
-// xhostOp implements [I.ChangeHosts].
-type xhostOp string
-
-func (x xhostOp) Type() hst.Enablement { return hst.EX11 }
-
-func (x xhostOp) apply(sys *I) error {
- sys.msg.Verbosef("inserting entry %s to X11", x)
- return newOpError("xhost",
- sys.xcbChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), false)
-}
-
-func (x xhostOp) revert(sys *I, ec *Criteria) error {
- if ec.hasType(x.Type()) {
- sys.msg.Verbosef("deleting entry %s from X11", x)
- return newOpError("xhost",
- sys.xcbChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), true)
- } else {
- sys.msg.Verbosef("skipping entry %s in X11", x)
- return nil
- }
-}
-
-func (x xhostOp) Is(o Op) bool { target, ok := o.(xhostOp); return ok && x == target }
-func (x xhostOp) Path() string { return "/tmp/.X11-unix" }
-func (x xhostOp) String() string { return string("SI:localuser:" + x) }
diff --git a/system/xhost_test.go b/system/xhost_test.go
deleted file mode 100644
index 759f7536..00000000
--- a/system/xhost_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package system
-
-import (
- "testing"
-
- "hakurei.app/container/stub"
- "hakurei.app/hst"
- "hakurei.app/system/internal/xcb"
-)
-
-func TestXHostOp(t *testing.T) {
- t.Parallel()
-
- checkOpBehaviour(t, []opBehaviourTestCase{
- {"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
- 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(1)),
- }, &OpError{Op: "xhost", Err: stub.UniqueError(1)}, nil, nil},
-
- {"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
- 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(0)),
- }, &OpError{Op: "xhost", Err: stub.UniqueError(0), Revert: true}},
-
- {"success skip", 0xbeef, 0, xhostOp("chronos"), []stub.Call{
- 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{"skipping entry %s in X11", []any{xhostOp("chronos")}}, nil, nil),
- }, nil},
-
- {"success", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
- 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),
- }, nil},
- })
-
- checkOpsBuilder(t, "ChangeHosts", []opsBuilderTestCase{
- {"xhost", 0xcafe, func(_ *testing.T, sys *I) {
- sys.ChangeHosts("chronos")
- }, []Op{
- xhostOp("chronos"),
- }, stub.Expect{}},
- })
-
- checkOpIs(t, []opIsTestCase{
- {"differs", xhostOp("kbd"), xhostOp("chronos"), false},
- {"equals", xhostOp("chronos"), xhostOp("chronos"), true},
- })
-
- checkOpMeta(t, []opMetaTestCase{
- {"xhost", xhostOp("chronos"), hst.EX11, "/tmp/.X11-unix", "SI:localuser:chronos"},
- })
-}