Files
mesh-catalog/modules/dbus/cmd/dbus-tools/names.go
T
jochen 7b5e1d3362 dbus: hold node-message-bus, and never restart the bus live (hq ADR 0215)
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.
2026-10-05 11:50:52 +02:00

104 lines
2.9 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
)
// busctlName is one line of `busctl list --json`.
type busctlName struct {
Name string `json:"name"`
PID *uint32 `json:"pid"`
Process *string `json:"process"`
User *string `json:"user"`
Connection string `json:"connection"`
Unit *string `json:"unit"`
}
// Name is one well-known name on a bus, with what holds it.
type Name struct {
Name string `json:"name"`
PID uint32 `json:"pid,omitempty"`
Process string `json:"process,omitempty"`
User string `json:"user,omitempty"`
Unit string `json:"unit,omitempty"`
Connection string `json:"connection,omitempty"`
}
// Names is dbus_names's answer.
type Names struct {
Bus string `json:"bus"`
Running []Name `json:"running"`
ActivatableNotRunning []string `json:"activatable_not_running"`
Connections int `json:"connections"`
Cut bool `json:"cut,omitempty"`
}
// ParseNames reads `busctl list --json=short`: the well-known names running, the activatable names
// that are not, and how many connections (unique names) the bus has.
func ParseNames(bus, out string) (Names, error) {
var raw []busctlName
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &raw); err != nil {
return Names{}, fmt.Errorf("busctl list answered what is not its JSON: %w", err)
}
n := Names{Bus: bus, Running: []Name{}, ActivatableNotRunning: []string{}}
for _, r := range raw {
switch {
case r.Name == busName: // the bus itself, which busctl attributes to the service manager
case strings.HasPrefix(r.Name, ":"):
n.Connections++
case r.Connection == "(activatable)" || r.PID == nil:
n.ActivatableNotRunning = append(n.ActivatableNotRunning, r.Name)
default:
n.Running = append(n.Running, Name{Name: r.Name, PID: deref(r.PID), Process: derefS(r.Process),
User: derefS(r.User), Unit: derefS(r.Unit), Connection: r.Connection})
}
}
sort.Slice(n.Running, func(i, j int) bool { return n.Running[i].Name < n.Running[j].Name })
sort.Strings(n.ActivatableNotRunning)
if len(n.Running) > AnswerCap {
n.Running, n.Cut = n.Running[:AnswerCap], true
}
if len(n.ActivatableNotRunning) > AnswerCap {
n.ActivatableNotRunning, n.Cut = n.ActivatableNotRunning[:AnswerCap], true
}
return n, nil
}
// Names lists one bus's names as this account sees them.
func (m *Machine) Names(ctx context.Context, bus string) (Names, error) {
args, err := m.busArgs(bus)
if err != nil {
return Names{}, err
}
out, err := m.Run(ctx, "busctl", append(args, "--json=short", "--no-pager", "list")...)
if err != nil {
return Names{}, err
}
return ParseNames(orWord(bus, "system"), out)
}
func deref(p *uint32) uint32 {
if p == nil {
return 0
}
return *p
}
func derefS(p *string) string {
if p == nil {
return ""
}
return *p
}
func orWord(s, word string) string {
if strings.TrimSpace(s) == "" {
return word
}
return s
}