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"
|
|
"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
|
|
}
|