aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--flag.go4
-rw-r--r--internal/app/dbus.go23
-rw-r--r--internal/app/ensure.go22
-rw-r--r--internal/app/launch.go4
-rw-r--r--internal/app/pulse.go76
-rw-r--r--internal/app/run.go20
-rw-r--r--internal/app/setup.go21
-rw-r--r--internal/app/wayland.go11
-rw-r--r--internal/app/x.go11
-rw-r--r--internal/early.go16
-rw-r--r--internal/enablement.go (renamed from internal/state/enablement.go)6
-rw-r--r--internal/exit.go176
-rw-r--r--internal/final/exit.go74
-rw-r--r--internal/final/prepare.go20
-rw-r--r--internal/final/register.go48
-rw-r--r--internal/state/data.go14
-rw-r--r--internal/state/print.go5
-rw-r--r--internal/state/track.go4
-rw-r--r--internal/util/early.go12
-rw-r--r--internal/util/std.go46
-rw-r--r--main.go20
21 files changed, 346 insertions, 287 deletions
diff --git a/flag.go b/flag.go
index f69f8f33..04bc329f 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 a327e2cd..fe8039f6 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 ada6349f..ed283a94 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 886e683c..b44c7785 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 7c360252..8b4921ed 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 d4a9e13c..cc01f487 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 8d6eb881..c13468a0 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 3b12e3ec..33ed9914 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 f198fee3..16b3d95c 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 00000000..48dfcd54
--- /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
index 9358f432..a2dd1e6a 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 00000000..35e86629
--- /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 063d8d04..00000000
--- 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 859013cf..00000000
--- 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 16e8d868..00000000
--- 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 65b6395e..45493885 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 83912d32..838cacb0 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 308be28a..8ecad134 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 ba7375c7..00000000
--- 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 9709cc22..b237ad73 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 f1bf07a3..c35e4251 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)
}
}
}