cmd/hakurei/print: use new store interface
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m11s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m11s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m11s
Test / Hakurei (race detector) (push) Successful in 5m3s
Test / Flake checks (push) Successful in 1m40s
This removes the final uses of the compat interfaces. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
0edcb7c1d3
commit
1c168babf2
@ -20,7 +20,6 @@ import (
|
|||||||
"hakurei.app/internal"
|
"hakurei.app/internal"
|
||||||
"hakurei.app/internal/env"
|
"hakurei.app/internal/env"
|
||||||
"hakurei.app/internal/outcome"
|
"hakurei.app/internal/outcome"
|
||||||
"hakurei.app/internal/store"
|
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
"hakurei.app/system/dbus"
|
"hakurei.app/system/dbus"
|
||||||
)
|
)
|
||||||
@ -340,7 +339,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
c.NewCommand("ps", "List active instances", func(args []string) error {
|
c.NewCommand("ps", "List active instances", func(args []string) error {
|
||||||
var sc hst.Paths
|
var sc hst.Paths
|
||||||
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
env.CopyPaths().Copy(&sc, new(outcome.Hsu).MustID(nil))
|
||||||
printPs(os.Stdout, time.Now().UTC(), store.NewMulti(msg, sc.SharePath), flagShort, flagJSON)
|
printPs(msg, os.Stdout, time.Now().UTC(), outcome.NewStore(&sc), flagShort, flagJSON)
|
||||||
return errSuccess
|
return errSuccess
|
||||||
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"maps"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -170,52 +169,52 @@ func printShowInstance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// printPs writes a representation of active instances to output.
|
// printPs writes a representation of active instances to output.
|
||||||
func printPs(output io.Writer, now time.Time, s store.Compat, short, flagJSON bool) {
|
func printPs(msg message.Msg, output io.Writer, now time.Time, s *store.Store, short, flagJSON bool) {
|
||||||
var entries map[hst.ID]*hst.State
|
f := func(a func(eh *store.EntryHandle)) {
|
||||||
if e, err := store.Join(s); err != nil {
|
entries, copyError := s.All()
|
||||||
log.Fatalf("cannot join store: %v", err)
|
for eh := range entries {
|
||||||
} else {
|
a(eh)
|
||||||
entries = e
|
}
|
||||||
|
if err := copyError(); err != nil {
|
||||||
|
msg.GetLogger().Println(getMessage("cannot iterate over store:", err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !short && flagJSON {
|
if short { // short output requires identifier only
|
||||||
es := slices.Collect(maps.Values(entries))
|
var identifiers []*hst.ID
|
||||||
slices.SortFunc(es, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
|
f(func(eh *store.EntryHandle) {
|
||||||
encodeJSON(log.Fatal, output, short, es)
|
if _, err := eh.Load(nil); err != nil { // passes through decode error
|
||||||
|
msg.GetLogger().Println(getMessage("cannot validate state entry header:", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
identifiers = append(identifiers, &eh.ID)
|
||||||
|
})
|
||||||
|
slices.SortFunc(identifiers, func(a, b *hst.ID) int { return bytes.Compare(a[:], b[:]) })
|
||||||
|
|
||||||
|
if flagJSON {
|
||||||
|
encodeJSON(log.Fatal, output, short, identifiers)
|
||||||
|
} else {
|
||||||
|
for _, id := range identifiers {
|
||||||
|
mustPrintln(output, shortIdentifier(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort state entries by id string to ensure consistency between runs
|
// long output requires full instance state
|
||||||
exp := make([]*expandedStateEntry, 0, len(entries))
|
var instances []*hst.State
|
||||||
for id, instance := range entries {
|
f(func(eh *store.EntryHandle) {
|
||||||
// gracefully skip nil states
|
var state hst.State
|
||||||
if instance == nil {
|
if _, err := eh.Load(&state); err != nil { // passes through decode error
|
||||||
log.Printf("got invalid state entry %s", id.String())
|
msg.GetLogger().Println(getMessage("cannot load state entry:", err))
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
instances = append(instances, &state)
|
||||||
|
})
|
||||||
|
slices.SortFunc(instances, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
|
||||||
|
|
||||||
// gracefully skip inconsistent states
|
if flagJSON {
|
||||||
if id != instance.ID {
|
encodeJSON(log.Fatal, output, short, instances)
|
||||||
log.Printf("possible store corruption: entry %s has id %s",
|
|
||||||
id.String(), instance.ID.String())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
exp = append(exp, &expandedStateEntry{s: id.String(), State: instance})
|
|
||||||
}
|
|
||||||
slices.SortFunc(exp, func(a, b *expandedStateEntry) int { return a.Time.Compare(b.Time) })
|
|
||||||
|
|
||||||
if short {
|
|
||||||
if flagJSON {
|
|
||||||
v := make([]string, len(exp))
|
|
||||||
for i, e := range exp {
|
|
||||||
v[i] = e.s
|
|
||||||
}
|
|
||||||
encodeJSON(log.Fatal, output, short, v)
|
|
||||||
} else {
|
|
||||||
for _, e := range exp {
|
|
||||||
mustPrintln(output, shortIdentifierString(e.s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,33 +222,21 @@ func printPs(output io.Writer, now time.Time, s store.Compat, short, flagJSON bo
|
|||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
t.Println("\tInstance\tPID\tApplication\tUptime")
|
t.Println("\tInstance\tPID\tApplication\tUptime")
|
||||||
for _, e := range exp {
|
for _, instance := range instances {
|
||||||
if len(e.s) != 1<<5 {
|
|
||||||
// unreachable
|
|
||||||
log.Printf("possible store corruption: invalid instance string %s", e.s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
as := "(No configuration information)"
|
as := "(No configuration information)"
|
||||||
if e.Config != nil {
|
if instance.Config != nil {
|
||||||
as = strconv.Itoa(e.Config.Identity)
|
as = strconv.Itoa(instance.Config.Identity)
|
||||||
id := e.Config.ID
|
id := instance.Config.ID
|
||||||
if id == "" {
|
if id == "" {
|
||||||
id = "app.hakurei." + shortIdentifierString(e.s)
|
id = "app.hakurei." + shortIdentifier(&instance.ID)
|
||||||
}
|
}
|
||||||
as += " (" + id + ")"
|
as += " (" + id + ")"
|
||||||
}
|
}
|
||||||
t.Printf("\t%s\t%d\t%s\t%s\n",
|
t.Printf("\t%s\t%d\t%s\t%s\n",
|
||||||
shortIdentifierString(e.s), e.PID, as, now.Sub(e.Time).Round(time.Second).String())
|
shortIdentifier(&instance.ID), instance.PID, as, now.Sub(instance.Time).Round(time.Second).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandedStateEntry stores [hst.State] alongside a string representation of its [hst.ID].
|
|
||||||
type expandedStateEntry struct {
|
|
||||||
s string
|
|
||||||
*hst.State
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPrinter returns a configured, wrapped [tabwriter.Writer].
|
// newPrinter returns a configured, wrapped [tabwriter.Writer].
|
||||||
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
|
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/store"
|
"hakurei.app/internal/store"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -18,13 +20,30 @@ var (
|
|||||||
0x4c, 0xf0, 0x73, 0xbd,
|
0x4c, 0xf0, 0x73, 0xbd,
|
||||||
0xb4, 0x6e, 0xb5, 0xc1,
|
0xb4, 0x6e, 0xb5, 0xc1,
|
||||||
}
|
}
|
||||||
testState = &hst.State{
|
testState = hst.State{
|
||||||
ID: testID,
|
ID: testID,
|
||||||
PID: 0xcafebabe,
|
PID: 0xcafebabe,
|
||||||
ShimPID: 0xdeadbeef,
|
ShimPID: 0xdeadbeef,
|
||||||
Config: hst.Template(),
|
Config: hst.Template(),
|
||||||
Time: testAppTime,
|
Time: testAppTime,
|
||||||
}
|
}
|
||||||
|
testStateSmall = hst.State{
|
||||||
|
ID: (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
|
||||||
|
PID: 0xbeef,
|
||||||
|
ShimPID: 0xcafe,
|
||||||
|
Config: &hst.Config{
|
||||||
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
||||||
|
Identity: 1,
|
||||||
|
Container: &hst.ContainerConfig{
|
||||||
|
Shell: check.MustAbs("/bin/sh"),
|
||||||
|
Home: check.MustAbs("/data/data/uk.gensokyo.cat"),
|
||||||
|
Path: check.MustAbs("/usr/bin/cat"),
|
||||||
|
Args: []string{"cat"},
|
||||||
|
Flags: hst.FUserns,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Time: time.Unix(0, 0xdeadbeef).UTC(),
|
||||||
|
}
|
||||||
testTime = time.Unix(3752, 1).UTC()
|
testTime = time.Unix(3752, 1).UTC()
|
||||||
testAppTime = time.Unix(0, 9).UTC()
|
testAppTime = time.Unix(0, 9).UTC()
|
||||||
)
|
)
|
||||||
@ -133,7 +152,7 @@ Session bus
|
|||||||
|
|
||||||
`, false},
|
`, false},
|
||||||
|
|
||||||
{"instance", testState, hst.Template(), false, false, `State
|
{"instance", &testState, hst.Template(), false, false, `State
|
||||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
||||||
Uptime: 1h2m32s
|
Uptime: 1h2m32s
|
||||||
|
|
||||||
@ -173,7 +192,7 @@ System bus
|
|||||||
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
|
||||||
|
|
||||||
`, true},
|
`, true},
|
||||||
{"instance pd", testState, new(hst.Config), false, false, `Error: configuration missing container state!
|
{"instance pd", &testState, new(hst.Config), false, false, `Error: configuration missing container state!
|
||||||
|
|
||||||
State
|
State
|
||||||
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
|
||||||
@ -187,7 +206,7 @@ App
|
|||||||
|
|
||||||
{"json nil", nil, nil, false, true, `null
|
{"json nil", nil, nil, false, true, `null
|
||||||
`, true},
|
`, true},
|
||||||
{"json instance", testState, nil, false, true, `{
|
{"json instance", &testState, nil, false, true, `{
|
||||||
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
||||||
"pid": 3405691582,
|
"pid": 3405691582,
|
||||||
"shim_pid": 3735928559,
|
"shim_pid": 3735928559,
|
||||||
@ -515,41 +534,27 @@ func TestPrintPs(t *testing.T) {
|
|||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
entries map[hst.ID]*hst.State
|
data []hst.State
|
||||||
short, json bool
|
short, json bool
|
||||||
want string
|
want, log string
|
||||||
}{
|
}{
|
||||||
{"no entries", make(map[hst.ID]*hst.State), false, false, " Instance PID Application Uptime\n"},
|
{"no entries", []hst.State{}, false, false, " Instance PID Application Uptime\n", ""},
|
||||||
{"no entries short", make(map[hst.ID]*hst.State), true, false, ""},
|
{"no entries short", []hst.State{}, true, false, "", ""},
|
||||||
{"nil instance", map[hst.ID]*hst.State{testID: nil}, false, false, " Instance PID Application Uptime\n"},
|
|
||||||
{"state corruption", map[hst.ID]*hst.State{hst.ID{}: testState}, false, false, " Instance PID Application Uptime\n"},
|
|
||||||
|
|
||||||
{"valid pd", map[hst.ID]*hst.State{testID: {ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, ` Instance PID Application Uptime
|
{"invalid config", []hst.State{{ID: testID, PID: 1 << 8, Config: new(hst.Config), Time: testAppTime}}, false, false, " Instance PID Application Uptime\n", "check: configuration missing container state\n"},
|
||||||
4cf073bd 256 0 (app.hakurei.4cf073bd) 1h2m32s
|
|
||||||
`},
|
|
||||||
|
|
||||||
{"valid", map[hst.ID]*hst.State{testID: testState}, false, false, ` Instance PID Application Uptime
|
{"valid", []hst.State{testStateSmall, testState}, false, false, ` Instance PID Application Uptime
|
||||||
4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
|
4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
|
||||||
`},
|
aaaaaaaa 48879 1 (app.hakurei.aaaaaaaa) 1h2m28s
|
||||||
{"valid short", map[hst.ID]*hst.State{testID: testState}, true, false, "4cf073bd\n"},
|
`, ""},
|
||||||
{"valid json", map[hst.ID]*hst.State{testID: testState, (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))): {
|
{"valid single", []hst.State{testState}, false, false, ` Instance PID Application Uptime
|
||||||
ID: (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
|
4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
|
||||||
PID: 0xbeef,
|
`, ""},
|
||||||
ShimPID: 0xcafe,
|
|
||||||
Config: &hst.Config{
|
{"valid short", []hst.State{testStateSmall, testState}, true, false, "4cf073bd\naaaaaaaa\n", ""},
|
||||||
ID: "uk.gensokyo.cat",
|
{"valid short single", []hst.State{testState}, true, false, "4cf073bd\n", ""},
|
||||||
Enablements: hst.NewEnablements(hst.EWayland | hst.EPulse),
|
|
||||||
Identity: 1,
|
{"valid json", []hst.State{testState, testStateSmall}, false, true, `[
|
||||||
Container: &hst.ContainerConfig{
|
|
||||||
Shell: check.MustAbs("/bin/sh"),
|
|
||||||
Home: check.MustAbs("/data/data/uk.gensokyo.cat"),
|
|
||||||
Path: check.MustAbs("/usr/bin/cat"),
|
|
||||||
Args: []string{"cat"},
|
|
||||||
Flags: hst.FUserns,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Time: time.Unix(0, 0xdeadbeef).UTC(),
|
|
||||||
}}, false, true, `[
|
|
||||||
{
|
{
|
||||||
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
|
||||||
"pid": 3405691582,
|
"pid": 3405691582,
|
||||||
@ -707,7 +712,7 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"instance": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
"instance": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
"pid": 48879,
|
"pid": 48879,
|
||||||
"shim_pid": 51966,
|
"shim_pid": 51966,
|
||||||
"id": "uk.gensokyo.cat",
|
"id": "",
|
||||||
"enablements": {
|
"enablements": {
|
||||||
"wayland": true,
|
"wayland": true,
|
||||||
"pulse": true
|
"pulse": true
|
||||||
@ -729,30 +734,44 @@ func TestPrintPs(t *testing.T) {
|
|||||||
"time": "1970-01-01T00:00:03.735928559Z"
|
"time": "1970-01-01T00:00:03.735928559Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
`},
|
`, ""},
|
||||||
{"valid short json", map[hst.ID]*hst.State{testID: testState}, true, true, `["8e2c76b066dabe574cf073bdb46eb5c1"]
|
{"valid short json", []hst.State{testStateSmall, testState}, true, true, `["8e2c76b066dabe574cf073bdb46eb5c1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]
|
||||||
`},
|
`, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
s := store.New(check.MustAbs(t.TempDir()).Append("store"))
|
||||||
|
for i := range tc.data {
|
||||||
|
if h, err := s.Handle(tc.data[i].Identity); err != nil {
|
||||||
|
t.Fatalf("Handle: error = %v", err)
|
||||||
|
} else {
|
||||||
|
var unlock func()
|
||||||
|
if unlock, err = h.Lock(); err != nil {
|
||||||
|
t.Fatalf("Lock: error = %v", err)
|
||||||
|
}
|
||||||
|
_, err = h.Save(&tc.data[i])
|
||||||
|
unlock()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Save: error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store must not be written to beyond this point
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
output := new(strings.Builder)
|
var printBuf, logBuf bytes.Buffer
|
||||||
printPs(output, testTime, stubStore(tc.entries), tc.short, tc.json)
|
msg := message.NewMsg(log.New(&logBuf, "check: ", 0))
|
||||||
if got := output.String(); got != tc.want {
|
msg.SwapVerbose(true)
|
||||||
t.Errorf("printPs: got\n%s\nwant\n%s",
|
printPs(msg, &printBuf, testTime, s, tc.short, tc.json)
|
||||||
got, tc.want)
|
if got := printBuf.String(); got != tc.want {
|
||||||
|
t.Errorf("printPs:\n%s\nwant\n%s", got, tc.want)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if got := logBuf.String(); got != tc.log {
|
||||||
|
t.Errorf("msg:\n%s\nwant\n%s", got, tc.log)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stubStore implements [state.Store] and returns test samples via [state.Joiner].
|
|
||||||
type stubStore map[hst.ID]*hst.State
|
|
||||||
|
|
||||||
func (s stubStore) Join() (map[hst.ID]*hst.State, error) { return s, nil }
|
|
||||||
func (s stubStore) Do(int, func(c store.Cursor)) (bool, error) { panic("unreachable") }
|
|
||||||
func (s stubStore) List() ([]int, error) { panic("unreachable") }
|
|
||||||
func (s stubStore) Close() error { return nil }
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user