diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index fca922c..a099fd6 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -351,14 +351,12 @@ func TestAnUnknownServiceStateIsRefusedNotGuessed(t *testing.T) { } // A boot-time one-shot a package ships, declared stopped and enabled at boot, is satisfied while -// nothing of it runs: before its first boot run and after it (novox/hq issue 1126). The kernel -// module's linux-modules-cleanup.service failed on ace with "was asked to be stopped and is -// running" for a unit that had never run, and every apply after a reboot would have failed the -// same way for one that had. +// nothing of it runs, whatever its last run did (novox/hq issue 1126). The kernel module's +// linux-modules-cleanup.service failed on ace with "was asked to be stopped and is running": an +// inactive one-shot with ExecMainStatus=0 reads "running", and stopping it changes nothing. func TestABootOneShotDeclaredStoppedIsSatisfiedAtRest(t *testing.T) { - for _, c := range []struct{ name, started, status string }{ - {"never ran", "0", "0"}, - {"ran at boot", "5123456", "0"}, + for _, c := range []struct{ name, status string }{ + {"exited 0: never ran, ran at boot, or unloaded", "0"}, } { t.Run(c.name, func(t *testing.T) { var asked []string @@ -367,7 +365,7 @@ func TestABootOneShotDeclaredStoppedIsSatisfiedAtRest(t *testing.T) { switch args[0] { case "show": return "LoadState=loaded\nActiveState=inactive\nType=oneshot\nRemainAfterExit=no\n" + - "ExecMainStatus=" + c.status + "\nExecMainStartTimestampMonotonic=" + c.started + "\n", nil + "ExecMainStatus=" + c.status + "\n", nil case "is-enabled": return "enabled\n", nil } diff --git a/internal/system/arch.go b/internal/system/arch.go index 6126d2f..2b53faf 100644 --- a/internal/system/arch.go +++ b/internal/system/arch.go @@ -217,16 +217,12 @@ func (arch) ServiceState(ctx context.Context, run Runner, unit string) (string, // // So for that shape, what "running" means is "it ran, and it worked". // - // **And one that never ran is not one that worked** (novox/hq issue 1126). systemd says - // ExecMainStatus=0 for a unit that has not run since the manager loaded it exactly as for one - // that succeeded; only its start timestamp, 0, separates them. Read as "running", a package's - // boot-time one-shot declared stopped could never be satisfied — stopping an inactive unit - // changes nothing, and the read back said running again — and one declared running was never - // started. A timestamp the manager did not say keeps the reading it had before. + // It cannot be told from one that never ran: systemd says ExecMainStatus=0 and a start timestamp + // of 0 for a one-shot that never ran, and also for one that ran and was unloaded because nothing + // referenced it, then loaded again. So a one-shot declared running that must be seen to have run + // lingers (RemainAfterExit) or is enabled at boot; one declared stopped is judged at rest by the + // apply (ServiceAtRest, novox/hq issue 1126). if u.oneShotAtRest() { - if u.started == "0" { - return "stopped", nil - } if u.exited == "0" || u.exited == "" { return "running", nil } @@ -259,7 +255,7 @@ func (arch) ServiceAtRest(ctx context.Context, run Runner, unit string) (bool, e // shownUnit is what `systemctl show` says of a unit, as far as its state is read. type shownUnit struct { - load, active, kind, remains, exited, started string + load, active, kind, remains, exited string } // oneShotAtRest is a one-shot that does not linger and is not running now. @@ -273,8 +269,7 @@ func (u shownUnit) oneShotAtRest() bool { func showUnit(ctx context.Context, run Runner, unit string) (shownUnit, error) { out, asked := run(ctx, "systemctl", "show", "--property=LoadState", "--property=ActiveState", "--property=Type", - "--property=RemainAfterExit", "--property=ExecMainStatus", - "--property=ExecMainStartTimestampMonotonic", "--", unit) + "--property=RemainAfterExit", "--property=ExecMainStatus", "--", unit) var u shownUnit for _, line := range strings.Split(out, "\n") { key, value, found := strings.Cut(strings.TrimSpace(line), "=") @@ -292,8 +287,6 @@ func showUnit(ctx context.Context, run Runner, unit string) (shownUnit, error) { u.remains = value case "ExecMainStatus": u.exited = value - case "ExecMainStartTimestampMonotonic": - u.started = value } } if u.load == "" { diff --git a/internal/system/arch_test.go b/internal/system/arch_test.go index 5e57236..637396d 100644 --- a/internal/system/arch_test.go +++ b/internal/system/arch_test.go @@ -14,28 +14,19 @@ import ( // **One that failed is still stopped**, or the host would report success for work that did not // happen — the opposite mistake and the worse one. // -// **One that never ran is stopped** (novox/hq issue 1126, the kernel's boot-time cleanup on ace): -// systemd says ExecMainStatus=0 for a unit that never ran exactly as for one that succeeded, and -// only its start timestamp separates them. Read as "running", a package's one-shot declared -// running was never started, and one declared stopped could never be. -// -// **At rest** is whether a one-shot that does not linger has nothing running now: a declaration +// **At rest** (novox/hq issue 1126, the kernel's boot-time cleanup on ace) is whether a one-shot that does not linger has nothing running now: a declaration // that it be stopped is then satisfied, whatever it last did, because there is nothing to stop. func TestAOneShotIsReadByWhatItDid(t *testing.T) { for _, c := range []struct { name, show, want string atRest bool }{ - {"finished", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" + - "ExecMainStartTimestampMonotonic=5123456\n", "running", true}, - {"finished, no timestamp read", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\n" + - "ExecMainStatus=0\n", "running", true}, - {"failed", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=1\n" + - "ExecMainStartTimestampMonotonic=5123456\n", "stopped", true}, - {"never ran", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" + - "ExecMainStartTimestampMonotonic=0\n", "stopped", true}, - {"running now", "ActiveState=activating\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n" + - "ExecMainStartTimestampMonotonic=5123456\n", "running", false}, + {"finished", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n", + "running", true}, + {"failed", "ActiveState=inactive\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=1\n", + "stopped", true}, + {"running now", "ActiveState=activating\nType=oneshot\nRemainAfterExit=no\nExecMainStatus=0\n", + "running", false}, {"lingers", "ActiveState=active\nType=oneshot\nRemainAfterExit=yes\nExecMainStatus=0\n", "running", false}, {"a daemon, stopped", "ActiveState=inactive\nType=simple\nRemainAfterExit=no\nExecMainStatus=0\n",