From 05db06c87b743b2e1eb0e8158476ba2a24962967 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 6 Sep 2025 14:25:19 +0900 Subject: [PATCH] system/dbus: use syscall dispatcher This allows dbus op methods and builder to be instrumented. Signed-off-by: Ophestra --- system/dbus.go | 35 ++-- system/dbus_test.go | 371 ++++++++++++++++++++++++++++++++++++++ system/dispatcher.go | 30 ++- system/dispatcher_test.go | 81 ++++++++- 4 files changed, 499 insertions(+), 18 deletions(-) diff --git a/system/dbus.go b/system/dbus.go index 12841e6..819f249 100644 --- a/system/dbus.go +++ b/system/dbus.go @@ -43,10 +43,10 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st d.system = system != nil var sessionBus, systemBus dbus.ProxyPair - sessionBus[0], systemBus[0] = dbus.Address() + sessionBus[0], systemBus[0] = sys.dbusAddress() sessionBus[1], systemBus[1] = sessionPath, systemPath d.out = &linePrefixWriter{println: log.Println, prefix: "(dbus) ", buf: new(strings.Builder)} - if final, err := dbus.Finalise(sessionBus, systemBus, session, system); err != nil { + if final, err := sys.dbusFinalise(sessionBus, systemBus, session, system); err != nil { if errors.Is(err, syscall.EINVAL) { return nil, newOpErrorMessage("dbus", err, "message bus proxy configuration contains NUL byte", false) @@ -54,14 +54,14 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st return nil, newOpErrorMessage("dbus", err, fmt.Sprintf("cannot finalise message bus proxy: %v", err), false) } else { - if msg.IsVerbose() { - msg.Verbose("session bus proxy:", session.Args(sessionBus)) + if sys.isVerbose() { + sys.verbose("session bus proxy:", session.Args(sessionBus)) if system != nil { - msg.Verbose("system bus proxy:", system.Args(systemBus)) + sys.verbose("system bus proxy:", system.Args(systemBus)) } // this calls the argsWt String method - msg.Verbose("message bus proxy final args:", final.WriterTo) + sys.verbose("message bus proxy final args:", final.WriterTo) } d.final = final @@ -85,29 +85,32 @@ type DBusProxyOp struct { func (d *DBusProxyOp) Type() Enablement { return Process } func (d *DBusProxyOp) apply(sys *I) error { - msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0]) + sys.verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0]) if d.system { - msg.Verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0]) + sys.verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0]) } d.proxy = dbus.New(sys.ctx, d.final, d.out) - if err := d.proxy.Start(); err != nil { + 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) } - msg.Verbose("starting message bus proxy", d.proxy) + sys.verbose("starting message bus proxy", d.proxy) return nil } -func (d *DBusProxyOp) revert(*I, *Criteria) error { +func (d *DBusProxyOp) revert(sys *I, _ *Criteria) error { // criteria ignored here since dbus is always process-scoped - msg.Verbose("terminating message bus proxy") - d.proxy.Close() - defer msg.Verbose("message bus proxy exit") - err := d.proxy.Wait() + sys.verbose("terminating message bus proxy") + sys.dbusProxyClose(d.proxy) + + exitMessage := "message bus proxy exit" + defer func() { sys.verbose(exitMessage) }() + + err := sys.dbusProxyWait(d.proxy) if errors.Is(err, context.Canceled) { - msg.Verbose("message bus proxy canceled upstream") + exitMessage = "message bus proxy canceled upstream" err = nil } return newOpErrorMessage("dbus", err, diff --git a/system/dbus_test.go b/system/dbus_test.go index d5d053b..e3ace3d 100644 --- a/system/dbus_test.go +++ b/system/dbus_test.go @@ -1,13 +1,384 @@ package system import ( + "context" "reflect" "slices" + "strconv" "strings" "syscall" "testing" + + "hakurei.app/container/stub" + "hakurei.app/helper" + "hakurei.app/system/dbus" ) +func TestDBusProxyOp(t *testing.T) { + checkOpBehaviour(t, []opBehaviourTestCase{ + {"dbusProxyStart", 0xdeadbeef, 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", 0xdeadbeef, 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", 0xdeadbeef, 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", 0xdeadbeef, 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", 0xcafebabe, 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 f, err := sys.ProxyDBus(nil, new(dbus.Config), "", ""); !reflect.DeepEqual(err, wantErr) { + t.Errorf("ProxyDBus: error = %v, want %v", err, wantErr) + } else if f != nil { + t.Errorf("ProxyDBus: f = %p", f) + } + }, nil, stub.Expect{}}, + + {"dbusFinalise NUL", 0xcafebabe, 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( + "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"session\x00"}, Filter: true, + }, "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"system\x00"}, Filter: true, + }) + }, nil, stub.Expect{Calls: []stub.Call{ + call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil), + 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"}, + &dbus.Config{Talk: []string{"session\x00"}, Filter: true}, + &dbus.Config{Talk: []string{"system\x00"}, Filter: true}, + }, (*dbus.Final)(nil), syscall.EINVAL), + }}}, + + {"dbusFinalise", 0xcafebabe, 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 f, err := sys.ProxyDBus( + &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"session\x00"}, Filter: true, + }, &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"system\x00"}, Filter: true, + }, + "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", + "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket"); !reflect.DeepEqual(err, wantErr) { + t.Errorf("ProxyDBus: error = %v", err) + } else if f != nil { + t.Errorf("ProxyDBus: f = %p", f) + } + }, nil, stub.Expect{Calls: []stub.Call{ + call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil), + 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"}, + &dbus.Config{Talk: []string{"session\x00"}, Filter: true}, + &dbus.Config{Talk: []string{"system\x00"}, Filter: true}, + }, (*dbus.Final)(nil), stub.UniqueError(0)), + }}}, + + {"full", 0xcafebabe, func(_ *testing.T, sys *I) { + sys.MustProxyDBus( + "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/bus", &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"session\x00"}, Filter: true, + }, "/tmp/hakurei.0/99dd71ee2146369514e0d10783368f8f/system_bus_socket", &dbus.Config{ + // use impossible value here as an implicit assert that it goes through the stub + Talk: []string{"system\x00"}, Filter: true, + }) + }, []Op{ + &DBusProxyOp{ + final: dbusNewFinalSample(0), + system: true, + }, + }, stub.Expect{Calls: []stub.Call{ + call("dbusAddress", stub.ExpectArgs{}, [2]string{"unix:path=/run/user/1000/bus", "unix:path=/run/dbus/system_bus_socket"}, nil), + 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"}, + &dbus.Config{Talk: []string{"session\x00"}, Filter: true}, + &dbus.Config{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([]string{"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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{ + "--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([]string{"unique", "value", strconv.Itoa(v), "injected", "by", "the", "test", "suite"}), + } +} + func TestLinePrefixWriter(t *testing.T) { testCases := []struct { name string diff --git a/system/dispatcher.go b/system/dispatcher.go index c483c81..c896de5 100644 --- a/system/dispatcher.go +++ b/system/dispatcher.go @@ -1,6 +1,9 @@ package system -import "hakurei.app/system/acl" +import ( + "hakurei.app/system/acl" + "hakurei.app/system/dbus" +) // 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. @@ -13,6 +16,18 @@ type syscallDispatcher interface { // aclUpdate provides [acl.Update]. aclUpdate(name string, uid int, perms ...acl.Perm) error + // dbusAddress provides [dbus.Address]. + dbusAddress() (session, system string) + // dbusFinalise provides [dbus.Finalise]. + dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *dbus.Config) (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 + + isVerbose() bool verbose(v ...any) verbosef(format string, v ...any) } @@ -26,5 +41,18 @@ func (k direct) aclUpdate(name string, uid int, perms ...acl.Perm) error { return acl.Update(name, uid, perms...) } +func (k direct) dbusAddress() (session, system string) { + return dbus.Address() +} + +func (k direct) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *dbus.Config) (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() } + +func (k direct) isVerbose() bool { return msg.IsVerbose() } func (direct) verbose(v ...any) { msg.Verbose(v...) } func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) } diff --git a/system/dispatcher_test.go b/system/dispatcher_test.go index 7c7c10c..c23c6c5 100644 --- a/system/dispatcher_test.go +++ b/system/dispatcher_test.go @@ -1,12 +1,15 @@ package system import ( + "os" "reflect" "slices" "testing" + "unsafe" "hakurei.app/container/stub" "hakurei.app/system/acl" + "hakurei.app/system/dbus" ) // call initialises a [stub.Call]. @@ -197,9 +200,85 @@ func (k *kstub) aclUpdate(name string, uid int, perms ...acl.Perm) error { stub.CheckArgReflect(k.Stub, "perms", perms, 2)) } +func (k *kstub) dbusAddress() (session, system string) { + k.Helper() + ret := k.Expects("dbusAddress").Ret.([2]string) + return ret[0], ret[1] +} + +func (k *kstub) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, system *dbus.Config) (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) isVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) } + +// ignoreValue marks a value to be ignored by the test suite. +type ignoreValue struct{} + func (k *kstub) verbose(v ...any) { k.Helper() - if k.Expects("verbose").Error( + 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() }