All checks were successful
		
		
	
	Test / Create distribution (push) Successful in 35s
				
			Test / Sandbox (race detector) (push) Successful in 3m56s
				
			Test / Hpkg (push) Successful in 4m2s
				
			Test / Hakurei (race detector) (push) Successful in 4m44s
				
			Test / Sandbox (push) Successful in 1m23s
				
			Test / Hakurei (push) Successful in 2m14s
				
			Test / Flake checks (push) Successful in 1m26s
				
			This enables external testing of system.I state. Signed-off-by: Ophestra <cat@gensokyo.uk>
		
			
				
	
	
		
			208 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package system
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"reflect"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"syscall"
 | |
| 
 | |
| 	"hakurei.app/container"
 | |
| 	"hakurei.app/hst"
 | |
| 	"hakurei.app/system/dbus"
 | |
| )
 | |
| 
 | |
| // ErrDBusConfig is returned when a required [hst.BusConfig] argument is nil.
 | |
| var ErrDBusConfig = errors.New("dbus config not supplied")
 | |
| 
 | |
| // MustProxyDBus calls ProxyDBus and panics if an error is returned.
 | |
| func (sys *I) MustProxyDBus(
 | |
| 	session, system *hst.BusConfig,
 | |
| 	sessionBus, systemBus dbus.ProxyPair,
 | |
| ) *I {
 | |
| 	if err := sys.ProxyDBus(session, system, sessionBus, systemBus); err != nil {
 | |
| 		panic(err.Error())
 | |
| 	} else {
 | |
| 		return sys
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via [dbus] and terminates it on revert.
 | |
| // This [Op] is always [Process] scoped.
 | |
| func (sys *I) ProxyDBus(
 | |
| 	session, system *hst.BusConfig,
 | |
| 	sessionBus, systemBus dbus.ProxyPair,
 | |
| ) error {
 | |
| 	d := new(dbusProxyOp)
 | |
| 
 | |
| 	// session bus is required as otherwise this is effectively a very expensive noop
 | |
| 	if session == nil {
 | |
| 		return newOpErrorMessage("dbus", ErrDBusConfig,
 | |
| 			"attempted to create message bus proxy args without session bus config", false)
 | |
| 	}
 | |
| 
 | |
| 	// system bus is optional
 | |
| 	d.system = system != nil
 | |
| 
 | |
| 	d.out = &linePrefixWriter{println: log.Println, prefix: "(dbus) ", buf: new(strings.Builder)}
 | |
| 	if final, err := sys.dbusFinalise(sessionBus, systemBus, session, system); err != nil {
 | |
| 		if errors.Is(err, syscall.EINVAL) {
 | |
| 			return newOpErrorMessage("dbus", err,
 | |
| 				"message bus proxy configuration contains NUL byte", false)
 | |
| 		}
 | |
| 		return newOpErrorMessage("dbus", err,
 | |
| 			fmt.Sprintf("cannot finalise message bus proxy: %v", err), false)
 | |
| 	} else {
 | |
| 		if sys.msg.IsVerbose() {
 | |
| 			sys.msg.Verbose("session bus proxy:", dbus.Args(session, sessionBus))
 | |
| 			if system != nil {
 | |
| 				sys.msg.Verbose("system bus proxy:", dbus.Args(system, systemBus))
 | |
| 			}
 | |
| 
 | |
| 			// this calls the argsWt String method
 | |
| 			sys.msg.Verbose("message bus proxy final args:", final.WriterTo)
 | |
| 		}
 | |
| 
 | |
| 		d.final = final
 | |
| 	}
 | |
| 
 | |
| 	sys.ops = append(sys.ops, d)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // dbusProxyOp implements [I.ProxyDBus].
 | |
| type dbusProxyOp struct {
 | |
| 	proxy *dbus.Proxy // populated during apply
 | |
| 
 | |
| 	final *dbus.Final
 | |
| 	out   *linePrefixWriter
 | |
| 	// whether system bus proxy is enabled
 | |
| 	system bool
 | |
| }
 | |
| 
 | |
| func (d *dbusProxyOp) Type() hst.Enablement { return Process }
 | |
| 
 | |
| func (d *dbusProxyOp) apply(sys *I) error {
 | |
| 	sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
 | |
| 	if d.system {
 | |
| 		sys.msg.Verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
 | |
| 	}
 | |
| 
 | |
| 	d.proxy = dbus.New(sys.ctx, sys.msg, d.final, d.out)
 | |
| 	if err := sys.dbusProxyStart(d.proxy); err != nil {
 | |
| 		d.out.Dump()
 | |
| 		return newOpErrorMessage("dbus", err,
 | |
| 			fmt.Sprintf("cannot start message bus proxy: %v", err), false)
 | |
| 	}
 | |
| 	sys.msg.Verbose("starting message bus proxy", d.proxy)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (d *dbusProxyOp) revert(sys *I, _ *Criteria) error {
 | |
| 	// criteria ignored here since dbus is always process-scoped
 | |
| 	sys.msg.Verbose("terminating message bus proxy")
 | |
| 	sys.dbusProxyClose(d.proxy)
 | |
| 
 | |
| 	exitMessage := "message bus proxy exit"
 | |
| 	defer func() { sys.msg.Verbose(exitMessage) }()
 | |
| 
 | |
| 	if d.out != nil {
 | |
| 		d.out.Dump()
 | |
| 	}
 | |
| 
 | |
| 	err := sys.dbusProxyWait(d.proxy)
 | |
| 	if errors.Is(err, context.Canceled) {
 | |
| 		exitMessage = "message bus proxy canceled upstream"
 | |
| 		err = nil
 | |
| 	}
 | |
| 	return newOpErrorMessage("dbus", err,
 | |
| 		fmt.Sprintf("message bus proxy error: %v", err), true)
 | |
| }
 | |
| 
 | |
| func (d *dbusProxyOp) Is(o Op) bool {
 | |
| 	target, ok := o.(*dbusProxyOp)
 | |
| 	return ok && d != nil && target != nil &&
 | |
| 		d.system == target.system &&
 | |
| 		d.final != nil && target.final != nil &&
 | |
| 		d.final.Session == target.final.Session &&
 | |
| 		d.final.System == target.final.System &&
 | |
| 		dbus.EqualAddrEntries(d.final.SessionUpstream, target.final.SessionUpstream) &&
 | |
| 		dbus.EqualAddrEntries(d.final.SystemUpstream, target.final.SystemUpstream) &&
 | |
| 		reflect.DeepEqual(d.final.WriterTo, target.final.WriterTo)
 | |
| }
 | |
| 
 | |
| func (d *dbusProxyOp) Path() string   { return container.Nonexistent }
 | |
| func (d *dbusProxyOp) String() string { return d.proxy.String() }
 | |
| 
 | |
| const (
 | |
| 	// lpwSizeThreshold is the threshold of bytes written to linePrefixWriter which,
 | |
| 	// if reached or exceeded, causes linePrefixWriter to drop all future writes.
 | |
| 	lpwSizeThreshold = 1 << 24
 | |
| )
 | |
| 
 | |
| // linePrefixWriter calls println with a prefix for every line written.
 | |
| type linePrefixWriter struct {
 | |
| 	prefix  string
 | |
| 	println func(v ...any)
 | |
| 
 | |
| 	n   int
 | |
| 	msg []string
 | |
| 	buf *strings.Builder
 | |
| 
 | |
| 	mu sync.RWMutex
 | |
| }
 | |
| 
 | |
| func (s *linePrefixWriter) Write(p []byte) (n int, err error) {
 | |
| 	s.mu.Lock()
 | |
| 	defer s.mu.Unlock()
 | |
| 	return s.write(p, 0)
 | |
| }
 | |
| 
 | |
| func (s *linePrefixWriter) write(p []byte, a int) (int, error) {
 | |
| 	if s.n >= lpwSizeThreshold {
 | |
| 		if len(p) == 0 {
 | |
| 			return a, nil
 | |
| 		}
 | |
| 		return a, syscall.ENOMEM
 | |
| 	}
 | |
| 
 | |
| 	if i := bytes.IndexByte(p, '\n'); i == -1 {
 | |
| 		n, _ := s.buf.Write(p)
 | |
| 		s.n += n
 | |
| 		return a + n, nil
 | |
| 	} else {
 | |
| 		n, _ := s.buf.Write(p[:i])
 | |
| 		s.n += n + 1
 | |
| 
 | |
| 		v := s.buf.String()
 | |
| 		if strings.HasPrefix(v, "init: ") {
 | |
| 			s.n -= len(v) + 1
 | |
| 			// pass through container init messages
 | |
| 			s.println(s.prefix + v)
 | |
| 		} else {
 | |
| 			s.msg = append(s.msg, v)
 | |
| 		}
 | |
| 
 | |
| 		s.buf.Reset()
 | |
| 		return s.write(p[i+1:], a+n+1)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *linePrefixWriter) Dump() {
 | |
| 	s.mu.RLock()
 | |
| 	for _, m := range s.msg {
 | |
| 		s.println(s.prefix + m)
 | |
| 	}
 | |
| 	if s.buf != nil && s.buf.Len() != 0 {
 | |
| 		s.println("*" + s.prefix + s.buf.String())
 | |
| 	}
 | |
| 	if s.n >= lpwSizeThreshold {
 | |
| 		s.println("+" + s.prefix + "write threshold reached, output may be incomplete")
 | |
| 	}
 | |
| 	s.mu.RUnlock()
 | |
| }
 |