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.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user