internal/system: relocate from system
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m17s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m13s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m18s
Test / Hakurei (push) Successful in 3m17s
Test / Sandbox (race detector) (push) Successful in 4m7s
Test / Hpkg (push) Successful in 4m13s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
These packages are highly specific to hakurei and are difficult to use safely from other pieces of code. Their exported symbols are made available until v0.4.0 where they will be removed for #24. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
115
system/dbus/deprecated.go
Normal file
115
system/dbus/deprecated.go
Normal file
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)}
|
||||
}
|
||||
}
|
||||
@@ -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()) }
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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
|
||||
}()
|
||||
)
|
||||
18
system/dbus/testdata/dev.vencord.Vesktop.json
vendored
18
system/dbus/testdata/dev.vencord.Vesktop.json
vendored
@@ -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
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"talk":[
|
||||
"org.bluez",
|
||||
"org.freedesktop.Avahi",
|
||||
"org.freedesktop.UPower"
|
||||
],
|
||||
|
||||
"filter":true
|
||||
}
|
||||
24
system/dbus/testdata/org.chromium.Chromium.json
vendored
24
system/dbus/testdata/org.chromium.Chromium.json
vendored
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user