All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 34s
				
			Test / Sandbox (push) Successful in 2m13s
				
			Test / Hakurei (push) Successful in 3m6s
				
			Test / Hpkg (push) Successful in 4m2s
				
			Test / Sandbox (race detector) (push) Successful in 4m5s
				
			Test / Hakurei (race detector) (push) Successful in 4m51s
				
			Test / Flake checks (push) Successful in 1m30s
				
			This is now part of the hst API. This change also improves identifier generation and serialisation. Signed-off-by: Ophestra <cat@gensokyo.uk>
		
			
				
	
	
		
			292 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			292 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"slices"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"text/tabwriter"
 | |
| 	"time"
 | |
| 
 | |
| 	"hakurei.app/hst"
 | |
| 	"hakurei.app/internal"
 | |
| 	"hakurei.app/internal/app"
 | |
| 	"hakurei.app/internal/app/state"
 | |
| 	"hakurei.app/message"
 | |
| )
 | |
| 
 | |
| // printShowSystem populates and writes a representation of [hst.Info] to output.
 | |
| func printShowSystem(output io.Writer, short, flagJSON bool) {
 | |
| 	t := newPrinter(output)
 | |
| 	defer t.MustFlush()
 | |
| 
 | |
| 	info := &hst.Info{Version: internal.Version(), User: new(app.Hsu).MustID(nil)}
 | |
| 	app.CopyPaths().Copy(&info.Paths, info.User)
 | |
| 
 | |
| 	if flagJSON {
 | |
| 		encodeJSON(log.Fatal, output, short, info)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	t.Printf("Version:\t%s\n", info.Version)
 | |
| 	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)
 | |
| }
 | |
| 
 | |
| // printShowInstance writes a representation of [state.State] or [hst.Config] to output.
 | |
| func printShowInstance(
 | |
| 	output io.Writer, now time.Time,
 | |
| 	instance *hst.State, config *hst.Config,
 | |
| 	short, flagJSON bool) (valid bool) {
 | |
| 	valid = true
 | |
| 
 | |
| 	if flagJSON {
 | |
| 		if instance != nil {
 | |
| 			encodeJSON(log.Fatal, output, short, instance)
 | |
| 		} else {
 | |
| 			encodeJSON(log.Fatal, output, short, config)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	t := newPrinter(output)
 | |
| 	defer t.MustFlush()
 | |
| 
 | |
| 	if err := config.Validate(); err != nil {
 | |
| 		valid = false
 | |
| 		if m, ok := message.GetMessage(err); ok {
 | |
| 			mustPrint(output, "Error: "+m+"!\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.Container != nil {
 | |
| 		params := config.Container
 | |
| 		if params.Home != nil {
 | |
| 			t.Printf(" Home:\t%s\n", params.Home)
 | |
| 		}
 | |
| 		if params.Hostname != "" {
 | |
| 			t.Printf(" Hostname:\t%s\n", params.Hostname)
 | |
| 		}
 | |
| 		flags := params.Flags.String()
 | |
| 
 | |
| 		// this is included in the upper hst.Config struct but is relevant here
 | |
| 		const flagDirectWayland = "directwl"
 | |
| 		if config.DirectWayland {
 | |
| 			// hardcoded value when every flag is unset
 | |
| 			if flags == "none" {
 | |
| 				flags = flagDirectWayland
 | |
| 			} else {
 | |
| 				flags += ", " + flagDirectWayland
 | |
| 			}
 | |
| 		}
 | |
| 		t.Printf(" Flags:\t%s\n", flags)
 | |
| 
 | |
| 		if params.Path != nil {
 | |
| 			t.Printf(" Path:\t%s\n", params.Path)
 | |
| 		}
 | |
| 		if len(params.Args) > 0 {
 | |
| 			t.Printf(" Arguments:\t%s\n", strings.Join(params.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() {
 | |
| 					valid = false
 | |
| 					t.Println(" <invalid>")
 | |
| 					continue
 | |
| 				}
 | |
| 				t.Printf(" %s\n", f)
 | |
| 			}
 | |
| 			t.Printf("\n")
 | |
| 		}
 | |
| 		if len(config.ExtraPerms) > 0 {
 | |
| 			t.Printf("Extra ACL\n")
 | |
| 			for i := range config.ExtraPerms {
 | |
| 				t.Printf(" %s\n", config.ExtraPerms[i].String())
 | |
| 			}
 | |
| 			t.Printf("\n")
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	printDBus := func(c *hst.BusConfig) {
 | |
| 		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")
 | |
| 	}
 | |
| 
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // printPs writes a representation of active instances to output.
 | |
| func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) {
 | |
| 	var entries map[hst.ID]*hst.State
 | |
| 	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]*hst.State, len(entries))
 | |
| 		for id, instance := range entries {
 | |
| 			es[id.String()] = instance
 | |
| 		}
 | |
| 		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
 | |
| 		}
 | |
| 
 | |
| 		// 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
 | |
| 			}
 | |
| 			encodeJSON(log.Fatal, 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())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // expandedStateEntry stores [state.State] alongside a string representation of its [state.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)} }
 | |
| 
 | |
| // tp wraps [tabwriter.Writer] to provide additional formatting methods.
 | |
| type tp struct{ *tabwriter.Writer }
 | |
| 
 | |
| // Printf calls [fmt.Fprintf] on the underlying [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)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Println calls [fmt.Fprintln] on the underlying [tabwriter.Writer].
 | |
| func (p *tp) Println(a ...any) {
 | |
| 	if _, err := fmt.Fprintln(p, a...); err != nil {
 | |
| 		log.Fatalf("cannot write to tabwriter: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // MustFlush calls the Flush method of [tabwriter.Writer] and calls [log.Fatalf] on a non-nil error.
 | |
| 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)
 | |
| 	}
 | |
| }
 |