A live restart of the system bus during an upgrade hung every login on a workstation until a reboot. The module owns the bus's packages, declares the bus running with no restart or reload trigger, publishes only curated events (health, services, denials; never traffic) and serves tools to look at both buses.
104 lines
2.9 KiB
Go
104 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
const (
|
|
busName = "org.freedesktop.DBus"
|
|
busPath = "/org/freedesktop/DBus"
|
|
)
|
|
|
|
// systemBus is the machine's system bus as the watcher uses it, over its own private connection: the
|
|
// bus driver's answers and its NameOwnerChanged signal, never another peer's messages.
|
|
type systemBus struct {
|
|
conn *dbus.Conn
|
|
out chan NameChange
|
|
}
|
|
|
|
// DialSystemBus connects to the system bus as this account and listens for names changing owner.
|
|
func DialSystemBus() (Bus, error) {
|
|
conn, err := dbus.SystemBusPrivate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := conn.Auth(nil); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
if err := conn.Hello(); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
if err := conn.AddMatchSignal(dbus.WithMatchSender(busName), dbus.WithMatchInterface(busName),
|
|
dbus.WithMatchMember("NameOwnerChanged")); err != nil {
|
|
conn.Close()
|
|
return nil, err
|
|
}
|
|
raw := make(chan *dbus.Signal, 256)
|
|
conn.Signal(raw)
|
|
b := &systemBus{conn: conn, out: make(chan NameChange, 256)}
|
|
go func() {
|
|
// The signal channel closes when the connection is lost; so does this one, which is how the
|
|
// watcher learns the bus went away.
|
|
defer close(b.out)
|
|
for {
|
|
select {
|
|
case s, open := <-raw:
|
|
if !open {
|
|
return
|
|
}
|
|
if s.Name != busName+".NameOwnerChanged" || len(s.Body) != 3 {
|
|
continue
|
|
}
|
|
name, _ := s.Body[0].(string)
|
|
old, _ := s.Body[1].(string)
|
|
nw, _ := s.Body[2].(string)
|
|
b.out <- NameChange{Name: name, Old: old, New: nw}
|
|
case <-conn.Context().Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
return b, nil
|
|
}
|
|
|
|
func (b *systemBus) driver() dbus.BusObject { return b.conn.Object(busName, busPath) }
|
|
|
|
// Ping asks the bus driver its id. Not org.freedesktop.DBus.Peer.Ping: dbus-broker's system policy
|
|
// refuses that to an account that is not root, and logs the refusal as a denial.
|
|
func (b *systemBus) Ping(ctx context.Context) error {
|
|
var id string
|
|
return b.driver().CallWithContext(ctx, busName+".GetId", 0).Store(&id)
|
|
}
|
|
|
|
func (b *systemBus) ID(ctx context.Context) (string, error) {
|
|
var id string
|
|
err := b.driver().CallWithContext(ctx, busName+".GetId", 0).Store(&id)
|
|
return id, err
|
|
}
|
|
|
|
func (b *systemBus) PID(ctx context.Context, name string) (uint32, error) {
|
|
var pid uint32
|
|
err := b.driver().CallWithContext(ctx, busName+".GetConnectionUnixProcessID", 0, name).Store(&pid)
|
|
return pid, err
|
|
}
|
|
|
|
func (b *systemBus) Names(ctx context.Context) ([]string, error) {
|
|
var names []string
|
|
err := b.driver().CallWithContext(ctx, busName+".ListNames", 0).Store(&names)
|
|
return names, err
|
|
}
|
|
|
|
func (b *systemBus) Activatable(ctx context.Context) ([]string, error) {
|
|
var names []string
|
|
err := b.driver().CallWithContext(ctx, busName+".ListActivatableNames", 0).Store(&names)
|
|
return names, err
|
|
}
|
|
|
|
func (b *systemBus) Changes() <-chan NameChange { return b.out }
|
|
|
|
func (b *systemBus) Close() { b.conn.Close() }
|