Unify trunk on main: initialization → main #3
@@ -16,7 +16,10 @@ const (
|
||||
CapFirewall = "firewall"
|
||||
CapOverlay = "overlay"
|
||||
CapGraphicalSession = "graphical-session"
|
||||
CapPrivileged = "privileged"
|
||||
// CapSeat is hardware: somewhere a display server COULD run. CapGraphicalSession above is
|
||||
// state: whether one IS running. Assignment needs the first.
|
||||
CapSeat = "seat"
|
||||
CapPrivileged = "privileged"
|
||||
)
|
||||
|
||||
// commandCapability is the shape most detectors take: run something, and treat a working
|
||||
@@ -172,6 +175,7 @@ func Default(runner Runner) []Detector {
|
||||
return []Detector{
|
||||
privileged{},
|
||||
graphicalSession{},
|
||||
seat{},
|
||||
commandCapability{
|
||||
name: CapContainerRuntime, command: "docker", args: []string{"info", "--format", "{{.ServerVersion}}"},
|
||||
why: "asks the daemon for its version — a running daemon, not an installed client",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A seat is somewhere a display server could run: a graphics device with a display attached.
|
||||
//
|
||||
// **Not the same question as `graphical-session`**, and the difference is what makes it worth
|
||||
// having. That one asks whether a session is running *now*, which is state; this asks whether one
|
||||
// could ever run here, which is hardware. Deciding whether to assign a display server needs the
|
||||
// second — a headless server can never have one, a workstation with nothing installed yet can,
|
||||
// and until this existed those two looked identical. The mesh would have assigned xorg to the
|
||||
// server and found out at apply time.
|
||||
//
|
||||
// **`seat` rather than `display`, and the distinction matters most on a phone.** An Android
|
||||
// device plainly has a screen and has no seat: nothing there is going to take a DRM device and
|
||||
// present an X or Wayland session on it. A capability called `display` would answer *yes* and be
|
||||
// useless; `seat` answers *no*, which is the true and useful answer.
|
||||
//
|
||||
// The term is logind's, and it means what is wanted here: a set of hardware one person sits at.
|
||||
|
||||
// SeatSource is where the kernel reports what is attached. A variable so a test can point it at a
|
||||
// directory it made, rather than at whatever this machine happens to have.
|
||||
var SeatSource = "/sys/class/drm"
|
||||
|
||||
type seat struct{}
|
||||
|
||||
func (seat) Name() string { return CapSeat }
|
||||
|
||||
func (seat) Detect(context.Context) Verdict {
|
||||
const how = "/sys/class/drm/*/status — a connector the kernel reports as connected"
|
||||
|
||||
entries, err := os.ReadDir(SeatSource)
|
||||
if err != nil {
|
||||
// No DRM subsystem at all: a container, a phone, a machine with no graphics stack. Said
|
||||
// as what was looked for rather than as an error, because this is the ordinary answer on
|
||||
// most nodes and not a fault on any of them.
|
||||
return Verdict{
|
||||
Name: CapSeat, Present: false,
|
||||
Detail: "no graphics devices are present (" + SeatSource + " is not readable)",
|
||||
How: how,
|
||||
}
|
||||
}
|
||||
|
||||
var connected, disconnected []string
|
||||
for _, e := range entries {
|
||||
status, err := os.ReadFile(filepath.Join(SeatSource, e.Name(), "status"))
|
||||
if err != nil {
|
||||
// Cards themselves have no status file; only connectors do. Skipping is right and
|
||||
// not a failure to report.
|
||||
continue
|
||||
}
|
||||
switch strings.TrimSpace(string(status)) {
|
||||
case "connected":
|
||||
connected = append(connected, e.Name())
|
||||
default:
|
||||
disconnected = append(disconnected, e.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(connected)
|
||||
|
||||
if len(connected) > 0 {
|
||||
return Verdict{
|
||||
Name: CapSeat, Present: true,
|
||||
Detail: strings.Join(connected, ", "),
|
||||
How: how,
|
||||
}
|
||||
}
|
||||
if len(disconnected) > 0 {
|
||||
// The distinction worth drawing: this machine has graphics hardware and nothing plugged
|
||||
// into it. A server with a GPU and no monitor, which is a different thing from a machine
|
||||
// with no graphics at all — and a person deciding where a desktop goes wants to know
|
||||
// which they are looking at.
|
||||
return Verdict{
|
||||
Name: CapSeat, Present: false,
|
||||
Detail: "a graphics device with no display attached",
|
||||
How: how,
|
||||
}
|
||||
}
|
||||
return Verdict{
|
||||
Name: CapSeat, Present: false,
|
||||
Detail: "no display connectors are present",
|
||||
How: how,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// drm builds what the kernel would expose, so the cases below are the real ones rather than a
|
||||
// fake's idea of them: a workstation, a server with a card and no monitor, a machine with no
|
||||
// graphics at all.
|
||||
func drm(t *testing.T, connectors map[string]string) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
for name, status := range connectors {
|
||||
if err := os.MkdirAll(filepath.Join(dir, name), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, name, "status"), []byte(status+"\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// The card itself has no status file, and skipping it must not be mistaken for an answer.
|
||||
if err := os.MkdirAll(filepath.Join(dir, "card0"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
old := SeatSource
|
||||
SeatSource = dir
|
||||
t.Cleanup(func() { SeatSource = old })
|
||||
}
|
||||
|
||||
func TestAMachineWithAMonitorHasASeat(t *testing.T) {
|
||||
drm(t, map[string]string{"card0-DP-1": "connected", "card0-HDMI-A-1": "disconnected"})
|
||||
|
||||
got := seat{}.Detect(context.Background())
|
||||
if !got.Present {
|
||||
t.Fatalf("a machine with a connected display has no seat: %s", got.Detail)
|
||||
}
|
||||
if !strings.Contains(got.Detail, "card0-DP-1") {
|
||||
t.Errorf("the answer does not say which connector: %q", got.Detail)
|
||||
}
|
||||
if strings.Contains(got.Detail, "HDMI") {
|
||||
t.Errorf("a disconnected connector was counted: %q", got.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAServerWithAGraphicsCardAndNoMonitorHasNoSeat(t *testing.T) {
|
||||
// The case this capability exists for. A display server assigned here would install, start,
|
||||
// and have nowhere to draw — and the mesh would have thought it succeeded.
|
||||
drm(t, map[string]string{"card0-HDMI-A-1": "disconnected", "card0-DP-1": "disconnected"})
|
||||
|
||||
got := seat{}.Detect(context.Background())
|
||||
if got.Present {
|
||||
t.Fatal("a machine with nothing plugged in reported a seat")
|
||||
}
|
||||
// And it says which of the two "no" answers this is, because a person deciding where a
|
||||
// desktop goes wants to know whether the hardware is there.
|
||||
if !strings.Contains(got.Detail, "no display attached") {
|
||||
t.Errorf("the answer does not distinguish this from having no graphics at all: %q", got.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAMachineWithNoGraphicsAtAllHasNoSeat(t *testing.T) {
|
||||
// A container, or a phone, where the DRM subsystem is not there to read. The ordinary answer
|
||||
// on most nodes and a fault on none of them, so it is a plain "no" rather than an error.
|
||||
old := SeatSource
|
||||
SeatSource = filepath.Join(t.TempDir(), "not-here")
|
||||
t.Cleanup(func() { SeatSource = old })
|
||||
|
||||
got := seat{}.Detect(context.Background())
|
||||
if got.Present {
|
||||
t.Fatal("a machine with no graphics subsystem reported a seat")
|
||||
}
|
||||
if got.Detail == "" || strings.Contains(strings.ToLower(got.Detail), "error") {
|
||||
t.Errorf("absence was reported as a failure: %q", got.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnUnknownConnectorIsNotASeat(t *testing.T) {
|
||||
// Writeback connectors and some virtual devices report "unknown". Counting those would give
|
||||
// a seat to machines that have none — and this workstation has one, so it is not theoretical.
|
||||
drm(t, map[string]string{"card0-Writeback-1": "unknown"})
|
||||
|
||||
if (seat{}).Detect(context.Background()).Present {
|
||||
t.Fatal("a connector reporting \"unknown\" was counted as a display")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheAnswerSaysHowItKnows(t *testing.T) {
|
||||
// Every detector says how, so a wrong answer can be found rather than argued about.
|
||||
drm(t, map[string]string{"card0-DP-1": "connected"})
|
||||
if !strings.Contains((seat{}).Detect(context.Background()).How, "status") {
|
||||
t.Error("the seat detector does not say what it looked at")
|
||||
}
|
||||
}
|
||||
|
||||
func TestASeatIsNotAGraphicalSession(t *testing.T) {
|
||||
// The two are different questions and the whole point of adding one was that they had been
|
||||
// answered by the same thing. A machine can have a seat and no session — that is every
|
||||
// workstation before anything is installed on it, and exactly where a display server should
|
||||
// be assigned.
|
||||
drm(t, map[string]string{"card0-DP-1": "connected"})
|
||||
t.Setenv("DISPLAY", "")
|
||||
t.Setenv("WAYLAND_DISPLAY", "")
|
||||
|
||||
if !(seat{}).Detect(context.Background()).Present {
|
||||
t.Fatal("a machine with a monitor and no session has no seat")
|
||||
}
|
||||
if (graphicalSession{}).Detect(context.Background()).Present {
|
||||
t.Fatal("a machine with no session reported one")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user