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 <cat@ophivana.moe>
This commit is contained in:
2024-09-17 13:48:42 +09:00
parent 6a6f62efa6
commit 4b7d616862
21 changed files with 346 additions and 287 deletions

View File

@@ -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])

View File

@@ -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")

View File

@@ -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()

View File

@@ -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 ""
}

View File

@@ -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] + "' ")

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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()
}
}
}