From 4b7d616862dc7d8c73561b85deb0ca1fabf30781 Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Tue, 17 Sep 2024 13:48:42 +0900 Subject: [PATCH] exit: move final and early code to internal package Exit cleanup state information is now stored in a dedicated struct and built up using methods of that struct. Signed-off-by: Ophestra Umiker --- flag.go | 4 +- internal/app/dbus.go | 23 ++-- internal/app/ensure.go | 22 ++-- internal/app/launch.go | 4 +- internal/app/pulse.go | 76 ++++++++++--- internal/app/run.go | 20 ++-- internal/app/setup.go | 21 +++- internal/app/wayland.go | 11 +- internal/app/x.go | 11 +- internal/early.go | 16 +++ internal/{state => }/enablement.go | 6 +- internal/exit.go | 176 +++++++++++++++++++++++++++++ internal/final/exit.go | 74 ------------ internal/final/prepare.go | 20 ---- internal/final/register.go | 48 -------- internal/state/data.go | 14 ++- internal/state/print.go | 5 +- internal/state/track.go | 4 +- internal/util/early.go | 12 -- internal/util/std.go | 46 -------- main.go | 20 ++-- 21 files changed, 346 insertions(+), 287 deletions(-) create mode 100644 internal/early.go rename internal/{state => }/enablement.go (83%) create mode 100644 internal/exit.go delete mode 100644 internal/final/exit.go delete mode 100644 internal/final/prepare.go delete mode 100644 internal/final/register.go delete mode 100644 internal/util/early.go diff --git a/flag.go b/flag.go index f69f8f3..04bc329 100644 --- a/flag.go +++ b/flag.go @@ -3,7 +3,7 @@ package main import ( "flag" - "git.ophivana.moe/cat/fortify/internal/util" + "git.ophivana.moe/cat/fortify/internal" ) var ( @@ -44,7 +44,7 @@ func init() { func init() { methodHelpString := "Method of launching the child process, can be one of \"sudo\", \"bubblewrap\"" - if util.SdBootedV { + if internal.SdBootedV { methodHelpString += ", \"systemd\"" } diff --git a/internal/app/dbus.go b/internal/app/dbus.go index a327e2c..fe8039f 100644 --- a/internal/app/dbus.go +++ b/internal/app/dbus.go @@ -9,8 +9,7 @@ import ( "git.ophivana.moe/cat/fortify/acl" "git.ophivana.moe/cat/fortify/dbus" - "git.ophivana.moe/cat/fortify/internal/final" - "git.ophivana.moe/cat/fortify/internal/state" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/util" "git.ophivana.moe/cat/fortify/internal/verbose" ) @@ -26,7 +25,7 @@ var ( ) func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) { - a.setEnablement(state.EnableDBus) + a.setEnablement(internal.EnableDBus) dbusSystem = dsg != nil var binPath string @@ -41,7 +40,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) { } if b, ok := util.Which("xdg-dbus-proxy"); !ok { - final.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH") + internal.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH") } else { binPath = b } @@ -69,7 +68,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) { verbose.Println("D-Bus: sealing system proxy", dsg.Args(systemBus)) } if err := p.Seal(dse, dsg); err != nil { - final.Fatal("D-Bus: invalid config when sealing proxy,", err) + internal.Fatal("D-Bus: invalid config when sealing proxy,", err) } ready := make(chan bool, 1) @@ -80,7 +79,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) { verbose.Printf("Starting system bus proxy '%s' for address '%s'\n", dbusAddress[1], systemBus[0]) } if err := p.Start(&ready); err != nil { - final.Fatal("D-Bus: error starting proxy,", err) + internal.Fatal("D-Bus: error starting proxy,", err) } verbose.Println("D-Bus proxy launch:", p) @@ -97,24 +96,24 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) { }() // register early to enable Fatal cleanup - final.RegisterDBus(p, &done) + a.exit.SealDBus(p, &done) if !<-ready { - final.Fatal("D-Bus: proxy did not start correctly") + internal.Fatal("D-Bus: proxy did not start correctly") } a.AppendEnv(dbusSessionBusAddress, dbusAddress[0]) if err := acl.UpdatePerm(sessionBus[1], a.UID(), acl.Read, acl.Write); err != nil { - final.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err) + internal.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err) } else { - final.RegisterRevertPath(sessionBus[1]) + a.exit.RegisterRevertPath(sessionBus[1]) } if dsg != nil { a.AppendEnv(dbusSystemBusAddress, dbusAddress[1]) if err := acl.UpdatePerm(systemBus[1], a.UID(), acl.Read, acl.Write); err != nil { - final.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err) + internal.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err) } else { - final.RegisterRevertPath(systemBus[1]) + a.exit.RegisterRevertPath(systemBus[1]) } } verbose.Printf("Session bus proxy '%s' for address '%s' configured\n", dbusAddress[0], sessionBus[0]) diff --git a/internal/app/ensure.go b/internal/app/ensure.go index ada6349..ed283a9 100644 --- a/internal/app/ensure.go +++ b/internal/app/ensure.go @@ -8,29 +8,29 @@ import ( "path" "git.ophivana.moe/cat/fortify/acl" - "git.ophivana.moe/cat/fortify/internal/final" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/verbose" ) func (a *App) EnsureRunDir() { if err := os.Mkdir(a.runDirPath, 0700); err != nil && !errors.Is(err, fs.ErrExist) { - final.Fatal("Error creating runtime directory:", err) + internal.Fatal("Error creating runtime directory:", err) } } func (a *App) EnsureRuntime() { if s, err := os.Stat(a.runtimePath); err != nil { if errors.Is(err, fs.ErrNotExist) { - final.Fatal("Runtime directory does not exist") + internal.Fatal("Runtime directory does not exist") } - final.Fatal("Error accessing runtime directory:", err) + internal.Fatal("Error accessing runtime directory:", err) } else if !s.IsDir() { - final.Fatal(fmt.Sprintf("Path '%s' is not a directory", a.runtimePath)) + internal.Fatal(fmt.Sprintf("Path '%s' is not a directory", a.runtimePath)) } else { if err = acl.UpdatePerm(a.runtimePath, a.UID(), acl.Execute); err != nil { - final.Fatal("Error preparing runtime directory:", err) + internal.Fatal("Error preparing runtime directory:", err) } else { - final.RegisterRevertPath(a.runtimePath) + a.exit.RegisterRevertPath(a.runtimePath) } verbose.Printf("Runtime data dir '%s' configured\n", a.runtimePath) } @@ -39,7 +39,7 @@ func (a *App) EnsureRuntime() { func (a *App) EnsureShare() { // acl is unnecessary as this directory is world executable if err := os.Mkdir(a.sharePath, 0701); err != nil && !errors.Is(err, fs.ErrExist) { - final.Fatal("Error creating shared directory:", err) + internal.Fatal("Error creating shared directory:", err) } // workaround for launch method sudo @@ -47,12 +47,12 @@ func (a *App) EnsureShare() { // ensure child runtime directory (e.g. `/tmp/fortify.%d/%d.share`) cr := path.Join(a.sharePath, a.Uid+".share") if err := os.Mkdir(cr, 0700); err != nil && !errors.Is(err, fs.ErrExist) { - final.Fatal("Error creating child runtime directory:", err) + internal.Fatal("Error creating child runtime directory:", err) } else { if err = acl.UpdatePerm(cr, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil { - final.Fatal("Error preparing child runtime directory:", err) + internal.Fatal("Error preparing child runtime directory:", err) } else { - final.RegisterRevertPath(cr) + a.exit.RegisterRevertPath(cr) } a.AppendEnv("XDG_RUNTIME_DIR", cr) a.AppendEnv("XDG_SESSION_CLASS", "user") diff --git a/internal/app/launch.go b/internal/app/launch.go index 886e683..b44c778 100644 --- a/internal/app/launch.go +++ b/internal/app/launch.go @@ -5,11 +5,11 @@ import ( "encoding/base64" "encoding/gob" "fmt" - "git.ophivana.moe/cat/fortify/internal/final" "os" "strings" "syscall" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/util" ) @@ -20,7 +20,7 @@ func (a *App) launcherPayloadEnv() string { enc := base64.NewEncoder(base64.StdEncoding, r) if err := gob.NewEncoder(enc).Encode(a.command); err != nil { - final.Fatal("Error encoding launcher payload:", err) + internal.Fatal("Error encoding launcher payload:", err) } _ = enc.Close() diff --git a/internal/app/pulse.go b/internal/app/pulse.go index 7c36025..8b4921e 100644 --- a/internal/app/pulse.go +++ b/internal/app/pulse.go @@ -8,58 +8,102 @@ import ( "path" "git.ophivana.moe/cat/fortify/acl" - "git.ophivana.moe/cat/fortify/internal/final" - "git.ophivana.moe/cat/fortify/internal/state" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/util" "git.ophivana.moe/cat/fortify/internal/verbose" ) +const ( + pulseServer = "PULSE_SERVER" + pulseCookie = "PULSE_COOKIE" + + home = "HOME" + xdgConfigHome = "XDG_CONFIG_HOME" +) + func (a *App) SharePulse() { - a.setEnablement(state.EnablePulse) + a.setEnablement(internal.EnablePulse) // ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`) pulse := path.Join(a.runtimePath, "pulse") pulseS := path.Join(pulse, "native") if s, err := os.Stat(pulse); err != nil { if !errors.Is(err, fs.ErrNotExist) { - final.Fatal("Error accessing PulseAudio directory:", err) + internal.Fatal("Error accessing PulseAudio directory:", err) } - final.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse)) + internal.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse)) } else { // add environment variable for new process - a.AppendEnv(util.PulseServer, "unix:"+pulseS) + a.AppendEnv(pulseServer, "unix:"+pulseS) if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil { - final.Fatal("Error preparing PulseAudio:", err) + internal.Fatal("Error preparing PulseAudio:", err) } else { - final.RegisterRevertPath(pulse) + a.exit.RegisterRevertPath(pulse) } // ensure PulseAudio socket permission (e.g. `/run/user/%d/pulse/native`) if s, err = os.Stat(pulseS); err != nil { if errors.Is(err, fs.ErrNotExist) { - final.Fatal("PulseAudio directory found but socket does not exist") + internal.Fatal("PulseAudio directory found but socket does not exist") } - final.Fatal("Error accessing PulseAudio socket:", err) + internal.Fatal("Error accessing PulseAudio socket:", err) } else { if m := s.Mode(); m&0o006 != 0o006 { - final.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m) + internal.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m) } } // Publish current user's pulse-cookie for target user - pulseCookieSource := util.DiscoverPulseCookie() + pulseCookieSource := discoverPulseCookie() pulseCookieFinal := path.Join(a.sharePath, "pulse-cookie") - a.AppendEnv(util.PulseCookie, pulseCookieFinal) + a.AppendEnv(pulseCookie, pulseCookieFinal) verbose.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal) if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil { - final.Fatal("Error copying PulseAudio cookie:", err) + internal.Fatal("Error copying PulseAudio cookie:", err) } if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil { - final.Fatal("Error publishing PulseAudio cookie:", err) + internal.Fatal("Error publishing PulseAudio cookie:", err) } else { - final.RegisterRevertPath(pulseCookieFinal) + a.exit.RegisterRevertPath(pulseCookieFinal) } verbose.Printf("PulseAudio dir '%s' configured\n", pulse) } } + +// discoverPulseCookie try various standard methods to discover the current user's PulseAudio authentication cookie +func discoverPulseCookie() string { + if p, ok := os.LookupEnv(pulseCookie); ok { + return p + } + + if p, ok := os.LookupEnv(home); ok { + p = path.Join(p, ".pulse-cookie") + if s, err := os.Stat(p); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + internal.Fatal("Error accessing PulseAudio cookie:", err) + // unreachable + return p + } + } else if !s.IsDir() { + return p + } + } + + if p, ok := os.LookupEnv(xdgConfigHome); ok { + p = path.Join(p, "pulse", "cookie") + if s, err := os.Stat(p); err != nil { + if !errors.Is(err, fs.ErrNotExist) { + internal.Fatal("Error accessing PulseAudio cookie:", err) + // unreachable + return p + } + } else if !s.IsDir() { + return p + } + } + + internal.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)", + pulseCookie, xdgConfigHome, home)) + return "" +} diff --git a/internal/app/run.go b/internal/app/run.go index d4a9e13..cc01f48 100644 --- a/internal/app/run.go +++ b/internal/app/run.go @@ -3,11 +3,11 @@ package app import ( "errors" "fmt" - "git.ophivana.moe/cat/fortify/internal/final" "os" "os/exec" "strings" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/state" "git.ophivana.moe/cat/fortify/internal/util" "git.ophivana.moe/cat/fortify/internal/verbose" @@ -52,28 +52,28 @@ func (a *App) Run() { verbose.Println("Executing:", cmd) if err := cmd.Start(); err != nil { - final.Fatal("Error starting process:", err) + internal.Fatal("Error starting process:", err) } - final.RegisterEnablement(a.enablements) + a.exit.SealEnablements(a.enablements) if statePath, err := state.SaveProcess(a.Uid, cmd, a.runDirPath, a.command, a.enablements); err != nil { // process already started, shouldn't be fatal fmt.Println("Error registering process:", err) } else { - final.RegisterStatePath(statePath) + a.exit.SealStatePath(statePath) } var r int if err := cmd.Wait(); err != nil { var exitError *exec.ExitError if !errors.As(err, &exitError) { - final.Fatal("Error running process:", err) + internal.Fatal("Error running process:", err) } } verbose.Println("Process exited with exit code", r) - final.BeforeExit() + internal.BeforeExit() os.Exit(r) } @@ -101,7 +101,7 @@ func (a *App) commandBuilderSudo() (args []string) { func (a *App) commandBuilderBwrap() (args []string) { // TODO: build bwrap command - final.Fatal("bwrap") + internal.Fatal("bwrap") panic("unreachable") } @@ -129,7 +129,7 @@ func (a *App) commandBuilderMachineCtl() (args []string) { // /bin/sh -c if sh, ok := util.Which("sh"); !ok { - final.Fatal("Did not find 'sh' in PATH") + internal.Fatal("Did not find 'sh' in PATH") } else { args = append(args, sh, "-c") } @@ -147,9 +147,9 @@ func (a *App) commandBuilderMachineCtl() (args []string) { innerCommand.WriteString("; ") if executable, err := os.Executable(); err != nil { - final.Fatal("Error reading executable path:", err) + internal.Fatal("Error reading executable path:", err) } else { - if a.enablements.Has(state.EnableDBus) { + if a.enablements.Has(internal.EnableDBus) { innerCommand.WriteString(dbusSessionBusAddress + "=" + "'" + dbusAddress[0] + "' ") if dbusSystem { innerCommand.WriteString(dbusSystemBusAddress + "=" + "'" + dbusAddress[1] + "' ") diff --git a/internal/app/setup.go b/internal/app/setup.go index 8d6eb88..c13468a 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -8,7 +8,7 @@ import ( "path" "strconv" - "git.ophivana.moe/cat/fortify/internal/state" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/util" "git.ophivana.moe/cat/fortify/internal/verbose" ) @@ -22,6 +22,8 @@ type App struct { env []string // modified via AppendEnv command []string // set on initialisation + exit *internal.ExitState // assigned + launchOptionText string // set on initialisation launchOption uint8 // assigned @@ -30,8 +32,8 @@ type App struct { runDirPath string // assigned toolPath string // assigned - enablements state.Enablements // set via setEnablement - *user.User // assigned + enablements internal.Enablements // set via setEnablement + *user.User // assigned // absolutely *no* method of this type is thread-safe // so don't treat it as if it is @@ -45,7 +47,7 @@ func (a *App) RunDir() string { return a.runDirPath } -func (a *App) setEnablement(e state.Enablement) { +func (a *App) setEnablement(e internal.Enablement) { if a.enablements.Has(e) { panic("enablement " + e.String() + " set twice") } @@ -53,6 +55,13 @@ func (a *App) setEnablement(e state.Enablement) { a.enablements |= e.Mask() } +func (a *App) SealExit(exit *internal.ExitState) { + if a.exit != nil { + panic("application exit state sealed twice") + } + a.exit = exit +} + func New(userName string, args []string, launchOptionText string) *App { a := &App{ command: args, @@ -96,7 +105,7 @@ func New(userName string, args []string, launchOptionText string) *App { } verbose.Println("Running as user", a.Username, "("+a.Uid+"),", "command:", a.command) - if util.SdBootedV { + if internal.SdBootedV { verbose.Println("System booted with systemd as init system (PID 1).") } @@ -120,7 +129,7 @@ func New(userName string, args []string, launchOptionText string) *App { } case "systemd": a.launchOption = LaunchMethodMachineCtl - if !util.SdBootedV { + if !internal.SdBootedV { fmt.Println("System has not been booted with systemd as init system (PID 1).") os.Exit(1) } diff --git a/internal/app/wayland.go b/internal/app/wayland.go index 3b12e3e..33ed991 100644 --- a/internal/app/wayland.go +++ b/internal/app/wayland.go @@ -6,8 +6,7 @@ import ( "path" "git.ophivana.moe/cat/fortify/acl" - "git.ophivana.moe/cat/fortify/internal/final" - "git.ophivana.moe/cat/fortify/internal/state" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/verbose" ) @@ -17,19 +16,19 @@ const ( ) func (a *App) ShareWayland() { - a.setEnablement(state.EnableWayland) + a.setEnablement(internal.EnableWayland) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) if w, ok := os.LookupEnv(waylandDisplay); !ok { - final.Fatal("Wayland: WAYLAND_DISPLAY not set") + internal.Fatal("Wayland: WAYLAND_DISPLAY not set") } else { // add environment variable for new process wp := path.Join(a.runtimePath, w) a.AppendEnv(waylandDisplay, wp) if err := acl.UpdatePerm(wp, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil { - final.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err) + internal.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err) } else { - final.RegisterRevertPath(wp) + a.exit.RegisterRevertPath(wp) } verbose.Printf("Wayland socket '%s' configured\n", w) } diff --git a/internal/app/x.go b/internal/app/x.go index f198fee..16b3d95 100644 --- a/internal/app/x.go +++ b/internal/app/x.go @@ -4,8 +4,7 @@ import ( "fmt" "os" - "git.ophivana.moe/cat/fortify/internal/final" - "git.ophivana.moe/cat/fortify/internal/state" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/verbose" "git.ophivana.moe/cat/fortify/xcb" ) @@ -13,20 +12,20 @@ import ( const display = "DISPLAY" func (a *App) ShareX() { - a.setEnablement(state.EnableX) + a.setEnablement(internal.EnableX) // discovery X11 and grant user permission via the `ChangeHosts` command if d, ok := os.LookupEnv(display); !ok { - final.Fatal("X11: DISPLAY not set") + internal.Fatal("X11: DISPLAY not set") } else { // add environment variable for new process a.AppendEnv(display, d) verbose.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", a.Username, d) if err := xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+a.Username); err != nil { - final.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err) + internal.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err) } else { - final.XcbActionComplete() + a.exit.XcbActionComplete() } } } diff --git a/internal/early.go b/internal/early.go new file mode 100644 index 0000000..48dfcd5 --- /dev/null +++ b/internal/early.go @@ -0,0 +1,16 @@ +package internal + +import ( + "fmt" + + "git.ophivana.moe/cat/fortify/internal/util" +) + +var SdBootedV = func() bool { + if v, err := util.SdBooted(); err != nil { + fmt.Println("warn: read systemd marker:", err) + return false + } else { + return v + } +}() diff --git a/internal/state/enablement.go b/internal/enablement.go similarity index 83% rename from internal/state/enablement.go rename to internal/enablement.go index 9358f43..a2dd1e6 100644 --- a/internal/state/enablement.go +++ b/internal/enablement.go @@ -1,4 +1,4 @@ -package state +package internal type ( Enablement uint8 @@ -11,10 +11,10 @@ const ( EnableDBus EnablePulse - enableLength + EnableLength ) -var enablementString = [enableLength]string{ +var enablementString = [EnableLength]string{ "Wayland", "X11", "D-Bus", diff --git a/internal/exit.go b/internal/exit.go new file mode 100644 index 0000000..35e8662 --- /dev/null +++ b/internal/exit.go @@ -0,0 +1,176 @@ +package internal + +import ( + "errors" + "fmt" + "io/fs" + "os" + "os/user" + + "git.ophivana.moe/cat/fortify/acl" + "git.ophivana.moe/cat/fortify/dbus" + "git.ophivana.moe/cat/fortify/internal/verbose" + "git.ophivana.moe/cat/fortify/xcb" +) + +// ExitState keeps track of various changes fortify made to the system +// as well as other resources that need to be manually released. +// NOT thread safe. +type ExitState struct { + // target fortified user inherited from app.App + user *user.User + // integer UID of targeted user + uid int + // returns amount of launcher states read + launcherStateCount func() (int, error) + + // paths to strip ACLs (of target user) from + aclCleanupCandidate []string + // target process capability enablements + enablements *Enablements + // whether the xcb.ChangeHosts action was complete + xcbActionComplete bool + + // reference to D-Bus proxy instance, nil if disabled + dbusProxy *dbus.Proxy + // D-Bus wait complete notification + dbusDone *chan struct{} + + // path to fortify process state information + statePath string + + // prevents cleanup from happening twice + complete bool +} + +// RegisterRevertPath registers a path with ACLs added by fortify +func (s *ExitState) RegisterRevertPath(p string) { + s.aclCleanupCandidate = append(s.aclCleanupCandidate, p) +} + +// SealEnablements submits the child process enablements +func (s *ExitState) SealEnablements(e Enablements) { + if s.enablements != nil { + panic("enablement exit state set twice") + } + s.enablements = &e +} + +// XcbActionComplete submits xcb.ChangeHosts action completion +func (s *ExitState) XcbActionComplete() { + if s.xcbActionComplete { + Fatal("xcb inserted twice") + } + s.xcbActionComplete = true +} + +// SealDBus submits the child's D-Bus proxy instance +func (s *ExitState) SealDBus(p *dbus.Proxy, done *chan struct{}) { + if p == nil { + Fatal("unexpected nil dbus proxy exit state submitted") + } + if s.dbusProxy != nil { + Fatal("dbus proxy exit state set twice") + } + s.dbusProxy = p + s.dbusDone = done +} + +// SealStatePath submits filesystem path to the fortify process's state file +func (s *ExitState) SealStatePath(v string) { + if s.statePath != "" { + panic("statePath set twice") + } + + s.statePath = v +} + +// NewExit initialises a new ExitState containing basic, unchanging information +// about the fortify process required during cleanup +func NewExit(u *user.User, uid int, f func() (int, error)) *ExitState { + return &ExitState{ + uid: uid, + user: u, + + launcherStateCount: f, + } +} + +func Fatal(msg ...any) { + fmt.Println(msg...) + BeforeExit() + os.Exit(1) +} + +var exitState *ExitState + +func SealExit(s *ExitState) { + if exitState != nil { + panic("exit state submitted twice") + } + + exitState = s +} + +func BeforeExit() { + if exitState == nil { + fmt.Println("warn: cleanup attempted before exit state submission") + return + } + + exitState.beforeExit() +} + +func (s *ExitState) beforeExit() { + if s.complete { + panic("beforeExit called twice") + } + + if s.statePath == "" { + verbose.Println("State path is unset") + } else { + if err := os.Remove(s.statePath); err != nil && !errors.Is(err, fs.ErrNotExist) { + fmt.Println("Error removing state file:", err) + } + } + + if count, err := s.launcherStateCount(); err != nil { + fmt.Println("Error reading active launchers:", err) + os.Exit(1) + } else if count > 0 { + // other launchers are still active + verbose.Printf("Found %d active launchers, exiting without cleaning up\n", count) + return + } + + verbose.Println("No other launchers active, will clean up") + + if s.xcbActionComplete { + verbose.Printf("X11: Removing XHost entry SI:localuser:%s\n", s.user.Username) + if err := xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+s.user.Username); err != nil { + fmt.Println("Error removing XHost entry:", err) + } + } + + for _, candidate := range s.aclCleanupCandidate { + if err := acl.UpdatePerm(candidate, s.uid); err != nil { + fmt.Printf("Error stripping ACL entry from '%s': %s\n", candidate, err) + } + verbose.Printf("Stripped ACL entry for user '%s' from '%s'\n", s.user.Username, candidate) + } + + if s.dbusProxy != nil { + verbose.Println("D-Bus proxy registered, cleaning up") + + if err := s.dbusProxy.Close(); err != nil { + if errors.Is(err, os.ErrClosed) { + verbose.Println("D-Bus proxy already closed") + } else { + fmt.Println("Error closing D-Bus proxy:", err) + } + } + + // wait for Proxy.Wait to return + <-*s.dbusDone + } +} diff --git a/internal/final/exit.go b/internal/final/exit.go deleted file mode 100644 index 063d8d0..0000000 --- a/internal/final/exit.go +++ /dev/null @@ -1,74 +0,0 @@ -package final - -import ( - "errors" - "fmt" - "io/fs" - "os" - - "git.ophivana.moe/cat/fortify/acl" - "git.ophivana.moe/cat/fortify/internal/state" - "git.ophivana.moe/cat/fortify/internal/verbose" - "git.ophivana.moe/cat/fortify/xcb" -) - -func Fatal(msg ...any) { - fmt.Println(msg...) - BeforeExit() - os.Exit(1) -} - -func BeforeExit() { - if u == nil { - fmt.Println("warn: beforeExit called before app init") - return - } - - if statePath == "" { - verbose.Println("State path is unset") - } else { - if err := os.Remove(statePath); err != nil && !errors.Is(err, fs.ErrNotExist) { - fmt.Println("Error removing state file:", err) - } - } - - if d, err := state.ReadLaunchers(runDirPath, u.Uid); err != nil { - fmt.Println("Error reading active launchers:", err) - os.Exit(1) - } else if len(d) > 0 { - // other launchers are still active - verbose.Printf("Found %d active launchers, exiting without cleaning up\n", len(d)) - return - } - - verbose.Println("No other launchers active, will clean up") - - if xcbActionComplete { - verbose.Printf("X11: Removing XHost entry SI:localuser:%s\n", u.Username) - if err := xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+u.Username); err != nil { - fmt.Println("Error removing XHost entry:", err) - } - } - - for _, candidate := range cleanupCandidate { - if err := acl.UpdatePerm(candidate, uid); err != nil { - fmt.Printf("Error stripping ACL entry from '%s': %s\n", candidate, err) - } - verbose.Printf("Stripped ACL entry for user '%s' from '%s'\n", u.Username, candidate) - } - - if dbusProxy != nil { - verbose.Println("D-Bus proxy registered, cleaning up") - - if err := dbusProxy.Close(); err != nil { - if errors.Is(err, os.ErrClosed) { - verbose.Println("D-Bus proxy already closed") - } else { - fmt.Println("Error closing D-Bus proxy:", err) - } - } - - // wait for Proxy.Wait to return - <-*dbusDone - } -} diff --git a/internal/final/prepare.go b/internal/final/prepare.go deleted file mode 100644 index 859013c..0000000 --- a/internal/final/prepare.go +++ /dev/null @@ -1,20 +0,0 @@ -package final - -import "os/user" - -var ( - u *user.User - uid int - - runDirPath string -) - -func Prepare(val user.User, d int, s string) { - if u != nil { - panic("final prepared twice") - } - - u = &val - uid = d - runDirPath = s -} diff --git a/internal/final/register.go b/internal/final/register.go deleted file mode 100644 index 16e8d86..0000000 --- a/internal/final/register.go +++ /dev/null @@ -1,48 +0,0 @@ -package final - -import ( - "git.ophivana.moe/cat/fortify/dbus" - "git.ophivana.moe/cat/fortify/internal/state" -) - -var ( - cleanupCandidate []string - enablements *state.Enablements - xcbActionComplete bool - - dbusProxy *dbus.Proxy - dbusDone *chan struct{} - - statePath string -) - -func RegisterRevertPath(p string) { - cleanupCandidate = append(cleanupCandidate, p) -} - -func RegisterEnablement(e state.Enablements) { - if enablements != nil { - panic("enablement state set twice") - } - enablements = &e -} - -func XcbActionComplete() { - if xcbActionComplete { - Fatal("xcb inserted twice") - } - xcbActionComplete = true -} - -func RegisterDBus(p *dbus.Proxy, done *chan struct{}) { - dbusProxy = p - dbusDone = done -} - -func RegisterStatePath(v string) { - if statePath != "" { - panic("statePath set twice") - } - - statePath = v -} diff --git a/internal/state/data.go b/internal/state/data.go index 65b6395..4549388 100644 --- a/internal/state/data.go +++ b/internal/state/data.go @@ -4,6 +4,8 @@ import ( "encoding/gob" "os" "path" + + "git.ophivana.moe/cat/fortify/internal" ) // we unfortunately have to assume there are never races between processes @@ -14,10 +16,12 @@ type launcherState struct { Launcher string Argv []string Command []string - Capability Enablements + Capability internal.Enablements } -func ReadLaunchers(runDirPath, uid string) ([]*launcherState, error) { +// ReadLaunchers reads all launcher state file entries for the requested user +// and if decode is true decodes these launchers as well. +func ReadLaunchers(runDirPath, uid string, decode bool) ([]*launcherState, error) { var f *os.File var r []*launcherState launcherPrefix := path.Join(runDirPath, uid) @@ -39,7 +43,11 @@ func ReadLaunchers(runDirPath, uid string) ([]*launcherState, error) { var s launcherState r = append(r, &s) - return gob.NewDecoder(f).Decode(&s) + if decode { + return gob.NewDecoder(f).Decode(&s) + } else { + return nil + } } }(); err != nil { return nil, err diff --git a/internal/state/print.go b/internal/state/print.go index 83912d3..838cacb 100644 --- a/internal/state/print.go +++ b/internal/state/print.go @@ -7,6 +7,7 @@ import ( "strings" "text/tabwriter" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/verbose" ) @@ -31,7 +32,7 @@ func MustPrintLauncherStateGlobal(w **tabwriter.Writer, runDirPath string) { } func MustPrintLauncherState(w **tabwriter.Writer, runDirPath, uid string) { - launchers, err := ReadLaunchers(runDirPath, uid) + launchers, err := ReadLaunchers(runDirPath, uid, true) if err != nil { fmt.Println("Error reading launchers:", err) os.Exit(1) @@ -49,7 +50,7 @@ func MustPrintLauncherState(w **tabwriter.Writer, runDirPath, uid string) { for _, state := range launchers { enablementsDescription := strings.Builder{} - for i := Enablement(0); i < enableLength; i++ { + for i := internal.Enablement(0); i < internal.EnableLength; i++ { if state.Capability.Has(i) { enablementsDescription.WriteString(", " + i.String()) } diff --git a/internal/state/track.go b/internal/state/track.go index 308be28..8ecad13 100644 --- a/internal/state/track.go +++ b/internal/state/track.go @@ -8,10 +8,12 @@ import ( "os/exec" "path" "strconv" + + "git.ophivana.moe/cat/fortify/internal" ) // SaveProcess called after process start, before wait -func SaveProcess(uid string, cmd *exec.Cmd, runDirPath string, command []string, enablements Enablements) (string, error) { +func SaveProcess(uid string, cmd *exec.Cmd, runDirPath string, command []string, enablements internal.Enablements) (string, error) { statePath := path.Join(runDirPath, uid, strconv.Itoa(cmd.Process.Pid)) state := launcherState{ PID: cmd.Process.Pid, diff --git a/internal/util/early.go b/internal/util/early.go deleted file mode 100644 index ba7375c..0000000 --- a/internal/util/early.go +++ /dev/null @@ -1,12 +0,0 @@ -package util - -import "fmt" - -var SdBootedV = func() bool { - if v, err := SdBooted(); err != nil { - fmt.Println("warn: read systemd marker:", err) - return false - } else { - return v - } -}() diff --git a/internal/util/std.go b/internal/util/std.go index 9709cc2..b237ad7 100644 --- a/internal/util/std.go +++ b/internal/util/std.go @@ -2,21 +2,12 @@ package util import ( "errors" - "fmt" - "git.ophivana.moe/cat/fortify/internal/final" "io/fs" "os" - "path" ) const ( systemdCheckPath = "/run/systemd/system" - - home = "HOME" - xdgConfigHome = "XDG_CONFIG_HOME" - - PulseServer = "PULSE_SERVER" - PulseCookie = "PULSE_COOKIE" ) // SdBooted implements https://www.freedesktop.org/software/systemd/man/sd_booted.html @@ -31,40 +22,3 @@ func SdBooted() (bool, error) { return true, nil } - -// DiscoverPulseCookie try various standard methods to discover the current user's PulseAudio authentication cookie -func DiscoverPulseCookie() string { - if p, ok := os.LookupEnv(PulseCookie); ok { - return p - } - - if p, ok := os.LookupEnv(home); ok { - p = path.Join(p, ".pulse-cookie") - if s, err := os.Stat(p); err != nil { - if !errors.Is(err, fs.ErrNotExist) { - final.Fatal("Error accessing PulseAudio cookie:", err) - // unreachable - return p - } - } else if !s.IsDir() { - return p - } - } - - if p, ok := os.LookupEnv(xdgConfigHome); ok { - p = path.Join(p, "pulse", "cookie") - if s, err := os.Stat(p); err != nil { - if !errors.Is(err, fs.ErrNotExist) { - final.Fatal("Error accessing PulseAudio cookie:", err) - // unreachable - return p - } - } else if !s.IsDir() { - return p - } - } - - final.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)", - PulseCookie, xdgConfigHome, home)) - return "" -} diff --git a/main.go b/main.go index f1bf07a..c35e425 100644 --- a/main.go +++ b/main.go @@ -10,10 +10,10 @@ import ( "strconv" "syscall" - "git.ophivana.moe/cat/fortify/internal/final" - "git.ophivana.moe/cat/fortify/dbus" + "git.ophivana.moe/cat/fortify/internal" "git.ophivana.moe/cat/fortify/internal/app" + "git.ophivana.moe/cat/fortify/internal/state" "git.ophivana.moe/cat/fortify/internal/verbose" ) @@ -21,6 +21,7 @@ var ( Version = "impure" a *app.App + s *internal.ExitState dbusSession *dbus.Config dbusSystem *dbus.Config @@ -47,7 +48,12 @@ func main() { tryLicense() a = app.New(userName, flag.Args(), launchOptionText) - final.Prepare(*a.User, a.UID(), a.RunDir()) + s = internal.NewExit(a.User, a.UID(), func() (int, error) { + d, err := state.ReadLaunchers(a.RunDir(), a.Uid, false) + return len(d), err + }) + a.SealExit(s) + internal.SealExit(s) // parse D-Bus config file if applicable if mustDBus { @@ -55,10 +61,10 @@ func main() { dbusSession = dbus.NewConfig(dbusID, true, mpris) } else { if f, err := os.Open(dbusConfigSession); err != nil { - final.Fatal("Error opening D-Bus proxy config file:", err) + internal.Fatal("Error opening D-Bus proxy config file:", err) } else { if err = json.NewDecoder(f).Decode(&dbusSession); err != nil { - final.Fatal("Error parsing D-Bus proxy config file:", err) + internal.Fatal("Error parsing D-Bus proxy config file:", err) } } } @@ -66,10 +72,10 @@ func main() { // system bus proxy is optional if dbusConfigSystem != "nil" { if f, err := os.Open(dbusConfigSystem); err != nil { - final.Fatal("Error opening D-Bus proxy config file:", err) + internal.Fatal("Error opening D-Bus proxy config file:", err) } else { if err = json.NewDecoder(f).Decode(&dbusSystem); err != nil { - final.Fatal("Error parsing D-Bus proxy config file:", err) + internal.Fatal("Error parsing D-Bus proxy config file:", err) } } }