Compare commits
2 Commits
392717c6dc
...
18db464bd5
Author | SHA1 | Date |
---|---|---|
Ophestra Umiker | 18db464bd5 | |
Ophestra Umiker | a3c2916c1a |
|
@ -0,0 +1,9 @@
|
||||||
|
Copyright (C) 2024 Ophestra Umiker
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL OPHESTRA UMIKER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
Except as contained in this notice, the name of Ophestra Umiker shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Ophestra Umiker.
|
48
main.go
48
main.go
|
@ -56,8 +56,12 @@ func main() {
|
||||||
fatal("Env variable", xdgRuntimeDir, "unset")
|
fatal("Env variable", xdgRuntimeDir, "unset")
|
||||||
} else {
|
} else {
|
||||||
runtime = r
|
runtime = r
|
||||||
|
runDir = path.Join(runtime, "ego")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// state query command
|
||||||
|
tryState()
|
||||||
|
|
||||||
// Report warning if user home directory does not exist or has wrong ownership
|
// Report warning if user home directory does not exist or has wrong ownership
|
||||||
if stat, err := os.Stat(ego.HomeDir); err != nil {
|
if stat, err := os.Stat(ego.HomeDir); err != nil {
|
||||||
if verbose {
|
if verbose {
|
||||||
|
@ -87,9 +91,10 @@ func main() {
|
||||||
} else if !s.IsDir() {
|
} else if !s.IsDir() {
|
||||||
fatal(fmt.Sprintf("Path '%s' is not a directory", runtime))
|
fatal(fmt.Sprintf("Path '%s' is not a directory", runtime))
|
||||||
} else {
|
} else {
|
||||||
// Cleanup: need revert
|
|
||||||
if err = aclUpdatePerm(runtime, uid, aclExecute); err != nil {
|
if err = aclUpdatePerm(runtime, uid, aclExecute); err != nil {
|
||||||
fatal("Error preparing runtime dir:", err)
|
fatal("Error preparing runtime dir:", err)
|
||||||
|
} else {
|
||||||
|
registerRevertPath(runtime)
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("Runtime data dir '%s' configured\n", runtime)
|
fmt.Printf("Runtime data dir '%s' configured\n", runtime)
|
||||||
|
@ -97,15 +102,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create runtime dir for Ego itself (e.g. `/run/user/%d/ego`) and make it readable for target
|
// Create runtime dir for Ego itself (e.g. `/run/user/%d/ego`) and make it readable for target
|
||||||
runDir = path.Join(runtime, "ego")
|
|
||||||
if err := os.Mkdir(runDir, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
if err := os.Mkdir(runDir, 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
fatal("Error creating Ego runtime dir:", err)
|
fatal("Error creating Ego runtime dir:", err)
|
||||||
}
|
}
|
||||||
// Cleanup: need revert
|
|
||||||
if err := aclUpdatePerm(runDir, uid, aclExecute); err != nil {
|
if err := aclUpdatePerm(runDir, uid, aclExecute); err != nil {
|
||||||
fatal("Error preparing Ego runtime dir:", err)
|
fatal("Error preparing Ego runtime dir:", err)
|
||||||
|
} else {
|
||||||
|
registerRevertPath(runDir)
|
||||||
}
|
}
|
||||||
// Cleanup: need register control PID
|
|
||||||
|
|
||||||
// Add rwx permissions to Wayland socket (e.g. `/run/user/%d/wayland-0`)
|
// Add rwx permissions to Wayland socket (e.g. `/run/user/%d/wayland-0`)
|
||||||
if w, ok := os.LookupEnv(waylandDisplay); !ok {
|
if w, ok := os.LookupEnv(waylandDisplay); !ok {
|
||||||
|
@ -115,9 +119,11 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
// add environment variable for new process
|
// add environment variable for new process
|
||||||
env = append(env, waylandDisplay+"="+path.Join(runtime, w))
|
env = append(env, waylandDisplay+"="+path.Join(runtime, w))
|
||||||
// Cleanup: need revert
|
wp := path.Join(runtime, w)
|
||||||
if err := aclUpdatePerm(path.Join(runtime, w), uid, aclRead, aclWrite, aclExecute); err != nil {
|
if err := aclUpdatePerm(wp, uid, aclRead, aclWrite, aclExecute); err != nil {
|
||||||
fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
fatal(fmt.Sprintf("Error preparing Wayland '%s':", w), err)
|
||||||
|
} else {
|
||||||
|
registerRevertPath(wp)
|
||||||
}
|
}
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("Wayland socket '%s' configured\n", w)
|
fmt.Printf("Wayland socket '%s' configured\n", w)
|
||||||
|
@ -132,13 +138,15 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
// add environment variable for new process
|
// add environment variable for new process
|
||||||
env = append(env, display+"="+d)
|
env = append(env, display+"="+d)
|
||||||
// Cleanup: need revert
|
|
||||||
if err := changeHosts(xcbHostModeInsert, xcbFamilyServerInterpreted, "localuser\x00"+ego.Username); err != nil {
|
|
||||||
fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
|
||||||
}
|
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", ego.Username, d)
|
fmt.Printf("X11: Adding XHost entry SI:localuser:%s to display '%s'\n", ego.Username, d)
|
||||||
}
|
}
|
||||||
|
if err := changeHosts(xcbHostModeInsert, xcbFamilyServerInterpreted, "localuser\x00"+ego.Username); err != nil {
|
||||||
|
fatal(fmt.Sprintf("Error adding XHost entry to '%s':", d), err)
|
||||||
|
} else {
|
||||||
|
xcbActionComplete = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add execute permissions to PulseAudio directory (e.g. `/run/user/%d/pulse`)
|
// Add execute permissions to PulseAudio directory (e.g. `/run/user/%d/pulse`)
|
||||||
|
@ -157,9 +165,10 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
// add environment variable for new process
|
// add environment variable for new process
|
||||||
env = append(env, pulseServer+"=unix:"+pulseS)
|
env = append(env, pulseServer+"=unix:"+pulseS)
|
||||||
// Cleanup: need revert
|
|
||||||
if err = aclUpdatePerm(pulse, uid, aclExecute); err != nil {
|
if err = aclUpdatePerm(pulse, uid, aclExecute); err != nil {
|
||||||
fatal("Error preparing PulseAudio:", err)
|
fatal("Error preparing PulseAudio:", err)
|
||||||
|
} else {
|
||||||
|
registerRevertPath(pulse)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure permissions of PulseAudio socket `/run/user/%d/pulse/native`
|
// Ensure permissions of PulseAudio socket `/run/user/%d/pulse/native`
|
||||||
|
@ -184,9 +193,10 @@ func main() {
|
||||||
if err = copyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
if err = copyFile(pulseCookieFinal, pulseCookieSource); err != nil {
|
||||||
fatal("Error copying PulseAudio cookie:", err)
|
fatal("Error copying PulseAudio cookie:", err)
|
||||||
}
|
}
|
||||||
// Cleanup: need revert
|
|
||||||
if err = aclUpdatePerm(pulseCookieFinal, uid, aclRead); err != nil {
|
if err = aclUpdatePerm(pulseCookieFinal, uid, aclRead); err != nil {
|
||||||
fatal("Error publishing PulseAudio cookie:", err)
|
fatal("Error publishing PulseAudio cookie:", err)
|
||||||
|
} else {
|
||||||
|
registerRevertPath(pulseCookieFinal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
|
@ -249,7 +259,10 @@ func main() {
|
||||||
fatal("Error starting process:", err)
|
fatal("Error starting process:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup: need register
|
if err := registerProcess(ego.Uid, cmd); err != nil {
|
||||||
|
// process already started, shouldn't be fatal
|
||||||
|
fmt.Println("Error registering process:", err)
|
||||||
|
}
|
||||||
|
|
||||||
var r int
|
var r int
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
|
@ -259,11 +272,10 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup: deregister, call revert
|
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Println("Process exited with exit code", r)
|
fmt.Println("Process exited with exit code", r)
|
||||||
}
|
}
|
||||||
|
beforeExit()
|
||||||
os.Exit(r)
|
os.Exit(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,9 +355,3 @@ func launchByMachineCtl(bare bool) (args []string) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(msg ...any) {
|
|
||||||
// Cleanup: call revert
|
|
||||||
fmt.Println(msg...)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// we unfortunately have to assume there are never races between processes
|
||||||
|
// this and launcher should eventually be replaced by a server process
|
||||||
|
|
||||||
|
var (
|
||||||
|
stateActionEarly bool
|
||||||
|
statePath string
|
||||||
|
cleanupCandidate []string
|
||||||
|
xcbActionComplete bool
|
||||||
|
)
|
||||||
|
|
||||||
|
type launcherState struct {
|
||||||
|
PID int
|
||||||
|
Launcher string
|
||||||
|
Argv []string
|
||||||
|
Command []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&stateActionEarly, "state", false, "query state value of current active launchers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryState() {
|
||||||
|
if !stateActionEarly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
launchers, err := readLaunchers()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading launchers:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\tPID\tLauncher")
|
||||||
|
for _, state := range launchers {
|
||||||
|
fmt.Printf("\t%d\t%s\nCommand: %s\nArgv: %s\n", state.PID, state.Launcher, state.Command, state.Argv)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerRevertPath(p string) {
|
||||||
|
cleanupCandidate = append(cleanupCandidate, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// called after process start, before wait
|
||||||
|
func registerProcess(uid string, cmd *exec.Cmd) error {
|
||||||
|
statePath = path.Join(runDir, uid, strconv.Itoa(cmd.Process.Pid))
|
||||||
|
state := launcherState{
|
||||||
|
PID: cmd.Process.Pid,
|
||||||
|
Launcher: cmd.Path,
|
||||||
|
Argv: cmd.Args,
|
||||||
|
Command: command,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(path.Join(runDir, uid), 0700); err != nil && !errors.Is(err, fs.ErrExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, err := os.OpenFile(statePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
if f.Close() != nil {
|
||||||
|
// unreachable
|
||||||
|
panic("state file closed prematurely")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return gob.NewEncoder(f).Encode(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLaunchers() ([]*launcherState, error) {
|
||||||
|
var f *os.File
|
||||||
|
var r []*launcherState
|
||||||
|
launcherPrefix := path.Join(runDir, ego.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func beforeExit() {
|
||||||
|
if err := os.Remove(statePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
fmt.Println("Error removing state file:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a, err := readLaunchers(); err != nil {
|
||||||
|
fmt.Println("Error reading active launchers:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
} else if len(a) > 0 {
|
||||||
|
// other launchers are still active
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Found %d active launchers, exiting without cleaning up\n", len(a))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
fmt.Println("No other launchers active, will clean up")
|
||||||
|
}
|
||||||
|
|
||||||
|
if xcbActionComplete {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("X11: Removing XHost entry SI:localuser:%s\n", ego.Username)
|
||||||
|
}
|
||||||
|
if err := changeHosts(xcbHostModeDelete, xcbFamilyServerInterpreted, "localuser\x00"+ego.Username); err != nil {
|
||||||
|
fmt.Println("Error removing XHost entry:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, candidate := range cleanupCandidate {
|
||||||
|
if err := aclUpdatePerm(candidate, uid); err != nil {
|
||||||
|
fmt.Printf("Error stripping ACL entry from '%s': %s\n", candidate, err)
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Stripped ACL entry for user '%s' from '%s'\n", ego.Username, candidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(msg ...any) {
|
||||||
|
fmt.Println(msg...)
|
||||||
|
beforeExit()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
Loading…
Reference in New Issue