Files
latchd/legacy/go/session/x11.go

101 lines
2.4 KiB
Go

package session
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"time"
"latchd/config"
)
func setupX(uid, gid uint32, groups []uint32, cfg *config.Config) (*exec.Cmd, error) {
display := os.Getenv("DISPLAY")
vtnr := os.Getenv("XDG_VTNR")
home := os.Getenv("HOME")
if display == "" || vtnr == "" || home == "" {
return nil, fmt.Errorf("missing DISPLAY/XDG_VTNR/HOME env vars")
}
xauthPath := filepath.Join(home, ".Xauthority")
os.Remove(xauthPath)
cookie, err := mCookie()
if err != nil {
return nil, err
}
xauthCmd := exec.Command(cfg.X11.XauthPath, "add", display, ".", cookie)
xauthCmd.SysProcAttr = &syscall.SysProcAttr{
Credential: &syscall.Credential{Uid: uid, Gid: gid, Groups: groups},
}
if out, err := xauthCmd.CombinedOutput(); err != nil {
return nil, fmt.Errorf("xauth: %w — %s", err, string(out))
}
os.Setenv("XAUTHORITY", xauthPath)
dd := vtnr
if len(dd) == 1 {
dd = "0" + dd
}
// Xorg sends SIGUSR1 only if the parent ignores it during startup.
signal.Ignore(syscall.SIGUSR1)
cmd := exec.Command(cfg.SystemShell, "-c",
fmt.Sprintf("%s %s vt%s", cfg.X11.ServerPath, display, dd))
if logF := openLog(cfg.X11.ServerLogPath); logF != nil {
lw := &limitWriter{w: logF, max: logLimit}
cmd.Stdout = lw
cmd.Stderr = lw
defer logF.Close()
}
if err := cmd.Start(); err != nil {
signal.Reset(syscall.SIGUSR1)
return nil, fmt.Errorf("X server: %w", err)
}
signal.Reset(syscall.SIGUSR1)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGUSR1)
defer signal.Stop(ch)
timeout := time.Duration(cfg.X11.ServerTimeoutSecs) * time.Second
deadline := time.After(timeout)
tick := time.NewTicker(100 * time.Millisecond)
defer tick.Stop()
for {
select {
case <-ch:
return cmd, nil
case <-deadline:
if cfg.X11.ServerTimeoutSecs == 0 {
continue
}
cmd.Process.Signal(syscall.SIGTERM)
cmd.Wait()
return nil, fmt.Errorf("X server timed out after %ds", cfg.X11.ServerTimeoutSecs)
case <-tick.C:
var ws syscall.WaitStatus
if pid, err := syscall.Wait4(cmd.Process.Pid, &ws, syscall.WNOHANG, nil); err == nil && pid == cmd.Process.Pid {
return nil, fmt.Errorf("X server exited with status %d", ws.ExitStatus())
}
}
}
}
func mCookie() (string, error) {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
return "", err
}
return hex.EncodeToString(b[:]), nil
}