aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-11-02 23:10:07 +0900
committerOphestra <cat@gensokyo.uk>2025-11-03 01:19:16 +0900
commit1c168babf2213f2b6c5bc90134a3ea74aca9934f (patch)
tree391aaab6071a373b98d6e776f9b5c639a1547c5a
parent0edcb7c1d39bf92cfae7ebcaab278620ca35ef11 (diff)
cmd/hakurei/print: use new store interface
This removes the final uses of the compat interfaces. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--cmd/hakurei/command.go3
-rw-r--r--cmd/hakurei/print.go101
-rw-r--r--cmd/hakurei/print_test.go121
3 files changed, 115 insertions, 110 deletions
diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go
index b2302b3b..6f727124 100644
--- a/cmd/hakurei/command.go
+++ b/cmd/hakurei/command.go
@@ -20,7 +20,6 @@ import (
"hakurei.app/internal"
"hakurei.app/internal/env"
"hakurei.app/internal/outcome"
- "hakurei.app/internal/store"
"hakurei.app/message"
"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 {
var sc hst.Paths
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
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
}
diff --git a/cmd/hakurei/print.go b/cmd/hakurei/print.go
index eaa8661a..c6f060a8 100644
--- a/cmd/hakurei/print.go
+++ b/cmd/hakurei/print.go
@@ -5,7 +5,6 @@ import (
"fmt"
"io"
"log"
- "maps"
"slices"
"strconv"
"strings"
@@ -170,86 +169,74 @@ func printShowInstance(
}
// printPs writes a representation of active instances to output.
-func printPs(output io.Writer, now time.Time, s store.Compat, short, flagJSON bool) {
- var entries map[hst.ID]*hst.State
- if e, err := store.Join(s); err != nil {
- log.Fatalf("cannot join store: %v", err)
- } else {
- entries = e
- }
-
- if !short && flagJSON {
- es := slices.Collect(maps.Values(entries))
- slices.SortFunc(es, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
- encodeJSON(log.Fatal, output, short, es)
- return
- }
-
- // sort state entries by id string to ensure consistency between runs
- exp := make([]*expandedStateEntry, 0, len(entries))
- for id, instance := range entries {
- // gracefully skip nil states
- if instance == nil {
- log.Printf("got invalid state entry %s", id.String())
- continue
+func printPs(msg message.Msg, output io.Writer, now time.Time, s *store.Store, short, flagJSON bool) {
+ f := func(a func(eh *store.EntryHandle)) {
+ entries, copyError := s.All()
+ for eh := range entries {
+ a(eh)
}
-
- // gracefully skip inconsistent states
- if id != instance.ID {
- log.Printf("possible store corruption: entry %s has id %s",
- id.String(), instance.ID.String())
- continue
+ if err := copyError(); err != nil {
+ msg.GetLogger().Println(getMessage("cannot iterate over store:", err))
}
- 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
+ if short { // short output requires identifier only
+ var identifiers []*hst.ID
+ f(func(eh *store.EntryHandle) {
+ if _, err := eh.Load(nil); err != nil { // passes through decode error
+ msg.GetLogger().Println(getMessage("cannot validate state entry header:", err))
+ return
}
- encodeJSON(log.Fatal, output, short, v)
+ 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 _, e := range exp {
- mustPrintln(output, shortIdentifierString(e.s))
+ for _, id := range identifiers {
+ mustPrintln(output, shortIdentifier(id))
}
}
return
}
+ // long output requires full instance state
+ var instances []*hst.State
+ f(func(eh *store.EntryHandle) {
+ var state hst.State
+ if _, err := eh.Load(&state); err != nil { // passes through decode error
+ msg.GetLogger().Println(getMessage("cannot load state entry:", err))
+ return
+ }
+ instances = append(instances, &state)
+ })
+ slices.SortFunc(instances, func(a, b *hst.State) int { return bytes.Compare(a.ID[:], b.ID[:]) })
+
+ if flagJSON {
+ encodeJSON(log.Fatal, output, short, instances)
+ return
+ }
+
t := newPrinter(output)
defer t.MustFlush()
t.Println("\tInstance\tPID\tApplication\tUptime")
- for _, e := range exp {
- if len(e.s) != 1<<5 {
- // unreachable
- log.Printf("possible store corruption: invalid instance string %s", e.s)
- continue
- }
-
+ for _, instance := range instances {
as := "(No configuration information)"
- if e.Config != nil {
- as = strconv.Itoa(e.Config.Identity)
- id := e.Config.ID
+ if instance.Config != nil {
+ as = strconv.Itoa(instance.Config.Identity)
+ id := instance.Config.ID
if id == "" {
- id = "app.hakurei." + shortIdentifierString(e.s)
+ id = "app.hakurei." + shortIdentifier(&instance.ID)
}
as += " (" + id + ")"
}
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].
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go
index 5309e376..5a7d2143 100644
--- a/cmd/hakurei/print_test.go
+++ b/cmd/hakurei/print_test.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ "log"
"strings"
"testing"
"time"
@@ -9,6 +10,7 @@ import (
"hakurei.app/container/check"
"hakurei.app/hst"
"hakurei.app/internal/store"
+ "hakurei.app/message"
)
var (
@@ -18,13 +20,30 @@ var (
0x4c, 0xf0, 0x73, 0xbd,
0xb4, 0x6e, 0xb5, 0xc1,
}
- testState = &hst.State{
+ testState = hst.State{
ID: testID,
PID: 0xcafebabe,
ShimPID: 0xdeadbeef,
Config: hst.Template(),
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()
testAppTime = time.Unix(0, 9).UTC()
)
@@ -133,7 +152,7 @@ Session bus
`, false},
- {"instance", testState, hst.Template(), false, false, `State
+ {"instance", &testState, hst.Template(), false, false, `State
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
Uptime: 1h2m32s
@@ -173,7 +192,7 @@ System bus
Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"]
`, 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
Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3405691582 -> 3735928559)
@@ -187,7 +206,7 @@ App
{"json nil", nil, nil, false, true, `null
`, true},
- {"json instance", testState, nil, false, true, `{
+ {"json instance", &testState, nil, false, true, `{
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
"pid": 3405691582,
"shim_pid": 3735928559,
@@ -515,41 +534,27 @@ func TestPrintPs(t *testing.T) {
testCases := []struct {
name string
- entries map[hst.ID]*hst.State
+ data []hst.State
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 short", make(map[hst.ID]*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"},
+ {"no entries", []hst.State{}, false, false, " Instance PID Application Uptime\n", ""},
+ {"no entries short", []hst.State{}, true, false, "", ""},
- {"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
- 4cf073bd 256 0 (app.hakurei.4cf073bd) 1h2m32s
-`},
+ {"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"},
- {"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
-`},
- {"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{}))): {
- ID: (hst.ID)(bytes.Repeat([]byte{0xaa}, len(hst.ID{}))),
- PID: 0xbeef,
- ShimPID: 0xcafe,
- Config: &hst.Config{
- ID: "uk.gensokyo.cat",
- 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(),
- }}, false, true, `[
+ aaaaaaaa 48879 1 (app.hakurei.aaaaaaaa) 1h2m28s
+`, ""},
+ {"valid single", []hst.State{testState}, false, false, ` Instance PID Application Uptime
+ 4cf073bd 3405691582 9 (org.chromium.Chromium) 1h2m32s
+`, ""},
+
+ {"valid short", []hst.State{testStateSmall, testState}, true, false, "4cf073bd\naaaaaaaa\n", ""},
+ {"valid short single", []hst.State{testState}, true, false, "4cf073bd\n", ""},
+
+ {"valid json", []hst.State{testState, testStateSmall}, false, true, `[
{
"instance": "8e2c76b066dabe574cf073bdb46eb5c1",
"pid": 3405691582,
@@ -707,7 +712,7 @@ func TestPrintPs(t *testing.T) {
"instance": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"pid": 48879,
"shim_pid": 51966,
- "id": "uk.gensokyo.cat",
+ "id": "",
"enablements": {
"wayland": true,
"pulse": true
@@ -729,30 +734,44 @@ func TestPrintPs(t *testing.T) {
"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 {
+ 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.Parallel()
- output := new(strings.Builder)
- printPs(output, testTime, stubStore(tc.entries), tc.short, tc.json)
- if got := output.String(); got != tc.want {
- t.Errorf("printPs: got\n%s\nwant\n%s",
- got, tc.want)
+ var printBuf, logBuf bytes.Buffer
+ msg := message.NewMsg(log.New(&logBuf, "check: ", 0))
+ msg.SwapVerbose(true)
+ printPs(msg, &printBuf, testTime, s, tc.short, tc.json)
+ if got := printBuf.String(); got != tc.want {
+ t.Errorf("printPs:\n%s\nwant\n%s", got, tc.want)
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 }