aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--internal/app/dbus.go22
-rw-r--r--internal/app/ensure.go62
-rw-r--r--internal/app/launch.go4
-rw-r--r--internal/app/pulse.go26
-rw-r--r--internal/app/run.go22
-rw-r--r--internal/app/setup.go49
-rw-r--r--internal/app/wayland.go10
-rw-r--r--internal/app/x.go7
-rw-r--r--internal/final/exit.go (renamed from internal/state/exit.go)10
-rw-r--r--internal/final/prepare.go20
-rw-r--r--internal/final/register.go (renamed from internal/state/register.go)21
-rw-r--r--internal/state/data.go51
-rw-r--r--internal/state/print.go61
-rw-r--r--internal/state/track.go64
-rw-r--r--internal/state/value.go21
-rw-r--r--internal/system/retrieve.go28
-rw-r--r--internal/system/value.go13
-rw-r--r--internal/util/std.go9
-rw-r--r--main.go62
-rw-r--r--state.go42
20 files changed, 321 insertions, 283 deletions
diff --git a/internal/app/dbus.go b/internal/app/dbus.go
index 60cf3f51..06523e88 100644
--- a/internal/app/dbus.go
+++ b/internal/app/dbus.go
@@ -3,6 +3,7 @@ package app
import (
"errors"
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"os"
"path"
"strconv"
@@ -10,7 +11,6 @@ import (
"git.ophivana.moe/cat/fortify/dbus"
"git.ophivana.moe/cat/fortify/internal/acl"
"git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/util"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -32,7 +32,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
var binPath string
var sessionBus, systemBus [2]string
- target := path.Join(system.V.Share, strconv.Itoa(os.Getpid()))
+ target := path.Join(a.sharePath, strconv.Itoa(os.Getpid()))
sessionBus[1] = target + ".bus"
systemBus[1] = target + ".system-bus"
dbusAddress = [2]string{
@@ -41,7 +41,7 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
}
if b, ok := util.Which("xdg-dbus-proxy"); !ok {
- state.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH")
+ final.Fatal("D-Bus: Did not find 'xdg-dbus-proxy' in PATH")
} else {
binPath = b
}
@@ -69,7 +69,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 {
- state.Fatal("D-Bus: invalid config when sealing proxy,", err)
+ final.Fatal("D-Bus: invalid config when sealing proxy,", err)
}
ready := make(chan bool, 1)
@@ -80,7 +80,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 {
- state.Fatal("D-Bus: error starting proxy,", err)
+ final.Fatal("D-Bus: error starting proxy,", err)
}
verbose.Println("D-Bus proxy launch:", p)
@@ -97,24 +97,24 @@ func (a *App) ShareDBus(dse, dsg *dbus.Config, log bool) {
}()
// register early to enable Fatal cleanup
- state.RegisterDBus(p, &done)
+ final.RegisterDBus(p, &done)
if !<-ready {
- state.Fatal("D-Bus: proxy did not start correctly")
+ final.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 {
- state.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err)
+ final.Fatal(fmt.Sprintf("Error preparing D-Bus session proxy '%s':", dbusAddress[0]), err)
} else {
- state.RegisterRevertPath(sessionBus[1])
+ final.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 {
- state.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err)
+ final.Fatal(fmt.Sprintf("Error preparing D-Bus system proxy '%s':", dbusAddress[1]), err)
} else {
- state.RegisterRevertPath(systemBus[1])
+ final.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
new file mode 100644
index 00000000..9ab63ff2
--- /dev/null
+++ b/internal/app/ensure.go
@@ -0,0 +1,62 @@
+package app
+
+import (
+ "errors"
+ "fmt"
+ "git.ophivana.moe/cat/fortify/internal/acl"
+ "git.ophivana.moe/cat/fortify/internal/final"
+ "git.ophivana.moe/cat/fortify/internal/verbose"
+ "io/fs"
+ "os"
+ "path"
+)
+
+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)
+ }
+}
+
+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")
+ }
+ final.Fatal("Error accessing runtime directory:", err)
+ } else if !s.IsDir() {
+ final.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)
+ } else {
+ final.RegisterRevertPath(a.runtimePath)
+ }
+ verbose.Printf("Runtime data dir '%s' configured\n", a.runtimePath)
+ }
+}
+
+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)
+ }
+
+ // workaround for launch method sudo
+ if a.LaunchOption() == LaunchMethodSudo {
+ // 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)
+ } else {
+ if err = acl.UpdatePerm(cr, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
+ final.Fatal("Error preparing child runtime directory:", err)
+ } else {
+ final.RegisterRevertPath(cr)
+ }
+ a.AppendEnv("XDG_RUNTIME_DIR", cr)
+ a.AppendEnv("XDG_SESSION_CLASS", "user")
+ a.AppendEnv("XDG_SESSION_TYPE", "tty")
+ verbose.Printf("Child runtime data dir '%s' configured\n", cr)
+ }
+ }
+}
diff --git a/internal/app/launch.go b/internal/app/launch.go
index 02204aae..886e683c 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/state"
"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 {
- state.Fatal("Error encoding launcher payload:", err)
+ final.Fatal("Error encoding launcher payload:", err)
}
_ = enc.Close()
diff --git a/internal/app/pulse.go b/internal/app/pulse.go
index d7e08ba9..4b67ff2e 100644
--- a/internal/app/pulse.go
+++ b/internal/app/pulse.go
@@ -3,13 +3,13 @@ package app
import (
"errors"
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"io/fs"
"os"
"path"
"git.ophivana.moe/cat/fortify/internal/acl"
"git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/util"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -18,46 +18,46 @@ func (a *App) SharePulse() {
a.setEnablement(state.EnablePulse)
// ensure PulseAudio directory ACL (e.g. `/run/user/%d/pulse`)
- pulse := path.Join(system.V.Runtime, "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) {
- state.Fatal("Error accessing PulseAudio directory:", err)
+ final.Fatal("Error accessing PulseAudio directory:", err)
}
- state.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
+ final.Fatal(fmt.Sprintf("PulseAudio dir '%s' not found", pulse))
} else {
// add environment variable for new process
a.AppendEnv(util.PulseServer, "unix:"+pulseS)
if err = acl.UpdatePerm(pulse, a.UID(), acl.Execute); err != nil {
- state.Fatal("Error preparing PulseAudio:", err)
+ final.Fatal("Error preparing PulseAudio:", err)
} else {
- state.RegisterRevertPath(pulse)
+ final.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) {
- state.Fatal("PulseAudio directory found but socket does not exist")
+ final.Fatal("PulseAudio directory found but socket does not exist")
}
- state.Fatal("Error accessing PulseAudio socket:", err)
+ final.Fatal("Error accessing PulseAudio socket:", err)
} else {
if m := s.Mode(); m&0o006 != 0o006 {
- state.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
+ final.Fatal(fmt.Sprintf("Unexpected permissions on '%s':", pulseS), m)
}
}
// Publish current user's pulse-cookie for target user
pulseCookieSource := util.DiscoverPulseCookie()
- pulseCookieFinal := path.Join(system.V.Share, "pulse-cookie")
+ pulseCookieFinal := path.Join(a.sharePath, "pulse-cookie")
a.AppendEnv(util.PulseCookie, pulseCookieFinal)
verbose.Printf("Publishing PulseAudio cookie '%s' to '%s'\n", pulseCookieSource, pulseCookieFinal)
if err = util.CopyFile(pulseCookieFinal, pulseCookieSource); err != nil {
- state.Fatal("Error copying PulseAudio cookie:", err)
+ final.Fatal("Error copying PulseAudio cookie:", err)
}
if err = acl.UpdatePerm(pulseCookieFinal, a.UID(), acl.Read); err != nil {
- state.Fatal("Error publishing PulseAudio cookie:", err)
+ final.Fatal("Error publishing PulseAudio cookie:", err)
} else {
- state.RegisterRevertPath(pulseCookieFinal)
+ final.RegisterRevertPath(pulseCookieFinal)
}
verbose.Printf("PulseAudio dir '%s' configured\n", pulse)
diff --git a/internal/app/run.go b/internal/app/run.go
index 53548d74..d4a9e13c 100644
--- a/internal/app/run.go
+++ b/internal/app/run.go
@@ -3,12 +3,12 @@ package app
import (
"errors"
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"os"
"os/exec"
"strings"
"git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/util"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -47,31 +47,33 @@ func (a *App) Run() {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
- cmd.Dir = system.V.RunDir
+ cmd.Dir = a.runDirPath
verbose.Println("Executing:", cmd)
if err := cmd.Start(); err != nil {
- state.Fatal("Error starting process:", err)
+ final.Fatal("Error starting process:", err)
}
- state.RegisterEnablement(a.enablements)
+ final.RegisterEnablement(a.enablements)
- if err := state.SaveProcess(a.Uid, cmd); err != nil {
+ 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)
}
var r int
if err := cmd.Wait(); err != nil {
var exitError *exec.ExitError
if !errors.As(err, &exitError) {
- state.Fatal("Error running process:", err)
+ final.Fatal("Error running process:", err)
}
}
verbose.Println("Process exited with exit code", r)
- state.BeforeExit()
+ final.BeforeExit()
os.Exit(r)
}
@@ -99,7 +101,7 @@ func (a *App) commandBuilderSudo() (args []string) {
func (a *App) commandBuilderBwrap() (args []string) {
// TODO: build bwrap command
- state.Fatal("bwrap")
+ final.Fatal("bwrap")
panic("unreachable")
}
@@ -127,7 +129,7 @@ func (a *App) commandBuilderMachineCtl() (args []string) {
// /bin/sh -c
if sh, ok := util.Which("sh"); !ok {
- state.Fatal("Did not find 'sh' in PATH")
+ final.Fatal("Did not find 'sh' in PATH")
} else {
args = append(args, sh, "-c")
}
@@ -145,7 +147,7 @@ func (a *App) commandBuilderMachineCtl() (args []string) {
innerCommand.WriteString("; ")
if executable, err := os.Executable(); err != nil {
- state.Fatal("Error reading executable path:", err)
+ final.Fatal("Error reading executable path:", err)
} else {
if a.enablements.Has(state.EnableDBus) {
innerCommand.WriteString(dbusSessionBusAddress + "=" + "'" + dbusAddress[0] + "' ")
diff --git a/internal/app/setup.go b/internal/app/setup.go
index c6b44611..8d6eb881 100644
--- a/internal/app/setup.go
+++ b/internal/app/setup.go
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"os/user"
+ "path"
"strconv"
"git.ophivana.moe/cat/fortify/internal/state"
@@ -12,18 +13,25 @@ import (
"git.ophivana.moe/cat/fortify/internal/verbose"
)
+const (
+ xdgRuntimeDir = "XDG_RUNTIME_DIR"
+)
+
type App struct {
- launchOptionText string
+ uid int // assigned
+ env []string // modified via AppendEnv
+ command []string // set on initialisation
- uid int
- env []string
- command []string
+ launchOptionText string // set on initialisation
+ launchOption uint8 // assigned
- launchOption uint8
- toolPath string
+ sharePath string // set on initialisation
+ runtimePath string // assigned
+ runDirPath string // assigned
+ toolPath string // assigned
- enablements state.Enablements
- *user.User
+ enablements state.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
@@ -33,6 +41,10 @@ func (a *App) LaunchOption() uint8 {
return a.launchOption
}
+func (a *App) RunDir() string {
+ return a.runDirPath
+}
+
func (a *App) setEnablement(e state.Enablement) {
if a.enablements.Has(e) {
panic("enablement " + e.String() + " set twice")
@@ -42,8 +54,25 @@ func (a *App) setEnablement(e state.Enablement) {
}
func New(userName string, args []string, launchOptionText string) *App {
- a := &App{command: args, launchOptionText: launchOptionText}
+ a := &App{
+ command: args,
+ launchOptionText: launchOptionText,
+ sharePath: path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid())),
+ }
+
+ // runtimePath, runDirPath
+ if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
+ fmt.Println("Env variable", xdgRuntimeDir, "unset")
+
+ // too early for fatal
+ os.Exit(1)
+ } else {
+ a.runtimePath = r
+ a.runDirPath = path.Join(a.runtimePath, "fortify")
+ verbose.Println("Runtime directory at", a.runDirPath)
+ }
+ // *user.User
if u, err := user.Lookup(userName); err != nil {
if errors.As(err, new(user.UnknownUserError)) {
fmt.Println("unknown user", userName)
@@ -58,6 +87,7 @@ func New(userName string, args []string, launchOptionText string) *App {
a.User = u
}
+ // uid
if u, err := strconv.Atoi(a.Uid); err != nil {
// usually unreachable
panic("uid parse")
@@ -70,6 +100,7 @@ func New(userName string, args []string, launchOptionText string) *App {
verbose.Println("System booted with systemd as init system (PID 1).")
}
+ // launchOption, toolPath
switch a.launchOptionText {
case "sudo":
a.launchOption = LaunchMethodSudo
diff --git a/internal/app/wayland.go b/internal/app/wayland.go
index 12568712..9834fea9 100644
--- a/internal/app/wayland.go
+++ b/internal/app/wayland.go
@@ -2,12 +2,12 @@ package app
import (
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"os"
"path"
"git.ophivana.moe/cat/fortify/internal/acl"
"git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -21,15 +21,15 @@ func (a *App) ShareWayland() {
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
if w, ok := os.LookupEnv(waylandDisplay); !ok {
- state.Fatal("Wayland: WAYLAND_DISPLAY not set")
+ final.Fatal("Wayland: WAYLAND_DISPLAY not set")
} else {
// add environment variable for new process
- wp := path.Join(system.V.Runtime, w)
+ 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 {
- state.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
+ final.Fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
} else {
- state.RegisterRevertPath(wp)
+ final.RegisterRevertPath(wp)
}
verbose.Printf("Wayland socket '%s' configured\n", w)
}
diff --git a/internal/app/x.go b/internal/app/x.go
index 20c0de1c..07034026 100644
--- a/internal/app/x.go
+++ b/internal/app/x.go
@@ -2,6 +2,7 @@ package app
import (
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"os"
"git.ophivana.moe/cat/fortify/internal/state"
@@ -16,16 +17,16 @@ func (a *App) ShareX() {
// discovery X11 and grant user permission via the `ChangeHosts` command
if d, ok := os.LookupEnv(display); !ok {
- state.Fatal("X11: DISPLAY not set")
+ final.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 {
- state.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
+ final.Fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
} else {
- state.XcbActionComplete()
+ final.XcbActionComplete()
}
}
}
diff --git a/internal/state/exit.go b/internal/final/exit.go
index 0eb80a83..86cc2e71 100644
--- a/internal/state/exit.go
+++ b/internal/final/exit.go
@@ -1,14 +1,14 @@
-package state
+package final
import (
"errors"
"fmt"
- "io/fs"
- "os"
-
"git.ophivana.moe/cat/fortify/internal/acl"
+ "git.ophivana.moe/cat/fortify/internal/state"
"git.ophivana.moe/cat/fortify/internal/verbose"
"git.ophivana.moe/cat/fortify/internal/xcb"
+ "io/fs"
+ "os"
)
func Fatal(msg ...any) {
@@ -31,7 +31,7 @@ func BeforeExit() {
}
}
- if d, err := readLaunchers(u.Uid); err != nil {
+ 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 {
diff --git a/internal/final/prepare.go b/internal/final/prepare.go
new file mode 100644
index 00000000..859013cf
--- /dev/null
+++ b/internal/final/prepare.go
@@ -0,0 +1,20 @@
+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/state/register.go b/internal/final/register.go
index be9ce540..16e8d868 100644
--- a/internal/state/register.go
+++ b/internal/final/register.go
@@ -1,21 +1,26 @@
-package state
+package final
-import "git.ophivana.moe/cat/fortify/dbus"
+import (
+ "git.ophivana.moe/cat/fortify/dbus"
+ "git.ophivana.moe/cat/fortify/internal/state"
+)
var (
cleanupCandidate []string
- enablements *Enablements
+ enablements *state.Enablements
xcbActionComplete bool
dbusProxy *dbus.Proxy
dbusDone *chan struct{}
+
+ statePath string
)
func RegisterRevertPath(p string) {
cleanupCandidate = append(cleanupCandidate, p)
}
-func RegisterEnablement(e Enablements) {
+func RegisterEnablement(e state.Enablements) {
if enablements != nil {
panic("enablement state set twice")
}
@@ -33,3 +38,11 @@ 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
new file mode 100644
index 00000000..65b6395e
--- /dev/null
+++ b/internal/state/data.go
@@ -0,0 +1,51 @@
+package state
+
+import (
+ "encoding/gob"
+ "os"
+ "path"
+)
+
+// we unfortunately have to assume there are never races between processes
+// this and launcher should eventually be replaced by a server process
+
+type launcherState struct {
+ PID int
+ Launcher string
+ Argv []string
+ Command []string
+ Capability Enablements
+}
+
+func ReadLaunchers(runDirPath, uid string) ([]*launcherState, error) {
+ var f *os.File
+ var r []*launcherState
+ launcherPrefix := path.Join(runDirPath, uid)
+
+ if pl, err := os.ReadDir(launcherPrefix); err != nil {
+ return nil, err
+ } else {
+ for _, e := range pl {
+ if err = func() error {
+ if f, err = os.Open(path.Join(launcherPrefix, e.Name())); err != nil {
+ return err
+ } else {
+ defer func() {
+ if f.Close() != nil {
+ // unreachable
+ panic("foreign state file closed prematurely")
+ }
+ }()
+
+ var s launcherState
+ r = append(r, &s)
+ return gob.NewDecoder(f).Decode(&s)
+ }
+ }(); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return r, nil
+}
diff --git a/internal/state/print.go b/internal/state/print.go
index 0881f199..83912d32 100644
--- a/internal/state/print.go
+++ b/internal/state/print.go
@@ -1,68 +1,37 @@
package state
import (
- "flag"
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
-var (
- stateActionEarly bool
- stateActionEarlyC bool
-)
-
-func init() {
- flag.BoolVar(&stateActionEarly, "state", false, "print state information of active launchers")
- flag.BoolVar(&stateActionEarlyC, "state-current", false, "print state information of active launchers for the specified user")
-}
-
-func Early() {
- var w *tabwriter.Writer
-
- switch {
- case stateActionEarly:
- if runDir, err := os.ReadDir(system.V.RunDir); err != nil {
- fmt.Println("Error reading runtime directory:", err)
- } else {
- for _, e := range runDir {
- if !e.IsDir() {
- verbose.Println("Skipped non-directory entry", e.Name())
- continue
- }
-
- if _, err = strconv.Atoi(e.Name()); err != nil {
- verbose.Println("Skipped non-uid entry", e.Name())
- continue
- }
+func MustPrintLauncherStateGlobal(w **tabwriter.Writer, runDirPath string) {
+ if dirs, err := os.ReadDir(runDirPath); err != nil {
+ fmt.Println("Error reading runtime directory:", err)
+ } else {
+ for _, e := range dirs {
+ if !e.IsDir() {
+ verbose.Println("Skipped non-directory entry", e.Name())
+ continue
+ }
- printLauncherState(e.Name(), &w)
+ if _, err = strconv.Atoi(e.Name()); err != nil {
+ verbose.Println("Skipped non-uid entry", e.Name())
+ continue
}
- }
- case stateActionEarlyC:
- printLauncherState(u.Uid, &w)
- default:
- return
- }
- if w != nil {
- if err := w.Flush(); err != nil {
- fmt.Println("warn: error formatting output:", err)
+ MustPrintLauncherState(w, runDirPath, e.Name())
}
- } else {
- fmt.Println("No information available.")
}
-
- os.Exit(0)
}
-func printLauncherState(uid string, w **tabwriter.Writer) {
- launchers, err := readLaunchers(uid)
+func MustPrintLauncherState(w **tabwriter.Writer, runDirPath, uid string) {
+ launchers, err := ReadLaunchers(runDirPath, uid)
if err != nil {
fmt.Println("Error reading launchers:", err)
os.Exit(1)
diff --git a/internal/state/track.go b/internal/state/track.go
index 198abebf..308be28a 100644
--- a/internal/state/track.go
+++ b/internal/state/track.go
@@ -8,42 +8,25 @@ import (
"os/exec"
"path"
"strconv"
-
- "git.ophivana.moe/cat/fortify/internal/system"
-)
-
-// we unfortunately have to assume there are never races between processes
-// this and launcher should eventually be replaced by a server process
-
-var (
- statePath string
)
-type launcherState struct {
- PID int
- Launcher string
- Argv []string
- Command []string
- Capability Enablements
-}
-
// SaveProcess called after process start, before wait
-func SaveProcess(uid string, cmd *exec.Cmd) error {
- statePath = path.Join(system.V.RunDir, uid, strconv.Itoa(cmd.Process.Pid))
+func SaveProcess(uid string, cmd *exec.Cmd, runDirPath string, command []string, enablements Enablements) (string, error) {
+ statePath := path.Join(runDirPath, uid, strconv.Itoa(cmd.Process.Pid))
state := launcherState{
PID: cmd.Process.Pid,
Launcher: cmd.Path,
Argv: cmd.Args,
Command: command,
- Capability: *enablements,
+ Capability: enablements,
}
- if err := os.Mkdir(path.Join(system.V.RunDir, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
- return err
+ if err := os.Mkdir(path.Join(runDirPath, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
+ return statePath, err
}
if f, err := os.OpenFile(statePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600); err != nil {
- return err
+ return statePath, err
} else {
defer func() {
if f.Close() != nil {
@@ -51,39 +34,6 @@ func SaveProcess(uid string, cmd *exec.Cmd) error {
panic("state file closed prematurely")
}
}()
- return gob.NewEncoder(f).Encode(state)
- }
-}
-
-func readLaunchers(uid string) ([]*launcherState, error) {
- var f *os.File
- var r []*launcherState
- launcherPrefix := path.Join(system.V.RunDir, uid)
-
- if pl, err := os.ReadDir(launcherPrefix); err != nil {
- return nil, err
- } else {
- for _, e := range pl {
- if err = func() error {
- if f, err = os.Open(path.Join(launcherPrefix, e.Name())); err != nil {
- return err
- } else {
- defer func() {
- if f.Close() != nil {
- // unreachable
- panic("foreign state file closed prematurely")
- }
- }()
-
- var s launcherState
- r = append(r, &s)
- return gob.NewDecoder(f).Decode(&s)
- }
- }(); err != nil {
- return nil, err
- }
- }
+ return statePath, gob.NewEncoder(f).Encode(state)
}
-
- return r, nil
}
diff --git a/internal/state/value.go b/internal/state/value.go
deleted file mode 100644
index cb20818c..00000000
--- a/internal/state/value.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package state
-
-import (
- "os/user"
-)
-
-var (
- u *user.User
- uid int
- command []string
-)
-
-func Set(val user.User, c []string, d int) {
- if u != nil {
- panic("state set twice")
- }
-
- u = &val
- command = c
- uid = d
-}
diff --git a/internal/system/retrieve.go b/internal/system/retrieve.go
deleted file mode 100644
index 06798423..00000000
--- a/internal/system/retrieve.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package system
-
-import (
- "fmt"
- "os"
- "path"
- "strconv"
-)
-
-func Retrieve() {
- if V != nil {
- panic("system info retrieved twice")
- }
-
- v := &Values{Share: path.Join(os.TempDir(), "fortify."+strconv.Itoa(os.Geteuid()))}
-
- if r, ok := os.LookupEnv(xdgRuntimeDir); !ok {
- fmt.Println("Env variable", xdgRuntimeDir, "unset")
-
- // too early for fatal
- os.Exit(1)
- } else {
- v.Runtime = r
- v.RunDir = path.Join(v.Runtime, "fortify")
- }
-
- V = v
-}
diff --git a/internal/system/value.go b/internal/system/value.go
deleted file mode 100644
index c34e7fa8..00000000
--- a/internal/system/value.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package system
-
-const (
- xdgRuntimeDir = "XDG_RUNTIME_DIR"
-)
-
-type Values struct {
- Share string
- Runtime string
- RunDir string
-}
-
-var V *Values
diff --git a/internal/util/std.go b/internal/util/std.go
index 4f304fb6..9709cc22 100644
--- a/internal/util/std.go
+++ b/internal/util/std.go
@@ -3,11 +3,10 @@ package util
import (
"errors"
"fmt"
+ "git.ophivana.moe/cat/fortify/internal/final"
"io/fs"
"os"
"path"
-
- "git.ophivana.moe/cat/fortify/internal/state"
)
const (
@@ -43,7 +42,7 @@ func DiscoverPulseCookie() string {
p = path.Join(p, ".pulse-cookie")
if s, err := os.Stat(p); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
- state.Fatal("Error accessing PulseAudio cookie:", err)
+ final.Fatal("Error accessing PulseAudio cookie:", err)
// unreachable
return p
}
@@ -56,7 +55,7 @@ func DiscoverPulseCookie() string {
p = path.Join(p, "pulse", "cookie")
if s, err := os.Stat(p); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
- state.Fatal("Error accessing PulseAudio cookie:", err)
+ final.Fatal("Error accessing PulseAudio cookie:", err)
// unreachable
return p
}
@@ -65,7 +64,7 @@ func DiscoverPulseCookie() string {
}
}
- state.Fatal(fmt.Sprintf("Cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
+ 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 ebd5970b..f1bf07a3 100644
--- a/main.go
+++ b/main.go
@@ -7,15 +7,13 @@ import (
"fmt"
"io/fs"
"os"
- "path"
"strconv"
"syscall"
+ "git.ophivana.moe/cat/fortify/internal/final"
+
"git.ophivana.moe/cat/fortify/dbus"
- "git.ophivana.moe/cat/fortify/internal/acl"
"git.ophivana.moe/cat/fortify/internal/app"
- "git.ophivana.moe/cat/fortify/internal/state"
- "git.ophivana.moe/cat/fortify/internal/system"
"git.ophivana.moe/cat/fortify/internal/verbose"
)
@@ -48,9 +46,8 @@ func main() {
tryVersion()
tryLicense()
- system.Retrieve()
a = app.New(userName, flag.Args(), launchOptionText)
- state.Set(*a.User, a.Command(), a.UID())
+ final.Prepare(*a.User, a.UID(), a.RunDir())
// parse D-Bus config file if applicable
if mustDBus {
@@ -58,10 +55,10 @@ func main() {
dbusSession = dbus.NewConfig(dbusID, true, mpris)
} else {
if f, err := os.Open(dbusConfigSession); err != nil {
- state.Fatal("Error opening D-Bus proxy config file:", err)
+ final.Fatal("Error opening D-Bus proxy config file:", err)
} else {
if err = json.NewDecoder(f).Decode(&dbusSession); err != nil {
- state.Fatal("Error parsing D-Bus proxy config file:", err)
+ final.Fatal("Error parsing D-Bus proxy config file:", err)
}
}
}
@@ -69,46 +66,23 @@ func main() {
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if f, err := os.Open(dbusConfigSystem); err != nil {
- state.Fatal("Error opening D-Bus proxy config file:", err)
+ final.Fatal("Error opening D-Bus proxy config file:", err)
} else {
if err = json.NewDecoder(f).Decode(&dbusSystem); err != nil {
- state.Fatal("Error parsing D-Bus proxy config file:", err)
+ final.Fatal("Error parsing D-Bus proxy config file:", err)
}
}
}
}
// ensure RunDir (e.g. `/run/user/%d/fortify`)
- if err := os.Mkdir(system.V.RunDir, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
- state.Fatal("Error creating runtime directory:", err)
- }
+ a.EnsureRunDir()
// state query command early exit
- state.Early()
+ tryState()
// ensure Share (e.g. `/tmp/fortify.%d`)
- // acl is unnecessary as this directory is world executable
- if err := os.Mkdir(system.V.Share, 0701); err != nil && !errors.Is(err, fs.ErrExist) {
- state.Fatal("Error creating shared directory:", err)
- }
-
- if a.LaunchOption() == app.LaunchMethodSudo {
- // ensure child runtime directory (e.g. `/tmp/fortify.%d/%d.share`)
- cr := path.Join(system.V.Share, a.Uid+".share")
- if err := os.Mkdir(cr, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
- state.Fatal("Error creating child runtime directory:", err)
- } else {
- if err = acl.UpdatePerm(cr, a.UID(), acl.Read, acl.Write, acl.Execute); err != nil {
- state.Fatal("Error preparing child runtime directory:", err)
- } else {
- state.RegisterRevertPath(cr)
- }
- a.AppendEnv("XDG_RUNTIME_DIR", cr)
- a.AppendEnv("XDG_SESSION_CLASS", "user")
- a.AppendEnv("XDG_SESSION_TYPE", "tty")
- verbose.Printf("Child runtime data dir '%s' configured\n", cr)
- }
- }
+ a.EnsureShare()
// warn about target user home directory ownership
if stat, err := os.Stat(a.HomeDir); err != nil {
@@ -131,21 +105,7 @@ func main() {
}
// ensure runtime directory ACL (e.g. `/run/user/%d`)
- if s, err := os.Stat(system.V.Runtime); err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- state.Fatal("Runtime directory does not exist")
- }
- state.Fatal("Error accessing runtime directory:", err)
- } else if !s.IsDir() {
- state.Fatal(fmt.Sprintf("Path '%s' is not a directory", system.V.Runtime))
- } else {
- if err = acl.UpdatePerm(system.V.Runtime, a.UID(), acl.Execute); err != nil {
- state.Fatal("Error preparing runtime directory:", err)
- } else {
- state.RegisterRevertPath(system.V.Runtime)
- }
- verbose.Printf("Runtime data dir '%s' configured\n", system.V.Runtime)
- }
+ a.EnsureRuntime()
if mustWayland {
a.ShareWayland()
diff --git a/state.go b/state.go
new file mode 100644
index 00000000..2e68ddcc
--- /dev/null
+++ b/state.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "git.ophivana.moe/cat/fortify/internal/state"
+ "os"
+ "text/tabwriter"
+)
+
+var (
+ stateActionEarly [2]bool
+)
+
+func init() {
+ flag.BoolVar(&stateActionEarly[0], "state", false, "print state information of active launchers")
+ flag.BoolVar(&stateActionEarly[1], "state-current", false, "print state information of active launchers for the specified user")
+}
+
+// tryState is called after app initialisation
+func tryState() {
+ var w *tabwriter.Writer
+
+ switch {
+ case stateActionEarly[0]:
+ state.MustPrintLauncherStateGlobal(&w, a.RunDir())
+ case stateActionEarly[1]:
+ state.MustPrintLauncherState(&w, a.RunDir(), a.Uid)
+ default:
+ return
+ }
+
+ if w != nil {
+ if err := w.Flush(); err != nil {
+ fmt.Println("warn: error formatting output:", err)
+ }
+ } else {
+ fmt.Println("No information available")
+ }
+
+ os.Exit(0)
+}