Files
hakurei/cmd/hakurei/print.go
Ophestra c9facb746b hst/config: remove data field, rename dir to home
There is no reason to give the home directory special treatment, as this behaviour can be quite confusing. The home directory also does not necessarily require its own mount point, it could be provided by a parent or simply be ephemeral.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-08-26 00:56:10 +09:00

297 lines
6.8 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"slices"
"strconv"
"strings"
"text/tabwriter"
"time"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system/dbus"
)
func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output)
defer t.MustFlush()
info := &hst.Info{Paths: std.Paths()}
// get hid by querying uid of identity 0
if uid, err := std.Uid(0); err != nil {
hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
os.Exit(1)
} else {
info.User = (uid / 10000) - 100
}
if flagJSON {
printJSON(output, short, info)
return
}
t.Printf("User:\t%d\n", info.User)
t.Printf("TempDir:\t%s\n", info.TempDir)
t.Printf("SharePath:\t%s\n", info.SharePath)
t.Printf("RuntimePath:\t%s\n", info.RuntimePath)
t.Printf("RunDirPath:\t%s\n", info.RunDirPath)
}
func printShowInstance(
output io.Writer, now time.Time,
instance *state.State, config *hst.Config,
short, flagJSON bool) {
if flagJSON {
if instance != nil {
printJSON(output, short, instance)
} else {
printJSON(output, short, config)
}
return
}
t := newPrinter(output)
defer t.MustFlush()
if config.Container == nil {
mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n")
}
if instance != nil {
t.Printf("State\n")
t.Printf(" Instance:\t%s (%d)\n", instance.ID.String(), instance.PID)
t.Printf(" Uptime:\t%s\n", now.Sub(instance.Time).Round(time.Second).String())
t.Printf("\n")
}
t.Printf("App\n")
if config.ID != "" {
t.Printf(" Identity:\t%d (%s)\n", config.Identity, config.ID)
} else {
t.Printf(" Identity:\t%d\n", config.Identity)
}
t.Printf(" Enablements:\t%s\n", config.Enablements.Unwrap().String())
if len(config.Groups) > 0 {
t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", "))
}
if config.Home != nil {
t.Printf(" Home:\t%s\n", config.Home)
}
if config.Container != nil {
params := config.Container
if params.Hostname != "" {
t.Printf(" Hostname:\t%s\n", params.Hostname)
}
flags := make([]string, 0, 7)
writeFlag := func(name string, value bool) {
if value {
flags = append(flags, name)
}
}
writeFlag("userns", params.Userns)
writeFlag("devel", params.Devel)
writeFlag("net", params.HostNet)
writeFlag("abstract", params.HostAbstract)
writeFlag("device", params.Device)
writeFlag("tty", params.Tty)
writeFlag("mapuid", params.MapRealUID)
writeFlag("directwl", config.DirectWayland)
if len(flags) == 0 {
flags = append(flags, "none")
}
t.Printf(" Flags:\t%s\n", strings.Join(flags, " "))
if config.Path != nil {
t.Printf(" Path:\t%s\n", config.Path)
}
}
if len(config.Args) > 0 {
t.Printf(" Arguments:\t%s\n", strings.Join(config.Args, " "))
}
t.Printf("\n")
if !short {
if config.Container != nil && len(config.Container.Filesystem) > 0 {
t.Printf("Filesystem\n")
for _, f := range config.Container.Filesystem {
if !f.Valid() {
t.Println(" <invalid>")
continue
}
t.Printf(" %s\n", f)
}
t.Printf("\n")
}
if len(config.ExtraPerms) > 0 {
t.Printf("Extra ACL\n")
for _, p := range config.ExtraPerms {
if p == nil {
continue
}
t.Printf(" %s\n", p.String())
}
t.Printf("\n")
}
}
printDBus := func(c *dbus.Config) {
t.Printf(" Filter:\t%v\n", c.Filter)
if len(c.See) > 0 {
t.Printf(" See:\t%q\n", c.See)
}
if len(c.Talk) > 0 {
t.Printf(" Talk:\t%q\n", c.Talk)
}
if len(c.Own) > 0 {
t.Printf(" Own:\t%q\n", c.Own)
}
if len(c.Call) > 0 {
t.Printf(" Call:\t%q\n", c.Call)
}
if len(c.Broadcast) > 0 {
t.Printf(" Broadcast:\t%q\n", c.Broadcast)
}
}
if config.SessionBus != nil {
t.Printf("Session bus\n")
printDBus(config.SessionBus)
t.Printf("\n")
}
if config.SystemBus != nil {
t.Printf("System bus\n")
printDBus(config.SystemBus)
t.Printf("\n")
}
}
func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
var entries state.Entries
if e, err := state.Join(s); err != nil {
log.Fatalf("cannot join store: %v", err)
} else {
entries = e
}
if err := s.Close(); err != nil {
log.Printf("cannot close store: %v", err)
}
if !short && flagJSON {
es := make(map[string]*state.State, len(entries))
for id, instance := range entries {
es[id.String()] = instance
}
printJSON(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
}
// gracefully skip inconsistent states
if id != instance.ID {
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
}
printJSON(output, short, v)
} else {
for _, e := range exp {
mustPrintln(output, e.s[:8])
}
}
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
}
as := "(No configuration information)"
if e.Config != nil {
as = strconv.Itoa(e.Config.Identity)
id := e.Config.ID
if id == "" {
id = "app.hakurei." + e.s[:8]
}
as += " (" + id + ")"
}
t.Printf("\t%s\t%d\t%s\t%s\n",
e.s[:8], e.PID, as, now.Sub(e.Time).Round(time.Second).String())
}
}
type expandedStateEntry struct {
s string
*state.State
}
func printJSON(output io.Writer, short bool, v any) {
encoder := json.NewEncoder(output)
if !short {
encoder.SetIndent("", " ")
}
if err := encoder.Encode(v); err != nil {
log.Fatalf("cannot serialise: %v", err)
}
}
func newPrinter(output io.Writer) *tp { return &tp{tabwriter.NewWriter(output, 0, 1, 4, ' ', 0)} }
type tp struct{ *tabwriter.Writer }
func (p *tp) Printf(format string, a ...any) {
if _, err := fmt.Fprintf(p, format, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
}
}
func (p *tp) Println(a ...any) {
if _, err := fmt.Fprintln(p, a...); err != nil {
log.Fatalf("cannot write to tabwriter: %v", err)
}
}
func (p *tp) MustFlush() {
if err := p.Writer.Flush(); err != nil {
log.Fatalf("cannot flush tabwriter: %v", err)
}
}
func mustPrint(output io.Writer, a ...any) {
if _, err := fmt.Fprint(output, a...); err != nil {
log.Fatalf("cannot print: %v", err)
}
}
func mustPrintln(output io.Writer, a ...any) {
if _, err := fmt.Fprintln(output, a...); err != nil {
log.Fatalf("cannot print: %v", err)
}
}