Replace the legacy Go implementation as the production path, quarantine the reference tree, add deterministic release packaging and version metadata, and run verification through GitLab CI.
104 lines
2.4 KiB
Go
104 lines
2.4 KiB
Go
// session — X11 server setup.
|
|
//
|
|
// Mirrors vendor/src/post_login/x.rs:setup_x.
|
|
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
|
|
}
|
|
|
|
// Ignore SIGUSR1 so Xorg sends it when ready.
|
|
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
|
|
}
|