From 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 22:49:52 +0200 Subject: Adding upstream version 255.4. Signed-off-by: Daniel Baumann --- test/units/a-conj.service | 9 + test/units/a.service | 8 + test/units/autorelabel.service | 19 + test/units/b.service | 7 + test/units/basic.target | 22 + test/units/c.service | 7 + test/units/d.service | 9 + test/units/daughter.service | 9 + test/units/delegated_cgroup_filtering_payload.sh | 12 + .../delegated_cgroup_filtering_payload_child.sh | 11 + test/units/dml-discard-empty.service | 8 + test/units/dml-discard-set-ml.service | 9 + test/units/dml-discard.slice | 6 + test/units/dml-override-empty.service | 8 + test/units/dml-override.slice | 6 + test/units/dml-passthrough-empty.service | 8 + test/units/dml-passthrough-set-dml.service | 9 + test/units/dml-passthrough-set-ml.service | 9 + test/units/dml-passthrough.slice | 6 + test/units/dml.slice | 6 + test/units/e.service | 9 + test/units/end.service | 11 + test/units/end.sh | 13 + test/units/f.service | 6 + test/units/g.service | 7 + test/units/generator-utils.sh | 78 ++ test/units/grandchild.service | 8 + test/units/h.service | 7 + test/units/i.service | 9 + test/units/loopy.service | 3 + test/units/loopy.service.d/compat.conf | 6 + test/units/loopy2.service | 3 + test/units/loopy3.service | 6 + test/units/loopy4.service | 6 + test/units/nomem.slice | 6 + test/units/nomemleaf.service | 10 + test/units/parent-deep.slice | 6 + test/units/parent.slice | 6 + test/units/sched_idle_bad.service | 7 + test/units/sched_idle_ok.service | 7 + test/units/sched_rr_bad.service | 9 + test/units/sched_rr_change.service | 10 + test/units/sched_rr_ok.service | 7 + test/units/shutdown.target | 14 + test/units/sockets.target | 12 + test/units/son.service | 9 + test/units/success-failure-test-failure.service | 3 + test/units/success-failure-test-success.service | 3 + test/units/success-failure-test.service | 9 + test/units/sysinit.target | 15 + test/units/test-control.sh | 166 +++ test/units/testsuite-01.service | 10 + test/units/testsuite-01.sh | 61 + test/units/testsuite-02.service | 8 + test/units/testsuite-02.sh | 113 ++ test/units/testsuite-03.service | 9 + test/units/testsuite-03.sh | 169 +++ test/units/testsuite-04.LogFilterPatterns.sh | 86 ++ .../units/testsuite-04.SYSTEMD_JOURNAL_COMPRESS.sh | 42 + test/units/testsuite-04.bsod.sh | 103 ++ test/units/testsuite-04.corrupted-journals.sh | 48 + test/units/testsuite-04.fss.sh | 46 + test/units/testsuite-04.journal-append.sh | 46 + test/units/testsuite-04.journal-gatewayd.sh | 119 ++ test/units/testsuite-04.journal-remote.sh | 230 ++++ test/units/testsuite-04.journal.sh | 271 ++++ test/units/testsuite-04.service | 8 + test/units/testsuite-04.sh | 11 + test/units/testsuite-05.service | 8 + test/units/testsuite-05.sh | 27 + test/units/testsuite-06.service | 8 + test/units/testsuite-06.sh | 43 + test/units/testsuite-07.exec-context.sh | 375 ++++++ test/units/testsuite-07.issue-14566.sh | 29 + test/units/testsuite-07.issue-16115.sh | 16 + test/units/testsuite-07.issue-1981.sh | 47 + test/units/testsuite-07.issue-2467.sh | 17 + test/units/testsuite-07.issue-27953.sh | 11 + test/units/testsuite-07.issue-30412.sh | 32 + test/units/testsuite-07.issue-3166.sh | 16 + test/units/testsuite-07.issue-3171.sh | 50 + test/units/testsuite-07.main-PID-change.sh | 172 +++ test/units/testsuite-07.mount-invalid-chars.sh | 70 ++ test/units/testsuite-07.poll-limit.sh | 48 + test/units/testsuite-07.private-network.sh | 7 + test/units/testsuite-07.service | 13 + test/units/testsuite-07.sh | 15 + test/units/testsuite-08.service | 9 + test/units/testsuite-08.sh | 30 + test/units/testsuite-09.journal.sh | 72 ++ test/units/testsuite-09.service | 9 + test/units/testsuite-09.sh | 25 + test/units/testsuite-13.machinectl.sh | 218 ++++ test/units/testsuite-13.nspawn-oci.sh | 467 +++++++ test/units/testsuite-13.nspawn.sh | 884 +++++++++++++ test/units/testsuite-13.nss-mymachines.sh | 135 ++ test/units/testsuite-13.service | 8 + test/units/testsuite-13.sh | 11 + test/units/testsuite-15.service | 8 + test/units/testsuite-15.sh | 711 +++++++++++ test/units/testsuite-16.service | 20 + test/units/testsuite-16.sh | 119 ++ test/units/testsuite-17.00.sh | 57 + test/units/testsuite-17.01.sh | 75 ++ test/units/testsuite-17.02.sh | 182 +++ test/units/testsuite-17.03.sh | 75 ++ test/units/testsuite-17.04.sh | 49 + test/units/testsuite-17.05.sh | 23 + test/units/testsuite-17.06.sh | 69 ++ test/units/testsuite-17.07.sh | 205 +++ test/units/testsuite-17.08.sh | 72 ++ test/units/testsuite-17.09.sh | 70 ++ test/units/testsuite-17.10.sh | 254 ++++ test/units/testsuite-17.11.sh | 447 +++++++ test/units/testsuite-17.12.sh | 86 ++ test/units/testsuite-17.13.sh | 89 ++ test/units/testsuite-17.service | 8 + test/units/testsuite-17.sh | 13 + test/units/testsuite-18.service | 8 + test/units/testsuite-18.sh | 17 + test/units/testsuite-19.ExitType-cgroup.sh | 102 ++ test/units/testsuite-19.cleanup-slice.sh | 49 + test/units/testsuite-19.delegate.sh | 115 ++ test/units/testsuite-19.service | 8 + test/units/testsuite-19.sh | 11 + test/units/testsuite-21.service | 10 + test/units/testsuite-21.sh | 110 ++ test/units/testsuite-22.01.sh | 13 + test/units/testsuite-22.02.sh | 167 +++ test/units/testsuite-22.03.sh | 246 ++++ test/units/testsuite-22.04.sh | 43 + test/units/testsuite-22.05.sh | 45 + test/units/testsuite-22.06.sh | 38 + test/units/testsuite-22.07.sh | 30 + test/units/testsuite-22.08.sh | 32 + test/units/testsuite-22.09.sh | 59 + test/units/testsuite-22.10.sh | 28 + test/units/testsuite-22.11.sh | 141 +++ test/units/testsuite-22.12.sh | 196 +++ test/units/testsuite-22.13.sh | 75 ++ test/units/testsuite-22.14.sh | 37 + test/units/testsuite-22.15.sh | 32 + test/units/testsuite-22.16.sh | 36 + test/units/testsuite-22.17.sh | 15 + test/units/testsuite-22.service | 11 + test/units/testsuite-22.sh | 11 + test/units/testsuite-23-short-lived.sh | 18 + test/units/testsuite-23.ExecReload.sh | 61 + test/units/testsuite-23.ExecStopPost.sh | 104 ++ test/units/testsuite-23.JoinsNamespaceOf.sh | 31 + .../units/testsuite-23.RuntimeDirectoryPreserve.sh | 26 + test/units/testsuite-23.StandardOutput.sh | 60 + test/units/testsuite-23.Upholds.sh | 99 ++ test/units/testsuite-23.clean-unit.sh | 329 +++++ test/units/testsuite-23.exec-command-ex.sh | 44 + test/units/testsuite-23.oneshot-restart.sh | 52 + test/units/testsuite-23.percentj-wantedby.sh | 15 + test/units/testsuite-23.runtime-bind-paths.sh | 43 + test/units/testsuite-23.service | 8 + test/units/testsuite-23.sh | 12 + test/units/testsuite-23.start-stop-no-reload.sh | 93 ++ test/units/testsuite-23.statedir.sh | 60 + test/units/testsuite-23.success-failure.sh | 49 + test/units/testsuite-23.type-exec.sh | 63 + test/units/testsuite-23.utmp.sh | 22 + test/units/testsuite-23.whoami.sh | 15 + test/units/testsuite-24.service | 9 + test/units/testsuite-24.sh | 216 ++++ test/units/testsuite-25.service | 8 + test/units/testsuite-25.sh | 143 +++ test/units/testsuite-26.service | 8 + test/units/testsuite-26.sh | 465 +++++++ test/units/testsuite-29.service | 8 + test/units/testsuite-29.sh | 280 +++++ test/units/testsuite-30.service | 8 + test/units/testsuite-30.sh | 29 + test/units/testsuite-31.service | 8 + test/units/testsuite-31.sh | 10 + test/units/testsuite-32.service | 9 + test/units/testsuite-32.sh | 36 + test/units/testsuite-34.service | 8 + test/units/testsuite-34.sh | 160 +++ test/units/testsuite-35.service | 8 + test/units/testsuite-35.sh | 660 ++++++++++ test/units/testsuite-36.service | 8 + test/units/testsuite-36.sh | 352 ++++++ test/units/testsuite-38-sleep.service | 3 + test/units/testsuite-38.service | 7 + test/units/testsuite-38.sh | 301 +++++ test/units/testsuite-43.service | 10 + test/units/testsuite-43.sh | 143 +++ test/units/testsuite-44.service | 12 + test/units/testsuite-44.sh | 18 + test/units/testsuite-45.service | 8 + test/units/testsuite-45.sh | 412 ++++++ test/units/testsuite-46.service | 13 + test/units/testsuite-46.sh | 319 +++++ test/units/testsuite-50.service | 8 + test/units/testsuite-50.sh | 718 +++++++++++ test/units/testsuite-52.service | 7 + test/units/testsuite-52.sh | 11 + test/units/testsuite-53.service | 8 + test/units/testsuite-53.sh | 31 + test/units/testsuite-54.service | 8 + test/units/testsuite-54.sh | 319 +++++ test/units/testsuite-55-testbloat.service | 10 + test/units/testsuite-55-testchill.service | 8 + test/units/testsuite-55-testmunch.service | 8 + test/units/testsuite-55-workload.slice | 11 + test/units/testsuite-55.service | 10 + test/units/testsuite-55.sh | 182 +++ test/units/testsuite-58.service | 7 + test/units/testsuite-58.sh | 1307 ++++++++++++++++++++ test/units/testsuite-59.service | 7 + test/units/testsuite-59.sh | 160 +++ test/units/testsuite-60.service | 8 + test/units/testsuite-60.sh | 308 +++++ test/units/testsuite-62-1.service | 9 + test/units/testsuite-62-2.service | 10 + test/units/testsuite-62-3.service | 10 + test/units/testsuite-62-4.service | 10 + test/units/testsuite-62-5.service | 11 + test/units/testsuite-62.service | 8 + test/units/testsuite-62.sh | 63 + test/units/testsuite-63.service | 8 + test/units/testsuite-63.sh | 125 ++ test/units/testsuite-64.service | 8 + test/units/testsuite-64.sh | 1192 ++++++++++++++++++ test/units/testsuite-65.service | 8 + test/units/testsuite-65.sh | 909 ++++++++++++++ test/units/testsuite-66-deviceisolation.service | 10 + test/units/testsuite-66.service | 8 + test/units/testsuite-66.sh | 24 + test/units/testsuite-67.service | 9 + test/units/testsuite-67.sh | 121 ++ test/units/testsuite-68.service | 7 + test/units/testsuite-68.sh | 216 ++++ test/units/testsuite-69.service | 7 + test/units/testsuite-70.creds.sh | 16 + test/units/testsuite-70.cryptenroll.sh | 84 ++ test/units/testsuite-70.cryptsetup.sh | 226 ++++ test/units/testsuite-70.measure.sh | 130 ++ test/units/testsuite-70.pcrextend.sh | 124 ++ test/units/testsuite-70.pcrlock.sh | 146 +++ test/units/testsuite-70.service | 7 + test/units/testsuite-70.sh | 11 + test/units/testsuite-70.tpm2-setup.sh | 27 + test/units/testsuite-71.service | 8 + test/units/testsuite-71.sh | 228 ++++ test/units/testsuite-72.service | 8 + test/units/testsuite-72.sh | 278 +++++ test/units/testsuite-73.service | 8 + test/units/testsuite-73.sh | 693 +++++++++++ test/units/testsuite-74.battery-check.sh | 9 + test/units/testsuite-74.bootctl.sh | 266 ++++ test/units/testsuite-74.busctl.sh | 110 ++ test/units/testsuite-74.cgls.sh | 27 + test/units/testsuite-74.cgtop.sh | 32 + test/units/testsuite-74.coredump.sh | 221 ++++ test/units/testsuite-74.delta.sh | 59 + test/units/testsuite-74.escape.sh | 108 ++ test/units/testsuite-74.firstboot.sh | 197 +++ test/units/testsuite-74.id128.sh | 50 + test/units/testsuite-74.machine-id-setup.sh | 77 ++ test/units/testsuite-74.modules-load.sh | 88 ++ test/units/testsuite-74.mount.sh | 151 +++ test/units/testsuite-74.networkctl.sh | 86 ++ test/units/testsuite-74.path.sh | 89 ++ test/units/testsuite-74.pstore.sh | 258 ++++ test/units/testsuite-74.run.sh | 236 ++++ test/units/testsuite-74.service | 8 + test/units/testsuite-74.sh | 11 + test/units/testsuite-74.varlinkctl.sh | 89 ++ test/units/testsuite-75.service | 8 + test/units/testsuite-75.sh | 729 +++++++++++ test/units/testsuite-76.service | 8 + test/units/testsuite-76.sh | 39 + test/units/testsuite-77-client.sh | 14 + test/units/testsuite-77-run.sh | 14 + test/units/testsuite-77-server.socket | 6 + test/units/testsuite-77-server@.service | 7 + test/units/testsuite-77.service | 10 + test/units/testsuite-77.sh | 38 + test/units/testsuite-78.service | 7 + test/units/testsuite-78.sh | 35 + test/units/testsuite-79.service | 8 + test/units/testsuite-79.sh | 58 + test/units/testsuite-80.service | 8 + test/units/testsuite-80.sh | 126 ++ test/units/testsuite-81.debug-generator.sh | 105 ++ test/units/testsuite-81.environment-d-generator.sh | 80 ++ test/units/testsuite-81.fstab-generator.sh | 406 ++++++ test/units/testsuite-81.getty-generator.sh | 89 ++ test/units/testsuite-81.run-generator.sh | 76 ++ test/units/testsuite-81.service | 8 + test/units/testsuite-81.sh | 11 + test/units/testsuite-81.system-update-generator.sh | 38 + test/units/testsuite-82.service | 11 + test/units/testsuite-82.sh | 223 ++++ test/units/testsuite-83.service | 8 + test/units/testsuite-83.sh | 25 + test/units/testsuite-84.service | 9 + test/units/testsuite-84.sh | 26 + test/units/testsuite.target | 7 + test/units/timers.target | 15 + test/units/unit-.service.d/10-override.conf | 3 + test/units/unit-with-.service.d/20-override.conf | 3 + .../unit-with-multiple-.service.d/20-override.conf | 3 + .../unit-with-multiple-.service.d/30-override.conf | 3 + test/units/unit-with-multiple-dashes.service | 7 + .../10-override.conf | 3 + test/units/util.sh | 218 ++++ 312 files changed, 26678 insertions(+) create mode 100644 test/units/a-conj.service create mode 100644 test/units/a.service create mode 100644 test/units/autorelabel.service create mode 100644 test/units/b.service create mode 100644 test/units/basic.target create mode 100644 test/units/c.service create mode 100644 test/units/d.service create mode 100644 test/units/daughter.service create mode 100755 test/units/delegated_cgroup_filtering_payload.sh create mode 100755 test/units/delegated_cgroup_filtering_payload_child.sh create mode 100644 test/units/dml-discard-empty.service create mode 100644 test/units/dml-discard-set-ml.service create mode 100644 test/units/dml-discard.slice create mode 100644 test/units/dml-override-empty.service create mode 100644 test/units/dml-override.slice create mode 100644 test/units/dml-passthrough-empty.service create mode 100644 test/units/dml-passthrough-set-dml.service create mode 100644 test/units/dml-passthrough-set-ml.service create mode 100644 test/units/dml-passthrough.slice create mode 100644 test/units/dml.slice create mode 100644 test/units/e.service create mode 100644 test/units/end.service create mode 100755 test/units/end.sh create mode 100644 test/units/f.service create mode 100644 test/units/g.service create mode 100755 test/units/generator-utils.sh create mode 100644 test/units/grandchild.service create mode 100644 test/units/h.service create mode 100644 test/units/i.service create mode 100644 test/units/loopy.service create mode 100644 test/units/loopy.service.d/compat.conf create mode 100644 test/units/loopy2.service create mode 100644 test/units/loopy3.service create mode 100644 test/units/loopy4.service create mode 100644 test/units/nomem.slice create mode 100644 test/units/nomemleaf.service create mode 100644 test/units/parent-deep.slice create mode 100644 test/units/parent.slice create mode 100644 test/units/sched_idle_bad.service create mode 100644 test/units/sched_idle_ok.service create mode 100644 test/units/sched_rr_bad.service create mode 100644 test/units/sched_rr_change.service create mode 100644 test/units/sched_rr_ok.service create mode 100644 test/units/shutdown.target create mode 100644 test/units/sockets.target create mode 100644 test/units/son.service create mode 100644 test/units/success-failure-test-failure.service create mode 100644 test/units/success-failure-test-success.service create mode 100644 test/units/success-failure-test.service create mode 100644 test/units/sysinit.target create mode 100644 test/units/test-control.sh create mode 100644 test/units/testsuite-01.service create mode 100755 test/units/testsuite-01.sh create mode 100644 test/units/testsuite-02.service create mode 100755 test/units/testsuite-02.sh create mode 100644 test/units/testsuite-03.service create mode 100755 test/units/testsuite-03.sh create mode 100755 test/units/testsuite-04.LogFilterPatterns.sh create mode 100755 test/units/testsuite-04.SYSTEMD_JOURNAL_COMPRESS.sh create mode 100755 test/units/testsuite-04.bsod.sh create mode 100755 test/units/testsuite-04.corrupted-journals.sh create mode 100755 test/units/testsuite-04.fss.sh create mode 100755 test/units/testsuite-04.journal-append.sh create mode 100755 test/units/testsuite-04.journal-gatewayd.sh create mode 100755 test/units/testsuite-04.journal-remote.sh create mode 100755 test/units/testsuite-04.journal.sh create mode 100644 test/units/testsuite-04.service create mode 100755 test/units/testsuite-04.sh create mode 100644 test/units/testsuite-05.service create mode 100755 test/units/testsuite-05.sh create mode 100644 test/units/testsuite-06.service create mode 100755 test/units/testsuite-06.sh create mode 100755 test/units/testsuite-07.exec-context.sh create mode 100755 test/units/testsuite-07.issue-14566.sh create mode 100755 test/units/testsuite-07.issue-16115.sh create mode 100755 test/units/testsuite-07.issue-1981.sh create mode 100755 test/units/testsuite-07.issue-2467.sh create mode 100755 test/units/testsuite-07.issue-27953.sh create mode 100755 test/units/testsuite-07.issue-30412.sh create mode 100755 test/units/testsuite-07.issue-3166.sh create mode 100755 test/units/testsuite-07.issue-3171.sh create mode 100755 test/units/testsuite-07.main-PID-change.sh create mode 100755 test/units/testsuite-07.mount-invalid-chars.sh create mode 100755 test/units/testsuite-07.poll-limit.sh create mode 100755 test/units/testsuite-07.private-network.sh create mode 100644 test/units/testsuite-07.service create mode 100755 test/units/testsuite-07.sh create mode 100644 test/units/testsuite-08.service create mode 100755 test/units/testsuite-08.sh create mode 100755 test/units/testsuite-09.journal.sh create mode 100644 test/units/testsuite-09.service create mode 100755 test/units/testsuite-09.sh create mode 100755 test/units/testsuite-13.machinectl.sh create mode 100755 test/units/testsuite-13.nspawn-oci.sh create mode 100755 test/units/testsuite-13.nspawn.sh create mode 100755 test/units/testsuite-13.nss-mymachines.sh create mode 100644 test/units/testsuite-13.service create mode 100755 test/units/testsuite-13.sh create mode 100644 test/units/testsuite-15.service create mode 100755 test/units/testsuite-15.sh create mode 100644 test/units/testsuite-16.service create mode 100755 test/units/testsuite-16.sh create mode 100755 test/units/testsuite-17.00.sh create mode 100755 test/units/testsuite-17.01.sh create mode 100755 test/units/testsuite-17.02.sh create mode 100755 test/units/testsuite-17.03.sh create mode 100755 test/units/testsuite-17.04.sh create mode 100755 test/units/testsuite-17.05.sh create mode 100755 test/units/testsuite-17.06.sh create mode 100755 test/units/testsuite-17.07.sh create mode 100755 test/units/testsuite-17.08.sh create mode 100755 test/units/testsuite-17.09.sh create mode 100755 test/units/testsuite-17.10.sh create mode 100755 test/units/testsuite-17.11.sh create mode 100755 test/units/testsuite-17.12.sh create mode 100755 test/units/testsuite-17.13.sh create mode 100644 test/units/testsuite-17.service create mode 100755 test/units/testsuite-17.sh create mode 100644 test/units/testsuite-18.service create mode 100755 test/units/testsuite-18.sh create mode 100755 test/units/testsuite-19.ExitType-cgroup.sh create mode 100755 test/units/testsuite-19.cleanup-slice.sh create mode 100755 test/units/testsuite-19.delegate.sh create mode 100644 test/units/testsuite-19.service create mode 100755 test/units/testsuite-19.sh create mode 100644 test/units/testsuite-21.service create mode 100755 test/units/testsuite-21.sh create mode 100755 test/units/testsuite-22.01.sh create mode 100755 test/units/testsuite-22.02.sh create mode 100755 test/units/testsuite-22.03.sh create mode 100755 test/units/testsuite-22.04.sh create mode 100755 test/units/testsuite-22.05.sh create mode 100755 test/units/testsuite-22.06.sh create mode 100755 test/units/testsuite-22.07.sh create mode 100755 test/units/testsuite-22.08.sh create mode 100755 test/units/testsuite-22.09.sh create mode 100755 test/units/testsuite-22.10.sh create mode 100755 test/units/testsuite-22.11.sh create mode 100755 test/units/testsuite-22.12.sh create mode 100755 test/units/testsuite-22.13.sh create mode 100755 test/units/testsuite-22.14.sh create mode 100755 test/units/testsuite-22.15.sh create mode 100755 test/units/testsuite-22.16.sh create mode 100755 test/units/testsuite-22.17.sh create mode 100644 test/units/testsuite-22.service create mode 100755 test/units/testsuite-22.sh create mode 100755 test/units/testsuite-23-short-lived.sh create mode 100755 test/units/testsuite-23.ExecReload.sh create mode 100755 test/units/testsuite-23.ExecStopPost.sh create mode 100755 test/units/testsuite-23.JoinsNamespaceOf.sh create mode 100755 test/units/testsuite-23.RuntimeDirectoryPreserve.sh create mode 100755 test/units/testsuite-23.StandardOutput.sh create mode 100755 test/units/testsuite-23.Upholds.sh create mode 100755 test/units/testsuite-23.clean-unit.sh create mode 100755 test/units/testsuite-23.exec-command-ex.sh create mode 100755 test/units/testsuite-23.oneshot-restart.sh create mode 100755 test/units/testsuite-23.percentj-wantedby.sh create mode 100755 test/units/testsuite-23.runtime-bind-paths.sh create mode 100644 test/units/testsuite-23.service create mode 100755 test/units/testsuite-23.sh create mode 100755 test/units/testsuite-23.start-stop-no-reload.sh create mode 100755 test/units/testsuite-23.statedir.sh create mode 100755 test/units/testsuite-23.success-failure.sh create mode 100755 test/units/testsuite-23.type-exec.sh create mode 100755 test/units/testsuite-23.utmp.sh create mode 100755 test/units/testsuite-23.whoami.sh create mode 100644 test/units/testsuite-24.service create mode 100755 test/units/testsuite-24.sh create mode 100644 test/units/testsuite-25.service create mode 100755 test/units/testsuite-25.sh create mode 100644 test/units/testsuite-26.service create mode 100755 test/units/testsuite-26.sh create mode 100644 test/units/testsuite-29.service create mode 100755 test/units/testsuite-29.sh create mode 100644 test/units/testsuite-30.service create mode 100755 test/units/testsuite-30.sh create mode 100644 test/units/testsuite-31.service create mode 100755 test/units/testsuite-31.sh create mode 100644 test/units/testsuite-32.service create mode 100755 test/units/testsuite-32.sh create mode 100644 test/units/testsuite-34.service create mode 100755 test/units/testsuite-34.sh create mode 100644 test/units/testsuite-35.service create mode 100755 test/units/testsuite-35.sh create mode 100644 test/units/testsuite-36.service create mode 100755 test/units/testsuite-36.sh create mode 100644 test/units/testsuite-38-sleep.service create mode 100644 test/units/testsuite-38.service create mode 100755 test/units/testsuite-38.sh create mode 100644 test/units/testsuite-43.service create mode 100755 test/units/testsuite-43.sh create mode 100644 test/units/testsuite-44.service create mode 100755 test/units/testsuite-44.sh create mode 100644 test/units/testsuite-45.service create mode 100755 test/units/testsuite-45.sh create mode 100644 test/units/testsuite-46.service create mode 100755 test/units/testsuite-46.sh create mode 100644 test/units/testsuite-50.service create mode 100755 test/units/testsuite-50.sh create mode 100644 test/units/testsuite-52.service create mode 100755 test/units/testsuite-52.sh create mode 100644 test/units/testsuite-53.service create mode 100755 test/units/testsuite-53.sh create mode 100644 test/units/testsuite-54.service create mode 100755 test/units/testsuite-54.sh create mode 100644 test/units/testsuite-55-testbloat.service create mode 100644 test/units/testsuite-55-testchill.service create mode 100644 test/units/testsuite-55-testmunch.service create mode 100644 test/units/testsuite-55-workload.slice create mode 100644 test/units/testsuite-55.service create mode 100755 test/units/testsuite-55.sh create mode 100644 test/units/testsuite-58.service create mode 100755 test/units/testsuite-58.sh create mode 100644 test/units/testsuite-59.service create mode 100755 test/units/testsuite-59.sh create mode 100644 test/units/testsuite-60.service create mode 100755 test/units/testsuite-60.sh create mode 100644 test/units/testsuite-62-1.service create mode 100644 test/units/testsuite-62-2.service create mode 100644 test/units/testsuite-62-3.service create mode 100644 test/units/testsuite-62-4.service create mode 100644 test/units/testsuite-62-5.service create mode 100644 test/units/testsuite-62.service create mode 100755 test/units/testsuite-62.sh create mode 100644 test/units/testsuite-63.service create mode 100755 test/units/testsuite-63.sh create mode 100644 test/units/testsuite-64.service create mode 100755 test/units/testsuite-64.sh create mode 100644 test/units/testsuite-65.service create mode 100755 test/units/testsuite-65.sh create mode 100644 test/units/testsuite-66-deviceisolation.service create mode 100644 test/units/testsuite-66.service create mode 100755 test/units/testsuite-66.sh create mode 100644 test/units/testsuite-67.service create mode 100755 test/units/testsuite-67.sh create mode 100644 test/units/testsuite-68.service create mode 100755 test/units/testsuite-68.sh create mode 100644 test/units/testsuite-69.service create mode 100755 test/units/testsuite-70.creds.sh create mode 100755 test/units/testsuite-70.cryptenroll.sh create mode 100755 test/units/testsuite-70.cryptsetup.sh create mode 100755 test/units/testsuite-70.measure.sh create mode 100755 test/units/testsuite-70.pcrextend.sh create mode 100755 test/units/testsuite-70.pcrlock.sh create mode 100644 test/units/testsuite-70.service create mode 100755 test/units/testsuite-70.sh create mode 100755 test/units/testsuite-70.tpm2-setup.sh create mode 100644 test/units/testsuite-71.service create mode 100755 test/units/testsuite-71.sh create mode 100644 test/units/testsuite-72.service create mode 100755 test/units/testsuite-72.sh create mode 100644 test/units/testsuite-73.service create mode 100755 test/units/testsuite-73.sh create mode 100755 test/units/testsuite-74.battery-check.sh create mode 100755 test/units/testsuite-74.bootctl.sh create mode 100755 test/units/testsuite-74.busctl.sh create mode 100755 test/units/testsuite-74.cgls.sh create mode 100755 test/units/testsuite-74.cgtop.sh create mode 100755 test/units/testsuite-74.coredump.sh create mode 100755 test/units/testsuite-74.delta.sh create mode 100755 test/units/testsuite-74.escape.sh create mode 100755 test/units/testsuite-74.firstboot.sh create mode 100755 test/units/testsuite-74.id128.sh create mode 100755 test/units/testsuite-74.machine-id-setup.sh create mode 100755 test/units/testsuite-74.modules-load.sh create mode 100755 test/units/testsuite-74.mount.sh create mode 100755 test/units/testsuite-74.networkctl.sh create mode 100755 test/units/testsuite-74.path.sh create mode 100755 test/units/testsuite-74.pstore.sh create mode 100755 test/units/testsuite-74.run.sh create mode 100644 test/units/testsuite-74.service create mode 100755 test/units/testsuite-74.sh create mode 100755 test/units/testsuite-74.varlinkctl.sh create mode 100644 test/units/testsuite-75.service create mode 100755 test/units/testsuite-75.sh create mode 100644 test/units/testsuite-76.service create mode 100755 test/units/testsuite-76.sh create mode 100755 test/units/testsuite-77-client.sh create mode 100755 test/units/testsuite-77-run.sh create mode 100644 test/units/testsuite-77-server.socket create mode 100644 test/units/testsuite-77-server@.service create mode 100644 test/units/testsuite-77.service create mode 100755 test/units/testsuite-77.sh create mode 100644 test/units/testsuite-78.service create mode 100755 test/units/testsuite-78.sh create mode 100644 test/units/testsuite-79.service create mode 100755 test/units/testsuite-79.sh create mode 100644 test/units/testsuite-80.service create mode 100755 test/units/testsuite-80.sh create mode 100755 test/units/testsuite-81.debug-generator.sh create mode 100755 test/units/testsuite-81.environment-d-generator.sh create mode 100755 test/units/testsuite-81.fstab-generator.sh create mode 100755 test/units/testsuite-81.getty-generator.sh create mode 100755 test/units/testsuite-81.run-generator.sh create mode 100644 test/units/testsuite-81.service create mode 100755 test/units/testsuite-81.sh create mode 100755 test/units/testsuite-81.system-update-generator.sh create mode 100644 test/units/testsuite-82.service create mode 100755 test/units/testsuite-82.sh create mode 100644 test/units/testsuite-83.service create mode 100755 test/units/testsuite-83.sh create mode 100644 test/units/testsuite-84.service create mode 100755 test/units/testsuite-84.sh create mode 100644 test/units/testsuite.target create mode 100644 test/units/timers.target create mode 100644 test/units/unit-.service.d/10-override.conf create mode 100644 test/units/unit-with-.service.d/20-override.conf create mode 100644 test/units/unit-with-multiple-.service.d/20-override.conf create mode 100644 test/units/unit-with-multiple-.service.d/30-override.conf create mode 100644 test/units/unit-with-multiple-dashes.service create mode 100644 test/units/unit-with-multiple-dashes.service.d/10-override.conf create mode 100755 test/units/util.sh (limited to 'test/units') diff --git a/test/units/a-conj.service b/test/units/a-conj.service new file mode 100644 index 0000000..3a7c9e1 --- /dev/null +++ b/test/units/a-conj.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=A conjugate +Requires=a.service +After=a.service +Before=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/a.service b/test/units/a.service new file mode 100644 index 0000000..ec5d059 --- /dev/null +++ b/test/units/a.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=A +Requires=b.service +Before=b.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/autorelabel.service b/test/units/autorelabel.service new file mode 100644 index 0000000..7e5f9a2 --- /dev/null +++ b/test/units/autorelabel.service @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Relabel all filesystems +DefaultDependencies=no +Requires=local-fs.target +Conflicts=shutdown.target +After=local-fs.target +Before=sysinit.target shutdown.target +ConditionSecurity=selinux +ConditionPathExists=|/.autorelabel + +[Service] +ExecStart=sh -xec 'echo 0 >/sys/fs/selinux/enforce; fixfiles -f -F relabel; rm /.autorelabel; systemctl --force reboot' +Type=oneshot +TimeoutSec=infinity +RemainAfterExit=yes + +[Install] +WantedBy=basic.target diff --git a/test/units/b.service b/test/units/b.service new file mode 100644 index 0000000..4503cf3 --- /dev/null +++ b/test/units/b.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=B +Wants=f.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/basic.target b/test/units/basic.target new file mode 100644 index 0000000..d8cdd5a --- /dev/null +++ b/test/units/basic.target @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Basic System +Documentation=man:systemd.special(7) +Requires=sysinit.target +Wants=sockets.target timers.target paths.target slices.target +After=sysinit.target sockets.target paths.target slices.target tmp.mount + +# We support /var, /tmp, /var/tmp, being on NFS, but we don't pull in +# remote-fs.target by default, hence pull them in explicitly here. Note that we +# require /var and /var/tmp, but only add a Wants= type dependency on /tmp, as +# we support that unit being masked, and this should not be considered an error. +RequiresMountsFor=/var /var/tmp +Wants=tmp.mount diff --git a/test/units/c.service b/test/units/c.service new file mode 100644 index 0000000..a1ce28c --- /dev/null +++ b/test/units/c.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=C +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/d.service b/test/units/d.service new file mode 100644 index 0000000..8202325 --- /dev/null +++ b/test/units/d.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=D:Cyclic +After=b.service +Before=a.service +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/daughter.service b/test/units/daughter.service new file mode 100644 index 0000000..385fbed --- /dev/null +++ b/test/units/daughter.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Daughter Service + +[Service] +Slice=parent.slice +Type=oneshot +ExecStart=/bin/true +CPUAccounting=true diff --git a/test/units/delegated_cgroup_filtering_payload.sh b/test/units/delegated_cgroup_filtering_payload.sh new file mode 100755 index 0000000..50d01a5 --- /dev/null +++ b/test/units/delegated_cgroup_filtering_payload.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# SPDX-License-Identifier: LGPL-2.1-or-later + +mkdir /sys/fs/cgroup/system.slice/delegated-cgroup-filtering.service/the_child +/bin/sh /usr/lib/systemd/tests/testdata/units/delegated_cgroup_filtering_payload_child.sh & + +while true +do + echo "parent_process: hello, world!" + echo "parent_process: hello, people!" + sleep .15 +done diff --git a/test/units/delegated_cgroup_filtering_payload_child.sh b/test/units/delegated_cgroup_filtering_payload_child.sh new file mode 100755 index 0000000..b5635b5 --- /dev/null +++ b/test/units/delegated_cgroup_filtering_payload_child.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# SPDX-License-Identifier: LGPL-2.1-or-later + +echo $$ >/sys/fs/cgroup/system.slice/delegated-cgroup-filtering.service/the_child/cgroup.procs + +while true +do + echo "child_process: hello, world!" + echo "child_process: hello, people!" + sleep .15 +done diff --git a/test/units/dml-discard-empty.service b/test/units/dml-discard-empty.service new file mode 100644 index 0000000..720c1da --- /dev/null +++ b/test/units/dml-discard-empty.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML discard empty service + +[Service] +Slice=dml-discard.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/units/dml-discard-set-ml.service b/test/units/dml-discard-set-ml.service new file mode 100644 index 0000000..93246ac --- /dev/null +++ b/test/units/dml-discard-set-ml.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML discard set ml service + +[Service] +Slice=dml-discard.slice +Type=oneshot +ExecStart=/bin/true +MemoryLow=15 diff --git a/test/units/dml-discard.slice b/test/units/dml-discard.slice new file mode 100644 index 0000000..dc8a397 --- /dev/null +++ b/test/units/dml-discard.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML discard slice + +[Slice] +DefaultMemoryLow= diff --git a/test/units/dml-override-empty.service b/test/units/dml-override-empty.service new file mode 100644 index 0000000..ac96de0 --- /dev/null +++ b/test/units/dml-override-empty.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML override empty service + +[Service] +Slice=dml-override.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/units/dml-override.slice b/test/units/dml-override.slice new file mode 100644 index 0000000..ac664d1 --- /dev/null +++ b/test/units/dml-override.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML override slice + +[Slice] +DefaultMemoryLow=10 diff --git a/test/units/dml-passthrough-empty.service b/test/units/dml-passthrough-empty.service new file mode 100644 index 0000000..1e1ba34 --- /dev/null +++ b/test/units/dml-passthrough-empty.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML passthrough empty service + +[Service] +Slice=dml-passthrough.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/units/dml-passthrough-set-dml.service b/test/units/dml-passthrough-set-dml.service new file mode 100644 index 0000000..9a15311 --- /dev/null +++ b/test/units/dml-passthrough-set-dml.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML passthrough set DML service + +[Service] +Slice=dml-passthrough.slice +Type=oneshot +ExecStart=/bin/true +DefaultMemoryLow=15 diff --git a/test/units/dml-passthrough-set-ml.service b/test/units/dml-passthrough-set-ml.service new file mode 100644 index 0000000..65083bc --- /dev/null +++ b/test/units/dml-passthrough-set-ml.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML passthrough set ML service + +[Service] +Slice=dml-passthrough.slice +Type=oneshot +ExecStart=/bin/true +MemoryLow=0 diff --git a/test/units/dml-passthrough.slice b/test/units/dml-passthrough.slice new file mode 100644 index 0000000..1c8769d --- /dev/null +++ b/test/units/dml-passthrough.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML passthrough slice + +[Slice] +MemoryLow=100 diff --git a/test/units/dml.slice b/test/units/dml.slice new file mode 100644 index 0000000..8e00e7f --- /dev/null +++ b/test/units/dml.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=DML slice + +[Slice] +DefaultMemoryLow=50 diff --git a/test/units/e.service b/test/units/e.service new file mode 100644 index 0000000..5bbcde2 --- /dev/null +++ b/test/units/e.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=E:Cyclic +After=b.service +Before=a.service +Wants=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/end.service b/test/units/end.service new file mode 100644 index 0000000..50a68b9 --- /dev/null +++ b/test/units/end.service @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=End the test +After=testsuite.target +OnFailure=poweroff.target +OnFailureJobMode=replace-irreversibly + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/end.sh +TimeoutStartSec=5m diff --git a/test/units/end.sh b/test/units/end.sh new file mode 100755 index 0000000..230b716 --- /dev/null +++ b/test/units/end.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +(! journalctl -q -o short-monotonic --grep "didn't pass validation" >>/failed) + +# Here, the redundant '[.]' at the end is for making not the logged self command hit the grep. +(! journalctl -q -o short-monotonic --grep 'Attempted to close sd-bus after fork whose connection is opened before the fork, this should not happen[.]' >>/failed) + +systemctl poweroff --no-block +exit 0 diff --git a/test/units/f.service b/test/units/f.service new file mode 100644 index 0000000..ca20053 --- /dev/null +++ b/test/units/f.service @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=F + +[Service] +ExecStart=/bin/true diff --git a/test/units/g.service b/test/units/g.service new file mode 100644 index 0000000..5fd794d --- /dev/null +++ b/test/units/g.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=G +Conflicts=e.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/generator-utils.sh b/test/units/generator-utils.sh new file mode 100755 index 0000000..fb62747 --- /dev/null +++ b/test/units/generator-utils.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +link_endswith() { + [[ -h "${1:?}" && "$(readlink "${1:?}")" =~ ${2:?}$ ]] +} + +link_eq() { + [[ -h "${1:?}" && "$(readlink "${1:?}")" == "${2:?}" ]] +} + +# Get the value from a 'key=value' assignment +opt_get_arg() { + local arg + + IFS="=" read -r _ arg <<< "${1:?}" + test -n "$arg" + echo "$arg" +} + +in_initrd() { + [[ "${SYSTEMD_IN_INITRD:-0}" -ne 0 ]] +} + +# Check if we're parsing host's fstab in initrd +in_initrd_host() { + in_initrd && [[ "${SYSTEMD_SYSROOT_FSTAB:-/dev/null}" != /dev/null ]] +} + +in_container() { + systemd-detect-virt -qc +} + +opt_filter() ( + set +x + local opt split_options filtered_options + + IFS="," read -ra split_options <<< "${1:?}" + for opt in "${split_options[@]}"; do + if [[ "$opt" =~ ${2:?} ]]; then + continue + fi + + filtered_options+=("$opt") + done + + IFS=","; printf "%s" "${filtered_options[*]}" +) + +# Run the given generator $1 with target directory $2 - clean the target +# directory beforehand +run_and_list() { + local generator="${1:?}" + local out_dir="${2:?}" + local environ + + # If $PID1_ENVIRON is set temporarily overmount /proc/1/environ with + # a temporary file that contains contents of $PID1_ENVIRON. This is + # necessary in cases where the generator reads the environment through + # getenv_for_pid(1, ...) or similar like getty-generator does. + # + # Note: $PID1_ENVIRON should be a NUL separated list of env assignments + if [[ -n "${PID1_ENVIRON:-}" ]]; then + environ="$(mktemp)" + echo -ne "${PID1_ENVIRON}\0" >"${environ:?}" + mount -v --bind "$environ" /proc/1/environ + fi + + rm -fr "${out_dir:?}"/* + mkdir -p "$out_dir"/{normal,early,late} + SYSTEMD_LOG_LEVEL="${SYSTEMD_LOG_LEVEL:-debug}" "$generator" "$out_dir/normal" "$out_dir/early" "$out_dir/late" + ls -lR "$out_dir" + + if [[ -n "${environ:-}" ]]; then + umount /proc/1/environ + rm -f "$environ" + fi +} diff --git a/test/units/grandchild.service b/test/units/grandchild.service new file mode 100644 index 0000000..4fe77b4 --- /dev/null +++ b/test/units/grandchild.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Grandchild Service + +[Service] +Slice=parent-deep.slice +Type=oneshot +ExecStart=/bin/true diff --git a/test/units/h.service b/test/units/h.service new file mode 100644 index 0000000..5361d42 --- /dev/null +++ b/test/units/h.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=H +Wants=g.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/i.service b/test/units/i.service new file mode 100644 index 0000000..2b5e821 --- /dev/null +++ b/test/units/i.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=I +Conflicts=a.service d.service +Wants=b.service +After=b.service + +[Service] +ExecStart=/bin/true diff --git a/test/units/loopy.service b/test/units/loopy.service new file mode 100644 index 0000000..7fc0e42 --- /dev/null +++ b/test/units/loopy.service @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +ExecStart=/bin/true diff --git a/test/units/loopy.service.d/compat.conf b/test/units/loopy.service.d/compat.conf new file mode 100644 index 0000000..53d213c --- /dev/null +++ b/test/units/loopy.service.d/compat.conf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +BindsTo=loopy2.service + +[Install] +Also=loopy2.service diff --git a/test/units/loopy2.service b/test/units/loopy2.service new file mode 100644 index 0000000..7fc0e42 --- /dev/null +++ b/test/units/loopy2.service @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +ExecStart=/bin/true diff --git a/test/units/loopy3.service b/test/units/loopy3.service new file mode 100644 index 0000000..b2af20a --- /dev/null +++ b/test/units/loopy3.service @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +ExecStart=/bin/true + +[Unit] +Conflicts=loopy4.service diff --git a/test/units/loopy4.service b/test/units/loopy4.service new file mode 100644 index 0000000..b2af20a --- /dev/null +++ b/test/units/loopy4.service @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +ExecStart=/bin/true + +[Unit] +Conflicts=loopy4.service diff --git a/test/units/nomem.slice b/test/units/nomem.slice new file mode 100644 index 0000000..f4837da --- /dev/null +++ b/test/units/nomem.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Nomem Parent Slice + +[Slice] +DisableControllers=memory diff --git a/test/units/nomemleaf.service b/test/units/nomemleaf.service new file mode 100644 index 0000000..14ce5ad --- /dev/null +++ b/test/units/nomemleaf.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Nomem Leaf Service + +[Service] +Slice=nomem.slice +Type=oneshot +ExecStart=/bin/true +IOWeight=200 +MemoryAccounting=true diff --git a/test/units/parent-deep.slice b/test/units/parent-deep.slice new file mode 100644 index 0000000..983ed65 --- /dev/null +++ b/test/units/parent-deep.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Deeper Parent Slice + +[Slice] +MemoryLimit=3G diff --git a/test/units/parent.slice b/test/units/parent.slice new file mode 100644 index 0000000..f49530b --- /dev/null +++ b/test/units/parent.slice @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Parent Slice + +[Slice] +IOWeight=200 diff --git a/test/units/sched_idle_bad.service b/test/units/sched_idle_bad.service new file mode 100644 index 0000000..be8f1c2 --- /dev/null +++ b/test/units/sched_idle_bad.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Bad sched priority for Idle + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=1 diff --git a/test/units/sched_idle_ok.service b/test/units/sched_idle_ok.service new file mode 100644 index 0000000..5a1d809 --- /dev/null +++ b/test/units/sched_idle_ok.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Sched idle with prio 0 + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=0 diff --git a/test/units/sched_rr_bad.service b/test/units/sched_rr_bad.service new file mode 100644 index 0000000..b51b868 --- /dev/null +++ b/test/units/sched_rr_bad.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Bad sched priority for RR + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=-1 +CPUSchedulingPriority=100 +CPUSchedulingPolicy=rr diff --git a/test/units/sched_rr_change.service b/test/units/sched_rr_change.service new file mode 100644 index 0000000..6ae1feb --- /dev/null +++ b/test/units/sched_rr_change.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Change prio + +[Service] +ExecStart=/bin/true +CPUSchedulingPriority=1 +CPUSchedulingPriority=2 +CPUSchedulingPriority=99 +CPUSchedulingPolicy=rr diff --git a/test/units/sched_rr_ok.service b/test/units/sched_rr_ok.service new file mode 100644 index 0000000..00b9822 --- /dev/null +++ b/test/units/sched_rr_ok.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Default prio for RR + +[Service] +ExecStart=/bin/true +CPUSchedulingPolicy=rr diff --git a/test/units/shutdown.target b/test/units/shutdown.target new file mode 100644 index 0000000..582ae6b --- /dev/null +++ b/test/units/shutdown.target @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Shutdown +Documentation=man:systemd.special(7) +DefaultDependencies=no +RefuseManualStart=yes diff --git a/test/units/sockets.target b/test/units/sockets.target new file mode 100644 index 0000000..c6e20d7 --- /dev/null +++ b/test/units/sockets.target @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Sockets +Documentation=man:systemd.special(7) diff --git a/test/units/son.service b/test/units/son.service new file mode 100644 index 0000000..2059118 --- /dev/null +++ b/test/units/son.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Son Service + +[Service] +Slice=parent.slice +Type=oneshot +ExecStart=/bin/true +CPUShares=100 diff --git a/test/units/success-failure-test-failure.service b/test/units/success-failure-test-failure.service new file mode 100644 index 0000000..f4ce013 --- /dev/null +++ b/test/units/success-failure-test-failure.service @@ -0,0 +1,3 @@ +[Service] +Type=notify +ExecStart=bash -c "echo failure >> /tmp/success-failure-test-result && systemd-notify --ready && sleep infinity" diff --git a/test/units/success-failure-test-success.service b/test/units/success-failure-test-success.service new file mode 100644 index 0000000..8503c45 --- /dev/null +++ b/test/units/success-failure-test-success.service @@ -0,0 +1,3 @@ +[Service] +Type=notify +ExecStart=bash -c "echo success >> /tmp/success-failure-test-result && systemd-notify --ready && sleep infinity" diff --git a/test/units/success-failure-test.service b/test/units/success-failure-test.service new file mode 100644 index 0000000..f66ff6c --- /dev/null +++ b/test/units/success-failure-test.service @@ -0,0 +1,9 @@ +[Unit] +OnSuccess=success-failure-test-success.service +OnFailure=success-failure-test-failure.service + +[Service] +Type=notify +Restart=always +ExecStart=bash -c 'test -f /tmp/success-failure-test-ran && touch /tmp/success-failure-test-ran2 && systemd-notify --ready && sleep infinity' +ExecStopPost=touch /tmp/success-failure-test-ran diff --git a/test/units/sysinit.target b/test/units/sysinit.target new file mode 100644 index 0000000..eed3d16 --- /dev/null +++ b/test/units/sysinit.target @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=System Initialization +Documentation=man:systemd.special(7) +Conflicts=emergency.service emergency.target +Wants=local-fs.target swap.target +After=local-fs.target swap.target emergency.service emergency.target diff --git a/test/units/test-control.sh b/test/units/test-control.sh new file mode 100644 index 0000000..0a1611b --- /dev/null +++ b/test/units/test-control.sh @@ -0,0 +1,166 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck shell=bash + +if [[ "${BASH_SOURCE[0]}" -ef "$0" ]]; then + echo >&2 "This file should not be executed directly" + exit 1 +fi + +declare -i _CHILD_PID=0 +_PASSED_TESTS=() + +# Like trap, but passes the signal name as the first argument +_trap_with_sig() { + local fun="${1:?}" + local sig + shift + + for sig in "$@"; do + # shellcheck disable=SC2064 + trap "$fun $sig" "$sig" + done +} + +# Propagate the caught signal to the current child process +_handle_signal() { + local sig="${1:?}" + + if [[ $_CHILD_PID -gt 0 ]]; then + echo "Propagating signal $sig to child process $_CHILD_PID" + kill -s "$sig" "$_CHILD_PID" + fi +} + +# In order to make the _handle_signal() stuff above work, we have to execute +# each script asynchronously, since bash won't execute traps until the currently +# executed command finishes. This, however, introduces another issue regarding +# how bash's wait works. Quoting: +# +# When bash is waiting for an asynchronous command via the wait builtin, +# the reception of a signal for which a trap has been set will cause the wait +# builtin to return immediately with an exit status greater than 128, +# immediately after which the trap is executed. +# +# In other words - every time we propagate a signal, wait returns with +# 128+signal, so we have to wait again - repeat until the process dies. +_wait_harder() { + local pid="${1:?}" + + while kill -0 "$pid" &>/dev/null; do + wait "$pid" || : + done + + wait "$pid" +} + +_show_summary() {( + set +x + + if [[ ${#_PASSED_TESTS[@]} -eq 0 ]]; then + echo >&2 "No tests were executed, this is most likely an error" + exit 1 + fi + + printf "PASSED TESTS: %3d:\n" "${#_PASSED_TESTS[@]}" + echo "------------------" + for t in "${_PASSED_TESTS[@]}"; do + echo "$t" + done +)} + +# Like run_subtests, but propagate specified signals to the subtest script +run_subtests_with_signals() { + local subtests=("${0%.sh}".*.sh) + local subtest + + if [[ "${#subtests[@]}" -eq 0 ]]; then + echo >&2 "No subtests found for file $0" + exit 1 + fi + + if [[ "$#" -eq 0 ]]; then + echo >&2 "No signals to propagate were specified" + exit 1 + fi + + _trap_with_sig _handle_signal "$@" + + for subtest in "${subtests[@]}"; do + if [[ -n "${TEST_MATCH_SUBTEST:-}" ]] && ! [[ "$subtest" =~ $TEST_MATCH_SUBTEST ]]; then + echo "Skipping $subtest (not matching '$TEST_MATCH_SUBTEST')" + continue + fi + + : "--- $subtest BEGIN ---" + SECONDS=0 + "./$subtest" & + _CHILD_PID=$! + if ! _wait_harder "$_CHILD_PID"; then + echo "Subtest $subtest failed" + return 1 + fi + + _PASSED_TESTS+=("$subtest") + : "--- $subtest END (${SECONDS}s) ---" + done + + _show_summary +} + +# Run all subtests (i.e. files named as testsuite-..sh) +run_subtests() { + local subtests=("${0%.sh}".*.sh) + local subtest + + if [[ "${#subtests[@]}" -eq 0 ]]; then + echo >&2 "No subtests found for file $0" + exit 1 + fi + + for subtest in "${subtests[@]}"; do + if [[ -n "${TEST_MATCH_SUBTEST:-}" ]] && ! [[ "$subtest" =~ $TEST_MATCH_SUBTEST ]]; then + echo "Skipping $subtest (not matching '$TEST_MATCH_SUBTEST')" + continue + fi + + : "--- $subtest BEGIN ---" + SECONDS=0 + if ! "./$subtest"; then + echo "Subtest $subtest failed" + return 1 + fi + + _PASSED_TESTS+=("$subtest") + : "--- $subtest END (${SECONDS}s) ---" + done + + _show_summary +} + +# Run all test cases (i.e. functions prefixed with testcase_ in the current namespace) +run_testcases() { + local testcase testcases + + # Create a list of all functions prefixed with testcase_ + mapfile -t testcases < <(declare -F | awk '$3 ~ /^testcase_/ {print $3;}') + + if [[ "${#testcases[@]}" -eq 0 ]]; then + echo >&2 "No test cases found, this is most likely an error" + exit 1 + fi + + for testcase in "${testcases[@]}"; do + if [[ -n "${TEST_MATCH_TESTCASE:-}" ]] && ! [[ "$testcase" =~ $TEST_MATCH_TESTCASE ]]; then + echo "Skipping $testcase (not matching '$TEST_MATCH_TESTCASE')" + continue + fi + + : "+++ $testcase BEGIN +++" + # Note: the subshell here is used purposefully, otherwise we might + # unexpectedly inherit a RETURN trap handler from the called + # function and call it for the second time once we return, + # causing a "double-free" + ("$testcase") + : "+++ $testcase END +++" + done +} diff --git a/test/units/testsuite-01.service b/test/units/testsuite-01.service new file mode 100644 index 0000000..9074e09 --- /dev/null +++ b/test/units/testsuite-01.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-01-BASIC +After=multi-user.target +Wants=systemd-resolved.service systemd-networkd.service + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-01.sh b/test/units/testsuite-01.sh new file mode 100755 index 0000000..870b62d --- /dev/null +++ b/test/units/testsuite-01.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Check if the colored --version output behaves correctly +SYSTEMD_COLORS=256 systemctl --version + +# Check if we properly differentiate between a full systemd setup and a "light" +# version of it that's done during daemon-reexec +# +# See: https://github.com/systemd/systemd/issues/27106 +if systemd-detect-virt -q --container; then + # We initialize /run/systemd/container only during a full setup + test -e /run/systemd/container + cp -afv /run/systemd/container /tmp/container + rm -fv /run/systemd/container + systemctl daemon-reexec + test ! -e /run/systemd/container + cp -afv /tmp/container /run/systemd/container +else + # We bring the loopback netdev up only during a full setup, so it should + # not get brought back up during reexec if we disable it beforehand + [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]] + ip link set lo down + [[ "$(ip -o link show lo)" =~ state\ DOWN ]] + systemctl daemon-reexec + [[ "$(ip -o link show lo)" =~ state\ DOWN ]] + ip link set lo up + + # We also disable coredumps only during a full setup + sysctl -w kernel.core_pattern=dont-overwrite-me + systemctl daemon-reexec + diff <(echo dont-overwrite-me) <(sysctl --values kernel.core_pattern) +fi + +# Collect failed units & do one daemon-reload to a basic sanity check +systemctl --state=failed --no-legend --no-pager | tee /failed +test ! -s /failed +systemctl daemon-reload + +# Check that the early setup is actually skipped on reexec. +# If the early setup is done more than once, then several timestamps, +# e.g. SecurityStartTimestamp, are re-initialized, and causes an ABRT +# of systemd-analyze blame. See issue #27187. +systemd-analyze blame + +# Test for 'systemd-update-utmp runlevel' vs 'systemctl daemon-reexec'. +# See issue #27163. +# shellcheck disable=SC2034 +for _ in {0..10}; do + systemctl daemon-reexec & + pid_reexec=$! + # shellcheck disable=SC2034 + for _ in {0..10}; do + SYSTEMD_LOG_LEVEL=debug /usr/lib/systemd/systemd-update-utmp runlevel + done + wait "$pid_reexec" +done + +touch /testok diff --git a/test/units/testsuite-02.service b/test/units/testsuite-02.service new file mode 100644 index 0000000..dea2c4f --- /dev/null +++ b/test/units/testsuite-02.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-02-UNITTESTS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-02.sh b/test/units/testsuite-02.sh new file mode 100755 index 0000000..2a3cb08 --- /dev/null +++ b/test/units/testsuite-02.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! systemd-detect-virt -qc && [[ "${TEST_CMDLINE_NEWLINE:-}" != bar ]]; then + cat /proc/cmdline + echo >&2 "Expected TEST_CMDLINE_NEWLINE=bar from the kernel command line" + exit 1 +fi + +# If we're running with TEST_PREFER_NSPAWN=1 limit the set of tests we run +# in QEMU to only those that can't run in a container to avoid running +# the same tests again in a, most likely, very slow environment +if ! systemd-detect-virt -qc && [[ "${TEST_PREFER_NSPAWN:-0}" -ne 0 ]]; then + TESTS_GLOB="test-loop-block" +else + TESTS_GLOB=${TESTS_GLOB:-test-*} +fi + +NPROC=$(nproc) +MAX_QUEUE_SIZE=${NPROC:-2} +mapfile -t TEST_LIST < <(find /usr/lib/systemd/tests/unit-tests/ -maxdepth 1 -type f -name "${TESTS_GLOB}") + +# Reset state +rm -fv /failed /skipped /testok + +if ! systemd-detect-virt -qc; then + # Make sure ping works for unprivileged users (for test-bpf-firewall) + sysctl net.ipv4.ping_group_range="0 2147483647" +fi + +# Check & report test results +# Arguments: +# $1: test path +# $2: test exit code +report_result() { + if [[ $# -ne 2 ]]; then + echo >&2 "check_result: missing arguments" + exit 1 + fi + + local name="${1##*/}" + local ret=$2 + + if [[ $ret -ne 0 && $ret != 77 && $ret != 127 ]]; then + echo "$name failed with $ret" + echo "$name" >>/failed-tests + { + echo "--- $name begin ---" + cat "/$name.log" + echo "--- $name end ---" + } >>/failed + elif [[ $ret == 77 || $ret == 127 ]]; then + echo "$name skipped" + echo "$name" >>/skipped-tests + { + echo "--- $name begin ---" + cat "/$name.log" + echo "--- $name end ---" + } >>/skipped + else + echo "$name OK" + echo "$name" >>/testok + fi +} + +set +x +# Associative array for running tasks, where running[test-path]=PID +declare -A running=() +for task in "${TEST_LIST[@]}"; do + # If there's MAX_QUEUE_SIZE running tasks, keep checking the running queue + # until one of the tasks finishes, so we can replace it. + while [[ ${#running[@]} -ge $MAX_QUEUE_SIZE ]]; do + for key in "${!running[@]}"; do + if ! kill -0 "${running[$key]}" &>/dev/null; then + # Task has finished, report its result and drop it from the queue + wait "${running[$key]}" && ec=0 || ec=$? + report_result "$key" "$ec" + unset "running[$key]" + # Break from inner for loop and outer while loop to skip + # the sleep below when we find a free slot in the queue + break 2 + fi + done + + # Precisely* calculated constant to keep the spinlock from burning the CPU(s) + sleep 0.01 + done + + if [[ -x $task ]]; then + echo "Executing test '$task'" + log_file="/${task##*/}.log" + $task &>"$log_file" & + running[$task]=$! + fi +done + +# Wait for remaining running tasks +for key in "${!running[@]}"; do + echo "Waiting for test '$key' to finish" + wait "${running[$key]}" && ec=0 || ec=$? + report_result "$key" "$ec" + unset "running[$key]" +done + +set -x + +# Test logs are sometimes lost, as the system shuts down immediately after +journalctl --sync + +test ! -s /failed +touch /testok diff --git a/test/units/testsuite-03.service b/test/units/testsuite-03.service new file mode 100644 index 0000000..836f962 --- /dev/null +++ b/test/units/testsuite-03.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-03-JOBS +After=multi-user.target + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-03.sh b/test/units/testsuite-03.sh new file mode 100755 index 0000000..e3567c2 --- /dev/null +++ b/test/units/testsuite-03.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Simple test for that daemon-reexec works in container. +# See: https://github.com/systemd/systemd/pull/23883 +systemctl daemon-reexec + +# Test merging of a --job-mode=ignore-dependencies job into a previously +# installed job. + +systemctl start --no-block hello-after-sleep.target + +systemctl list-jobs >/root/list-jobs.txt +until grep 'sleep\.service.*running' /root/list-jobs.txt; do + systemctl list-jobs >/root/list-jobs.txt +done + +grep 'hello\.service.*waiting' /root/list-jobs.txt + +# This is supposed to finish quickly, not wait for sleep to finish. +START_SEC=$(date -u '+%s') +systemctl start --job-mode=ignore-dependencies hello +END_SEC=$(date -u '+%s') +ELAPSED=$((END_SEC-START_SEC)) + +test "$ELAPSED" -lt 3 + +# sleep should still be running, hello not. +systemctl list-jobs >/root/list-jobs.txt +grep 'sleep\.service.*running' /root/list-jobs.txt +grep 'hello\.service' /root/list-jobs.txt && exit 1 +systemctl stop sleep.service hello-after-sleep.target + +# Some basic testing that --show-transaction does something useful +(! systemctl is-active systemd-importd) +systemctl -T start systemd-importd +systemctl is-active systemd-importd +systemctl --show-transaction stop systemd-importd +(! systemctl is-active systemd-importd) + +# Test for a crash when enqueuing a JOB_NOP when other job already exists +systemctl start --no-block hello-after-sleep.target +# hello.service should still be waiting, so these try-restarts will collapse +# into NOPs. +systemctl try-restart --job-mode=fail hello.service +systemctl try-restart hello.service +systemctl stop hello.service sleep.service hello-after-sleep.target + +# TODO: add more job queueing/merging tests here. + +# Test that restart propagates to activating units +systemctl -T --no-block start always-activating.service +systemctl list-jobs | grep 'always-activating.service' +ACTIVATING_ID_PRE=$(systemctl show -P InvocationID always-activating.service) +systemctl -T start always-activating.socket # Wait for the socket to come up +systemctl -T restart always-activating.socket +ACTIVATING_ID_POST=$(systemctl show -P InvocationID always-activating.service) +[ "$ACTIVATING_ID_PRE" != "$ACTIVATING_ID_POST" ] || exit 1 + +# Test for irreversible jobs +systemctl start unstoppable.service + +# This is expected to fail with 'job cancelled' +systemctl stop unstoppable.service && exit 1 +# But this should succeed +systemctl stop --job-mode=replace-irreversibly unstoppable.service + +# We're going to shutdown soon. Let's see if it succeeds when +# there's an active service that tries to be unstoppable. +# Shutdown of the container/VM will hang if not. +systemctl start unstoppable.service + +# Test waiting for a started units to terminate again +cat </run/systemd/system/wait2.service +[Unit] +Description=Wait for 2 seconds +[Service] +ExecStart=/bin/sh -ec 'sleep 2' +EOF +cat </run/systemd/system/wait5fail.service +[Unit] +Description=Wait for 5 seconds and fail +[Service] +ExecStart=/bin/sh -ec 'sleep 5; false' +EOF + +# wait2 succeeds +START_SEC=$(date -u '+%s') +systemctl start --wait wait2.service +END_SEC=$(date -u '+%s') +ELAPSED=$((END_SEC-START_SEC)) +[[ "$ELAPSED" -ge 2 ]] && [[ "$ELAPSED" -le 4 ]] || exit 1 + +# wait5fail fails, so systemctl should fail +START_SEC=$(date -u '+%s') +(! systemctl start --wait wait2.service wait5fail.service) +END_SEC=$(date -u '+%s') +ELAPSED=$((END_SEC-START_SEC)) +[[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1 + +# Test time-limited scopes +START_SEC=$(date -u '+%s') +set +e +systemd-run --scope --property=RuntimeMaxSec=3s sleep 10 +RESULT=$? +END_SEC=$(date -u '+%s') +ELAPSED=$((END_SEC-START_SEC)) +[[ "$ELAPSED" -ge 3 ]] && [[ "$ELAPSED" -le 5 ]] || exit 1 +[[ "$RESULT" -ne 0 ]] || exit 1 + +# Test transactions with cycles +# Provides coverage for issues like https://github.com/systemd/systemd/issues/26872 +for i in {0..19}; do + cat >"/run/systemd/system/transaction-cycle$i.service" <"/run/systemd/system/$unit.d/$override_name.conf" + systemctl daemon-reload +} + +run_service_and_fetch_logs() { + local unit="${1:?}" + local start end + + start="$(date '+%Y-%m-%d %T.%6N')" + systemctl restart "$unit" + sleep .5 + journalctl --sync + end="$(date '+%Y-%m-%d %T.%6N')" + + journalctl -q -u "$unit" -S "$start" -U "$end" -p notice + systemctl stop "$unit" +} + +if cgroupfs_supports_user_xattrs; then + # Accept all log messages + add_logs_filtering_override "logs-filtering.service" "00-reset" "" + [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]] + + add_logs_filtering_override "logs-filtering.service" "01-allow-all" ".*" + [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Discard all log messages + add_logs_filtering_override "logs-filtering.service" "02-discard-all" "~.*" + [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Accept all test messages + add_logs_filtering_override "logs-filtering.service" "03-reset" "" + [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Discard all test messages + add_logs_filtering_override "logs-filtering.service" "04-discard-gg" "~.*gg.*" + [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Deny filter takes precedence + add_logs_filtering_override "logs-filtering.service" "05-allow-all-but-too-late" ".*" + [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Use tilde in a deny pattern + add_logs_filtering_override "logs-filtering.service" "06-reset" "" + add_logs_filtering_override "logs-filtering.service" "07-prevent-tilde" "~~more~" + [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Only allow a pattern that won't be matched + add_logs_filtering_override "logs-filtering.service" "08-reset" "" + add_logs_filtering_override "logs-filtering.service" "09-allow-only-non-existing" "non-existing string" + [[ -z $(run_service_and_fetch_logs "logs-filtering.service") ]] + + # Allow a pattern starting with a tilde + add_logs_filtering_override "logs-filtering.service" "10-allow-with-escape-char" "\\\\x7emore~" + [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]] + + add_logs_filtering_override "logs-filtering.service" "11-reset" "" + add_logs_filtering_override "logs-filtering.service" "12-allow-with-spaces" "foo bar" + [[ -n $(run_service_and_fetch_logs "logs-filtering.service") ]] + + add_logs_filtering_override "delegated-cgroup-filtering.service" "00-allow-all" ".*" + [[ -n $(run_service_and_fetch_logs "delegated-cgroup-filtering.service") ]] + + add_logs_filtering_override "delegated-cgroup-filtering.service" "01-discard-hello" "~hello" + [[ -z $(run_service_and_fetch_logs "delegated-cgroup-filtering.service") ]] + + rm -rf /run/systemd/system/{logs-filtering,delegated-cgroup-filtering}.service.d +fi diff --git a/test/units/testsuite-04.SYSTEMD_JOURNAL_COMPRESS.sh b/test/units/testsuite-04.SYSTEMD_JOURNAL_COMPRESS.sh new file mode 100755 index 0000000..96d096d --- /dev/null +++ b/test/units/testsuite-04.SYSTEMD_JOURNAL_COMPRESS.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# https://bugzilla.redhat.com/show_bug.cgi?id=2183546 +mkdir /run/systemd/system/systemd-journald.service.d +MACHINE_ID="$(/run/systemd/system/systemd-journald.service.d/compress.conf <&1 | grep -q -F 'compress=${c}'; do sleep .5; done" + + # $SYSTEMD_JOURNAL_COMPRESS= also works for journal-remote + if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then + for cc in NONE XZ LZ4 ZSTD; do + rm -f /tmp/foo.journal + SYSTEMD_JOURNAL_COMPRESS="${cc}" /usr/lib/systemd/systemd-journal-remote --split-mode=none -o /tmp/foo.journal --getter="journalctl -b -o export -t $ID" + SYSTEMD_LOG_LEVEL=debug journalctl --verify --quiet --file /tmp/foo.journal 2>&1 | grep -q -F "compress=${cc}" + journalctl -t "$ID" -o cat --file /tmp/foo.journal | grep -q -F "hoge with ${c}" + done + fi +done + +rm /run/systemd/system/systemd-journald.service.d/compress.conf +systemctl daemon-reload +systemctl restart systemd-journald.service +systemctl reset-failed systemd-journald.service +journalctl --rotate diff --git a/test/units/testsuite-04.bsod.sh b/test/units/testsuite-04.bsod.sh new file mode 100755 index 0000000..30f0cb0 --- /dev/null +++ b/test/units/testsuite-04.bsod.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if systemd-detect-virt -cq; then + echo "This test requires a VM, skipping the test" + exit 0 +fi + +# shellcheck disable=SC2317 +at_exit() { + local EC=$? + + if [[ $EC -ne 0 ]] && [[ -e /tmp/console.dump ]]; then + cat /tmp/console.dump + fi + + if mountpoint -q /var/log/journal; then + journalctl --relinquish-var + umount /var/log/journal + journalctl --flush + fi + + return 0 +} + +vcs_dump_and_check() { + local expected_message="${1:?}" + + # It might take a while before the systemd-bsod stuff appears on the VCS, + # so try it a couple of times + for _ in {0..9}; do + setterm --term linux --dump --file /tmp/console.dump + if grep -aq "Press any key to exit" /tmp/console.dump && + grep -aq "$expected_message" /tmp/console.dump && + grep -aq "The current boot has failed" /tmp/console.dump; then + + return 0 + fi + + sleep .5 + done + + return 1 +} + +# Since systemd-bsod always fetches only the first emergency message from the +# current boot, let's temporarily overmount /var/log/journal with a tmpfs, +# as we're going to wipe it multiple times, but we need to keep the original +# journal intact for the other tests to work correctly. +trap at_exit EXIT +mount -t tmpfs tmpfs /var/log/journal +systemctl restart systemd-journald + +systemctl stop systemd-bsod + +# Since we just wiped the journal, there should be no emergency messages and +# systemd-bsod should be just a no-op +timeout 10s /usr/lib/systemd/systemd-bsod +setterm --term linux --dump --file /tmp/console.dump +(! grep "The current boot has failed" /tmp/console.dump) + +# systemd-bsod should pick up emergency messages only with UID=0, so let's check +# that as well +systemd-run --user --machine testuser@ --wait --pipe systemd-cat -p emerg echo "User emergency message" +systemd-cat -p emerg echo "Root emergency message" +journalctl --sync +# Set $SYSTEMD_COLORS so systemd-bsod also prints out the QR code +SYSTEMD_COLORS=256 /usr/lib/systemd/systemd-bsod & +PID=$! +vcs_dump_and_check "Root emergency message" +grep -aq "Scan the QR code" /tmp/console.dump +# TODO: check if systemd-bsod exits on a key press (didn't figure this one out yet) +kill $PID +timeout 10 bash -c "while kill -0 $PID; do sleep .5; done" + +# Wipe the journal +journalctl --vacuum-size=1 --rotate +(! journalctl -q -b -p emerg --grep .) + +# Check the systemd-bsod.service as well +# Note: the systemd-bsod.service unit has ConditionVirtualization=no, so let's +# temporarily override it just for the test +mkdir /run/systemd/system/systemd-bsod.service.d +printf '[Unit]\nConditionVirtualization=\n' >/run/systemd/system/systemd-bsod.service.d/99-override.conf +systemctl daemon-reload +systemctl start systemd-bsod +systemd-cat -p emerg echo "Service emergency message" +vcs_dump_and_check "Service emergency message" +systemctl stop systemd-bsod + +# Wipe the journal +journalctl --vacuum-size=1 --rotate +(! journalctl -q -b -p emerg --grep .) + +# Same as above, but make sure the service responds to signals even when there are +# no "emerg" messages, see systemd/systemd#30084 +(! systemctl is-active systemd-bsod) +systemctl start systemd-bsod +timeout 5s bash -xec 'until systemctl is-active systemd-bsod; do sleep .5; done' +timeout 5s systemctl stop systemd-bsod +timeout 5s bash -xec 'while systemctl is-active systemd-bsod; do sleep .5; done' diff --git a/test/units/testsuite-04.corrupted-journals.sh b/test/units/testsuite-04.corrupted-journals.sh new file mode 100755 index 0000000..2123b10 --- /dev/null +++ b/test/units/testsuite-04.corrupted-journals.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +JOURNAL_DIR="$(mktemp -d)" +REMOTE_OUT="$(mktemp -d)" +# tar on C8S doesn't support the --zstd option +unzstd --stdout "/test-journals/afl-corrupted-journals.tar.zst" | tar -xC "$JOURNAL_DIR/" +while read -r file; do + filename="${file##*/}" + unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}" +done < <(find /test-journals/corrupted/ -name "*.zst") +# First, try each of them sequentially. Skip this part when running with plain +# QEMU, as it is excruciatingly slow +# Note: we care only about exit code 124 (timeout) and special bash exit codes +# >124 (like signals) +if [[ "$(systemd-detect-virt -v)" != "qemu" ]]; then + while read -r file; do + timeout 10 journalctl --file="$file" --boot >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --verify >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --output=export >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --fields >/dev/null || [[ $? -lt 124 ]] + timeout 10 journalctl --file="$file" --list-boots >/dev/null || [[ $? -lt 124 ]] + if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then + timeout 10 /usr/lib/systemd/systemd-journal-remote \ + --getter="journalctl --file=$file --output=export" \ + --split-mode=none \ + --output="$REMOTE_OUT/system.journal" || [[ $? -lt 124 ]] + timeout 10 journalctl --directory="$REMOTE_OUT" >/dev/null || [[ $? -lt 124 ]] + rm -f "$REMOTE_OUT"/* + fi + done < <(find "$JOURNAL_DIR" -type f) +fi +# And now all at once +timeout 30 journalctl --directory="$JOURNAL_DIR" --boot >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --verify >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --output=export >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --fields >/dev/null || [[ $? -lt 124 ]] +timeout 30 journalctl --directory="$JOURNAL_DIR" --list-boots >/dev/null || [[ $? -lt 124 ]] +if [[ -x /usr/lib/systemd/systemd-journal-remote ]]; then + timeout 30 /usr/lib/systemd/systemd-journal-remote \ + --getter="journalctl --directory=$JOURNAL_DIR --output=export" \ + --split-mode=none \ + --output="$REMOTE_OUT/system.journal" || [[ $? -lt 124 ]] + timeout 30 journalctl --directory="$REMOTE_OUT" >/dev/null || [[ $? -lt 124 ]] + rm -f "$REMOTE_OUT"/* +fi diff --git a/test/units/testsuite-04.fss.sh b/test/units/testsuite-04.fss.sh new file mode 100755 index 0000000..03351b8 --- /dev/null +++ b/test/units/testsuite-04.fss.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Forward Secure Sealing + +if ! journalctl --version | grep -qF +GCRYPT; then + echo "Built without gcrypt, skipping the FSS tests" + exit 0 +fi + +journalctl --force --setup-keys --interval=2 |& tee /tmp/fss +FSS_VKEY="$(sed -rn '/([a-f0-9]{6}\-){3}[a-f0-9]{6}\/[a-f0-9]+\-[a-f0-9]+/p' /tmp/fss)" +[[ -n "$FSS_VKEY" ]] + +# Generate some buzz in the journal and wait until the FSS key is changed +# at least once +systemd-cat cat /etc/os-release +sleep 4 +# Seal the journal +journalctl --rotate +# Verification should fail without a valid FSS key +(! journalctl --verify) +(! journalctl --verify --verify-key="") +(! journalctl --verify --verify-key="000000-000000-000000-000000/00000000-00000") +# FIXME: ignore --verify result until #27532 is resolved +journalctl --verify --verify-key="$FSS_VKEY" || : + +# Sealing + systemd-journal-remote +/usr/lib/systemd/systemd-journal-remote --getter="journalctl -n 5 -o export" \ + --split-mode=none \ + --seal=yes \ + --output=/tmp/sealed.journal +(! journalctl --file=/tmp/sealed.journal --verify) +(! journalctl --file=/tmp/sealed.journal --verify --verify-key="") +(! journalctl --file=/tmp/sealed.journal --verify --verify-key="000000-000000-000000-000000/00000000-00000") +# FIXME: ignore --verify result until #27532 is resolved +journalctl --file=/tmp/sealed.journal --verify --verify-key="$FSS_VKEY" || : +rm -f /tmp/sealed.journal + +# Return back to a journal without FSS +rm -fv "/var/log/journal/$(Journal" +curl -Lfs http://localhost:19531/browse | grep -qF "Journal" +(! curl -Lfs http://localhost:19531/foo/bar/baz) +(! curl -Lfs http://localhost:19531/foo/../../../bar/../baz) + +# /entries +# Accept: text/plain should be the default +curl -Lfs http://localhost:19531/entries | \ + grep -qE " $TEST_TAG\[[0-9]+\]: $TEST_MESSAGE" +curl -Lfs --header "Accept: text/plain" http://localhost:19531/entries | \ + grep -qE " $TEST_TAG\[[0-9]+\]: $TEST_MESSAGE" +curl -Lfs --header "Accept: application/json" http://localhost:19531/entries | \ + jq -se ".[] | select(.MESSAGE == \"$TEST_MESSAGE\")" +curl -Lfs --header "Accept: application/json" http://localhost:19531/entries?boot | \ + jq -se ".[] | select(.MESSAGE == \"$TEST_MESSAGE\")" +curl -Lfs --header "Accept: application/json" http://localhost:19531/entries?SYSLOG_IDENTIFIER="$TEST_TAG" | \ + jq -se "length == 1 and select(.[].MESSAGE == \"$TEST_MESSAGE\")" +# Show 10 entries starting from $BOOT_CURSOR, skip the first 5 +curl -Lfs --header "Accept: application/json" --header "Range: entries=$BOOT_CURSOR:5:10" http://localhost:19531/entries | \ + jq -se "length == 10" +# Check if the specified cursor refers to an existing entry and return just that entry +curl -Lfs --header "Accept: application/json" --header "Range: entries=$TEST_CURSOR" http://localhost:19531/entries?discrete | \ + jq -se "length == 1 and select(.[].MESSAGE == \"$TEST_MESSAGE\")" +# No idea how to properly parse this (jq won't cut it), so let's at least do some sanity checks that every +# line is either empty or begins with data: +curl -Lfs --header "Accept: text/event-stream" http://localhost:19531/entries | \ + awk '!/^(data: \{.+\}|)$/ { exit 1; }' +# Same thing as journalctl --output=export +mkdir /tmp/remote-journal +curl -Lfs --header "Accept: application/vnd.fdo.journal" http://localhost:19531/entries | \ + /usr/lib/systemd/systemd-journal-remote --output=/tmp/remote-journal/system.journal --split-mode=none - +journalctl --directory=/tmp/remote-journal -t "$TEST_TAG" --grep "$TEST_MESSAGE" +rm -rf /tmp/remote-journal/* +# Let's do the same thing again, but let systemd-journal-remote spawn curl itself +/usr/lib/systemd/systemd-journal-remote --url=http://localhost:19531/entries \ + --output=/tmp/remote-journal/system.journal \ + --split-mode=none +journalctl --directory=/tmp/remote-journal -t "$TEST_TAG" --grep "$TEST_MESSAGE" +rm -rf /tmp/remote-journal + +# /machine +curl -Lfs http://localhost:19531/machine | jq + +# /fields +curl -Lfs http://localhost:19531/fields/MESSAGE | grep -qE -- "$TEST_MESSAGE" +curl -Lfs http://localhost:19531/fields/_TRANSPORT +(! curl -Lfs http://localhost:19531/fields) +(! curl -Lfs http://localhost:19531/fields/foo-bar-baz) + +systemctl stop systemd-journal-gatewayd.{socket,service} + +if ! command -v openssl >/dev/null; then + echo "openssl command not available, skipping the HTTPS tests" + exit 0 +fi + +# Generate a self-signed certificate for systemd-journal-gatewayd +# +# Note: older OpenSSL requires a config file with some extra options, unfortunately +cat >/tmp/openssl.conf <Journal" +curl -Lfsk https://localhost:19531/entries | \ + grep -qE " $TEST_TAG\[[0-9]+\]: $TEST_MESSAGE" +curl -Lfsk --header "Accept: application/json" https://localhost:19531/entries | \ + jq -se ".[] | select(.MESSAGE == \"$TEST_MESSAGE\")" +curl -Lfsk https://localhost:19531/machine | jq +curl -Lfsk https://localhost:19531/fields/_TRANSPORT + +kill "$GATEWAYD_PID" diff --git a/test/units/testsuite-04.journal-remote.sh b/test/units/testsuite-04.journal-remote.sh new file mode 100755 index 0000000..c7b99b1 --- /dev/null +++ b/test/units/testsuite-04.journal-remote.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +if [[ ! -x /usr/lib/systemd/systemd-journal-remote || ! -x /usr/lib/systemd/systemd-journal-upload ]]; then + echo "Built without systemd-journal-remote/upload support, skipping the test" + exit 0 +fi + +if ! command -v openssl >/dev/null; then + echo "openssl command not available, skipping the tests" + exit 0 +fi + +at_exit() { + set +e + + systemctl stop systemd-journal-upload + systemctl stop systemd-journal-remote.{socket,service} + # Remove any remote journals on exit, so we don't try to export them together + # with the local journals, causing a mess + rm -rf /var/log/journal/remote +} + +trap at_exit EXIT + +TEST_MESSAGE="-= This is a test message $RANDOM =-" +TEST_TAG="$(systemd-id128 new)" + +echo "$TEST_MESSAGE" | systemd-cat -t "$TEST_TAG" +journalctl --sync + +/usr/lib/systemd/systemd-journal-remote --version +/usr/lib/systemd/systemd-journal-remote --help +/usr/lib/systemd/systemd-journal-upload --version +/usr/lib/systemd/systemd-journal-upload --help + +# Generate a self-signed certificate for systemd-journal-remote +# +# Note: older OpenSSL requires a config file with some extra options, unfortunately +# Note2: /run here is used on purpose, since the systemd-journal-remote service uses PrivateTmp=yes +mkdir -p /run/systemd/journal-remote-tls +cat >/tmp/openssl.conf </run/systemd/journal-remote.conf.d/99-test.conf </run/systemd/journal-upload.conf.d/99-test.conf </run/systemd/remote-pki/ca.conf </run/systemd/remote-pki/client.conf </run/systemd/remote-pki/server.conf </run/systemd/remote-pki/ca.srl +# Generate a client key and signing request +openssl req -nodes -newkey rsa:2048 -sha256 \ + -config /run/systemd/remote-pki/client.conf \ + -keyout /run/systemd/remote-pki/client.key \ + -out /run/systemd/remote-pki/client.csr +# Sign the request with the CA key +openssl x509 -req -days 7 \ + -in /run/systemd/remote-pki/client.csr \ + -CA /run/systemd/remote-pki/ca.crt \ + -CAkey /run/systemd/remote-pki/ca.key \ + -out /run/systemd/remote-pki/client.crt +# And do the same for the server +openssl req -nodes -newkey rsa:2048 -sha256 \ + -config /run/systemd/remote-pki/server.conf \ + -keyout /run/systemd/remote-pki/server.key \ + -out /run/systemd/remote-pki/server.csr +openssl x509 -req -days 7 \ + -in /run/systemd/remote-pki/server.csr \ + -CA /run/systemd/remote-pki/ca.crt \ + -CAkey /run/systemd/remote-pki/ca.key \ + -out /run/systemd/remote-pki/server.crt +chown -R systemd-journal-remote:systemd-journal /run/systemd/remote-pki +chmod -R g+rwX /run/systemd/remote-pki + +# Reconfigure journal-upload/journal remote with the new keys +cat >/run/systemd/journal-remote.conf.d/99-test.conf </run/systemd/journal-upload.conf.d/99-test.conf </run/systemd/journal-upload.conf.d/99-test.conf </run/systemd/system/systemd-journal-upload.service.d/99-test.conf <\n<6>\n<7>\n" "" --level-prefix true +# Remove trailing spaces +write_and_match "Trailing spaces \t \n" "Trailing spaces\n" --level-prefix false +write_and_match "<5>Trailing spaces \t \n" "Trailing spaces\n" --level-prefix true +# Don't remove leading spaces +write_and_match " \t Leading spaces\n" " \t Leading spaces\n" --level-prefix false +write_and_match "<5> \t Leading spaces\n" " \t Leading spaces\n" --level-prefix true + +# --output-fields restricts output +ID="$(systemd-id128 new)" +echo -ne "foo" | systemd-cat -t "$ID" --level-prefix false +# Let's test varlinkctl a bit, i.e. implement the equivalent of 'journalctl --sync' via varlinkctl +varlinkctl call /run/systemd/journal/io.systemd.journal io.systemd.Journal.Synchronize '{}' +journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/tmp/output +[[ $(wc -l /dev/null + +# -b always behaves like -b0 +journalctl -q -b-1 -b0 | head -1 >/tmp/expected +journalctl -q -b-1 -b | head -1 >/tmp/output +diff /tmp/expected /tmp/output +# ... even when another option follows (both of these should fail due to -m) +{ journalctl -ball -b0 -m 2>&1 || :; } | head -1 >/tmp/expected +{ journalctl -ball -b -m 2>&1 || :; } | head -1 >/tmp/output +diff /tmp/expected /tmp/output + +# https://github.com/systemd/systemd/issues/13708 +ID=$(systemd-id128 new) +systemd-cat -t "$ID" bash -c 'echo parent; (echo child) & wait' & +PID=$! +wait $PID +journalctl --sync +# We can drop this grep when https://github.com/systemd/systemd/issues/13937 +# has a fix. +journalctl -b -o export -t "$ID" --output-fields=_PID | grep '^_PID=' >/tmp/output +[[ $(wc -l /tmp/expected +systemd-cat -t "$ID" /bin/sh -c 'env echo -n "This will";echo;env echo -n "usually fail";echo;env echo -n "and be truncated";echo;' +journalctl --sync +journalctl -b -o cat -t "$ID" >/tmp/output +diff /tmp/expected /tmp/output +[[ $(journalctl -b -o cat -t "$ID" --output-fields=_TRANSPORT | grep -Pc "^stdout$") -eq 3 ]] +[[ $(journalctl -b -o cat -t "$ID" --output-fields=_LINE_BREAK | grep -Pc "^pid-change$") -eq 3 ]] +[[ $(journalctl -b -o cat -t "$ID" --output-fields=_PID | sort -u | grep -c "^.*$") -eq 3 ]] +[[ $(journalctl -b -o cat -t "$ID" --output-fields=MESSAGE | grep -Pc "^(This will|usually fail|and be truncated)$") -eq 3 ]] + +# test that LogLevelMax can also suppress logging about services, not only by services +systemctl start silent-success +journalctl --sync +[[ -z "$(journalctl -b -q -u silent-success.service)" ]] + +# Exercise the matching machinery +SYSTEMD_LOG_LEVEL=debug journalctl -b -n 1 /dev/null /dev/zero /dev/null /dev/null /dev/null +journalctl -b -n 1 /bin/true /bin/false +journalctl -b -n 1 /bin/true + /bin/false +journalctl -b -n 1 -r --unit "systemd*" + +systemd-run --user -M "testuser@.host" /bin/echo hello +journalctl --sync +journalctl -b -n 1 -r --user-unit "*" + +(! journalctl -b /dev/lets-hope-this-doesnt-exist) +(! journalctl -b /dev/null /dev/zero /dev/this-also-shouldnt-exist) +(! journalctl -b --unit "this-unit-should-not-exist*") + +# Facilities & priorities +journalctl --facility help +journalctl --facility kern -n 1 +journalctl --facility syslog --priority 0..3 -n 1 +journalctl --facility syslog --priority 3..0 -n 1 +journalctl --facility user --priority 0..0 -n 1 +journalctl --facility daemon --priority warning -n 1 +journalctl --facility daemon --priority warning..info -n 1 +journalctl --facility daemon --priority notice..crit -n 1 +journalctl --facility daemon --priority 5..crit -n 1 + +# Assorted combinations +journalctl -o help +journalctl -q -n all -a | grep . >/dev/null +journalctl -q --no-full | grep . >/dev/null +journalctl -q --user --system | grep . >/dev/null +journalctl --namespace "*" | grep . >/dev/null +journalctl --namespace "" | grep . >/dev/null +journalctl -q --namespace "+foo-bar-baz-$RANDOM" | grep . >/dev/null +(! journalctl -q --namespace "foo-bar-baz-$RANDOM" | grep .) +journalctl --root / | grep . >/dev/null +journalctl --cursor "t=0;t=-1;t=0;t=0x0" | grep . >/dev/null +journalctl --header | grep system.journal +journalctl --field _EXE | grep . >/dev/null +journalctl --no-hostname --utc --catalog | grep . >/dev/null +# Exercise executable_is_script() and the related code, e.g. `journalctl -b /path/to/a/script.sh` should turn +# into ((_EXE=/bin/bash AND _COMM=script.sh) AND _BOOT_ID=c002e3683ba14fa8b6c1e12878386514) +journalctl -b "$(readlink -f "$0")" | grep . >/dev/null +journalctl -b "$(systemd-id128 boot-id)" | grep . >/dev/null +journalctl --since yesterday --reverse | grep . >/dev/null +journalctl --machine .host | grep . >/dev/null +# Log something that journald will forward to wall +echo "Oh no!" | systemd-cat -t "emerg$RANDOM" -p emerg --stderr-priority emerg + +TAG="$(systemd-id128 new)" +echo "Foo Bar Baz" | systemd-cat -t "$TAG" +journalctl --sync +# Relevant excerpt from journalctl(1): +# If the pattern is all lowercase, matching is case insensitive. Otherwise, matching is case sensitive. +# This can be overridden with the --case-sensitive option +journalctl -e -t "$TAG" --grep "Foo Bar Baz" +journalctl -e -t "$TAG" --grep "foo bar baz" +(! journalctl -e -t "$TAG" --grep "foo Bar baz") +journalctl -e -t "$TAG" --case-sensitive=false --grep "foo Bar baz" + +(! journalctl --facility hopefully-an-unknown-facility) +(! journalctl --priority hello-world) +(! journalctl --priority 0..128) +(! journalctl --priority 0..systemd) + +# Other options +journalctl --disk-usage +journalctl --dmesg -n 1 +journalctl --fields +journalctl --list-boots +journalctl --update-catalog +journalctl --list-catalog + +# Add new tests before here, the journald restarts below +# may make tests flappy. + +# Don't lose streams on restart +systemctl start forever-print-hola +sleep 3 +systemctl restart systemd-journald +sleep 3 +systemctl stop forever-print-hola +[[ ! -f "/tmp/i-lose-my-logs" ]] + +# https://github.com/systemd/systemd/issues/4408 +rm -f /tmp/i-lose-my-logs +systemctl start forever-print-hola +sleep 3 +systemctl kill --signal=SIGKILL systemd-journald +sleep 3 +[[ ! -f "/tmp/i-lose-my-logs" ]] +systemctl stop forever-print-hola + +set +o pipefail +# https://github.com/systemd/systemd/issues/15528 +journalctl --follow --file=/var/log/journal/*/* | head -n1 | grep . +# https://github.com/systemd/systemd/issues/24565 +journalctl --follow --merge | head -n1 | grep . +set -o pipefail + +# https://github.com/systemd/systemd/issues/26746 +rm -f /tmp/issue-26746-log /tmp/issue-26746-cursor +ID="$(systemd-id128 new)" +journalctl -t "$ID" --follow --cursor-file=/tmp/issue-26746-cursor | tee /tmp/issue-26746-log & +systemd-cat -t "$ID" /bin/sh -c 'echo hogehoge' +# shellcheck disable=SC2016 +timeout 10 bash -c 'until [[ -f /tmp/issue-26746-log && "$(cat /tmp/issue-26746-log)" =~ hogehoge ]]; do sleep .5; done' +pkill -TERM journalctl +timeout 10 bash -c 'until test -f /tmp/issue-26746-cursor; do sleep .5; done' +CURSOR_FROM_FILE="$(cat /tmp/issue-26746-cursor)" +CURSOR_FROM_JOURNAL="$(journalctl -t "$ID" --output=export MESSAGE=hogehoge | sed -n -e '/__CURSOR=/ { s/__CURSOR=//; p }')" +test "$CURSOR_FROM_FILE" = "$CURSOR_FROM_JOURNAL" + +# Check that the seqnum field at least superficially works +systemd-cat echo "ya" +journalctl --sync +SEQNUM1=$(journalctl -o export -n 1 | grep -Ea "^__SEQNUM=" | cut -d= -f2) +systemd-cat echo "yo" +journalctl --sync +SEQNUM2=$(journalctl -o export -n 1 | grep -Ea "^__SEQNUM=" | cut -d= -f2) +test "$SEQNUM2" -gt "$SEQNUM1" + +# Test for journals without RTC +# See: https://github.com/systemd/systemd/issues/662 +JOURNAL_DIR="$(mktemp -d)" +while read -r file; do + filename="${file##*/}" + unzstd "$file" -o "$JOURNAL_DIR/${filename%*.zst}" +done < <(find /test-journals/no-rtc -name "*.zst") + +journalctl --directory="$JOURNAL_DIR" --list-boots --output=json >/tmp/lb1 +diff -u /tmp/lb1 - <<'EOF' +[{"index":-3,"boot_id":"5ea5fc4f82a14186b5332a788ef9435e","first_entry":1666569600994371,"last_entry":1666584266223608},{"index":-2,"boot_id":"bea6864f21ad4c9594c04a99d89948b0","first_entry":1666569601005945,"last_entry":1666584347230411},{"index":-1,"boot_id":"4c708e1fd0744336be16f3931aa861fb","first_entry":1666569601017222,"last_entry":1666584354649355},{"index":0,"boot_id":"35e8501129134edd9df5267c49f744a4","first_entry":1666569601009823,"last_entry":1666584438086856}] +EOF +rm -rf "$JOURNAL_DIR" /tmp/lb1 + +# Check that using --after-cursor/--cursor-file= together with journal filters doesn't +# skip over entries matched by the filter +# See: https://github.com/systemd/systemd/issues/30288 +UNIT_NAME="test-cursor-$RANDOM.service" +CURSOR_FILE="$(mktemp)" +# Generate some messages we can match against +journalctl --cursor-file="$CURSOR_FILE" -n1 +systemd-run --unit="$UNIT_NAME" --wait --service-type=exec bash -xec "echo hello; echo world" +journalctl --sync +# --after-cursor= + --unit= +# The format of the "Starting ..." message depends on StatusUnitFormat=, so match only the beginning +# which should be enough in this case +[[ "$(journalctl -n 1 -p info -o cat --unit="$UNIT_NAME" --after-cursor="$(<"$CURSOR_FILE")" _PID=1 )" =~ ^Starting\ ]] +# There should be no such messages before the cursor +[[ -z "$(journalctl -n 1 -p info -o cat --unit="$UNIT_NAME" --after-cursor="$(<"$CURSOR_FILE")" --reverse)" ]] +# --cursor-file= + a journal filter +diff <(journalctl --cursor-file="$CURSOR_FILE" -p info -o cat _SYSTEMD_UNIT="$UNIT_NAME") - <$P/rlimits.conf <= 5.1 +# +# * in POSIX mode -c a -f options show values in 512-byte increments; let's hope +# we never run in the POSIX mode +systemd-run --wait --pipe "${ARGUMENTS[@]}" \ + bash -xec 'KB=1; MB=$((KB * 1024)); GB=$((MB * 1024)); TB=$((GB * 1024)); + : CPU; [[ $(ulimit -St) -eq 10 ]]; [[ $(ulimit -Ht) -eq 15 ]]; + : FSIZE; [[ $(ulimit -Sf) -eq $((96 * GB)) ]]; [[ $(ulimit -Hf) -eq $((96 * GB)) ]]; + : DATA; [[ $(ulimit -Sd) == unlimited ]]; [[ $(ulimit -Hd) == unlimited ]]; + : STACK; [[ $(ulimit -Ss) -eq $((8 * MB)) ]]; [[ $(ulimit -Hs) -eq $((8 * MB)) ]]; + : CORE; [[ $(ulimit -Sc) -eq $((17 * MB)) ]]; [[ $(ulimit -Hc) -eq $((17 * MB)) ]]; + : RSS; [[ $(ulimit -Sm) -eq $((27 * GB)) ]]; [[ $(ulimit -Hm) -eq $((27 * GB)) ]]; + : NOFILE; [[ $(ulimit -Sn) -eq 7 ]]; [[ $(ulimit -Hn) -eq 127 ]]; + : AS; [[ $(ulimit -Sv) == unlimited ]]; [[ $(ulimit -Hv) == unlimited ]]; + : NPROC; [[ $(ulimit -Su) -eq 64 ]]; [[ $(ulimit -Hu) == unlimited ]]; + : MEMLOCK; [[ $(ulimit -Sl) -eq $((37 * MB)) ]]; [[ $(ulimit -Hl) -eq $((37 * MB)) ]]; + : LOCKS; [[ $(ulimit -Sx) -eq 19 ]]; [[ $(ulimit -Hx) -eq 1021 ]]; + : SIGPENDING; [[ $(ulimit -Si) -eq 21 ]]; [[ $(ulimit -Hi) -eq 21 ]]; + : MSGQUEUE; [[ $(ulimit -Sq) -eq 666 ]]; [[ $(ulimit -Hq) -eq 666 ]]; + : NICE; [[ $(ulimit -Se) -eq 4 ]]; [[ $(ulimit -He) -eq 4 ]]; + : RTPRIO; [[ $(ulimit -Sr) -eq 8 ]]; [[ $(ulimit -Hr) -eq 8 ]]; + ulimit -R || exit 0; + : RTTIME; [[ $(ulimit -SR) -eq 666666 ]]; [[ $(ulimit -HR) -eq 666666 ]];' + +# RestrictFileSystems= +# +# Note: running instrumented binaries requires at least /proc to be accessible, so let's +# skip the test when we're running under sanitizers +# +# Note: $GCOV_ERROR_LOG is used during coverage runs to suppress errors when creating *.gcda files, +# since gcov can't access the restricted filesystem (as expected) +if [[ ! -v ASAN_OPTIONS ]] && systemctl --version | grep "+BPF_FRAMEWORK" && kernel_supports_lsm bpf; then + ROOTFS="$(df --output=fstype /usr/bin | sed --quiet 2p)" + systemd-run --wait --pipe -p RestrictFileSystems="" ls / + systemd-run --wait --pipe -p RestrictFileSystems="$ROOTFS foo bar" ls / + (! systemd-run --wait --pipe -p RestrictFileSystems="$ROOTFS" ls /proc) + (! systemd-run --wait --pipe -p GCOV_ERROR_LOG=/dev/null -p RestrictFileSystems="foo" ls /) + systemd-run --wait --pipe -p RestrictFileSystems="$ROOTFS foo bar baz proc" ls /proc + systemd-run --wait --pipe -p RestrictFileSystems="$ROOTFS @foo @basic-api" ls /proc + systemd-run --wait --pipe -p RestrictFileSystems="$ROOTFS @foo @basic-api" ls /sys/fs/cgroup + + systemd-run --wait --pipe -p RestrictFileSystems="~" ls / + systemd-run --wait --pipe -p RestrictFileSystems="~proc" ls / + systemd-run --wait --pipe -p RestrictFileSystems="~@basic-api" ls / + (! systemd-run --wait --pipe -p GCOV_ERROR_LOG=/dev/null -p RestrictFileSystems="~$ROOTFS" ls /) + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc" ls /proc) + (! systemd-run --wait --pipe -p RestrictFileSystems="~@basic-api" ls /proc) + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc foo @bar @basic-api" ls /proc) + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc foo @bar @basic-api" ls /sys) + systemd-run --wait --pipe -p RestrictFileSystems="~proc devtmpfs sysfs" ls / + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc devtmpfs sysfs" ls /proc) + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc devtmpfs sysfs" ls /dev) + (! systemd-run --wait --pipe -p RestrictFileSystems="~proc devtmpfs sysfs" ls /sys) +fi + +# Make sure we properly (de)serialize various string arrays, including whitespaces +# See: https://github.com/systemd/systemd/issues/31214 +systemd-run --wait --pipe -p Environment="FOO='bar4 '" \ + bash -xec '[[ $FOO == "bar4 " ]]' +systemd-run --wait --pipe -p Environment="FOO='bar4 ' BAR='\n\n'" \ + bash -xec "[[ \$FOO == 'bar4 ' && \$BAR == $'\n\n' ]]" +systemd-run --wait --pipe -p Environment='FOO="bar4 \\ "' -p Environment="BAR='\n\t'" \ + bash -xec "[[ \$FOO == 'bar4 \\ ' && \$BAR == $'\n\t' ]]" +TEST_ENV_FILE="/tmp/test-env-file-$RANDOM- " +cat >"$TEST_ENV_FILE" </run/systemd/system/my.service <<\EOF +[Service] +Type=oneshot +ExecStartPre=sh -c 'test "$TRIGGER_UNIT" = my.timer' +ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_REALTIME_USEC"' +ExecStartPre=sh -c 'test -n "$TRIGGER_TIMER_MONOTONIC_USEC"' +ExecStart=/bin/echo Timer runs me +EOF + +cat >/run/systemd/system/my.timer </run/systemd/system/my.timer.d/override.conf </run/systemd/system/badbin_assert.service </run/systemd/system/badbin_assert.socket <$U </run/systemd/system/issue-3171@.service </tmp/test-mainpid.sh <<\EOF +#!/usr/bin/env bash + +set -eux +set -o pipefail + +# Create a number of children, and make one the main one +sleep infinity & +disown + +sleep infinity & +MAINPID=$! +disown + +sleep infinity & +disown + +echo $MAINPID >/run/mainpidsh/pid +EOF +chmod +x /tmp/test-mainpid.sh + +systemd-run --unit=test-mainpidsh.service \ + -p StandardOutput=tty \ + -p StandardError=tty \ + -p Type=forking \ + -p RuntimeDirectory=mainpidsh \ + -p PIDFile=/run/mainpidsh/pid \ + /tmp/test-mainpid.sh +test "$(systemctl show -P MainPID test-mainpidsh.service)" -eq "$(cat /run/mainpidsh/pid)" + +cat >/tmp/test-mainpid2.sh <<\EOF +#!/usr/bin/env bash + +set -eux +set -o pipefail + +# Create a number of children, and make one the main one +sleep infinity & +disown + +sleep infinity & +MAINPID=$! +disown + +sleep infinity & +disown + +echo $MAINPID >/run/mainpidsh2/pid +chown 1001:1001 /run/mainpidsh2/pid +EOF +chmod +x /tmp/test-mainpid2.sh + +systemd-run --unit=test-mainpidsh2.service \ + -p StandardOutput=tty \ + -p StandardError=tty \ + -p Type=forking \ + -p RuntimeDirectory=mainpidsh2 \ + -p PIDFile=/run/mainpidsh2/pid \ + /tmp/test-mainpid2.sh +test "$(systemctl show -P MainPID test-mainpidsh2.service)" -eq "$(cat /run/mainpidsh2/pid)" + +cat >/dev/shm/test-mainpid3.sh <>"$TMP_MOUNTINFO" +mount --bind "$TMP_MOUNTINFO" /proc/1/mountinfo +systemctl daemon-reload +# On affected versions this would throw an error: +# Failed to get properties: Bad message +systemctl status foo-mountinfo.mount + +umount /proc/1/mountinfo +systemctl daemon-reload +rm -f "$TMP_MOUNTINFO" + +# Check invalid characters in a mount unit +# +# systemd already handles this and refuses to load the invalid string, e.g.: +# foo-fstab.mount:9: String is not UTF-8 clean, ignoring assignment: What=//localhost/foo���bar +# +# a) Unit generated from /etc/fstab +[[ -e /etc/fstab ]] && cp -f /etc/fstab /tmp/fstab.bak + +LANG="C.UTF-8" printf '//localhost/foo\ufffebar /foo/fstab cifs defaults 0 0\n' >/etc/fstab +systemctl daemon-reload +[[ "$(systemctl show -P UnitFileState foo-fstab.mount)" == bad ]] + +# b) Unit generated from /etc/fstab (but the invalid character is in options) +LANG="C.UTF-8" printf '//localhost/foobar /foo/fstab/opt cifs nosuid,a\ufffeb,noexec 0 0\n' >/etc/fstab +systemctl daemon-reload +[[ "$(systemctl show -P UnitFileState foo-fstab-opt.mount)" == bad ]] +rm -f /etc/fstab + +[[ -e /tmp/fstab.bak ]] && mv -f /tmp/fstab.bak /etc/fstab +systemctl daemon-reload + +# c) Mount unit +mkdir -p /run/systemd/system +LANG="C.UTF-8" printf '[Mount]\nWhat=//localhost/foo\ufffebar\nWhere=/foo/unit\nType=cifs\nOptions=noexec\n' >/run/systemd/system/foo-unit.mount +systemctl daemon-reload +[[ "$(systemctl show -P UnitFileState foo-unit.mount)" == bad ]] +rm -f /run/systemd/system/foo-unit.mount + +# d) Mount unit (but the invalid character is in Options=) +mkdir -p /run/systemd/system +LANG="C.UTF-8" printf '[Mount]\nWhat=//localhost/foobar\nWhere=/foo/unit/opt\nType=cifs\nOptions=noexec,a\ufffeb,nosuid\n' >/run/systemd/system/foo-unit-opt.mount +systemctl daemon-reload +[[ "$(systemctl show -P UnitFileState foo-unit-opt.mount)" == bad ]] +rm -f /run/systemd/system/foo-unit-opt.mount diff --git a/test/units/testsuite-07.poll-limit.sh b/test/units/testsuite-07.poll-limit.sh new file mode 100755 index 0000000..480d7ee --- /dev/null +++ b/test/units/testsuite-07.poll-limit.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemd-analyze log-level debug + +cat > /run/systemd/system/floodme@.service < /run/systemd/system/floodme.socket <&2 "This test can't run in a container" + exit 1 +fi + +# This test requires systemd to run in the initrd as well, which is not the case +# for mkinitrd-based initrd (Ubuntu/Debian) +if [[ "$(systemctl show -P InitRDTimestampMonotonic)" -eq 0 ]]; then + echo "systemd didn't run in the initrd, skipping the test" + touch /skipped + exit 0 +fi + +# We should've created a mount under /run in initrd (see the other half of the test) +# that should've survived the transition from initrd to the real system +test -d /run/initrd-mount-target +mountpoint /run/initrd-mount-target +[[ -e /run/initrd-mount-target/hello-world ]] + +# Copy the prepared shutdown initrd to its intended location. Check the respective +# test.sh file for details +mkdir -p /run/initramfs +cp -r /shutdown-initrd/* /run/initramfs/ + +touch /testok diff --git a/test/units/testsuite-09.journal.sh b/test/units/testsuite-09.journal.sh new file mode 100755 index 0000000..2ef192c --- /dev/null +++ b/test/units/testsuite-09.journal.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +get_first_boot_id() { + journalctl -b "${1:?}" -o json -n +1 | jq -r '._BOOT_ID' +} + +get_last_boot_id() { + journalctl -b "${1:?}" -o json -n 1 | jq -r '._BOOT_ID' +} + +get_first_timestamp() { + journalctl -b "${1:?}" -o json -n +1 | jq -r '.__REALTIME_TIMESTAMP' +} + +get_last_timestamp() { + journalctl -b "${1:?}" -o json -n 1 | jq -r '.__REALTIME_TIMESTAMP' +} + +# Issue: #29275, second part +# Now let's check if the boot entries are in the correct/expected order +index=0 +SYSTEMD_LOG_LEVEL=debug journalctl --list-boots +journalctl --list-boots -o json | jq -r '.[] | [.index, .boot_id, .first_entry, .last_entry] | @tsv' | + while read -r offset boot_id first_ts last_ts; do + : "Boot #$((++index)) ($offset) with ID $boot_id" + + # Try the "regular" (non-json) variants first, as they provide a helpful + # error message if something is not right + SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$index" + SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$offset" + SYSTEMD_LOG_LEVEL=debug journalctl -q -n 0 -b "$boot_id" + + # Check the boot ID of the first entry + entry_boot_id="$(get_first_boot_id "$index")" + assert_eq "$entry_boot_id" "$boot_id" + entry_boot_id="$(get_first_boot_id "$offset")" + assert_eq "$entry_boot_id" "$boot_id" + entry_boot_id="$(get_first_boot_id "$boot_id")" + assert_eq "$entry_boot_id" "$boot_id" + + # Check the timestamp of the first entry + entry_ts="$(get_first_timestamp "$index")" + assert_eq "$entry_ts" "$first_ts" + entry_ts="$(get_first_timestamp "$offset")" + assert_eq "$entry_ts" "$first_ts" + entry_ts="$(get_first_timestamp "$boot_id")" + assert_eq "$entry_ts" "$first_ts" + + # Check the boot ID of the last entry + entry_boot_id="$(get_last_boot_id "$index")" + assert_eq "$entry_boot_id" "$boot_id" + entry_boot_id="$(get_last_boot_id "$offset")" + assert_eq "$entry_boot_id" "$boot_id" + entry_boot_id="$(get_last_boot_id "$boot_id")" + assert_eq "$entry_boot_id" "$boot_id" + + # Check the timestamp of the last entry + if [[ "$offset" != "0" ]]; then + entry_ts="$(get_last_timestamp "$index")" + assert_eq "$entry_ts" "$last_ts" + entry_ts="$(get_last_timestamp "$offset")" + assert_eq "$entry_ts" "$last_ts" + entry_ts="$(get_last_timestamp "$boot_id")" + assert_eq "$entry_ts" "$last_ts" + fi + done diff --git a/test/units/testsuite-09.service b/test/units/testsuite-09.service new file mode 100644 index 0000000..6c957ec --- /dev/null +++ b/test/units/testsuite-09.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-09-REBOOT +After=multi-user.target + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-09.sh b/test/units/testsuite-09.sh new file mode 100755 index 0000000..cd95660 --- /dev/null +++ b/test/units/testsuite-09.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +NUM_REBOOT=4 + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +systemd-cat echo "Reboot count: $REBOOT_COUNT" +systemd-cat journalctl --list-boots + +run_subtests + +if [[ "$REBOOT_COUNT" -lt "$NUM_REBOOT" ]]; then + systemctl_final reboot +elif [[ "$REBOOT_COUNT" -gt "$NUM_REBOOT" ]]; then + assert_not_reached +fi + +touch /testok diff --git a/test/units/testsuite-13.machinectl.sh b/test/units/testsuite-13.machinectl.sh new file mode 100755 index 0000000..b5f90f6 --- /dev/null +++ b/test/units/testsuite-13.machinectl.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export PAGER= + +at_exit() { + set +e + + machinectl status long-running >/dev/null && machinectl kill --signal=KILL long-running + mountpoint -q /var/lib/machines && timeout 10 sh -c "until umount /var/lib/machines; do sleep .5; done" + [[ -n "${NSPAWN_FRAGMENT:-}" ]] && rm -f "/etc/systemd/nspawn/$NSPAWN_FRAGMENT" "/var/lib/machines/$NSPAWN_FRAGMENT" + rm -f /run/systemd/nspawn/*.nspawn +} + +trap at_exit EXIT + +# Mount tmpfs over /var/lib/machines to not pollute the image +mkdir -p /var/lib/machines +mount -t tmpfs tmpfs /var/lib/machines + +# Create a couple of containers we can refer to in tests +for i in {0..4}; do + create_dummy_container "/var/lib/machines/container$i" + machinectl start "container$i" +done +# Create one "long running" container with some basic signal handling +create_dummy_container /var/lib/machines/long-running +cat >/var/lib/machines/long-running/sbin/init <<\EOF +#!/usr/bin/bash -x + +PID=0 + +trap "touch /poweroff" RTMIN+4 +trap "touch /reboot" INT +trap "touch /trap" TRAP +trap 'kill $PID' EXIT + +# We need to wait for the sleep process asynchronously in order to allow +# bash to process signals +sleep infinity & +PID=$! +while :; do + wait || : +done +EOF +machinectl start long-running + +machinectl +machinectl --no-pager --help +machinectl --version +machinectl list +machinectl list --no-legend --no-ask-password + +machinectl status long-running long-running long-running +machinectl status --full long-running +machinectl status --quiet --lines=1 long-running +machinectl status --lines=0 --max-addresses=0 long-running +machinectl status --machine=testuser@.host long-running +machinectl status --output=help long-running +while read -r output; do + machinectl status --output="$output" long-running +done < <(machinectl --output=help) + +machinectl show +machinectl show --all +machinectl show --all --machine=root@ +machinectl show --all --machine=testuser@ +[[ "$(machinectl show --property=PoolPath --value)" == "/var/lib/machines" ]] +machinectl show long-running +machinectl show long-running long-running long-running --all +[[ "$(machinectl show --property=RootDirectory --value long-running)" == "/var/lib/machines/long-running" ]] + +machinectl enable long-running +test -L /etc/systemd/system/machines.target.wants/systemd-nspawn@long-running.service +machinectl enable long-running long-running long-running container1 +machinectl disable long-running +test ! -L /etc/systemd/system/machines.target.wants/systemd-nspawn@long-running.service +machinectl disable long-running long-running long-running container1 + +[[ "$(machinectl shell testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "" ]] +[[ "$(machinectl shell --setenv=FOO=bar testuser@ /usr/bin/bash -c 'echo -ne $FOO')" == "bar" ]] + +[[ "$(machinectl show --property=State --value long-running)" == "running" ]] +# Equivalent to machinectl kill --signal=SIGRTMIN+4 --kill-whom=leader +rm -f /var/lib/machines/long-running/poweroff +machinectl poweroff long-running +timeout 10 bash -c "until test -e /var/lib/machines/long-running/poweroff; do sleep .5; done" +# Equivalent to machinectl kill --signal=SIGINT --kill-whom=leader +rm -f /var/lib/machines/long-running/reboot +machinectl reboot long-running +timeout 10 bash -c "until test -e /var/lib/machines/long-running/reboot; do sleep .5; done" +# Skip machinectl terminate for now, as it doesn't play well with our "init" +rm -f /var/lib/machines/long-running/trap +machinectl kill --signal=SIGTRAP --kill-whom=leader long-running +timeout 10 bash -c "until test -e /var/lib/machines/long-running/trap; do sleep .5; done" +# Multiple machines at once +machinectl poweroff long-running long-running long-running +machinectl reboot long-running long-running long-running +machinectl kill --signal=SIGTRAP --kill-whom=leader long-running long-running long-running +# All used signals should've been caught by a handler +[[ "$(machinectl show --property=State --value long-running)" == "running" ]] + +cp /etc/machine-id /tmp/foo +machinectl copy-to long-running /tmp/foo /root/foo +test -f /var/lib/machines/long-running/root/foo +machinectl copy-from long-running /root/foo /tmp/bar +diff /tmp/foo /tmp/bar +rm -f /tmp/{foo,bar} + +# machinectl bind is covered by testcase_check_machinectl_bind() in nspawn tests + +machinectl list-images +machinectl list-images --no-legend +machinectl image-status +machinectl image-status container1 +machinectl image-status container1 container1 container{0..4} +machinectl show-image +machinectl show-image container1 +machinectl show-image container1 container1 container{0..4} + +machinectl clone container1 clone1 +machinectl show-image clone1 +machinectl rename clone1 clone2 +(! machinectl show-image clone1) +machinectl show-image clone2 +if lsattr -d /var/lib/machines >/dev/null; then + [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == no ]] + machinectl read-only clone2 yes + [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == yes ]] + machinectl read-only clone2 no + [[ "$(machinectl show-image --property=ReadOnly --value clone2)" == no ]] +fi +machinectl remove clone2 +for i in {0..4}; do + machinectl clone container1 "clone$i" +done +machinectl remove clone{0..4} +for i in {0..4}; do + machinectl clone container1 ".hidden$i" +done +machinectl list-images --all +test -d /var/lib/machines/.hidden1 +machinectl clean +test ! -d /var/lib/machines/.hidden1 + +# Prepare a simple raw container +mkdir -p /tmp/mnt +dd if=/dev/zero of=/tmp/container.raw bs=1M count=64 +mkfs.ext4 /tmp/container.raw +mount -o loop /tmp/container.raw /tmp/mnt +cp -r /var/lib/machines/container1/* /tmp/mnt +umount /tmp/mnt +# Try to import it, run it, export it, and re-import it +machinectl import-raw /tmp/container.raw container-raw +[[ "$(machinectl show-image --property=Type --value container-raw)" == "raw" ]] +machinectl start container-raw +machinectl export-raw container-raw /tmp/container-export.raw +machinectl import-raw /tmp/container-export.raw container-raw-reimport +[[ "$(machinectl show-image --property=Type --value container-raw-reimport)" == "raw" ]] +rm -f /tmp/container{,-export}.raw + +# Prepare a simple tar.gz container +tar -pczf /tmp/container.tar.gz -C /var/lib/machines/container1 . +# Try to import it, run it, export it, and re-import it +machinectl import-tar /tmp/container.tar.gz container-tar +[[ "$(machinectl show-image --property=Type --value container-tar)" == "directory" ]] +machinectl start container-tar +machinectl export-tar container-tar /tmp/container-export.tar.gz +machinectl import-tar /tmp/container-export.tar.gz container-tar-reimport +[[ "$(machinectl show-image --property=Type --value container-tar-reimport)" == "directory" ]] +rm -f /tmp/container{,-export}.tar.gz + +# Try to import a container directory & run it +cp -r /var/lib/machines/container1 /tmp/container.dir +machinectl import-fs /tmp/container.dir container-dir +[[ "$(machinectl show-image --property=Type --value container-dir)" == "directory" ]] +machinectl start container-dir +rm -fr /tmp/container.dir + +timeout 10 bash -c "until machinectl clean --all; do sleep .5; done" + +NSPAWN_FRAGMENT="machinectl-test-$RANDOM.nspawn" +cat >"/var/lib/machines/$NSPAWN_FRAGMENT" </tmp/fragment.nspawn <"$OCI/config.json" <"$OCI/config.json" </prestart" + ], + "env" : [ + "PRESTART_FOO=prestart_bar", + "ALSO_FOO=also_bar" + ], + "timeout" : 666 + }, + { + "path" : "/bin/touch", + "args" : [ + "/tmp/also-prestart" + ] + } + ], + "poststart" : [ + { + "path" : "/bin/sh", + "args" : [ + "touch", + "/poststart" + ] + } + ], + "poststop" : [ + { + "path" : "/bin/sh", + "args" : [ + "touch", + "/poststop" + ] + } + ] + }, + "annotations" : { + "hello.world" : "1", + "foo" : "bar" + } +} +EOF +# Create a simple "entrypoint" script that validates that the container +# is created correctly according to the OCI config +cat >"$OCI/rootfs/entrypoint.sh" <"$OCI/config.json" <"$OCI/config.json" <"$root/bin/getent" <<\EOF +#!/bin/bash + +if [[ $# -eq 0 ]]; then + : +elif [[ $1 == passwd ]]; then + echo "testuser:x:1000:1000:testuser:/:/bin/sh" +elif [[ $1 == initgroups ]]; then + echo "testuser" +fi +EOF + chmod +x "$root/bin/getent" + systemd-nspawn --directory="$root" bash -xec '[[ $USER == root ]]' + systemd-nspawn --directory="$root" --user=testuser bash -xec '[[ $USER == testuser ]]' + + # --settings= + .nspawn files + mkdir -p /run/systemd/nspawn/ + uuid="deadbeef-dead-dead-beef-000000000000" + echo -ne "[Exec]\nMachineID=deadbeef-dead-dead-beef-111111111111" >/run/systemd/nspawn/foo-bar.nspawn + systemd-nspawn --directory="$root" \ + --machine=foo-bar \ + --settings=yes \ + bash -xec '[[ $container_uuid == deadbeef-dead-dead-beef-111111111111 ]]' + systemd-nspawn --directory="$root" \ + --machine=foo-bar \ + --uuid="$uuid" \ + --settings=yes \ + bash -xec "[[ \$container_uuid == $uuid ]]" + systemd-nspawn --directory="$root" \ + --machine=foo-bar \ + --uuid="$uuid" \ + --settings=override \ + bash -xec '[[ $container_uuid == deadbeef-dead-dead-beef-111111111111 ]]' + systemd-nspawn --directory="$root" \ + --machine=foo-bar \ + --uuid="$uuid" \ + --settings=trusted \ + bash -xec "[[ \$container_uuid == $uuid ]]" + + # Mounts + mkdir "$tmpdir"/{1,2,3} + touch "$tmpdir/1/one" "$tmpdir/2/two" "$tmpdir/3/three" + touch "$tmpdir/foo" + # --bind= + systemd-nspawn --directory="$root" \ + ${COVERAGE_BUILD_DIR:+--bind="$COVERAGE_BUILD_DIR"} \ + --bind="$tmpdir:/foo" \ + --bind="$tmpdir:/also-foo:noidmap,norbind" \ + bash -xec 'test -e /foo/foo; touch /foo/bar; test -e /also-foo/bar' + test -e "$tmpdir/bar" + # --bind-ro= + systemd-nspawn --directory="$root" \ + --bind-ro="$tmpdir:/foo" \ + --bind-ro="$tmpdir:/bar:noidmap,norbind" \ + bash -xec 'test -e /foo/foo; touch /foo/baz && exit 1; touch /bar && exit 1; true' + # --inaccessible= + systemd-nspawn --directory="$root" \ + --inaccessible=/var \ + bash -xec 'touch /var/foo && exit 1; true' + # --tmpfs= + systemd-nspawn --directory="$root" \ + --tmpfs=/var:rw,nosuid,noexec \ + bash -xec 'touch /var/nope' + test ! -e "$root/var/nope" + # --overlay= + systemd-nspawn --directory="$root" \ + --overlay="$tmpdir/1:$tmpdir/2:$tmpdir/3:/var" \ + bash -xec 'test -e /var/one; test -e /var/two; test -e /var/three; touch /var/foo' + test -e "$tmpdir/3/foo" + # --overlay-ro= + systemd-nspawn --directory="$root" \ + --overlay-ro="$tmpdir/1:$tmpdir/2:$tmpdir/3:/var" \ + bash -xec 'test -e /var/one; test -e /var/two; test -e /var/three; touch /var/nope && exit 1; true' + test ! -e "$tmpdir/3/nope" + rm -fr "$tmpdir" + + # --port (sanity only) + systemd-nspawn --network-veth --directory="$root" --port=80 --port=90 true + systemd-nspawn --network-veth --directory="$root" --port=80:8080 true + systemd-nspawn --network-veth --directory="$root" --port=tcp:80 true + systemd-nspawn --network-veth --directory="$root" --port=tcp:80:8080 true + systemd-nspawn --network-veth --directory="$root" --port=udp:80 true + systemd-nspawn --network-veth --directory="$root" --port=udp:80:8080 --port=tcp:80:8080 true + (! systemd-nspawn --network-veth --directory="$root" --port= true) + (! systemd-nspawn --network-veth --directory="$root" --port=-1 true) + (! systemd-nspawn --network-veth --directory="$root" --port=: true) + (! systemd-nspawn --network-veth --directory="$root" --port=icmp:80:8080 true) + (! systemd-nspawn --network-veth --directory="$root" --port=tcp::8080 true) + (! systemd-nspawn --network-veth --directory="$root" --port=8080: true) + # Exercise adding/removing ports from an interface + systemd-nspawn --directory="$root" \ + --network-veth \ + --port=6667 \ + --port=80:8080 \ + --port=udp:53 \ + --port=tcp:22:2222 \ + bash -xec 'ip addr add dev host0 10.0.0.10/24; ip a; ip addr del dev host0 10.0.0.10/24' + + # --load-credential=, --set-credential= + echo "foo bar" >/tmp/cred.path + systemd-nspawn --directory="$root" \ + --load-credential=cred.path:/tmp/cred.path \ + --set-credential="cred.set:hello world" \ + bash -xec '[[ "$("/run/systemd/nspawn/$container.nspawn" <"$root/entrypoint.sh" <<\EOF +#!/bin/bash +set -ex + +env + +[[ "$1" == "foo bar" ]] +[[ "$2" == "bar baz" ]] + +[[ "$USER" == root ]] +[[ "$FOO" == bar ]] +[[ "$BAZ" == "hello world" ]] +[[ "$PWD" == /tmp ]] +[[ "$container_uuid" == f28f129b-5187-4b12-80a8-9421ec4b4ad4 ]] +[[ "$(ulimit -S -n)" -eq 1024 ]] +[[ "$(ulimit -H -n)" -eq 2048 ]] +[[ "$(ulimit -S -r)" -eq 8 ]] +[[ "$(ulimit -H -r)" -eq 16 ]] +[[ "$("/run/systemd/nspawn/$container.nspawn" <"$root/etc/passwd" + (! systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + true) + rm -f "$root/etc/passwd" + + echo "nspawn-bind-user-2:x:1000:" >"$root/etc/group" + (! systemd-nspawn --directory="$root" \ + --private-users=pick \ + --bind-user=nspawn-bind-user-1 \ + --bind-user=nspawn-bind-user-2 \ + true) + rm -f "$root/etc/group" + + rm -fr "$root" +} + +testcase_bind_tmp_path() { + # https://github.com/systemd/systemd/issues/4789 + local root + + root="$(mktemp -d /var/lib/machines/testsuite-13.bind-tmp-path.XXX)" + create_dummy_container "$root" + : >/tmp/bind + systemd-nspawn --register=no \ + --directory="$root" \ + --bind=/tmp/bind \ + bash -c 'test -e /tmp/bind' + + rm -fr "$root" /tmp/bind +} + +testcase_norbind() { + # https://github.com/systemd/systemd/issues/13170 + local root + + root="$(mktemp -d /var/lib/machines/testsuite-13.norbind-path.XXX)" + mkdir -p /tmp/binddir/subdir + echo -n "outer" >/tmp/binddir/subdir/file + mount -t tmpfs tmpfs /tmp/binddir/subdir + echo -n "inner" >/tmp/binddir/subdir/file + create_dummy_container "$root" + + systemd-nspawn --register=no \ + --directory="$root" \ + --bind=/tmp/binddir:/mnt:norbind \ + bash -c 'CONTENT=$(cat /mnt/subdir/file); if [[ $CONTENT != "outer" ]]; then echo "*** unexpected content: $CONTENT"; exit 1; fi' + + umount /tmp/binddir/subdir + rm -fr "$root" /tmp/binddir/ +} + +rootidmap_cleanup() { + local dir="${1:?}" + + mountpoint -q "$dir/bind" && umount "$dir/bind" + rm -fr "$dir" +} + +testcase_rootidmap() { + local root cmd permissions + local owner=1000 + + root="$(mktemp -d /var/lib/machines/testsuite-13.rootidmap-path.XXX)" + # Create ext4 image, as ext4 supports idmapped-mounts. + mkdir -p /tmp/rootidmap/bind + dd if=/dev/zero of=/tmp/rootidmap/ext4.img bs=4k count=2048 + mkfs.ext4 /tmp/rootidmap/ext4.img + mount /tmp/rootidmap/ext4.img /tmp/rootidmap/bind + trap "rootidmap_cleanup /tmp/rootidmap/" RETURN + + touch /tmp/rootidmap/bind/file + chown -R "$owner:$owner" /tmp/rootidmap/bind + + create_dummy_container "$root" + cmd='PERMISSIONS=$(stat -c "%u:%g" /mnt/file); if [[ $PERMISSIONS != "0:0" ]]; then echo "*** wrong permissions: $PERMISSIONS"; return 1; fi; touch /mnt/other_file' + if ! SYSTEMD_LOG_TARGET=console \ + systemd-nspawn --register=no \ + --directory="$root" \ + --bind=/tmp/rootidmap/bind:/mnt:rootidmap \ + bash -c "$cmd" |& tee nspawn.out; then + if grep -q "Failed to map ids for bind mount.*: Function not implemented" nspawn.out; then + echo "idmapped mounts are not supported, skipping the test..." + return 0 + fi + + return 1 + fi + + permissions=$(stat -c "%u:%g" /tmp/rootidmap/bind/other_file) + if [[ $permissions != "$owner:$owner" ]]; then + echo "*** wrong permissions: $permissions" + [[ "$IS_USERNS_SUPPORTED" == "yes" ]] && return 1 + fi +} + +testcase_notification_socket() { + # https://github.com/systemd/systemd/issues/4944 + local root + local cmd='echo a | nc -U -u -w 1 /run/host/notify' + + root="$(mktemp -d /var/lib/machines/testsuite-13.check_notification_socket.XXX)" + create_dummy_container "$root" + + systemd-nspawn --register=no --directory="$root" bash -x -c "$cmd" + systemd-nspawn --register=no --directory="$root" -U bash -x -c "$cmd" + + rm -fr "$root" +} + +testcase_os_release() { + local root entrypoint os_release_source + + root="$(mktemp -d /var/lib/machines/testsuite-13.os-release.XXX)" + create_dummy_container "$root" + entrypoint="$root/entrypoint.sh" + cat >"$entrypoint" <<\EOF +#!/usr/bin/bash -ex + +. /tmp/os-release +[[ -n "${ID:-}" && "$ID" != "$container_host_id" ]] && exit 1 +[[ -n "${VERSION_ID:-}" && "$VERSION_ID" != "$container_host_version_id" ]] && exit 1 +[[ -n "${BUILD_ID:-}" && "$BUILD_ID" != "$container_host_build_id" ]] && exit 1 +[[ -n "${VARIANT_ID:-}" && "$VARIANT_ID" != "$container_host_variant_id" ]] && exit 1 + +cd /tmp +(cd /run/host && md5sum os-release) | md5sum -c +EOF + chmod +x "$entrypoint" + + os_release_source="/etc/os-release" + if [[ ! -r "$os_release_source" ]]; then + os_release_source="/usr/lib/os-release" + elif [[ -L "$os_release_source" ]]; then + # Ensure that /etc always wins if available + cp --remove-destination -fv /usr/lib/os-release /etc/os-release + echo MARKER=1 >>/etc/os-release + fi + + systemd-nspawn --register=no \ + --directory="$root" \ + --bind="$os_release_source:/tmp/os-release" \ + "${entrypoint##"$root"}" + + if grep -q MARKER /etc/os-release; then + ln -svrf /usr/lib/os-release /etc/os-release + fi + + rm -fr "$root" +} + +testcase_machinectl_bind() { + local service_path service_name root container_name ec + local cmd='for i in $(seq 1 20); do if test -f /tmp/marker; then exit 0; fi; sleep .5; done; exit 1;' + + root="$(mktemp -d /var/lib/machines/testsuite-13.machinectl-bind.XXX)" + create_dummy_container "$root" + container_name="$(basename "$root")" + + service_path="$(mktemp /run/systemd/system/nspawn-machinectl-bind-XXX.service)" + service_name="${service_path##*/}" + cat >"$service_path" </dev/null || ! selinuxenabled; then + echo >&2 "SELinux is not enabled, skipping SELinux-related tests" + return 0 + fi + + local root + + root="$(mktemp -d /var/lib/machines/testsuite-13.selinux.XXX)" + create_dummy_container "$root" + chcon -R -t container_t "$root" + + systemd-nspawn --register=no \ + --boot \ + --directory="$root" \ + --selinux-apifs-context=system_u:object_r:container_file_t:s0:c0,c1 \ + --selinux-context=system_u:system_r:container_t:s0:c0,c1 + + rm -fr "$root" +} + +testcase_ephemeral_config() { + # https://github.com/systemd/systemd/issues/13297 + local root container_name + + root="$(mktemp -d /var/lib/machines/testsuite-13.ephemeral-config.XXX)" + create_dummy_container "$root" + container_name="$(basename "$root")" + + mkdir -p /run/systemd/nspawn/ + rm -f "/etc/systemd/nspawn/$container_name.nspawn" + cat >"/run/systemd/nspawn/$container_name.nspawn" <&2 "Unified cgroup hierarchy is not supported, skipping..." + return 0 + fi + + if [[ "$use_cgns" == "yes" && "$IS_CGNS_SUPPORTED" == "no" ]]; then + echo >&2 "CGroup namespaces are not supported, skipping..." + return 0 + fi + + root="$(mktemp -d "/var/lib/machines/testsuite-13.unified-$1-cgns-$2-api-vfs-writable-$3.XXX")" + create_dummy_container "$root" + + SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --boot + + SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --private-network \ + --boot + + if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --private-users=pick \ + --boot; then + [[ "$IS_USERNS_SUPPORTED" == "yes" && "$api_vfs_writable" == "network" ]] && return 1 + else + [[ "$IS_USERNS_SUPPORTED" == "no" && "$api_vfs_writable" = "network" ]] && return 1 + fi + + if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --private-network \ + --private-users=pick \ + --boot; then + [[ "$IS_USERNS_SUPPORTED" == "yes" && "$api_vfs_writable" == "yes" ]] && return 1 + else + [[ "$IS_USERNS_SUPPORTED" == "no" && "$api_vfs_writable" = "yes" ]] && return 1 + fi + + local netns_opt="--network-namespace-path=/proc/self/ns/net" + local net_opt + local net_opts=( + "--network-bridge=lo" + "--network-interface=lo" + "--network-ipvlan=lo" + "--network-macvlan=lo" + "--network-veth" + "--network-veth-extra=lo" + "--network-zone=zone" + ) + + # --network-namespace-path and network-related options cannot be used together + for net_opt in "${net_opts[@]}"; do + echo "$netns_opt in combination with $net_opt should fail" + if SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --boot \ + "$netns_opt" \ + "$net_opt"; then + echo >&2 "unexpected pass" + return 1 + fi + done + + # allow combination of --network-namespace-path and --private-network + SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --boot \ + --private-network \ + "$netns_opt" + + # test --network-namespace-path works with a network namespace created by "ip netns" + ip netns add nspawn_test + netns_opt="--network-namespace-path=/run/netns/nspawn_test" + SYSTEMD_NSPAWN_UNIFIED_HIERARCHY="$cgroupsv2" SYSTEMD_NSPAWN_USE_CGNS="$use_cgns" SYSTEMD_NSPAWN_API_VFS_WRITABLE="$api_vfs_writable" \ + systemd-nspawn --register=no \ + --directory="$root" \ + --network-namespace-path=/run/netns/nspawn_test \ + ip a | grep -v -E '^1: lo.*UP' + ip netns del nspawn_test + + rm -fr "$root" + + return 0 +} + +testcase_check_os_release() { + # https://github.com/systemd/systemd/issues/29185 + local base common_opts root + + base="$(mktemp -d /var/lib/machines/testsuite-13.check_os_release_base.XXX)" + root="$(mktemp -d /var/lib/machines/testsuite-13.check_os_release.XXX)" + create_dummy_container "$base" + cp -d "$base"/{bin,sbin,lib,lib64} "$root/" + common_opts=( + --boot + --register=no + --directory="$root" + --bind-ro="$base/usr:/usr" + ) + + # Empty /etc/ & /usr/ + (! systemd-nspawn "${common_opts[@]}") + (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=1 systemd-nspawn "${common_opts[@]}") + (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=foo systemd-nspawn "${common_opts[@]}") + SYSTEMD_NSPAWN_CHECK_OS_RELEASE=0 systemd-nspawn "${common_opts[@]}" + + # Empty /usr/ + a broken /etc/os-release -> /usr/os-release symlink + ln -svrf "$root/etc/os-release" "$root/usr/os-release" + (! systemd-nspawn "${common_opts[@]}") + (! SYSTEMD_NSPAWN_CHECK_OS_RELEASE=1 systemd-nspawn "${common_opts[@]}") + SYSTEMD_NSPAWN_CHECK_OS_RELEASE=0 systemd-nspawn "${common_opts[@]}" + + rm -fr "$root" "$base" +} + +run_testcases + +for api_vfs_writable in yes no network; do + matrix_run_one no no $api_vfs_writable + matrix_run_one yes no $api_vfs_writable + matrix_run_one no yes $api_vfs_writable + matrix_run_one yes yes $api_vfs_writable +done diff --git a/test/units/testsuite-13.nss-mymachines.sh b/test/units/testsuite-13.nss-mymachines.sh new file mode 100755 index 0000000..b566c73 --- /dev/null +++ b/test/units/testsuite-13.nss-mymachines.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + set +e + + machinectl kill --signal=KILL nss-mymachines-{noip,singleip,manyips} + mountpoint -q /var/lib/machines && timeout 10 sh -c "until umount /var/lib/machines; do sleep .5; done" + rm -f /run/systemd/nspawn/*.nspawn +} + +trap at_exit EXIT + +mkdir -p /var/lib/machines +mount -t tmpfs tmpfs /var/lib/machines + +# Create a bunch of containers that: +# 1) Have no IP addresses assigned +create_dummy_container /var/lib/machines/nss-mymachines-noip +cat >/var/lib/machines/nss-mymachines-noip/sbin/init <<\EOF +#!/usr/bin/bash -ex + +ip addr show dev ve-noip +touch /initialized +sleep infinity & +# Run the sleep command asynchronously, so bash is able to process signals +while :; do + wait || : +done +EOF +# 2) Have one IP address assigned (IPv4 only) +create_dummy_container /var/lib/machines/nss-mymachines-singleip +cat >/var/lib/machines/nss-mymachines-singleip/sbin/init <<\EOF +#!/usr/bin/bash -ex + +ip addr add 10.1.0.2/24 dev ve-singleip +ip addr show dev ve-singleip +touch /initialized +sleep infinity & +while :; do + wait || : +done +EOF +# 3) Have bunch of IP addresses assigned (both IPv4 and IPv6) +create_dummy_container /var/lib/machines/nss-mymachines-manyips +cat >/var/lib/machines/nss-mymachines-manyips/sbin/init <<\EOF +#!/usr/bin/bash -ex + +ip addr add 10.2.0.2/24 dev ve-manyips +for i in {100..120}; do + ip addr add 10.2.0.$i/24 dev ve-manyips +done +ip addr add fd00:dead:beef:cafe::2/64 dev ve-manyips +ip addr show dev ve-manyips +touch /initialized +sleep infinity +while :; do + wait || : +done +EOF +# Create the respective .nspawn config files +mkdir -p /run/systemd/nspawn +for container in noip singleip manyips; do + cat >"/run/systemd/nspawn/nss-mymachines-$container.nspawn" </dev/null || : + rm -f /{etc,run,usr/lib}/systemd/system/"$unit_name" + rm -fr /{etc,run,usr/lib}/systemd/system/"$unit_name".d + rm -fr /{etc,run,usr/lib}/systemd/system/"$unit_name".{wants,requires} + if [[ $unit_name == *@* ]]; then + base="${unit_name%@*}" + suffix="${unit_name##*.}" + systemctl stop "$base@"*."$suffix" 2>/dev/null || : + rm -f /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix" + rm -fr /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix".d + rm -fr /{etc,run,usr/lib}/systemd/system/"$base@"*."$suffix".{wants,requires} + fi +} + +clear_units() { + for u in "$@"; do + clear_unit "$u" + done + systemctl daemon-reload +} + +create_service() { + local service_name="${1:?}" + clear_units "${service_name}".service + + cat >/etc/systemd/system/"$service_name".service </run/systemd/system/service.d/override.conf <"/run/systemd/system/$dropin/override.conf" <"/run/systemd/system/$dropin/override.conf" </run/systemd/system/a-b-c.slice </etc/systemd/system/service.d/drop1.conf + echo -e '[Service]\nStandardInputText=bbb' >/etc/systemd/system/a-.service.d/drop2.conf + echo -e '[Service]\nStandardInputText=ccc' >/etc/systemd/system/a-b-.service.d/drop3.conf + echo -e '[Service]\nStandardInputText=ddd' >/etc/systemd/system/a-b-c.service.d/drop4.conf + + # There's no fragment yet, so this fails + systemctl cat a-b-c.service && exit 1 + + # xxx → eHh4Cg== + systemd-run -u a-b-c.service -p StandardInputData=eHh4Cg== sleep infinity + + data=$(systemctl show -P StandardInputData a-b-c.service) + # xxx\naaa\n\bbb\nccc\nddd\n → eHh4… + test "$data" = "eHh4CmFhYQpiYmIKY2NjCmRkZAo=" + + # Do a reload and check again + systemctl daemon-reload + data=$(systemctl show -P StandardInputData a-b-c.service) + test "$data" = "eHh4CmFhYQpiYmIKY2NjCmRkZAo=" + + clear_units a-b-c.service + rm /etc/systemd/system/service.d/drop1.conf \ + /etc/systemd/system/a-.service.d/drop2.conf \ + /etc/systemd/system/a-b-.service.d/drop3.conf +} + +testcase_transient_slice_dropins() { + echo "Testing dropins for a transient slice..." + echo "*** test transient slice drop-ins" + + # FIXME: implement reloading of individual units. + # + # The settings here are loaded twice. For most settings it doesn't matter, + # but Documentation is not deduplicated, so we current get repeated entried + # which is a bug. + + mkdir -p /etc/systemd/system/slice.d + mkdir -p /etc/systemd/system/a-.slice.d + mkdir -p /etc/systemd/system/a-b-.slice.d + mkdir -p /etc/systemd/system/a-b-c.slice.d + + echo -e '[Unit]\nDocumentation=man:drop1' >/etc/systemd/system/slice.d/drop1.conf + echo -e '[Unit]\nDocumentation=man:drop2' >/etc/systemd/system/a-.slice.d/drop2.conf + echo -e '[Unit]\nDocumentation=man:drop3' >/etc/systemd/system/a-b-.slice.d/drop3.conf + echo -e '[Unit]\nDocumentation=man:drop4' >/etc/systemd/system/a-b-c.slice.d/drop4.conf + + # Invoke daemon-reload to make sure that the call below doesn't fail + systemctl daemon-reload + + # No fragment is required, so this works + systemctl cat a-b-c.slice + + busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + StartTransientUnit 'ssa(sv)a(sa(sv))' \ + 'a-b-c.slice' 'replace' \ + 1 \ + 'Documentation' as 1 'man:drop5' \ + 0 + + data=$(systemctl show -P Documentation a-b-c.slice) + test "$data" = "man:drop1 man:drop2 man:drop3 man:drop4 man:drop5 man:drop1 man:drop2 man:drop3 man:drop4" + + # Do a reload and check again + systemctl daemon-reload + data=$(systemctl show -P Documentation a-b-c.slice) + test "$data" = "man:drop5 man:drop1 man:drop2 man:drop3 man:drop4" + + clear_units a-b-c.slice + rm /etc/systemd/system/slice.d/drop1.conf \ + /etc/systemd/system/a-.slice.d/drop2.conf \ + /etc/systemd/system/a-b-.slice.d/drop3.conf +} + +testcase_template_dropins() { + echo "Testing template dropins..." + + create_services foo bar@ yup@ + + # Declare some deps to check if the body was loaded + cat >>/etc/systemd/system/bar@.service <>/etc/systemd/system/yup@.service </usr/lib/systemd/system/test15-a.service.d/override.conf </usr/lib/systemd/system/test15-a.service.d/wants-b.conf </tmp/testsuite-15-test15-a-dropin-directory/override.conf <>"$TESTLOG" + fi +} + +wait_for_timeout() +{ + local unit="$1" + local time="$2" + + while [[ $time -gt 0 ]]; do + if [[ "$(systemctl show --property=Result "$unit")" == "Result=timeout" ]]; then + return 0 + fi + + sleep 1 + time=$((time - 1)) + done + + journalctl -u "$unit" >>"$TESTLOG" + + return 1 +} + +# This checks all stages, start, runtime and stop, can be extended by +# EXTEND_TIMEOUT_USEC + +wait_for success_all + +# These check that EXTEND_TIMEOUT_USEC that occurs at greater than the +# extend timeout interval but less then the stage limit (TimeoutStartSec, +# RuntimeMaxSec, TimeoutStopSec) still succeed. + +wait_for success_start +wait_for success_runtime +wait_for success_stop + +# These ensure that EXTEND_TIMEOUT_USEC will still timeout in the +# appropriate stage, after the stage limit, when the EXTEND_TIMEOUT_USEC +# message isn't sent within the extend timeout interval. + +wait_for fail_start startfail +wait_for fail_stop stopfail +wait_for fail_runtime runtimefail + +# These ensure that RuntimeMaxSec is honored for scope and service units +# when they are created. +runtime_max_sec=5 + +systemd-run \ + --property=RuntimeMaxSec=${runtime_max_sec}s \ + -u runtime-max-sec-test-1.service \ + /usr/bin/sh -c "while true; do sleep 1; done" +wait_for_timeout runtime-max-sec-test-1.service $((runtime_max_sec + 2)) + +systemd-run \ + --property=RuntimeMaxSec=${runtime_max_sec}s \ + --scope \ + -u runtime-max-sec-test-2.scope \ + /usr/bin/sh -c "while true; do sleep 1; done" & +wait_for_timeout runtime-max-sec-test-2.scope $((runtime_max_sec + 2)) + +# These ensure that RuntimeMaxSec is honored for scope and service +# units if the value is changed and then the manager is reloaded. +systemd-run \ + -u runtime-max-sec-test-3.service \ + /usr/bin/sh -c "while true; do sleep 1; done" +mkdir -p /etc/systemd/system/runtime-max-sec-test-3.service.d/ +cat > /etc/systemd/system/runtime-max-sec-test-3.service.d/override.conf << EOF +[Service] +RuntimeMaxSec=${runtime_max_sec}s +EOF +systemctl daemon-reload +wait_for_timeout runtime-max-sec-test-3.service $((runtime_max_sec + 2)) + +systemd-run \ + --scope \ + -u runtime-max-sec-test-4.scope \ + /usr/bin/sh -c "while true; do sleep 1; done" & + +# Wait until the unit is running to avoid race with creating the override. +until systemctl is-active runtime-max-sec-test-4.scope; do + sleep 1 +done +mkdir -p /etc/systemd/system/runtime-max-sec-test-4.scope.d/ +cat > /etc/systemd/system/runtime-max-sec-test-4.scope.d/override.conf << EOF +[Scope] +RuntimeMaxSec=${runtime_max_sec}s +EOF +systemctl daemon-reload +wait_for_timeout runtime-max-sec-test-4.scope $((runtime_max_sec + 2)) + +if [[ -f "$TESTLOG" ]]; then + # no mv + cp "$TESTLOG" /test.log + exit 1 +fi + +touch /testok diff --git a/test/units/testsuite-17.00.sh b/test/units/testsuite-17.00.sh new file mode 100755 index 0000000..d2aec60 --- /dev/null +++ b/test/units/testsuite-17.00.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Tests for issue #28588 and #28653. + +# On boot, services need to be started in the following order: +# 1. systemd-tmpfiles-setup-dev-early.service +# 2. systemd-sysusers.service +# 3. systemd-tmpfiles-setup-dev.service +# 4. systemd-udevd.service + +output="$(systemctl show --property After --value systemd-udevd.service)" +assert_in "systemd-tmpfiles-setup-dev-early.service" "$output" +assert_in "systemd-sysusers.service" "$output" +assert_in "systemd-tmpfiles-setup-dev.service" "$output" + +output="$(systemctl show --property After --value systemd-tmpfiles-setup-dev.service)" +assert_in "systemd-tmpfiles-setup-dev-early.service" "$output" +assert_in "systemd-sysusers.service" "$output" + +output="$(systemctl show --property After --value systemd-sysusers.service)" +assert_in "systemd-tmpfiles-setup-dev-early.service" "$output" + +check_owner_and_mode() { + local dev=${1?} + local user=${2?} + local group=${3?} + local mode=${4:-} + + if [[ -e "$dev" ]]; then + assert_in "$user" "$(stat --format=%U "$dev")" + assert_in "$group" "$(stat --format=%G "$dev")" + if [[ -n "$mode" ]]; then + assert_in "$mode" "$(stat --format=%#0a "$dev")" + fi + fi + + return 0 +} + +# Check owner and access mode specified in static-nodes-permissions.conf +check_owner_and_mode /dev/snd/seq root audio 0660 +check_owner_and_mode /dev/snd/timer root audio 0660 +check_owner_and_mode /dev/loop-control root disk 0660 +check_owner_and_mode /dev/net/tun root root 0666 +check_owner_and_mode /dev/fuse root root 0666 +check_owner_and_mode /dev/vfio/vfio root root 0666 +check_owner_and_mode /dev/kvm root kvm +check_owner_and_mode /dev/vhost-net root kvm +check_owner_and_mode /dev/vhost-vsock root kvm + +exit 0 diff --git a/test/units/testsuite-17.01.sh b/test/units/testsuite-17.01.sh new file mode 100755 index 0000000..44f36f5 --- /dev/null +++ b/test/units/testsuite-17.01.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +mkdir -p /run/udev/rules.d/ + +rm -f /run/udev/rules.d/50-testsuite.rules +udevadm control --reload +udevadm trigger --settle /dev/sda + +while : ; do + ( + udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=foobar.service + udevadm info /dev/sda | grep -q -v SYSTEMD_WANTS=waldo.service + systemctl show -p WantedBy foobar.service | grep -q -v sda + systemctl show -p WantedBy waldo.service | grep -q -v sda + ) && break + + sleep .5 +done + +cat >/run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/50-testsuite.rules <"$TMPDIR"/monitor.txt & + KILL_PID="$!" + + # make sure that 'udevadm monitor' actually monitor uevents + sleep 1 + + since="$(date '+%H:%M:%S')" + + # add another interface which will conflict with an existing interface + ip link add foobar type dummy + + for _ in {1..40}; do + if ( + grep -q 'ACTION=add' "$TMPDIR"/monitor.txt + grep -q 'DEVPATH=/devices/virtual/net/foobar' "$TMPDIR"/monitor.txt + grep -q 'SUBSYSTEM=net' "$TMPDIR"/monitor.txt + grep -q 'INTERFACE=foobar' "$TMPDIR"/monitor.txt + grep -q 'ID_NET_DRIVER=dummy' "$TMPDIR"/monitor.txt + grep -q 'ID_NET_NAME=foobar' "$TMPDIR"/monitor.txt + # Even when network interface renaming is failed, SYSTEMD_ALIAS with the conflicting name will be broadcast. + grep -q 'SYSTEMD_ALIAS=/sys/subsystem/net/devices/hoge' "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_ERRNO=17' "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_ERRNO_NAME=EEXIST' "$TMPDIR"/monitor.txt + ); then + cat "$TMPDIR"/monitor.txt + found=1 + break + fi + sleep .5 + done + test -n "$found" + + timeout 30 bash -c "until journalctl _PID=1 _COMM=systemd --since $since | grep -q 'foobar: systemd-udevd failed to process the device, ignoring: File exists'; do sleep 1; done" + # check if the invalid SYSTEMD_ALIAS property for the interface foobar is ignored by PID1 + assert_eq "$(systemctl show --property=SysFSPath --value /sys/subsystem/net/devices/hoge)" "/sys/devices/virtual/net/hoge" +} + +test_netif_renaming_conflict + +exit 0 diff --git a/test/units/testsuite-17.03.sh b/test/units/testsuite-17.03.sh new file mode 100755 index 0000000..56e352e --- /dev/null +++ b/test/units/testsuite-17.03.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex + +TEST_RULE="/run/udev/rules.d/49-test.rules" +KILL_PID= + +setup() { + mkdir -p "${TEST_RULE%/*}" + [[ -e /etc/udev/udev.conf ]] && cp -f /etc/udev/udev.conf /etc/udev/udev.conf.bak + # Don't bother storing the coredumps in journal for this particular test + mkdir -p /run/systemd/coredump.conf.d/ + echo -ne "[Coredump]\nStorage=external\n" >/run/systemd/coredump.conf.d/99-storage-journal.conf + + cat >"${TEST_RULE}" </etc/udev/udev.conf <"$TMPDIR"/monitor.txt & + KILL_PID="$!" + + SYSTEMD_LOG_LEVEL=debug udevadm trigger --verbose --action add /dev/null + + for _ in {1..40}; do + if coredumpctl --since "$since" --no-legend --no-pager | grep /bin/udevadm ; then + kill "$KILL_PID" + KILL_PID= + + cat "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_SIGNAL=6' "$TMPDIR"/monitor.txt + grep -q 'UDEV_WORKER_SIGNAL_NAME=ABRT' "$TMPDIR"/monitor.txt + return 0 + fi + sleep .5 + done + + return 1 +} + +trap teardown EXIT + +setup +run_test + +exit 0 diff --git a/test/units/testsuite-17.04.sh b/test/units/testsuite-17.04.sh new file mode 100755 index 0000000..d1c3c85 --- /dev/null +++ b/test/units/testsuite-17.04.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +mkdir -p /run/udev/rules.d/ + +test ! -f /run/udev/tags/added/c1:3 +test ! -f /run/udev/tags/changed/c1:3 +udevadm info /dev/null | grep -E 'E: (TAGS|CURRENT_TAGS)=.*:(added|changed):' && exit 1 + +cat >/run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/00-debug.rules </run/udev/rules.d/50-testsuite.rules </run/udev/rules.d/50-testsuite.rules < 1 )) && sleep 0.5 + if systemctl --quiet is-active "${1?}"; then + return 0 + fi + done + return 1 +)} + +wait_service_inactive() {( + set +ex + for i in {1..20}; do + (( i > 1 )) && sleep 0.5 + systemctl --quiet is-active "${1?}" + if [[ "$?" == "3" ]]; then + return 0 + fi + done + return 1 +)} + +mkdir -p /run/systemd/system +cat >/run/systemd/system/both.service </run/systemd/system/on-add.service </run/systemd/system/on-change.service </run/udev/rules.d/50-testsuite.rules </run/systemd/system/both.service </run/systemd/system/on-add.service </run/systemd/system/on-change.service </run/udev/rules.d/50-testsuite.rules < 1)) && sleep .5 + + ( + systemctl -q is-active /dev/test/symlink-to-null-on-add + ! systemctl -q is-active /dev/test/symlink-to-null-on-change + systemctl -q is-active /sys/test/alias-to-null-on-add + ! systemctl -q is-active /sys/test/alias-to-null-on-change + ) && break +done +assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-add +assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-change +assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-add +assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change + +udevadm trigger --settle --action change /dev/null +for i in {1..20}; do + ((i > 1)) && sleep .5 + + ( + ! systemctl -q is-active /dev/test/symlink-to-null-on-add + systemctl -q is-active /dev/test/symlink-to-null-on-change + ! systemctl -q is-active /sys/test/alias-to-null-on-add + systemctl -q is-active /sys/test/alias-to-null-on-change + ) && break +done +assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-add +assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-change +assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-add +assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-change + +udevadm trigger --settle --action add /dev/null +for i in {1..20}; do + ((i > 1)) && sleep .5 + + ( + systemctl -q is-active /dev/test/symlink-to-null-on-add + ! systemctl -q is-active /dev/test/symlink-to-null-on-change + systemctl -q is-active /sys/test/alias-to-null-on-add + ! systemctl -q is-active /sys/test/alias-to-null-on-change + ) && break +done +assert_rc 0 systemctl -q is-active /dev/test/symlink-to-null-on-add +assert_rc 3 systemctl -q is-active /dev/test/symlink-to-null-on-change +assert_rc 0 systemctl -q is-active /sys/test/alias-to-null-on-add +assert_rc 3 systemctl -q is-active /sys/test/alias-to-null-on-change + +# cleanup +rm -f /run/udev/rules.d/50-testsuite.rules +udevadm control --reload + +exit 0 diff --git a/test/units/testsuite-17.09.sh b/test/units/testsuite-17.09.sh new file mode 100755 index 0000000..9993196 --- /dev/null +++ b/test/units/testsuite-17.09.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# This is a test for issue #24987. + +mkdir -p /run/udev/rules.d/ +cat >/run/udev/rules.d/50-testsuite.rules <"$TMPDIR"/monitor.txt 2>&1 & +KILL_PID="$!" + +FOUND= +for _ in {1..40}; do + if grep -F 'UDEV - the event which udev sends out after rule processing' "$TMPDIR"/monitor.txt; then + FOUND=1 + break + fi + sleep .5 +done +[[ -n "$FOUND" ]] + +udevadm trigger --verbose --settle --action add /dev/null + +FOUND= +for _ in {1..40}; do + if ! grep -e 'UDEV *\[[0-9.]*\] *add *\/devices\/virtual\/mem\/null (mem)' "$TMPDIR"/monitor.txt; then + sleep .5 + continue + fi + + FOUND=1 + for i in {1..100}; do + if ! grep -F "$(printf 'XXX%03i=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' "$i")" "$TMPDIR"/monitor.txt; then + FOUND= + break + fi + done + if [[ -n "$FOUND" ]]; then + break; + fi + + sleep .5 +done +[[ -n "$FOUND" ]] + +# cleanup +rm -f /run/udev/rules.d/50-testsuite.rules +udevadm control --reload + +kill "$KILL_PID" +rm -rf "$TMPDIR" + +exit 0 diff --git a/test/units/testsuite-17.10.sh b/test/units/testsuite-17.10.sh new file mode 100755 index 0000000..f229dcf --- /dev/null +++ b/test/units/testsuite-17.10.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Coverage test for udevadm + +# shellcheck disable=SC2317 +cleanup_17_10() { + set +e + + losetup -d "$loopdev" + rm -f "$blk" + + ip link delete "$netdev" +} + +# Set up some test devices +trap cleanup_17_10 EXIT + +netdev=dummy17.10 +ip link add $netdev type dummy + +blk="$(mktemp)" +dd if=/dev/zero of="$blk" bs=1M count=1 +loopdev="$(losetup --show -f "$blk")" + +udevadm -h + +udevadm control -e +udevadm control -l emerg +udevadm control -l alert +udevadm control -l crit +udevadm control -l err +udevadm control -l warning +udevadm control -l notice +udevadm control --log-level info +udevadm control --log-level debug +(! udevadm control -l hello) +udevadm control -s +udevadm control -S +udevadm control -R +udevadm control -p HELLO=world +udevadm control -m 42 +udevadm control --ping +udevadm control -t 5 +udevadm control -h + +udevadm info /dev/null +udevadm info /sys/class/net/$netdev +udevadm info "$(systemd-escape -p --suffix device /sys/devices/virtual/net/$netdev)" +udevadm info --property DEVNAME /sys/class/net/$netdev +udevadm info --property DEVNAME --value /sys/class/net/$netdev +udevadm info --property HELLO /sys/class/net/$netdev +udevadm info -p class/net/$netdev +udevadm info -p /class/net/$netdev +udevadm info --json=off -p class/net/$netdev +udevadm info --json=pretty -p class/net/$netdev | jq . +udevadm info --json=short -p class/net/$netdev | jq . +udevadm info -n null +udevadm info -q all /sys/class/net/$netdev +udevadm info -q name /dev/null +udevadm info -q path /sys/class/net/$netdev +udevadm info -q property /sys/class/net/$netdev +udevadm info -q symlink /sys/class/net/$netdev +udevadm info -q name -r /dev/null +udevadm info --query symlink --root /sys/class/net/$netdev +(! udevadm info -q hello -r /sys/class/net/$netdev) +udevadm info -a /sys/class/net/$netdev +udevadm info -t >/dev/null +udevadm info --tree /sys/class/net/$netdev +udevadm info -x /sys/class/net/$netdev +udevadm info -x -q path /sys/class/net/$netdev +udevadm info -P TEST_ /sys/class/net/$netdev +udevadm info -d /dev/null +udevadm info -e >/dev/null +udevadm info -e --json=off >/dev/null +udevadm info -e --json=pretty | jq . >/dev/null +udevadm info -e --json=short | jq . >/dev/null +udevadm info -e --subsystem-match acpi >/dev/null +udevadm info -e --subsystem-nomatch acpi >/dev/null +udevadm info -e --attr-match ifindex=2 >/dev/null +udevadm info -e --attr-nomatch ifindex=2 >/dev/null +udevadm info -e --property-match SUBSYSTEM=acpi >/dev/null +udevadm info -e --tag-match systemd >/dev/null +udevadm info -e --sysname-match lo >/dev/null +udevadm info -e --name-match /sys/class/net/$netdev >/dev/null +udevadm info -e --parent-match /sys/class/net/$netdev >/dev/null +udevadm info -e --initialized-match >/dev/null +udevadm info -e --initialized-nomatch >/dev/null +# udevadm info -c +udevadm info -w /sys/class/net/$netdev +udevadm info --wait-for-initialization=5 /sys/class/net/$netdev +udevadm info -h + +assert_rc 124 timeout 1 udevadm monitor +assert_rc 124 timeout 1 udevadm monitor -k +assert_rc 124 timeout 1 udevadm monitor -u +assert_rc 124 timeout 1 udevadm monitor -s net +assert_rc 124 timeout 1 udevadm monitor --subsystem-match net/$netdev +assert_rc 124 timeout 1 udevadm monitor -t systemd +assert_rc 124 timeout 1 udevadm monitor --tag-match hello +udevadm monitor -h + +udevadm settle +udevadm settle -t 5 +udevadm settle -E /sys/class/net/$netdev +udevadm settle -h + +udevadm test /dev/null +udevadm info /sys/class/net/$netdev +udevadm test "$(systemd-escape -p --suffix device /sys/devices/virtual/net/$netdev)" +udevadm test -a add /sys/class/net/$netdev +udevadm test -a change /sys/class/net/$netdev +udevadm test -a move /sys/class/net/$netdev +udevadm test -a online /sys/class/net/$netdev +udevadm test -a offline /sys/class/net/$netdev +udevadm test -a bind /sys/class/net/$netdev +udevadm test -a unbind /sys/class/net/$netdev +udevadm test -a help /sys/class/net/$netdev +udevadm test --action help +(! udevadm test -a hello /sys/class/net/$netdev) +udevadm test -N early /sys/class/net/$netdev +udevadm test -N late /sys/class/net/$netdev +udevadm test --resolve-names never /sys/class/net/$netdev +(! udevadm test -N hello /sys/class/net/$netdev) +udevadm test -h + +# udevadm test-builtin path_id "$loopdev" +udevadm test-builtin net_id /sys/class/net/$netdev +udevadm test-builtin net_id "$(systemd-escape -p --suffix device /sys/devices/virtual/net/$netdev)" +udevadm test-builtin -a add net_id /sys/class/net/$netdev +udevadm test-builtin -a remove net_id /sys/class/net/$netdev +udevadm test-builtin -a change net_id /sys/class/net/$netdev +udevadm test-builtin -a move net_id /sys/class/net/$netdev +udevadm test-builtin -a online net_id /sys/class/net/$netdev +udevadm test-builtin -a offline net_id /sys/class/net/$netdev +udevadm test-builtin -a bind net_id /sys/class/net/$netdev +udevadm test-builtin -a unbind net_id /sys/class/net/$netdev +udevadm test-builtin -a help net_id /sys/class/net/$netdev +udevadm test-builtin net_setup_link /sys/class/net/$netdev +udevadm test-builtin blkid "$loopdev" +udevadm test-builtin input_id /sys/class/net/$netdev +udevadm test-builtin keyboard /dev/null +# udevadm test-builtin kmod /sys/class/net/$netdev +udevadm test-builtin uaccess /dev/null +# udevadm test-builtin usb_id dev/null +(! udevadm test-builtin hello /sys/class/net/$netdev) +# systemd-hwdb update is extremely slow when combined with sanitizers and run +# in a VM without acceleration, so let's just skip the one particular test +# if we detect this combination +if ! [[ -v ASAN_OPTIONS && "$(systemd-detect-virt -v)" == "qemu" ]]; then + modprobe scsi_debug + scsidev=$(readlink -f /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/[0-9]*) + mkdir -p /etc/udev/hwdb.d + cat >/etc/udev/hwdb.d/99-test.hwdb <"${workdir}/default_output_1_success" <"${workdir}/default_output_1_fail" <"${workdir}/output_0_files" <"${out}" + if [ -f "${exo}" ]; then + diff -u "${exo}" "${out}" + elif [ -f "${rules}" ]; then + diff -u "${workdir}/default_output_1_success" "${out}" + fi +} + +assert_0() { + assert_0_impl "$@" + next_test_number +} + +assert_1_impl() { + local rc + set +e + udevadm verify "$@" >"${out}" 2>"${err}" + rc=$? + set -e + assert_eq "$rc" 1 + + if [ -f "${exp}" ]; then + diff -u "${exp}" "${err}" + fi + + if [ -f "${exo}" ]; then + diff -u "${exo}" "${out}" + elif [ -f "${rules}" ]; then + diff -u "${workdir}/default_output_1_fail" "${out}" + fi +} + +assert_1() { + assert_1_impl "$@" + next_test_number +} + +# initialize variables +next_test_number + +assert_0 -h +assert_0 --help +assert_0 -V +assert_0 --version +assert_0 /dev/null + +# unrecognized option '--unknown' +assert_1 --unknown +# option requires an argument -- 'N' +assert_1 -N +# --resolve-names= takes "early" or "never" +assert_1 -N now +# option '--resolve-names' requires an argument +assert_1 --resolve-names +# --resolve-names= takes "early" or "never" +assert_1 --resolve-names=now +# Failed to parse rules file ./nosuchfile: No such file or directory +assert_1 ./nosuchfile +# Failed to parse rules file ./nosuchfile: No such file or directory +cat >"${exo}" <"${exo}" +assert_0 --root="${workdir}" --no-summary + +# Directory with a single *.rules file. +cp "${workdir}/default_output_1_success" "${exo}" +assert_0 "${rules_dir}" + +# Combination of --root= and FILEs is not supported. +assert_1 --root="${workdir}" /dev/null +# No rules files found in nosuchdir +assert_1 --root=nosuchdir + +cd "${rules_dir}" + +# UDEV_LINE_SIZE 16384 +printf '%16383s\n' ' ' >"${rules}" +assert_0 "${rules}" + +# Failed to parse rules file ${rules}: No buffer space available +printf '%16384s\n' ' ' >"${rules}" +echo "Failed to parse rules file ${rules}: No buffer space available" >"${exp}" +assert_1 "${rules}" + +{ + printf 'RUN+="/bin/true",%8174s\\\n' ' ' + printf 'RUN+="/bin/false"%8174s\\\n' ' ' + echo +} >"${rules}" +assert_0 "${rules}" + +printf 'RUN+="/bin/true"%8176s\\\n #\n' ' ' ' ' >"${rules}" +echo >>"${rules}" +cat >"${exp}" <"${rules}" +cat >"${exp}" <"${rules}" + cat >"${exp}" <"${rules}" + cat >"${exp}" <"${rules}" <<'EOF' +KERNEL=="a|b", KERNEL=="a|c", NAME="d" +KERNEL=="a|b", KERNEL!="a|c", NAME="d" +KERNEL!="a", KERNEL!="b", NAME="c" +KERNEL=="|a", KERNEL=="|b", NAME="c" +KERNEL=="*", KERNEL=="a*", NAME="b" +KERNEL=="a*", KERNEL=="c*|ab*", NAME="d" +PROGRAM="a", RESULT=="b" +EOF +assert_0 "${rules}" + +echo 'GOTO="a"' >"${rules}" +cat >"${exp}" <"${rules}" <<'EOF' +GOTO="a" +LABEL="a" +EOF +assert_0 "${rules}" + +cat >"${rules}" <<'EOF' +GOTO="b" +LABEL="b" +LABEL="b" +EOF +cat >"${exp}" <"${rules}" <<'EOF' +GOTO="a" +LABEL="a", LABEL="b" +EOF +cat >"${exp}" <"${rules}" <<'EOF' +KERNEL!="", KERNEL=="?*", KERNEL=="", NAME="a" +EOF +cat >"${exp}" <"${rules}" <<'EOF' +ACTION=="a"NAME="b" +EOF +cat >"${exp}" <"${rules}" <<'EOF' +ACTION=="a" ,NAME="b" +EOF +cat >"${exp}" <"${workdir}/${exp}" +cd - +assert_1 --root="${workdir}" +cd - + +# udevadm verify path/ +sed "s|sample-[0-9]*.rules|${workdir}/${rules_dir}/&|" sample-*.exp >"${workdir}/${exp}" +cd - +assert_1 "${rules_dir}" +cd - + +exit 0 diff --git a/test/units/testsuite-17.12.sh b/test/units/testsuite-17.12.sh new file mode 100755 index 0000000..ccc91bf --- /dev/null +++ b/test/units/testsuite-17.12.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +create_link_file() { + name=${1?} + + mkdir -p /run/systemd/network/ + cat >/run/systemd/network/10-test.link < "$rules" <<'EOF' +ENV{FOO}=="?*", ENV{PROP_FOO}="$env{FOO}" +ENV{BAR}=="?*", ENV{PROP_BAR}="$env{BAR}" +EOF + +udevadm control --reload + +test_not_property /dev/null PROP_FOO +test_not_property /dev/null PROP_BAR + +: Setting of a property works + +udevadm control --property FOO=foo +udevadm trigger --action change --settle /dev/null +test_property /dev/null PROP_FOO foo +test_not_property /dev/null PROP_BAR + +: Change of a property works + +udevadm control --property FOO=goo +udevadm trigger --action change --settle /dev/null +test_property /dev/null PROP_FOO goo + +: Removal of a property works + +udevadm control --property FOO= +udevadm trigger --action change --settle /dev/null +test_not_property /dev/null PROP_FOO + +: Repeated removal of a property does nothing + +udevadm control --property FOO= +udevadm trigger --action change --settle /dev/null +test_not_property /dev/null PROP_FOO + +: Multiple properties can be set at once + +udevadm control --property FOO=foo --property BAR=bar +udevadm trigger --action change --settle /dev/null +test_property /dev/null PROP_FOO foo +test_property /dev/null PROP_BAR bar + +: Multiple setting of the same property is handled correctly + +udevadm control --property FOO=foo --property FOO=42 +udevadm trigger --action change --settle /dev/null +test_property /dev/null PROP_FOO 42 + +: Mix of settings and removals of the same property is handled correctly + +udevadm control -p FOO= -p FOO=foo -p BAR=car -p BAR= +udevadm trigger --action change --settle /dev/null +test_property /dev/null PROP_FOO foo +test_not_property /dev/null PROP_BAR + +exit 0 diff --git a/test/units/testsuite-17.service b/test/units/testsuite-17.service new file mode 100644 index 0000000..d218d72 --- /dev/null +++ b/test/units/testsuite-17.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-17-UDEV + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-17.sh b/test/units/testsuite-17.sh new file mode 100755 index 0000000..14ceeba --- /dev/null +++ b/test/units/testsuite-17.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +udevadm settle + +run_subtests + +touch /testok diff --git a/test/units/testsuite-18.service b/test/units/testsuite-18.service new file mode 100644 index 0000000..16d90a1 --- /dev/null +++ b/test/units/testsuite-18.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-18-FAILUREACTION + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-18.sh b/test/units/testsuite-18.sh new file mode 100755 index 0000000..44b792f --- /dev/null +++ b/test/units/testsuite-18.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemd-run --wait -p FailureAction=poweroff true +(! systemd-run --wait -p SuccessAction=poweroff false) + +if ! test -f /firstphase ; then + echo OK >/firstphase + systemd-run --wait -p SuccessAction=reboot true +else + echo OK >/testok + systemd-run --wait -p FailureAction=poweroff false +fi + +sleep infinity diff --git a/test/units/testsuite-19.ExitType-cgroup.sh b/test/units/testsuite-19.ExitType-cgroup.sh new file mode 100755 index 0000000..cd221d7 --- /dev/null +++ b/test/units/testsuite-19.ExitType-cgroup.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -eux + +# Test ExitType=cgroup + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ "$(get_cgroup_hierarchy)" != unified ]]; then + echo "Skipping $0 as we're not running with the unified cgroup hierarchy" + exit 0 +fi + +systemd-analyze log-level debug + +# Multiple level process tree, parent process stays up +cat >/tmp/test19-exit-cgroup.sh < sleep +sleep infinity & +disown + +# process tree: systemd -> bash -> bash -> sleep +((sleep infinity); true) & + +systemd-notify --ready + +# Run the stop/kill command +\$1 & + +# process tree: systemd -> bash -> sleep +sleep infinity +EOF +chmod +x /tmp/test19-exit-cgroup.sh + +# service should be stopped cleanly +systemd-run --wait \ + --unit=one \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + /tmp/test19-exit-cgroup.sh 'systemctl stop one' + +# same thing with a truthy exec condition +systemd-run --wait \ + --unit=two \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + --property="ExecCondition=true" \ + /tmp/test19-exit-cgroup.sh 'systemctl stop two' + +# false exec condition: systemd-run should exit immediately with status code: 1 +(! systemd-run --wait \ + --unit=three \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + --property="ExecCondition=false" \ + /tmp/test19-exit-cgroup.sh) + +# service should exit uncleanly (main process exits with SIGKILL) +(! systemd-run --wait \ + --unit=four \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + /tmp/test19-exit-cgroup.sh 'systemctl kill --signal 9 four') + + +# Multiple level process tree, parent process exits quickly +cat >/tmp/test19-exit-cgroup-parentless.sh < sleep +sleep infinity & + +# process tree: systemd -> bash -> sleep +((sleep infinity); true) & + +systemd-notify --ready + +# Run the stop/kill command after this bash process exits +(sleep 1; \$1) & +EOF +chmod +x /tmp/test19-exit-cgroup-parentless.sh + +# service should be stopped cleanly +systemd-run --wait \ + --unit=five \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + /tmp/test19-exit-cgroup-parentless.sh 'systemctl stop five' + +# service should still exit cleanly despite SIGKILL (the main process already exited cleanly) +systemd-run --wait \ + --unit=six \ + --property="Type=notify" \ + --property="ExitType=cgroup" \ + /tmp/test19-exit-cgroup-parentless.sh 'systemctl kill --signal 9 six' + + +systemd-analyze log-level info diff --git a/test/units/testsuite-19.cleanup-slice.sh b/test/units/testsuite-19.cleanup-slice.sh new file mode 100755 index 0000000..5d63160 --- /dev/null +++ b/test/units/testsuite-19.cleanup-slice.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +# Create service with KillMode=none inside a slice +cat </run/systemd/system/test19cleanup.service +[Unit] +Description=Test 19 cleanup Service +[Service] +Slice=test19cleanup.slice +Type=exec +ExecStart=sleep infinity +KillMode=none +EOF +cat </run/systemd/system/test19cleanup.slice +[Unit] +Description=Test 19 cleanup Slice +EOF + +# Start service +systemctl start test19cleanup.service +assert_rc 0 systemd-cgls /test19cleanup.slice + +pid=$(systemctl show --property MainPID --value test19cleanup) +ps "$pid" + +# Stop slice +# The sleep process will not be killed because of KillMode=none +# Since there is still a process running under it, the /test19cleanup.slice cgroup won't be removed +systemctl stop test19cleanup.slice + +ps "$pid" + +# Kill sleep process manually +kill -s TERM "$pid" +while kill -0 "$pid" 2>/dev/null; do sleep 0.1; done + +timeout 30 bash -c 'while systemd-cgls /test19cleanup.slice/test19cleanup.service >& /dev/null; do sleep .5; done' +assert_rc 1 systemd-cgls /test19cleanup.slice/test19cleanup.service + +# Check that empty cgroup /test19cleanup.slice has been removed +timeout 30 bash -c 'while systemd-cgls /test19cleanup.slice >& /dev/null; do sleep .5; done' +assert_rc 1 systemd-cgls /test19cleanup.slice diff --git a/test/units/testsuite-19.delegate.sh b/test/units/testsuite-19.delegate.sh new file mode 100755 index 0000000..74d36c4 --- /dev/null +++ b/test/units/testsuite-19.delegate.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Test cgroup delegation in the unified hierarchy + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ "$(get_cgroup_hierarchy)" != unified ]]; then + echo "Skipping $0 as we're not running with the unified cgroup hierarchy" + exit 0 +fi + +at_exit() { + set +e + userdel -r test +} + +systemd-run --wait \ + --unit=test-0.service \ + --property="DynamicUser=1" \ + --property="Delegate=" \ + test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.procs -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/cgroup.subtree_control + +# Test if this also works for some of the more recent attrs the kernel might or might not support +for attr in cgroup.threads memory.oom.group memory.reclaim ; do + + if grep -q "$attr" /sys/kernel/cgroup/delegate ; then + systemd-run --wait \ + --unit=test-0.service \ + --property="DynamicUser=1" \ + --property="Delegate=" \ + test -w /sys/fs/cgroup/system.slice/test-0.service/ -a \ + -w /sys/fs/cgroup/system.slice/test-0.service/"$attr" + fi +done + +systemd-run --wait \ + --unit=test-1.service \ + --property="DynamicUser=1" \ + --property="Delegate=memory pids" \ + grep -q memory /sys/fs/cgroup/system.slice/test-1.service/cgroup.controllers + +systemd-run --wait \ + --unit=test-2.service \ + --property="DynamicUser=1" \ + --property="Delegate=memory pids" \ + grep -q pids /sys/fs/cgroup/system.slice/test-2.service/cgroup.controllers + +# "io" is not among the controllers enabled by default for all units, verify that +grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers + +# Run a service with "io" enabled, and verify it works +systemd-run --wait \ + --unit=test-3.service \ + --property="IOAccounting=yes" \ + --property="Slice=system-foo-bar-baz.slice" \ + grep -q io /sys/fs/cgroup/system.slice/system-foo.slice/system-foo-bar.slice/system-foo-bar-baz.slice/test-3.service/cgroup.controllers + +# We want to check if "io" is removed again from the controllers +# list. However, PID 1 (rightfully) does this asynchronously. In order +# to force synchronization on this, let's start a short-lived service +# which requires PID 1 to refresh the cgroup tree, so that we can +# verify that this all works. +systemd-run --wait --unit=test-4.service true + +# And now check again, "io" should have vanished +grep -qv io /sys/fs/cgroup/system.slice/cgroup.controllers + +# Check that unprivileged delegation works for scopes +useradd test ||: +systemd-run --uid=test \ + --property="User=test" \ + --property="Delegate=yes" \ + --slice workload.slice \ + --unit test-workload0.scope\ + --scope \ + test -w /sys/fs/cgroup/workload.slice/test-workload0.scope -a \ + -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.procs -a \ + -w /sys/fs/cgroup/workload.slice/test-workload0.scope/cgroup.subtree_control + +# Verify that DelegateSubgroup= affects ownership correctly +unit="test-subgroup-$RANDOM.service" +systemd-run --wait \ + --unit="$unit" \ + --property="DynamicUser=1" \ + --property="Delegate=pids" \ + --property="DelegateSubgroup=foo" \ + test -w "/sys/fs/cgroup/system.slice/$unit" -a \ + -w "/sys/fs/cgroup/system.slice/$unit/foo" + +# Check that for the subgroup also attributes that aren't covered by +# regular (i.e. main cgroup) delegation ownership rules are delegated properly +if test -f /sys/fs/cgroup/cgroup.max.depth; then + unit="test-subgroup-$RANDOM.service" + systemd-run --wait \ + --unit="$unit" \ + --property="DynamicUser=1" \ + --property="Delegate=pids" \ + --property="DelegateSubgroup=zzz" \ + test -w "/sys/fs/cgroup/system.slice/$unit/zzz/cgroup.max.depth" +fi + +# Check that the invoked process itself is also in the subgroup +unit="test-subgroup-$RANDOM.service" +systemd-run --wait \ + --unit="$unit" \ + --property="DynamicUser=1" \ + --property="Delegate=pids" \ + --property="DelegateSubgroup=bar" \ + grep -q -x -F "0::/system.slice/$unit/bar" /proc/self/cgroup diff --git a/test/units/testsuite-19.service b/test/units/testsuite-19.service new file mode 100644 index 0000000..9ee5fc9 --- /dev/null +++ b/test/units/testsuite-19.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-19-DELEGATE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-19.sh b/test/units/testsuite-19.sh new file mode 100755 index 0000000..9c2a033 --- /dev/null +++ b/test/units/testsuite-19.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok diff --git a/test/units/testsuite-21.service b/test/units/testsuite-21.service new file mode 100644 index 0000000..a5f77d0 --- /dev/null +++ b/test/units/testsuite-21.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Fuzz our D-Bus interfaces with dfuzzer +After=dbus.service multi-user.target +Wants=dbus.service multi-user.target + +[Service] +ExecStartPre=rm -f /failed /skipped /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-21.sh b/test/units/testsuite-21.sh new file mode 100755 index 0000000..02673ab --- /dev/null +++ b/test/units/testsuite-21.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Save the end.service state before we start fuzzing, as it might get changed +# on the fly by one of the fuzzers +systemctl list-jobs | grep -F 'end.service' && SHUTDOWN_AT_EXIT=1 || SHUTDOWN_AT_EXIT=0 + +# shellcheck disable=SC2317 +at_exit() { + set +e + # We have to call the end.service/poweroff explicitly even if it's specified on + # the kernel cmdline via systemd.wants=end.service, since dfuzzer calls + # org.freedesktop.systemd1.Manager.ClearJobs() which drops the service + # from the queue + if [[ $SHUTDOWN_AT_EXIT -ne 0 ]] && ! systemctl poweroff; then + # PID1 is down let's try to save the journal + journalctl --sync # journal can be down as well so let's ignore exit codes here + systemctl -ff poweroff # sync() and reboot(RB_POWER_OFF) + fi +} + +trap at_exit EXIT + +systemctl log-level info + +# FIXME: systemd-run doesn't play well with daemon-reexec +# See: https://github.com/systemd/systemd/issues/27204 +sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:Reexecute FIXME' /etc/dfuzzer.conf +sed -i '/\[org.freedesktop.systemd1\]/aorg.freedesktop.systemd1.Manager:SoftReboot destructive' /etc/dfuzzer.conf + +# TODO +# * check for possibly newly introduced buses? +BUS_LIST=( + org.freedesktop.home1 + org.freedesktop.hostname1 + org.freedesktop.import1 + org.freedesktop.locale1 + org.freedesktop.login1 + org.freedesktop.machine1 + org.freedesktop.portable1 + org.freedesktop.resolve1 + org.freedesktop.systemd1 + org.freedesktop.timedate1 +) + +# systemd-oomd requires PSI +if tail -n +1 /proc/pressure/{cpu,io,memory}; then + BUS_LIST+=( + org.freedesktop.oom1 + ) +fi + +# Some services require specific conditions: +# - systemd-timesyncd can't run in a container +# - systemd-networkd can run in a container if it has CAP_NET_ADMIN capability +if ! systemd-detect-virt --container; then + BUS_LIST+=( + org.freedesktop.network1 + org.freedesktop.timesync1 + ) +elif busctl introspect org.freedesktop.network1 / &>/dev/null; then + BUS_LIST+=( + org.freedesktop.network1 + ) +fi + +SESSION_BUS_LIST=( + org.freedesktop.systemd1 +) + +# Maximum payload size generated by dfuzzer (in bytes) - default: 50K +PAYLOAD_MAX=50000 +# Tweak the maximum payload size if we're running under sanitizers, since +# with larger payloads we start hitting reply timeouts +if [[ -v ASAN_OPTIONS || -v UBSAN_OPTIONS ]]; then + PAYLOAD_MAX=10000 # 10K +fi + +# Overmount /var/lib/machines with a size-limited tmpfs, as fuzzing +# the org.freedesktop.machine1 stuff makes quite a mess +mount -t tmpfs -o size=50M tmpfs /var/lib/machines + +# Fuzz both the system and the session buses (where applicable) +for bus in "${BUS_LIST[@]}"; do + echo "Bus: $bus (system)" + systemd-run --pipe --wait \ + -- dfuzzer -b "$PAYLOAD_MAX" -n "$bus" + + # Let's reload the systemd daemon to test (de)serialization as well + systemctl daemon-reload + # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved + systemctl daemon-reexec +done + +umount /var/lib/machines + +for bus in "${SESSION_BUS_LIST[@]}"; do + echo "Bus: $bus (session)" + systemd-run --machine 'testuser@.host' --user --pipe --wait \ + -- dfuzzer -b "$PAYLOAD_MAX" -n "$bus" + + # Let's reload the systemd user daemon to test (de)serialization as well + systemctl --machine 'testuser@.host' --user daemon-reload + # FIXME: explicitly trigger reexecute until systemd/systemd#27204 is resolved + systemctl --machine 'testuser@.host' --user daemon-reexec +done + +touch /testok diff --git a/test/units/testsuite-22.01.sh b/test/units/testsuite-22.01.sh new file mode 100755 index 0000000..2276b75 --- /dev/null +++ b/test/units/testsuite-22.01.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# With "e" don't attempt to set permissions when file doesn't exist, see +# https://github.com/systemd/systemd/pull/6682. +set -eux +set -o pipefail + +rm -fr /tmp/test + +echo "e /tmp/test - root root 1d" | systemd-tmpfiles --create - + +test ! -e /tmp/test diff --git a/test/units/testsuite-22.02.sh b/test/units/testsuite-22.02.sh new file mode 100755 index 0000000..b883a96 --- /dev/null +++ b/test/units/testsuite-22.02.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Basic tests for types creating directories +set -eux +set -o pipefail + +rm -fr /tmp/{C,d,D,e} +mkdir /tmp/{C,d,D,e} + +# +# 'd' +# +mkdir /tmp/d/2 +chmod 777 /tmp/d/2 + +systemd-tmpfiles --create - < /tmp/C/3/f1 + +systemd-tmpfiles --create - </tmp/F/truncated +echo "This should be truncated" >/tmp/F/truncated-with-content + +systemd-tmpfiles --create - </tmp/F/rw-fs/foo +(! systemd-tmpfiles --create -) </tmp/F/rw-fs/foo +(! systemd-tmpfiles --create -) < fails. +(! systemd-tmpfiles --create -) </dev/null | grep -qv '^[\?\-]$'; then + TEST_TMPFILES_AGEBY_BTIME=1 +fi + +touch -a --date "2 minutes ago" /tmp/ageby/d1/f1 +touch -m --date "4 minutes ago" /tmp/ageby/d2/f1 + +# Create a bunch of other files. +touch /tmp/ageby/d{1,2}/f{2..4} + +# For "ctime". +touch /tmp/ageby/d3/f1 +chmod +x /tmp/ageby/d3/f1 +sleep 1 + +# For "btime". +touch /tmp/ageby/d4/f1 +sleep 1 + +# More files with recent "{a,b}time" values. +touch /tmp/ageby/d{3,4}/f{2..4} + +# Check for cleanup of "f1" in each of "/tmp/d{1..4}". +systemd-tmpfiles --clean - <<-EOF +d /tmp/ageby/d1 - - - a:1m - +e /tmp/ageby/d2 - - - m:3m - +D /tmp/ageby/d3 - - - c:2s - +EOF + +for d in d{1..3}; do + test ! -f "/tmp/ageby/${d}/f1" +done + +if [[ $TEST_TMPFILES_AGEBY_BTIME -gt 0 ]]; then + systemd-tmpfiles --clean - <<-EOF +d /tmp/ageby/d4 - - - b:1s - +EOF + + test ! -f "/tmp/ageby/d4/f1" +else + # Remove the file manually. + rm "/tmp/ageby/d4/f1" +fi + +# Check for an invalid "age" and "age-by" arguments. +for a in ':' ':1s' '2:1h' 'nope:42h' '" :7m"' 'm:' '::' '"+r^w-x:2/h"' 'b ar::64'; do + systemd-tmpfiles --clean - <&1 | grep -q -F 'Invalid age' +d /tmp/ageby - - - ${a} - +EOF +done + +for d in d{1..4}; do + for f in f{2..4}; do + test -f "/tmp/ageby/${d}/${f}" + done +done + +# Check for parsing with whitespace, repeated values +# for "age-by" (valid arguments). +for a in '" a:24h"' 'cccaab:2h' '" aa : 4h"' '" a A B C c:1h"'; do + systemd-tmpfiles --clean - </usr/lib/tmpfiles.d/L-z.conf</etc/tmpfiles.d/L-z.conf</usr/lib/tmpfiles.d/L-a.conf</etc/tmpfiles.d/L-a.conf</usr/lib/tmpfiles.d/w-$i.conf</etc/tmpfiles.d/w-$i.conf</usr/lib/tmpfiles.d/w-b.conf</tmp/testsuite-23.counter + +if [ "$counter" -eq 5 ] ; then + systemctl kill --kill-whom=main -sUSR1 testsuite-23.service +fi + +exec sleep 1.5 diff --git a/test/units/testsuite-23.ExecReload.sh b/test/units/testsuite-23.ExecReload.sh new file mode 100755 index 0000000..b497f73 --- /dev/null +++ b/test/units/testsuite-23.ExecReload.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Test ExecReload= (PR #13098) + +systemd-analyze log-level debug + +export SYSTEMD_PAGER= +SERVICE_PATH="$(mktemp /etc/systemd/system/execreloadXXX.service)" +SERVICE_NAME="${SERVICE_PATH##*/}" + +echo "[#1] Failing ExecReload= should not kill the service" +cat >"$SERVICE_PATH" <"$SERVICE_PATH" <"$SERVICE_PATH" </tmp/forking1.sh </tmp/forking2.sh </tmp/notify1.sh <&2' +cmp /tmp/stdout <&2' +cmp /tmp/stdout <&2' +cmp /tmp/stdout <&2' +cmp /tmp/stdout </run/systemd/system/test-service.service </run/systemd/system/test-service.service </run/systemd/system/tmp-hoge.mount </run/systemd/system/test-service.socket <$TMP_FILE + +# test two: make sure StartLimitBurst correctly limits the number of restarts +# and restarts execution of the unit from the first ExecStart= +(! systemd-run --unit=oneshot-restart-two \ + -p StartLimitIntervalSec=120 \ + -p StartLimitBurst=3 \ + -p Type=oneshot \ + -p Restart=on-failure \ + -p ExecStart="/bin/bash -c \"printf a >>$TMP_FILE\"" /bin/bash -c "exit 1") + +# wait for at least 3 restarts +for ((secs = 0; secs < MAX_SECS; secs++)); do + [[ $(cat $TMP_FILE) != "aaa" ]] || break + sleep 1 +done +if [[ $(cat $TMP_FILE) != "aaa" ]]; then + exit 1 +fi + +# wait for 5 more seconds to make sure there aren't excess restarts +sleep 5 +if [[ $(cat $TMP_FILE) != "aaa" ]]; then + exit 1 +fi + +systemd-analyze log-level info diff --git a/test/units/testsuite-23.percentj-wantedby.sh b/test/units/testsuite-23.percentj-wantedby.sh new file mode 100755 index 0000000..e9ffaba --- /dev/null +++ b/test/units/testsuite-23.percentj-wantedby.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -eux +set -o pipefail + +# Ensure %j Wants directives work +systemd-run --wait \ + --property="Type=oneshot" \ + --property="Wants=testsuite-23-specifier-j-wants.service" \ + --property="After=testsuite-23-specifier-j-wants.service" \ + true + +test -f /tmp/tetsuite-23-specifier-j-done diff --git a/test/units/testsuite-23.runtime-bind-paths.sh b/test/units/testsuite-23.runtime-bind-paths.sh new file mode 100755 index 0000000..65c2dbf --- /dev/null +++ b/test/units/testsuite-23.runtime-bind-paths.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# Test adding new BindPaths while unit is already running + +at_exit() { + set +e + + rm -f /run/testsuite-23-marker-{fixed,runtime} + rm -fr /run/inaccessible +} + +trap at_exit EXIT + +echo "MARKER_FIXED" >/run/testsuite-23-marker-fixed +mkdir /run/inaccessible + +systemctl start testsuite-23-namespaced.service + +# Ensure that inaccessible paths aren't bypassed by the runtime setup, +(! systemctl bind --mkdir testsuite-23-namespaced.service /run/testsuite-23-marker-fixed /run/inaccessible/testfile-marker-fixed) + +echo "MARKER_WRONG" >/run/testsuite-23-marker-wrong +echo "MARKER_RUNTIME" >/run/testsuite-23-marker-runtime + +# Mount twice to exercise mount-beneath (on kernel 6.5+, on older kernels it will just overmount) +systemctl bind --mkdir testsuite-23-namespaced.service /run/testsuite-23-marker-wrong /tmp/testfile-marker-runtime +test "$(systemctl show -P SubState testsuite-23-namespaced.service)" = "running" +systemctl bind --mkdir testsuite-23-namespaced.service /run/testsuite-23-marker-runtime /tmp/testfile-marker-runtime + +timeout 10 bash -xec 'while [[ "$(systemctl show -P SubState testsuite-23-namespaced.service)" == running ]]; do sleep .5; done' +systemctl is-active testsuite-23-namespaced.service + +# Now test that systemctl bind fails when attempted on a non-namespaced unit +systemctl start testsuite-23-non-namespaced.service + +(! systemctl bind --mkdir testsuite-49-non-namespaced.service /run/testsuite-23-marker-runtime /tmp/testfile-marker-runtime) + +timeout 10 bash -xec 'while [[ "$(systemctl show -P SubState testsuite-23-non-namespaced.service)" == running ]]; do sleep .5; done' +(! systemctl is-active testsuite-23-non-namespaced.service) diff --git a/test/units/testsuite-23.service b/test/units/testsuite-23.service new file mode 100644 index 0000000..26f5226 --- /dev/null +++ b/test/units/testsuite-23.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-23-TYPE-EXEC + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-23.sh b/test/units/testsuite-23.sh new file mode 100755 index 0000000..a929c8b --- /dev/null +++ b/test/units/testsuite-23.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +# Note: the signal shenanigans are necessary for the Upholds= tests +run_subtests_with_signals SIGUSR1 SIGUSR2 SIGRTMIN+1 + +touch /testok diff --git a/test/units/testsuite-23.start-stop-no-reload.sh b/test/units/testsuite-23.start-stop-no-reload.sh new file mode 100755 index 0000000..9c4f17d --- /dev/null +++ b/test/units/testsuite-23.start-stop-no-reload.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -eux +set -o pipefail + +# Test start & stop operations without daemon-reload + +at_exit() { + set +e + + rm -f /run/systemd/system/testsuite-23-no-reload.{service,target} +} + +trap at_exit EXIT + +cat >/run/systemd/system/testsuite-23-no-reload.target </run/systemd/system/testsuite-23-no-reload.service </run/systemd/system/testsuite-23-no-reload.service </run/systemd/system/testsuite-23-no-reload.target </run/systemd/system/testsuite-23-no-reload.service <&2 "Found units in failed state" + exit 1 +fi + +at_exit() { + set +e + + mountpoint -q /proc/cmdline && umount /proc/cmdline + rm -f /etc/crypttab + [[ -e /tmp/crypttab.bak ]] && cp -fv /tmp/crypttab.bak /etc/crypttab + [[ -n "${STORE_LOOP:-}" ]] && losetup -d "$STORE_LOOP" + [[ -n "${WORKDIR:-}" ]] && rm -rf "$WORKDIR" + + systemctl daemon-reload +} + +trap at_exit EXIT + +cryptsetup_start_and_check() { + local expect_fail=0 + local ec volume unit + + if [[ "${1:?}" == "-f" ]]; then + expect_fail=1 + shift + fi + + for volume in "$@"; do + unit="systemd-cryptsetup@$volume.service" + + # The unit existence check should always pass + [[ "$(systemctl show -P LoadState "$unit")" == loaded ]] + systemctl list-unit-files "$unit" + + systemctl start "$unit" && ec=0 || ec=$? + if [[ "$expect_fail" -ne 0 ]]; then + if [[ "$ec" -eq 0 ]]; then + echo >&2 "Unexpected pass when starting $unit" + return 1 + fi + + return 0 + fi + + if [[ "$ec" -ne 0 ]]; then + echo >&2 "Unexpected fail when starting $unit" + return 1 + fi + + systemctl status "$unit" + test -e "/dev/mapper/$volume" + systemctl stop "$unit" + test ! -e "/dev/mapper/$volume" + done + + return 0 +} + +# Note: some stuff (especially TPM-related) is already tested by TEST-70-TPM2, +# so focus more on other areas instead + +# Use a common workdir to make the cleanup easier +WORKDIR="$(mktemp -d)" + +# Prepare a couple of LUKS2-encrypted disk images +# +# 1) Image with an empty password +IMAGE_EMPTY="$WORKDIR/empty.img)" +IMAGE_EMPTY_KEYFILE="$WORKDIR/empty.keyfile" +IMAGE_EMPTY_KEYFILE_ERASE="$WORKDIR/empty-erase.keyfile" +IMAGE_EMPTY_KEYFILE_ERASE_FAIL="$WORKDIR/empty-erase-fail.keyfile)" +truncate -s 32M "$IMAGE_EMPTY" +echo -n passphrase >"$IMAGE_EMPTY_KEYFILE" +chmod 0600 "$IMAGE_EMPTY_KEYFILE" +cryptsetup luksFormat --batch-mode \ + --pbkdf pbkdf2 \ + --pbkdf-force-iterations 1000 \ + --use-urandom \ + "$IMAGE_EMPTY" "$IMAGE_EMPTY_KEYFILE" +PASSWORD=passphrase NEWPASSWORD="" systemd-cryptenroll --password "$IMAGE_EMPTY" +# Duplicate the key file to test keyfile-erase as well +cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE" +# The key should get erased even on a failed attempt, so test that too +cp -v "$IMAGE_EMPTY_KEYFILE" "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" + +# 2) Image with a detached header and a key file offset + size +IMAGE_DETACHED="$WORKDIR/detached.img" +IMAGE_DETACHED_KEYFILE="$WORKDIR/detached.keyfile" +IMAGE_DETACHED_KEYFILE2="$WORKDIR/detached.keyfile2" +IMAGE_DETACHED_HEADER="$WORKDIR/detached.header" +truncate -s 32M "$IMAGE_DETACHED" +dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE" count=64 bs=1 +dd if=/dev/urandom of="$IMAGE_DETACHED_KEYFILE2" count=32 bs=1 +chmod 0600 "$IMAGE_DETACHED_KEYFILE" "$IMAGE_DETACHED_KEYFILE2" +cryptsetup luksFormat --batch-mode \ + --pbkdf pbkdf2 \ + --pbkdf-force-iterations 1000 \ + --use-urandom \ + --header "$IMAGE_DETACHED_HEADER" \ + --keyfile-offset 32 \ + --keyfile-size 16 \ + "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE" +# Also, add a second key file to key slot 8 +# Note: --key-slot= behaves as --new-key-slot= when used alone for backwards compatibility +cryptsetup luksAddKey --batch-mode \ + --header "$IMAGE_DETACHED_HEADER" \ + --key-file "$IMAGE_DETACHED_KEYFILE" \ + --keyfile-offset 32 \ + --keyfile-size 16 \ + --key-slot 8 \ + "$IMAGE_DETACHED" "$IMAGE_DETACHED_KEYFILE2" + +# Prepare a couple of dummy devices we'll store a copy of the detached header +# and one of the keys on to test if systemd-cryptsetup correctly mounts them +# when necessary +STORE_IMAGE="$WORKDIR/store.img" +truncate -s 64M "$STORE_IMAGE" +STORE_LOOP="$(losetup --show --find --partscan "$STORE_IMAGE")" +sfdisk "$STORE_LOOP" </etc/crypttab </tmp/cmdline.tmp +mount --bind /tmp/cmdline.tmp /proc/cmdline +# Run the systemd-cryptsetup-generator once explicitly, to collect coverage, +# as during daemon-reload we run generators in a sandbox +mkdir -p /tmp/systemd-cryptsetup-generator.out +/usr/lib/systemd/system-generators/systemd-cryptsetup-generator /tmp/systemd-cryptsetup-generator.out/ +systemctl daemon-reload +systemctl list-unit-files "systemd-cryptsetup@*" + +cryptsetup_start_and_check empty_key +test -e "$IMAGE_EMPTY_KEYFILE_ERASE" +cryptsetup_start_and_check empty_key_erase +test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE" +test -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" +cryptsetup_start_and_check -f empty_key_erase_fail +test ! -e "$IMAGE_EMPTY_KEYFILE_ERASE_FAIL" +cryptsetup_start_and_check -f empty_fail{0..1} +cryptsetup_start_and_check empty{0..1} +# First, check if we correctly fail without any key +cryptsetup_start_and_check -f empty_nokey +# And now provide the key via /{etc,run}/cryptsetup-keys.d/ +mkdir -p /run/cryptsetup-keys.d +cp "$IMAGE_EMPTY_KEYFILE" /run/cryptsetup-keys.d/empty_nokey.key +cryptsetup_start_and_check empty_nokey + +cryptsetup_start_and_check detached +cryptsetup_start_and_check detached_store{0..2} +cryptsetup_start_and_check -f detached_fail{0..4} +cryptsetup_start_and_check detached_slot{0..1} +cryptsetup_start_and_check -f detached_slot_fail + +touch /testok diff --git a/test/units/testsuite-25.service b/test/units/testsuite-25.service new file mode 100644 index 0000000..503eabb --- /dev/null +++ b/test/units/testsuite-25.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-25-IMPORT + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-25.sh b/test/units/testsuite-25.sh new file mode 100755 index 0000000..b298c50 --- /dev/null +++ b/test/units/testsuite-25.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +export SYSTEMD_PAGER=cat + +dd if=/dev/urandom of=/var/tmp/testimage.raw bs=$((1024*1024+7)) count=5 + +# Test import +machinectl import-raw /var/tmp/testimage.raw +machinectl image-status testimage +test -f /var/lib/machines/testimage.raw +cmp /var/tmp/testimage.raw /var/lib/machines/testimage.raw + +# Test export +machinectl export-raw testimage /var/tmp/testimage2.raw +cmp /var/tmp/testimage.raw /var/tmp/testimage2.raw +rm /var/tmp/testimage2.raw + +# Test compressed export (gzip) +machinectl export-raw testimage /var/tmp/testimage2.raw.gz +gunzip /var/tmp/testimage2.raw.gz +cmp /var/tmp/testimage.raw /var/tmp/testimage2.raw +rm /var/tmp/testimage2.raw + +# Test clone +machinectl clone testimage testimage3 +test -f /var/lib/machines/testimage3.raw +machinectl image-status testimage3 +test -f /var/lib/machines/testimage.raw +machinectl image-status testimage +cmp /var/tmp/testimage.raw /var/lib/machines/testimage.raw +cmp /var/tmp/testimage.raw /var/lib/machines/testimage3.raw + +# Test removal +machinectl remove testimage +test ! -f /var/lib/machines/testimage.raw +(! machinectl image-status testimage) + +# Test export of clone +machinectl export-raw testimage3 /var/tmp/testimage3.raw +cmp /var/tmp/testimage.raw /var/tmp/testimage3.raw +rm /var/tmp/testimage3.raw + +# Test rename +machinectl rename testimage3 testimage4 +test -f /var/lib/machines/testimage4.raw +machinectl image-status testimage4 +test ! -f /var/lib/machines/testimage3.raw +(! machinectl image-status testimage3) +cmp /var/tmp/testimage.raw /var/lib/machines/testimage4.raw + +# Test export of rename +machinectl export-raw testimage4 /var/tmp/testimage4.raw +cmp /var/tmp/testimage.raw /var/tmp/testimage4.raw +rm /var/tmp/testimage4.raw + +# Test removal +machinectl remove testimage4 +test ! -f /var/lib/machines/testimage4.raw +(! machinectl image-status testimage4) + +# → And now, let's test directory trees ← # + +# Set up a directory we can import +mkdir /var/tmp/scratch +mv /var/tmp/testimage.raw /var/tmp/scratch/ +touch /var/tmp/scratch/anotherfile +mkdir /var/tmp/scratch/adirectory +echo "piep" >/var/tmp/scratch/adirectory/athirdfile + +# Test import-fs +machinectl import-fs /var/tmp/scratch/ +test -d /var/lib/machines/scratch +machinectl image-status scratch + +# Test export-tar +machinectl export-tar scratch /var/tmp/scratch.tar.gz +test -f /var/tmp/scratch.tar.gz +mkdir /var/tmp/extract +(cd /var/tmp/extract ; tar xzf /var/tmp/scratch.tar.gz) +diff -r /var/tmp/scratch/ /var/tmp/extract/ +rm -rf /var/tmp/extract + +# Test import-tar +machinectl import-tar /var/tmp/scratch.tar.gz scratch2 +test -d /var/lib/machines/scratch2 +machinectl image-status scratch2 +diff -r /var/tmp/scratch/ /var/lib/machines/scratch2 + +# Test removal +machinectl remove scratch +test ! -f /var/lib/machines/scratch +(! machinectl image-status scratch) + +# Test clone +machinectl clone scratch2 scratch3 +test -d /var/lib/machines/scratch2 +machinectl image-status scratch2 +test -d /var/lib/machines/scratch3 +machinectl image-status scratch3 +diff -r /var/tmp/scratch/ /var/lib/machines/scratch3 + +# Test removal +machinectl remove scratch2 +test ! -f /var/lib/machines/scratch2 +(! machinectl image-status scratch2) + +# Test rename +machinectl rename scratch3 scratch4 +test -d /var/lib/machines/scratch4 +machinectl image-status scratch4 +test ! -f /var/lib/machines/scratch3 +(! machinectl image-status scratch3) +diff -r /var/tmp/scratch/ /var/lib/machines/scratch4 + +# Test removal +machinectl remove scratch4 +test ! -f /var/lib/machines/scratch4 +(! machinectl image-status scratch4) + +# Test import-tar hyphen/stdin pipe behavior +# shellcheck disable=SC2002 +cat /var/tmp/scratch.tar.gz | machinectl import-tar - scratch5 +test -d /var/lib/machines/scratch5 +machinectl image-status scratch5 +diff -r /var/tmp/scratch/ /var/lib/machines/scratch5 + +# Test export-tar hyphen/stdout pipe behavior +mkdir -p /var/tmp/extract +machinectl export-tar scratch5 - | tar xvf - -C /var/tmp/extract/ +diff -r /var/tmp/scratch/ /var/tmp/extract/ +rm -rf /var/tmp/extract + +rm -rf /var/tmp/scratch + +# Test removal +machinectl remove scratch5 +test ! -f /var/lib/machines/scratch5 +(! machinectl image-status scratch5) + +touch /testok diff --git a/test/units/testsuite-26.service b/test/units/testsuite-26.service new file mode 100644 index 0000000..d8fdaff --- /dev/null +++ b/test/units/testsuite-26.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-26-SYSTEMCTL + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-26.sh b/test/units/testsuite-26.sh new file mode 100755 index 0000000..1e11c42 --- /dev/null +++ b/test/units/testsuite-26.sh @@ -0,0 +1,465 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + if [[ -v UNIT_NAME && -e "/usr/lib/systemd/system/$UNIT_NAME" ]]; then + rm -fvr "/usr/lib/systemd/system/$UNIT_NAME" "/etc/systemd/system/$UNIT_NAME.d" "+4" + fi + + rm -f /etc/init.d/issue-24990 + return 0 +} + +trap at_exit EXIT + +# Create a simple unit file for testing +# Note: the service file is created under /usr on purpose to test +# the 'revert' verb as well +export UNIT_NAME="systemctl-test-$RANDOM.service" +cat >"/usr/lib/systemd/system/$UNIT_NAME" <<\EOF +[Unit] +Description=systemctl test + +[Service] +ExecStart=sleep infinity +ExecReload=true + +# For systemctl clean +CacheDirectory=%n +ConfigurationDirectory=%n +LogsDirectory=%n +RuntimeDirectory=%n +StateDirectory=%n + +[Install] +WantedBy=multi-user.target +EOF + +# Configure the preset setting for the unit file +mkdir /run/systemd/system-preset/ +echo "disable $UNIT_NAME" >/run/systemd/system-preset/99-systemd-test.preset + +EDITOR='true' script -ec 'systemctl edit "$UNIT_NAME"' /dev/null +[ ! -e "/etc/systemd/system/$UNIT_NAME.d/override.conf" ] + +printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=sleep 10d' >"+4" +EDITOR='mv' script -ec 'systemctl edit "$UNIT_NAME"' /dev/null +printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=sleep 10d' | cmp - "/etc/systemd/system/$UNIT_NAME.d/override.conf" + +printf '%b' '[Service]\n' 'ExecStart=\n' 'ExecStart=sleep 10d' >"+4" +EDITOR='mv' script -ec 'systemctl edit "$UNIT_NAME"' /dev/null +printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=sleep 10d' | cmp - "/etc/systemd/system/$UNIT_NAME.d/override.conf" + +# Double free when editing a template unit (#26483) +EDITOR='true' script -ec 'systemctl edit user@0' /dev/null + +# Argument help +systemctl --state help +systemctl --signal help +systemctl --type help + +# list-dependencies +systemctl list-dependencies systemd-journald +systemctl list-dependencies --after systemd-journald +systemctl list-dependencies --before systemd-journald +systemctl list-dependencies --after --reverse systemd-journald +systemctl list-dependencies --before --reverse systemd-journald +systemctl list-dependencies --plain systemd-journald + +# list-* verbs +systemctl list-units +systemctl list-units --recursive +systemctl list-units --type=socket +systemctl list-units --type=service,timer +# Compat: --type= allows load states for compatibility reasons +systemctl list-units --type=loaded +systemctl list-units --type=loaded,socket +systemctl list-units --legend=yes -a "systemd-*" +systemctl list-units --state=active +systemctl list-units --with-dependencies systemd-journald.service +systemctl list-units --with-dependencies --after systemd-journald.service +systemctl list-units --with-dependencies --before --reverse systemd-journald.service +systemctl list-sockets +systemctl list-sockets --legend=no -a "*journal*" +systemctl list-sockets --show-types +systemctl list-sockets --state=listening +systemctl list-timers -a -l +systemctl list-jobs +systemctl list-jobs --after +systemctl list-jobs --before +systemctl list-jobs --after --before +systemctl list-jobs "*" +systemctl list-dependencies sysinit.target --type=socket,mount +systemctl list-dependencies multi-user.target --state=active +systemctl list-dependencies sysinit.target --state=mounted --all +systemctl list-paths +systemctl list-paths --legend=no -a "systemd*" + +test_list_unit_files() { + systemctl list-unit-files "$@" + systemctl list-unit-files "$@" "*journal*" +} + +test_list_unit_files +test_list_unit_files --root=/ + +# is-* verbs +# Should return 4 for a missing unit file +assert_rc 4 systemctl --quiet is-active not-found.service +assert_rc 4 systemctl --quiet is-failed not-found.service +assert_rc 4 systemctl --quiet is-enabled not-found.service +# is-active: return 3 when the unit exists but inactive +assert_rc 3 systemctl --quiet is-active "$UNIT_NAME" +# is-enabled: return 1 when the unit exists but disabled +assert_rc 1 systemctl --quiet is-enabled "$UNIT_NAME" + +# Basic service management +systemctl start --show-transaction "$UNIT_NAME" +systemctl status -n 5 "$UNIT_NAME" +systemctl is-active "$UNIT_NAME" +systemctl reload -T "$UNIT_NAME" +systemctl restart -T "$UNIT_NAME" +systemctl try-restart --show-transaction "$UNIT_NAME" +systemctl try-reload-or-restart --show-transaction "$UNIT_NAME" +systemctl kill "$UNIT_NAME" +(! systemctl is-active "$UNIT_NAME") +systemctl restart "$UNIT_NAME" +systemctl is-active "$UNIT_NAME" +systemctl restart "$UNIT_NAME" +systemctl stop "$UNIT_NAME" +(! systemctl is-active "$UNIT_NAME") + +assert_eq "$(systemctl is-system-running)" "$(systemctl is-failed)" + +# enable/disable/preset +test_enable_disable_preset() { + (! systemctl is-enabled "$@" "$UNIT_NAME") + systemctl enable "$@" "$UNIT_NAME" + systemctl is-enabled "$@" -l "$UNIT_NAME" + # We created a preset file for this unit above with a "disable" policy + systemctl preset "$@" "$UNIT_NAME" + (! systemctl is-enabled "$@" "$UNIT_NAME") + systemctl reenable "$@" "$UNIT_NAME" + systemctl is-enabled "$@" "$UNIT_NAME" + systemctl preset "$@" --preset-mode=enable-only "$UNIT_NAME" + systemctl is-enabled "$@" "$UNIT_NAME" + systemctl preset "$@" --preset-mode=disable-only "$UNIT_NAME" + (! systemctl is-enabled "$@" "$UNIT_NAME") + systemctl enable "$@" --runtime "$UNIT_NAME" + [[ -e "/run/systemd/system/multi-user.target.wants/$UNIT_NAME" ]] + systemctl is-enabled "$@" "$UNIT_NAME" + systemctl disable "$@" "$UNIT_NAME" + # The unit should be still enabled, as we didn't use the --runtime switch + systemctl is-enabled "$@" "$UNIT_NAME" + systemctl disable "$@" --runtime "$UNIT_NAME" + (! systemctl is-enabled "$@" "$UNIT_NAME") +} + +test_enable_disable_preset +test_enable_disable_preset --root=/ + +# mask/unmask/revert +test_mask_unmask_revert() { + systemctl disable "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]] + systemctl mask "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]] + systemctl unmask "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]] + systemctl mask "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked ]] + systemctl revert "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]] + systemctl mask "$@" --runtime "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]] + # This should be a no-op without the --runtime switch + systemctl unmask "$@" "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == masked-runtime ]] + systemctl unmask "$@" --runtime "$UNIT_NAME" + [[ "$(systemctl is-enabled "$@" "$UNIT_NAME")" == disabled ]] +} + +test_mask_unmask_revert +test_mask_unmask_revert --root=/ + +# add-wants/add-requires +(! systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service") +systemctl add-wants "$UNIT_NAME" "systemd-journald.service" +systemctl show -P Wants "$UNIT_NAME" | grep "systemd-journald.service" +(! systemctl show -P Requires "$UNIT_NAME" | grep "systemd-journald.service") +systemctl add-requires "$UNIT_NAME" "systemd-journald.service" +systemctl show -P Requires "$UNIT_NAME" | grep "systemd-journald.service" + +# set-property +systemctl set-property "$UNIT_NAME" IPAccounting=yes MemoryMax=1234567 +systemctl cat "$UNIT_NAME" +# These properties should be saved to a persistent storage +grep -r "IPAccounting=yes" "/etc/systemd/system.control/${UNIT_NAME}.d/" +grep -r "MemoryMax=1234567" "/etc/systemd/system.control/${UNIT_NAME}.d" +systemctl revert "$UNIT_NAME" +(! grep -r "IPAccounting=" "/etc/systemd/system.control/${UNIT_NAME}.d/") +(! grep -r "MemoryMax=" "/etc/systemd/system.control/${UNIT_NAME}.d/") +# Same stuff, but with --runtime, which should use /run +systemctl set-property --runtime "$UNIT_NAME" CPUAccounting=no CPUQuota=10% +systemctl cat "$UNIT_NAME" +grep -r "CPUAccounting=no" "/run/systemd/system.control/${UNIT_NAME}.d/" +grep -r "CPUQuota=10%" "/run/systemd/system.control/${UNIT_NAME}.d/" +systemctl revert "$UNIT_NAME" +(! grep -r "CPUAccounting=" "/run/systemd/system.control/${UNIT_NAME}.d/") +(! grep -r "CPUQuota=" "/run/systemd/system.control/${UNIT_NAME}.d/") + +# Failed-unit related tests +(! systemd-run --wait --unit "failed.service" /bin/false) +systemctl is-failed failed.service +systemctl --state=failed | grep failed.service +systemctl --failed | grep failed.service +systemctl reset-failed "fail*.service" +(! systemctl is-failed failed.service) + +# clean +systemctl restart "$UNIT_NAME" +systemctl stop "$UNIT_NAME" +# Check if the directories from *Directory= directives exist +# (except RuntimeDirectory= in /run, which is removed when the unit is stopped) +for path in /var/lib /var/cache /var/log /etc; do + [[ -e "$path/$UNIT_NAME" ]] +done +# Run the cleanup +for what in "" configuration state cache logs runtime all; do + systemctl clean ${what:+--what="$what"} "$UNIT_NAME" +done +# All respective directories should be removed +for path in /run /var/lib /var/cache /var/log /etc; do + [[ ! -e "$path/$UNIT_NAME" ]] +done + +# --timestamp +for value in pretty us µs utc us+utc µs+utc; do + systemctl show -P KernelTimestamp --timestamp="$value" +done + +# set-default/get-default +test_get_set_default() { + target="$(systemctl get-default "$@")" + systemctl set-default "$@" emergency.target + [[ "$(systemctl get-default "$@")" == emergency.target ]] + systemctl set-default "$@" "$target" + [[ "$(systemctl get-default "$@")" == "$target" ]] +} + +test_get_set_default +test_get_set_default --root=/ + +# show/status +systemctl show --property "" +# Pick a heavily sandboxed unit for the best effect on coverage +systemctl show systemd-logind.service +systemctl status +# Ignore the exit code in this case, as it might try to load non-existing units +systemctl status -a >/dev/null || : +systemctl status -a --state active,running,plugged >/dev/null +systemctl status "systemd-*.timer" +systemctl status "systemd-journald*.socket" +systemctl status "sys-devices-*-ttyS0.device" +systemctl status -- -.mount +systemctl status 1 + +# --marked +systemctl restart "$UNIT_NAME" +systemctl set-property "$UNIT_NAME" Markers=needs-restart +systemctl show -P Markers "$UNIT_NAME" | grep needs-restart +systemctl reload-or-restart --marked +(! systemctl show -P Markers "$UNIT_NAME" | grep needs-restart) + +# --dry-run with destructive verbs +# kexec is skipped intentionally, as it requires a bit more involved setup +VERBS=( + default + emergency + exit + halt + hibernate + hybrid-sleep + poweroff + reboot + rescue + suspend + suspend-then-hibernate +) + +for verb in "${VERBS[@]}"; do + systemctl --dry-run "$verb" + + if [[ "$verb" =~ (halt|poweroff|reboot) ]]; then + systemctl --dry-run --message "Hello world" "$verb" + systemctl --dry-run --no-wall "$verb" + systemctl --dry-run -f "$verb" + systemctl --dry-run -ff "$verb" + fi +done + +# Aux verbs & assorted checks +systemctl is-active "*-journald.service" +systemctl cat "*journal*" +systemctl cat "$UNIT_NAME" +systemctl help "$UNIT_NAME" +systemctl service-watchdogs +systemctl service-watchdogs "$(systemctl service-watchdogs)" + +# show/set-environment +# Make sure PATH is set +systemctl show-environment | grep -q '^PATH=' +# Let's add an entry and override a built-in one +systemctl set-environment PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/testaddition FOO=BAR +# Check that both are set +systemctl show-environment | grep -q '^PATH=.*testaddition$' +systemctl show-environment | grep -q '^FOO=BAR$' +systemctl daemon-reload +# Check again after the reload +systemctl show-environment | grep -q '^PATH=.*testaddition$' +systemctl show-environment | grep -q '^FOO=BAR$' +# Check that JSON output is supported +systemctl show-environment --output=json | grep -q '^{.*"FOO":"BAR".*}$' +# Drop both +systemctl unset-environment FOO PATH +# Check that one is gone and the other reverted to the built-in +systemctl show-environment | grep '^FOO=$' && exit 1 +systemctl show-environment | grep '^PATH=.*testaddition$' && exit 1 +systemctl show-environment | grep -q '^PATH=' +# Check import-environment +export IMPORT_THIS=hello +export IMPORT_THIS_TOO=world +systemctl import-environment IMPORT_THIS IMPORT_THIS_TOO +systemctl show-environment | grep "^IMPORT_THIS=$IMPORT_THIS" +systemctl show-environment | grep "^IMPORT_THIS_TOO=$IMPORT_THIS_TOO" +systemctl unset-environment IMPORT_THIS IMPORT_THIS_TOO +(! systemctl show-environment | grep "^IMPORT_THIS=") +(! systemctl show-environment | grep "^IMPORT_THIS_TOO=") + +# test for sysv-generator (issue #24990) +if [[ -x /usr/lib/systemd/system-generators/systemd-sysv-generator ]]; then + # This is configurable via -Dsysvinit-path=, but we can't get the value + # at runtime, so let's just support the two most common paths for now. + [[ -d /etc/rc.d/init.d ]] && SYSVINIT_PATH="/etc/rc.d/init.d" || SYSVINIT_PATH="/etc/init.d" + + # invalid dependency + cat >"${SYSVINIT_PATH:?}/issue-24990" <<\EOF +#!/bin/bash + +### BEGIN INIT INFO +# Provides:test1 test2 +# Required-Start:test1 $remote_fs $network +# Required-Stop:test1 $remote_fs $network +# Description:Test +# Short-Description: Test +### END INIT INFO + +case "$1" in + start) + echo "Starting issue-24990.service" + sleep 1000 & + ;; + stop) + echo "Stopping issue-24990.service" + sleep 10 & + ;; + *) + echo "Usage: service test {start|stop|restart|status}" + ;; +esac +EOF + + chmod +x "$SYSVINIT_PATH/issue-24990" + systemctl daemon-reload + [[ -L /run/systemd/generator.late/test1.service ]] + [[ -L /run/systemd/generator.late/test2.service ]] + assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service" + assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service" + output=$(systemctl cat issue-24990) + assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output" + assert_in "Description=LSB: Test" "$output" + assert_in "After=test1.service" "$output" + assert_in "After=remote-fs.target" "$output" + assert_in "After=network-online.target" "$output" + assert_in "Wants=network-online.target" "$output" + assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output" + assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output" + systemctl status issue-24990 || : + systemctl show issue-24990 + assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)" + assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)" + + if ! systemctl is-active network-online.target; then + systemctl start network-online.target + fi + + systemctl restart issue-24990 + systemctl stop issue-24990 + + # valid dependency + cat >"$SYSVINIT_PATH/issue-24990" <<\EOF +#!/bin/bash + +### BEGIN INIT INFO +# Provides:test1 test2 +# Required-Start:$remote_fs +# Required-Stop:$remote_fs +# Description:Test +# Short-Description: Test +### END INIT INFO + +case "$1" in + start) + echo "Starting issue-24990.service" + sleep 1000 & + ;; + stop) + echo "Stopping issue-24990.service" + sleep 10 & + ;; + *) + echo "Usage: service test {start|stop|restart|status}" + ;; +esac +EOF + + chmod +x "$SYSVINIT_PATH/issue-24990" + systemctl daemon-reload + [[ -L /run/systemd/generator.late/test1.service ]] + [[ -L /run/systemd/generator.late/test2.service ]] + assert_eq "$(readlink -f /run/systemd/generator.late/test1.service)" "/run/systemd/generator.late/issue-24990.service" + assert_eq "$(readlink -f /run/systemd/generator.late/test2.service)" "/run/systemd/generator.late/issue-24990.service" + output=$(systemctl cat issue-24990) + assert_in "SourcePath=$SYSVINIT_PATH/issue-24990" "$output" + assert_in "Description=LSB: Test" "$output" + assert_in "After=remote-fs.target" "$output" + assert_in "ExecStart=$SYSVINIT_PATH/issue-24990 start" "$output" + assert_in "ExecStop=$SYSVINIT_PATH/issue-24990 stop" "$output" + systemctl status issue-24990 || : + systemctl show issue-24990 + assert_not_in "issue-24990.service" "$(systemctl show --property=After --value)" + assert_not_in "issue-24990.service" "$(systemctl show --property=Before --value)" + + systemctl restart issue-24990 + systemctl stop issue-24990 +fi + +# %J in WantedBy= causes ABRT (#26467) +cat >/run/systemd/system/test-WantedBy.service </run/systemd/system.conf.d/10-timeout.conf </run/systemd/system/systemd-portabled.service.d/override.conf +[Service] +Environment=SYSTEMD_LOG_LEVEL=debug +EOF + +portablectl "${ARGS[@]}" attach --now --runtime /usr/share/minimal_0.raw minimal-app0 + +portablectl is-attached minimal-app0 +portablectl inspect /usr/share/minimal_0.raw minimal-app0.service +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-foo.service +systemctl is-active minimal-app0-bar.service && exit 1 + +portablectl "${ARGS[@]}" reattach --now --runtime /usr/share/minimal_1.raw minimal-app0 + +portablectl is-attached minimal-app0 +portablectl inspect /usr/share/minimal_0.raw minimal-app0.service +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-bar.service +systemctl is-active minimal-app0-foo.service && exit 1 + +portablectl list | grep -q -F "minimal_1" +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' + +portablectl detach --now --runtime /usr/share/minimal_1.raw minimal-app0 + +portablectl list | grep -q -F "No images." +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 + +# Ensure we don't regress (again) when using --force + +portablectl "${ARGS[@]}" attach --force --now --runtime /usr/share/minimal_0.raw minimal-app0 + +portablectl is-attached --force minimal-app0 +portablectl inspect --force /usr/share/minimal_0.raw minimal-app0.service +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-foo.service +systemctl is-active minimal-app0-bar.service && exit 1 + +portablectl "${ARGS[@]}" reattach --force --now --runtime /usr/share/minimal_1.raw minimal-app0 + +portablectl is-attached --force minimal-app0 +portablectl inspect --force /usr/share/minimal_0.raw minimal-app0.service +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-bar.service +systemctl is-active minimal-app0-foo.service && exit 1 + +portablectl list | grep -q -F "minimal_1" +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' + +portablectl detach --force --now --runtime /usr/share/minimal_1.raw minimal-app0 + +portablectl list | grep -q -F "No images." +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 + +# portablectl also works with directory paths rather than images + +unsquashfs -dest /tmp/minimal_0 /usr/share/minimal_0.raw +unsquashfs -dest /tmp/minimal_1 /usr/share/minimal_1.raw + +portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/minimal_0 minimal-app0 + +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-foo.service +systemctl is-active minimal-app0-bar.service && exit 1 + +portablectl "${ARGS[@]}" reattach --now --enable --runtime /tmp/minimal_1 minimal-app0 + +systemctl is-active minimal-app0.service +systemctl is-active minimal-app0-bar.service +systemctl is-active minimal-app0-foo.service && exit 1 + +portablectl list | grep -q -F "minimal_1" +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' + +portablectl detach --now --enable --runtime /tmp/minimal_1 minimal-app0 + +portablectl list | grep -q -F "No images." +busctl tree org.freedesktop.portable1 --no-pager | grep -q -F '/org/freedesktop/portable1/image/minimal_5f1' && exit 1 + +portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0 + +systemctl is-active app0.service +status="$(portablectl is-attached --extension app0 minimal_0)" +[[ "${status}" == "running-runtime" ]] + +grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf + +portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0 + +systemctl is-active app0.service +status="$(portablectl is-attached --extension app0 minimal_1)" +[[ "${status}" == "running-runtime" ]] + +grep -q -F "LogExtraFields=PORTABLE_ROOT=minimal_1.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0.raw" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf + +portablectl detach --now --runtime --extension /usr/share/app0.raw /usr/share/minimal_1.raw app0 + +portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1 + +systemctl is-active app1.service +status="$(portablectl is-attached --extension app1 minimal_0)" +[[ "${status}" == "running-runtime" ]] + +# Ensure that adding or removing a version to the image doesn't break reattaching +cp /usr/share/app1.raw /tmp/app1_2.raw +portablectl "${ARGS[@]}" reattach --now --runtime --extension /tmp/app1_2.raw /usr/share/minimal_1.raw app1 + +systemctl is-active app1.service +status="$(portablectl is-attached --extension app1_2 minimal_1)" +[[ "${status}" == "running-runtime" ]] + +portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 + +systemctl is-active app1.service +status="$(portablectl is-attached --extension app1 minimal_1)" +[[ "${status}" == "running-runtime" ]] + +portablectl detach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 +portablectl "${ARGS[@]}" attach --force --no-reload --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1 +systemctl daemon-reload +systemctl restart app1.service + +systemctl is-active app1.service +status="$(portablectl is-attached --extension app1 minimal_0)" +[[ "${status}" == "running-runtime" ]] + +portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_0.raw app1 + +# Ensure that the combination of read-only images, state directory and dynamic user works, and that +# state is retained. Check after detaching, as on slow systems (eg: sanitizers) it might take a while +# after the service is attached before the file appears. +grep -q -F bar "${STATE_DIRECTORY}/app0/foo" +grep -q -F baz "${STATE_DIRECTORY}/app1/foo" + +# Ensure that we can override the check on extension-release.NAME +cp /usr/share/app0.raw /tmp/app10.raw +portablectl "${ARGS[@]}" attach --force --now --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 + +systemctl is-active app0.service +status="$(portablectl is-attached --extension /tmp/app10.raw /usr/share/minimal_0.raw)" +[[ "${status}" == "running-runtime" ]] + +portablectl inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 | grep -q -F "Extension Release: /tmp/app10.raw" + +# Ensure that we can detach even when an image has been deleted already (stop the unit manually as +# portablectl won't find it) +rm -f /tmp/app10.raw +systemctl stop app0.service +portablectl detach --force --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 + +# portablectl also accepts confexts +portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0 + +systemctl is-active app0.service +status="$(portablectl is-attached --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw)" +[[ "${status}" == "running-runtime" ]] + +portablectl inspect --force --cat --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0 | grep -q -F "Extension Release: /usr/share/conf0.raw" + +portablectl detach --now --runtime --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0 + +# portablectl also works with directory paths rather than images + +mkdir /tmp/rootdir /tmp/app0 /tmp/app1 /tmp/overlay /tmp/os-release-fix /tmp/os-release-fix/etc +mount /usr/share/app0.raw /tmp/app0 +mount /usr/share/app1.raw /tmp/app1 +mount /usr/share/minimal_0.raw /tmp/rootdir + +# Fix up os-release to drop the valid PORTABLE_SERVICES field (because we are +# bypassing the sysext logic in portabled here it will otherwise not see the +# extensions additional valid prefix) +grep -v "^PORTABLE_PREFIXES=" /tmp/rootdir/etc/os-release >/tmp/os-release-fix/etc/os-release + +mount -t overlay overlay -o lowerdir=/tmp/os-release-fix:/tmp/app1:/tmp/rootdir /tmp/overlay + +grep . /tmp/overlay/usr/lib/extension-release.d/* +grep . /tmp/overlay/etc/os-release + +portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/overlay app1 + +systemctl is-active app1.service + +portablectl detach --now --runtime overlay app1 + +umount /tmp/overlay + +portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1 + +systemctl is-active app0.service +systemctl is-active app1.service + +portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/rootdir/usr/lib/os-release +portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/extension-release.d/extension-release.app0 +portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/extension-release.d/extension-release.app2 +portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app1/usr/lib/systemd/system/app1.service +portablectl inspect --cat --extension app0 --extension app1 rootdir app0 app1 | grep -q -f /tmp/app0/usr/lib/systemd/system/app0.service + +grep -q -F "LogExtraFields=PORTABLE=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app0.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app0.service.d/20-portable.conf + +grep -q -F "LogExtraFields=PORTABLE=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_ROOT=rootdir" /run/systemd/system.attached/app1.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app0" /run/systemd/system.attached/app1.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app" /run/systemd/system.attached/app1.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION=app1" /run/systemd/system.attached/app1.service.d/20-portable.conf +grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/systemd/system.attached/app1.service.d/20-portable.conf + +portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1 + +# Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce. +# Provides coverage for https://github.com/systemd/systemd/issues/23481 +portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0 +portablectl detach --now --runtime --enable /tmp/rootdir minimal-app0 +# attach and detach again to check if all drop-in configs are removed even if the main unit files are removed +portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0 +portablectl detach --now --runtime --enable /tmp/rootdir minimal-app0 + +umount /tmp/rootdir +umount /tmp/app0 +umount /tmp/app1 + +# Lack of ID field in os-release should be rejected, but it caused a crash in the past instead +mkdir -p /tmp/emptyroot/usr/lib +mkdir -p /tmp/emptyext/usr/lib/extension-release.d +touch /tmp/emptyroot/usr/lib/os-release +touch /tmp/emptyext/usr/lib/extension-release.d/extension-release.emptyext + +# Remote peer disconnected -> portabled crashed +res="$(! portablectl attach --extension /tmp/emptyext /tmp/emptyroot 2> >(grep "Remote peer disconnected"))" +test -z "${res}" + +touch /testok diff --git a/test/units/testsuite-30.service b/test/units/testsuite-30.service new file mode 100644 index 0000000..253f7b5 --- /dev/null +++ b/test/units/testsuite-30.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-30-ONCLOCKCHANGE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-30.sh b/test/units/testsuite-30.sh new file mode 100755 index 0000000..104c87b --- /dev/null +++ b/test/units/testsuite-30.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemd-analyze log-level debug + +systemctl disable --now systemd-timesyncd.service + +timedatectl set-timezone Europe/Berlin +timedatectl set-time 1980-10-15 + +systemd-run --on-timezone-change touch /tmp/timezone-changed +systemd-run --on-clock-change touch /tmp/clock-changed + +test ! -f /tmp/timezone-changed +test ! -f /tmp/clock-changed + +timedatectl set-timezone Europe/Kiev + +while test ! -f /tmp/timezone-changed ; do sleep .5 ; done + +timedatectl set-time 2018-1-1 + +while test ! -f /tmp/clock-changed ; do sleep .5 ; done + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-31.service b/test/units/testsuite-31.service new file mode 100644 index 0000000..f0e78a9 --- /dev/null +++ b/test/units/testsuite-31.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-31-DEVICE-ENUMERATION + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-31.sh b/test/units/testsuite-31.sh new file mode 100755 index 0000000..03aba36 --- /dev/null +++ b/test/units/testsuite-31.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if journalctl -b -t systemd --grep '\.device: Changed plugged -> dead'; then + exit 1 +fi + +touch /testok diff --git a/test/units/testsuite-32.service b/test/units/testsuite-32.service new file mode 100644 index 0000000..50f5823 --- /dev/null +++ b/test/units/testsuite-32.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-32-OOMPOLICY + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot +MemoryAccounting=yes diff --git a/test/units/testsuite-32.sh b/test/units/testsuite-32.sh new file mode 100755 index 0000000..83b548a --- /dev/null +++ b/test/units/testsuite-32.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Let's run this test only if the "memory.oom.group" cgroupfs attribute +# exists. This test is a bit too strict, since the "memory.events"/"oom_kill" +# logic has been around since a longer time than "memory.oom.group", but it's +# an easier thing to test for, and also: let's not get confused by older +# kernels where the concept was still new. + +if test -f /sys/fs/cgroup/system.slice/testsuite-32.service/memory.oom.group; then + systemd-analyze log-level debug + + # Run a service that is guaranteed to be the first candidate for OOM killing + systemd-run --unit=oomtest.service \ + -p Type=exec -p OOMScoreAdjust=1000 -p OOMPolicy=stop -p MemoryAccounting=yes \ + sleep infinity + + # Trigger an OOM killer run + echo 1 >/proc/sys/kernel/sysrq + echo f >/proc/sysrq-trigger + + while : ; do + STATE="$(systemctl show -P ActiveState oomtest.service)" + [ "$STATE" = "failed" ] && break + sleep .5 + done + + RESULT="$(systemctl show -P Result oomtest.service)" + test "$RESULT" = "oom-kill" + + systemd-analyze log-level info +fi + +touch /testok diff --git a/test/units/testsuite-34.service b/test/units/testsuite-34.service new file mode 100644 index 0000000..6917afe --- /dev/null +++ b/test/units/testsuite-34.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-34-DYNAMICUSERMIGRATE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-34.sh b/test/units/testsuite-34.sh new file mode 100755 index 0000000..d15b675 --- /dev/null +++ b/test/units/testsuite-34.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemd-analyze log-level debug + +test_directory() { + local directory="$1" + local path="$2" + + # cleanup for previous invocation + for i in xxx xxx2 yyy zzz x:yz x:yz2; do + rm -rf "${path:?}/${i}" "${path:?}/private/${i}" + done + + # Set everything up without DynamicUser=1 + + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz touch "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + + test -d "${path}"/zzz + test ! -L "${path}"/zzz + test ! -e "${path}"/private/zzz + + test ! -e "${path}"/xxx + test ! -e "${path}"/private/xxx + test ! -e "${path}"/xxx2 + test ! -e "${path}"/private/xxx2 + test -L "${path}"/yyy + test ! -e "${path}"/private/yyy + + test -f "${path}"/zzz/test + test ! -e "${path}"/zzz/test-missing + + # Convert to DynamicUser=1 + + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}=zzz:xxx zzz:xxx2" \ + -p TemporaryFileSystem="${path}" -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + + test -L "${path}"/zzz + test -d "${path}"/private/zzz + + test ! -e "${path}"/xxx + test ! -e "${path}"/private/xxx + test ! -e "${path}"/xxx2 + test ! -e "${path}"/private/xxx2 + test -L "${path}"/yyy # previous symlink is not removed + test ! -e "${path}"/private/yyy + + test -f "${path}"/zzz/test + test ! -e "${path}"/zzz/test-missing + + # Convert back + + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test" + systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test + (! systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing) + + test -d "${path}"/zzz + test ! -L "${path}"/zzz + test ! -e "${path}"/private/zzz + + test ! -e "${path}"/xxx + test ! -e "${path}"/private/xxx + test ! -e "${path}"/xxx2 + test ! -e "${path}"/private/xxx2 + test -L "${path}"/yyy + test ! -e "${path}"/private/yyy + + test -f "${path}"/zzz/test + test ! -e "${path}"/zzz/test-missing + + # Exercise the unit parsing paths too + cat >/run/systemd/system/testservice-34.service </run/systemd/system/testservice-34-check-writable.service <<\EOF +[Unit] +Description=Check writable directories when DynamicUser= with StateDirectory= + +[Service] +# Relevant only for sanitizer runs +EnvironmentFile=-/usr/lib/systemd/systemd-asan-env + +Type=oneshot +DynamicUser=yes +StateDirectory=waldo quux/pief aaa/bbb aaa aaa/ccc xxx/yyy:aaa/111 xxx:aaa/222 xxx/zzz:aaa/333 + +# Make sure that the state directories are really the only writable directory besides the obvious candidates +ExecStart=bash -c ' \ + set -eux; \ + set -o pipefail; \ + declare -a writable_dirs; \ + readarray -t writable_dirs < <(find / \( -path /var/tmp -o -path /tmp -o -path /proc -o -path /dev/mqueue -o -path /dev/shm -o \ + -path /sys/fs/bpf -o -path /dev/.lxc -o -path /sys/devices/system/cpu \) \ + -prune -o -type d -writable -print 2>/dev/null | sort -u); \ + [[ "$${#writable_dirs[@]}" == "8" ]]; \ + [[ "$${writable_dirs[0]}" == "/var/lib/private/aaa" ]]; \ + [[ "$${writable_dirs[1]}" == "/var/lib/private/aaa/bbb" ]]; \ + [[ "$${writable_dirs[2]}" == "/var/lib/private/aaa/ccc" ]]; \ + [[ "$${writable_dirs[3]}" == "/var/lib/private/quux/pief" ]]; \ + [[ "$${writable_dirs[4]}" == "/var/lib/private/waldo" ]]; \ + [[ "$${writable_dirs[5]}" == "/var/lib/private/xxx" ]]; \ + [[ "$${writable_dirs[6]}" == "/var/lib/private/xxx/yyy" ]]; \ + [[ "$${writable_dirs[7]}" == "/var/lib/private/xxx/zzz" ]]; \ +' +EOF + systemctl daemon-reload + systemctl start testservice-34-check-writable.service +} + +test_directory "StateDirectory" "/var/lib" +test_directory "RuntimeDirectory" "/run" +test_directory "CacheDirectory" "/var/cache" +test_directory "LogsDirectory" "/var/log" + +test_check_writable + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-35.service b/test/units/testsuite-35.service new file mode 100644 index 0000000..0599f61 --- /dev/null +++ b/test/units/testsuite-35.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-35-LOGIN + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh new file mode 100755 index 0000000..36e26da --- /dev/null +++ b/test/units/testsuite-35.sh @@ -0,0 +1,660 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +cleanup_test_user() ( + set +ex + + pkill -u "$(id -u logind-test-user)" + sleep 1 + pkill -KILL -u "$(id -u logind-test-user)" + userdel -r logind-test-user + + return 0 +) + +setup_test_user() { + mkdir -p /var/spool/cron /var/spool/mail + useradd -m -s /bin/bash logind-test-user + trap cleanup_test_user EXIT +} + +test_enable_debug() { + mkdir -p /run/systemd/system/systemd-logind.service.d + cat >/run/systemd/system/systemd-logind.service.d/debug.conf </run/systemd/logind.conf.d/kill-user-processes.conf </run/systemd/logind.conf.d/kill-user-processes.conf </dev/null; then + echo "command evemu-device not found, skipping" + return + fi + if ! command -v evemu-event >/dev/null; then + echo "command evemu-event not found, skipping" + return + fi + + trap teardown_suspend RETURN + + # save pid + pid=$(systemctl show systemd-logind.service -p ExecMainPID --value) + + # create fake suspend + mkdir -p /run/systemd/system/systemd-suspend.service.d + cat >/run/systemd/system/systemd-suspend.service.d/override.conf </run/udev/rules.d/70-logindtest-lid.rules </run/lidswitch.evemu <&2 + exit 1 + fi + input_name=${input_name%/device/name} + lid_dev=/dev/${input_name#/sys/class/} + udevadm info --wait-for-initialization=10s "$lid_dev" + udevadm settle + + # close lid + evemu-event "$lid_dev" --sync --type 5 --code 0 --value 1 + # need to wait for 30s suspend inhibition after boot + wait_suspend 31 + # open lid again + evemu-event "$lid_dev" --sync --type 5 --code 0 --value 0 + + # waiting for 30s inhibition time between suspends + sleep 30 + + # now closing lid should cause instant suspend + evemu-event "$lid_dev" --sync --type 5 --code 0 --value 1 + wait_suspend 2 + evemu-event "$lid_dev" --sync --type 5 --code 0 --value 0 + + assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid" +} + +testcase_shutdown() { + local pid + + # save pid + pid=$(systemctl show systemd-logind.service -p ExecMainPID --value) + + # scheduled shutdown with wall message + shutdown 2>&1 + sleep 5 + shutdown -c || : + # logind should still be running + assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid" + + # scheduled shutdown without wall message + shutdown --no-wall 2>&1 + sleep 5 + shutdown -c --no-wall || true + assert_eq "$(systemctl show systemd-logind.service -p ExecMainPID --value)" "$pid" +} + +cleanup_session() ( + set +ex + + local uid s + + uid=$(id -u logind-test-user) + + loginctl disable-linger logind-test-user + + systemctl stop getty@tty2.service + + for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do + echo "INFO: stopping session $s" + loginctl terminate-session "$s" + done + + loginctl terminate-user logind-test-user + + if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then + echo "WARNING: session for logind-test-user still active, ignoring." + fi + + pkill -u "$uid" + sleep 1 + pkill -KILL -u "$uid" + + if ! timeout 30 bash -c "while systemctl is-active --quiet user@${uid}.service; do sleep 1; done"; then + echo "WARNING: user@${uid}.service is still active, ignoring." + fi + + if ! timeout 30 bash -c "while systemctl is-active --quiet user-runtime-dir@${uid}.service; do sleep 1; done"; then + echo "WARNING: user-runtime-dir@${uid}.service is still active, ignoring." + fi + + if ! timeout 30 bash -c "while systemctl is-active --quiet user-${uid}.slice; do sleep 1; done"; then + echo "WARNING: user-${uid}.slice is still active, ignoring." + fi + + rm -rf /run/systemd/system/getty@tty2.service.d + systemctl daemon-reload + + return 0 +) + +teardown_session() ( + set +ex + + cleanup_session + + rm -f /run/udev/rules.d/70-logindtest-scsi_debug-user.rules + udevadm control --reload + rmmod scsi_debug + + return 0 +) + +check_session() ( + set +ex + + local seat session leader_pid + + if [[ $(loginctl --no-legend | grep -c "logind-test-user") != 1 ]]; then + echo "no session or multiple sessions for logind-test-user." >&2 + return 1 + fi + + seat=$(loginctl --no-legend | grep 'logind-test-user *seat' | awk '{ print $4 }') + if [[ -z "$seat" ]]; then + echo "no seat found for user logind-test-user" >&2 + return 1 + fi + + session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }') + if [[ -z "$session" ]]; then + echo "no session found for user logind-test-user" >&2 + return 1 + fi + + if ! loginctl session-status "$session" | grep -q "Unit: session-${session}\.scope"; then + echo "cannot find scope unit for session $session" >&2 + return 1 + fi + + leader_pid=$(loginctl session-status "$session" | awk '$1 == "Leader:" { print $2 }') + if [[ -z "$leader_pid" ]]; then + echo "cannot found leader process for session $session" >&2 + return 1 + fi + + # cgroup v1: "1:name=systemd:/user.slice/..."; unified hierarchy: "0::/user.slice" + if ! grep -q -E '(name=systemd|^0:):.*session.*scope' /proc/"$leader_pid"/cgroup; then + echo "FAIL: process $leader_pid is not in the session cgroup" >&2 + cat /proc/self/cgroup + return 1 + fi +) + +create_session() { + # login with the test user to start a session + mkdir -p /run/systemd/system/getty@tty2.service.d + cat >/run/systemd/system/getty@tty2.service.d/override.conf < 1 )) && sleep 1 + check_session && break + done + check_session + assert_eq "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" "tty2" +} + +testcase_sanity_check() { + # Exercise basic loginctl options + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap cleanup_session RETURN + create_session + + # Run most of the loginctl commands from a user session to make + # the seat/session autodetection work-ish + systemd-run --user --pipe --wait -M "logind-test-user@.host" bash -eux <<\EOF + loginctl list-sessions + loginctl session-status + loginctl show-session + loginctl show-session -P DelayInhibited + + # We're not in the same session scope, so in this case we need to specify + # the session ID explicitly + session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; exit; }') + loginctl kill-session --signal=SIGCONT "$session" + # FIXME(?) + #loginctl kill-session --signal=SIGCONT --kill-whom=leader "$session" + + loginctl list-users + loginctl user-status + loginctl show-user -a + loginctl show-user -P IdleAction + loginctl kill-user --signal=SIGCONT "" + + loginctl list-seats + loginctl seat-status + loginctl show-seat + loginctl show-seat -P IdleActionUSec +EOF + + # Requires root privileges + loginctl lock-sessions + loginctl unlock-sessions + loginctl flush-devices +} + +testcase_session() { + local dev + + if systemd-detect-virt --quiet --container; then + echo "Skipping ACL tests in container" + return + fi + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap teardown_session RETURN + + create_session + + # scsi_debug should not be loaded yet + if [[ -d /sys/bus/pseudo/drivers/scsi_debug ]]; then + echo "scsi_debug module is already loaded." >&2 + exit 1 + fi + + # we use scsi_debug to create new devices which we can put ACLs on + # tell udev about the tagging, so that logind can pick it up + mkdir -p /run/udev/rules.d + cat >/run/udev/rules.d/70-logindtest-scsi_debug-user.rules </dev/null; do sleep 1; done' + dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null) + if [[ ! -b "$dev" ]]; then + echo "cannot find suitable scsi block device" >&2 + exit 1 + fi + udevadm settle + udevadm info "$dev" + + # trigger logind and activate session + loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')" + + # check ACL + sleep 1 + assert_in "user:logind-test-user:rw-" "$(getfacl -p "$dev")" + + # hotplug: new device appears while logind is running + rmmod scsi_debug + modprobe scsi_debug + timeout 30 bash -c 'until ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null; do sleep 1; done' + dev=/dev/$(ls /sys/bus/pseudo/drivers/scsi_debug/adapter*/host*/target*/*:*/block 2>/dev/null) + if [[ ! -b "$dev" ]]; then + echo "cannot find suitable scsi block device" >&2 + exit 1 + fi + udevadm settle + + # check ACL + sleep 1 + assert_in "user:logind-test-user:rw-" "$(getfacl -p "$dev")" +} + +teardown_lock_idle_action() ( + set +eux + + rm -f /run/systemd/logind.conf.d/idle-action-lock.conf + systemctl restart systemd-logind.service + + cleanup_session + + return 0 +) + +testcase_lock_idle_action() { + local ts + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + if loginctl --no-legend | grep -q logind-test-user; then + echo >&2 "Session of the 'logind-test-user' is already present." + exit 1 + fi + + trap teardown_lock_idle_action RETURN + + create_session + + ts="$(date '+%H:%M:%S')" + + mkdir -p /run/systemd/logind.conf.d + cat >/run/systemd/logind.conf.d/idle-action-lock.conf <&2 "System haven't entered idle state at least 2 times." + exit 1 + fi +} + +testcase_session_properties() { + local s + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap cleanup_session RETURN + create_session + + s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }') + /usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}" /dev/tty2 +} + +testcase_list_users_sessions_seats() { + local session seat + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + trap cleanup_session RETURN + create_session + + # Activate the session + loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')" + + session=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }') + : check that we got a valid session id + busctl get-property org.freedesktop.login1 "/org/freedesktop/login1/session/_3${session?}" org.freedesktop.login1.Session Id + assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)" + seat=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $4 }') + assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $5 }')" tty2 + assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $6 }')" active + assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $7 }')" no + assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $8 }')" '-' + + loginctl list-seats --no-legend | grep -Fwq "${seat?}" + + assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $1 }')" "$(id -ru logind-test-user)" + assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" no + assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $4 }')" active + + loginctl enable-linger logind-test-user + assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes + + for s in $(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }'); do + loginctl terminate-session "$s" + done + if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then + echo "WARNING: session for logind-test-user still active, ignoring." + return + fi + + assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $4 }')" lingering +} + +teardown_stop_idle_session() ( + set +eux + + rm -f /run/systemd/logind.conf.d/stop-idle-session.conf + systemctl restart systemd-logind.service + + cleanup_session +) + +testcase_stop_idle_session() { + local id ts + + if [[ ! -c /dev/tty2 ]]; then + echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}." + return + fi + + create_session + trap teardown_stop_idle_session RETURN + + id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')" + ts="$(date '+%H:%M:%S')" + + mkdir -p /run/systemd/logind.conf.d + cat >/run/systemd/logind.conf.d/stop-idle-session.conf <&2 + return + fi + + typeset -i BND MASK + + # Get PID 1's bounding set + BND="0x$(grep 'CapBnd:' /proc/1/status | cut -d: -f2 | tr -d '[:space:]')" + + # CAP_CHOWN | CAP_KILL + MASK=$(((1 << 0) | (1 << 5))) + + if [ $((BND & MASK)) -ne "$MASK" ] ; then + echo "CAP_CHOWN or CAP_KILL not available in bounding set, skipping test." >&2 + return + fi + + PAMSERVICE="pamserv$RANDOM" + TRANSIENTUNIT="capwakealarm$RANDOM.service" + SCRIPT="/tmp/capwakealarm$RANDOM.sh" + + cat > /etc/pam.d/"$PAMSERVICE" < "$SCRIPT" <<'EOF' +#!/bin/bash +set -ex +typeset -i AMB MASK +AMB="0x$(grep 'CapAmb:' /proc/self/status | cut -d: -f2 | tr -d '[:space:]')" +MASK=$(((1 << 0) | (1 << 5))) +test "$AMB" -eq "$MASK" +EOF + + chmod +x "$SCRIPT" + + systemd-run -u "$TRANSIENTUNIT" -p PAMName="$PAMSERVICE" -p Type=oneshot -p User=logind-test-user -p StandardError=tty "$SCRIPT" + + rm -f "$SCRIPT" "$PAMSERVICE" +} + +setup_test_user +test_enable_debug +run_testcases + +touch /testok diff --git a/test/units/testsuite-36.service b/test/units/testsuite-36.service new file mode 100644 index 0000000..5746dc1 --- /dev/null +++ b/test/units/testsuite-36.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-36-NUMAPOLICY + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-36.sh b/test/units/testsuite-36.sh new file mode 100755 index 0000000..8a53b98 --- /dev/null +++ b/test/units/testsuite-36.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck disable=SC2317 +at_exit() { + # shellcheck disable=SC2181 + if [[ $? -ne 0 ]]; then + # We're exiting with a non-zero EC, let's dump test artifacts + # for easier debugging + [[ -v straceLog && -f "$straceLog" ]] && cat "$straceLog" + [[ -v journalLog && -f "$journalLog" ]] && cat "$journalLog" + fi +} + +trap at_exit EXIT + +systemd-analyze log-level debug +systemd-analyze log-target journal + +# Log files +straceLog='strace.log' +journalLog='journal.log' + +# Systemd config files +testUnit='numa-test.service' +testUnitFile="/run/systemd/system/$testUnit" +testUnitNUMAConf="$testUnitFile.d/numa.conf" + +# Sleep constants (we should probably figure out something better but nothing comes to mind) +sleepAfterStart=1 + +# Journal cursor for easier navigation +journalCursorFile="jounalCursorFile" + +startStrace() { + coproc strace -qq -p 1 -o "$straceLog" -e set_mempolicy -s 1024 ${1:+"$1"} + # Wait for strace to properly "initialize", i.e. until PID 1 has the TracerPid + # field set to the current strace's PID + until awk -v spid="$COPROC_PID" '/^TracerPid:/ {exit !($2 == spid);}' /proc/1/status; do sleep 0.1; done +} + +stopStrace() { + [[ -v COPROC_PID ]] || return + + local PID=$COPROC_PID + kill -s TERM "$PID" + # Make sure the strace process is indeed dead + while kill -0 "$PID" 2>/dev/null; do sleep 0.1; done +} + +startJournalctl() { + : >"$journalCursorFile" + # Save journal's cursor for later navigation + journalctl --no-pager --cursor-file="$journalCursorFile" -n0 -ocat +} + +stopJournalctl() { + local unit="${1:-init.scope}" + # Using journalctl --sync should be better than using SIGRTMIN+1, as + # the --sync wait until the synchronization is complete + echo "Force journald to write all queued messages" + journalctl --sync + journalctl -u "$unit" --cursor-file="$journalCursorFile" >"$journalLog" +} + +checkNUMA() { + # NUMA enabled system should have at least NUMA node0 + test -e /sys/devices/system/node/node0 +} + +writePID1NUMAPolicy() { + cat >"$confDir/numa.conf" <"$testUnitFile" +} + +writeTestUnitNUMAPolicy() { + cat >"$testUnitNUMAConf" <"$LOGFILE" + grep "NUMAPolicy=$NUMA_POLICY" "$LOGFILE" + + : >"$LOGFILE" + + if [ -n "$NUMA_MASK" ]; then + systemctl show -p NUMAMask "$UNIT_NAME" >"$LOGFILE" + grep "NUMAMask=$NUMA_MASK" "$LOGFILE" + fi +} + +writeTestUnit + +# Create systemd config drop-in directory +confDir="/run/systemd/system.conf.d/" +mkdir -p "$confDir" + +if ! checkNUMA; then + echo >&2 "NUMA is not supported on this machine, switching to a simple sanity check" + + echo "PID1 NUMAPolicy=default && NUMAMask=0 check without NUMA support" + writePID1NUMAPolicy "default" "0" + startJournalctl + systemctl daemon-reload + stopJournalctl + grep "NUMA support not available, ignoring" "$journalLog" + + echo "systemd-run NUMAPolicy=default && NUMAMask=0 check without NUMA support" + runUnit='numa-systemd-run-test.service' + startJournalctl + systemd-run -p NUMAPolicy=default -p NUMAMask=0 --unit "$runUnit" sleep 1000 + sleep $sleepAfterStart + pid1StopUnit "$runUnit" + stopJournalctl "$runUnit" + grep "NUMA support not available, ignoring" "$journalLog" + +else + echo "PID1 NUMAPolicy support - Default policy w/o mask" + writePID1NUMAPolicy "default" + pid1ReloadWithStrace + # Kernel requires that nodemask argument is set to NULL when setting default policy + grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog" + + echo "PID1 NUMAPolicy support - Default policy w/ mask" + writePID1NUMAPolicy "default" "0" + pid1ReloadWithStrace + grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog" + + echo "PID1 NUMAPolicy support - Bind policy w/o mask" + writePID1NUMAPolicy "bind" + pid1ReloadWithJournal + grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" + + echo "PID1 NUMAPolicy support - Bind policy w/ mask" + writePID1NUMAPolicy "bind" "0" + pid1ReloadWithStrace + grep -P "set_mempolicy\(MPOL_BIND, \[0x0*1\]" "$straceLog" + + echo "PID1 NUMAPolicy support - Interleave policy w/o mask" + writePID1NUMAPolicy "interleave" + pid1ReloadWithJournal + grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" + + echo "PID1 NUMAPolicy support - Interleave policy w/ mask" + writePID1NUMAPolicy "interleave" "0" + pid1ReloadWithStrace + grep -P "set_mempolicy\(MPOL_INTERLEAVE, \[0x0*1\]" "$straceLog" + + echo "PID1 NUMAPolicy support - Preferred policy w/o mask" + writePID1NUMAPolicy "preferred" + pid1ReloadWithJournal + # Preferred policy with empty node mask is actually allowed and should reset allocation policy to default + grep "Failed to set NUMA memory policy, ignoring: Invalid argument" "$journalLog" && { echo >&2 "unexpected pass"; exit 1; } + + echo "PID1 NUMAPolicy support - Preferred policy w/ mask" + writePID1NUMAPolicy "preferred" "0" + pid1ReloadWithStrace + grep -P "set_mempolicy\(MPOL_PREFERRED, \[0x0*1\]" "$straceLog" + + echo "PID1 NUMAPolicy support - Local policy w/o mask" + writePID1NUMAPolicy "local" + pid1ReloadWithStrace + # Kernel requires that nodemask argument is set to NULL when setting default policy + # The unpatched versions of strace don't recognize the MPOL_LOCAL constant and + # return a numerical constant instead (with a comment): + # set_mempolicy(0x4 /* MPOL_??? */, NULL, 0) = 0 + # Let's cover this scenario as well + grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + + echo "PID1 NUMAPolicy support - Local policy w/ mask" + writePID1NUMAPolicy "local" "0" + pid1ReloadWithStrace + grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + + echo "Unit file NUMAPolicy support - Default policy w/o mask" + writeTestUnitNUMAPolicy "default" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "default" + pid1StopUnit "$testUnit" + grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog" + + echo "Unit file NUMAPolicy support - Default policy w/ mask" + writeTestUnitNUMAPolicy "default" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "default" "0" + pid1StopUnit $testUnit + # Mask must be ignored + grep "set_mempolicy(MPOL_DEFAULT, NULL" "$straceLog" + + echo "Unit file NUMAPolicy support - Bind policy w/o mask" + writeTestUnitNUMAPolicy "bind" + pid1StartUnitWithJournal "$testUnit" + pid1StopUnit "$testUnit" + [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] + + echo "Unit file NUMAPolicy support - Bind policy w/ mask" + writeTestUnitNUMAPolicy "bind" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "bind" "0" + pid1StopUnit "$testUnit" + grep -P "set_mempolicy\(MPOL_BIND, \[0x0*1\]" "$straceLog" + + echo "Unit file NUMAPolicy support - Interleave policy w/o mask" + writeTestUnitNUMAPolicy "interleave" + pid1StartUnitWithStrace "$testUnit" + pid1StopUnit "$testUnit" + [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] + + echo "Unit file NUMAPolicy support - Interleave policy w/ mask" + writeTestUnitNUMAPolicy "interleave" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "interleave" "0" + pid1StopUnit "$testUnit" + grep -P "set_mempolicy\(MPOL_INTERLEAVE, \[0x0*1\]" "$straceLog" + + echo "Unit file NUMAPolicy support - Preferred policy w/o mask" + writeTestUnitNUMAPolicy "preferred" + pid1StartUnitWithJournal "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "preferred" + pid1StopUnit "$testUnit" + [[ $(systemctl show "$testUnit" -P ExecMainStatus) == "242" ]] && { echo >&2 "unexpected pass"; exit 1; } + + echo "Unit file NUMAPolicy support - Preferred policy w/ mask" + writeTestUnitNUMAPolicy "preferred" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "preferred" "0" + pid1StopUnit "$testUnit" + grep -P "set_mempolicy\(MPOL_PREFERRED, \[0x0*1\]" "$straceLog" + + echo "Unit file NUMAPolicy support - Local policy w/o mask" + writeTestUnitNUMAPolicy "local" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "local" + pid1StopUnit "$testUnit" + grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + + echo "Unit file NUMAPolicy support - Local policy w/ mask" + writeTestUnitNUMAPolicy "local" "0" + pid1StartUnitWithStrace "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "local" "0" + pid1StopUnit "$testUnit" + # Mask must be ignored + grep -E "set_mempolicy\((MPOL_LOCAL|0x4 [^,]*), NULL" "$straceLog" + + echo "Unit file CPUAffinity=NUMA support" + writeTestUnitNUMAPolicy "bind" "0" + echo "CPUAffinity=numa" >>"$testUnitNUMAConf" + systemctl daemon-reload + systemctl start "$testUnit" + systemctlCheckNUMAProperties "$testUnit" "bind" "0" + cpulist="$(cat /sys/devices/system/node/node0/cpulist)" + affinity_systemd="$(systemctl show --value -p CPUAffinity "$testUnit")" + [ "$cpulist" = "$affinity_systemd" ] + pid1StopUnit "$testUnit" + + echo "systemd-run NUMAPolicy support" + runUnit='numa-systemd-run-test.service' + + systemd-run -p NUMAPolicy=default --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "default" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=default -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "default" "" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=bind -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "bind" "0" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=interleave -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "interleave" "0" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=preferred -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "preferred" "0" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=local --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "local" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=local -p NUMAMask=0 --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "local" "" + pid1StopUnit "$runUnit" + + systemd-run -p NUMAPolicy=local -p NUMAMask=0 -p CPUAffinity=numa --unit "$runUnit" sleep 1000 + systemctlCheckNUMAProperties "$runUnit" "local" "" + systemctl cat "$runUnit" | grep -q 'CPUAffinity=numa' + pid1StopUnit "$runUnit" +fi + +# Cleanup +rm -rf "$confDir" +systemctl daemon-reload + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-38-sleep.service b/test/units/testsuite-38-sleep.service new file mode 100644 index 0000000..c116c80 --- /dev/null +++ b/test/units/testsuite-38-sleep.service @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Service] +ExecStart=/bin/sleep 3600 diff --git a/test/units/testsuite-38.service b/test/units/testsuite-38.service new file mode 100644 index 0000000..ac77836 --- /dev/null +++ b/test/units/testsuite-38.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-38-FREEZER + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-38.sh b/test/units/testsuite-38.sh new file mode 100755 index 0000000..5fc87fc --- /dev/null +++ b/test/units/testsuite-38.sh @@ -0,0 +1,301 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2317 +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +systemd-analyze log-level debug + +unit=testsuite-38-sleep.service + +start_test_service() { + systemctl daemon-reload + systemctl start "${unit}" +} + +dbus_freeze() { + local name object_path suffix + + suffix="${1##*.}" + name="${1%".$suffix"}" + object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}" + + busctl call \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + Freeze +} + +dbus_thaw() { + local name object_path suffix + + suffix="${1##*.}" + name="${1%".$suffix"}" + object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}" + + busctl call \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + Thaw +} + +dbus_freeze_unit() { + busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + FreezeUnit \ + s \ + "$1" +} + +dbus_thaw_unit() { + busctl call \ + org.freedesktop.systemd1 \ + /org/freedesktop/systemd1 \ + org.freedesktop.systemd1.Manager \ + ThawUnit \ + s \ + "$1" +} + +dbus_can_freeze() { + local name object_path suffix + + suffix="${1##*.}" + name="${1%".$suffix"}" + object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}" + + busctl get-property \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + CanFreeze +} + +check_freezer_state() { + local name object_path suffix + + suffix="${1##*.}" + name="${1%".$suffix"}" + object_path="/org/freedesktop/systemd1/unit/${name//-/_2d}_2e${suffix}" + + for _ in {0..10}; do + state=$(busctl get-property \ + org.freedesktop.systemd1 \ + "${object_path}" \ + org.freedesktop.systemd1.Unit \ + FreezerState | cut -d " " -f2 | tr -d '"') + + # Ignore the intermediate freezing & thawing states in case we check + # the unit state too quickly + [[ "$state" =~ ^(freezing|thawing)$ ]] || break + sleep .5 + done + + [ "$state" = "$2" ] || { + echo "error: unexpected freezer state, expected: $2, actual: $state" >&2 + exit 1 + } +} + +check_cgroup_state() { + grep -q "frozen $2" /sys/fs/cgroup/system.slice/"$1"/cgroup.events +} + +testcase_dbus_api() { + echo "Test that DBus API works:" + echo -n " - Freeze(): " + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + echo "[ OK ]" + + echo -n " - Thaw(): " + dbus_thaw "${unit}" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + echo "[ OK ]" + + echo -n " - FreezeUnit(): " + dbus_freeze_unit "${unit}" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + echo "[ OK ]" + + echo -n " - ThawUnit(): " + dbus_thaw_unit "${unit}" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + echo "[ OK ]" + + echo -n " - CanFreeze(): " + output=$(dbus_can_freeze "${unit}") + [ "$output" = "b true" ] + echo "[ OK ]" + + echo +} + +testcase_jobs() { + local pid_before= + local pid_after= + echo "Test that it is possible to apply jobs on frozen units:" + + systemctl start "${unit}" + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + + echo -n " - restart: " + pid_before=$(systemctl show -p MainPID "${unit}" --value) + systemctl restart "${unit}" + pid_after=$(systemctl show -p MainPID "${unit}" --value) + [ "$pid_before" != "$pid_after" ] && echo "[ OK ]" + + dbus_freeze "${unit}" + check_freezer_state "${unit}" "frozen" + + echo -n " - stop: " + timeout 5s systemctl stop "${unit}" + echo "[ OK ]" + + echo +} + +testcase_systemctl() { + echo "Test that systemctl freeze/thaw verbs:" + + systemctl start "$unit" + + echo -n " - freeze: " + systemctl freeze "$unit" + check_freezer_state "${unit}" "frozen" + check_cgroup_state "$unit" 1 + # Freezing already frozen unit should be NOP and return quickly + timeout 3s systemctl freeze "$unit" + echo "[ OK ]" + + echo -n " - thaw: " + systemctl thaw "$unit" + check_freezer_state "${unit}" "running" + check_cgroup_state "$unit" 0 + # Likewise thawing already running unit shouldn't block + timeout 3s systemctl thaw "$unit" + echo "[ OK ]" + + systemctl stop "$unit" + + echo +} + +testcase_systemctl_show() { + echo "Test systemctl show integration:" + + systemctl start "$unit" + + echo -n " - FreezerState property: " + state=$(systemctl show -p FreezerState --value "$unit") + [ "$state" = "running" ] + systemctl freeze "$unit" + state=$(systemctl show -p FreezerState --value "$unit") + [ "$state" = "frozen" ] + systemctl thaw "$unit" + echo "[ OK ]" + + echo -n " - CanFreeze property: " + state=$(systemctl show -p CanFreeze --value "$unit") + [ "$state" = "yes" ] + echo "[ OK ]" + + systemctl stop "$unit" + echo +} + +testcase_recursive() { + local slice="bar.slice" + local unit="baz.service" + + systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 + + echo "Test recursive freezing:" + + echo -n " - freeze: " + systemctl freeze "$slice" + check_freezer_state "${slice}" "frozen" + check_freezer_state "${unit}" "frozen" + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw: " + systemctl thaw "$slice" + check_freezer_state "${unit}" "running" + check_freezer_state "${slice}" "running" + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + systemctl stop "$unit" + systemctl stop "$slice" + + echo +} + +testcase_preserve_state() { + local slice="bar.slice" + local unit="baz.service" + + systemd-run --unit "$unit" --slice "$slice" sleep 3600 >/dev/null 2>&1 + + echo "Test that freezer state is preserved when recursive freezing is initiated from outside (e.g. by manager up the tree):" + + echo -n " - freeze from outside: " + echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze + # Give kernel some time to freeze the slice + sleep 1 + + # Our state should not be affected + check_freezer_state "${slice}" "running" + check_freezer_state "${unit}" "running" + + # However actual kernel state should be frozen + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 1" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw from outside: " + echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze + sleep 1 + + check_freezer_state "${unit}" "running" + check_freezer_state "${slice}" "running" + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/cgroup.events + grep -q "frozen 0" /sys/fs/cgroup/"${slice}"/"${unit}"/cgroup.events + echo "[ OK ]" + + echo -n " - thaw from outside while inner service is frozen: " + systemctl freeze "$unit" + check_freezer_state "${unit}" "frozen" + echo 1 >/sys/fs/cgroup/"${slice}"/cgroup.freeze + echo 0 >/sys/fs/cgroup/"${slice}"/cgroup.freeze + check_freezer_state "${slice}" "running" + check_freezer_state "${unit}" "frozen" + echo "[ OK ]" + + systemctl stop "$unit" + systemctl stop "$slice" + + echo +} + +if [[ -e /sys/fs/cgroup/system.slice/cgroup.freeze ]]; then + start_test_service + run_testcases +fi + +touch /testok diff --git a/test/units/testsuite-43.service b/test/units/testsuite-43.service new file mode 100644 index 0000000..e36afea --- /dev/null +++ b/test/units/testsuite-43.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-43-PRIVATEUSER-UNPRIV +After=systemd-logind.service user@4711.service +Wants=user@4711.service + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-43.sh b/test/units/testsuite-43.sh new file mode 100755 index 0000000..4f31a33 --- /dev/null +++ b/test/units/testsuite-43.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +if [[ "$(sysctl -ne kernel.apparmor_restrict_unprivileged_userns)" -eq 1 ]]; then + echo "Cannot create unprivileged user namespaces" >/skipped + exit 0 +fi + +systemd-analyze log-level debug + +runas testuser systemd-run --wait --user --unit=test-private-users \ + -p PrivateUsers=yes -P echo hello + +runas testuser systemctl --user log-level debug + +runas testuser systemd-run --wait --user --unit=test-private-tmp-innerfile \ + -p PrivateTmp=yes \ + -P touch /tmp/innerfile.txt +# File should not exist outside the job's tmp directory. +test ! -e /tmp/innerfile.txt + +touch /tmp/outerfile.txt +# File should not appear in unit's private tmp. +runas testuser systemd-run --wait --user --unit=test-private-tmp-outerfile \ + -p PrivateTmp=yes \ + -P test ! -e /tmp/outerfile.txt + +# Confirm that creating a file in home works +runas testuser systemd-run --wait --user --unit=test-unprotected-home \ + -P touch /home/testuser/works.txt +test -e /home/testuser/works.txt + +# Confirm that creating a file in home is blocked under read-only +(! runas testuser systemd-run --wait --user --unit=test-protect-home-read-only \ + -p ProtectHome=read-only \ + -P bash -c ' + test -e /home/testuser/works.txt || exit 10 + touch /home/testuser/blocked.txt && exit 11 + ') +test ! -e /home/testuser/blocked.txt + +# Check that tmpfs hides the whole directory +runas testuser systemd-run --wait --user --unit=test-protect-home-tmpfs \ + -p ProtectHome=tmpfs \ + -P test ! -e /home/testuser + +# Confirm that home, /root, and /run/user are inaccessible under "yes" +# shellcheck disable=SC2016 +runas testuser systemd-run --wait --user --unit=test-protect-home-yes \ + -p ProtectHome=yes \ + -P bash -c ' + test "$(stat -c %a /home)" = "0" + test "$(stat -c %a /root)" = "0" + test "$(stat -c %a /run/user)" = "0" + ' + +# Confirm we cannot change groups because we only have one mapping in the user +# namespace (no CAP_SETGID in the parent namespace to write the additional +# mapping of the user supplied group and thus cannot change groups to an +# unmapped group ID) +(! runas testuser systemd-run --wait --user --unit=test-group-fail \ + -p PrivateUsers=yes -p Group=daemon \ + -P true) + +# Check that with a new user namespace we can bind mount +# files and use a different root directory +runas testuser systemd-run --wait --user --unit=test-bind-mount \ + -p BindPaths=/dev/null:/etc/os-release \ + test ! -s /etc/os-release + +runas testuser systemd-run --wait --user --unit=test-read-write \ + -p ReadOnlyPaths=/ \ + -p ReadWritePaths="/var /run /tmp" \ + -p NoExecPaths=/ -p ExecPaths=/usr \ + test ! -w /etc/os-release + +runas testuser systemd-run --wait --user --unit=test-caps \ + -p PrivateUsers=yes -p AmbientCapabilities=CAP_SYS_ADMIN \ + -p CapabilityBoundingSet=CAP_SYS_ADMIN \ + test -s /etc/os-release + +runas testuser systemd-run --wait --user --unit=test-devices \ + -p PrivateDevices=yes -p PrivateIPC=yes \ + sh -c "ls -1 /dev/ | wc -l | grep -q -F 18" + +# Same check as test/test-execute/exec-privatenetwork-yes.service +runas testuser systemd-run --wait --user --unit=test-network \ + -p PrivateNetwork=yes \ + /bin/sh -x -c '! ip link | grep -E "^[0-9]+: " | grep -Ev ": (lo|(erspan|gre|gretap|ip_vti|ip6_vti|ip6gre|ip6tnl|sit|tunl)0@.*):"' + +(! runas testuser systemd-run --wait --user --unit=test-hostname \ + -p ProtectHostname=yes \ + hostnamectl hostname foo) + +(! runas testuser systemd-run --wait --user --unit=test-clock \ + -p ProtectClock=yes \ + timedatectl set-time "2012-10-30 18:17:16") + +(! runas testuser systemd-run --wait --user --unit=test-kernel-tunable \ + -p ProtectKernelTunables=yes \ + sh -c "echo 0 >/proc/sys/user/max_user_namespaces") + +(! runas testuser systemd-run --wait --user --unit=test-kernel-mod \ + -p ProtectKernelModules=yes \ + sh -c "modprobe -r overlay && modprobe overlay") + +if sysctl kernel.dmesg_restrict=0; then + (! runas testuser systemd-run --wait --user --unit=test-kernel-log \ + -p ProtectKernelLogs=yes -p LogNamespace=yes \ + dmesg) +fi + +unsquashfs -no-xattrs -d /tmp/img /usr/share/minimal_0.raw +runas testuser systemd-run --wait --user --unit=test-root-dir \ + -p RootDirectory=/tmp/img \ + grep MARKER=1 /etc/os-release + +mkdir /tmp/img_bind +mount --bind /tmp/img /tmp/img_bind +runas testuser systemd-run --wait --user --unit=test-root-dir-bind \ + -p RootDirectory=/tmp/img_bind -p MountFlags=private \ + grep MARKER=1 /etc/os-release +umount /tmp/img_bind + +# Unprivileged overlayfs was added to Linux 5.11, so try to detect it first +mkdir -p /tmp/a /tmp/b /tmp/c +if unshare --mount --user --map-root-user mount -t overlay overlay /tmp/c -o lowerdir=/tmp/a:/tmp/b; then + unsquashfs -no-xattrs -d /tmp/app2 /usr/share/app1.raw + runas testuser systemd-run --wait --user --unit=test-extension-dir \ + -p ExtensionDirectories=/tmp/app2 \ + -p TemporaryFileSystem=/run -p RootDirectory=/tmp/img \ + -p MountAPIVFS=yes \ + grep PORTABLE_PREFIXES=app1 /usr/lib/extension-release.d/extension-release.app2 +fi + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-44.service b/test/units/testsuite-44.service new file mode 100644 index 0000000..4dffdea --- /dev/null +++ b/test/units/testsuite-44.service @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TESTSUITE-44-LOG-NAMESPACE +Before=getty-pre.target +Wants=getty-pre.target +Wants=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket +After=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-44.sh b/test/units/testsuite-44.sh new file mode 100755 index 0000000..fbd4ae6 --- /dev/null +++ b/test/units/testsuite-44.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux + +systemd-analyze log-level debug + +systemd-run --wait -p LogNamespace=foobar echo "hello world" + +journalctl --namespace=foobar --sync +journalctl -o cat --namespace=foobar >/tmp/hello-world +journalctl -o cat >/tmp/no-hello-world + +grep "^hello world$" /tmp/hello-world +(! grep "^hello world$" /tmp/no-hello-world) + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-45.service b/test/units/testsuite-45.service new file mode 100644 index 0000000..b16ce99 --- /dev/null +++ b/test/units/testsuite-45.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-45-TIMEDATE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-45.sh b/test/units/testsuite-45.sh new file mode 100755 index 0000000..f124a24 --- /dev/null +++ b/test/units/testsuite-45.sh @@ -0,0 +1,412 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +testcase_timedatectl() { + timedatectl --no-pager --help + timedatectl --version + + timedatectl + timedatectl --no-ask-password + timedatectl status --machine=testuser@.host + timedatectl status + timedatectl show + timedatectl show --all + timedatectl show -p NTP + timedatectl show -p NTP --value + timedatectl list-timezones + + if ! systemd-detect-virt -qc; then + systemctl enable --runtime --now systemd-timesyncd + timedatectl timesync-status + timedatectl show-timesync + fi +} + +restore_timezone() { + if [[ -f /tmp/timezone.bak ]]; then + mv /tmp/timezone.bak /etc/timezone + else + rm -f /etc/timezone + fi +} + +testcase_timezone() { + local ORIG_TZ= + + # Debian/Ubuntu specific file + if [[ -f /etc/timezone ]]; then + mv /etc/timezone /tmp/timezone.bak + fi + + trap restore_timezone RETURN + + if [[ -L /etc/localtime ]]; then + ORIG_TZ=$(readlink /etc/localtime | sed 's#^.*zoneinfo/##') + echo "original tz: $ORIG_TZ" + fi + + echo 'timedatectl works' + assert_in "Local time:" "$(timedatectl --no-pager)" + + echo 'change timezone' + assert_eq "$(timedatectl --no-pager set-timezone Europe/Kiev 2>&1)" "" + assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "Europe/Kiev" + if [[ -f /etc/timezone ]]; then + assert_eq "$(cat /etc/timezone)" "Europe/Kiev" + fi + assert_in "Time zone: Europe/Kiev \(EES*T, \+0[0-9]00\)" "$(timedatectl)" + + if [[ -n "$ORIG_TZ" ]]; then + echo 'reset timezone to original' + assert_eq "$(timedatectl set-timezone "$ORIG_TZ" 2>&1)" "" + assert_eq "$(readlink /etc/localtime | sed 's#^.*zoneinfo/##')" "$ORIG_TZ" + if [[ -f /etc/timezone ]]; then + assert_eq "$(cat /etc/timezone)" "$ORIG_TZ" + fi + fi +} + +restore_adjtime() { + if [[ -e /etc/adjtime.bak ]]; then + mv /etc/adjtime.bak /etc/adjtime + else + rm /etc/adjtime + fi +} + +check_adjtime_not_exist() { + if [[ -e /etc/adjtime ]]; then + echo "/etc/adjtime unexpectedly exists." >&2 + exit 1 + fi +} + +testcase_adjtime() { + # test setting UTC vs. LOCAL in /etc/adjtime + if [[ -e /etc/adjtime ]]; then + mv /etc/adjtime /etc/adjtime.bak + fi + + trap restore_adjtime RETURN + + echo 'no adjtime file' + rm -f /etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + timedatectl set-local-rtc 0 + check_adjtime_not_exist + + echo 'UTC set in adjtime file' + printf '0.0 0 0\n0\nUTC\n' >/etc/adjtime + timedatectl set-local-rtc 0 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +UTC" + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'non-zero values in adjtime file' + printf '0.1 123 0\n0\nLOCAL\n' >/etc/adjtime + timedatectl set-local-rtc 0 + assert_eq "$(cat /etc/adjtime)" "0.1 123 0 +0 +UTC" + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.1 123 0 +0 +LOCAL" + + echo 'fourth line adjtime file' + printf '0.0 0 0\n0\nLOCAL\nsomethingelse\n' >/etc/adjtime + timedatectl set-local-rtc 0 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +UTC +somethingelse" + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL +somethingelse" + + echo 'no final newline in adjtime file' + printf '0.0 0 0\n0\nUTC' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0\n0\nUTC' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'only one line in adjtime file' + printf '0.0 0 0\n' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0\n' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'only one line in adjtime file, no final newline' + printf '0.0 0 0' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'only two lines in adjtime file' + printf '0.0 0 0\n0\n' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0\n0\n' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'only two lines in adjtime file, no final newline' + printf '0.0 0 0\n0' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0\n0' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" + + echo 'unknown value in 3rd line of adjtime file' + printf '0.0 0 0\n0\nFOO\n' >/etc/adjtime + timedatectl set-local-rtc 0 + check_adjtime_not_exist + printf '0.0 0 0\n0\nFOO\n' >/etc/adjtime + timedatectl set-local-rtc 1 + assert_eq "$(cat /etc/adjtime)" "0.0 0 0 +0 +LOCAL" +} + +assert_ntp() { + local value="${1:?}" + + for _ in {0..9}; do + [[ "$(busctl get-property org.freedesktop.timedate1 /org/freedesktop/timedate1 org.freedesktop.timedate1 NTP)" == "b $value" ]] && return 0 + sleep .5 + done + + return 1 +} + +assert_timedated_signal() { + local timestamp="${1:?}" + local value="${2:?}" + local args=(-q -n 1 --since="$timestamp" -p info _SYSTEMD_UNIT="busctl-monitor.service") + + journalctl --sync + + for _ in {0..9}; do + if journalctl "${args[@]}" --grep .; then + [[ "$(journalctl "${args[@]}" -o cat | jq -r '.payload.data[1].NTP.data')" == "$value" ]]; + return 0 + fi + + sleep .5 + done + + return 1 +} + +assert_timesyncd_state() { + local state="${1:?}" + + for _ in {0..9}; do + [[ "$(systemctl show systemd-timesyncd.service -P ActiveState)" == "$state" ]] && return 0 + sleep .5 + done + + return 1 +} + +testcase_ntp() { + # This fails due to https://github.com/systemd/systemd/issues/30886 + # but it is too complex and risky to backport, so disable the test + return + + # timesyncd has ConditionVirtualization=!container by default; drop/mock that for testing + if systemd-detect-virt --container --quiet; then + systemctl disable --quiet --now systemd-timesyncd + mkdir -p /run/systemd/system/systemd-timesyncd.service.d + cat >/run/systemd/system/systemd-timesyncd.service.d/container.conf </dev/null; then + echo "This test requires systemd-networkd, skipping..." + return 0 + fi + + # Create a dummy interface managed by networkd, so we can configure link NTP servers + mkdir -p /run/systemd/network/ + cat >/etc/systemd/network/10-ntp99.netdev </etc/systemd/network/10-ntp99.network </skipped + exit 0 +fi + +inspect() { + # As updating disk-size-related attributes can take some time on some + # filesystems, let's drop these fields before comparing the outputs to + # avoid unexpected fails. To see the full outputs of both homectl & + # userdbctl (for debugging purposes) drop the fields just before the + # comparison. + local USERNAME="${1:?}" + homectl inspect "$USERNAME" | tee /tmp/a + userdbctl user "$USERNAME" | tee /tmp/b + + # diff uses the grep BREs for pattern matching + diff -I '^\s*Disk \(Size\|Free\|Floor\|Ceiling\):' /tmp/{a,b} + rm /tmp/{a,b} + + homectl inspect --json=pretty "$USERNAME" +} + +wait_for_state() { + for i in {1..10}; do + (( i > 1 )) && sleep 0.5 + homectl inspect "$1" | grep -qF "State: $2" && break + done +} + +systemd-analyze log-level debug +systemctl service-log-level systemd-homed debug + +# Create a tmpfs to use as backing store for the home dir. That way we can enforce a size limit nicely. +mkdir -p /home +mount -t tmpfs tmpfs /home -o size=290M + +# we enable --luks-discard= since we run our tests in a tight VM, hence don't +# needlessly pressure for storage. We also set the cheapest KDF, since we don't +# want to waste CI CPU cycles on it. +NEWPASSWORD=xEhErW0ndafV4s homectl create test-user \ + --disk-size=min \ + --luks-discard=yes \ + --image-path=/home/test-user.home \ + --luks-pbkdf-type=pbkdf2 \ + --luks-pbkdf-time-cost=1ms +inspect test-user + +PASSWORD=xEhErW0ndafV4s homectl authenticate test-user + +PASSWORD=xEhErW0ndafV4s homectl activate test-user +inspect test-user + +PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test" +inspect test-user + +homectl deactivate test-user +inspect test-user + +PASSWORD=xEhErW0ndafV4s NEWPASSWORD=yPN4N0fYNKUkOq homectl passwd test-user +inspect test-user + +PASSWORD=yPN4N0fYNKUkOq homectl activate test-user +inspect test-user + +SYSTEMD_LOG_LEVEL=debug PASSWORD=yPN4N0fYNKUkOq NEWPASSWORD=xEhErW0ndafV4s homectl passwd test-user +inspect test-user + +homectl deactivate test-user +inspect test-user + +PASSWORD=xEhErW0ndafV4s homectl activate test-user +inspect test-user + +homectl deactivate test-user +inspect test-user + +PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test" +inspect test-user + +PASSWORD=xEhErW0ndafV4s homectl activate test-user +inspect test-user + +homectl deactivate test-user +inspect test-user + +# Do some resize tests, but only if we run on real kernels, as quota inside of containers will fail +if ! systemd-detect-virt -cq ; then + # grow while inactive + PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M + inspect test-user + + # minimize while inactive + PASSWORD=xEhErW0ndafV4s homectl resize test-user min + inspect test-user + + PASSWORD=xEhErW0ndafV4s homectl activate test-user + inspect test-user + + # grow while active + PASSWORD=xEhErW0ndafV4s homectl resize test-user max + inspect test-user + + # minimize while active + PASSWORD=xEhErW0ndafV4s homectl resize test-user 0 + inspect test-user + + # grow while active + PASSWORD=xEhErW0ndafV4s homectl resize test-user 300M + inspect test-user + + # shrink to original size while active + PASSWORD=xEhErW0ndafV4s homectl resize test-user 256M + inspect test-user + + # minimize again + PASSWORD=xEhErW0ndafV4s homectl resize test-user min + inspect test-user + + # Increase space, so that we can reasonably rebalance free space between to home dirs + mount /home -o remount,size=800M + + # create second user + NEWPASSWORD=uuXoo8ei homectl create test-user2 \ + --disk-size=min \ + --luks-discard=yes \ + --image-path=/home/test-user2.home \ + --luks-pbkdf-type=pbkdf2 \ + --luks-pbkdf-time-cost=1ms + inspect test-user2 + + # activate second user + PASSWORD=uuXoo8ei homectl activate test-user2 + inspect test-user2 + + # set second user's rebalance weight to 100 + PASSWORD=uuXoo8ei homectl update test-user2 --rebalance-weight=100 + inspect test-user2 + + # set first user's rebalance weight to quarter of that of the second + PASSWORD=xEhErW0ndafV4s homectl update test-user --rebalance-weight=25 + inspect test-user + + # synchronously rebalance + homectl rebalance + inspect test-user + inspect test-user2 +fi + +PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz +(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz) +PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz +PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz +PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz +PASSWORD=xEhErW0ndafV4s homectl with test-user -- test ! -f /home/test-user/xyz +(! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz) + +wait_for_state test-user inactive +homectl remove test-user + +if ! systemd-detect-virt -cq ; then + wait_for_state test-user2 active + homectl deactivate test-user2 + wait_for_state test-user2 inactive + homectl remove test-user2 +fi + +# userdbctl tests +export PAGER= + +# Create a couple of user/group records to test io.systemd.DropIn +# See docs/USER_RECORD.md and docs/GROUP_RECORD.md +mkdir -p /run/userdb/ +cat >"/run/userdb/dropingroup.group" <<\EOF +{ + "groupName" : "dropingroup", + "gid" : 1000000 +} +EOF +cat >"/run/userdb/dropinuser.user" <<\EOF +{ + "userName" : "dropinuser", + "uid" : 2000000, + "realName" : "🐱", + "memberOf" : [ + "dropingroup" + ] +} +EOF +cat >"/run/userdb/dropinuser.user-privileged" <<\EOF +{ + "privileged" : { + "hashedPassword" : [ + "$6$WHBKvAFFT9jKPA4k$OPY4D4TczKN/jOnJzy54DDuOOagCcvxxybrwMbe1SVdm.Bbr.zOmBdATp.QrwZmvqyr8/SafbbQu.QZ2rRvDs/" + ], + "sshAuthorizedKeys" : [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA//dxI2xLg4MgxIKKZv1nqwTEIlE/fdakii2Fb75pG+ foo@bar.tld", + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMlaqG2rTMje5CQnfjXJKmoSpEVJ2gWtx4jBvsQbmee2XbU/Qdq5+SRisssR9zVuxgg5NA5fv08MgjwJQMm+csc= hello@world.tld" + ] + } +} +EOF +# Set permissions and create necessary symlinks as described in nss-systemd(8) +chmod 0600 "/run/userdb/dropinuser.user-privileged" +ln -svrf "/run/userdb/dropingroup.group" "/run/userdb/1000000.group" +ln -svrf "/run/userdb/dropinuser.user" "/run/userdb/2000000.user" +ln -svrf "/run/userdb/dropinuser.user-privileged" "/run/userdb/2000000.user-privileged" + +userdbctl +userdbctl --version +userdbctl --help --no-pager +userdbctl --no-legend +userdbctl --output=classic +userdbctl --output=friendly +userdbctl --output=table +userdbctl --output=json | jq +userdbctl -j --json=pretty | jq +userdbctl -j --json=short | jq +userdbctl --with-varlink=no + +userdbctl user +userdbctl user testuser +userdbctl user root +userdbctl user testuser root +userdbctl user -j testuser root | jq +# Check only UID for the nobody user, since the name is build-configurable +userdbctl user --with-nss=no --synthesize=yes +userdbctl user --with-nss=no --synthesize=yes 0 root 65534 +userdbctl user dropinuser +userdbctl user 2000000 +userdbctl user --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropinuser +userdbctl user --with-nss=no 2000000 +(! userdbctl user '') +(! userdbctl user 🐱) +(! userdbctl user 🐱 '' bar) +(! userdbctl user i-do-not-exist) +(! userdbctl user root i-do-not-exist testuser) +(! userdbctl user --with-nss=no --synthesize=no 0 root 65534) +(! userdbctl user -N root nobody) +(! userdbctl user --with-dropin=no dropinuser) +(! userdbctl user --with-dropin=no 2000000) + +userdbctl group +userdbctl group testuser +userdbctl group root +userdbctl group testuser root +userdbctl group -j testuser root | jq +# Check only GID for the nobody group, since the name is build-configurable +userdbctl group --with-nss=no --synthesize=yes +userdbctl group --with-nss=no --synthesize=yes 0 root 65534 +userdbctl group dropingroup +userdbctl group 1000000 +userdbctl group --with-nss=no --with-varlink=no --synthesize=no --multiplexer=no dropingroup +userdbctl group --with-nss=no 1000000 +(! userdbctl group '') +(! userdbctl group 🐱) +(! userdbctl group 🐱 '' bar) +(! userdbctl group i-do-not-exist) +(! userdbctl group root i-do-not-exist testuser) +(! userdbctl group --with-nss=no --synthesize=no 0 root 65534) +(! userdbctl group --with-dropin=no dropingroup) +(! userdbctl group --with-dropin=no 1000000) + +userdbctl users-in-group +userdbctl users-in-group testuser +userdbctl users-in-group testuser root +userdbctl users-in-group -j testuser root | jq +userdbctl users-in-group 🐱 +(! userdbctl users-in-group '') +(! userdbctl users-in-group foo '' bar) + +userdbctl groups-of-user +userdbctl groups-of-user testuser +userdbctl groups-of-user testuser root +userdbctl groups-of-user -j testuser root | jq +userdbctl groups-of-user 🐱 +(! userdbctl groups-of-user '') +(! userdbctl groups-of-user foo '' bar) + +userdbctl services +userdbctl services -j | jq + +varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"testuser","service":"io.systemd.Multiplexer"}' +varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"root","service":"io.systemd.Multiplexer"}' +varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"dropinuser","service":"io.systemd.Multiplexer"}' +varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"uid":2000000,"service":"io.systemd.Multiplexer"}' +(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"","service":"io.systemd.Multiplexer"}') +(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"🐱","service":"io.systemd.Multiplexer"}') +(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"i-do-not-exist","service":"io.systemd.Multiplexer"}') + +userdbctl ssh-authorized-keys dropinuser | tee /tmp/authorized-keys +grep "ssh-ed25519" /tmp/authorized-keys +grep "ecdsa-sha2-nistp256" /tmp/authorized-keys +echo "my-top-secret-key 🐱" >/tmp/my-top-secret-key +userdbctl ssh-authorized-keys dropinuser --chain /bin/cat /tmp/my-top-secret-key | tee /tmp/authorized-keys +grep "ssh-ed25519" /tmp/authorized-keys +grep "ecdsa-sha2-nistp256" /tmp/authorized-keys +grep "my-top-secret-key 🐱" /tmp/authorized-keys +(! userdbctl ssh-authorized-keys 🐱) +(! userdbctl ssh-authorized-keys dropin-user --chain) +(! userdbctl ssh-authorized-keys dropin-user --chain '') +(! SYSTEMD_LOG_LEVEL=debug userdbctl ssh-authorized-keys dropin-user --chain /bin/false) + +(! userdbctl '') +for opt in json multiplexer output synthesize with-dropin with-nss with-varlink; do + (! userdbctl "--$opt=''") + (! userdbctl "--$opt='🐱'") + (! userdbctl "--$opt=foo") + (! userdbctl "--$opt=foo" "--$opt=''" "--$opt=🐱") +done + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-50.service b/test/units/testsuite-50.service new file mode 100644 index 0000000..bcafe6e --- /dev/null +++ b/test/units/testsuite-50.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-50-DISSECT + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh new file mode 100755 index 0000000..28218ab --- /dev/null +++ b/test/units/testsuite-50.sh @@ -0,0 +1,718 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +# shellcheck disable=SC2233,SC2235 +set -eux +set -o pipefail + +export SYSTEMD_LOG_LEVEL=debug + +# shellcheck disable=SC2317 +cleanup() {( + set +ex + + if [ -z "${image_dir}" ]; then + return + fi + umount "${image_dir}/app0" + umount "${image_dir}/app1" + umount "${image_dir}/app-nodistro" + umount "${image_dir}/service-scoped-test" + rm -rf "${image_dir}" +)} + +udevadm control --log-level=debug + +cd /tmp + +image_dir="$(mktemp -d -t -p /tmp tmp.XXXXXX)" +if [ -z "${image_dir}" ] || [ ! -d "${image_dir}" ]; then + echo "mktemp under /tmp failed" + exit 1 +fi + +trap cleanup EXIT + +cp /usr/share/minimal* "${image_dir}/" +image="${image_dir}/minimal_0" +roothash="$(cat "${image}.roothash")" + +os_release="$(test -e /etc/os-release && echo /etc/os-release || echo /usr/lib/os-release)" + +systemd-dissect --json=short "${image}.raw" | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"partition_label":null,"fstype":"squashfs","architecture":null,"verity":"external"' +systemd-dissect "${image}.raw" | grep -q -F "MARKER=1" +systemd-dissect "${image}.raw" | grep -q -F -f <(sed 's/"//g' "$os_release") + +systemd-dissect --list "${image}.raw" | grep -q '^etc/os-release$' +systemd-dissect --mtree "${image}.raw" --mtree-hash yes | grep -qe "^./usr/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]* sha256sum=[a-z0-9]*$" +systemd-dissect --mtree "${image}.raw" --mtree-hash no | grep -qe "^./usr/bin/cat type=file mode=0755 uid=0 gid=0 size=[0-9]*$" + +read -r SHA256SUM1 _ < <(systemd-dissect --copy-from "${image}.raw" etc/os-release | sha256sum) +test "$SHA256SUM1" != "" +read -r SHA256SUM2 _ < <(systemd-dissect --read-only --with "${image}.raw" sha256sum etc/os-release) +test "$SHA256SUM2" != "" +test "$SHA256SUM1" = "$SHA256SUM2" + +mv "${image}.verity" "${image}.fooverity" +mv "${image}.roothash" "${image}.foohash" +systemd-dissect --json=short "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F '{"rw":"ro","designator":"root","partition_uuid":null,"partition_label":null,"fstype":"squashfs","architecture":null,"verity":"external"' +systemd-dissect "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F "MARKER=1" +systemd-dissect "${image}.raw" --root-hash="${roothash}" --verity-data="${image}.fooverity" | grep -q -F -f <(sed 's/"//g' "$os_release") +mv "${image}.fooverity" "${image}.verity" +mv "${image}.foohash" "${image}.roothash" + +mkdir -p "${image_dir}/mount" "${image_dir}/mount2" +systemd-dissect --mount "${image}.raw" "${image_dir}/mount" +grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release" +grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" +grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release" +# Verity volume should be shared (opened only once) +systemd-dissect --mount "${image}.raw" "${image_dir}/mount2" +verity_count=$(find /dev/mapper/ -name "*verity*" | wc -l) +# In theory we should check that count is exactly one. In practice, libdevmapper +# randomly and unpredictably fails with an unhelpful EINVAL when a device is open +# (and even mounted and in use), so best-effort is the most we can do for now +if [ "${verity_count}" -lt 1 ]; then + echo "Verity device ${image}.raw not found in /dev/mapper/" + exit 1 +fi +systemd-dissect --umount "${image_dir}/mount" +systemd-dissect --umount "${image_dir}/mount2" + +systemd-run -P -p RootImage="${image}.raw" cat /usr/lib/os-release | grep -q -F "MARKER=1" +mv "${image}.verity" "${image}.fooverity" +mv "${image}.roothash" "${image}.foohash" +systemd-run -P -p RootImage="${image}.raw" -p RootHash="${image}.foohash" -p RootVerity="${image}.fooverity" cat /usr/lib/os-release | grep -q -F "MARKER=1" +# Let's use the long option name just here as a test +systemd-run -P --property RootImage="${image}.raw" --property RootHash="${roothash}" --property RootVerity="${image}.fooverity" cat /usr/lib/os-release | grep -q -F "MARKER=1" +mv "${image}.fooverity" "${image}.verity" +mv "${image}.foohash" "${image}.roothash" + +# Make a GPT disk on the fly, with the squashfs as partition 1 and the verity hash tree as partition 2 +machine="$(uname -m)" +if [ "${machine}" = "x86_64" ]; then + root_guid=4f68bce3-e8cd-4db1-96e7-fbcaf984b709 + verity_guid=2c7357ed-ebd2-46d9-aec1-23d437ec2bf5 + signature_guid=41092b05-9fc8-4523-994f-2def0408b176 + architecture="x86-64" +elif [ "${machine}" = "i386" ] || [ "${machine}" = "i686" ] || [ "${machine}" = "x86" ]; then + root_guid=44479540-f297-41b2-9af7-d131d5f0458a + verity_guid=d13c5d3b-b5d1-422a-b29f-9454fdc89d76 + signature_guid=5996fc05-109c-48de-808b-23fa0830b676 + architecture="x86" +elif [ "${machine}" = "aarch64" ] || [ "${machine}" = "aarch64_be" ] || [ "${machine}" = "armv8b" ] || [ "${machine}" = "armv8l" ]; then + root_guid=b921b045-1df0-41c3-af44-4c6f280d3fae + verity_guid=df3300ce-d69f-4c92-978c-9bfb0f38d820 + signature_guid=6db69de6-29f4-4758-a7a5-962190f00ce3 + architecture="arm64" +elif [ "${machine}" = "arm" ]; then + root_guid=69dad710-2ce4-4e3c-b16c-21a1d49abed3 + verity_guid=7386cdf2-203c-47a9-a498-f2ecce45a2d6 + signature_guid=42b0455f-eb11-491d-98d3-56145ba9d037 + architecture="arm" +elif [ "${machine}" = "loongarch64" ]; then + root_guid=77055800-792c-4f94-b39a-98c91b762bb6 + verity_guid=f3393b22-e9af-4613-a948-9d3bfbd0c535 + signature_guid=5afb67eb-ecc8-4f85-ae8e-ac1e7c50e7d0 + architecture="loongarch64" +elif [ "${machine}" = "ia64" ]; then + root_guid=993d8d3d-f80e-4225-855a-9daf8ed7ea97 + verity_guid=86ed10d5-b607-45bb-8957-d350f23d0571 + signature_guid=e98b36ee-32ba-4882-9b12-0ce14655f46a + architecture="ia64" +elif [ "${machine}" = "s390x" ]; then + root_guid=5eead9a9-fe09-4a1e-a1d7-520d00531306 + verity_guid=b325bfbe-c7be-4ab8-8357-139e652d2f6b + signature_guid=c80187a5-73a3-491a-901a-017c3fa953e9 + architecture="s390x" +elif [ "${machine}" = "ppc64le" ]; then + root_guid=c31c45e6-3f39-412e-80fb-4809c4980599 + verity_guid=906bd944-4589-4aae-a4e4-dd983917446a + signature_guid=d4a236e7-e873-4c07-bf1d-bf6cf7f1c3c6 + architecture="ppc64-le" +else + echo "Unexpected uname -m: ${machine} in testsuite-50.sh, please fix me" + exit 1 +fi +# du rounds up to block size, which is more helpful for partitioning +root_size="$(du -k "${image}.raw" | cut -f1)" +verity_size="$(du -k "${image}.verity" | cut -f1)" +signature_size=4 +# 4MB seems to be the minimum size blkid will accept, below that probing fails +dd if=/dev/zero of="${image}.gpt" bs=512 count=$((8192+root_size*2+verity_size*2+signature_size*2)) +# sfdisk seems unhappy if the size overflows into the next unit, eg: 1580KiB will be interpreted as 1MiB +# so do some basic rounding up if the minimal image is more than 1 MB +if [ "${root_size}" -ge 1024 ]; then + root_size="$((root_size/1024 + 1))MiB" +else + root_size="${root_size}KiB" +fi +verity_size="$((verity_size * 2))KiB" +signature_size="$((signature_size * 2))KiB" + +HAVE_OPENSSL=0 +if systemctl --version | grep -q -- +OPENSSL ; then + # The openssl binary is installed conditionally. + # If we have OpenSSL support enabled and openssl is missing, fail early + # with a proper error message. + if ! command -v openssl >/dev/null 2>&1; then + echo "openssl missing" >/failed + exit 1 + fi + + HAVE_OPENSSL=1 + OPENSSL_CONFIG="$(mktemp)" + # Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents + cat >"${OPENSSL_CONFIG:?}" <"${image}.verity-sig" + # Pad it + truncate -s "${signature_size}" "${image}.verity-sig" + # Register certificate in the (userspace) verity key ring + mkdir -p /run/verity.d + ln -s "${image}.crt" /run/verity.d/ok.crt +fi + +# Construct a UUID from hash +# input: 11111111222233334444555566667777 +# output: 11111111-2222-3333-4444-555566667777 +uuid="$(head -c 32 "${image}.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "label: gpt\nsize=${root_size}, type=${root_guid}, uuid=${uuid}" | sfdisk "${image}.gpt" +uuid="$(tail -c 32 "${image}.roothash" | sed -r 's/(.{8})(.{4})(.{4})(.{4})(.+)/\1-\2-\3-\4-\5/')" +echo -e "size=${verity_size}, type=${verity_guid}, uuid=${uuid}" | sfdisk "${image}.gpt" --append +if [ "${HAVE_OPENSSL}" -eq 1 ]; then + echo -e "size=${signature_size}, type=${signature_guid}" | sfdisk "${image}.gpt" --append +fi +sfdisk --part-label "${image}.gpt" 1 "Root Partition" +sfdisk --part-label "${image}.gpt" 2 "Verity Partition" +if [ "${HAVE_OPENSSL}" -eq 1 ]; then + sfdisk --part-label "${image}.gpt" 3 "Signature Partition" +fi +loop="$(losetup --show -P -f "${image}.gpt")" +partitions=( + "${loop:?}p1" + "${loop:?}p2" +) +if [ "${HAVE_OPENSSL}" -eq 1 ]; then + partitions+=( "${loop:?}p3" ) +fi +# The kernel sometimes(?) does not emit "add" uevent for loop block partition devices. +# Let's not expect the devices to be initialized. +udevadm wait --timeout 60 --settle --initialized=no "${partitions[@]}" +udevadm lock --device="${loop}p1" dd if="${image}.raw" of="${loop}p1" +udevadm lock --device="${loop}p2" dd if="${image}.verity" of="${loop}p2" +if [ "${HAVE_OPENSSL}" -eq 1 ]; then + udevadm lock --device="${loop}p3" dd if="${image}.verity-sig" of="${loop}p3" +fi +losetup -d "${loop}" + +# Derive partition UUIDs from root hash, in UUID syntax +ROOT_UUID="$(systemd-id128 -u show "$(head -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)" +VERITY_UUID="$(systemd-id128 -u show "$(tail -c 32 "${image}.roothash")" -u | tail -n 1 | cut -b 6-)" + +systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root","partition_uuid":"'"$ROOT_UUID"'","partition_label":"Root Partition","fstype":"squashfs","architecture":"'"$architecture"'","verity":"signed",' +systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q '{"rw":"ro","designator":"root-verity","partition_uuid":"'"$VERITY_UUID"'","partition_label":"Verity Partition","fstype":"DM_verity_hash","architecture":"'"$architecture"'","verity":null,' +if [ "${HAVE_OPENSSL}" -eq 1 ]; then + systemd-dissect --json=short --root-hash "${roothash}" "${image}.gpt" | grep -q -E '{"rw":"ro","designator":"root-verity-sig","partition_uuid":"'".*"'","partition_label":"Signature Partition","fstype":"verity_hash_signature","architecture":"'"$architecture"'","verity":null,' +fi +systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F "MARKER=1" +systemd-dissect --root-hash "${roothash}" "${image}.gpt" | grep -q -F -f <(sed 's/"//g' "$os_release") + +# Test image policies +systemd-dissect --validate "${image}.gpt" +systemd-dissect --validate "${image}.gpt" --image-policy='*' +(! systemd-dissect --validate "${image}.gpt" --image-policy='~') +(! systemd-dissect --validate "${image}.gpt" --image-policy='-') +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=absent) +(! systemd-dissect --validate "${image}.gpt" --image-policy=swap=unprotected+encrypted+verity) +systemd-dissect --validate "${image}.gpt" --image-policy=root=unprotected +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity-sig=unused+absent +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent +systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:swap=absent+unprotected +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=verity:root-verity=unused+absent) +systemd-dissect --validate "${image}.gpt" --image-policy=root=signed +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity-sig=unused+absent) +(! systemd-dissect --validate "${image}.gpt" --image-policy=root=signed:root-verity=unused+absent) + +# Test RootImagePolicy= unit file setting +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='*' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='~' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='-' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=absent' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=verity' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=signed' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" +(! systemd-run --wait -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p RootImagePolicy='root=encrypted' -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1") + +systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" "${image_dir}/mount" +grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release" +grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" +grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release" +systemd-dissect --umount "${image_dir}/mount" + +systemd-dissect --root-hash "${roothash}" --mount "${image}.gpt" --in-memory "${image_dir}/mount" +grep -q -F -f "$os_release" "${image_dir}/mount/usr/lib/os-release" +grep -q -F -f "$os_release" "${image_dir}/mount/etc/os-release" +grep -q -F "MARKER=1" "${image_dir}/mount/usr/lib/os-release" +systemd-dissect --umount "${image_dir}/mount" + +# add explicit -p MountAPIVFS=yes once to test the parser +systemd-run -P -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountAPIVFS=yes cat /usr/lib/os-release | grep -q -F "MARKER=1" + +systemd-run -P -p RootImage="${image}.raw" -p RootImageOptions="root:nosuid,dev home:ro,dev ro,noatime" mount | grep -F "squashfs" | grep -q -F "nosuid" +systemd-run -P -p RootImage="${image}.gpt" -p RootImageOptions="root:ro,noatime root:ro,dev" mount | grep -F "squashfs" | grep -q -F "noatime" + +mkdir -p "${image_dir}/result" +cat >/run/systemd/system/testservice-50a.service </run/result/a" +BindPaths=${image_dir}/result:/run/result +TemporaryFileSystem=/run +RootImage=${image}.raw +RootImageOptions=root:ro,noatime home:ro,dev relatime,dev +RootImageOptions=nosuid,dev +EOF +systemctl start testservice-50a.service +grep -F "squashfs" "${image_dir}/result/a" | grep -q -F "noatime" +grep -F "squashfs" "${image_dir}/result/a" | grep -q -F -v "nosuid" + +cat >/run/systemd/system/testservice-50b.service </run/result/b" +BindPaths=${image_dir}/result:/run/result +TemporaryFileSystem=/run +RootImage=${image}.gpt +RootImageOptions=root:ro,noatime,nosuid home:ro,dev nosuid,dev +RootImageOptions=home:ro,dev nosuid,dev,%%foo +# this is the default, but let's specify once to test the parser +MountAPIVFS=yes +EOF +systemctl start testservice-50b.service +grep -F "squashfs" "${image_dir}/result/b" | grep -q -F "noatime" + +# Check that specifier escape is applied %%foo → %foo +busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/testservice_2d50b_2eservice org.freedesktop.systemd1.Service RootImageOptions | grep -F "nosuid,dev,%foo" + +# Now do some checks with MountImages, both by itself, with options and in combination with RootImage, and as single FS or GPT image +systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -P -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2:nosuid,dev" mount | grep -F "squashfs" | grep -q -F "nosuid" +systemd-run -P -p MountImages="${image}.gpt:/run/img1:root:nosuid ${image}.raw:/run/img2:home:suid" mount | grep -F "squashfs" | grep -q -F "nosuid" +systemd-run -P -p MountImages="${image}.raw:/run/img2\:3" cat /run/img2:3/usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -P -p MountImages="${image}.raw:/run/img2\:3:nosuid" mount | grep -F "squashfs" | grep -q -F "nosuid" +systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.raw" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.raw" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -P -p TemporaryFileSystem=/run -p RootImage="${image}.gpt" -p RootHash="${roothash}" -p MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1" +cat >/run/systemd/system/testservice-50c.service </run/result/c" +ExecStart=bash -c "cat /run/img2:3/usr/lib/os-release >>/run/result/c" +ExecStart=bash -c "mount >>/run/result/c" +BindPaths=${image_dir}/result:/run/result +Type=oneshot +EOF +systemctl start testservice-50c.service +grep -q -F "MARKER=1" "${image_dir}/result/c" +grep -F "squashfs" "${image_dir}/result/c" | grep -q -F "noatime" +grep -F "squashfs" "${image_dir}/result/c" | grep -q -F -v "nosuid" + +# Adding a new mounts at runtime works if the unit is in the active state, +# so use Type=notify to make sure there's no race condition in the test +cat >/run/systemd/system/testservice-50d.service </run/systemd/system/testservice-50e.service </run/systemd/system/testservice-50f.service </run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject +echo "ID=_any" >/run/extensions/app-reject/usr/lib/os-release +touch /run/extensions/app-reject/usr/lib/systemd/system/other_file +(! systemd-sysext merge) +test ! -e /usr/lib/systemd/system/some_file +test ! -e /usr/lib/systemd/system/other_file +systemd-sysext unmerge +rm -rf /run/extensions/app-reject +rm /var/lib/extensions/app-nodistro.raw + +mkdir -p /run/machines /run/portables /run/extensions +touch /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw + +systemd-dissect --discover --json=short >/tmp/discover.json +grep -q -F '{"name":"a","type":"raw","class":"machine","ro":false,"path":"/run/machines/a.raw"' /tmp/discover.json +grep -q -F '{"name":"b","type":"raw","class":"portable","ro":false,"path":"/run/portables/b.raw"' /tmp/discover.json +grep -q -F '{"name":"c","type":"raw","class":"sysext","ro":false,"path":"/run/extensions/c.raw"' /tmp/discover.json +rm /tmp/discover.json /run/machines/a.raw /run/portables/b.raw /run/extensions/c.raw + +# Check that the /sbin/mount.ddi helper works +T="/tmp/mounthelper.$RANDOM" +mount -t ddi "${image}.gpt" "$T" -o ro,X-mount.mkdir,discard +umount -R "$T" +rmdir "$T" + +LOOP="$(systemd-dissect --attach --loop-ref=waldo "${image}.raw")" + +# Wait until the symlinks we want to test are established +udevadm trigger -w "$LOOP" + +# Check if the /dev/loop/* symlinks really reference the right device +test /dev/disk/by-loop-ref/waldo -ef "$LOOP" + +if [ "$(stat -c '%Hd:%Ld' "${image}.raw")" != '?d:?d' ] ; then + # Old stat didn't know the %Hd and %Ld specifiers and turned them into ?d + # instead. Let's simply skip the test on such old systems. + test "$(stat -c '/dev/disk/by-loop-inode/%Hd:%Ld-%i' "${image}.raw")" -ef "$LOOP" +fi + +# Detach by loopback device +systemd-dissect --detach "$LOOP" + +# Test long reference name. +# Note, sizeof_field(struct loop_info64, lo_file_name) == 64, +# and --loop-ref accepts upto 63 characters, and udev creates symlink +# based on the name when it has upto _62_ characters. +name="$(for _ in {1..62}; do echo -n 'x'; done)" +LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")" +udevadm trigger -w "$LOOP" + +# Check if the /dev/disk/by-loop-ref/$name symlink really references the right device +test "/dev/disk/by-loop-ref/$name" -ef "$LOOP" + +# Detach by the /dev/disk/by-loop-ref symlink +systemd-dissect --detach "/dev/disk/by-loop-ref/$name" + +name="$(for _ in {1..63}; do echo -n 'x'; done)" +LOOP="$(systemd-dissect --attach --loop-ref="$name" "${image}.raw")" +udevadm trigger -w "$LOOP" + +# Check if the /dev/disk/by-loop-ref/$name symlink does not exist +test ! -e "/dev/disk/by-loop-ref/$name" + +# Detach by backing inode +systemd-dissect --detach "${image}.raw" +(! systemd-dissect --detach "${image}.raw") + +# check for confext functionality +mkdir -p /run/confexts/test/etc/extension-release.d +echo "ID=_any" >/run/confexts/test/etc/extension-release.d/extension-release.test +echo "ARCHITECTURE=_any" >>/run/confexts/test/etc/extension-release.d/extension-release.test +echo "MARKER_CONFEXT_123" >/run/confexts/test/etc/testfile +cat </run/confexts/test/etc/testscript +#!/bin/bash +echo "This should not happen" +EOF +chmod +x /run/confexts/test/etc/testscript +systemd-confext merge +grep -q -F "MARKER_CONFEXT_123" /etc/testfile +(! /etc/testscript) +systemd-confext status +systemd-confext unmerge +rm -rf /run/confexts/ + +unsquashfs -no-xattrs -d /tmp/img "${image}.raw" +systemd-run --unit=test-root-ephemeral \ + -p RootDirectory=/tmp/img \ + -p RootEphemeral=yes \ + -p Type=exec \ + bash -c "touch /abc && sleep infinity" +test -n "$(ls -A /var/lib/systemd/ephemeral-trees)" +systemctl stop test-root-ephemeral +# shellcheck disable=SC2016 +timeout 10 bash -c 'until test -z "$(ls -A /var/lib/systemd/ephemeral-trees)"; do sleep .5; done' +test ! -f /tmp/img/abc + +systemd-dissect --mtree /tmp/img +systemd-dissect --list /tmp/img + +read -r SHA256SUM1 _ < <(systemd-dissect --copy-from /tmp/img etc/os-release | sha256sum) +test "$SHA256SUM1" != "" + +echo abc > abc +systemd-dissect --copy-to /tmp/img abc /abc +test -f /tmp/img/abc + +# Test for dissect tool support with systemd-sysext +mkdir -p /run/extensions/ testkit/usr/lib/extension-release.d/ +echo "ID=_any" >testkit/usr/lib/extension-release.d/extension-release.testkit +echo "ARCHITECTURE=_any" >>testkit/usr/lib/extension-release.d/extension-release.testkit +echo "MARKER_SYSEXT_123" >testkit/usr/lib/testfile +mksquashfs testkit/ testkit.raw +cp testkit.raw /run/extensions/ +unsquashfs -l /run/extensions/testkit.raw +systemd-dissect --no-pager /run/extensions/testkit.raw | grep -q '✓ sysext for portable service' +systemd-dissect --no-pager /run/extensions/testkit.raw | grep -q '✓ sysext for system' +systemd-sysext merge +systemd-sysext status +grep -q -F "MARKER_SYSEXT_123" /usr/lib/testfile +systemd-sysext unmerge +rm -rf /run/extensions/ testkit/ + +# Test for dissect tool support with systemd-confext +mkdir -p /run/confexts/ testjob/etc/extension-release.d/ +echo "ID=_any" >testjob/etc/extension-release.d/extension-release.testjob +echo "ARCHITECTURE=_any" >>testjob/etc/extension-release.d/extension-release.testjob +echo "MARKER_CONFEXT_123" >testjob/etc/testfile +mksquashfs testjob/ testjob.raw +cp testjob.raw /run/confexts/ +unsquashfs -l /run/confexts/testjob.raw +systemd-dissect --no-pager /run/confexts/testjob.raw | grep -q '✓ confext for system' +systemd-dissect --no-pager /run/confexts/testjob.raw | grep -q '✓ confext for portable service' +systemd-confext merge +systemd-confext status +grep -q -F "MARKER_CONFEXT_123" /etc/testfile +systemd-confext unmerge +rm -rf /run/confexts/ testjob/ + +systemd-run -P -p RootImage="${image}.raw" cat /run/host/os-release | cmp "${os_release}" + +# Test that systemd-sysext reloads the daemon. +mkdir -p /var/lib/extensions/ +ln -s /usr/share/app-reload.raw /var/lib/extensions/app-reload.raw +systemd-sysext merge --no-reload +# the service should not be running +if systemctl --quiet is-active foo.service; then + echo "foo.service should not be active" + exit 1 +fi +systemd-sysext unmerge --no-reload +systemd-sysext merge +for RETRY in $(seq 60) LAST; do + if journalctl --boot --unit foo.service | grep -q -P 'echo\[[0-9]+\]: foo'; then + break + fi + if [ "${RETRY}" = LAST ]; then + echo "Output of foo.service not found" + exit 1 + fi + sleep 0.5 +done +systemd-sysext unmerge --no-reload +# Grep on the Warning to find the warning helper mentioning the daemon reload. +systemctl status foo.service 2>&1 | grep -q -F "Warning" +systemd-sysext merge +systemd-sysext unmerge +systemctl status foo.service 2>&1 | grep -v -q -F "Warning" +rm /var/lib/extensions/app-reload.raw + +# Test systemd-repart --make-ddi=: +if command -v mksquashfs >/dev/null 2>&1; then + + openssl req -config "$OPENSSL_CONFIG" -subj="/CN=waldo" -x509 -sha256 -nodes -days 365 -newkey rsa:4096 -keyout /tmp/test-50-privkey.key -out /tmp/test-50-cert.crt + + mkdir -p /tmp/test-50-confext/etc/extension-release.d/ + + echo "foobar50" > /tmp/test-50-confext/etc/waldo + + ( grep -e '^\(ID\|VERSION_ID\)=' /etc/os-release ; echo IMAGE_ID=waldo ; echo IMAGE_VERSION=7 ) > /tmp/test-50-confext/etc/extension-release.d/extension-release.waldo + + mkdir -p /run/confexts + + SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs systemd-repart -C -s /tmp/test-50-confext --certificate=/tmp/test-50-cert.crt --private-key=/tmp/test-50-privkey.key /run/confexts/waldo.confext.raw + rm -rf /tmp/test-50-confext + + mkdir -p /run/verity.d + cp /tmp/test-50-cert.crt /run/verity.d/ + systemd-dissect --mtree /run/confexts/waldo.confext.raw + + systemd-confext refresh + + read -r X < /etc/waldo + test "$X" = foobar50 + + rm /run/confexts/waldo.confext.raw + + systemd-confext refresh + + (! test -f /etc/waldo ) + + mkdir -p /tmp/test-50-sysext/usr/lib/extension-release.d/ + + # Make sure the sysext is big enough to not fit in the minimum partition size of repart so we know the + # Minimize= logic is working. + truncate --size=50M /tmp/test-50-sysext/usr/waldo + + ( grep -e '^\(ID\|VERSION_ID\)=' /etc/os-release ; echo IMAGE_ID=waldo ; echo IMAGE_VERSION=7 ) > /tmp/test-50-sysext/usr/lib/extension-release.d/extension-release.waldo + + mkdir -p /run/extensions + + SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs systemd-repart -S -s /tmp/test-50-sysext --certificate=/tmp/test-50-cert.crt --private-key=/tmp/test-50-privkey.key /run/extensions/waldo.sysext.raw + + systemd-dissect --mtree /run/extensions/waldo.sysext.raw + + systemd-sysext refresh + + test -f /usr/waldo + + rm /run/verity.d/test-50-cert.crt /run/extensions/waldo.sysext.raw /tmp/test-50-cert.crt /tmp/test-50-privkey.key + + systemd-sysext refresh + + (! test -f /usr/waldo) +fi + +# Sneak in a couple of expected-to-fail invocations to cover +# https://github.com/systemd/systemd/issues/29610 +(! systemd-run -P -p MountImages="/this/should/definitely/not/exist.img:/run/img2\:3:nosuid" false) +(! systemd-run -P -p ExtensionImages="/this/should/definitely/not/exist.img" false) +(! systemd-run -P -p RootImage="/this/should/definitely/not/exist.img" false) +(! systemd-run -P -p ExtensionDirectories="/foo/bar /foo/baz" false) + +touch /testok diff --git a/test/units/testsuite-52.service b/test/units/testsuite-52.service new file mode 100644 index 0000000..b9f2909 --- /dev/null +++ b/test/units/testsuite-52.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Testsuite service + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-52.sh b/test/units/testsuite-52.sh new file mode 100755 index 0000000..16ff507 --- /dev/null +++ b/test/units/testsuite-52.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +systemd-analyze log-level debug + +systemctl enable test-honor-first-shutdown.service +systemctl start test-honor-first-shutdown.service + +touch /testok diff --git a/test/units/testsuite-53.service b/test/units/testsuite-53.service new file mode 100644 index 0000000..cf3adbb --- /dev/null +++ b/test/units/testsuite-53.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-53-ISSUE-16347 + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-53.sh b/test/units/testsuite-53.sh new file mode 100755 index 0000000..84cd661 --- /dev/null +++ b/test/units/testsuite-53.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +: >/failed + +# Reset host date to current time, 3 days in the past. +date -s "-3 days" + +# Run a timer for every 15 minutes. +systemd-run --unit test-timer --on-calendar "*:0/15:0" true + +next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value) +next_elapsed=$(date -d "${next_elapsed}" +%s) +now=$(date +%s) +time_delta=$((next_elapsed - now)) + +# Check that the timer will elapse in less than 20 minutes. +((0 < time_delta && time_delta < 1200)) || { + echo 'Timer elapse outside of the expected 20 minute window.' + echo " next_elapsed=${next_elapsed}" + echo " now=${now}" + echo " time_delta=${time_delta}" + echo '' +} >>/failed + +if test ! -s /failed ; then + rm -f /failed + touch /testok +fi diff --git a/test/units/testsuite-54.service b/test/units/testsuite-54.service new file mode 100644 index 0000000..ba8cdad --- /dev/null +++ b/test/units/testsuite-54.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TESTSUITE-54-CREDS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh new file mode 100755 index 0000000..bcbe7a1 --- /dev/null +++ b/test/units/testsuite-54.sh @@ -0,0 +1,319 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux + +systemd-analyze log-level debug + +run_with_cred_compare() { + local cred="${1:?}" + local exp="${2?}" + shift 2 + + diff <(systemd-run -p SetCredential="$cred" --wait --pipe -- systemd-creds "$@") <(echo -ne "$exp") +} + +# Sanity checks +# +# Create a dummy "full" disk (similar to /dev/full) to check out-of-space +# scenarios +mkdir /tmp/full +mount -t tmpfs -o size=1,nr_inodes=1 tmpfs /tmp/full + +# verb: setup +# Run this first, otherwise any encrypted credentials wouldn't be decryptable +# as we regenerate the host key +rm -fv /var/lib/systemd/credential.secret +systemd-creds setup +test -e /var/lib/systemd/credential.secret +rm -fv /var/lib/systemd/credential.secret + +# Prepare a couple of dummy credentials for the cat/list verbs +CRED_DIR="$(mktemp -d)" +ENC_CRED_DIR="$(mktemp -d)" +echo foo >"$CRED_DIR/secure-or-weak" +echo foo >"$CRED_DIR/insecure" +echo foo | systemd-creds --name="encrypted" encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted" +echo foo | systemd-creds encrypt - - | base64 -d >"$ENC_CRED_DIR/encrypted-unnamed" +chmod -R 0400 "$CRED_DIR" "$ENC_CRED_DIR" +chmod -R 0444 "$CRED_DIR/insecure" +mkdir /tmp/empty/ + +systemd-creds --system +systemd-creds --no-pager --help +systemd-creds --version +systemd-creds has-tpm2 || : +systemd-creds has-tpm2 -q || : + +# verb: list +systemd-creds list --system +ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --no-legend +ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=pretty | jq +ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=short | jq +ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds list --json=off +ENCRYPTED_CREDENTIALS_DIRECTORY="/tmp/empty/" CREDENTIALS_DIRECTORY="/tmp/empty/" systemd-creds list + +# verb: cat +for cred in secure-or-weak insecure encrypted encrypted-unnamed; do + ENCRYPTED_CREDENTIALS_DIRECTORY="$ENC_CRED_DIR" CREDENTIALS_DIRECTORY="$CRED_DIR" systemd-creds cat "$cred" +done +run_with_cred_compare "mycred:" "" cat mycred +run_with_cred_compare "mycred:\n" "\n" cat mycred +run_with_cred_compare "mycred:foo" "foo" cat mycred +run_with_cred_compare "mycred:foo" "foofoofoo" cat mycred mycred mycred +# Note: --newline= does nothing when stdout is not a tty, which is the case here +run_with_cred_compare "mycred:foo" "foo" --newline=yes cat mycred +run_with_cred_compare "mycred:foo" "foo" --newline=no cat mycred +run_with_cred_compare "mycred:foo" "foo" --newline=auto cat mycred +run_with_cred_compare "mycred:foo" "foo" --transcode=no cat mycred +run_with_cred_compare "mycred:foo" "foo" --transcode=0 cat mycred +run_with_cred_compare "mycred:foo" "foo" --transcode=false cat mycred +run_with_cred_compare "mycred:foo" "Zm9v" --transcode=base64 cat mycred +run_with_cred_compare "mycred:Zm9v" "foo" --transcode=unbase64 cat mycred +run_with_cred_compare "mycred:Zm9v" "foofoofoo" --transcode=unbase64 cat mycred mycred mycred +run_with_cred_compare "mycred:Zm9vCg==" "foo\n" --transcode=unbase64 cat mycred +run_with_cred_compare "mycred:hello world" "68656c6c6f20776f726c64" --transcode=hex cat mycred +run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello world" --transcode=unhex cat mycred +run_with_cred_compare "mycred:68656c6c6f20776f726c64" "hello worldhello world" --transcode=unhex cat mycred mycred +run_with_cred_compare "mycred:68656c6c6f0a776f726c64" "hello\nworld" --transcode=unhex cat mycred +run_with_cred_compare 'mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' '{"foo":"bar","baz":[3,4]}\n' --json=short cat mycred +systemd-run -p SetCredential='mycred:{ "foo" : "bar", "baz" : [ 3, 4 ] }' --wait --pipe -- systemd-creds --json=pretty cat mycred | jq + +# verb: encrypt/decrypt +echo "According to all known laws of aviation..." >/tmp/cred.orig +systemd-creds --with-key=host encrypt /tmp/cred.orig /tmp/cred.enc +systemd-creds decrypt /tmp/cred.enc /tmp/cred.dec +diff /tmp/cred.orig /tmp/cred.dec +rm -f /tmp/cred.{enc,dec} +# --pretty +cred_name="fo'''o''bar" +cred_option="$(systemd-creds --pretty --name="$cred_name" encrypt /tmp/cred.orig -)" +mkdir -p /run/systemd/system +cat >/run/systemd/system/test-54-pretty-cred.service </tmp/ts54-concat +(cat /etc/passwd /etc/shadow && echo -n wuff) | cmp /tmp/ts54-concat +rm /tmp/ts54-concat + +# Test that SetCredential= acts as fallback for LoadCredential= +echo piff >/tmp/ts54-fallback +[ "$(systemd-run -p LoadCredential=paff:/tmp/ts54-fallback -p SetCredential=paff:poff --pipe --wait systemd-creds cat paff)" = "piff" ] +rm /tmp/ts54-fallback +[ "$(systemd-run -p LoadCredential=paff:/tmp/ts54-fallback -p SetCredential=paff:poff --pipe --wait systemd-creds cat paff)" = "poff" ] + +if systemd-detect-virt -q -c ; then + expected_credential=mynspawncredential + expected_value=strangevalue +elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then + # Verify that passing creds through kernel cmdline works + [ "$(systemd-creds --system cat kernelcmdlinecred)" = "uff" ] + [ "$(systemd-creds --system cat waldi)" = "woooofffwufffwuff" ] + + # And that it also works via SMBIOS + [ "$(systemd-creds --system cat smbioscredential)" = "magicdata" ] + [ "$(systemd-creds --system cat binarysmbioscredential)" = "magicbinarydata" ] + + # If we aren't run in nspawn, we are run in qemu + systemd-detect-virt -q -v + expected_credential=myqemucredential + expected_value=othervalue + + # Verify that writing a sysctl via the kernel cmdline worked + [ "$(cat /proc/sys/kernel/domainname)" = "sysctltest" ] + + # Verify that creating a user via sysusers via the kernel cmdline worked + grep -q ^credtestuser: /etc/passwd + + # Verify that writing a file via tmpfiles worked + [ "$(cat /tmp/sourcedfromcredential)" = "tmpfilessecret" ] + [ "$(cat /etc/motd.d/50-provision.conf)" = "hello" ] + [ "$(cat /etc/issue.d/50-provision.conf)" = "welcome" ] +else + echo "qemu_fw_cfg support missing in kernel. Sniff!" + expected_credential="" + expected_value="" +fi + +if [ "$expected_credential" != "" ] ; then + # If this test is run in nspawn a credential should have been passed to us. See test/TEST-54-CREDS/test.sh + [ "$(systemd-creds --system cat "$expected_credential")" = "$expected_value" ] + + # Test that propagation from system credential to service credential works + [ "$(systemd-run -p LoadCredential="$expected_credential" --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ] + + # Check it also works, if we rename it while propagating it + [ "$(systemd-run -p LoadCredential=miau:"$expected_credential" --pipe --wait systemd-creds cat miau)" = "$expected_value" ] + + # Combine it with a fallback (which should have no effect, given the cred should be passed down) + [ "$(systemd-run -p LoadCredential="$expected_credential" -p SetCredential="$expected_credential":zzz --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ] + + # This should succeed + systemd-run -p AssertCredential="$expected_credential" -p Type=oneshot true + + # And this should fail + (! systemd-run -p AssertCredential="undefinedcredential" -p Type=oneshot true) +fi + +# Verify that the creds are immutable +(! systemd-run -p LoadCredential=passwd:/etc/passwd \ + -p DynamicUser=1 \ + --unit=test-54-immutable-touch.service \ + --wait \ + touch '${CREDENTIALS_DIRECTORY}/passwd') +(! systemd-run -p LoadCredential=passwd:/etc/passwd \ + -p DynamicUser=1 \ + --unit=test-54-immutable-rm.service \ + --wait \ + rm '${CREDENTIALS_DIRECTORY}/passwd') + +# Check directory-based loading +mkdir -p /tmp/ts54-creds/sub +echo -n a >/tmp/ts54-creds/foo +echo -n b >/tmp/ts54-creds/bar +echo -n c >/tmp/ts54-creds/baz +echo -n d >/tmp/ts54-creds/sub/qux +systemd-run -p LoadCredential=cred:/tmp/ts54-creds \ + -p DynamicUser=1 \ + --unit=test-54-dir.service \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/cred_foo' \ + '${CREDENTIALS_DIRECTORY}/cred_bar' \ + '${CREDENTIALS_DIRECTORY}/cred_baz' \ + '${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat +cmp /tmp/ts54-concat <(echo -n abcd) +rm /tmp/ts54-concat +rm -rf /tmp/ts54-creds + +# Check that globs work as expected +mkdir -p /run/credstore +echo -n a >/run/credstore/test.creds.first +echo -n b >/run/credstore/test.creds.second +mkdir -p /etc/credstore +echo -n c >/etc/credstore/test.creds.third +systemd-run -p "ImportCredential=test.creds.*" \ + --unit=test-54-ImportCredential.service \ + -p DynamicUser=1 \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/test.creds.first' \ + '${CREDENTIALS_DIRECTORY}/test.creds.second' \ + '${CREDENTIALS_DIRECTORY}/test.creds.third' >/tmp/ts54-concat +cmp /tmp/ts54-concat <(echo -n abc) + +# Now test encrypted credentials (only supported when built with OpenSSL though) +if systemctl --version | grep -q -- +OPENSSL ; then + echo -n $RANDOM >/tmp/test-54-plaintext + systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext + systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext + + systemd-run -p LoadCredentialEncrypted=test-54:/tmp/test-54-ciphertext \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext + + echo -n $RANDOM >/tmp/test-54-plaintext + systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext + systemd-creds decrypt --name=test-54 /tmp/test-54-ciphertext | cmp /tmp/test-54-plaintext + + systemd-run -p SetCredentialEncrypted=test-54:"$(cat /tmp/test-54-ciphertext)" \ + --wait \ + --pipe \ + cat '${CREDENTIALS_DIRECTORY}/test-54' | cmp /tmp/test-54-plaintext + + rm /tmp/test-54-plaintext /tmp/test-54-ciphertext +fi + +# https://github.com/systemd/systemd/issues/27275 +systemd-run -p DynamicUser=yes -p 'LoadCredential=os:/etc/os-release' \ + -p 'ExecStartPre=true' \ + -p 'ExecStartPre=systemd-creds cat os' \ + --unit=test-54-exec-start.service \ + --wait \ + --pipe \ + true | cmp /etc/os-release + +if ! systemd-detect-virt -q -c ; then + # Validate that the credential we inserted via the initrd logic arrived + test "$(systemd-creds cat --system myinitrdcred)" = "guatemala" + + # Check that the fstab credential logic worked + test -d /injected + grep -q /injected /proc/self/mountinfo + + # Make sure the getty generator processed the credentials properly + systemctl -P Wants show getty.target | grep -q container-getty@idontexist.service +fi + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-55-testbloat.service b/test/units/testsuite-55-testbloat.service new file mode 100644 index 0000000..6c8e3c9 --- /dev/null +++ b/test/units/testsuite-55-testbloat.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Create a lot of memory pressure + +[Service] +# A VERY small memory.high will cause the 'stress' (trying to use a lot of memory) +# to throttle and be put under heavy pressure. +MemoryHigh=3M +Slice=testsuite-55-workload.slice +ExecStart=stress --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-stride 1 diff --git a/test/units/testsuite-55-testchill.service b/test/units/testsuite-55-testchill.service new file mode 100644 index 0000000..369b802 --- /dev/null +++ b/test/units/testsuite-55-testchill.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=No memory pressure + +[Service] +MemoryHigh=3M +Slice=testsuite-55-workload.slice +ExecStart=sleep infinity diff --git a/test/units/testsuite-55-testmunch.service b/test/units/testsuite-55-testmunch.service new file mode 100644 index 0000000..3730059 --- /dev/null +++ b/test/units/testsuite-55-testmunch.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Create some memory pressure + +[Service] +MemoryHigh=12M +Slice=testsuite-55-workload.slice +ExecStart=stress --timeout 3m --vm 10 --vm-bytes 200M --vm-keep --vm-stride 1 diff --git a/test/units/testsuite-55-workload.slice b/test/units/testsuite-55-workload.slice new file mode 100644 index 0000000..d117b75 --- /dev/null +++ b/test/units/testsuite-55-workload.slice @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Test slice for memory pressure kills + +[Slice] +CPUAccounting=true +MemoryAccounting=true +IOAccounting=true +TasksAccounting=true +ManagedOOMMemoryPressure=kill +ManagedOOMMemoryPressureLimit=20% diff --git a/test/units/testsuite-55.service b/test/units/testsuite-55.service new file mode 100644 index 0000000..00fb499 --- /dev/null +++ b/test/units/testsuite-55.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TESTSUITE-55-OOMD +After=user@4711.service +Wants=user@4711.service + +[Service] +ExecStartPre=rm -f /failed /skipped /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-55.sh b/test/units/testsuite-55.sh new file mode 100755 index 0000000..81617db --- /dev/null +++ b/test/units/testsuite-55.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh + . "$(dirname "$0")"/util.sh + +systemd-analyze log-level debug + +# Ensure that the init.scope.d drop-in is applied on boot +test "$(cat /sys/fs/cgroup/init.scope/memory.high)" != "max" + +# Loose checks to ensure the environment has the necessary features for systemd-oomd +[[ -e /proc/pressure ]] || echo "no PSI" >>/skipped +[[ "$(get_cgroup_hierarchy)" == "unified" ]] || echo "no cgroupsv2" >>/skipped +[[ -x /usr/lib/systemd/systemd-oomd ]] || echo "no oomd" >>/skipped +if [[ -s /skipped ]]; then + exit 0 +fi + +rm -rf /run/systemd/system/testsuite-55-testbloat.service.d + +# Activate swap file if we are in a VM +if systemd-detect-virt --vm --quiet; then + if [[ "$(findmnt -n -o FSTYPE /)" == btrfs ]]; then + btrfs filesystem mkswapfile -s 64M /swapfile + else + dd if=/dev/zero of=/swapfile bs=1M count=64 + chmod 0600 /swapfile + mkswap /swapfile + fi + + swapon /swapfile + swapon --show +fi + +# Configure oomd explicitly to avoid conflicts with distro dropins +mkdir -p /run/systemd/oomd.conf.d/ +cat >/run/systemd/oomd.conf.d/99-oomd-test.conf </run/systemd/system/-.slice.d/99-oomd-test.conf </run/systemd/system/user@.service.d/99-oomd-test.conf </run/systemd/system/systemd-oomd.service.d/debug.conf </run/systemd/system/testsuite-55-testchill.service.d/99-MemoryHigh.conf </run/systemd/system/testsuite-55-testbloat.service.d/override.conf </dev/null; then + echo "no systemd-repart" >/skipped + exit 0 +fi + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug +export PAGER=cat + +# Disable use of special glyphs such as → +export SYSTEMD_UTF8=0 + +seed=750b6cd5c4ae4012a15e7be3c29e6a47 + +if ! systemd-detect-virt --quiet --container; then + udevadm control --log-level debug +fi + +machine="$(uname -m)" +if [ "${machine}" = "x86_64" ]; then + root_guid=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709 + root_uuid=60F33797-1D71-4DCB-AA6F-20564F036CD0 + root_uuid2=73A4CCD2-EAF5-44DA-A366-F99188210FDC + usr_guid=8484680C-9521-48C6-9C11-B0720656F69E + usr_uuid=7E3369DD-D653-4513-ADF5-B993A9F20C16 + architecture="x86-64" +elif [ "${machine}" = "i386" ] || [ "${machine}" = "i686" ] || [ "${machine}" = "x86" ]; then + root_guid=44479540-F297-41B2-9AF7-D131D5F0458A + root_uuid=02B4253F-29A4-404E-8972-1669D3B03C87 + root_uuid2=268E0FD3-B468-4806-A823-E533FE9BB9CC + usr_guid=75250D76-8CC6-458E-BD66-BD47CC81A812 + usr_uuid=7B42FFB0-B0E1-4395-B20B-C78F4A571648 + architecture="x86" +elif [ "${machine}" = "aarch64" ] || [ "${machine}" = "aarch64_be" ] || [ "${machine}" = "armv8b" ] || [ "${machine}" = "armv8l" ]; then + root_guid=B921B045-1DF0-41C3-AF44-4C6F280D3FAE + root_uuid=055D0227-53A6-4033-85C3-9A5973EFF483 + root_uuid2=F7DBBE48-8FD0-4833-8411-AA34E7C8E60A + usr_guid=B0E01050-EE5F-4390-949A-9101B17104E9 + usr_uuid=FCE3C75E-D6A4-44C0-87F0-4C105183FB1F + architecture="arm64" +elif [ "${machine}" = "arm" ]; then + root_guid=69DAD710-2CE4-4E3C-B16C-21A1D49ABED3 + root_uuid=567DA89E-8DE2-4499-8D10-18F212DFF034 + root_uuid2=813ECFE5-4C89-4193-8A52-437493F2F96E + usr_guid=7D0359A3-02B3-4F0A-865C-654403E70625 + usr_uuid=71E93DC2-5073-42CB-8A84-A354E64D8966 + architecture="arm" +elif [ "${machine}" = "loongarch64" ]; then + root_guid=77055800-792C-4F94-B39A-98C91B762BB6 + root_uuid=D8EFC2D2-0133-41E4-BDCB-3B9F4CFDDDE8 + root_uuid2=36499F9E-0688-40C1-A746-EA8FD9543C56 + usr_guid=E611C702-575C-4CBE-9A46-434FA0BF7E3F + usr_uuid=031FFA75-00BB-49B6-A70D-911D2D82A5B7 + architecture="loongarch64" +elif [ "${machine}" = "ia64" ]; then + root_guid=993D8D3D-F80E-4225-855A-9DAF8ED7EA97 + root_uuid=DCF33449-0896-4EA9-BC24-7D58AEEF522D + root_uuid2=C2A6CAB7-ABEA-4FBA-8C48-CB4C52E6CA38 + usr_guid=4301D2A6-4E3B-4B2A-BB94-9E0B2C4225EA + usr_uuid=BC2BCCE7-80D6-449A-85CC-637424CE5241 + architecture="ia64" +elif [ "${machine}" = "s390x" ]; then + root_guid=5EEAD9A9-FE09-4A1E-A1D7-520D00531306 + root_uuid=7EBE0C85-E27E-48EC-B164-F4807606232E + root_uuid2=2A074E1C-2A19-4094-A0C2-24B1A5D52FCB + usr_guid=8A4F5770-50AA-4ED3-874A-99B710DB6FEA + usr_uuid=51171D30-35CF-4A49-B8B5-9478B9B796A5 + architecture="s390x" +elif [ "${machine}" = "ppc64le" ]; then + root_guid=C31C45E6-3F39-412E-80FB-4809C4980599 + root_uuid=061E67A1-092F-482F-8150-B525D50D6654 + root_uuid2=A6687CEF-4E4F-44E7-90B3-CDA52EA81739 + usr_guid=15BB03AF-77E7-4D4A-B12B-C0D084F7491C + usr_uuid=C0D0823B-8040-4C7C-A629-026248E297FB + architecture="ppc64-le" +else + echo "Unexpected uname -m: ${machine} in testsuite-58.sh, please fix me" + exit 1 +fi + +testcase_basic() { + local defs imgs output + local loop volume + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + echo "*** 1. create an empty image ***" + + systemd-repart --offline="$OFFLINE" \ + --empty=create \ + --size=1G \ + --seed="$seed" \ + "$imgs/zzz" + + output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') + + assert_eq "$output" "label: gpt +label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD +device: $imgs/zzz +unit: sectors +first-lba: 2048 +last-lba: 2097118" + + echo "*** 2. Testing with root, root2, home, and swap ***" + + tee "$defs/root.conf" <>"$defs/home.conf" + echo "UUID=b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" >>"$defs/home.conf" + + systemd-repart --offline="$OFFLINE" \ + --definitions="$defs" \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" + + output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') + + assert_eq "$output" "label: gpt +label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD +device: $imgs/zzz +unit: sectors +first-lba: 2048 +last-lba: 2097118 +$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\" +$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\" +$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\" +$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\" +$imgs/zzz5 : start= 1908696, size= 188416, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\"" + + echo "*** 4. Resizing to 2G ***" + + systemd-repart --offline="$OFFLINE" \ + --definitions="$defs" \ + --size=2G \ + --dry-run=no \ + --seed="$seed" \ + "$imgs/zzz" + + output=$(sfdisk -d "$imgs/zzz" | grep -v -e 'sector-size' -e '^$') + + assert_eq "$output" "label: gpt +label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD +device: $imgs/zzz +unit: sectors +first-lba: 2048 +last-lba: 4194270 +$imgs/zzz1 : start= 2048, size= 591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\" +$imgs/zzz2 : start= 593904, size= 591856, type=${root_guid}, uuid=${root_uuid}, name=\"root-${architecture}\", attrs=\"GUID:59\" +$imgs/zzz3 : start= 1185760, size= 591864, type=${root_guid}, uuid=${root_uuid2}, name=\"root-${architecture}-2\", attrs=\"GUID:59\" +$imgs/zzz4 : start= 1777624, size= 131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\" +$imgs/zzz5 : start= 1908696, size= 2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=A0A1A2A3-A4A5-A6A7-A8A9-AAABACADAEAF, name=\"custom_label\"" + + echo "*** 5. Testing with root, root2, home, swap, another partition, and partition copy ***" + + dd if=/dev/urandom of="$imgs/block-copy" bs=4096 count=10240 + + tee "$defs/extra2.conf" </dev/null + umount "$imgs/mount" +} + +testcase_dropin() { + local defs imgs output + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + tee "$defs/root.conf" < 32.0M", + "old_padding" : 0, + "raw_padding" : 0, + "padding" : "-> 0B", + "activity" : "create", + "drop-in_files" : [ + "$defs/root.conf.d/override1.conf", + "$defs/root.conf.d/override2.conf" + ] + } +] +EOF +} + +testcase_multiple_definitions() { + local defs imgs output + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + mkdir -p "$defs/1" + tee "$defs/1/root1.conf" < 32.0M", + "old_padding" : 0, + "raw_padding" : 0, + "padding" : "-> 0B", + "activity" : "create" + }, + { + "type" : "swap", + "label" : "label2", + "uuid" : "837c3d67-21b3-478e-be82-7e7f83bf96d3", + "partno" : 1, + "file" : "$defs/2/root2.conf", + "node" : "$imgs/zzz2", + "offset" : 34603008, + "old_size" : 0, + "raw_size" : 33554432, + "size" : "-> 32.0M", + "old_padding" : 0, + "raw_padding" : 0, + "padding" : "-> 0B", + "activity" : "create" + } +] +EOF +} + +testcase_copy_blocks() { + local defs imgs output + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + echo "*** First, create a disk image and verify its in order ***" + + tee "$defs/esp.conf" <"$defs/verity.openssl.cnf" </dev/null; then + continue + fi + + tee "$defs/root-$format.conf" </dev/null; then + tee "$defs/root-squashfs.conf" </dev/null; then + echo "Skipping free area calculation test without squashfs." + return + fi + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs'" RETURN + chmod 0755 "$defs" + + # https://github.com/systemd/systemd/issues/28225 + echo "*** free area calculation ***" + + tee "$defs/00-ESP.conf" <"$defs/10-part1.conf" + echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=1\n" >"$defs/11-dropped-first.conf" + echo -ne "[Partition]\nType=root\n" >"$defs/12-part2.conf" + echo -ne "[Partition]\nType=root\nSizeMinBytes=1T\nPriority=2\n" >"$defs/13-dropped-second.conf" + + systemd-repart --empty=allow --pretty=yes --dry-run=no --definitions="$defs" "$image" + + sfdisk -q -l "$image" + [[ "$(sfdisk -q -l "$image" | grep -c "$image")" -eq 2 ]] +} + +OFFLINE="yes" +run_testcases + +# Online image builds need loop devices so we can't run them in nspawn. +if ! systemd-detect-virt --container; then + OFFLINE="no" + run_testcases +fi + +# Valid block sizes on the Linux block layer are >= 512 and <= PAGE_SIZE, and +# must be powers of 2. Which leaves exactly four different ones to test on +# typical hardware +test_sector 512 +test_sector 1024 +test_sector 2048 +test_sector 4096 + +touch /testok diff --git a/test/units/testsuite-59.service b/test/units/testsuite-59.service new file mode 100644 index 0000000..f85cfab --- /dev/null +++ b/test/units/testsuite-59.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-59-RELOADING-RESTART + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh diff --git a/test/units/testsuite-59.sh b/test/units/testsuite-59.sh new file mode 100755 index 0000000..1b622b3 --- /dev/null +++ b/test/units/testsuite-59.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +fail() { + systemd-analyze log-level info + exit 1 +} + +# Wait for a service to enter a state within a timeout period, if it doesn't +# enter the desired state within the timeout period then this function will +# exit the test case with a non zero exit code. +wait_on_state_or_fail() { + service=$1 + expected_state=$2 + timeout=$3 + + state=$(systemctl show "$service" --property=ActiveState --value) + while [ "$state" != "$expected_state" ]; do + if [ "$timeout" = "0" ]; then + fail + fi + timeout=$((timeout - 1)) + sleep 1 + state=$(systemctl show "$service" --property=ActiveState --value) + done +} + +systemd-analyze log-level debug + + +cat >/run/systemd/system/testservice-fail-59.service </run/systemd/system/testservice-fail-restart-59.service </run/systemd/system/testservice-abort-restart-59.service </run/systemd/system.conf.d/50-test-59-reload.conf </run/notify-reload-test.sh </run/systemd/system/tmp-deptest.mount </run/systemd/system/"$unit" <&2 "Test mount \"$unit\" unit isn't mounted" + return 1 + } + mountpoint -q "$tmpdir" + + trap 'systemctl stop $unit' RETURN + + # Trigger the mount ratelimiting + cd "$(mktemp -d)" + mkdir foo + for _ in {1..50}; do + mount --bind foo foo + umount foo + done + + # Unmount the test mount and start it immediately again via systemd + umount "$tmpdir" + systemctl start "$unit" + + # Make sure it is seen as mounted by systemd and it actually is mounted + [[ "$(systemctl show --property SubState --value "$unit")" = "mounted" ]] || { + echo >&2 "Test mount \"$unit\" unit isn't in \"mounted\" state" + return 1 + } + + mountpoint -q "$tmpdir" || { + echo >&2 "Test mount \"$unit\" is in \"mounted\" state, actually is not mounted" + return 1 + } +} + +test_issue_23796() { + local mount_path mount_mytmpfs + + mount_path="$(command -v mount 2>/dev/null)" + mount_mytmpfs="${mount_path/\/bin/\/sbin}.mytmpfs" + cat >"$mount_mytmpfs" </run/systemd/system/tmp-hoge.mount <>/skipped + exit 0 +fi + +if systemctl --version | grep -q -F -- "-BPF_FRAMEWORK"; then + echo "bpf-framework is disabled" >>/skipped + exit 0 +fi + +trap teardown EXIT +setup + +systemctl start --wait testsuite-62-1.service +systemctl start --wait testsuite-62-2.service +systemctl start --wait testsuite-62-3.service +systemctl start --wait testsuite-62-4.service +systemctl start --wait testsuite-62-5.service + +touch /testok diff --git a/test/units/testsuite-63.service b/test/units/testsuite-63.service new file mode 100644 index 0000000..483c6a8 --- /dev/null +++ b/test/units/testsuite-63.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-63-PATH + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-63.sh b/test/units/testsuite-63.sh new file mode 100755 index 0000000..ea8cd94 --- /dev/null +++ b/test/units/testsuite-63.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +systemctl log-level debug + +# Test that a path unit continuously triggering a service that fails condition checks eventually fails with +# the trigger-limit-hit error. +rm -f /tmp/nonexistent +systemctl start test63.path +touch /tmp/test63 + +# Make sure systemd has sufficient time to hit the trigger limit for test63.path. +# shellcheck disable=SC2016 +timeout 30 bash -c 'until test "$(systemctl show test63.path -P ActiveState)" = failed; do sleep .2; done' +test "$(systemctl show test63.service -P ActiveState)" = inactive +test "$(systemctl show test63.service -P Result)" = success +test "$(systemctl show test63.path -P Result)" = trigger-limit-hit + +# Test that starting the service manually doesn't affect the path unit. +rm -f /tmp/test63 +systemctl reset-failed +systemctl start test63.path +systemctl start test63.service +test "$(systemctl show test63.service -P ActiveState)" = inactive +test "$(systemctl show test63.service -P Result)" = success +test "$(systemctl show test63.path -P ActiveState)" = active +test "$(systemctl show test63.path -P Result)" = success + +# Test that glob matching works too, with $TRIGGER_PATH +systemctl start test63-glob.path +touch /tmp/test63-glob-foo +timeout 60 bash -c 'until systemctl -q is-active test63-glob.service; do sleep .2; done' +test "$(systemctl show test63-glob.service -P ActiveState)" = active +test "$(systemctl show test63-glob.service -P Result)" = success + +test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[["trigger_unit","test63-glob.path"],["trigger_path","/tmp/test63-glob-foo"]]}' + +systemctl stop test63-glob.path test63-glob.service + +test "$(busctl --json=short get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test63_2dglob_2eservice org.freedesktop.systemd1.Unit ActivationDetails)" = '{"type":"a(ss)","data":[]}' + +# tests for issue https://github.com/systemd/systemd/issues/24577#issuecomment-1522628906 +rm -f /tmp/hoge +systemctl start test63-issue-24577.path +systemctl status -n 0 test63-issue-24577.path +systemctl status -n 0 test63-issue-24577.service || : +systemctl list-jobs +output=$(systemctl list-jobs --no-legend) +assert_not_in "test63-issue-24577.service" "$output" +assert_not_in "test63-issue-24577-dep.service" "$output" + +touch /tmp/hoge +systemctl status -n 0 test63-issue-24577.path +systemctl status -n 0 test63-issue-24577.service || : +systemctl list-jobs +output=$(systemctl list-jobs --no-legend) +assert_in "test63-issue-24577.service" "$output" +assert_in "test63-issue-24577-dep.service" "$output" + +# even if the service is stopped, it will be soon retriggered. +systemctl stop test63-issue-24577.service +systemctl status -n 0 test63-issue-24577.path +systemctl status -n 0 test63-issue-24577.service || : +systemctl list-jobs +output=$(systemctl list-jobs --no-legend) +assert_in "test63-issue-24577.service" "$output" +assert_in "test63-issue-24577-dep.service" "$output" + +rm -f /tmp/hoge +systemctl stop test63-issue-24577.service +systemctl status -n 0 test63-issue-24577.path +systemctl status -n 0 test63-issue-24577.service || : +systemctl list-jobs +output=$(systemctl list-jobs --no-legend) +assert_not_in "test63-issue-24577.service" "$output" +assert_in "test63-issue-24577-dep.service" "$output" + +# Test for race condition fixed by https://github.com/systemd/systemd/pull/30768 +# Here's the schedule of events that we to happen during this test: +# (This test) (The service) +# .path unit monitors /tmp/copyme for changes +# Take lock on /tmp/noexeit ↓ +# Write to /tmp/copyme ↓ +# Wait for deactivating Started +# ↓ Copies /tmp/copyme to /tmp/copied +# ↓ Tells manager it's shutting down +# Ensure service did the copy Tries to lock /tmp/noexit and blocks +# Write to /tmp/copyme ↓ +# +# Now at this point the test can diverge. If we regress, this second write is +# missed and we'll see: +# ... (second write) ... (blocked) +# Drop lock on /tmp/noexit ↓ +# Wait for service to do copy Unblocks and exits +# ↓ (dead) +# ↓ +# (timeout) +# Test fails +# +# Otherwise, we'll see: +# ... (second write) ... (blocked) +# Drop lock on /tmp/noexit ↓ and .path unit queues a new start job +# Wait for service to do copy Unblocks and exits +# ↓ Starts again b/c of queued job +# ↓ Copies again +# Test Passes +systemctl start test63-pr-30768.path +exec {lock}<>/tmp/noexit +flock -e $lock +echo test1 > /tmp/copyme +# shellcheck disable=SC2016 +timeout 30 bash -c 'until test "$(systemctl show test63-pr-30768.service -P ActiveState)" = deactivating; do sleep .2; done' +diff /tmp/copyme /tmp/copied +echo test2 > /tmp/copyme +exec {lock}<&- +timeout 30 bash -c 'until diff /tmp/copyme /tmp/copied; do sleep .2; done' + +systemctl log-level info + +touch /testok diff --git a/test/units/testsuite-64.service b/test/units/testsuite-64.service new file mode 100644 index 0000000..f75a3d7 --- /dev/null +++ b/test/units/testsuite-64.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-64-UDEV + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-64.sh b/test/units/testsuite-64.sh new file mode 100755 index 0000000..65e5f6c --- /dev/null +++ b/test/units/testsuite-64.sh @@ -0,0 +1,1192 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# vi: ts=4 sw=4 tw=0 et: + +set -eux +set -o pipefail + +# Check if all symlinks under /dev/disk/ are valid +# shellcheck disable=SC2120 +helper_check_device_symlinks() {( + set +x + + local dev link path paths target + + [[ $# -gt 0 ]] && paths=("$@") || paths=("/dev/disk" "/dev/mapper") + + # Check if all given paths are valid + for path in "${paths[@]}"; do + if ! test -e "$path"; then + echo >&2 "Path '$path' doesn't exist" + return 1 + fi + done + + while read -r link; do + target="$(readlink -f "$link")" + # Both checks should do virtually the same thing, but check both to be + # on the safe side + if [[ ! -e "$link" || ! -e "$target" ]]; then + echo >&2 "ERROR: symlink '$link' points to '$target' which doesn't exist" + return 1 + fi + + # Check if the symlink points to the correct device in /dev + dev="/dev/$(udevadm info -q name "$link")" + if [[ "$target" != "$dev" ]]; then + echo >&2 "ERROR: symlink '$link' points to '$target' but '$dev' was expected" + return 1 + fi + done < <(find "${paths[@]}" -type l) +)} + +helper_check_udev_watch() {( + set +x + + local link target id dev + + while read -r link; do + target="$(readlink "$link")" + if [[ ! -L "/run/udev/watch/$target" ]]; then + echo >&2 "ERROR: symlink /run/udev/watch/$target does not exist" + return 1 + fi + if [[ "$(readlink "/run/udev/watch/$target")" != "$(basename "$link")" ]]; then + echo >&2 "ERROR: symlink target of /run/udev/watch/$target is inconsistent with $link" + return 1 + fi + + if [[ "$target" =~ ^[0-9]+$ ]]; then + # $link is ID -> wd + id="$(basename "$link")" + else + # $link is wd -> ID + id="$target" + fi + + if [[ "${id:0:1}" == "b" ]]; then + dev="/dev/block/${id:1}" + elif [[ "${id:0:1}" == "c" ]]; then + dev="/dev/char/${id:1}" + else + echo >&2 "ERROR: unexpected device ID '$id'" + return 1 + fi + + if [[ ! -e "$dev" ]]; then + echo >&2 "ERROR: device '$dev' corresponding to symlink '$link' does not exist" + return 1 + fi + done < <(find /run/udev/watch -type l) +)} + +check_device_unit() {( + set +x + + local log_level link links path syspath unit + + log_level="${1?}" + path="${2?}" + unit=$(systemd-escape --path --suffix=device "$path") + + [[ "$log_level" == 1 ]] && echo "INFO: check_device_unit($unit)" + + syspath=$(systemctl show --value --property SysFSPath "$unit" 2>/dev/null) + if [[ -z "$syspath" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit not found." + return 1 + fi + + if [[ ! -L "$path" ]]; then + if [[ ! -d "$syspath" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not exist." + return 1 + fi + return 0 + fi + + if [[ ! -b "$path" && ! -c "$path" ]]; then + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: invalid file type $path" + return 1 + fi + + read -r -a links < <(udevadm info -q symlink "$syspath" 2>/dev/null) + for link in "${links[@]}"; do + if [[ "/dev/$link" == "$path" ]]; then # DEVLINKS= given by -q symlink are relative to /dev + return 0 + fi + done + + read -r -a links < <(udevadm info "$syspath" | sed -ne '/SYSTEMD_ALIAS=/ { s/^E: SYSTEMD_ALIAS=//; p }' 2>/dev/null) + for link in "${links[@]}"; do + if [[ "$link" == "$path" ]]; then # SYSTEMD_ALIAS= are absolute + return 0 + fi + done + + [[ "$log_level" == 1 ]] && echo >&2 "ERROR: $unit exists for $syspath but it does not have the corresponding DEVLINKS or SYSTEMD_ALIAS." + return 1 +)} + +check_device_units() {( + set +x + + local log_level path paths + + log_level="${1?}" + shift + paths=("$@") + + for path in "${paths[@]}"; do + if ! check_device_unit "$log_level" "$path"; then + return 1 + fi + done + + while read -r unit _; do + path=$(systemd-escape --path --unescape "$unit") + if ! check_device_unit "$log_level" "$path"; then + return 1 + fi + done < <(systemctl list-units --all --type=device --no-legend dev-* | awk '$1 !~ /dev-tty.+/ { print $1 }' | sed -e 's/\.device$//') + + return 0 +)} + +helper_check_device_units() {( + set +x + + local i + + for i in {1..20}; do + (( i > 1 )) && sleep 0.5 + if check_device_units 0 "$@"; then + return 0 + fi + done + + check_device_units 1 "$@" +)} + +testcase_megasas2_basic() { + lsblk -S + [[ "$(lsblk --scsi --noheadings | wc -l)" -ge 128 ]] +} + +testcase_nvme_basic() { + local expected_symlinks=() + local i + + for (( i = 0; i < 5; i++ )); do + expected_symlinks+=( + # both replace mode provides the same devlink + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef"$i"_1 + ) + done + for (( i = 5; i < 10; i++ )); do + expected_symlinks+=( + # old replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl__deadbeef_"$i" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____deadbeef__"$i"_1 + ) + done + for (( i = 10; i < 15; i++ )); do + expected_symlinks+=( + # old replace mode does not provide devlink, as serial contains "/" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_____dead_beef_"$i"_1 + ) + done + for (( i = 15; i < 20; i++ )); do + expected_symlinks+=( + # old replace mode does not provide devlink, as serial contains "/" + # newer replace mode + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i" + # with nsid + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_dead_.._.._beef_"$i"_1 + ) + done + + udevadm settle + ls /dev/disk/by-id + for i in "${expected_symlinks[@]}"; do + udevadm wait --settle --timeout=30 "$i" + done + + lsblk --noheadings | grep "^nvme" + [[ "$(lsblk --noheadings | grep -c "^nvme")" -ge 20 ]] +} + +testcase_nvme_subsystem() { + local expected_symlinks=( + # Controller(s) + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16 + /dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17 + # Shared namespaces + /dev/disk/by-path/pci-*-nvme-16 + /dev/disk/by-path/pci-*-nvme-17 + ) + + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" +} + +testcase_virtio_scsi_identically_named_partitions() { + local num + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num=$((4 * 4)) + else + num=$((16 * 8)) + fi + + lsblk --noheadings -a -o NAME,PARTLABEL + [[ "$(lsblk --noheadings -a -o NAME,PARTLABEL | grep -c "Hello world")" -eq "$num" ]] +} + +testcase_multipath_basic_failover() { + local dmpath i path wwid + + # Configure multipath + cat >/etc/multipath.conf <<\EOF +defaults { + # Use /dev/mapper/$WWN paths instead of /dev/mapper/mpathX + user_friendly_names no + find_multipaths yes + enable_foreign "^$" +} + +blacklist_exceptions { + property "(SCSI_IDENT_|ID_WWN)" +} + +blacklist { +} +EOF + modprobe -v dm_multipath + systemctl start multipathd.service + systemctl status multipathd.service + multipath -ll + udevadm settle + ls -l /dev/disk/by-id/ + + for i in {0..15}; do + wwid="deaddeadbeef$(printf "%.4d" "$i")" + path="/dev/disk/by-id/wwn-0x$wwid" + dmpath="$(readlink -f "$path")" + + lsblk "$path" + multipath -C "$dmpath" + # We should have 4 active paths for each multipath device + [[ "$(multipath -l "$path" | grep -c running)" -eq 4 ]] + done + + # Test failover (with the first multipath device that has a partitioned disk) + echo "${FUNCNAME[0]}: test failover" + local device expected link mpoint part + local -a devices + mkdir -p /mnt + mpoint="$(mktemp -d /mnt/mpathXXX)" + wwid="deaddeadbeef0000" + path="/dev/disk/by-id/wwn-0x$wwid" + + # All following symlinks should exists and should be valid + local -a part_links=( + "/dev/disk/by-id/wwn-0x$wwid-part2" + "/dev/disk/by-partlabel/failover_part" + "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000" + "/dev/disk/by-label/failover_vol" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111" + ) + udevadm wait --settle --timeout=30 "${part_links[@]}" + helper_check_device_units "${part_links[@]}" + + # Choose a random symlink to the failover data partition each time, for + # a better coverage + part="${part_links[$RANDOM % ${#part_links[@]}]}" + + # Get all devices attached to a specific multipath device (in H:C:T:L format) + # and sort them in a random order, so we cut off different paths each time + mapfile -t devices < <(multipath -l "$path" | grep -Eo '[0-9]+:[0-9]+:[0-9]+:[0-9]+' | sort -R) + if [[ "${#devices[@]}" -ne 4 ]]; then + echo "Expected 4 devices attached to WWID=$wwid, got ${#devices[@]} instead" + return 1 + fi + # Drop the last path from the array, since we want to leave at least one path active + unset "devices[3]" + # Mount the first multipath partition, write some data we can check later, + # and then disconnect the remaining paths one by one while checking if we + # can still read/write from the mount + mount -t ext4 "$part" "$mpoint" + expected=0 + echo -n "$expected" >"$mpoint/test" + # Sanity check we actually wrote what we wanted + [[ "$(<"$mpoint/test")" == "$expected" ]] + + for device in "${devices[@]}"; do + echo offline >"/sys/class/scsi_device/$device/device/state" + [[ "$(<"$mpoint/test")" == "$expected" ]] + expected="$((expected + 1))" + echo -n "$expected" >"$mpoint/test" + + # Make sure all symlinks are still valid + udevadm wait --settle --timeout=30 "${part_links[@]}" + helper_check_device_units "${part_links[@]}" + done + + multipath -l "$path" + # Three paths should be now marked as 'offline' and one as 'running' + [[ "$(multipath -l "$path" | grep -c offline)" -eq 3 ]] + [[ "$(multipath -l "$path" | grep -c running)" -eq 1 ]] + + umount "$mpoint" + rm -fr "$mpoint" +} + +testcase_simultaneous_events_1() { + local disk expected i iterations key link num_part part partscript rule target timeout + local -a devices symlinks + local -A running + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num_part=2 + iterations=10 + timeout=240 + else + num_part=10 + iterations=100 + timeout=30 + fi + + for disk in {0..9}; do + link="/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_deadbeeftest${disk}" + target="$(readlink -f "$link")" + if [[ ! -b "$target" ]]; then + echo "ERROR: failed to find the test SCSI block device $link" + return 1 + fi + + devices+=("$target") + done + + for ((part = 1; part <= num_part; part++)); do + symlinks+=( + "/dev/disk/by-partlabel/test${part}" + ) + done + + partscript="$(mktemp)" + + cat >"$partscript" <"$rule" <&2 "ERROR: symlink '/dev/disk/by-partlabel/test${part}' points to '$target' but '$expected' was expected" + return 1 + fi + done + fi + done + + helper_check_device_units + rm -f "$rule" "$partscript" + + udevadm control --reload +} + +testcase_simultaneous_events_2() { + local disk expected i iterations key link num_part part script_dir target timeout + local -a devices symlinks + local -A running + + script_dir="$(mktemp --directory "/tmp/test-udev-storage.script.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$script_dir'" RETURN + + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + num_part=20 + iterations=1 + timeout=2400 + else + num_part=100 + iterations=3 + timeout=300 + fi + + for disk in {0..9}; do + link="/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_deadbeeftest${disk}" + target="$(readlink -f "$link")" + if [[ ! -b "$target" ]]; then + echo "ERROR: failed to find the test SCSI block device $link" + return 1 + fi + + devices+=("$target") + done + + for ((i = 1; i <= iterations; i++)); do + cat >"$script_dir/partscript-$i" <>/etc/fstab + systemctl daemon-reload + mount "/tmp/lvmluksmnt" + mountpoint "/tmp/lvmluksmnt" + # Temporarily suspend the LUKS device and trigger udev - basically what `cryptsetup resize` + # does but in a more deterministic way suitable for a test/reproducer + for _ in {0..5}; do + dmsetup suspend "/dev/mapper/lvmluksmap" + udevadm trigger -v --settle "/dev/mapper/lvmluksmap" + dmsetup resume "/dev/mapper/lvmluksmap" + # The mount should survive this sequence of events + mountpoint "/tmp/lvmluksmnt" + done + # Cleanup + umount "/tmp/lvmluksmnt" + cryptsetup close "/dev/mapper/lvmluksmap" + sed -i "/lvmluksfs/d" "/etc/fstab" + systemctl daemon-reload + + # Disable the VG and check symlinks... + lvm vgchange -an "$vgroup" + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" + helper_check_device_units + + # reenable the VG and check the symlinks again if all LVs are properly activated + lvm vgchange -ay "$vgroup" + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Same as above, but now with more "stress" + if [[ -v ASAN_OPTIONS || "$(systemd-detect-virt -v)" == "qemu" ]]; then + iterations=10 + else + iterations=50 + fi + + for ((i = 1; i <= iterations; i++)); do + lvm vgchange -an "$vgroup" + lvm vgchange -ay "$vgroup" + + if ((i % 5 == 0)); then + udevadm wait --settle --timeout="$timeout" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + fi + done + + # Remove the first LV + lvm lvremove -y "$vgroup/mypart1" + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/mypart1" + udevadm wait --timeout=0 "/dev/$vgroup/mypart2" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + + # Create & remove LVs in a loop, i.e. with more "stress" + if [[ -v ASAN_OPTIONS ]]; then + iterations=8 + partitions=16 + elif [[ "$(systemd-detect-virt -v)" == "qemu" ]]; then + iterations=8 + partitions=8 + else + iterations=16 + partitions=16 + fi + + for ((i = 1; i <= iterations; i++)); do + # 1) Create some logical volumes + for ((part = 0; part < partitions; part++)); do + lvm lvcreate -y -L 4M "$vgroup" -n "looppart$part" + done + + # 2) Immediately remove them + lvm lvremove -y $(seq -f "$vgroup/looppart%g" 0 "$((partitions - 1))") + + # 3) On every 4th iteration settle udev and check if all partitions are + # indeed gone, and if all symlinks are still valid + if ((i % 4 == 0)); then + for ((part = 0; part < partitions; part++)); do + udevadm wait --settle --timeout="$timeout" --removed "/dev/$vgroup/looppart$part" + done + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + fi + done +} + +testcase_btrfs_basic() { + local dev_stub i label mpoint uuid + local devices=( + /dev/disk/by-id/ata-foobar_deadbeefbtrfs{0..3} + ) + + ls -l "${devices[@]}" + + echo "Single device: default settings" + uuid="deadbeef-dead-dead-beef-000000000000" + label="btrfs_root" + udevadm lock --device="${devices[0]}" mkfs.btrfs -f -L "$label" -U "$uuid" "${devices[0]}" + udevadm wait --settle --timeout=30 "${devices[0]}" "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + + echo "Multiple devices: using partitions, data: single, metadata: raid1" + uuid="deadbeef-dead-dead-beef-000000000001" + label="btrfs_mpart" + udevadm lock --device="${devices[0]}" sfdisk --wipe=always "${devices[0]}" </etc/crypttab + for ((i = 0; i < ${#devices[@]}; i++)); do + # Intentionally use weaker cipher-related settings, since we don't care + # about security here as it's a throwaway LUKS partition + cryptsetup luksFormat -q \ + --use-urandom --pbkdf pbkdf2 --pbkdf-force-iterations 1000 \ + --uuid "deadbeef-dead-dead-beef-11111111111$i" --label "encdisk$i" "${devices[$i]}" /etc/btrfs_keyfile + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/deadbeef-dead-dead-beef-11111111111$i" "/dev/disk/by-label/encdisk$i" + # Add the device into /etc/crypttab, reload systemd, and then activate + # the device so we can create a filesystem on it later + echo "encbtrfs$i UUID=deadbeef-dead-dead-beef-11111111111$i /etc/btrfs_keyfile luks" >>/etc/crypttab + systemctl daemon-reload + systemctl start "systemd-cryptsetup@encbtrfs$i" + done + helper_check_device_symlinks + helper_check_device_units + # Check if we have all necessary DM devices + ls -l /dev/mapper/encbtrfs{0..3} + # Create a multi-device btrfs filesystem on the LUKS devices + udevadm lock \ + --device=/dev/mapper/encbtrfs0 \ + --device=/dev/mapper/encbtrfs1 \ + --device=/dev/mapper/encbtrfs2 \ + --device=/dev/mapper/encbtrfs3 \ + mkfs.btrfs -f -M -d raid1 -m raid1 -L "$label" -U "$uuid" /dev/mapper/encbtrfs{0..3} + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + # Mount it and write some data to it we can compare later + mount -t btrfs /dev/mapper/encbtrfs0 "$mpoint" + echo "hello there" >"$mpoint/test" + # "Deconstruct" the btrfs device and check if we're in a sane state (symlink-wise) + umount "$mpoint" + systemctl stop systemd-cryptsetup@encbtrfs{0..3} + udevadm wait --settle --timeout=30 --removed "/dev/disk/by-uuid/$uuid" + helper_check_device_symlinks + helper_check_device_units + # Add the mount point to /etc/fstab and check if the device can be put together + # automagically. The source device is the DM name of the first LUKS device + # (from /etc/crypttab). We have to specify all LUKS devices manually, as + # registering the necessary devices is usually initrd's job (via btrfs device scan) + dev_stub="/dev/mapper/encbtrfs" + echo "/dev/mapper/encbtrfs0 $mpoint btrfs device=${dev_stub}0,device=${dev_stub}1,device=${dev_stub}2,device=${dev_stub}3 0 2" >>/etc/fstab + # Tell systemd about the new mount + systemctl daemon-reload + # Restart cryptsetup.target to trigger autounlock of partitions in /etc/crypttab + systemctl restart cryptsetup.target + # Start the corresponding mount unit and check if the btrfs device was reconstructed + # correctly + systemctl start "${mpoint##*/}.mount" + udevadm wait --settle --timeout=30 "/dev/disk/by-uuid/$uuid" "/dev/disk/by-label/$label" + btrfs filesystem show + helper_check_device_symlinks + helper_check_device_units + grep "hello there" "$mpoint/test" + # Cleanup + systemctl stop "${mpoint##*/}.mount" + systemctl stop systemd-cryptsetup@encbtrfs{0..3} + sed -i "/${mpoint##*/}/d" /etc/fstab + : >/etc/crypttab + rm -fr "$mpoint" + systemctl daemon-reload + udevadm settle +} + +testcase_iscsi_lvm() { + local dev i label link lun_id mpoint target_name uuid + local target_ip="127.0.0.1" + local target_port="3260" + local vgroup="iscsi_lvm$RANDOM" + local expected_symlinks=() + local devices=( + /dev/disk/by-id/ata-foobar_deadbeefiscsi{0..3} + ) + + ls -l "${devices[@]}" + + # Start the target daemon + systemctl start tgtd + systemctl status tgtd + + echo "iSCSI LUNs backed by devices" + # See RFC3721 and RFC7143 + target_name="iqn.2021-09.com.example:iscsi.test" + # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each + # backed by a device + tgtadm --lld iscsi --op new --mode target --tid=1 --targetname "$target_name" + for ((i = 0; i < ${#devices[@]}; i++)); do + # lun-0 is reserved by iSCSI + lun_id="$((i + 1))" + tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun "$lun_id" -b "${devices[$i]}" + tgtadm --lld iscsi --op update --mode logicalunit --tid 1 --lun "$lun_id" + expected_symlinks+=( + "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$lun_id" + ) + done + tgtadm --lld iscsi --op bind --mode target --tid 1 -I ALL + # Configure the iSCSI initiator + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Cleanup + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + tgtadm --lld iscsi --op delete --mode target --tid=1 + + echo "iSCSI LUNs backed by files + LVM" + # Note: we use files here to "trick" LVM the disks are indeed on a different + # host, so it doesn't automagically detect another path to the backing + # device once we disconnect the iSCSI devices + target_name="iqn.2021-09.com.example:iscsi.lvm.test" + mpoint="$(mktemp -d /iscsi_storeXXX)" + expected_symlinks=() + # Use the first device as it's configured with larger capacity + mkfs.ext4 -L iscsi_store "${devices[0]}" + udevadm wait --settle --timeout=30 "${devices[0]}" + mount "${devices[0]}" "$mpoint" + for i in {1..4}; do + dd if=/dev/zero of="$mpoint/lun$i.img" bs=1M count=32 + done + # Initialize a new iSCSI target <$target_name> consisting of 4 LUNs, each + # backed by a file + tgtadm --lld iscsi --op new --mode target --tid=2 --targetname "$target_name" + # lun-0 is reserved by iSCSI + for i in {1..4}; do + tgtadm --lld iscsi --op new --mode logicalunit --tid 2 --lun "$i" -b "$mpoint/lun$i.img" + tgtadm --lld iscsi --op update --mode logicalunit --tid 2 --lun "$i" + expected_symlinks+=( + "/dev/disk/by-path/ip-$target_ip:$target_port-iscsi-$target_name-lun-$i" + ) + done + tgtadm --lld iscsi --op bind --mode target --tid 2 -I ALL + # Configure the iSCSI initiator + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + helper_check_device_symlinks + helper_check_device_units + # Add all iSCSI devices into a LVM volume group, create two logical volumes, + # and check if necessary symlinks exist (and are valid) + lvm pvcreate -y "${expected_symlinks[@]}" + lvm pvs + lvm vgcreate "$vgroup" -y "${expected_symlinks[@]}" + lvm vgs + lvm vgchange -ay "$vgroup" + lvm lvcreate -y -L 4M "$vgroup" -n mypart1 + lvm lvcreate -y -L 8M "$vgroup" -n mypart2 + lvm lvs + udevadm wait --settle --timeout=30 "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" + mkfs.ext4 -L mylvpart1 "/dev/$vgroup/mypart1" + udevadm wait --settle --timeout=30 "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + # Disconnect the iSCSI devices and check all the symlinks + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + # "Reset" the DM state, since we yanked the backing storage from under the LVM, + # so the currently active VGs/LVs are invalid + dmsetup remove_all --deferred + # The LVM and iSCSI related symlinks should be gone + udevadm wait --settle --timeout=30 --removed "/dev/$vgroup" "/dev/disk/by-label/mylvpart1" "${expected_symlinks[@]}" + helper_check_device_symlinks "/dev/disk" + helper_check_device_units + # Reconnect the iSCSI devices and check if everything get detected correctly + iscsiadm --mode discoverydb --type sendtargets --portal "$target_ip" --discover + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --login + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" "/dev/$vgroup/mypart1" "/dev/$vgroup/mypart2" "/dev/disk/by-label/mylvpart1" + helper_check_device_symlinks "/dev/disk" "/dev/$vgroup" + helper_check_device_units + # Cleanup + iscsiadm --mode node --targetname "$target_name" --portal "$target_ip:$target_port" --logout + tgtadm --lld iscsi --op delete --mode target --tid=2 + umount "$mpoint" + rm -rf "$mpoint" +} + +testcase_long_sysfs_path() { + local cursor link logfile mpoint + local expected_symlinks=( + "/dev/disk/by-label/data_vol" + "/dev/disk/by-label/swap_vol" + "/dev/disk/by-partlabel/test_swap" + "/dev/disk/by-partlabel/test_part" + "/dev/disk/by-partuuid/deadbeef-dead-dead-beef-000000000000" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-111111111111" + "/dev/disk/by-uuid/deadbeef-dead-dead-beef-222222222222" + ) + + # Create a cursor file to skip messages generated by udevd in initrd, as it + # might not be the same up-to-date version as we currently run (hence generating + # messages we check for later and making the test fail) + cursor="$(mktemp)" + journalctl --cursor-file="${cursor:?}" -n0 -q + + # Make sure the test device is connected and show its "wonderful" path + stat /sys/block/vda + readlink -f /sys/block/vda/dev + + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + + # Try to mount the data partition manually (using its label) + mpoint="$(mktemp -d /logsysfsXXX)" + mount LABEL=data_vol "$mpoint" + touch "$mpoint/test" + umount "$mpoint" + # Do the same, but with UUID and using fstab + echo "UUID=deadbeef-dead-dead-beef-222222222222 $mpoint ext4 defaults 0 0" >>/etc/fstab + systemctl daemon-reload + mount "$mpoint" + timeout 30 bash -c "until systemctl -q is-active '$mpoint'; do sleep .2; done" + test -e "$mpoint/test" + umount "$mpoint" + + # Test out the swap partition + swapon -v -L swap_vol + swapoff -v -L swap_vol + + udevadm settle + + logfile="$(mktemp)" + # Check state of affairs after https://github.com/systemd/systemd/pull/22759 + # Note: can't use `--cursor-file` here, since we don't want to update the cursor + # after using it + [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic -p info --grep "Device path.*vda.?' too long to fit into unit name" | wc -l)" -eq 0 ]] + [[ "$(journalctl --after-cursor="$(<"$cursor")" -q --no-pager -o short-monotonic --grep "Unit name .*vda.?\.device\" too long, falling back to hashed unit name" | wc -l)" -gt 0 ]] + # Check if the respective "hashed" units exist and are active (plugged) + systemctl status --no-pager "$(readlink -f /sys/block/vda/vda1)" + systemctl status --no-pager "$(readlink -f /sys/block/vda/vda2)" + # Make sure we don't unnecessarily spam the log + { journalctl -b -q --no-pager -o short-monotonic -p info --grep "/sys/devices/.+/vda[0-9]?" _PID=1 + UNIT=systemd-udevd.service || :;} | tee "$logfile" + [[ "$(wc -l <"$logfile")" -lt 10 ]] + + : >/etc/fstab + rm -fr "${cursor:?}" "${logfile:?}" "${mpoint:?}" +} + +testcase_mdadm_basic() { + local i part_name raid_name raid_dev uuid + local expected_symlinks=() + local devices=( + /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..4} + ) + + ls -l "${devices[@]}" + + echo "Mirror raid (RAID 1)" + raid_name="mdmirror" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000001" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + ) + # Create a simple RAID 1 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..1} -v -f --level=1 --raid-devices=2 + udevadm wait --settle --timeout=30 "$raid_dev" + mkfs.ext4 -L "$part_name" "$raid_dev" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + for i in {0..9}; do + echo "Disassemble - reassemble loop, iteration #$i" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + done + helper_check_device_symlinks + helper_check_device_units + # Cleanup + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + + echo "Parity raid (RAID 5)" + raid_name="mdparity" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00000101" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + ) + # Create a simple RAID 5 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..2} -v -f --level=5 --raid-devices=3 + udevadm wait --settle --timeout=30 "$raid_dev" + mkfs.ext4 -L "$part_name" "$raid_dev" + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + for i in {0..9}; do + echo "Disassemble - reassemble loop, iteration #$i" + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + mdadm --assemble "$raid_dev" --name "$raid_name" -v + udevadm wait --settle --timeout=30 "${expected_symlinks[@]}" + done + helper_check_device_symlinks + helper_check_device_units + # Cleanup + mdadm -v --stop "$raid_dev" + udevadm wait --settle --timeout=30 --removed "${expected_symlinks[@]}" + helper_check_device_units + + echo "Mirror + parity raid (RAID 10) + multiple partitions" + raid_name="mdmirpar" + raid_dev="/dev/md/$raid_name" + part_name="${raid_name}_part" + uuid="aaaaaaaa:bbbbbbbb:cccccccc:00001010" + expected_symlinks=( + "$raid_dev" + "/dev/disk/by-id/md-name-H:$raid_name" + "/dev/disk/by-id/md-uuid-$uuid" + "/dev/disk/by-label/$part_name" # ext4 partition + # Partitions + "${raid_dev}1" + "${raid_dev}2" + "${raid_dev}3" + "/dev/disk/by-id/md-name-H:$raid_name-part1" + "/dev/disk/by-id/md-name-H:$raid_name-part2" + "/dev/disk/by-id/md-name-H:$raid_name-part3" + "/dev/disk/by-id/md-uuid-$uuid-part1" + "/dev/disk/by-id/md-uuid-$uuid-part2" + "/dev/disk/by-id/md-uuid-$uuid-part3" + ) + # Create a simple RAID 10 with an ext4 filesystem + echo y | mdadm --create "$raid_dev" --name "$raid_name" --uuid "$uuid" /dev/disk/by-id/ata-foobar_deadbeefmdadm{0..3} -v -f --level=10 --raid-devices=4 + udevadm wait --settle --timeout=30 "$raid_dev" + # Partition the raid device + # Here, 'udevadm lock' is meaningless, as udevd does not lock MD devices. + sfdisk --wipe=always "$raid_dev" <&2 "Missing verification handler for test case '$TEST_FUNCTION_NAME'" + exit 1 +fi + +echo "TEST_FUNCTION_NAME=$TEST_FUNCTION_NAME" +"$TEST_FUNCTION_NAME" +udevadm settle + +echo "Check if all symlinks under /dev/disk/ are valid (post-test)" +helper_check_device_symlinks + +udevadm control --log-level info + +systemctl status systemd-udevd + +touch /testok diff --git a/test/units/testsuite-65.service b/test/units/testsuite-65.service new file mode 100644 index 0000000..3610baf --- /dev/null +++ b/test/units/testsuite-65.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-65-ANALYZE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh new file mode 100755 index 0000000..a6bb38d --- /dev/null +++ b/test/units/testsuite-65.sh @@ -0,0 +1,909 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +systemctl log-level debug +export SYSTEMD_LOG_LEVEL=debug + +# Sanity checks +# +# We can't really test time, critical-chain and plot verbs here, as +# the testsuite service is a part of the boot transaction, so let's assume +# they fail +systemd-analyze || : +systemd-analyze time || : +systemd-analyze critical-chain || : +# blame +systemd-analyze blame +systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame +# plot +systemd-analyze plot >/dev/null || : +systemd-analyze plot --json=pretty >/dev/null || : +systemd-analyze plot --json=short >/dev/null || : +systemd-analyze plot --json=off >/dev/null || : +systemd-analyze plot --json=pretty --no-legend >/dev/null || : +systemd-analyze plot --json=short --no-legend >/dev/null || : +systemd-analyze plot --json=off --no-legend >/dev/null || : +systemd-analyze plot --table >/dev/null || : +systemd-analyze plot --table --no-legend >/dev/null || : +# legacy/deprecated options (moved to systemctl, but still usable from analyze) +systemd-analyze log-level +systemd-analyze log-level "$(systemctl log-level)" +systemd-analyze get-log-level +systemd-analyze set-log-level "$(systemctl log-level)" +systemd-analyze log-target +systemd-analyze log-target "$(systemctl log-target)" +systemd-analyze get-log-target +systemd-analyze set-log-target "$(systemctl log-target)" +systemd-analyze service-watchdogs +systemd-analyze service-watchdogs "$(systemctl service-watchdogs)" +# dot +systemd-analyze dot >/dev/null +systemd-analyze dot systemd-journald.service >/dev/null +systemd-analyze dot systemd-journald.service systemd-logind.service >/dev/null +systemd-analyze dot --from-pattern="*" --from-pattern="*.service" systemd-journald.service >/dev/null +systemd-analyze dot --to-pattern="*" --to-pattern="*.service" systemd-journald.service >/dev/null +systemd-analyze dot --from-pattern="*.service" --to-pattern="*.service" systemd-journald.service >/dev/null +systemd-analyze dot --order systemd-journald.service systemd-logind.service >/dev/null +systemd-analyze dot --require systemd-journald.service systemd-logind.service >/dev/null +systemd-analyze dot "systemd-*.service" >/dev/null +(! systemd-analyze dot systemd-journald.service systemd-logind.service "*" bbb ccc) +# dump +# this should be rate limited to 10 calls in 10 minutes for unprivileged callers +for _ in {1..10}; do + runas testuser systemd-analyze dump systemd-journald.service >/dev/null +done +(! runas testuser systemd-analyze dump >/dev/null) +# still limited after a reload +systemctl daemon-reload +(! runas testuser systemd-analyze dump >/dev/null) +# and a re-exec +systemctl daemon-reexec +(! runas testuser systemd-analyze dump >/dev/null) +# privileged call, so should not be rate limited +for _ in {1..10}; do + systemd-analyze dump systemd-journald.service >/dev/null +done +systemd-analyze dump >/dev/null +systemd-analyze dump "*" >/dev/null +systemd-analyze dump "*.socket" >/dev/null +systemd-analyze dump "*.socket" "*.service" aaaaaaa ... >/dev/null +systemd-analyze dump systemd-journald.service >/dev/null +systemd-analyze malloc >/dev/null +(! systemd-analyze dump "") +# unit-files +systemd-analyze unit-files >/dev/null +systemd-analyze unit-files systemd-journald.service >/dev/null +systemd-analyze unit-files "*" >/dev/null +systemd-analyze unit-files "*" aaaaaa "*.service" "*.target" >/dev/null +systemd-analyze unit-files --user >/dev/null +systemd-analyze unit-files --user "*" aaaaaa "*.service" "*.target" >/dev/null +# unit-paths +systemd-analyze unit-paths +systemd-analyze unit-paths --user +systemd-analyze unit-paths --global +# exist-status +systemd-analyze exit-status +systemd-analyze exit-status STDOUT BPF +systemd-analyze exit-status 0 1 {63..65} +(! systemd-analyze exit-status STDOUT BPF "hello*") +# capability +systemd-analyze capability +systemd-analyze capability cap_chown CAP_KILL +systemd-analyze capability 0 1 {30..32} +(! systemd-analyze capability cap_chown CAP_KILL "hello*") +# condition +mkdir -p /run/systemd/system +UNIT_NAME="analyze-condition-$RANDOM.service" +cat >"/run/systemd/system/$UNIT_NAME" <1.0 +ConditionPathExists=/etc/os-release + +[Service] +ExecStart=/bin/true +EOF +systemctl daemon-reload +systemd-analyze condition --unit="$UNIT_NAME" +systemd-analyze condition 'ConditionKernelVersion = ! <4.0' \ + 'ConditionKernelVersion = >=3.1' \ + 'ConditionACPower=|false' \ + 'ConditionArchitecture=|!arm' \ + 'AssertPathExists=/etc/os-release' +(! systemd-analyze condition 'ConditionArchitecture=|!arm' 'AssertXYZ=foo') +(! systemd-analyze condition 'ConditionKernelVersion=<1.0') +(! systemd-analyze condition 'AssertKernelVersion=<1.0') +# syscall-filter +systemd-analyze syscall-filter >/dev/null +systemd-analyze syscall-filter @chown @sync +systemd-analyze syscall-filter @sync @sync @sync +(! systemd-analyze syscall-filter @chown @sync @foobar) +# filesystems (requires libbpf support) +if systemctl --version | grep "+BPF_FRAMEWORK"; then + systemd-analyze filesystems >/dev/null + systemd-analyze filesystems @basic-api + systemd-analyze filesystems @basic-api @basic-api @basic-api + (! systemd-analyze filesystems @basic-api @basic-api @foobar @basic-api) +fi +# calendar +systemd-analyze calendar '*-2-29 0:0:0' +systemd-analyze calendar --iterations=5 '*-2-29 0:0:0' +systemd-analyze calendar '*-* *:*:*' +systemd-analyze calendar --iterations=5 '*-* *:*:*' +systemd-analyze calendar --iterations=50 '*-* *:*:*' +systemd-analyze calendar --iterations=0 '*-* *:*:*' +systemd-analyze calendar --iterations=5 '01-01-22 01:00:00' +systemd-analyze calendar --base-time=yesterday --iterations=5 '*-* *:*:*' +(! systemd-analyze calendar --iterations=0 '*-* 99:*:*') +(! systemd-analyze calendar --base-time=never '*-* *:*:*') +(! systemd-analyze calendar 1) +(! systemd-analyze calendar "") +# timestamp +systemd-analyze timestamp now +systemd-analyze timestamp -- -1 +systemd-analyze timestamp yesterday now tomorrow +(! systemd-analyze timestamp yesterday never tomorrow) +(! systemd-analyze timestamp 1) +(! systemd-analyze timestamp '*-2-29 0:0:0') +(! systemd-analyze timestamp "") +# timespan +systemd-analyze timespan 1 +systemd-analyze timespan 1s 300s '1year 0.000001s' +(! systemd-analyze timespan 1s 300s aaaaaa '1year 0.000001s') +(! systemd-analyze timespan -- -1) +(! systemd-analyze timespan '*-2-29 0:0:0') +(! systemd-analyze timespan "") +# cat-config +systemd-analyze cat-config systemd/system.conf >/dev/null +systemd-analyze cat-config /etc/systemd/system.conf >/dev/null +systemd-analyze cat-config systemd/system.conf systemd/journald.conf >/dev/null +systemd-analyze cat-config systemd/system.conf foo/bar systemd/journald.conf >/dev/null +systemd-analyze cat-config foo/bar +systemd-analyze cat-config --tldr systemd/system.conf >/dev/null +systemd-analyze cat-config --tldr /etc/systemd/system.conf >/dev/null +systemd-analyze cat-config --tldr systemd/system.conf systemd/journald.conf >/dev/null +systemd-analyze cat-config --tldr systemd/system.conf foo/bar systemd/journald.conf >/dev/null +systemd-analyze cat-config --tldr foo/bar +# security +systemd-analyze security +systemd-analyze security --json=off +systemd-analyze security --json=pretty | jq +systemd-analyze security --json=short | jq + +if [[ ! -v ASAN_OPTIONS ]]; then + # check that systemd-analyze cat-config paths work in a chroot + mkdir -p /tmp/root + mount --bind / /tmp/root + systemd-analyze cat-config systemd/system-preset >/tmp/out1 + chroot /tmp/root systemd-analyze cat-config systemd/system-preset >/tmp/out2 + diff /tmp/out{1,2} +fi + +# verify +mkdir -p /tmp/img/usr/lib/systemd/system/ +mkdir -p /tmp/img/opt/ + +touch /tmp/img/opt/script0.sh +chmod +x /tmp/img/opt/script0.sh + +cat </tmp/img/usr/lib/systemd/system/testfile.service +[Service] +ExecStart = /opt/script0.sh +EOF + +set +e +# Default behaviour is to recurse through all dependencies when unit is loaded +(! systemd-analyze verify --root=/tmp/img/ testfile.service) + +# As above, recurses through all dependencies when unit is loaded +(! systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service) + +# Recurses through unit file and its direct dependencies when unit is loaded +(! systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service) + +set -e + +# zero exit status since dependencies are ignored when unit is loaded +systemd-analyze verify --recursive-errors=no --root=/tmp/img/ testfile.service + +rm /tmp/img/usr/lib/systemd/system/testfile.service + +cat </tmp/testfile.service +[Unit] +foo = bar + +[Service] +ExecStart = echo hello +EOF + +cat </tmp/testfile2.service +[Unit] +Requires = testfile.service + +[Service] +ExecStart = echo hello +EOF + +# Zero exit status since no additional dependencies are recursively loaded when the unit file is loaded +systemd-analyze verify --recursive-errors=no /tmp/testfile2.service + +set +e +# Non-zero exit status since all associated dependencies are recursively loaded when the unit file is loaded +(! systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service) +set -e + +rm /tmp/testfile.service +rm /tmp/testfile2.service + +cat </tmp/sample.service +[Unit] +Description = A Sample Service + +[Service] +ExecStart = echo hello +Slice=support.slice +EOF + +# Zero exit status since no additional dependencies are recursively loaded when the unit file is loaded +systemd-analyze verify --recursive-errors=no /tmp/sample.service + +cat </tmp/testfile.service +[Service] +ExecStart = echo hello +DeviceAllow=/dev/sda +EOF + +# Prevent regression from #13380 and #20859 where we can't verify hidden files +cp /tmp/testfile.service /tmp/.testfile.service + +systemd-analyze verify /tmp/.testfile.service + +rm /tmp/.testfile.service + +# Alias a unit file's name on disk (see #20061) +cp /tmp/testfile.service /tmp/testsrvc + +(! systemd-analyze verify /tmp/testsrvc) + +systemd-analyze verify /tmp/testsrvc:alias.service + +# Zero exit status since the value used for comparison determine exposure to security threats is by default 100 +systemd-analyze security --offline=true /tmp/testfile.service + +#The overall exposure level assigned to the unit is greater than the set threshold +(! systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service) + +# Ensure we print the list of ACLs, see https://github.com/systemd/systemd/issues/23185 +systemd-analyze security --offline=true /tmp/testfile.service | grep -q -F "/dev/sda" + +rm /tmp/testfile.service + +cat </tmp/img/usr/lib/systemd/system/testfile.service +[Service] +ExecStart = echo hello +PrivateNetwork = yes +PrivateDevices = yes +PrivateUsers = yes +EOF + +# The new overall exposure level assigned to the unit is less than the set thresholds +# Verifies that the --offline= option works with --root= +systemd-analyze security --threshold=90 --offline=true --root=/tmp/img/ testfile.service + +cat </tmp/foo@.service +[Service] +ExecStart=ls +EOF + +cat </tmp/hoge@test.service +[Service] +ExecStart=ls +EOF + +# issue #30357 +pushd /tmp +systemd-analyze verify foo@bar.service +systemd-analyze verify foo@.service +systemd-analyze verify hoge@test.service +(! systemd-analyze verify hoge@nonexist.service) +(! systemd-analyze verify hoge@.service) +popd +pushd / +systemd-analyze verify tmp/foo@bar.service +systemd-analyze verify tmp/foo@.service +systemd-analyze verify tmp/hoge@test.service +(! systemd-analyze verify tmp/hoge@nonexist.service) +(! systemd-analyze verify tmp/hoge@.service) +popd +pushd /usr +systemd-analyze verify ../tmp/foo@bar.service +systemd-analyze verify ../tmp/foo@.service +systemd-analyze verify ../tmp/hoge@test.service +(! systemd-analyze verify ../tmp/hoge@nonexist.service) +(! systemd-analyze verify ../tmp/hoge@.service) +popd +systemd-analyze verify /tmp/foo@bar.service +systemd-analyze verify /tmp/foo@.service +systemd-analyze verify /tmp/hoge@test.service +(! systemd-analyze verify /tmp/hoge@nonexist.service) +(! systemd-analyze verify /tmp/hoge@.service) + +# Added an additional "INVALID_ID" id to the .json to verify that nothing breaks when input is malformed +# The PrivateNetwork id description and weight was changed to verify that 'security' is actually reading in +# values from the .json file when required. The default weight for "PrivateNetwork" is 2500, and the new weight +# assigned to that id in the .json file is 6000. This increased weight means that when the "PrivateNetwork" key is +# set to 'yes' (as above in the case of testfile.service) in the content of the unit file, the overall exposure +# level for the unit file should decrease to account for that increased weight. +cat </tmp/testfile.json +{"UserOrDynamicUser": + {"description_bad": "Service runs as root user", + "weight": 0, + "range": 10 + }, +"SupplementaryGroups": + {"description_good": "Service has no supplementary groups", + "description_bad": "Service runs with supplementary groups", + "description_na": "Service runs as root, option does not matter", + "weight": 200, + "range": 1 + }, +"PrivateDevices": + {"description_good": "Service has no access to hardware devices", + "description_bad": "Service potentially has access to hardware devices", + "weight": 1000, + "range": 1 + }, +"PrivateMounts": + {"description_good": "Service cannot install system mounts", + "description_bad": "Service may install system mounts", + "weight": 1000, + "range": 1 + }, +"PrivateNetwork": + {"description_good": "Service doesn't have access to the host's network", + "description_bad": "Service has access to the host's network", + "weight": 6000, + "range": 1 + }, +"PrivateTmp": + {"description_good": "Service has no access to other software's temporary files", + "description_bad": "Service has access to other software's temporary files", + "weight": 1000, + "range": 1 + }, +"PrivateUsers": + {"description_good": "Service does not have access to other users", + "description_bad": "Service has access to other users", + "weight": 1000, + "range": 1 + }, +"ProtectControlGroups": + {"description_good": "Service cannot modify the control group file system", + "description_bad": "Service may modify the control group file system", + "weight": 1000, + "range": 1 + }, +"ProtectKernelModules": + {"description_good": "Service cannot load or read kernel modules", + "description_bad": "Service may load or read kernel modules", + "weight": 1000, + "range": 1 + }, +"ProtectKernelTunables": + {"description_good": "Service cannot alter kernel tunables (/proc/sys, …)", + "description_bad": "Service may alter kernel tunables", + "weight": 1000, + "range": 1 + }, +"ProtectKernelLogs": + {"description_good": "Service cannot read from or write to the kernel log ring buffer", + "description_bad": "Service may read from or write to the kernel log ring buffer", + "weight": 1000, + "range": 1 + }, +"ProtectClock": + {"description_good": "Service cannot write to the hardware clock or system clock", + "description_bad": "Service may write to the hardware clock or system clock", + "weight": 1000, + "range": 1 + }, +"ProtectHome": + {"weight": 1000, + "range": 10 + }, +"ProtectHostname": + {"description_good": "Service cannot change system host/domainname", + "description_bad": "Service may change system host/domainname", + "weight": 50, + "range": 1 + }, +"ProtectSystem": + {"weight": 1000, + "range": 10 + }, +"RootDirectoryOrRootImage": + {"description_good": "Service has its own root directory/image", + "description_bad": "Service runs within the host's root directory", + "weight": 200, + "range": 1 + }, +"LockPersonality": + {"description_good": "Service cannot change ABI personality", + "description_bad": "Service may change ABI personality", + "weight": 100, + "range": 1 + }, +"MemoryDenyWriteExecute": + {"description_good": "Service cannot create writable executable memory mappings", + "description_bad": "Service may create writable executable memory mappings", + "weight": 100, + "range": 1 + }, +"NoNewPrivileges": + {"description_good": "Service processes cannot acquire new privileges", + "description_bad": "Service processes may acquire new privileges", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_ADMIN": + {"description_good": "Service has no administrator privileges", + "description_bad": "Service has administrator privileges", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SET_UID_GID_PCAP": + {"description_good": "Service cannot change UID/GID identities/capabilities", + "description_bad": "Service may change UID/GID identities/capabilities", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_PTRACE": + {"description_good": "Service has no ptrace() debugging abilities", + "description_bad": "Service has ptrace() debugging abilities", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_TIME": + {"description_good": "Service processes cannot change the system clock", + "description_bad": "Service processes may change the system clock", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_NET_ADMIN": + {"description_good": "Service has no network configuration privileges", + "description_bad": "Service has network configuration privileges", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_RAWIO": + {"description_good": "Service has no raw I/O access", + "description_bad": "Service has raw I/O access", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_MODULE": + {"description_good": "Service cannot load kernel modules", + "description_bad": "Service may load kernel modules", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_AUDIT": + {"description_good": "Service has no audit subsystem access", + "description_bad": "Service has audit subsystem access", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYSLOG": + {"description_good": "Service has no access to kernel logging", + "description_bad": "Service has access to kernel logging", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_NICE_RESOURCE": + {"description_good": "Service has no privileges to change resource use parameters", + "description_bad": "Service has privileges to change resource use parameters", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_MKNOD": + {"description_good": "Service cannot create device nodes", + "description_bad": "Service may create device nodes", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_CHOWN_FSETID_SETFCAP": + {"description_good": "Service cannot change file ownership/access mode/capabilities", + "description_bad": "Service may change file ownership/access mode/capabilities unrestricted", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_DAC_FOWNER_IPC_OWNER": + {"description_good": "Service cannot override UNIX file/IPC permission checks", + "description_bad": "Service may override UNIX file/IPC permission checks", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_KILL": + {"description_good": "Service cannot send UNIX signals to arbitrary processes", + "description_bad": "Service may send UNIX signals to arbitrary processes", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW": + {"description_good": "Service has no elevated networking privileges", + "description_bad": "Service has elevated networking privileges", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_BOOT": + {"description_good": "Service cannot issue reboot()", + "description_bad": "Service may issue reboot()", + "weight": 100, + "range": 1 + }, +"CapabilityBoundingSet_CAP_MAC": + {"description_good": "Service cannot adjust SMACK MAC", + "description_bad": "Service may adjust SMACK MAC", + "weight": 100, + "range": 1 + }, +"CapabilityBoundingSet_CAP_LINUX_IMMUTABLE": + {"description_good": "Service cannot mark files immutable", + "description_bad": "Service may mark files immutable", + "weight": 75, + "range": 1 + }, +"CapabilityBoundingSet_CAP_IPC_LOCK": + {"description_good": "Service cannot lock memory into RAM", + "description_bad": "Service may lock memory into RAM", + "weight": 50, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_CHROOT": + {"description_good": "Service cannot issue chroot()", + "description_bad": "Service may issue chroot()", + "weight": 50, + "range": 1 + }, +"CapabilityBoundingSet_CAP_BLOCK_SUSPEND": + {"description_good": "Service cannot establish wake locks", + "description_bad": "Service may establish wake locks", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_WAKE_ALARM": + {"description_good": "Service cannot program timers that wake up the system", + "description_bad": "Service may program timers that wake up the system", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_LEASE": + {"description_good": "Service cannot create file leases", + "description_bad": "Service may create file leases", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_TTY_CONFIG": + {"description_good": "Service cannot issue vhangup()", + "description_bad": "Service may issue vhangup()", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_PACCT": + {"description_good": "Service cannot use acct()", + "description_bad": "Service may use acct()", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_BPF": + {"description_good": "Service may load BPF programs", + "description_bad": "Service may not load BPF programs", + "weight": 25, + "range": 1 + }, +"UMask": + {"weight": 100, + "range": 10 + }, +"KeyringMode": + {"description_good": "Service doesn't share key material with other services", + "description_bad": "Service shares key material with other service", + "weight": 1000, + "range": 1 + }, +"ProtectProc": + {"description_good": "Service has restricted access to process tree(/proc hidepid=)", + "description_bad": "Service has full access to process tree(/proc hidepid=)", + "weight": 1000, + "range": 3 + }, +"ProcSubset": + {"description_good": "Service has no access to non-process/proc files(/proc subset=)", + "description_bad": "Service has full access to non-process/proc files(/proc subset=)", + "weight": 10, + "range": 1 + }, +"NotifyAccess": + {"description_good": "Service child processes cannot alter service state", + "description_bad": "Service child processes may alter service state", + "weight": 1000, + "range": 1 + }, +"RemoveIPC": + {"description_good": "Service user cannot leave SysV IPC objects around", + "description_bad": "Service user may leave SysV IPC objects around", + "description_na": "Service runs as root, option does not apply", + "weight": 100, + "range": 1 + }, +"Delegate": + {"description_good": "Service does not maintain its own delegated control group subtree", + "description_bad": "Service maintains its own delegated control group subtree", + "weight": 100, + "range": 1 + }, +"RestrictRealtime": + {"description_good": "Service realtime scheduling access is restricted", + "description_bad": "Service may acquire realtime scheduling", + "weight": 500, + "range": 1 + }, +"RestrictSUIDSGID": + {"description_good": "SUID/SGIDfilecreationbyserviceisrestricted", + "description_bad": "ServicemaycreateSUID/SGIDfiles", + "weight": 1000, + "range": 1 + }, +"RestrictNamespaces_user": + {"description_good": "Servicecannotcreateusernamespaces", + "description_bad": "Servicemaycreateusernamespaces", + "weight": 1500, + "range": 1 + }, +"RestrictNamespaces_mnt": + {"description_good": "Service cannot create file system namespaces", + "description_bad": "Service may create file system namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_ipc": + {"description_good": "Service cannot create IPC namespaces", + "description_bad": "Service may create IPC namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_pid": + {"description_good": "Service cannot create process namespaces", + "description_bad": "Service may create process namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_cgroup": + {"description_good": "Service cannot create cgroup namespaces", + "description_bad": "Service may create cgroup namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_net": + {"description_good": "Service cannot create network namespaces", + "description_bad": "Service may create network namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_uts": + {"description_good": "Service cannot create hostname namespaces", + "description_bad": "Service may create hostname namespaces", + "weight": 100, + "range": 1 + }, +"RestrictAddressFamilies_AF_INET_INET6": + {"description_good": "Service cannot allocate Internet sockets", + "description_bad": "Service may allocate Internet sockets", + "weight": 1500, + "range": 1 + }, +"RestrictAddressFamilies_AF_UNIX": + {"description_good": "Service cannot allocate local sockets", + "description_bad": "Service may allocate local sockets", + "weight": 25, + "range": 1 + }, +"RestrictAddressFamilies_AF_NETLINK": + {"description_good": "Service cannot allocate netlink sockets", + "description_bad": "Service may allocate netlink sockets", + "weight": 200, + "range": 1 + }, +"RestrictAddressFamilies_AF_PACKET": + {"description_good": "Service cannot allocate packet sockets", + "description_bad": "Service may allocate packet sockets", + "weight": 1000, + "range": 1 + }, +"RestrictAddressFamilies_OTHER": + {"description_good": "Service cannot allocate exotic sockets", + "description_bad": "Service may allocate exotic sockets", + "weight": 1250, + "range": 1 + }, +"SystemCallArchitectures": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_swap": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_obsolete": + {"weight": 250, + "range": 10 + }, +"SystemCallFilter_clock": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_cpu_emulation": + {"weight": 250, + "range": 10 + }, +"SystemCallFilter_debug": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_mount": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_module": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_raw_io": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_reboot": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_privileged": + {"weight": 700, + "range": 10 + }, +"SystemCallFilter_resources": + {"weight": 700, + "range": 10 + }, +"IPAddressDeny": + {"weight": 1000, + "range": 10 + }, +"DeviceAllow": + {"weight": 1000, + "range": 10 + }, +"AmbientCapabilities": + {"description_good": "Service process does not receive ambient capabilities", + "description_bad": "Service process receives ambient capabilities", + "weight": 500, + "range": 1 + }, +"INVALID_ID": + {"weight": 1000, + "range": 10 + } +} +EOF + +# Reads in custom security requirements from the parsed .json file and uses these for comparison +systemd-analyze security --threshold=90 --offline=true \ + --security-policy=/tmp/testfile.json \ + --root=/tmp/img/ testfile.service + +# The strict profile adds a lot of sanboxing options +systemd-analyze security --threshold=25 --offline=true \ + --security-policy=/tmp/testfile.json \ + --profile=strict \ + --root=/tmp/img/ testfile.service + +# The trusted profile doesn't add any sanboxing options +(! systemd-analyze security --threshold=25 --offline=true \ + --security-policy=/tmp/testfile.json \ + --profile=/usr/lib/systemd/portable/profile/trusted/service.conf \ + --root=/tmp/img/ testfile.service) + +(! systemd-analyze security --threshold=50 --offline=true \ + --security-policy=/tmp/testfile.json \ + --root=/tmp/img/ testfile.service) + +rm /tmp/img/usr/lib/systemd/system/testfile.service + +if systemd-analyze --version | grep -q -F "+ELFUTILS"; then + systemd-analyze inspect-elf --json=short /lib/systemd/systemd | grep -q -F '"elfType":"executable"' +fi + +systemd-analyze --threshold=90 security systemd-journald.service + +# issue 23663 +check() {( + set +x + output=$(systemd-analyze security --offline="${2?}" "${3?}" | grep -F 'SystemCallFilter=') + assert_in "System call ${1?} list" "$output" + assert_in "[+✓] SystemCallFilter=~@swap" "$output" + assert_in "[+✓] SystemCallFilter=~@resources" "$output" + assert_in "[+✓] SystemCallFilter=~@reboot" "$output" + assert_in "[+✓] SystemCallFilter=~@raw-io" "$output" + assert_in "[-✗] SystemCallFilter=~@privileged" "$output" + assert_in "[+✓] SystemCallFilter=~@obsolete" "$output" + assert_in "[+✓] SystemCallFilter=~@mount" "$output" + assert_in "[+✓] SystemCallFilter=~@module" "$output" + assert_in "[+✓] SystemCallFilter=~@debug" "$output" + assert_in "[+✓] SystemCallFilter=~@cpu-emulation" "$output" + assert_in "[-✗] SystemCallFilter=~@clock" "$output" +)} + +export -n SYSTEMD_LOG_LEVEL + +mkdir -p /run/systemd/system +cat >/run/systemd/system/allow-list.service </run/systemd/system/deny-list.service <&1) +name=$(echo "$output" | awk '{ print $4 }' | cut -d';' -f1) + +check allow yes /run/systemd/transient/"$name" +check allow no "$name" + +output=$(systemd-run -p "SystemCallFilter=~@known" -p "SystemCallFilter=@system-service" -p "SystemCallFilter=~@resources:ENOANO @privileged" -p "SystemCallFilter=@clock" sleep 60 2>&1) +name=$(echo "$output" | awk '{ print $4 }' | cut -d';' -f1) + +check deny yes /run/systemd/transient/"$name" +check deny no "$name" + +# Let's also test the "image-policy" verb + +systemd-analyze image-policy '*' 2>&1 | grep -q -F "Long form: =verity+signed+encrypted+unprotected+unused+absent" +systemd-analyze image-policy '-' 2>&1 | grep -q -F "Long form: =unused+absent" +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -F "Long form: usr=verity:home=encrypted:=unused+absent" +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^home \+encrypted \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr \+verity \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^root \+ignore \+' +systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -q -e '^usr-verity \+unprotected \+' + +(! systemd-analyze image-policy 'doedel') + +# Output is very hard to predict, but let's run it for coverage anyway +systemd-analyze pcrs +systemd-analyze pcrs --json=pretty +systemd-analyze pcrs 14 7 0 ima + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-66-deviceisolation.service b/test/units/testsuite-66-deviceisolation.service new file mode 100644 index 0000000..2d815a9 --- /dev/null +++ b/test/units/testsuite-66-deviceisolation.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=Service that uses device isolation + +[Service] +DevicePolicy=strict +DeviceAllow=/dev/null r +StandardOutput=file:/tmp/testsuite66serviceresults +ExecStartPre=rm -f /tmp/testsuite66serviceresults +ExecStart=/bin/bash -c "while true; do sleep 0.01 && echo meow >/dev/null && echo thisshouldnotbehere; done" diff --git a/test/units/testsuite-66.service b/test/units/testsuite-66.service new file mode 100644 index 0000000..7e9dc3b --- /dev/null +++ b/test/units/testsuite-66.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TESTSUITE-66-DEVICEISOLATION + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-66.sh b/test/units/testsuite-66.sh new file mode 100755 index 0000000..147335a --- /dev/null +++ b/test/units/testsuite-66.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +RESULTS_FILE=/tmp/testsuite66serviceresults + +systemd-analyze log-level debug + +systemctl start testsuite-66-deviceisolation.service + +sleep 5 +grep -q "Operation not permitted" "$RESULTS_FILE" + +systemctl daemon-reload +systemctl daemon-reexec + +systemctl stop testsuite-66-deviceisolation.service + +grep -q "thisshouldnotbehere" "$RESULTS_FILE" && exit 42 + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-67.service b/test/units/testsuite-67.service new file mode 100644 index 0000000..82f998e --- /dev/null +++ b/test/units/testsuite-67.service @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-67-INTEGRITY +After=multi-user.target + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-67.sh b/test/units/testsuite-67.sh new file mode 100755 index 0000000..a42fd66 --- /dev/null +++ b/test/units/testsuite-67.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -euxo pipefail + +export DM_NAME="integrity_test" +export FULL_DM_DEV_NAME="/dev/mapper/${DM_NAME}" +export FS_UUID="01234567-ffff-eeee-eeee-0123456789ab" +export GEN="/var/run/systemd/generator" + +image_dir="" + +cleanup() +{ + if [ -z "${image_dir}" ]; then + return + fi + + if [ -f "${image_dir}/image" ]; then + if [ -e "${FULL_DM_DEV_NAME}" ]; then + integritysetup close "${DM_NAME}" + fi + losetup -d "${loop}" + fi + + rm -rf "${image_dir}" +} + +trap cleanup EXIT + +build_integrity_tab() +{ +cat <"/etc/integritytab" +${DM_NAME} ${loop} - integrity-algorithm=$1 +EOF +} + +image_dir="$(mktemp -d -t -p / integrity.tmp.XXXXXX)" +if [ -z "${image_dir}" ] || [ ! -d "${image_dir}" ]; then + echo "mktemp under / failed" + exit 1 +fi + +dd if=/dev/zero of="${image_dir}/image" bs=1048576 count=64 || exit 1 +dd if=/dev/zero of="${image_dir}/data" bs=1048576 count=64 || exit 1 +loop="$(losetup --show -f "${image_dir}/image")" + +if [[ ! -e ${loop} ]]; then + echo "Loopback device created not found!" + exit 1 +fi + +# Do one iteration with a separate data device, to test those branches +separate_data=1 + +for algorithm in crc32c crc32 sha1 sha256 +do + if [ "${separate_data}" -eq 1 ]; then + data_option="--data-device=${image_dir}/data" + else + data_option="" + fi + integritysetup format "${loop}" --batch-mode -I "${algorithm}" "${data_option}" || exit 1 + integritysetup open -I "${algorithm}" "${loop}" "${DM_NAME}" "${data_option}" || exit 1 + mkfs.ext4 -U "${FS_UUID}" "${FULL_DM_DEV_NAME}" || exit 1 + + # Give userspace time to handle udev events for new FS showing up ... + udevadm settle + + integritysetup close "${DM_NAME}" || exit 1 + + # create integritytab, generate units, start service + if [ "${separate_data}" -eq 1 ]; then + data_option=",data-device=${image_dir}/data" + else + data_option="" + fi + build_integrity_tab "${algorithm}${data_option}" + + # Cause the generator to re-run + systemctl daemon-reload || exit 1 + + # Check for existence of unit files... + if [[ ! -e "/run/systemd/generator/systemd-integritysetup@${DM_NAME}.service" ]]; then + echo "Service file does not exist!" + exit 1 + fi + + # Make sure we are in a consistent state, e.g. not already active before we start + systemctl stop systemd-integritysetup@"${DM_NAME}".service || exit 1 + systemctl start systemd-integritysetup@"${DM_NAME}".service || exit 1 + # Reset the start-limit counters, as we're going to restart the service a couple of times + systemctl reset-failed systemd-integritysetup@"${DM_NAME}".service + + # Check the signature on the FS to ensure we can retrieve it and that is matches + if [ -e "${FULL_DM_DEV_NAME}" ]; then + # If a separate device is used for the metadata storage, then blkid will return one of the loop devices + if [ "${separate_data}" -eq 1 ]; then + dev_name="$(integritysetup status ${DM_NAME} | grep '^\s*device:' | awk '{print $2}')" + else + dev_name="${FULL_DM_DEV_NAME}" + fi + if [ "${dev_name}" != "$(blkid -U "${FS_UUID}")" ]; then + echo "Failed to locate FS with matching UUID!" + exit 1 + fi + else + echo "Failed to bring up integrity device!" + exit 1 + fi + + systemctl stop systemd-integritysetup@"${DM_NAME}".service || exit 1 + + if [ -e "${FULL_DM_DEV_NAME}" ]; then + echo "Expecting ${FULL_DM_DEV_NAME} to not exist after stopping unit!" + exit 1 + fi + + separate_data=0 +done + +touch /testok diff --git a/test/units/testsuite-68.service b/test/units/testsuite-68.service new file mode 100644 index 0000000..2d86e1f --- /dev/null +++ b/test/units/testsuite-68.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-68-PROPAGATE-EXIT-STATUS + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh diff --git a/test/units/testsuite-68.sh b/test/units/testsuite-68.sh new file mode 100755 index 0000000..11da48a --- /dev/null +++ b/test/units/testsuite-68.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# Wait for a service to enter a state within a timeout period, if it doesn't +# enter the desired state within the timeout period then this function will +# exit the test case with a non zero exit code. +wait_on_state_or_fail() { + service=$1 + expected_state=$2 + timeout=$3 + + state=$(systemctl show "$service" --property=ActiveState --value) + while [ "$state" != "$expected_state" ]; do + if [ "$timeout" = "0" ]; then + systemd-analyze log-level info + exit 1 + fi + timeout=$((timeout - 1)) + sleep 1 + state=$(systemctl show "$service" --property=ActiveState --value) + done +} + +systemd-analyze log-level debug + +cat >/run/systemd/system/testservice-failure-68.service </run/systemd/system/testservice-failure-68-template.service </run/systemd/system/testservice-success-68.service </run/systemd/system/testservice-success-68-template.service </tmp/check_on_success.sh <<"EOF" +#!/bin/sh + +set -ex +env | sort +if [ "$MONITOR_SERVICE_RESULT" != "success" ]; then + echo "MONITOR_SERVICE_RESULT was '$MONITOR_SERVICE_RESULT', expected 'success'" + exit 1 +fi + +if [ "$MONITOR_EXIT_CODE" != "exited" ]; then + echo "MONITOR_EXIT_CODE was '$MONITOR_EXIT_CODE', expected 'exited'" + exit 1 +fi + +if [ "$MONITOR_EXIT_STATUS" != "0" ]; then + echo "MONITOR_EXIT_STATUS was '$MONITOR_EXIT_STATUS', expected '0'" + exit 1 +fi + +if [ -z "$MONITOR_INVOCATION_ID" ]; then + echo "MONITOR_INVOCATION_ID unset" + exit 1 +fi + +if [ "$MONITOR_UNIT" != "testservice-success-68.service" ] && + [ "$MONITOR_UNIT" != "testservice-success-68-template.service" ] && + [ "$MONITOR_UNIT" != "testservice-transient-success-68.service" ]; then + + echo "MONITOR_UNIT was '$MONITOR_UNIT', expected 'testservice[-transient]-success-68[-template].service'" + exit 1 +fi + +exit 0 +EOF +chmod +x /tmp/check_on_success.sh + +cat >/run/systemd/system/testservice-success-exit-handler-68.service </run/systemd/system/testservice-success-exit-handler-68-template@.service </tmp/check_on_failure.sh <<"EOF" +#!/bin/sh + +set -ex +env | sort +if [ "$MONITOR_SERVICE_RESULT" != "exit-code" ]; then + echo "MONITOR_SERVICE_RESULT was '$MONITOR_SERVICE_RESULT', expected 'exit-code'" + exit 1 +fi + +if [ "$MONITOR_EXIT_CODE" != "exited" ]; then + echo "MONITOR_EXIT_CODE was '$MONITOR_EXIT_CODE', expected 'exited'" + exit 1 +fi + +if [ "$MONITOR_EXIT_STATUS" != "1" ]; then + echo "MONITOR_EXIT_STATUS was '$MONITOR_EXIT_STATUS', expected '1'" + exit 1 +fi + +if [ -z "$MONITOR_INVOCATION_ID" ]; then + echo "MONITOR_INVOCATION_ID unset" + exit 1 +fi + +if [ "$MONITOR_UNIT" != "testservice-failure-68.service" ] && + [ "$MONITOR_UNIT" != "testservice-failure-68-template.service" ] && + [ "$MONITOR_UNIT" != "testservice-transient-failure-68.service" ]; then + + echo "MONITOR_UNIT was '$MONITOR_UNIT', expected 'testservice[-transient]-failure-68[-template].service'" + exit 1 +fi + +exit 0 +EOF +chmod +x /tmp/check_on_failure.sh + + +cat >/run/systemd/system/testservice-failure-exit-handler-68.service </run/systemd/system/testservice-failure-exit-handler-68-template@.service < /tmp/testdata +systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 +# LoadCredentialEncrypted +systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata +# SetCredentialEncrypted +systemd-run -p PrivateDevices=yes -p SetCredentialEncrypted=testdata.encrypted:"$(cat /tmp/testdata.encrypted)" --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata + +rm -f /tmp/testdata diff --git a/test/units/testsuite-70.cryptenroll.sh b/test/units/testsuite-70.cryptenroll.sh new file mode 100755 index 0000000..3f8c14e --- /dev/null +++ b/test/units/testsuite-70.cryptenroll.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +cryptenroll_wipe_and_check() {( + set +o pipefail + + : >/tmp/cryptenroll.out + systemd-cryptenroll "$@" |& tee /tmp/cryptenroll.out + grep -qE "Wiped slot [[:digit:]]+" /tmp/cryptenroll.out +)} + +# There is an external issue with libcryptsetup on ppc64 that hits 95% of Ubuntu ppc64 test runs, so skip it +if [[ "$(uname -m)" == "ppc64le" ]]; then + echo "Skipping systemd-cryptenroll tests on ppc64le, see https://github.com/systemd/systemd/issues/27716" + exit 0 +fi + +export SYSTEMD_LOG_LEVEL=debug +IMAGE="$(mktemp /tmp/systemd-cryptenroll-XXX.image)" + +truncate -s 20M "$IMAGE" +echo -n password >/tmp/password +# Change file mode to avoid "/tmp/password has 0644 mode that is too permissive" messages +chmod 0600 /tmp/password +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/password + +# Enroll additional tokens, keys, and passwords to exercise the list and wipe stuff +systemd-cryptenroll --unlock-key-file=/tmp/password --tpm2-device=auto "$IMAGE" +NEWPASSWORD="" systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" +NEWPASSWORD=foo systemd-cryptenroll --unlock-key-file=/tmp/password --password "$IMAGE" +for _ in {0..9}; do + systemd-cryptenroll --unlock-key-file=/tmp/password --recovery-key "$IMAGE" +done +PASSWORD="" NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true "$IMAGE" +# Do some basic checks before we start wiping stuff +systemd-cryptenroll "$IMAGE" +systemd-cryptenroll "$IMAGE" | grep password +systemd-cryptenroll "$IMAGE" | grep recovery +# Let's start wiping +cryptenroll_wipe_and_check "$IMAGE" --wipe=empty +(! cryptenroll_wipe_and_check "$IMAGE" --wipe=empty) +cryptenroll_wipe_and_check "$IMAGE" --wipe=empty,0 +PASSWORD=foo NEWPASSWORD=foo cryptenroll_wipe_and_check "$IMAGE" --wipe=0,0,empty,0,pkcs11,fido2,000,recovery,password --password +systemd-cryptenroll "$IMAGE" | grep password +(! systemd-cryptenroll "$IMAGE" | grep recovery) +# We shouldn't be able to wipe all keyslots without enrolling a new key first +(! systemd-cryptenroll "$IMAGE" --wipe=all) +PASSWORD=foo NEWPASSWORD=foo cryptenroll_wipe_and_check "$IMAGE" --password --wipe=all +# Check if the newly (and only) enrolled password works +(! systemd-cryptenroll --unlock-key-file=/tmp/password --recovery-key "$IMAGE") +(! PASSWORD="" systemd-cryptenroll --recovery-key "$IMAGE") +PASSWORD=foo systemd-cryptenroll --recovery-key "$IMAGE" + +systemd-cryptenroll --fido2-with-client-pin=false "$IMAGE" +systemd-cryptenroll --fido2-with-user-presence=false "$IMAGE" +systemd-cryptenroll --fido2-with-user-verification=false "$IMAGE" +systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE" +systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE" + +(! systemd-cryptenroll --fido2-with-client-pin=false) +(! systemd-cryptenroll --fido2-with-user-presence=f "$IMAGE" /tmp/foo) +(! systemd-cryptenroll --fido2-with-client-pin=1234 "$IMAGE") +(! systemd-cryptenroll --fido2-with-user-presence=1234 "$IMAGE") +(! systemd-cryptenroll --fido2-with-user-verification=1234 "$IMAGE") +(! systemd-cryptenroll --tpm2-with-pin=1234 "$IMAGE") +(! systemd-cryptenroll --recovery-key --password "$IMAGE") +(! systemd-cryptenroll --password --recovery-key "$IMAGE") +(! systemd-cryptenroll --password --fido2-device=auto "$IMAGE") +(! systemd-cryptenroll --password --pkcs11-token-uri=auto "$IMAGE") +(! systemd-cryptenroll --password --tpm2-device=auto "$IMAGE") +(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto "$IMAGE") +(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock "$IMAGE") +(! systemd-cryptenroll --fido2-credential-algorithm=es512 "$IMAGE") +(! systemd-cryptenroll --tpm2-public-key-pcrs=key "$IMAGE") +(! systemd-cryptenroll --tpm2-pcrs=key "$IMAGE") +(! systemd-cryptenroll --tpm2-pcrs=44+8 "$IMAGE") +(! systemd-cryptenroll --tpm2-pcrs=hello "$IMAGE") +(! systemd-cryptenroll --wipe-slot "$IMAGE") +(! systemd-cryptenroll --wipe-slot=10240000 "$IMAGE") +(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto "$IMAGE") + +rm -f "$IMAGE" diff --git a/test/units/testsuite-70.cryptsetup.sh b/test/units/testsuite-70.cryptsetup.sh new file mode 100755 index 0000000..4cd627f --- /dev/null +++ b/test/units/testsuite-70.cryptsetup.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +cryptsetup_has_token_plugin_support() { + local plugin_path + + plugin_path="$(cryptsetup --help | sed -nr 's/.*LUKS2 external token plugin path: (.*)\./\1/p')/libcryptsetup-token-systemd-tpm2.so)" + cryptsetup --help | grep -q 'LUKS2 external token plugin support is compiled-in' && [[ -f "$plugin_path" ]] +} + +tpm_check_failure_with_wrong_pin() { + local testIMAGE="${1:?}" + local badpin="${2:?}" + local goodpin="${3:?}" + + # We need to be careful not to trigger DA lockout; allow 2 failures + tpm2_dictionarylockout -s -n 2 + (! PIN=$badpin systemd-cryptsetup attach test-volume "$testIMAGE" - tpm2-device=auto,headless=1) + # Verify the correct PIN works, to be sure the failure wasn't a DA lockout + PIN=$goodpin systemd-cryptsetup attach test-volume "$testIMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + # Clear/reset the DA lockout counter + tpm2_dictionarylockout -c +} + +at_exit() { + # Evict the TPM primary key that we persisted + if [[ -n "${PERSISTENT_HANDLE:-}" ]]; then + tpm2_evictcontrol -c "$PERSISTENT_HANDLE" + fi +} + +trap at_exit EXIT + +# Prepare a fresh disk image +IMAGE="$(mktemp /tmp/systemd-cryptsetup-XXX.IMAGE)" + +truncate -s 20M "$IMAGE" +echo -n passphrase >/tmp/passphrase +# Change file mode to avoid "/tmp/passphrase has 0644 mode that is too permissive" messages +chmod 0600 /tmp/passphrase +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase + +# Unlocking via keyfile +systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto "$IMAGE" + +# Enroll unlock with default PCR policy +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Check with wrong PCR +tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000 +(! systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) + +# Enroll unlock with PCR+PIN policy +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true "$IMAGE" +PIN=123456 systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Check failure with wrong PIN; try a few times to make sure we avoid DA lockout +for _ in {0..3}; do + tpm_check_failure_with_wrong_pin "$IMAGE" 123457 123456 +done + +# Check LUKS2 token plugin unlock (i.e. without specifying tpm2-device=auto) +if cryptsetup_has_token_plugin_support; then + PIN=123456 systemd-cryptsetup attach test-volume "$IMAGE" - headless=1 + systemd-cryptsetup detach test-volume + + # Check failure with wrong PIN + for _ in {0..3}; do + tpm_check_failure_with_wrong_pin "$IMAGE" 123457 123456 + done +else + echo 'cryptsetup has no LUKS2 token plugin support, skipping' +fi + +# Check failure with wrong PCR (and correct PIN) +tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000 +(! PIN=123456 systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) + +# Enroll unlock with PCR 0+7 +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Check with wrong PCR 0 +tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 +(! systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) + +if tpm_has_pcr sha256 12; then + # Enroll using an explicit PCR value (that does match current PCR value) + systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + + # Same as above plus more PCRs without the value or alg specified + systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + + # Same as above plus more PCRs with hash alg specified but hash value not specified + systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" + EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$IMAGE" + systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + + # Now the interesting part, enrolling using a hash value that doesn't match the current PCR value + systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" + tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 + CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + EXPECTED_PCR_VALUE=$(cat /tmp/pcr.dat /tmp/pcr.dat | openssl dgst -sha256 -r | cut -d ' ' -f 1) + PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$IMAGE" + (! systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1) + tpm2_pcrextend "12:sha256=$CURRENT_PCR_VALUE" + systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + + # enroll TPM using device key instead of direct access, then verify unlock using TPM + tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 + CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) + tpm2_readpublic -c 0x81000001 -o /tmp/srk.pub + systemd-analyze srk > /tmp/srk2.pub + cmp /tmp/srk.pub /tmp/srk2.pub + if [ -f /run/systemd/tpm2-srk-public-key.tpm2b_public ] ; then + cmp /tmp/srk.pub /run/systemd/tpm2-srk-public-key.tpm2b_public + fi + + # --tpm2-device-key= requires OpenSSL >= 3 with KDF-SS + if openssl_supports_kdf SSKDF; then + PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" + systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 + systemd-cryptsetup detach test-volume + fi + + rm -f /tmp/pcr.dat /tmp/srk.pub +fi + +# Use default (0) seal key handle +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0 "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x0 "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Use SRK seal key handle +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=81000001 "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x81000001 "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# Test invalid ranges: pcr, nv, session, permanent +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=7 "$IMAGE") # PCR +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x01000001 "$IMAGE") # NV index +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x02000001 "$IMAGE") # HMAC/loaded session +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x03000001 "$IMAGE") # Policy/saved session +(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x40000001 "$IMAGE") # Permanent + +# Use non-SRK persistent seal key handle (by creating/persisting new key) +PRIMARY=/tmp/primary.ctx +tpm2_createprimary -c "$PRIMARY" +PERSISTENT_LINE=$(tpm2_evictcontrol -c "$PRIMARY" | grep persistent-handle) +PERSISTENT_HANDLE="0x${PERSISTENT_LINE##*0x}" +tpm2_flushcontext -t + +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${PERSISTENT_HANDLE#0x}" "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$PERSISTENT_HANDLE" "$IMAGE" +systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 +systemd-cryptsetup detach test-volume + +# --tpm2-device-key= requires OpenSSL >= 3 with KDF-SS +if openssl_supports_kdf SSKDF; then + # Make sure that --tpm2-device-key= also works with systemd-repart + tpm2_readpublic -c 0x81000001 -o /tmp/srk.pub + mkdir /tmp/dditest + cat > /tmp/dditest/50-root.conf </tmp/tpmdata1 +echo foobar >/tmp/tpmdata2 + +cat >/tmp/result </tmp/result.json </tmp/result </tmp/result.json </dev/null; then + MEASURE_BANKS+=("--bank=sha1") +fi + +# Sign current PCR state with it +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: | tee "/tmp/pcrsign.sig" +dd if=/dev/urandom of=/tmp/pcrtestdata bs=1024 count=64 +systemd-creds encrypt /tmp/pcrtestdata /tmp/pcrtestdata.encrypted --with-key=host+tpm2-with-public-key --tpm2-public-key="/tmp/pcrsign-public.pem" +systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" | cmp - /tmp/pcrtestdata + +# Invalidate PCR, decrypting should fail now +tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000 +(! systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig" >/dev/null) + +# Sign new PCR state, decrypting should work now. +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig2" +systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata + +# Now, do the same, but with a cryptsetup binding +truncate -s 20M "$IMAGE" +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase +# Ensure that an unrelated signature, when not requested, is not used +touch /run/systemd/tpm2-pcr-signature.json +systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" "$IMAGE" +# Reset and use the signature now +rm -f /run/systemd/tpm2-pcr-signature.json +systemd-cryptenroll --wipe-slot=tpm2 "$IMAGE" +systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-signature="/tmp/pcrsign.sig2" "$IMAGE" + +# Check if we can activate that (without the token module stuff) +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup detach test-volume2 + +# Check if we can activate that (and a second time with the token module stuff enabled) +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup detach test-volume2 + +# After extending the PCR things should fail +tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000 +(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1) +(! SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1) + +# But once we sign the current PCRs, we should be able to unlock again +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: >"/tmp/pcrsign.sig3" +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1 +systemd-cryptsetup detach test-volume2 +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1 +systemd-cryptsetup detach test-volume2 + +# Test --append mode and de-duplication. With the same parameters signing should not add a new entry +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: --append="/tmp/pcrsign.sig3" >"/tmp/pcrsign.sig4" +cmp "/tmp/pcrsign.sig3" "/tmp/pcrsign.sig4" + +# Sign one more phase, this should +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig4" >"/tmp/pcrsign.sig5" +(! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5") + +# Should still be good to unlock, given the old entry still exists +SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach test-volume2 "$IMAGE" - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig5",headless=1 +systemd-cryptsetup detach test-volume2 + +# Adding both signatures once more should not change anything, due to the deduplication +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: --append="/tmp/pcrsign.sig5" >"/tmp/pcrsign.sig6" +"$SD_MEASURE" sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig6" >"/tmp/pcrsign.sig7" +cmp "/tmp/pcrsign.sig5" "/tmp/pcrsign.sig7" + +rm -f "$IMAGE" diff --git a/test/units/testsuite-70.pcrextend.sh b/test/units/testsuite-70.pcrextend.sh new file mode 100755 index 0000000..318fce0 --- /dev/null +++ b/test/units/testsuite-70.pcrextend.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug +SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend" + +if [[ ! -x "${SD_PCREXTEND:?}" ]] || ! tpm_has_pcr sha256 11 || ! tpm_has_pcr sha256 15; then + echo "$SD_PCREXTEND or PCR sysfs files not found, skipping PCR extension tests" + exit 0 +fi + +at_exit() { + if [[ $? -ne 0 ]]; then + # Dump the event log on fail, to make debugging a bit easier + jq --seq --slurp /etc/machine-id +SYSTEMD_FORCE_MEASURE=1 "$SD_PCREXTEND" --machine-id +mv /etc/machine-id.save /etc/machine-id +tpm2_pcrread sha256:15 -Q -o /tmp/newpcr15 + +# And check it matches expectations +diff /tmp/newpcr15 \ + <(cat /tmp/oldpcr15 <(echo -n "machine-id:994013bf23864ee7992eab39a96dd3bb" | openssl dgst -binary -sha256) | openssl dgst -binary -sha256) + +# Check that the event log record was properly written +test "$(jq --seq --slurp ".[$RECORD_COUNT].pcr" /run/systemd/system/systemd-pcrextend.socket.d/50-no-condition.conf </tmp/borked +set +e +SYSTEMD_MEASURE_LOG_USERSPACE=/tmp/borked "$SD_PCRLOCK" cel --no-pager --json=pretty +ret=$? +set -e +# If it crashes the exit code will be 149 +test $ret -eq 1 + +SYSTEMD_COLORS=256 "$SD_PCRLOCK" +"$SD_PCRLOCK" cel --no-pager --json=pretty +"$SD_PCRLOCK" log --pcr="$PCRS" +"$SD_PCRLOCK" log --json=pretty --pcr="$PCRS" +"$SD_PCRLOCK" list-components +"$SD_PCRLOCK" list-components --location=250- +"$SD_PCRLOCK" list-components --location=250-:350- +"$SD_PCRLOCK" lock-firmware-config +"$SD_PCRLOCK" lock-gpt +"$SD_PCRLOCK" lock-machine-id +"$SD_PCRLOCK" lock-file-system +"$SD_PCRLOCK" lock-file-system / +"$SD_PCRLOCK" predict --pcr="$PCRS" +"$SD_PCRLOCK" predict --pcr="0x1+0x3+4" +"$SD_PCRLOCK" predict --json=pretty --pcr="$PCRS" + +SD_STUB="$(find /usr/lib/systemd/boot/efi/ -name "systemd-boot*.efi" | head -n1)" +if [[ -n "$SD_STUB" ]]; then + "$SD_PCRLOCK" lock-pe "$SD_STUB" + "$SD_PCRLOCK" lock-pe <"$SD_STUB" + "$SD_PCRLOCK" lock-uki "$SD_STUB" + "$SD_PCRLOCK" lock-uki <"$SD_STUB" +fi + +PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes +# Repeat immediately (this call will have to reuse the nvindex, rather than create it) +"$SD_PCRLOCK" make-policy --pcr="$PCRS" +"$SD_PCRLOCK" make-policy --pcr="$PCRS" --force + +img="/tmp/pcrlock.img" +truncate -s 20M "$img" +echo -n hoho >/tmp/pcrlockpwd +chmod 0600 /tmp/pcrlockpwd +cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$img" /tmp/pcrlockpwd + +systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --tpm2-public-key= --wipe-slot=tpm2 "$img" +systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless +systemd-cryptsetup detach pcrlock + +# Measure something into PCR 16 (the "debug" PCR), which should make the activation fail +"$SD_PCREXTEND" --pcr=16 test70 + +"$SD_PCRLOCK" cel --json=pretty + +(! systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless ) + +# Now add a component for it, rebuild policy and it should work (we'll rebuild +# once like that, but don't provide the recovery pin. This should fail, since +# the PCR is hosed after all. But then we'll use recovery pin, and it should +# work. +echo -n test70 | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/910-test70.pcrlock --pcr=16 +(! "$SD_PCRLOCK" make-policy --pcr="$PCRS") +PIN=huhu "$SD_PCRLOCK" make-policy --pcr="$PCRS" --recovery-pin=yes + +systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless +systemd-cryptsetup detach pcrlock + +# And now let's do it the clean way, and generate the right policy ahead of time. +echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/920-test70.pcrlock --pcr=16 +"$SD_PCRLOCK" make-policy --pcr="$PCRS" + +"$SD_PCREXTEND" --pcr=16 test70-take-two + +"$SD_PCRLOCK" cel --json=pretty + +systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless +systemd-cryptsetup detach pcrlock + +"$SD_PCRLOCK" remove-policy + +"$SD_PCRLOCK" unlock-firmware-config +"$SD_PCRLOCK" unlock-gpt +"$SD_PCRLOCK" unlock-machine-id +"$SD_PCRLOCK" unlock-file-system +"$SD_PCRLOCK" unlock-raw --pcrlock=/var/lib/pcrlock.d/910-test70.pcrlock +"$SD_PCRLOCK" unlock-raw --pcrlock=/var/lib/pcrlock.d/920-test70.pcrlock + +(! "$SD_PCRLOCK" "") +(! "$SD_PCRLOCK" predict --pcr=-1) +(! "$SD_PCRLOCK" predict --pcr=foo) +(! "$SD_PCRLOCK" predict --pcr=1+1) +(! "$SD_PCRLOCK" predict --pcr=1+++++1) +(! "$SD_PCRLOCK" make-policy --nv-index=0) +(! "$SD_PCRLOCK" make-policy --nv-index=foo) +(! "$SD_PCRLOCK" list-components --location=:) +(! "$SD_PCRLOCK" lock-gpt "") +(! "$SD_PCRLOCK" lock-gpt /dev/sr0) +(! "$SD_PCRLOCK" lock-pe /dev/full) +(! "$SD_PCRLOCK" lock-pe /bin/true) +(! "$SD_PCRLOCK" lock-uki /dev/full) +(! "$SD_PCRLOCK" lock-uki /bin/true) +(! "$SD_PCRLOCK" lock-file-system "") + +rm "$img" /tmp/pcrlockpwd diff --git a/test/units/testsuite-70.service b/test/units/testsuite-70.service new file mode 100644 index 0000000..c13c2d5 --- /dev/null +++ b/test/units/testsuite-70.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-70-TPM2 + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh new file mode 100755 index 0000000..9c2a033 --- /dev/null +++ b/test/units/testsuite-70.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok diff --git a/test/units/testsuite-70.tpm2-setup.sh b/test/units/testsuite-70.tpm2-setup.sh new file mode 100755 index 0000000..faf6fe7 --- /dev/null +++ b/test/units/testsuite-70.tpm2-setup.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +export SYSTEMD_LOG_LEVEL=debug +SD_TPM2SETUP="/usr/lib/systemd/systemd-tpm2-setup" + +if [[ ! -x "${SD_TPM2SETUP:?}" ]]; then + echo "$SD_TPM2SETUP not found, skipping the test" + exit 0 +fi + +"$SD_TPM2SETUP" --help +"$SD_TPM2SETUP" --version +"$SD_TPM2SETUP" --tpm2-device=list +"$SD_TPM2SETUP" --tpm2-device=auto +"$SD_TPM2SETUP" --tpm2-device=/dev/tpm0 +"$SD_TPM2SETUP" --early=yes +"$SD_TPM2SETUP" --early=yes +"$SD_TPM2SETUP" --early=no +"$SD_TPM2SETUP" --early=no + +(! "$SD_TPM2SETUP" "") +(! "$SD_TPM2SETUP" --tpm2-device=) +(! "$SD_TPM2SETUP" --tpm2-device=/dev/null) +(! "$SD_TPM2SETUP" --foo=bar) diff --git a/test/units/testsuite-71.service b/test/units/testsuite-71.service new file mode 100644 index 0000000..1718629 --- /dev/null +++ b/test/units/testsuite-71.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-71-HOSTNAME + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-71.sh b/test/units/testsuite-71.sh new file mode 100755 index 0000000..da765a9 --- /dev/null +++ b/test/units/testsuite-71.sh @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later + +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +restore_hostname() { + if [[ -e /tmp/hostname.bak ]]; then + mv /tmp/hostname.bak /etc/hostname + else + rm -f /etc/hostname + fi +} + +testcase_hostname() { + local orig= + + if [[ -f /etc/hostname ]]; then + cp /etc/hostname /tmp/hostname.bak + orig=$(cat /etc/hostname) + fi + + trap restore_hostname RETURN + + # should activate daemon and work + if [[ -n "$orig" ]]; then + assert_in "Static hostname: $orig" "$(hostnamectl)" + fi + assert_in "Kernel: $(uname -s) $(uname -r)" "$(hostnamectl)" + + # change hostname + assert_rc 0 hostnamectl set-hostname testhost + assert_eq "$(cat /etc/hostname)" "testhost" + assert_in "Static hostname: testhost" "$(hostnamectl)" + + if [[ -n "$orig" ]]; then + # reset to original + assert_rc 0 hostnamectl set-hostname "$orig" + assert_eq "$(cat /etc/hostname)" "$orig" + assert_in "Static hostname: $orig" "$(hostnamectl)" + fi +} + +restore_machine_info() { + if [[ -e /tmp/machine-info.bak ]]; then + mv /tmp/machine-info.bak /etc/machine-info + else + rm -f /etc/machine-info + fi +} + +get_chassis() ( + # shellcheck source=/dev/null + . /etc/machine-info + + echo "$CHASSIS" +) + +testcase_chassis() { + local i + + if [[ -f /etc/machine-info ]]; then + cp /etc/machine-info /tmp/machine-info.bak + fi + + trap restore_machine_info RETURN + + # Invalid chassis type is refused + assert_rc 1 hostnamectl chassis hoge + + # Valid chassis types + for i in vm container desktop laptop convertible server tablet handset watch embedded; do + hostnamectl chassis "$i" + assert_eq "$(hostnamectl chassis)" "$i" + assert_eq "$(get_chassis)" "$i" + done + + systemctl stop systemd-hostnamed.service + rm -f /etc/machine-info + + # fallback chassis type + if systemd-detect-virt --quiet --container; then + assert_eq "$(hostnamectl chassis)" container + elif systemd-detect-virt --quiet --vm; then + assert_eq "$(hostnamectl chassis)" vm + fi +} + +restore_sysfs_dmi() { + umount /sys/class/dmi/id + rm -rf /run/systemd/system/systemd-hostnamed.service.d + systemctl daemon-reload + systemctl stop systemd-hostnamed +} + +testcase_firmware_date() { + # No DMI on s390x or ppc + if [[ ! -d /sys/class/dmi/id ]]; then + echo "/sys/class/dmi/id not found, skipping firmware date tests." + return 0 + fi + + trap restore_sysfs_dmi RETURN + + # Ignore /sys being mounted as tmpfs + mkdir -p /run/systemd/system/systemd-hostnamed.service.d/ + cat >/run/systemd/system/systemd-hostnamed.service.d/override.conf </sys/class/dmi/id/uevent + + echo '09/08/2000' >/sys/class/dmi/id/bios_date + systemctl stop systemd-hostnamed + assert_in '2000-09-08' "$(hostnamectl)" + + echo '2022' >/sys/class/dmi/id/bios_date + systemctl stop systemd-hostnamed + assert_not_in 'Firmware Date' "$(hostnamectl)" + + echo 'garbage' >/sys/class/dmi/id/bios_date + systemctl stop systemd-hostnamed + assert_not_in 'Firmware Date' "$(hostnamectl)" +} + +testcase_nss-myhostname() { + local database host i + + HOSTNAME="$(hostnamectl hostname)" + + # Set up a dummy network for _gateway and _outbound labels + ip link add foo type dummy + ip link set up dev foo + ip addr add 10.0.0.2/24 dev foo + for i in {128..150}; do + ip addr add "10.0.0.$i/24" dev foo + done + ip route add 10.0.0.1 dev foo + ip route add default via 10.0.0.1 dev foo + + # Note: `getent hosts` probes gethostbyname2(), whereas `getent ahosts` probes gethostbyname3() + # and gethostbyname4() (through getaddrinfo() -> gaih_inet() -> get_nss_addresses()) + getent hosts -s myhostname + getent ahosts -s myhostname + + # With IPv6 disabled + sysctl -w net.ipv6.conf.all.disable_ipv6=1 + # Everything under .localhost and .localhost.localdomain should resolve to localhost + for host in {foo.,foo.bar.baz.,.,}localhost{,.} {foo.,foo.bar.baz.,.,}localhost.localdomain{,.}; do + run_and_grep "^127\.0\.0\.1\s+localhost$" getent hosts -s myhostname "$host" + run_and_grep "^127\.0\.0\.1\s+STREAM\s+localhost" getent ahosts -s myhostname "$host" + run_and_grep "^127\.0\.0\.1\s+STREAM\s+localhost" getent ahostsv4 -s myhostname "$host" + (! getent ahostsv6 -s myhostname localhost) + done + for i in 2 {128..150}; do + run_and_grep "^10\.0\.0\.$i\s+$HOSTNAME$" getent hosts -s myhostname "$HOSTNAME" + run_and_grep "^10\.0\.0\.$i\s+" getent ahosts -s myhostname "$HOSTNAME" + run_and_grep "^10\.0\.0\.$i\s+" getent ahostsv4 -s myhostname "$HOSTNAME" + run_and_grep "^10\.0\.0\.$i\s+$HOSTNAME$" getent hosts -s myhostname "10.0.0.$i" + run_and_grep "^10\.0\.0\.$i\s+STREAM\s+10\.0\.0\.$i$" getent ahosts -s myhostname "10.0.0.$i" + run_and_grep "^10\.0\.0\.$i\s+STREAM\s+10\.0\.0\.$i$" getent ahostsv4 -s myhostname "10.0.0.$i" + done + for database in hosts ahosts ahostsv4 ahostsv6; do + (! getent "$database" -s myhostname ::1) + done + (! getent ahostsv6 -s myhostname "$HOSTNAME") + run_and_grep -n "^fe80:[^ ]+\s+STREAM$" getent ahosts -s myhostname "$HOSTNAME" + + # With IPv6 enabled + sysctl -w net.ipv6.conf.all.disable_ipv6=0 + # Everything under .localhost and .localhost.localdomain should resolve to localhost + for host in {foo.,foo.bar.baz.,.,}localhost{,.} {foo.,foo.bar.baz.,.,}localhost.localdomain{,.}; do + run_and_grep "^::1\s+localhost$" getent hosts -s myhostname "$host" + run_and_grep "^::1\s+STREAM" getent ahosts -s myhostname "$host" + run_and_grep "^127\.0\.0\.1\s+STREAM" getent ahosts -s myhostname "$host" + run_and_grep "^127\.0\.0\.1\s+STREAM" getent ahostsv4 -s myhostname "$host" + run_and_grep -n "^::1\s+STREAM" getent ahostsv4 -s myhostname "$host" + run_and_grep "^::1\s+STREAM" getent ahostsv6 -s myhostname "$host" + run_and_grep -n "^127\.0\.0\.1\s+STREAM" getent ahostsv6 -s myhostname "$host" + done + for i in 2 {128..150}; do + run_and_grep "^10\.0\.0\.$i\s+" getent ahosts -s myhostname "$HOSTNAME" + run_and_grep "^10\.0\.0\.$i\s+" getent ahostsv4 -s myhostname "$HOSTNAME" + run_and_grep "^10\.0\.0\.$i\s+STREAM\s+10\.0\.0\.$i$" getent ahosts -s myhostname "10.0.0.$i" + run_and_grep "^10\.0\.0\.$i\s+STREAM\s+10\.0\.0\.$i$" getent ahostsv4 -s myhostname "10.0.0.$i" + done + run_and_grep "^fe80:[^ ]+\s+$HOSTNAME$" getent hosts -s myhostname "$HOSTNAME" + run_and_grep "^fe80:[^ ]+\s+STREAM" getent ahosts -s myhostname "$HOSTNAME" + run_and_grep "^127\.0\.0\.1\s+localhost$" getent hosts -s myhostname 127.0.0.1 + run_and_grep "^127\.0\.0\.1\s+STREAM\s+127\.0\.0\.1$" getent ahosts -s myhostname 127.0.0.1 + run_and_grep "^::ffff:127\.0\.0\.1\s+STREAM\s+127\.0\.0\.1$" getent ahostsv6 -s myhostname 127.0.0.1 + run_and_grep "^127\.0\.0\.2\s+$HOSTNAME$" getent hosts -s myhostname 127.0.0.2 + run_and_grep "^::1\s+localhost $HOSTNAME$" getent hosts -s myhostname ::1 + run_and_grep "^::1\s+STREAM\s+::1$" getent ahosts -s myhostname ::1 + (! getent ahostsv4 -s myhostname ::1) + + # _gateway + for host in _gateway{,.} 10.0.0.1; do + run_and_grep "^10\.0\.0\.1\s+_gateway$" getent hosts -s myhostname "$host" + run_and_grep "^10\.0\.0\.1\s+STREAM" getent ahosts -s myhostname "$host" + done + + # _outbound + for host in _outbound{,.} 10.0.0.2; do + run_and_grep "^10\.0\.0\.2\s+" getent hosts -s myhostname "$host" + run_and_grep "^10\.0\.0\.2\s+STREAM" getent ahosts -s myhostname "$host" + done + + # Non-existent records + for database in hosts ahosts ahostsv4 ahostsv6; do + (! getent "$database" -s myhostname this.should.not.exist) + done + (! getent hosts -s myhostname 10.254.254.1) + (! getent hosts -s myhostname fd00:dead:beef:cafe::1) +} + +run_testcases + +touch /testok diff --git a/test/units/testsuite-72.service b/test/units/testsuite-72.service new file mode 100644 index 0000000..1640350 --- /dev/null +++ b/test/units/testsuite-72.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-72-SYSUPDATE + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-72.sh b/test/units/testsuite-72.sh new file mode 100755 index 0000000..953f2a1 --- /dev/null +++ b/test/units/testsuite-72.sh @@ -0,0 +1,278 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +set -eux +set -o pipefail + +SYSUPDATE=/lib/systemd/systemd-sysupdate +SECTOR_SIZES="512 4096" +BACKING_FILE=/var/tmp/72-joined.raw +export SYSTEMD_ESP_PATH=/var/tmp/72-esp +export SYSTEMD_XBOOTLDR_PATH=/var/tmp/72-xbootldr +export SYSTEMD_PAGER=cat +export SYSTEMD_LOG_LEVEL=debug + +if ! test -x "$SYSUPDATE"; then + echo "no systemd-sysupdate" >/skipped + exit 0 +fi + +# Loopback devices may not be supported. They are used because sfdisk cannot +# change the sector size of a file, and we want to test both 512 and 4096 byte +# sectors. If loopback devices are not supported, we can only test one sector +# size, and the underlying device is likely to have a sector size of 512 bytes. +if ! losetup --find >/dev/null 2>&1; then + echo "No loopback device support" + SECTOR_SIZES="512" +fi + +trap cleanup ERR +cleanup() { + set +o pipefail + blockdev="$( losetup --list --output NAME,BACK-FILE | grep $BACKING_FILE | cut -d' ' -f1)" + [ -n "$blockdev" ] && losetup --detach "$blockdev" + rm -f "$BACKING_FILE" + rm -rf /var/tmp/72-{dirs,defs,source,xbootldr,esp} + rm -f /testok +} + +new_version() { + # Inputs: + # $1: sector size + # $2: version + + # Create a pair of random partition payloads, and compress one + dd if=/dev/urandom of="/var/tmp/72-source/part1-$2.raw" bs="$1" count=2048 + dd if=/dev/urandom of="/var/tmp/72-source/part2-$2.raw" bs="$1" count=2048 + gzip -k -f "/var/tmp/72-source/part2-$2.raw" + + # Create a random "UKI" payload + echo $RANDOM >"/var/tmp/72-source/uki-$2.efi" + + # Create a random extra payload + echo $RANDOM >"/var/tmp/72-source/uki-extra-$2.efi" + + # Create tarball of a directory + mkdir -p "/var/tmp/72-source/dir-$2" + echo $RANDOM >"/var/tmp/72-source/dir-$2/foo.txt" + echo $RANDOM >"/var/tmp/72-source/dir-$2/bar.txt" + tar --numeric-owner -C "/var/tmp/72-source/dir-$2/" -czf "/var/tmp/72-source/dir-$2.tar.gz" . + + ( cd /var/tmp/72-source/ && sha256sum uki* part* dir-*.tar.gz >SHA256SUMS ) +} + +update_now() { + # Update to newest version. First there should be an update ready, then we + # do the update, and then there should not be any ready anymore + + "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new + "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no update + ( ! "$SYSUPDATE" --definitions=/var/tmp/72-defs --verify=no check-new ) +} + +verify_version() { + # Inputs: + # $1: block device + # $2: sector size + # $3: version + # $4: partition number of part1 + # $5: partition number of part2 + + gpt_reserved_sectors=$(( 1024 * 1024 / $2 )) + part1_offset=$(( ( $4 - 1 ) * 2048 + gpt_reserved_sectors )) + part2_offset=$(( ( $5 - 1 ) * 2048 + gpt_reserved_sectors )) + + # Check the partitions + dd if="$1" bs="$2" skip="$part1_offset" count=2048 | cmp "/var/tmp/72-source/part1-$3.raw" + dd if="$1" bs="$2" skip="$part2_offset" count=2048 | cmp "/var/tmp/72-source/part2-$3.raw" + + # Check the UKI + cmp "/var/tmp/72-source/uki-$3.efi" "/var/tmp/72-xbootldr/EFI/Linux/uki_$3+3-0.efi" + test -z "$(ls -A /var/tmp/72-esp/EFI/Linux)" + + # Check the extra efi + cmp "/var/tmp/72-source/uki-extra-$3.efi" "/var/tmp/72-xbootldr/EFI/Linux/uki_$3.efi.extra.d/extra.addon.efi" + + # Check the directories + cmp "/var/tmp/72-source/dir-$3/foo.txt" /var/tmp/72-dirs/current/foo.txt + cmp "/var/tmp/72-source/dir-$3/bar.txt" /var/tmp/72-dirs/current/bar.txt +} + +for sector_size in $SECTOR_SIZES ; do + # Disk size of: + # - 1MB for GPT + # - 4 partitions of 2048 sectors each + # - 1MB for backup GPT + disk_size=$(( sector_size * 2048 * 4 + 1024 * 1024 * 2 )) + rm -f "$BACKING_FILE" + truncate -s "$disk_size" "$BACKING_FILE" + + if losetup --find >/dev/null 2>&1; then + # shellcheck disable=SC2086 + blockdev="$(losetup --find --show --sector-size $sector_size $BACKING_FILE)" + else + blockdev="$BACKING_FILE" + fi + + sfdisk "$blockdev" </var/tmp/72-defs/01-first.conf </var/tmp/72-defs/02-second.conf </var/tmp/72-defs/03-third.conf </var/tmp/72-defs/04-fourth.conf </var/tmp/72-defs/05-fifth.conf </var/tmp/72-defs/02-second.conf </var/tmp/72-defs/03-third.conf <>/run/systemd/system/systemd-localed.service.d/override.conf <>/run/systemd/system/systemd-vconsole-setup.service.d/override.conf </dev/null 2>&1 && + ! localectl list-locales | grep -F "en_US.UTF-8"; then + # ensure at least one utf8 locale exist + echo "en_US.UTF-8 UTF-8" >/etc/locale.gen + locale-gen en_US.UTF-8 + fi + + # create invalid locale + mkdir -p /usr/lib/locale/xx_XX.UTF-8 + assert_not_in "xx_XX.UTF-8" "$(localectl list-locales)" + + if [[ -z "$(localectl list-locales)" ]]; then + echo "No locale installed, skipping test." + return + fi + + # start with a known default environment and make sure to also give a + # default value to LC_CTYPE= since we're about to also set/unset it. We + # also reload PID1 configuration to make sure that PID1 environment itself + # is updated as it's not always been the case. + assert_rc 0 localectl set-locale "LANG=en_US.UTF-8" "LC_CTYPE=C" + systemctl daemon-reload + output=$(localectl) + assert_in "System Locale: LANG=en_US.UTF-8" "$output" + assert_in "LC_CTYPE=C" "$output" + output=$(systemctl show-environment) + assert_in "LANG=en_US.UTF-8" "$output" + assert_in "LC_CTYPE=C" "$output" + + # warn when kernel command line has locale settings + output=$(SYSTEMD_PROC_CMDLINE="locale.LANG=C.UTF-8 locale.LC_CTYPE=ja_JP.UTF-8" localectl 2>&1) + assert_in "Warning:" "$output" + assert_in "Command Line: LANG=C.UTF-8" "$output" + assert_in "LC_CTYPE=ja_JP.UTF-8" "$output" + assert_in "System Locale:" "$output" + + # change locale + for i in $(localectl list-locales); do + assert_rc 0 localectl set-locale "LANG=C" "LC_CTYPE=$i" + if [[ -f /etc/default/locale ]]; then + assert_eq "$(cat /etc/default/locale)" "LANG=C +LC_CTYPE=$i" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=C +LC_CTYPE=$i" + fi + output=$(localectl) + assert_in "System Locale: LANG=C" "$output" + assert_in "LC_CTYPE=$i" "$output" + output=$(systemctl show-environment) + assert_in "LANG=C" "$output" + assert_in "LC_CTYPE=$i" "$output" + + assert_rc 0 localectl set-locale "$i" + if [[ -f /etc/default/locale ]]; then + assert_eq "$(cat /etc/default/locale)" "LANG=$i" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=$i" + fi + output=$(localectl) + assert_in "System Locale: LANG=$i" "$output" + assert_not_in "LC_CTYPE=" "$output" + output=$(systemctl show-environment) + assert_in "LANG=$i" "$output" + assert_not_in "LC_CTYPE=" "$output" + done + + # test if localed auto-runs locale-gen + if command -v locale-gen >/dev/null 2>&1 && + ! localectl list-locales | grep -F "de_DE.UTF-8"; then + + # clear previous locale + systemctl stop systemd-localed.service + rm -f /etc/locale.conf /etc/default/locale + + # change locale + assert_rc 0 localectl set-locale de_DE.UTF-8 + if [[ -f /etc/default/locale ]]; then + assert_eq "$(cat /etc/default/locale)" "LANG=de_DE.UTF-8" + else + assert_eq "$(cat /etc/locale.conf)" "LANG=de_DE.UTF-8" + fi + assert_in "System Locale: LANG=de_DE.UTF-8" "$(localectl)" + assert_in "LANG=de_DE.UTF-8" "$(systemctl show-environment)" + + # ensure tested locale exists and works now + assert_in "de_DE.UTF-8" "$(localectl list-locales)" + fi +} + +backup_keymap() { + if [[ -f /etc/vconsole.conf ]]; then + cp /etc/vconsole.conf /tmp/vconsole.conf.bak + fi + + if [[ -f /etc/X11/xorg.conf.d/00-keyboard.conf ]]; then + cp /etc/X11/xorg.conf.d/00-keyboard.conf /tmp/00-keyboard.conf.bak + fi + + # Debian/Ubuntu specific file + if [[ -f /etc/default/keyboard ]]; then + cp /etc/default/keyboard /tmp/default-keyboard.bak + fi + + mkdir -p /etc/default +} + +restore_keymap() { + if [[ -f /tmp/vconsole.conf.bak ]]; then + mv /tmp/vconsole.conf.bak /etc/vconsole.conf + else + rm -f /etc/vconsole.conf + fi + + if [[ -f /tmp/00-keyboard.conf.bak ]]; then + mv /tmp/00-keyboard.conf.bak /etc/X11/xorg.conf.d/00-keyboard.conf + else + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf + fi + + if [[ -f /tmp/default-keyboard.bak ]]; then + mv /tmp/default-keyboard.bak /etc/default/keyboard + else + rm -f /etc/default/keyboard + rmdir --ignore-fail-on-non-empty /etc/default + fi +} + +wait_vconsole_setup() { + local i ss + for i in {1..20}; do + (( i > 1 )) && sleep 0.5 + ss="$(systemctl --property SubState --value show systemd-vconsole-setup.service)" + if [[ "$ss" == "exited" || "$ss" == "dead" || "$ss" == "condition" ]]; then + return 0 + elif [[ "$ss" == "failed" ]]; then + echo "WARNING: systemd-vconsole-setup.service failed, ignoring." >&2 + systemctl reset-failed systemd-vconsole-setup.service + return 0 + fi + done + + systemctl status systemd-vconsole-setup.service + return 1 +} + +testcase_vc_keymap() { + local i output vc + + if [[ -z "$(localectl list-keymaps)" ]]; then + echo "No vconsole keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # should activate daemon and work + assert_in "VC Keymap:" "$(localectl)" + + for i in $(localectl list-keymaps); do + # clear previous conversion from VC -> X11 keymap + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/vconsole.conf /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # set VC keymap + assert_rc 0 localectl set-keymap "$i" + output=$(localectl) + + # check VC keymap + vc=$(cat /etc/vconsole.conf) + assert_in "KEYMAP=$i" "$vc" + assert_in "VC Keymap: $i" "$output" + + # check VC -> X11 keymap conversion + if [[ "$i" == "us" ]]; then + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_not_in "X11 Variant:" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + assert_in "XKBLAYOUT=us" "$vc" + assert_in "XKBMODEL=pc105\+inet" "$vc" + assert_not_in "XKBVARIANT" "$vc" + assert_in "XKBOPTIONS=terminate:ctrl_alt_bksp" "$vc" + elif [[ "$i" == "us-acentos" ]]; then + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105" "$output" + assert_in "X11 Variant: intl" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + assert_in "XKBLAYOUT=us" "$vc" + assert_in "XKBMODEL=pc105" "$vc" + assert_in "XKBVARIANT=intl" "$vc" + assert_in "XKBOPTIONS=terminate:ctrl_alt_bksp" "$vc" + elif [[ "$i" =~ ^us-.* ]]; then + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: microsoftpro" "$output" + assert_in "X11 Variant:" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + assert_in "XKBLAYOUT=us" "$vc" + assert_in "XKBMODEL=microsoftpro" "$vc" + assert_in "XKBVARIANT=" "$vc" + assert_in "XKBOPTIONS=terminate:ctrl_alt_bksp" "$vc" + fi + done + + # gets along without config file + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/vconsole.conf + assert_in "VC Keymap: .unset." "$(localectl)" +} + +testcase_x11_keymap() { + local output + + if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then + echo "No x11 keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # should activate daemon and work + assert_in "X11 Layout:" "$(localectl)" + + # set x11 keymap (layout, model, variant, options) + assert_rc 0 localectl set-x11-keymap us pc105+inet intl terminate:ctrl_alt_bksp + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet +XKBVARIANT=intl +XKBOPTIONS=terminate:ctrl_alt_bksp" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_in 'Option "XkbVariant" "intl"' "$output" + assert_in 'Option "XkbOptions" "terminate:ctrl_alt_bksp"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_in 'XKBMODEL=pc105\+inet' "$output" + assert_in 'XKBVARIANT=intl' "$output" + assert_in 'XKBOPTIONS=terminate:ctrl_alt_bksp' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_in "X11 Variant: intl" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout, model, variant) + assert_rc 0 localectl set-x11-keymap us pc105+inet intl + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet +XKBVARIANT=intl" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_in 'Option "XkbVariant" "intl"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_in 'XKBMODEL=pc105\+inet' "$output" + assert_in 'XKBVARIANT=intl' "$output" + assert_not_in 'XKBOPTIONS' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_in "X11 Variant: intl" "$output" + assert_not_in "X11 Options:" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout, model) + assert_rc 0 localectl set-x11-keymap us pc105+inet + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us +XKBMODEL=pc105+inet" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_in 'Option "XkbModel" "pc105\+inet"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_in 'XKBMODEL=pc105\+inet' "$output" + assert_not_in 'XKBVARIANT' "$output" + assert_not_in 'XKBOPTIONS' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + # Debian/Ubuntu patch is buggy, unspecified settings are not cleared + rm -f /etc/default/keyboard + + # set x11 keymap (layout) + assert_rc 0 localectl set-x11-keymap us + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_not_in 'Option "XkbModel"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_not_in 'XKBMODEL' "$output" + assert_not_in 'XKBVARIANT' "$output" + assert_not_in 'XKBOPTIONS' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + # gets along without config file + systemctl stop systemd-localed.service + rm -f /etc/vconsole.conf /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + output=$(localectl) + assert_in "X11 Layout: .unset." "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" +} + +testcase_convert() { + if [[ -z "$(localectl list-keymaps)" ]]; then + echo "No vconsole keymap installed, skipping test." + return + fi + + if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then + echo "No x11 keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # clear previous settings + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/vconsole.conf /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # set VC keymap without conversion + assert_rc 0 localectl --no-convert set-keymap us + output=$(localectl) + + # check VC keymap + vc=$(cat /etc/vconsole.conf) + assert_in "KEYMAP=us" "$vc" + assert_in "VC Keymap: us" "$output" + + # check VC -> X11 keymap conversion (nothing set) + assert_in "X11 Layout: .unset." "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + assert_not_in "XKBLAYOUT=" "$vc" + assert_not_in "XKBMODEL=" "$vc" + assert_not_in "XKBVARIANT=" "$vc" + assert_not_in "XKBOPTIONS=" "$vc" + + # set VC keymap with conversion + assert_rc 0 localectl set-keymap us + output=$(localectl) + + # check VC keymap + vc=$(cat /etc/vconsole.conf) + assert_in "KEYMAP=us" "$vc" + assert_in "VC Keymap: us" "$output" + + # check VC -> X11 keymap conversion + assert_in "X11 Layout: us" "$output" + assert_in "X11 Model: pc105\+inet" "$output" + assert_not_in "X11 Variant:" "$output" + assert_in "X11 Options: terminate:ctrl_alt_bksp" "$output" + + assert_in "XKBLAYOUT=us" "$vc" + assert_in "XKBMODEL=pc105\+inet" "$vc" + assert_not_in "XKBVARIANT" "$vc" + assert_in "XKBOPTIONS=terminate:ctrl_alt_bksp" "$vc" + + # clear previous settings + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/vconsole.conf /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # set x11 keymap (layout) without conversion + assert_rc 0 localectl --no-convert set-x11-keymap us + + assert_not_in "KEYMAP=" "$(cat /etc/vconsole.conf)" + assert_in "VC Keymap: .unset." "$(localectl)" + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_not_in 'Option "XkbModel"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_not_in 'XKBMODEL=' "$output" + assert_not_in 'XKBVARIANT=' "$output" + assert_not_in 'XKBOPTIONS=' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" + + # set x11 keymap (layout, model) with conversion + assert_rc 0 localectl set-x11-keymap us + + assert_in "KEYMAP=us" "$(cat /etc/vconsole.conf)" + assert_in "VC Keymap: us" "$(localectl)" + + if [[ -f /etc/default/keyboard ]]; then + assert_eq "$(cat /etc/default/keyboard)" "XKBLAYOUT=us" + else + output=$(cat /etc/X11/xorg.conf.d/00-keyboard.conf) + assert_in 'Option "XkbLayout" "us"' "$output" + assert_not_in 'Option "XkbModel"' "$output" + assert_not_in 'Option "XkbVariant"' "$output" + assert_not_in 'Option "XkbOptions"' "$output" + + output=$(cat /etc/vconsole.conf) + assert_in 'XKBLAYOUT=us' "$output" + assert_not_in 'XKBMODEL=' "$output" + assert_not_in 'XKBVARIANT=' "$output" + assert_not_in 'XKBOPTIONS=' "$output" + fi + + output=$(localectl) + assert_in "X11 Layout: us" "$output" + assert_not_in "X11 Model:" "$output" + assert_not_in "X11 Variant:" "$output" + assert_not_in "X11 Options:" "$output" +} + +testcase_validate() { + if [[ -z "$(localectl list-keymaps)" ]]; then + echo "No vconsole keymap installed, skipping test." + return + fi + + if [[ -z "$(localectl list-x11-keymap-layouts)" ]]; then + echo "No x11 keymap installed, skipping test." + return + fi + + backup_keymap + trap restore_keymap RETURN + + # clear previous settings + systemctl stop systemd-localed.service + wait_vconsole_setup + rm -f /etc/X11/xorg.conf.d/00-keyboard.conf /etc/default/keyboard + + # create invalid configs + cat >/etc/vconsole.conf </etc/vconsole.conf </etc/vconsole.conf </dev/null; then + echo "No locale-gen support, skipping test." + return 0 + fi + + [[ -e /etc/locale.gen ]] && cp -f /etc/locale.gen /tmp/locale.gen.bak + trap locale_gen_cleanup RETURN + # Overmount the existing locale-gen database with an empty directory + # to force it to regenerate locales + mount -t tmpfs tmpfs /usr/lib/locale + + { + echo -e "en_US.UTF-8 UTF-8" + echo -e " en_US.UTF-8 UTF-8" + echo -e "\ten_US.UTF-8 UTF-8" + echo -e " \t en_US.UTF-8 UTF-8 \t" + } >/etc/locale.gen + + localectl set-locale de_DE.UTF-8 + localectl set-locale en_US.UTF-8 +} + +# Make sure the content of kbd-model-map is the one that the tests expect +# regardless of the version installed on the distro where the testsuite is +# running on. +export SYSTEMD_KBD_MODEL_MAP=/usr/lib/systemd/tests/testdata/test-keymap-util/kbd-model-map + +enable_debug +run_testcases + +touch /testok diff --git a/test/units/testsuite-74.battery-check.sh b/test/units/testsuite-74.battery-check.sh new file mode 100755 index 0000000..52a92b8 --- /dev/null +++ b/test/units/testsuite-74.battery-check.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +/usr/lib/systemd/systemd-battery-check --help +/usr/lib/systemd/systemd-battery-check --version + +/usr/lib/systemd/systemd-battery-check || : diff --git a/test/units/testsuite-74.bootctl.sh b/test/units/testsuite-74.bootctl.sh new file mode 100755 index 0000000..4be7bfd --- /dev/null +++ b/test/units/testsuite-74.bootctl.sh @@ -0,0 +1,266 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if systemd-detect-virt --quiet --container; then + echo "running on container, skipping." + exit 0 +fi + +if ! command -v bootctl >/dev/null; then + echo "bootctl not found, skipping." + exit 0 +fi + +if [[ ! -d /usr/lib/systemd/boot/efi ]]; then + echo "sd-boot is not installed, skipping." + exit 0 +fi + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +basic_tests() { + bootctl "$@" --help + bootctl "$@" --version + + bootctl "$@" install --make-entry-directory=yes + bootctl "$@" remove --make-entry-directory=yes + + bootctl "$@" install --all-architectures + bootctl "$@" remove --all-architectures + + bootctl "$@" install --make-entry-directory=yes --all-architectures + bootctl "$@" remove --make-entry-directory=yes --all-architectures + + bootctl "$@" install + (! bootctl "$@" update) + bootctl "$@" update --graceful + + bootctl "$@" is-installed + bootctl "$@" is-installed --graceful + bootctl "$@" random-seed + + bootctl "$@" + bootctl "$@" status + bootctl "$@" status --quiet + bootctl "$@" list + bootctl "$@" list --quiet + bootctl "$@" list --json=short + bootctl "$@" list --json=pretty + + bootctl "$@" remove + (! bootctl "$@" is-installed) + (! bootctl "$@" is-installed --graceful) +} + +testcase_bootctl_basic() { + assert_eq "$(bootctl --print-esp-path)" "/efi" + assert_eq "$(bootctl --print-boot-path)" "/boot" + bootctl --print-root-device + + basic_tests +} + +cleanup_image() ( + set +e + + if [[ -z "${IMAGE_DIR:-}" ]]; then + return 0 + fi + + umount "${IMAGE_DIR}/root" + + if [[ -n "${LOOPDEV:-}" ]]; then + losetup -d "${LOOPDEV}" + unset LOOPDEV + fi + + udevadm settle + + rm -rf "${IMAGE_DIR}" + unset IMAGE_DIR + + return 0 +) + +testcase_bootctl_image() { + IMAGE_DIR="$(mktemp --directory /tmp/test-bootctl.XXXXXXXXXX)" + trap cleanup_image RETURN + + truncate -s 256m "${IMAGE_DIR}/image" + + cat >"${IMAGE_DIR}/partscript" </dev/null; then + echo "mdadm not found, skipping." + return 0 + fi + + if ! command -v mkfs.btrfs >/dev/null; then + echo "mkfs.btrfs not found, skipping." + return 0 + fi + + IMAGE_DIR="$(mktemp --directory /tmp/test-bootctl.XXXXXXXXXX)" + trap cleanup_raid RETURN + + truncate -s 256m "${IMAGE_DIR}/image1" + truncate -s 256m "${IMAGE_DIR}/image2" + + cat >"${IMAGE_DIR}/partscript" <"${MAKE_DUMP_SCRIPT:?}" <<\EOF +#!/bin/bash -ex + +bin="${1:?}" +sig="${2:?}" + +ulimit -c unlimited +"$bin" infinity & +pid=$! +# Sync with the "fake" binary, so we kill it once it's fully forked off, +# otherwise we might kill it during fork and kernel would then report +# "wrong" binary name (i.e. $MAKE_DUMP_SCRIPT instead of $CORE_TEST_BIN). +# In this case, wait until the "fake" binary (sleep in this case) enters +# the "interruptible sleep" state, at which point it should be ready +# to be sacrificed. +for _ in {0..9}; do + read -ra self_stat <"/proc/$pid/stat" + [[ "${self_stat[2]}" == S ]] && break + sleep .5 +done +kill -s "$sig" "$pid" +# This should always fail +! wait "$pid" +EOF +chmod +x "$MAKE_DUMP_SCRIPT" + +# Privileged stuff +[[ "$(id -u)" -eq 0 ]] +# Trigger a couple of coredumps +"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP" +"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT" +# In the tests we store the coredumps in journals, so let's generate a couple +# with Storage=external as well +mkdir -p /run/systemd/coredump.conf.d/ +printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf +"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP" +"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT" +rm -fv /run/systemd/coredump.conf.d/99-external.conf +# Wait a bit for the coredumps to get processed +timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_BIN | wc -l) -lt 4 ]]; do sleep 1; done" + +# Make sure we can forward crashes back to containers +CONTAINER="testsuite-74-container" + +mkdir -p "/var/lib/machines/$CONTAINER" +mkdir -p "/run/systemd/system/systemd-nspawn@$CONTAINER.service.d" +# Bind-mounting /etc into the container kinda defeats the purpose of --volatile=, +# but we need the ASan-related overrides scattered across /etc +cat > "/run/systemd/system/systemd-nspawn@$CONTAINER.service.d/override.conf" << EOF +[Service] +ExecStart= +ExecStart=systemd-nspawn --quiet --link-journal=try-guest --keep-unit --machine=%i --boot \ + --volatile=yes --directory=/ --bind-ro=/etc --inaccessible=/etc/machine-id +EOF +systemctl daemon-reload + +if cgroupfs_supports_user_xattrs; then + machinectl start "$CONTAINER" + timeout 60 bash -xec "until systemd-run -M '$CONTAINER' -q --wait --pipe true; do sleep .5; done" + + [[ "$(systemd-run -M "$CONTAINER" -q --wait --pipe coredumpctl list -q --no-legend /usr/bin/sleep | wc -l)" -eq 0 ]] + machinectl copy-to "$CONTAINER" "$MAKE_DUMP_SCRIPT" + systemd-run -M "$CONTAINER" -q --wait --pipe "$MAKE_DUMP_SCRIPT" "/usr/bin/sleep" "SIGABRT" + systemd-run -M "$CONTAINER" -q --wait --pipe "$MAKE_DUMP_SCRIPT" "/usr/bin/sleep" "SIGTRAP" + # Wait a bit for the coredumps to get processed + timeout 30 bash -c "while [[ \$(systemd-run -M $CONTAINER -q --wait --pipe coredumpctl list -q --no-legend /usr/bin/sleep | wc -l) -lt 2 ]]; do sleep 1; done" +fi + +coredumpctl +SYSTEMD_LOG_LEVEL=debug coredumpctl +coredumpctl --help +coredumpctl --version +coredumpctl --no-pager --no-legend +coredumpctl --all +coredumpctl -1 +coredumpctl -n 1 +coredumpctl --reverse +coredumpctl -F COREDUMP_EXE +coredumpctl --json=short | jq +coredumpctl --json=pretty | jq +coredumpctl --json=off +coredumpctl --root=/ +coredumpctl --directory=/var/log/journal +coredumpctl --file="/var/log/journal/$(/tmp/core.redirected +test -s /tmp/core.redirected +coredumpctl dump -o /tmp/core.output "${CORE_TEST_BIN##*/}" +test -s /tmp/core.output +rm -f /tmp/core.{output,redirected} + +# Unprivileged stuff +# Related issue: https://github.com/systemd/systemd/issues/26912 +UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" --) +# Trigger a couple of coredumps as an unprivileged user +"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP" +"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT" +# In the tests we store the coredumps in journals, so let's generate a couple +# with Storage=external as well +mkdir -p /run/systemd/coredump.conf.d/ +printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf +"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP" +"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT" +rm -fv /run/systemd/coredump.conf.d/99-external.conf +# Wait a bit for the coredumps to get processed +timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_UNPRIV_BIN | wc -l) -lt 4 ]]; do sleep 1; done" + +# root should see coredumps from both binaries +coredumpctl info "$CORE_TEST_UNPRIV_BIN" +coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" +# The test user should see only their own coredumps +"${UNPRIV_CMD[@]}" coredumpctl +"${UNPRIV_CMD[@]}" coredumpctl info "$CORE_TEST_UNPRIV_BIN" +"${UNPRIV_CMD[@]}" coredumpctl info "${CORE_TEST_UNPRIV_BIN##*/}" +(! "${UNPRIV_CMD[@]}" coredumpctl info --all "$CORE_TEST_BIN") +(! "${UNPRIV_CMD[@]}" coredumpctl info --all "${CORE_TEST_BIN##*/}") +# We should have a couple of externally stored coredumps +"${UNPRIV_CMD[@]}" coredumpctl --field=COREDUMP_FILENAME | tee /tmp/coredumpctl.out +grep "/var/lib/systemd/coredump/core" /tmp/coredumpctl.out +rm -f /tmp/coredumpctl.out + +"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true "$CORE_TEST_UNPRIV_BIN" +"${UNPRIV_CMD[@]}" coredumpctl debug --debugger=/bin/true --debugger-arguments="-this --does --not 'do anything' -a -t --all" "${CORE_TEST_UNPRIV_BIN##*/}" + +"${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_UNPRIV_BIN" >/tmp/core.redirected +test -s /tmp/core.redirected +"${UNPRIV_CMD[@]}" coredumpctl dump -o /tmp/core.output "${CORE_TEST_UNPRIV_BIN##*/}" +test -s /tmp/core.output +rm -f /tmp/core.{output,redirected} +(! "${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_BIN" >/dev/null) + +# --backtrace mode +# Pass one of the existing journal coredump records to systemd-coredump and +# use our PID as the source to make matching the coredump later easier +# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT HOSTNAME +journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" | + /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509994 12345 mymachine +# Wait a bit for the coredump to get processed +timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -eq 0 ]]; do sleep 1; done" +coredumpctl info "$$" +coredumpctl info COREDUMP_HOSTNAME="mymachine" + +# This used to cause a stack overflow +systemd-run -t --property CoredumpFilter=all ls /tmp +systemd-run -t --property CoredumpFilter=default ls /tmp + +(! coredumpctl --hello-world) +(! coredumpctl -n 0) +(! coredumpctl -n -1) +(! coredumpctl --file=/dev/null) +(! coredumpctl --since=0) +(! coredumpctl --until='') +(! coredumpctl --since=today --until=yesterday) +(! coredumpctl --directory=/ --root=/) +(! coredumpctl --json=foo) +(! coredumpctl -F foo -F bar) +(! coredumpctl list 0) +(! coredumpctl list -- -1) +(! coredumpctl list '') +(! coredumpctl info /../.~=) +(! coredumpctl info '') +(! coredumpctl dump --output=/dev/full "$CORE_TEST_BIN") +(! coredumpctl dump --output=/dev/null --output=/dev/null "$CORE_TEST_BIN") +(! coredumpctl debug --debugger=/bin/false) +(! coredumpctl debug --debugger=/bin/true --debugger-arguments='"') diff --git a/test/units/testsuite-74.delta.sh b/test/units/testsuite-74.delta.sh new file mode 100755 index 0000000..a0e1cb5 --- /dev/null +++ b/test/units/testsuite-74.delta.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +at_exit() { + rm -rfv /{run,etc}/systemd/system/delta-test* +} + +trap at_exit EXIT + +# Create a couple of supporting units with overrides +# +# Extended unit +cat >"/run/systemd/system/delta-test-unit-extended.service" <"/run/systemd/system/delta-test-unit-extended.service.d/override.conf" <>/etc/systemd/system/delta-test-unit-overridden.service +# Overridden but equivalent unit +ln -srfv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-equivalent.service +ln -sfv /run/systemd/system/delta-test-unit-extended.service /etc/systemd/system/delta-test-unit-equivalent.service +# Redirected unit +ln -srfv /run/systemd/system/delta-test-unit-extended.service /run/systemd/system/delta-test-unit-redirected.service +ln -sfv /run/systemd/system/delta-test-unit-overidden.service /etc/systemd/system/delta-test-unit-extended.service + +systemctl daemon-reload + +systemd-delta +systemd-delta /run +systemd-delta systemd/system +systemd-delta /run systemd/system /run +systemd-delta /run foo/bar hello/world systemd/system /run +systemd-delta foo/bar +systemd-delta --diff=true +systemd-delta --diff=false + +for type in masked equivalent redirected overridden extended unchanged; do + systemd-delta --type="$type" + systemd-delta --type="$type" /run +done +systemd-delta --type=equivalent,redirected + +(! systemd-delta --diff=foo) +(! systemd-delta --type=foo) +(! systemd-delta --type=equivalent,redirected,foo) diff --git a/test/units/testsuite-74.escape.sh b/test/units/testsuite-74.escape.sh new file mode 100755 index 0000000..e398d40 --- /dev/null +++ b/test/units/testsuite-74.escape.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +# Simple wrapper to check both escaping and unescaping of given strings +# Arguments: +# $1 - expected unescaped string +# $2 - expected escaped string +# $3 - optional arguments for systemd-escape +check_escape() { + unescaped="${1?}" + escaped="${2?}" + shift 2 + + assert_eq "$(systemd-escape "$@" -- "$unescaped")" "$escaped" + assert_eq "$(systemd-escape "$@" --unescape -- "$escaped")" "$unescaped" +} + +systemd-escape --help +systemd-escape --version + +check_escape '' '' +check_escape 'hello' 'hello' +check_escape 'hello-world' 'hello\x2dworld' +check_escape '-+ěščřž---🤔' '\x2d\x2b\xc4\x9b\xc5\xa1\xc4\x8d\xc5\x99\xc5\xbe\x2d\x2d\x2d\xf0\x9f\xa4\x94' +check_escape '/this/is/a/path/a b c' '-this-is-a-path-a\x20b\x20c' + +# Multiple strings to escape/unescape +assert_eq "$(systemd-escape 'hello-world' '/dev/loop1' 'template@🐍')" \ + 'hello\x2dworld -dev-loop1 template\x40\xf0\x9f\x90\x8d' +assert_eq "$(systemd-escape --unescape -- 'hello\x2dworld' '-dev-loop1' 'template\x40\xf0\x9f\x90\x8d')" \ + 'hello-world /dev/loop1 template@🐍' + +# --suffix= is not compatible with --unescape +assert_eq "$(systemd-escape --suffix=mount -- '-+ěščřž---🤔')" \ + '\x2d\x2b\xc4\x9b\xc5\xa1\xc4\x8d\xc5\x99\xc5\xbe\x2d\x2d\x2d\xf0\x9f\xa4\x94.mount' +assert_eq "$(systemd-escape --suffix=timer 'this has spaces')" \ + 'this\x20has\x20spaces.timer' +assert_eq "$(systemd-escape --suffix=service 'trailing-spaces ')" \ + 'trailing\x2dspaces\x20\x20.service' +assert_eq "$(systemd-escape --suffix=automount ' leading-spaces')" \ + '\x20\x20\x20leading\x2dspaces.automount' + +# --template= +check_escape 'hello' 'hello@hello.service' --template=hello@.service +check_escape ' what - is _ love? 🤔 ¯\_(ツ)_/¯' \ + 'hello@\x20\x20what\x20\x2d\x20is\x20_\x20love\x3f\x20\xf0\x9f\xa4\x94\x20\xc2\xaf\x5c_\x28\xe3\x83\x84\x29_-\xc2\xaf.service' \ + --template=hello@.service +check_escape '/this/is/where/my/stuff/is/ with spaces though ' \ + 'mount-my-stuff@-this-is-where-my-stuff-is-\x20with\x20spaces\x20though\x20.service' \ + --template=mount-my-stuff@.service +check_escape '/this/is/where/my/stuff/is/ with spaces though ' \ + 'mount-my-stuff@this-is-where-my-stuff-is-\x20with\x20spaces\x20though\x20.service' \ + --template=mount-my-stuff@.service --path + +# --instance (must be used with --unescape) +assert_eq "$(systemd-escape --unescape --instance 'hello@\x20\x20what\x20\x2d\x20is\x20_\x20love\x3f\x20\xf0\x9f\xa4\x94\x20\xc2\xaf\x5c_\x28\xe3\x83\x84\x29_-\xc2\xaf.service')" \ + ' what - is _ love? 🤔 ¯\_(ツ)_/¯' +assert_eq "$(systemd-escape --unescape --instance 'mount-my-stuff@-this-is-where-my-stuff-is-\x20with\x20spaces\x20though\x20.service')" \ + '/this/is/where/my/stuff/is/ with spaces though ' +assert_eq "$(systemd-escape --unescape --instance --path 'mount-my-stuff@this-is-where-my-stuff-is-\x20with\x20spaces\x20though\x20.service')" \ + '/this/is/where/my/stuff/is/ with spaces though ' + +# --path, reversible cases +check_escape / '-' --path +check_escape '/hello/world' 'hello-world' --path +check_escape '/mnt/smb/おにぎり' \ + 'mnt-smb-\xe3\x81\x8a\xe3\x81\xab\xe3\x81\x8e\xe3\x82\x8a' \ + --path + +# --path, non-reversible cases +assert_eq "$(systemd-escape --path ///////////////)" '-' +assert_eq "$(systemd-escape --path /..)" '-' +assert_eq "$(systemd-escape --path /../.././../.././)" '-' +assert_eq "$(systemd-escape --path /../.././../.././foo)" 'foo' + +# --mangle +assert_eq "$(systemd-escape --mangle 'hello-world')" 'hello-world.service' +assert_eq "$(systemd-escape --mangle '/mount/this')" 'mount-this.mount' +assert_eq "$(systemd-escape --mangle 'my-service@ 🐱 ')" 'my-service@\x20\xf0\x9f\x90\xb1\x20.service' +assert_eq "$(systemd-escape --mangle '/dev/disk/by-emoji/🍎')" 'dev-disk-by\x2demoji-\xf0\x9f\x8d\x8e.device' +assert_eq "$(systemd-escape --mangle 'daily-existential-crisis .timer')" 'daily-existential-crisis\x20.timer' +assert_eq "$(systemd-escape --mangle 'trailing-whitespace.mount ')" 'trailing-whitespace.mount\x20.service' + +(! systemd-escape) +(! systemd-escape --suffix='' hello) +(! systemd-escape --suffix=invalid hello) +(! systemd-escape --suffix=mount --template=hello@.service hello) +(! systemd-escape --suffix=mount --mangle) +(! systemd-escape --template='') +(! systemd-escape --template=@) +(! systemd-escape --template='hello@.service' '') +(! systemd-escape --unescape --template='hello@.service' '@hello.service') +(! systemd-escape --unescape --template='hello@.service' 'hello@.service') +(! systemd-escape --mangle --template=hello@.service hello) +(! systemd-escape --instance 'hello@hello.service') +(! systemd-escape --instance --template=hello@.service 'hello@hello.service') +(! systemd-escape --unescape --instance --path 'mount-my-stuff@-this-is-where-my-stuff-is-\x20with\x20spaces\x20though\x20.service') +(! systemd-escape --path '/../hello/..') +(! systemd-escape --path '.') +(! systemd-escape --path '..') +(! systemd-escape --path "$(set +x; printf '%0.sa' {0..256})") +(! systemd-escape --unescape --path '') +(! systemd-escape --mangle '') diff --git a/test/units/testsuite-74.firstboot.sh b/test/units/testsuite-74.firstboot.sh new file mode 100755 index 0000000..be08575 --- /dev/null +++ b/test/units/testsuite-74.firstboot.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +if ! command -v systemd-firstboot >/dev/null; then + echo "systemd-firstboot not found, skipping the test" + exit 0 +fi + +at_exit() { + if [[ -n "${ROOT:-}" ]]; then + ls -lR "$ROOT" + rm -fr "$ROOT" + fi +} + +trap at_exit EXIT + +# Generated via `mkpasswd -m sha-512 -S foobarsalt password1` +# shellcheck disable=SC2016 +ROOT_HASHED_PASSWORD1='$6$foobarsalt$YbwdaATX6IsFxvWbY3QcZj2gB31R/LFRFrjlFrJtTTqFtSfn4dfOAg/km2k4Sl.a2g7LOYDo31wMTaEsCo9j41' +# Generated via `mkpasswd -m sha-512 -S foobarsalt password2` +# shellcheck disable=SC2016 +ROOT_HASHED_PASSWORD2='$6$foobarsalt$q.P2932zYMLbKnjFwIxPI8y3iuxeuJ2BgE372LcZMMnj3Gcg/9mJg2LPKUl.ha0TG/.fRNNnRQcLfzM0SNot3.' + +# Debian and Ubuntu use /etc/default/locale instead of /etc/locale.conf. Make +# sure we use the appropriate path for locale configuration. +LOCALE_PATH="/etc/locale.conf" +[ -e "$LOCALE_PATH" ] || LOCALE_PATH="/etc/default/locale" +[ -e "$LOCALE_PATH" ] || systemd-firstboot --locale=C.UTF-8 + +# Create a minimal root so we don't modify the testbed +ROOT=test-root +mkdir -p "$ROOT/bin" +# Dummy shell for --root-shell= +touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell" + +systemd-firstboot --root="$ROOT" --locale=foo +grep -q "LANG=foo" "$ROOT$LOCALE_PATH" +rm -fv "$ROOT$LOCALE_PATH" +systemd-firstboot --root="$ROOT" --locale-messages=foo +grep -q "LC_MESSAGES=foo" "$ROOT$LOCALE_PATH" +rm -fv "$ROOT$LOCALE_PATH" +systemd-firstboot --root="$ROOT" --locale=foo --locale-messages=bar +grep -q "LANG=foo" "$ROOT$LOCALE_PATH" +grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH" + +systemd-firstboot --root="$ROOT" --keymap=foo +grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf" + +systemd-firstboot --root="$ROOT" --timezone=Europe/Berlin +readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin" + +systemd-firstboot --root="$ROOT" --hostname "foobar" +grep -q "foobar" "$ROOT/etc/hostname" + +systemd-firstboot --root="$ROOT" --machine-id=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id" + +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +systemd-firstboot --root="$ROOT" --root-password=foo +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" +echo "foo" >root.passwd +systemd-firstboot --root="$ROOT" --root-password-file=root.passwd +grep -q "^root:x:0:0:" "$ROOT/etc/passwd" +grep -q "^root:" "$ROOT/etc/shadow" +rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow" root.passwd +# Set the shell together with the password, as firstboot won't touch +# /etc/passwd if it already exists +systemd-firstboot --root="$ROOT" --root-password-hashed="$ROOT_HASHED_PASSWORD1" --root-shell=/bin/fooshell +grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow" + +systemd-firstboot --root="$ROOT" --kernel-command-line="foo.bar=42" +grep -q "foo.bar=42" "$ROOT/etc/kernel/cmdline" + +# Configs should not get overwritten if they exist unless --force is used +systemd-firstboot --root="$ROOT" \ + --locale=locale-overwrite \ + --locale-messages=messages-overwrite \ + --keymap=keymap-overwrite \ + --timezone=CET \ + --hostname=hostname-overwrite \ + --machine-id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ + --root-password-hashed="$ROOT_HASHED_PASSWORD2" \ + --root-shell=/bin/barshell \ + --kernel-command-line="hello.world=0" +grep -q "LANG=foo" "$ROOT$LOCALE_PATH" +grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH" +grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf" +readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$" +grep -q "foobar" "$ROOT/etc/hostname" +grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id" +grep -q "^root:x:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +grep -q "^root:$ROOT_HASHED_PASSWORD1:" "$ROOT/etc/shadow" +grep -q "foo.bar=42" "$ROOT/etc/kernel/cmdline" + +# The same thing, but now with --force +systemd-firstboot --root="$ROOT" --force \ + --locale=locale-overwrite \ + --locale-messages=messages-overwrite \ + --keymap=keymap-overwrite \ + --timezone=CET \ + --hostname=hostname-overwrite \ + --machine-id=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ + --root-password-hashed="$ROOT_HASHED_PASSWORD2" \ + --root-shell=/bin/barshell \ + --kernel-command-line="hello.world=0" +grep -q "LANG=locale-overwrite" "$ROOT$LOCALE_PATH" +grep -q "LC_MESSAGES=messages-overwrite" "$ROOT$LOCALE_PATH" +grep -q "KEYMAP=keymap-overwrite" "$ROOT/etc/vconsole.conf" +readlink "$ROOT/etc/localtime" | grep -q "/CET$" +grep -q "hostname-overwrite" "$ROOT/etc/hostname" +grep -q "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" "$ROOT/etc/machine-id" +grep -q "^root:x:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd" +grep -q "^root:$ROOT_HASHED_PASSWORD2:" "$ROOT/etc/shadow" +grep -q "hello.world=0" "$ROOT/etc/kernel/cmdline" + +# Test that --reset removes all files configured by firstboot. +systemd-firstboot --root="$ROOT" --reset +[[ ! -e "$ROOT/etc/locale.conf" ]] +[[ ! -e "$ROOT/etc/vconsole.conf" ]] +[[ ! -e "$ROOT/etc/localtime" ]] +[[ ! -e "$ROOT/etc/hostname" ]] +[[ ! -e "$ROOT/etc/machine-id" ]] +[[ ! -e "$ROOT/etc/kernel/cmdline" ]] + +# --copy-* options +rm -fr "$ROOT" +mkdir "$ROOT" +# Copy everything at once (--copy) +systemd-firstboot --root="$ROOT" --copy +diff $LOCALE_PATH "$ROOT$LOCALE_PATH" +diff <(awk -F: '/^root/ { print $7; }' /etc/passwd) <(awk -F: '/^root/ { print $7; }' "$ROOT/etc/passwd") +diff <(awk -F: '/^root/ { print $2; }' /etc/shadow) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow") +[[ -e /etc/vconsole.conf ]] && diff /etc/vconsole.conf "$ROOT/etc/vconsole.conf" +[[ -e /etc/localtime ]] && diff <(readlink /etc/localtime) <(readlink "$ROOT/etc/localtime") +rm -fr "$ROOT" +mkdir "$ROOT" +# Copy everything at once, but now by using separate switches +systemd-firstboot --root="$ROOT" --copy-locale --copy-keymap --copy-timezone --copy-root-password --copy-root-shell +diff $LOCALE_PATH "$ROOT$LOCALE_PATH" +diff <(awk -F: '/^root/ { print $7; }' /etc/passwd) <(awk -F: '/^root/ { print $7; }' "$ROOT/etc/passwd") +diff <(awk -F: '/^root/ { print $2; }' /etc/shadow) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow") +[[ -e /etc/vconsole.conf ]] && diff /etc/vconsole.conf "$ROOT/etc/vconsole.conf" +[[ -e /etc/localtime ]] && diff <(readlink /etc/localtime) <(readlink "$ROOT/etc/localtime") + +# --prompt-* options +rm -fr "$ROOT" +mkdir -p "$ROOT/bin" +touch "$ROOT/bin/fooshell" "$ROOT/bin/barshell" +# Temporarily disable pipefail to avoid `echo: write error: Broken pipe +set +o pipefail +# We can do only limited testing here, since it's all an interactive stuff, +# so --prompt and --prompt-root-password are skipped on purpose +echo -ne "\nfoo\nbar\n" | systemd-firstboot --root="$ROOT" --prompt-locale +grep -q "LANG=foo" "$ROOT$LOCALE_PATH" +grep -q "LC_MESSAGES=bar" "$ROOT$LOCALE_PATH" +# systemd-firstboot in prompt-keymap mode requires keymaps to be installed so +# it can present them as a list to the user. As Debian does not ship/provide +# compatible keymaps (from the kbd package), skip this test if the keymaps are +# missing. +if [ -d "/usr/share/keymaps/" ] || [ -d "/usr/share/kbd/keymaps/" ] || [ -d "/usr/lib/kbd/keymaps/" ] ; then + echo -ne "\nfoo\n" | systemd-firstboot --root="$ROOT" --prompt-keymap + grep -q "KEYMAP=foo" "$ROOT/etc/vconsole.conf" +fi +echo -ne "\nEurope/Berlin\n" | systemd-firstboot --root="$ROOT" --prompt-timezone +readlink "$ROOT/etc/localtime" | grep -q "Europe/Berlin$" +echo -ne "\nfoobar\n" | systemd-firstboot --root="$ROOT" --prompt-hostname +grep -q "foobar" "$ROOT/etc/hostname" +echo -ne "\n/bin/fooshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell +grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +# Existing files should not get overwritten +echo -ne "\n/bin/barshell\n" | systemd-firstboot --root="$ROOT" --prompt-root-shell +grep -q "^root:.*:0:0:.*:/bin/fooshell$" "$ROOT/etc/passwd" +# Now without the welcome screen but with force +echo -ne "/bin/barshell\n" | systemd-firstboot --root="$ROOT" --force --prompt-root-shell --welcome=no +grep -q "^root:.*:0:0:.*:/bin/barshell$" "$ROOT/etc/passwd" +# Re-enable pipefail +set -o pipefail + +# Assorted tests +rm -fr "$ROOT" +mkdir "$ROOT" + +systemd-firstboot --root="$ROOT" --setup-machine-id +grep -E "[a-z0-9]{32}" "$ROOT/etc/machine-id" + +systemd-firstboot --root="$ROOT" --delete-root-password +diff <(echo) <(awk -F: '/^root/ { print $2; }' "$ROOT/etc/shadow") + +(! systemd-firstboot --root="$ROOT" --root-shell=/bin/nonexistentshell) +(! systemd-firstboot --root="$ROOT" --machine-id=invalidmachineid) +(! systemd-firstboot --root="$ROOT" --timezone=Foo/Bar) diff --git a/test/units/testsuite-74.id128.sh b/test/units/testsuite-74.id128.sh new file mode 100755 index 0000000..c1b80d6 --- /dev/null +++ b/test/units/testsuite-74.id128.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +systemd-id128 --help +systemd-id128 help +systemd-id128 show +systemd-id128 show --pretty | tail +systemd-id128 show --value | tail +systemd-id128 show 4f68bce3e8cd4db196e7fbcaf984b709 # root-x86-64 +systemd-id128 show --pretty 4f68bce3e8cd4db196e7fbcaf984b709 +systemd-id128 show root-x86-64 +systemd-id128 show --pretty root-x86-64 +[[ "$(systemd-id128 show 4f68bce3e8cd4db196e7fbcaf984b709)" = "$(systemd-id128 show root-x86-64)" ]] +[[ "$(systemd-id128 show 4f68bce3-e8cd-4db1-96e7-fbcaf984b709)" = "$(systemd-id128 show root-x86-64)" ]] + +systemd-id128 show root-x86-64 --app-specific=4f68bce3e8cd4db196e7fbcaf984b709 +systemd-id128 show --pretty root-x86-64 --app-specific=4f68bce3e8cd4db196e7fbcaf984b709 +[[ "$(systemd-id128 show root-x86-64 --app-specific=4f68bce3e8cd4db196e7fbcaf984b709 -P)" = "8ee5535e7cb14c249e1d28b8dfbb939c" ]] + +[[ "$(systemd-id128 new | wc -c)" -eq 33 ]] +systemd-id128 new -p +systemd-id128 new -u +systemd-id128 new -a 4f68bce3e8cd4db196e7fbcaf984b709 + +systemd-id128 machine-id +systemd-id128 machine-id --pretty +systemd-id128 machine-id --uuid +systemd-id128 machine-id --app-specific=4f68bce3e8cd4db196e7fbcaf984b709 +assert_eq "$(systemd-id128 machine-id)" "$(>"$root/etc/machine-id" + machine_id="$(systemd-machine-id-setup --print --root "$root")" + diff <(echo "$machine_id") "$root/etc/machine-id" +} + +testcase_transient() { + local root transient_id committed_id + + root="$(mktemp -d)" + trap "root_cleanup $root" RETURN + root_mock "$root" + + systemd-machine-id-setup --print --root "$root" + echo abc >>"$root/etc/machine-id" + mount -o remount,ro "$root" + mount -t tmpfs tmpfs "$root/run" + transient_id="$(systemd-machine-id-setup --print --root "$root")" + mount -o remount,rw "$root" + committed_id="$(systemd-machine-id-setup --print --commit --root "$root")" + [[ "$transient_id" == "$committed_id" ]] + diff "$root/etc/machine-id" "$root/run/machine-id" +} + +# Check if we correctly processed the invalid machine ID we set up in the respective +# test.sh file +systemctl --state=failed --no-legend --no-pager >/failed +test ! -s /failed + +run_testcases diff --git a/test/units/testsuite-74.modules-load.sh b/test/units/testsuite-74.modules-load.sh new file mode 100755 index 0000000..3d00e07 --- /dev/null +++ b/test/units/testsuite-74.modules-load.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +MODULES_LOAD_BIN="/usr/lib/systemd/systemd-modules-load" +CONFIG_FILE="/run/modules-load.d/99-test.conf" + +at_exit() { + rm -rfv "${CONFIG_FILE:?}" +} + +trap at_exit EXIT + +if systemd-detect-virt -cq; then + echo "Running in a container, skipping the systemd-modules-load test..." + exit 0 +fi + +# Check if we have required kernel modules +modprobe --all --resolve-alias loop dummy + +mkdir -p /run/modules-load.d/ + +"$MODULES_LOAD_BIN" +"$MODULES_LOAD_BIN" --help +"$MODULES_LOAD_BIN" --version + +# Explicit config file +modprobe -v --all --remove loop dummy +printf "loop\ndummy" >"$CONFIG_FILE" +"$MODULES_LOAD_BIN" "$CONFIG_FILE" |& tee /tmp/out.log +grep -E "Inserted module .*loop" /tmp/out.log +grep -E "Inserted module .*dummy" /tmp/out.log + +# Implicit config file +modprobe -v --all --remove loop dummy +printf "loop\ndummy" >"$CONFIG_FILE" +"$MODULES_LOAD_BIN" |& tee /tmp/out.log +grep -E "Inserted module .*loop" /tmp/out.log +grep -E "Inserted module .*dummy" /tmp/out.log + +# Valid & invalid data mixed together +modprobe -v --all --remove loop dummy +cat >"$CONFIG_FILE" </dev/null; then + echo "Container detected, skipping the test" + exit 0 +fi + +at_exit() { + set +e + + [[ -n "${LOOP:-}" ]] && losetup -d "$LOOP" + [[ -n "${WORK_DIR:-}" ]] && rm -fr "$WORK_DIR" +} + +trap at_exit EXIT + +WORK_DIR="$(mktemp -d)" + +systemd-mount --list +systemd-mount --list --full +systemd-mount --list --no-legend +systemd-mount --list --no-pager +systemd-mount --list --quiet + +# Set up a simple block device for further tests +dd if=/dev/zero of="$WORK_DIR/simple.img" bs=1M count=16 +LOOP="$(losetup --show --find "$WORK_DIR/simple.img")" +mkfs.ext4 -L sd-mount-test "$LOOP" +mkdir "$WORK_DIR/mnt" +mount "$LOOP" "$WORK_DIR/mnt" +touch "$WORK_DIR/mnt/foo.bar" +umount "$LOOP" +(! mountpoint "$WORK_DIR/mnt") + +# Mount with both source and destination set +systemd-mount "$LOOP" "$WORK_DIR/mnt" +systemctl status "$WORK_DIR/mnt" +systemd-mount --list --full +test -e "$WORK_DIR/mnt/foo.bar" +systemd-umount "$WORK_DIR/mnt" +# Same thing, but with explicitly specified filesystem and disabled filesystem check +systemd-mount --type=ext4 --fsck=no --collect "$LOOP" "$WORK_DIR/mnt" +systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").mount" +test -e "$WORK_DIR/mnt/foo.bar" +systemd-mount --umount "$LOOP" +# Discover additional metadata (unit description should now contain filesystem label) +systemd-mount --no-ask-password --discover "$LOOP" "$WORK_DIR/mnt" +test -e "$WORK_DIR/mnt/foo.bar" +systemctl show -P Description "$WORK_DIR/mnt" | grep -q sd-mount-test +systemd-umount "$WORK_DIR/mnt" +# Set a unit description +systemd-mount --description="Very Important Unit" "$LOOP" "$WORK_DIR/mnt" +test -e "$WORK_DIR/mnt/foo.bar" +systemctl show -P Description "$WORK_DIR/mnt" | grep -q "Very Important Unit" +systemd-umount "$WORK_DIR/mnt" +# Set a property +systemd-mount --property="Description=Foo Bar" "$LOOP" "$WORK_DIR/mnt" +test -e "$WORK_DIR/mnt/foo.bar" +systemctl show -P Description "$WORK_DIR/mnt" | grep -q "Foo Bar" +systemd-umount "$WORK_DIR/mnt" +# Set mount options +systemd-mount --options=ro,x-foo-bar "$LOOP" "$WORK_DIR/mnt" +test -e "$WORK_DIR/mnt/foo.bar" +systemctl show -P Options "$WORK_DIR/mnt" | grep -Eq "(^ro|,ro)" +systemctl show -P Options "$WORK_DIR/mnt" | grep -q "x-foo-bar" +systemd-umount "$WORK_DIR/mnt" + +# Mount with only source set +systemd-mount "$LOOP" +systemctl status /run/media/system/sd-mount-test +systemd-mount --list --full +test -e /run/media/system/sd-mount-test/foo.bar +systemd-umount LABEL=sd-mount-test + +# Automount +systemd-mount --automount=yes "$LOOP" "$WORK_DIR/mnt" +systemd-mount --list --full +systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").automount" +[[ "$(systemctl show -P ActiveState "$WORK_DIR/mnt")" == inactive ]] +test -e "$WORK_DIR/mnt/foo.bar" +systemctl status "$WORK_DIR/mnt" +systemd-umount "$WORK_DIR/mnt" +# Automount + automount-specific property +systemd-mount -A --automount-property="Description=Bar Baz" "$LOOP" "$WORK_DIR/mnt" +systemctl show -P Description "$(systemd-escape --path "$WORK_DIR/mnt").automount" | grep -q "Bar Baz" +test -e "$WORK_DIR/mnt/foo.bar" +# Call --umount via --machine=, first with a relative path (bad) and then with +# an absolute one (good) +(! systemd-umount --machine=.host "$(realpath --relative-to=. "$WORK_DIR/mnt")") +systemd-umount --machine=.host "$WORK_DIR/mnt" + +# ext4 doesn't support uid=/gid= +(! systemd-mount -t ext4 --owner=testuser "$LOOP" "$WORK_DIR/mnt") + +# Automount + --bind-device +systemd-mount --automount=yes --bind-device --timeout-idle-sec=1 "$LOOP" "$WORK_DIR/mnt" +systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").automount" +# Trigger the automount +test -e "$WORK_DIR/mnt/foo.bar" +# Wait until it's idle again +sleep 1.5 +# Safety net for slower/overloaded systems +timeout 10s bash -c "while systemctl is-active -q $WORK_DIR/mnt; do sleep .2; done" +systemctl status "$(systemd-escape --path "$WORK_DIR/mnt").automount" +# Disassemble the underlying block device +losetup -d "$LOOP" +unset LOOP +# The automount unit should disappear once the underlying blockdev is gone +timeout 10s bash -c "while systemctl status '$(systemd-escape --path "$WORK_DIR/mnt".automount)'; do sleep .2; done" + +# Mount a disk image +systemd-mount --discover "$WORK_DIR/simple.img" +# We can access files in the image even if the loopback block device is not initialized by udevd. +test -e /run/media/system/simple.img/foo.bar +# systemd-mount --list and systemd-umount require the loopback block device is initialized by udevd. +udevadm settle --timeout 30 +assert_in "/dev/loop.* ext4 +sd-mount-test" "$(systemd-mount --list --full)" +systemd-umount "$WORK_DIR/simple.img" + +# --owner + vfat +# +# Create a vfat image, as ext4 doesn't support uid=/gid= fixating for all +# files/directories +dd if=/dev/zero of="$WORK_DIR/owner-vfat.img" bs=1M count=16 +LOOP="$(losetup --show --find "$WORK_DIR/owner-vfat.img")" +mkfs.vfat -n owner-vfat "$LOOP" +# Mount it and check the UID/GID +[[ "$(stat -c "%U:%G" "$WORK_DIR/mnt")" == "root:root" ]] +systemd-mount --owner=testuser "$LOOP" "$WORK_DIR/mnt" +systemctl status "$WORK_DIR/mnt" +[[ "$(stat -c "%U:%G" "$WORK_DIR/mnt")" == "testuser:testuser" ]] +touch "$WORK_DIR/mnt/hello" +[[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]] +systemd-umount LABEL=owner-vfat + +# tmpfs +mkdir -p "$WORK_DIR/mnt/foo/bar" +systemd-mount --tmpfs "$WORK_DIR/mnt/foo" +test ! -d "$WORK_DIR/mnt/foo/bar" +touch "$WORK_DIR/mnt/foo/baz" +systemd-umount "$WORK_DIR/mnt/foo" +test -d "$WORK_DIR/mnt/foo/bar" +test ! -e "$WORK_DIR/mnt/foo/baz" diff --git a/test/units/testsuite-74.networkctl.sh b/test/units/testsuite-74.networkctl.sh new file mode 100755 index 0000000..0a687af --- /dev/null +++ b/test/units/testsuite-74.networkctl.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + systemctl stop systemd-networkd + + if [[ -v NETWORK_NAME && -v NETDEV_NAME && -v LINK_NAME ]]; then + rm -fvr {/usr/lib,/etc}/systemd/network/"$NETWORK_NAME" "/usr/lib/systemd/network/$NETDEV_NAME" \ + {/usr/lib,/etc}/systemd/network/"$LINK_NAME" "/etc/systemd/network/${NETWORK_NAME}.d" \ + "new" "+4" + fi +} + +trap at_exit EXIT + +export NETWORK_NAME="10-networkctl-test-$RANDOM.network" +export NETDEV_NAME="10-networkctl-test-$RANDOM.netdev" +export LINK_NAME="10-networkctl-test-$RANDOM.link" +cat >"/usr/lib/systemd/network/$NETWORK_NAME" <new <"+4" <"/usr/lib/systemd/network/$NETDEV_NAME" <"/usr/lib/systemd/network/$LINK_NAME" <"${USER_DIRS_CONF:?}" <<\EOF +XDG_DESKTOP_DIR="$HOME/my-fancy-desktop" +XDG_INVALID + +XDG_DOWNLOAD_DIR = "$HOME" +XDG_TEMPLATES_DIR="/templates" +# Invalid records +XDG_TEMPLATES_DIR=/not-templates" +XDG_TEMPLATES_DIR="/also-not-teplates +XDG_TEMPLATES_DIR="" +XDG_TEMPLATES_DIR="../" + +XDG_PUBLICSHARE_DIR="$HOME/cat-pictures" +XDG_DOCUMENTS_DIR="$HOME/top/secret/documents" +XDG_MUSIC_DIR="/tmp/vaporwave" +XDG_PICTURES_DIR="$HOME/Pictures" +XDG_VIDEOS_DIR="$HOME/🤔" +EOF + +systemd-path --help +systemd-path --version +systemd-path +systemd-path temporary system-binaries user binfmt + +assert_eq "$(systemd-path system-runtime)" "/run" +assert_eq "$(systemd-path --suffix='' system-runtime)" "/run" +assert_eq "$(systemd-path --suffix='🤔' system-runtime)" "/run/🤔" +assert_eq "$(systemd-path --suffix=hello system-runtime)" "/run/hello" + +# Note for the stuff below: everything defaults to $HOME, only the desktop +# directory defaults to $HOME/Desktop. +# +# Check the user-dirs.dir stuff from above +assert_eq "$(systemd-path user)" "/root" +assert_eq "$(systemd-path user-desktop)" "/root/my-fancy-desktop" +assert_eq "$(systemd-path user-documents)" "/root/top/secret/documents" +assert_eq "$(systemd-path user-download)" "/root" +assert_eq "$(systemd-path user-music)" "/tmp/vaporwave" +assert_eq "$(systemd-path user-pictures)" "/root/Pictures" +assert_eq "$(systemd-path user-public)" "/root/cat-pictures" +assert_eq "$(systemd-path user-templates)" "/templates" +assert_eq "$(systemd-path user-videos)" "/root/🤔" + +# Remove the user-dirs.dir file and check the defaults +rm -fv "$USER_DIRS_CONF" +[[ ! -e "$USER_DIRS_CONF" ]] +assert_eq "$(systemd-path user-desktop)" "/root/Desktop" +for dir in "" documents download music pictures public templates videos; do + assert_eq "$(systemd-path "user${dir:+-$dir}")" "/root" +done + +# sd-path should consider only absolute $HOME +assert_eq "$(HOME=/hello-world systemd-path user)" "/hello-world" +assert_eq "$(HOME=hello-world systemd-path user)" "/root" +assert_eq "$(HOME=/hello systemd-path --suffix=world user)" "/hello/world" +assert_eq "$(HOME=hello systemd-path --suffix=world user)" "/root/world" +# Same with some other env variables +assert_in "/my-config" "$(HOME='' XDG_CONFIG_HOME=/my-config systemd-path search-configuration)" +assert_in "/my-config/foo" "$(HOME='' XDG_CONFIG_HOME=/my-config systemd-path --suffix=foo search-configuration)" +assert_in "/my-home/.config/foo" "$(HOME=/my-home XDG_CONFIG_HOME=my-config systemd-path --suffix=foo search-configuration)" +assert_not_in "my-config" "$(HOME=my-config XDG_CONFIG_HOME=my-config systemd-path search-configuration)" + +(! systemd-path '') +(! systemd-path system-binaries 🤔 user) +(! systemd-path --xyz) diff --git a/test/units/testsuite-74.pstore.sh b/test/units/testsuite-74.pstore.sh new file mode 100755 index 0000000..9be8066 --- /dev/null +++ b/test/units/testsuite-74.pstore.sh @@ -0,0 +1,258 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +systemctl log-level info + +if systemd-detect-virt -cq; then + echo "Running in a container, skipping the systemd-pstore test..." + exit 0 +fi + +DUMMY_DMESG_0="$(mktemp)" +cat >"$DUMMY_DMESG_0" <<\EOF +6,17159,5340096332127,-;usb 1-4: USB disconnect, device number 124 +6,17160,5340109662397,-;input: WH-1000XM3 (AVRCP) as /devices/virtual/input/input293 +6,17161,5343126458360,-;loop0: detected capacity change from 0 to 3145728 +6,17162,5343126766065,-; loop0: p1 p2 +6,17163,5343126815038,-;EXT4-fs (loop0p1): mounted filesystem with ordered data mode. Quota mode: none. +6,17164,5343158037334,-;EXT4-fs (loop0p1): unmounting filesystem. +6,17165,5343158072598,-;loop0: detected capacity change from 0 to 3145728 +6,17166,5343158073563,-; loop0: p1 p2 +6,17167,5343158074325,-; loop0: p1 p2 +6,17168,5343158140859,-;EXT4-fs (loop0p1): mounted filesystem with ordered data mode. Quota mode: none. +6,17169,5343158182977,-;EXT4-fs (loop0p1): unmounting filesystem. +6,17170,5343158700241,-;loop0: detected capacity change from 0 to 3145728 +6,17171,5343158700439,-; loop0: p1 p2 +6,17172,5343158701120,-; loop0: p1 p2 +EOF + +DUMMY_DMESG_1="$(mktemp)" +cat >"$DUMMY_DMESG_1" <<\EOF +Nechť již hříšné saxofony ďáblů rozezvučí síň úděsnými tóny waltzu, tanga a quickstepu. +Příliš žluťoučký kůň úpěl ďábelské ódy. +Zvlášť zákeřný učeň s ďolíčky běží podél zóny úlů. +Vyciď křišťálový nůž, ó učiň úděsné líbivým! +Loď čeří kýlem tůň obzvlášť v Grónské úžině +Ó, náhlý déšť již zvířil prach a čilá laň teď běží s houfcem gazel k úkrytům. +Vypätá dcéra grófa Maxwella s IQ nižším ako kôň núti čeľaď hrýzť hŕbu jabĺk. +Kŕdeľ šťastných ďatľov učí pri ústí Váhu mĺkveho koňa obhrýzať kôru a žrať čerstvé mäso. +Stróż pchnął kość w quiz gędźb vel fax myjń. +Portez ce vieux whisky au juge blond qui fume! +EOF + +file_count() { find "${1:?}" -type f | wc -l; } +file_size() { wc -l <"${1:?}"; } +random_efi_timestamp() { printf "%0.10d" "$((1000000000 + RANDOM))"; } + +# The dmesg- filename contains the backend-type and the Common Platform Error Record, CPER, +# record id, a 64-bit number. +# +# Files are processed in reverse lexigraphical order so as to properly reconstruct original dmesg. + +prepare_efi_logs() { + local file="${1:?}" + local timestamp="${2:?}" + local chunk count filename + + # For the EFI backend, the 3 least significant digits of record id encodes a + # "count" number, the next 2 least significant digits for the dmesg part + # (chunk) number, and the remaining digits as the timestamp. See + # linux/drivers/firmware/efi/efi-pstore.c in efi_pstore_write(). + count="$(file_size "$file")" + chunk=0 + # The sed in the process substitution below just reverses the file + while read -r line; do + filename="$(printf "dmesg-efi-%0.10d%0.2d%0.3d" "$timestamp" "$chunk" "$count")" + echo "$line" >"/sys/fs/pstore/$filename" + chunk=$((chunk + 1)) + done < <(sed '1!G;h;$!d' "$file") + + if [[ "$chunk" -eq 0 ]]; then + echo >&2 "No dmesg-efi files were created" + exit 1 + fi +} + +prepare_erst_logs() { + local file="${1:?}" + local start_id="${2:?}" + local id filename + + # For the ERST backend, the record is a monotonically increasing number, seeded as + # a timestamp. See linux/drivers/acpi/apei/erst.c in erst_writer(). + id="$start_id" + # The sed in the process substitution below just reverses the file + while read -r line; do + filename="$(printf "dmesg-erst-%0.16d" "$id")" + echo "$line" >"/sys/fs/pstore/$filename" + id=$((id + 1)) + done < <(sed '1!G;h;$!d' "$file") + + if [[ "$id" -eq "$start_id" ]]; then + echo >&2 "No dmesg-erst files were created" + exit 1 + fi + + # ID of the last dmesg file will be the ID of the erst subfolder + echo "$((id - 1))" +} + +prepare_pstore_config() { + local storage="${1:?}" + local unlink="${2:?}" + + systemctl stop systemd-pstore + + rm -fr /sys/fs/pstore/* /var/lib/systemd/pstore/* + + mkdir -p /run/systemd/pstore.conf.d + cat >/run/systemd/pstore.conf.d/99-test.conf </run/systemd/system/systemd-pstore.service.d/99-StartLimitInterval.conf </sys/fs/pstore/foo.bar + [[ "$unlink" == yes ]] && exp_count=0 || exp_count="$(file_count /sys/fs/pstore/)" + start_pstore + [[ "$(file_count /sys/fs/pstore)" -ge "$exp_count" ]] + [[ "$(file_count /var/lib/systemd/pstore/)" -ne 0 ]] + filename="$(printf "/var/lib/systemd/pstore/%s/%0.3d/dmesg.txt" "$timestamp" "$(file_size "${!dmesg}")")" + diff "${!dmesg}" "$filename" + grep "hello world" "/var/lib/systemd/pstore/foo.bar" + done + # Check that we kept all previous records as well + for timestamp in "${timestamps[@]}"; do + [[ -d "/var/lib/systemd/pstore/$timestamp" ]] + [[ "$(file_count "/var/lib/systemd/pstore/$timestamp/")" -gt 0 ]] + done + + : "Backend: EFI; Storage: journal; Unlink: $unlink" + timestamp="$(random_efi_timestamp)" + prepare_pstore_config "journal" "$unlink" + prepare_efi_logs "$DUMMY_DMESG_0" "$timestamp" + [[ "$unlink" == yes ]] && exp_count=0 || exp_count="$(file_count /sys/fs/pstore/)" + start_pstore + [[ "$(file_count /sys/fs/pstore)" -ge "$exp_count" ]] + [[ "$(file_count /var/lib/systemd/pstore/)" -eq 0 ]] + diff "$DUMMY_DMESG_0" <(journalctl -o cat --output-fields=FILE --cursor-file=/tmp/journal.cursor | sed "/^$/d") + + : "Backend: ERST; Storage: external; Unlink: $unlink" + prepare_pstore_config "external" "$unlink" + last_id="$(prepare_erst_logs "$DUMMY_DMESG_0" 0)" + [[ "$unlink" == yes ]] && exp_count=0 || exp_count="$(file_count /sys/fs/pstore/)" + start_pstore + [[ "$(file_count /sys/fs/pstore)" -ge "$exp_count" ]] + [[ "$(file_count /var/lib/systemd/pstore/)" -ne 0 ]] + # We always log to journal + diff "$DUMMY_DMESG_0" <(journalctl -o cat --output-fields=FILE --cursor-file=/tmp/journal.cursor | sed "/^$/d") + filename="$(printf "/var/lib/systemd/pstore/%0.16d/dmesg.txt" "$last_id")" + diff "$DUMMY_DMESG_0" "$filename" + + : "Backend: ERST; Storage: external; Unlink: $unlink; multiple dmesg files" + last_ids=() + prepare_pstore_config "external" "$unlink" + for i in {0..9}; do + # Create a name reference to one of the $DUMMY_DMESG_X variables + dmesg="DUMMY_DMESG_$((i % 2))" + last_id="$(prepare_erst_logs "${!dmesg}" "$((i * 100))")" + last_ids+=("$last_id") + # Add one "random" (non-dmesg) file as well + echo "hello world" >/sys/fs/pstore/foo.bar + [[ "$unlink" == yes ]] && exp_count=0 || exp_count="$(file_count /sys/fs/pstore/)" + start_pstore + [[ "$(file_count /sys/fs/pstore)" -ge "$exp_count" ]] + [[ "$(file_count /var/lib/systemd/pstore/)" -ne 0 ]] + filename="$(printf "/var/lib/systemd/pstore/%0.16d/dmesg.txt" "$last_id")" + diff "${!dmesg}" "$filename" + grep "hello world" "/var/lib/systemd/pstore/foo.bar" + done + # Check that we kept all previous records as well + for last_id in "${last_ids[@]}"; do + directory="$(printf "/var/lib/systemd/pstore/%0.16d" "$last_id")" + [[ -d "$directory" ]] + [[ "$(file_count "$directory")" -gt 0 ]] + done + + : "Backend: ERST; Storage: journal; Unlink: $unlink" + prepare_pstore_config "journal" "$unlink" + last_id="$(prepare_erst_logs "$DUMMY_DMESG_0" 0)" + [[ "$unlink" == yes ]] && exp_count=0 || exp_count="$(file_count /sys/fs/pstore/)" + start_pstore + [[ "$(file_count /sys/fs/pstore)" -ge "$exp_count" ]] + [[ "$(file_count /var/lib/systemd/pstore/)" -eq 0 ]] + diff "$DUMMY_DMESG_0" <(journalctl -o cat --output-fields=FILE --cursor-file=/tmp/journal.cursor | sed "/^$/d") +done diff --git a/test/units/testsuite-74.run.sh b/test/units/testsuite-74.run.sh new file mode 100755 index 0000000..e894932 --- /dev/null +++ b/test/units/testsuite-74.run.sh @@ -0,0 +1,236 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +systemd-run --help --no-pager +systemd-run --version +systemd-run --no-ask-password true +systemd-run --no-block --collect true + +export PARENT_FOO=bar +touch /tmp/public-marker + +: "Transient service (system daemon)" +systemd-run --wait --pipe \ + bash -xec '[[ "$( "$V" +mount --bind "$V" /proc/version + +B=$(systemd-run -q --wait --pipe -p ProtectProc=invisible cat /proc/version) + +assert_eq "$A" "$B" + +umount /proc/version +rm "$V" diff --git a/test/units/testsuite-74.service b/test/units/testsuite-74.service new file mode 100644 index 0000000..f782132 --- /dev/null +++ b/test/units/testsuite-74.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-74-AUX-UTILS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-74.sh b/test/units/testsuite-74.sh new file mode 100755 index 0000000..9c2a033 --- /dev/null +++ b/test/units/testsuite-74.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok diff --git a/test/units/testsuite-74.varlinkctl.sh b/test/units/testsuite-74.varlinkctl.sh new file mode 100755 index 0000000..5a96269 --- /dev/null +++ b/test/units/testsuite-74.varlinkctl.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# Unset $PAGER so we don't have to use --no-pager everywhere +export PAGER= + +varlinkctl --help +varlinkctl help --no-pager +varlinkctl --version +varlinkctl --json=help + +# TODO: abstract namespace sockets (@...) +# Path to a socket +varlinkctl info /run/systemd/journal/io.systemd.journal +varlinkctl info /run/systemd/../systemd/../../run/systemd/journal/io.systemd.journal +varlinkctl info "./$(realpath --relative-to="$PWD" /run/systemd/journal/io.systemd.journal)" +varlinkctl info unix:/run/systemd/journal/io.systemd.journal +varlinkctl info --json=off /run/systemd/journal/io.systemd.journal +varlinkctl info --json=pretty /run/systemd/journal/io.systemd.journal | jq . +varlinkctl info --json=short /run/systemd/journal/io.systemd.journal | jq . +varlinkctl info -j /run/systemd/journal/io.systemd.journal | jq . + +varlinkctl list-interfaces /run/systemd/journal/io.systemd.journal +varlinkctl list-interfaces -j /run/systemd/journal/io.systemd.journal | jq . + +varlinkctl introspect /run/systemd/journal/io.systemd.journal io.systemd.Journal +varlinkctl introspect -j /run/systemd/journal/io.systemd.journal io.systemd.Journal | jq . + +if command -v userdbctl >/dev/null; then + systemctl start systemd-userdbd + varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{ "userName" : "testuser", "service" : "io.systemd.Multiplexer" }' + varlinkctl call -j /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{ "userName" : "testuser", "service" : "io.systemd.Multiplexer" }' | jq . + varlinkctl call --more /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' + varlinkctl call --more -j /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' | jq --seq . + varlinkctl call --oneway /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' + (! varlinkctl call --oneway /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetMemberships '{ "service" : "io.systemd.Multiplexer" }' | grep .) +fi + +IDL_FILE="$(mktemp)" +varlinkctl introspect /run/systemd/journal/io.systemd.journal io.systemd.Journal | tee "${IDL_FILE:?}" +varlinkctl validate-idl "$IDL_FILE" +varlinkctl validate-idl "$IDL_FILE" +cat /bin/sh >"$IDL_FILE" +(! varlinkctl validate-idl "$IDL_FILE") + +if [[ -x /usr/lib/systemd/systemd-pcrextend ]]; then + # Path to an executable + varlinkctl info /usr/lib/systemd/systemd-pcrextend + varlinkctl info exec:/usr/lib/systemd/systemd-pcrextend + varlinkctl list-interfaces /usr/lib/systemd/systemd-pcrextend + varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend +fi + +# Go through all varlink sockets we can find under /run/systemd/ for some extra coverage +find /run/systemd/ -name "io.systemd*" -type s | while read -r socket; do + varlinkctl info "$socket" + + varlinkctl list-interfaces "$socket" | while read -r interface; do + varlinkctl introspect "$socket" "$interface" + done +done + +(! varlinkctl) +(! varlinkctl "") +(! varlinkctl info) +(! varlinkctl info "") +(! varlinkctl info /run/systemd/notify) +(! varlinkctl info /run/systemd/private) +# Relative paths must begin with ./ +(! varlinkctl info "$(realpath --relative-to="$PWD" /run/systemd/journal/io.systemd.journal)") +(! varlinkctl info unix:) +(! varlinkctl info unix:"") +(! varlinkctl info exec:) +(! varlinkctl info exec:"") +(! varlinkctl list-interfaces) +(! varlinkctl list-interfaces "") +(! varlinkctl introspect) +(! varlinkctl introspect /run/systemd/journal/io.systemd.journal) +(! varlinkctl introspect /run/systemd/journal/io.systemd.journal "") +(! varlinkctl introspect "" "") +(! varlinkctl call) +(! varlinkctl call "") +(! varlinkctl call "" "") +(! varlinkctl call "" "" "") +(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord /dev/null); then + TMPDIR=$(mktemp -d -p /tmp resolvconf-tests.XXXXXX) + RESOLVCONF="$TMPDIR"/resolvconf + ln -s "$(command -v resolvectl 2>/dev/null)" "$RESOLVCONF" +fi +echo nameserver 10.0.2.1 10.0.2.2 | "$RESOLVCONF" -a hoge +echo nameserver 10.0.2.3 10.0.2.4 | "$RESOLVCONF" -a hoge.foo +assert_in '10.0.2.1 10.0.2.2' "$(resolvectl dns hoge)" +assert_in '10.0.2.3 10.0.2.4' "$(resolvectl dns hoge.foo)" +echo nameserver 10.0.3.1 10.0.3.2 | "$RESOLVCONF" -a hoge.inet.ipsec.192.168.35 +echo nameserver 10.0.3.3 10.0.3.4 | "$RESOLVCONF" -a hoge.foo.dhcp +assert_in '10.0.3.1 10.0.3.2' "$(resolvectl dns hoge)" +assert_in '10.0.3.3 10.0.3.4' "$(resolvectl dns hoge.foo)" + +# Tests for _localdnsstub and _localdnsproxy +assert_in '127.0.0.53' "$(resolvectl query _localdnsstub)" +assert_in '_localdnsstub' "$(resolvectl query 127.0.0.53)" +assert_in '127.0.0.54' "$(resolvectl query _localdnsproxy)" +assert_in '_localdnsproxy' "$(resolvectl query 127.0.0.54)" + +assert_in '127.0.0.53' "$(dig @127.0.0.53 _localdnsstub)" +assert_in '_localdnsstub' "$(dig @127.0.0.53 -x 127.0.0.53)" +assert_in '127.0.0.54' "$(dig @127.0.0.53 _localdnsproxy)" +assert_in '_localdnsproxy' "$(dig @127.0.0.53 -x 127.0.0.54)" + +# Tests for mDNS and LLMNR settings +mkdir -p /run/systemd/resolved.conf.d +{ + echo "[Resolve]" + echo "MulticastDNS=yes" + echo "LLMNR=yes" +} >/run/systemd/resolved.conf.d/mdns-llmnr.conf +restart_resolved +# make sure networkd is not running. +systemctl stop systemd-networkd.service +# defaults to yes (both the global and per-link settings are yes) +assert_in 'yes' "$(resolvectl mdns hoge)" +assert_in 'yes' "$(resolvectl llmnr hoge)" +# set per-link setting +resolvectl mdns hoge yes +resolvectl llmnr hoge yes +assert_in 'yes' "$(resolvectl mdns hoge)" +assert_in 'yes' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge resolve +resolvectl llmnr hoge resolve +assert_in 'resolve' "$(resolvectl mdns hoge)" +assert_in 'resolve' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge no +resolvectl llmnr hoge no +assert_in 'no' "$(resolvectl mdns hoge)" +assert_in 'no' "$(resolvectl llmnr hoge)" +# downgrade global setting to resolve +{ + echo "[Resolve]" + echo "MulticastDNS=resolve" + echo "LLMNR=resolve" +} >/run/systemd/resolved.conf.d/mdns-llmnr.conf +restart_resolved +# set per-link setting +resolvectl mdns hoge yes +resolvectl llmnr hoge yes +assert_in 'resolve' "$(resolvectl mdns hoge)" +assert_in 'resolve' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge resolve +resolvectl llmnr hoge resolve +assert_in 'resolve' "$(resolvectl mdns hoge)" +assert_in 'resolve' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge no +resolvectl llmnr hoge no +assert_in 'no' "$(resolvectl mdns hoge)" +assert_in 'no' "$(resolvectl llmnr hoge)" +# downgrade global setting to no +{ + echo "[Resolve]" + echo "MulticastDNS=no" + echo "LLMNR=no" +} >/run/systemd/resolved.conf.d/mdns-llmnr.conf +restart_resolved +# set per-link setting +resolvectl mdns hoge yes +resolvectl llmnr hoge yes +assert_in 'no' "$(resolvectl mdns hoge)" +assert_in 'no' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge resolve +resolvectl llmnr hoge resolve +assert_in 'no' "$(resolvectl mdns hoge)" +assert_in 'no' "$(resolvectl llmnr hoge)" +resolvectl mdns hoge no +resolvectl llmnr hoge no +assert_in 'no' "$(resolvectl mdns hoge)" +assert_in 'no' "$(resolvectl llmnr hoge)" + +# Cleanup +rm -f /run/systemd/resolved.conf.d/mdns-llmnr.conf +ip link del hoge +ip link del hoge.foo + +### SETUP ### +# Configure network +hostnamectl hostname ns1.unsigned.test +cat >>/etc/hosts </etc/systemd/network/10-dns0.netdev </etc/systemd/network/10-dns0.network </run/systemd/resolved.conf.d/test.conf +ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf +# Override the default NTA list, which turns off DNSSEC validation for (among +# others) the test. domain +mkdir -p "/etc/dnssec-trust-anchors.d/" +echo local >/etc/dnssec-trust-anchors.d/local.negative + +# Sign the root zone +keymgr . generate algorithm=ECDSAP256SHA256 ksk=yes zsk=yes +# Create a trust anchor for resolved with our root zone +keymgr . ds | sed 's/ DS/ IN DS/g' >/etc/dnssec-trust-anchors.d/root.positive +# Create a bind-compatible trust anchor (for delv) +# Note: the trust-anchors directive is relatively new, so use the original +# managed-keys one until it's widespread enough +{ + echo 'managed-keys {' + keymgr . dnskey | sed -r 's/^\. DNSKEY ([0-9]+ [0-9]+ [0-9]+) (.+)$/. static-key \1 "\2";/g' + echo '};' +} >/etc/bind.keys +# Create an /etc/bind/bind.keys symlink, which is used by delv on Ubuntu +mkdir -p /etc/bind +ln -svf /etc/bind.keys /etc/bind/bind.keys + +# Start the services +systemctl unmask systemd-networkd +systemctl start systemd-networkd +restart_resolved +# Create knot's runtime dir, since from certain version it's provided only by +# the package and not created by tmpfiles/systemd +if [[ ! -d /run/knot ]]; then + mkdir -p /run/knot + chown -R knot:knot /run/knot +fi +systemctl start knot +# Wait a bit for the keys to propagate +sleep 4 + +networkctl status +resolvectl status +resolvectl log-level debug + +# Start monitoring queries +systemd-run -u resolvectl-monitor.service -p Type=notify resolvectl monitor +systemd-run -u resolvectl-monitor-json.service -p Type=notify resolvectl monitor --json=short + +# Check if all the zones are valid (zone-check always returns 0, so let's check +# if it produces any errors/warnings) +run knotc zone-check +[[ ! -s "$RUN_OUT" ]] +# We need to manually propagate the DS records of onlinesign.test. to the parent +# zone, since they're generated online +knotc zone-begin test. +if knotc zone-get test. onlinesign.test. ds | grep .; then + # Drop any old DS records, if present (e.g. on test re-run) + knotc zone-unset test. onlinesign.test. ds +fi +# Propagate the new DS records +while read -ra line; do + knotc zone-set test. "${line[0]}" 600 "${line[@]:1}" +done < <(keymgr onlinesign.test. ds) +knotc zone-commit test. + +knotc reload + +### SETUP END ### + +: "--- nss-resolve/nss-myhostname tests" +# Sanity check +TIMESTAMP=$(date '+%F %T') +# Issue: https://github.com/systemd/systemd/issues/23951 +# With IPv6 enabled +run getent -s resolve hosts ns1.unsigned.test +grep -qE "^fd00:dead:beef:cafe::1\s+ns1\.unsigned\.test" "$RUN_OUT" +monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN AAAA fd00:dead:beef:cafe::1" +# With IPv6 disabled +# Issue: https://github.com/systemd/systemd/issues/23951 +# FIXME +#disable_ipv6 +#run getent -s resolve hosts ns1.unsigned.test +#grep -qE "^10\.0\.0\.1\s+ns1\.unsigned\.test" "$RUN_OUT" +#monitor_check_rr "$TIMESTAMP" "ns1.unsigned.test IN A 10.0.0.1" +enable_ipv6 + +# Issue: https://github.com/systemd/systemd/issues/18812 +# PR: https://github.com/systemd/systemd/pull/18896 +# Follow-up issue: https://github.com/systemd/systemd/issues/23152 +# Follow-up PR: https://github.com/systemd/systemd/pull/23161 +# With IPv6 enabled +run getent -s resolve hosts localhost +grep -qE "^::1\s+localhost" "$RUN_OUT" +run getent -s myhostname hosts localhost +grep -qE "^::1\s+localhost" "$RUN_OUT" +# With IPv6 disabled +disable_ipv6 +run getent -s resolve hosts localhost +grep -qE "^127\.0\.0\.1\s+localhost" "$RUN_OUT" +run getent -s myhostname hosts localhost +grep -qE "^127\.0\.0\.1\s+localhost" "$RUN_OUT" +enable_ipv6 + +# Issue: https://github.com/systemd/systemd/issues/25088 +run getent -s resolve hosts 127.128.0.5 +grep -qEx '127\.128\.0\.5\s+localhost5(\s+localhost5?\.localdomain[45]?){4}' "$RUN_OUT" +[ "$(wc -l <"$RUN_OUT")" -eq 1 ] + +# Issue: https://github.com/systemd/systemd/issues/20158 +run dig +noall +answer +additional localhost5. +grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT" +[ "$(wc -l <"$RUN_OUT")" -eq 1 ] +run dig +noall +answer +additional localhost5.localdomain4. +grep -qEx 'localhost5\.localdomain4\.\s+0\s+IN\s+CNAME\s+localhost5\.' "$RUN_OUT" +grep -qEx 'localhost5\.\s+0\s+IN\s+A\s+127\.128\.0\.5' "$RUN_OUT" +[ "$(wc -l <"$RUN_OUT")" -eq 2 ] + +: "--- Basic resolved tests ---" +# Issue: https://github.com/systemd/systemd/issues/22229 +# PR: https://github.com/systemd/systemd/pull/22231 +FILTERED_NAMES=( + "0.in-addr.arpa" + "255.255.255.255.in-addr.arpa" + "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa" + "hello.invalid" + "hello.alt" +) + +for name in "${FILTERED_NAMES[@]}"; do + (! run host "$name") + grep -qF "NXDOMAIN" "$RUN_OUT" +done + +# Follow-up +# Issue: https://github.com/systemd/systemd/issues/22401 +# PR: https://github.com/systemd/systemd/pull/22414 +run dig +noall +authority +comments SRV . +grep -qF "status: NOERROR" "$RUN_OUT" +grep -qE "IN\s+SOA\s+ns1\.unsigned\.test\." "$RUN_OUT" + + +: "--- ZONE: unsigned.test. ---" +run dig @ns1.unsigned.test +short unsigned.test A unsigned.test AAAA +grep -qF "10.0.0.101" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT" +run resolvectl query unsigned.test +grep -qF "10.0.0.10" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::101" "$RUN_OUT" +grep -qF "authenticated: no" "$RUN_OUT" +run dig @ns1.unsigned.test +short MX unsigned.test +grep -qF "15 mail.unsigned.test." "$RUN_OUT" +run resolvectl query --legend=no -t MX unsigned.test +grep -qF "unsigned.test IN MX 15 mail.unsigned.test" "$RUN_OUT" + + +: "--- ZONE: signed.test (static DNSSEC) ---" +# Check the trust chain (with and without systemd-resolved in between +# Issue: https://github.com/systemd/systemd/issues/22002 +# PR: https://github.com/systemd/systemd/pull/23289 +run_delv @ns1.unsigned.test signed.test +grep -qF "; fully validated" "$RUN_OUT" +run_delv signed.test +grep -qF "; fully validated" "$RUN_OUT" + +for addr in "${DNS_ADDRESSES[@]}"; do + run_delv "@$addr" -t A mail.signed.test + grep -qF "; fully validated" "$RUN_OUT" + run_delv "@$addr" -t AAAA mail.signed.test + grep -qF "; fully validated" "$RUN_OUT" +done +run resolvectl query mail.signed.test +grep -qF "10.0.0.11" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::11" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" + +run dig +short signed.test +grep -qF "10.0.0.10" "$RUN_OUT" +run resolvectl query signed.test +grep -qF "signed.test: 10.0.0.10" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +run dig @ns1.unsigned.test +short MX signed.test +grep -qF "10 mail.signed.test." "$RUN_OUT" +run resolvectl query --legend=no -t MX signed.test +grep -qF "signed.test IN MX 10 mail.signed.test" "$RUN_OUT" +# Check a non-existent domain +run dig +dnssec this.does.not.exist.signed.test +grep -qF "status: NXDOMAIN" "$RUN_OUT" +# Check a wildcard record +run resolvectl query -t TXT this.should.be.authenticated.wild.signed.test +grep -qF 'this.should.be.authenticated.wild.signed.test IN TXT "this is a wildcard"' "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +# Check SRV support +run resolvectl service _mysvc._tcp signed.test +grep -qF "myservice.signed.test:1234" "$RUN_OUT" +grep -qF "10.0.0.20" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::17" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +(! run resolvectl service _invalidsvc._udp signed.test) +grep -qE "invalidservice\.signed\.test' not found" "$RUN_OUT" +run resolvectl service _untrustedsvc._udp signed.test +grep -qF "myservice.untrusted.test:1111" "$RUN_OUT" +grep -qF "10.0.0.123" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +# Check OPENPGPKEY support +run_delv -t OPENPGPKEY 5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test +grep -qF "; fully validated" "$RUN_OUT" +run resolvectl openpgp mr.smith@signed.test +grep -qF "5a786cdc59c161cdafd818143705026636962198c66ed4c5b3da321e._openpgpkey.signed.test" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" + +# DNSSEC validation with multiple records of the same type for the same name +# Issue: https://github.com/systemd/systemd/issues/22002 +# PR: https://github.com/systemd/systemd/pull/23289 +check_domain() { + local domain="${1:?}" + local record="${2:?}" + local message="${3:?}" + local addr + + for addr in "${DNS_ADDRESSES[@]}"; do + run_delv "@$addr" -t "$record" "$domain" + grep -qF "$message" "$RUN_OUT" + done + + run_delv -t "$record" "$domain" + grep -qF "$message" "$RUN_OUT" + + run resolvectl query "$domain" + grep -qF "authenticated: yes" "$RUN_OUT" +} + +check_domain "dupe.signed.test" "A" "; fully validated" +check_domain "dupe.signed.test" "AAAA" "; negative response, fully validated" +check_domain "dupe-ipv6.signed.test" "AAAA" "; fully validated" +check_domain "dupe-ipv6.signed.test" "A" "; negative response, fully validated" +check_domain "dupe-mixed.signed.test" "A" "; fully validated" +check_domain "dupe-mixed.signed.test" "AAAA" "; fully validated" + +# Test resolution of CNAME chains +TIMESTAMP=$(date '+%F %T') +run resolvectl query -t A cname-chain.signed.test +grep -qF "follow14.final.signed.test IN A 10.0.0.14" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" + +monitor_check_rr "$TIMESTAMP" "follow10.so.close.signed.test IN CNAME follow11.yet.so.far.signed.test" +monitor_check_rr "$TIMESTAMP" "follow11.yet.so.far.signed.test IN CNAME follow12.getting.hot.signed.test" +monitor_check_rr "$TIMESTAMP" "follow12.getting.hot.signed.test IN CNAME follow13.almost.final.signed.test" +monitor_check_rr "$TIMESTAMP" "follow13.almost.final.signed.test IN CNAME follow14.final.signed.test" +monitor_check_rr "$TIMESTAMP" "follow14.final.signed.test IN A 10.0.0.14" + +# Non-existing RR + CNAME chain +run dig +dnssec AAAA cname-chain.signed.test +grep -qF "status: NOERROR" "$RUN_OUT" +grep -qE "^follow14\.final\.signed\.test\..+IN\s+NSEC\s+" "$RUN_OUT" + + +: "--- ZONE: onlinesign.test (dynamic DNSSEC) ---" +# Check the trust chain (with and without systemd-resolved in between +# Issue: https://github.com/systemd/systemd/issues/22002 +# PR: https://github.com/systemd/systemd/pull/23289 +run_delv @ns1.unsigned.test sub.onlinesign.test +grep -qF "; fully validated" "$RUN_OUT" +run_delv sub.onlinesign.test +grep -qF "; fully validated" "$RUN_OUT" + +run dig +short sub.onlinesign.test +grep -qF "10.0.0.133" "$RUN_OUT" +run resolvectl query sub.onlinesign.test +grep -qF "sub.onlinesign.test: 10.0.0.133" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +run dig @ns1.unsigned.test +short TXT onlinesign.test +grep -qF '"hello from onlinesign"' "$RUN_OUT" +run resolvectl query --legend=no -t TXT onlinesign.test +grep -qF 'onlinesign.test IN TXT "hello from onlinesign"' "$RUN_OUT" + +for addr in "${DNS_ADDRESSES[@]}"; do + run_delv "@$addr" -t A dual.onlinesign.test + grep -qF "10.0.0.135" "$RUN_OUT" + run_delv "@$addr" -t AAAA dual.onlinesign.test + grep -qF "fd00:dead:beef:cafe::135" "$RUN_OUT" + run_delv "@$addr" -t ANY ipv6.onlinesign.test + grep -qF "fd00:dead:beef:cafe::136" "$RUN_OUT" +done +run resolvectl query dual.onlinesign.test +grep -qF "10.0.0.135" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::135" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" +run resolvectl query ipv6.onlinesign.test +grep -qF "fd00:dead:beef:cafe::136" "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" + +# Check a non-existent domain +# Note: mod-onlinesign utilizes Minimally Covering NSEC Records, hence the +# different response than with "standard" DNSSEC +run dig +dnssec this.does.not.exist.onlinesign.test +grep -qF "status: NOERROR" "$RUN_OUT" +grep -qF "NSEC \\000.this.does.not.exist.onlinesign.test." "$RUN_OUT" +# Check a wildcard record +run resolvectl query -t TXT this.should.be.authenticated.wild.onlinesign.test +grep -qF 'this.should.be.authenticated.wild.onlinesign.test IN TXT "this is an onlinesign wildcard"' "$RUN_OUT" +grep -qF "authenticated: yes" "$RUN_OUT" + +# Resolve via dbus method +TIMESTAMP=$(date '+%F %T') +run busctl call org.freedesktop.resolve1 /org/freedesktop/resolve1 org.freedesktop.resolve1.Manager ResolveHostname 'isit' 0 secondsub.onlinesign.test 0 0 +grep -qF '10 0 0 134 "secondsub.onlinesign.test"' "$RUN_OUT" +monitor_check_rr "$TIMESTAMP" "secondsub.onlinesign.test IN A 10.0.0.134" + + +: "--- ZONE: untrusted.test (DNSSEC without propagated DS records) ---" +# Issue: https://github.com/systemd/systemd/issues/23955 +# FIXME +resolvectl flush-caches +#run dig +short untrusted.test A untrusted.test AAAA +#grep -qF "10.0.0.121" "$RUN_OUT" +#grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT" +run resolvectl query untrusted.test +grep -qF "untrusted.test:" "$RUN_OUT" +grep -qF "10.0.0.121" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::121" "$RUN_OUT" +grep -qF "authenticated: no" "$RUN_OUT" +run resolvectl service _mysvc._tcp untrusted.test +grep -qF "myservice.untrusted.test:1234" "$RUN_OUT" +grep -qF "10.0.0.123" "$RUN_OUT" +grep -qF "fd00:dead:beef:cafe::123" "$RUN_OUT" + +# Issue: https://github.com/systemd/systemd/issues/19472 +# 1) Query for a non-existing RR should return NOERROR + NSEC (?), not NXDOMAIN +# FIXME: re-enable once the issue is resolved +#run dig +dnssec AAAA untrusted.test +#grep -qF "status: NOERROR" "$RUN_OUT" +#grep -qE "^untrusted\.test\..+IN\s+NSEC\s+" "$RUN_OUT" +## 2) Query for a non-existing name should return NXDOMAIN, not SERVFAIL +#run dig +dnssec this.does.not.exist.untrusted.test +#grep -qF "status: NXDOMAIN" "$RUN_OUT" + +### Test resolvectl show-cache +run resolvectl show-cache +run resolvectl show-cache --json=short +run resolvectl show-cache --json=pretty + +# Issue: https://github.com/systemd/systemd/issues/29580 (part #1) +dig @127.0.0.54 signed.test + +systemctl stop resolvectl-monitor.service +systemctl stop resolvectl-monitor-json.service + +# Issue: https://github.com/systemd/systemd/issues/29580 (part #2) +# +# Check for any warnings regarding malformed messages +(! journalctl -u resolvectl-monitor.service -u reseolvectl-monitor-json.service -p warning --grep malformed) +# Verify that all queries recorded by `resolvectl monitor --json` produced a valid JSON +# with expected fields +journalctl -p info -o cat _SYSTEMD_UNIT="resolvectl-monitor-json.service" | while read -r line; do + # Check that both "question" and "answer" fields are arrays + # + # The expression is slightly more complicated due to the fact that the "answer" field is optional, + # so we need to select it only if it's present, otherwise the type == "array" check would fail + echo "$line" | jq -e '[. | .question, (select(has("answer")) | .answer) | type == "array"] | all' +done + +# Test serve stale feature and NFTSet= if nftables is installed +if command -v nft >/dev/null; then + ### Test without serve stale feature ### + NFT_FILTER_NAME=dns_port_filter + + drop_dns_outbound_traffic() { + nft add table inet $NFT_FILTER_NAME + nft add chain inet $NFT_FILTER_NAME output \{ type filter hook output priority 0 \; \} + nft add rule inet $NFT_FILTER_NAME output ip daddr 10.0.0.1 udp dport 53 drop + nft add rule inet $NFT_FILTER_NAME output ip daddr 10.0.0.1 tcp dport 53 drop + nft add rule inet $NFT_FILTER_NAME output ip6 daddr fd00:dead:beef:cafe::1 udp dport 53 drop + nft add rule inet $NFT_FILTER_NAME output ip6 daddr fd00:dead:beef:cafe::1 tcp dport 53 drop + } + + run dig stale1.unsigned.test -t A + grep -qE "NOERROR" "$RUN_OUT" + sleep 2 + drop_dns_outbound_traffic + set +e + run dig stale1.unsigned.test -t A + set -eux + grep -qE "no servers could be reached" "$RUN_OUT" + nft flush ruleset + + ### Test TIMEOUT with serve stale feature ### + + mkdir -p /run/systemd/resolved.conf.d + { + echo "[Resolve]" + echo "StaleRetentionSec=1d" + } >/run/systemd/resolved.conf.d/test.conf + ln -svf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf + restart_resolved + + run dig stale1.unsigned.test -t A + grep -qE "NOERROR" "$RUN_OUT" + sleep 2 + drop_dns_outbound_traffic + run dig stale1.unsigned.test -t A + grep -qE "NOERROR" "$RUN_OUT" + grep -qE "10.0.0.112" "$RUN_OUT" + + nft flush ruleset + + ### Test NXDOMAIN with serve stale feature ### + # NXDOMAIN response should replace the cache with NXDOMAIN response + run dig stale1.unsigned.test -t A + grep -qE "NOERROR" "$RUN_OUT" + # Delete stale1 record from zone + knotc zone-begin unsigned.test + knotc zone-unset unsigned.test stale1 A + knotc zone-commit unsigned.test + knotc reload + sleep 2 + run dig stale1.unsigned.test -t A + grep -qE "NXDOMAIN" "$RUN_OUT" + + nft flush ruleset + + ### NFTSet= test + nft add table inet sd_test + nft add set inet sd_test c '{ type cgroupsv2; }' + nft add set inet sd_test u '{ typeof meta skuid; }' + nft add set inet sd_test g '{ typeof meta skgid; }' + + # service + systemd-run --unit test-nft.service --service-type=exec -p DynamicUser=yes \ + -p 'NFTSet=cgroup:inet:sd_test:c user:inet:sd_test:u group:inet:sd_test:g' sleep 10000 + run nft list set inet sd_test c + grep -qF "test-nft.service" "$RUN_OUT" + uid=$(getent passwd test-nft | cut -d':' -f3) + run nft list set inet sd_test u + grep -qF "$uid" "$RUN_OUT" + gid=$(getent passwd test-nft | cut -d':' -f4) + run nft list set inet sd_test g + grep -qF "$gid" "$RUN_OUT" + systemctl stop test-nft.service + + # scope + run systemd-run --scope -u test-nft.scope -p 'NFTSet=cgroup:inet:sd_test:c' nft list set inet sd_test c + grep -qF "test-nft.scope" "$RUN_OUT" + + mkdir -p /run/systemd/system + # socket + { + echo "[Socket]" + echo "ListenStream=12345" + echo "BindToDevice=lo" + echo "NFTSet=cgroup:inet:sd_test:c" + } >/run/systemd/system/test-nft.socket + { + echo "[Service]" + echo "ExecStart=/usr/bin/sleep 10000" + } >/run/systemd/system/test-nft.service + systemctl daemon-reload + systemctl start test-nft.socket + systemctl status test-nft.socket + run nft list set inet sd_test c + grep -qF "test-nft.socket" "$RUN_OUT" + systemctl stop test-nft.socket + rm -f /run/systemd/system/test-nft.{socket,service} + + # slice + mkdir /run/systemd/system/system.slice.d + { + echo "[Slice]" + echo "NFTSet=cgroup:inet:sd_test:c" + } >/run/systemd/system/system.slice.d/00-test-nft.conf + systemctl daemon-reload + run nft list set inet sd_test c + grep -qF "system.slice" "$RUN_OUT" + rm -rf /run/systemd/system/system.slice.d + + nft flush ruleset +else + echo "nftables is not installed. Skipped serve stale feature and NFTSet= tests." +fi + +### Test resolvectl show-server-state ### +run resolvectl show-server-state +grep -qF "10.0.0.1" "$RUN_OUT" +grep -qF "Interface" "$RUN_OUT" + +run resolvectl show-server-state --json=short +grep -qF "10.0.0.1" "$RUN_OUT" +grep -qF "Interface" "$RUN_OUT" + +run resolvectl show-server-state --json=pretty +grep -qF "10.0.0.1" "$RUN_OUT" +grep -qF "Interface" "$RUN_OUT" + +### Test resolvectl statistics ### +run resolvectl statistics +grep -qF "Transactions" "$RUN_OUT" +grep -qF "Cache" "$RUN_OUT" +grep -qF "Failure Transactions" "$RUN_OUT" +grep -qF "DNSSEC Verdicts" "$RUN_OUT" + +run resolvectl statistics --json=short +grep -qF "transactions" "$RUN_OUT" +grep -qF "cache" "$RUN_OUT" +grep -qF "dnssec" "$RUN_OUT" + +run resolvectl statistics --json=pretty +grep -qF "transactions" "$RUN_OUT" +grep -qF "cache" "$RUN_OUT" +grep -qF "dnssec" "$RUN_OUT" + +### Test resolvectl reset-statistics ### +run resolvectl reset-statistics + +run resolvectl reset-statistics --json=pretty + +run resolvectl reset-statistics --json=short + +# Check if resolved exits cleanly. +restart_resolved + +touch /testok diff --git a/test/units/testsuite-76.service b/test/units/testsuite-76.service new file mode 100644 index 0000000..3c8a9e8 --- /dev/null +++ b/test/units/testsuite-76.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-76-SYSCTL + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-76.sh b/test/units/testsuite-76.sh new file mode 100755 index 0000000..855d0ef --- /dev/null +++ b/test/units/testsuite-76.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +echo "foo.bar=42" >/tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf +assert_rc 1 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf + +echo "-foo.foo=42" >/tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl /tmp/foo.conf +assert_rc 0 /usr/lib/systemd/systemd-sysctl --strict /tmp/foo.conf + +if ! systemd-detect-virt --quiet --container; then + ip link add hoge type dummy + udevadm wait /sys/class/net/hoge + + cat >/tmp/foo.conf </proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp + echo 0 >/proc/sys/net/ipv4/conf/hoge/bootp_relay + echo 0 >/proc/sys/net/ipv4/conf/hoge/disable_policy + + assert_rc 0 /usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/hoge /tmp/foo.conf + assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/drop_gratuitous_arp)" "1" + assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/bootp_relay)" "1" + assert_eq "$(cat /proc/sys/net/ipv4/conf/hoge/disable_policy)" "0" +fi + +touch /testok diff --git a/test/units/testsuite-77-client.sh b/test/units/testsuite-77-client.sh new file mode 100755 index 0000000..0d9487a --- /dev/null +++ b/test/units/testsuite-77-client.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +assert_eq "$LISTEN_FDS" "1" +assert_eq "$LISTEN_FDNAMES" "socket" +read -r -u 3 text +assert_eq "$text" "Socket" diff --git a/test/units/testsuite-77-run.sh b/test/units/testsuite-77-run.sh new file mode 100755 index 0000000..fadd34d --- /dev/null +++ b/test/units/testsuite-77-run.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +assert_eq "$LISTEN_FDS" "1" +assert_eq "$LISTEN_FDNAMES" "new-file" +read -r -u 3 text +assert_eq "$text" "New" diff --git a/test/units/testsuite-77-server.socket b/test/units/testsuite-77-server.socket new file mode 100644 index 0000000..4305077 --- /dev/null +++ b/test/units/testsuite-77-server.socket @@ -0,0 +1,6 @@ +[Unit] +Description=TEST-77-OPENFILE server socket + +[Socket] +ListenStream=/tmp/test.sock +Accept=yes diff --git a/test/units/testsuite-77-server@.service b/test/units/testsuite-77-server@.service new file mode 100644 index 0000000..8e99ac8 --- /dev/null +++ b/test/units/testsuite-77-server@.service @@ -0,0 +1,7 @@ +[Unit] +Description=TEST-77-OPENFILE server + +[Service] +ExecStart=echo "Socket" +StandardInput=socket +StandardOutput=socket diff --git a/test/units/testsuite-77.service b/test/units/testsuite-77.service new file mode 100644 index 0000000..6ed8add --- /dev/null +++ b/test/units/testsuite-77.service @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-77-OPENFILE + +[Service] +OpenFile=/test-77-open.dat:open:read-only +OpenFile=/test-77-file.dat +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-77.sh b/test/units/testsuite-77.sh new file mode 100755 index 0000000..2b85a8c --- /dev/null +++ b/test/units/testsuite-77.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +export SYSTEMD_LOG_LEVEL=debug + +assert_eq "$LISTEN_FDS" "2" +assert_eq "$LISTEN_FDNAMES" "open:test-77-file.dat" +read -r -u 3 text +assert_eq "$text" "Open" +read -r -u 4 text +assert_eq "$text" "File" + +# Test for socket +systemctl start testsuite-77-server.socket +systemd-run -p OpenFile=/tmp/test.sock:socket:read-only \ + --wait \ + --pipe \ + /usr/lib/systemd/tests/testdata/units/testsuite-77-client.sh + +# Tests for D-Bus +diff <(systemctl show -p OpenFile testsuite-77) - </test-77-new-file.dat +systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only "$(dirname "$0")"/testsuite-77-run.sh + +assert_rc 202 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only "$(dirname "$0")"/testsuite-77-run.sh + +assert_rc 0 systemd-run --wait -p OpenFile=/test-77-new-file.dat:new-file:read-only -p OpenFile=/test-77-mssing-file.dat:missing-file:read-only,graceful "$(dirname "$0")"/testsuite-77-run.sh + +# End +touch /testok diff --git a/test/units/testsuite-78.service b/test/units/testsuite-78.service new file mode 100644 index 0000000..05f3eff --- /dev/null +++ b/test/units/testsuite-78.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-78-SIGQUEUE + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh diff --git a/test/units/testsuite-78.sh b/test/units/testsuite-78.sh new file mode 100755 index 0000000..46afd3c --- /dev/null +++ b/test/units/testsuite-78.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +if ! env --block-signal=SIGUSR1 true 2> /dev/null ; then + echo "env tool too old, can't block signals, skipping test." >&2 + echo OK >/testok + exit 0 +fi + +systemd-analyze log-level debug + +UNIT="test-sigqueue-$RANDOM.service" + +systemd-run -u "$UNIT" -p Type=notify -p DynamicUser=1 -- env --block-signal=SIGRTMIN+7 systemd-notify --exec --ready \; sleep infinity + +systemctl kill --kill-whom=main --kill-value=4 --signal=SIGRTMIN+7 "$UNIT" +systemctl kill --kill-whom=main --kill-value=4 --signal=SIGRTMIN+7 "$UNIT" +systemctl kill --kill-whom=main --kill-value=7 --signal=SIGRTMIN+7 "$UNIT" +systemctl kill --kill-whom=main --kill-value=16 --signal=SIGRTMIN+7 "$UNIT" +systemctl kill --kill-whom=main --kill-value=32 --signal=SIGRTMIN+7 "$UNIT" +systemctl kill --kill-whom=main --kill-value=16 --signal=SIGRTMIN+7 "$UNIT" + +# We simply check that six signals are queued now. There's no easy way to check +# from shell which ones those are, hence we don't check that. +P=$(systemctl show -P MainPID "$UNIT") + +test "$(grep SigQ: /proc/"$P"/status | cut -d: -f2 | cut -d/ -f1)" -eq 6 + +systemctl stop $UNIT + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-79.service b/test/units/testsuite-79.service new file mode 100644 index 0000000..f2d24df --- /dev/null +++ b/test/units/testsuite-79.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-79-MEMPRESS + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +MemoryAccounting=1 diff --git a/test/units/testsuite-79.sh b/test/units/testsuite-79.sh new file mode 100755 index 0000000..205f7f3 --- /dev/null +++ b/test/units/testsuite-79.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# We not just test if the file exists, but try to read from it, since if +# CONFIG_PSI_DEFAULT_DISABLED is set in the kernel the file will exist and can +# be opened, but any read()s will fail with EOPNOTSUPP, which we want to +# detect. +if ! cat /proc/pressure/memory >/dev/null ; then + echo "kernel too old, has no PSI." >&2 + echo OK >/testok + exit 0 +fi + +systemd-analyze log-level debug + +CGROUP=/sys/fs/cgroup/"$(systemctl show testsuite-79.service -P ControlGroup)" +test -d "$CGROUP" + +if ! test -f "$CGROUP"/memory.pressure ; then + echo "No memory accounting/PSI delegated via cgroup, can't test." >&2 + echo OK >/testok + exit 0 +fi + +UNIT="test-mempress-$RANDOM.service" +SCRIPT="/tmp/mempress-$RANDOM.sh" + +cat >"$SCRIPT" <<'EOF' +#!/bin/bash + +set -ex + +export +id + +test -n "$MEMORY_PRESSURE_WATCH" +test "$MEMORY_PRESSURE_WATCH" != /dev/null +test -w "$MEMORY_PRESSURE_WATCH" + +ls -al "$MEMORY_PRESSURE_WATCH" + +EXPECTED="$(echo -n -e "some 123000 2000000\x00" | base64)" + +test "$EXPECTED" = "$MEMORY_PRESSURE_WRITE" + +EOF + +chmod +x "$SCRIPT" + +systemd-run -u "$UNIT" -p Type=exec -p ProtectControlGroups=1 -p DynamicUser=1 -p MemoryPressureWatch=on -p MemoryPressureThresholdSec=123ms -p BindPaths=$SCRIPT --wait "$SCRIPT" + +rm "$SCRIPT" + +systemd-analyze log-level info + +touch /testok diff --git a/test/units/testsuite-80.service b/test/units/testsuite-80.service new file mode 100644 index 0000000..4c7f5d5 --- /dev/null +++ b/test/units/testsuite-80.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-80-NOTIFYACCESS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-80.sh b/test/units/testsuite-80.sh new file mode 100755 index 0000000..97b222a --- /dev/null +++ b/test/units/testsuite-80.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2016 +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +mkfifo /tmp/syncfifo1 /tmp/syncfifo2 + +sync_in() { + read -r x < /tmp/syncfifo1 + test "$x" = "$1" +} + +sync_out() { + echo "$1" > /tmp/syncfifo2 +} + +export SYSTEMD_LOG_LEVEL=debug + +systemctl --no-block start notify.service + +sync_in a + +assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all" +assert_eq "$(systemctl show notify.service -p StatusText --value)" "Test starts" + +sync_out b +sync_in c + +assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "main" +assert_eq "$(systemctl show notify.service -p StatusText --value)" "Sending READY=1 in an unprivileged process" +assert_rc 3 systemctl --quiet is-active notify.service + +sync_out d +sync_in e + +systemctl --quiet is-active notify.service +assert_eq "$(systemctl show notify.service -p StatusText --value)" "OK" +assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "none" + +systemctl stop notify.service +assert_eq "$(systemctl show notify.service -p NotifyAccess --value)" "all" + +rm /tmp/syncfifo1 /tmp/syncfifo2 + +# Now test basic fdstore behaviour + +MYSCRIPT="/tmp/myscript$RANDOM.sh" +cat >> "$MYSCRIPT" <<'EOF' +#!/usr/bin/env bash +set -eux +set -o pipefail +test "$FDSTORE" -eq 7 +N="/tmp/$RANDOM" +echo $RANDOM > "$N" +systemd-notify --fd=4 --fdname=quux --pid=parent 4< "$N" +rm "$N" +systemd-notify --ready +exec sleep infinity +EOF + +chmod +x "$MYSCRIPT" + +MYUNIT="myunit$RANDOM.service" +systemd-run -u "$MYUNIT" -p Type=notify -p FileDescriptorStoreMax=7 "$MYSCRIPT" + +test "$(systemd-analyze fdstore "$MYUNIT" | wc -l)" -eq 2 +systemd-analyze fdstore "$MYUNIT" --json=short +systemd-analyze fdstore "$MYUNIT" --json=short | grep -P -q '\[{"fdname":"quux","type":.*,"devno":\[.*\],"inode":.*,"rdevno":null,"path":"/tmp/.*","flags":"ro"}\]' + +systemctl stop "$MYUNIT" +rm "$MYSCRIPT" + +systemd-analyze log-level debug + +# Test fdstore pinning (this will pull in fdstore-pin.service fdstore-nopin.service) +systemctl start fdstore-pin.target + +assert_eq "$(systemctl show fdstore-pin.service -P FileDescriptorStorePreserve)" yes +assert_eq "$(systemctl show fdstore-nopin.service -P FileDescriptorStorePreserve)" restart +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1 + +# The file descriptor store should survive service restarts +systemctl restart fdstore-pin.service fdstore-nopin.service + +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running + +# It should not survive the service stop plus a later start (unless pinned) +systemctl stop fdstore-pin.service fdstore-nopin.service + +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0 +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead + +systemctl start fdstore-pin.service fdstore-nopin.service + +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0 +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" running +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" running + +systemctl stop fdstore-pin.service fdstore-nopin.service + +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 1 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0 +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead-resources-pinned +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead + +systemctl clean fdstore-pin.service --what=fdstore + +assert_eq "$(systemctl show fdstore-pin.service -P NFileDescriptorStore)" 0 +assert_eq "$(systemctl show fdstore-nopin.service -P NFileDescriptorStore)" 0 +assert_eq "$(systemctl show fdstore-pin.service -P SubState)" dead +assert_eq "$(systemctl show fdstore-nopin.service -P SubState)" dead + +touch /testok diff --git a/test/units/testsuite-81.debug-generator.sh b/test/units/testsuite-81.debug-generator.sh new file mode 100755 index 0000000..fddf85a --- /dev/null +++ b/test/units/testsuite-81.debug-generator.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-debug-generator" +OUT_DIR="$(mktemp -d /tmp/debug-generator.XXX)" + +at_exit() { + rm -frv "${OUT_DIR:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +# Potential FIXME: +# - debug-generator should gracefully handle duplicated mask/wants +# - also, handle gracefully empty mask/wants +ARGS=( + "systemd.mask=masked-no-suffix" + "systemd.mask=masked.service" + "systemd.mask=masked.socket" + "systemd.wants=wanted-no-suffix" + "systemd.wants=wanted.service" + "systemd.wants=wanted.mount" + "rd.systemd.mask=masked-initrd.service" + "rd.systemd.wants=wanted-initrd.service" +) + +# Regular (non-initrd) scenario +# +: "debug-shell: regular" +CMDLINE="ro root=/ ${ARGS[*]} rd.systemd.debug_shell" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/early/masked-no-suffix.service" /dev/null +link_eq "$OUT_DIR/early/masked.service" /dev/null +link_eq "$OUT_DIR/early/masked.socket" /dev/null +link_endswith "$OUT_DIR/early/default.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service +link_endswith "$OUT_DIR/early/default.target.wants/wanted.service" /lib/systemd/system/wanted.service +link_endswith "$OUT_DIR/early/default.target.wants/wanted.mount" /lib/systemd/system/wanted.mount +# Following stuff should be ignored, as it's prefixed with rd. +test ! -h "$OUT_DIR/early/masked-initrd.service" +test ! -h "$OUT_DIR/early/default.target.wants/wants-initrd.service" +test ! -h "$OUT_DIR/early/default.target.wants/debug-shell.service" +test ! -d "$OUT_DIR/early/initrd.target.wants" + +# Let's re-run the generator with systemd.debug_shell that should be honored +: "debug-shell: regular + systemd.debug_shell" +CMDLINE="$CMDLINE systemd.debug_shell" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/early/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service + +# Same thing, but with custom tty +: "debug-shell: regular + systemd.debug_shell=/dev/tty666" +CMDLINE="$CMDLINE systemd.debug_shell=/dev/tty666" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/early/default.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service +grep -F "/dev/tty666" "$OUT_DIR/early/debug-shell.service.d/50-tty.conf" + +# Now override the default target via systemd.unit= +: "debug-shell: regular + systemd.unit=" +CMDLINE="$CMDLINE systemd.unit=my-fancy.target" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/early/masked-no-suffix.service" /dev/null +link_eq "$OUT_DIR/early/masked.service" /dev/null +link_eq "$OUT_DIR/early/masked.socket" /dev/null +link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted-no-suffix.service" /lib/systemd/system/wanted-no-suffix.service +link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.service" /lib/systemd/system/wanted.service +link_endswith "$OUT_DIR/early/my-fancy.target.wants/wanted.mount" /lib/systemd/system/wanted.mount +link_endswith "$OUT_DIR/early/my-fancy.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service +test ! -d "$OUT_DIR/early/default.target.wants" + + +# Initrd scenario +: "debug-shell: initrd" +CMDLINE="ro root=/ ${ARGS[*]} systemd.debug_shell" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/early/masked-initrd.service" /dev/null +link_endswith "$OUT_DIR/early/initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service +# The non-initrd stuff (i.e. without the rd. suffix) should be ignored in +# this case +test ! -h "$OUT_DIR/early/masked-no-suffix.service" +test ! -h "$OUT_DIR/early/masked.service" +test ! -h "$OUT_DIR/early/masked.socket" +test ! -h "$OUT_DIR/early/initrd.target.wants/debug-shell.service" +test ! -d "$OUT_DIR/early/default.target.wants" + +# Again, but with rd.systemd.debug_shell +: "debug-shell: initrd + rd.systemd.debug_shell" +CMDLINE="$CMDLINE rd.systemd.debug_shell" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/early/initrd.target.wants/debug-shell.service" /lib/systemd/system/debug-shell.service + +# Override the default target +: "debug-shell: initrd + rd.systemd.unit" +CMDLINE="$CMDLINE rd.systemd.unit=my-fancy-initrd.target" +SYSTEMD_IN_INITRD=1 SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_eq "$OUT_DIR/early/masked-initrd.service" /dev/null +link_endswith "$OUT_DIR/early/my-fancy-initrd.target.wants/wanted-initrd.service" /lib/systemd/system/wanted-initrd.service +test ! -d "$OUT_DIR/early/initrd.target.wants" diff --git a/test/units/testsuite-81.environment-d-generator.sh b/test/units/testsuite-81.environment-d-generator.sh new file mode 100755 index 0000000..5bc3978 --- /dev/null +++ b/test/units/testsuite-81.environment-d-generator.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator" +CONFIG_FILE="/run/environment.d/99-test.conf" +OUT_FILE="$(mktemp)" + +at_exit() { + set +e + rm -frv "${CONFIG_FILE:?}" "${OUT_FILE:?}" + systemctl -M testuser@.host --user daemon-reload +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" +mkdir -p /run/environment.d/ + +cat >"$CONFIG_FILE" <"$OUT_FILE" + +# Check if the generator is correctly called in a user session +systemctl -M testuser@.host --user daemon-reload +systemctl -M testuser@.host --user show-environment | tee "$OUT_FILE" +check_environment "$OUT_FILE" + +(! "$GENERATOR_BIN" foo) diff --git a/test/units/testsuite-81.fstab-generator.sh b/test/units/testsuite-81.fstab-generator.sh new file mode 100755 index 0000000..50c4b2f --- /dev/null +++ b/test/units/testsuite-81.fstab-generator.sh @@ -0,0 +1,406 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235,SC2233 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-fstab-generator" +NETWORK_FS_RX="^(afs|ceph|cifs|gfs|gfs2|ncp|ncpfs|nfs|nfs4|ocfs2|orangefs|pvfs2|smb3|smbfs|davfs|glusterfs|lustre|sshfs)$" +OUT_DIR="$(mktemp -d /tmp/fstab-generator.XXX)" +FSTAB="$(mktemp)" + +at_exit() { + rm -fr "${OUT_DIR:?}" "${FSTAB:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +FSTAB_GENERAL=( + # Valid entries + "/dev/test2 /nofail ext4 nofail 0 0" + "/dev/test3 /regular btrfs defaults 0 0" + "/dev/test4 /x-systemd.requires xfs x-systemd.requires=foo.service 0 0" + "/dev/test5 /x-systemd.before-after xfs x-systemd.before=foo.service,x-systemd.after=bar.mount 0 0" + "/dev/test6 /x-systemd.wanted-required-by xfs x-systemd.wanted-by=foo.service,x-systemd.required-by=bar.device 0 0" + "/dev/test7 /x-systemd.requires-mounts-for xfs x-systemd.requires-mounts-for=/foo/bar/baz 0 0" + "/dev/test8 /x-systemd.automount-idle-timeout vfat x-systemd.automount,x-systemd.idle-timeout=50s 0 0" + "/dev/test9 /x-systemd.makefs xfs x-systemd.makefs 0 0" + "/dev/test10 /x-systemd.growfs xfs x-systemd.growfs 0 0" + "/dev/test11 /_netdev ext4 defaults,_netdev 0 0" + "/dev/test12 /_rwonly ext4 x-systemd.rw-only 0 0" + "/dev/test13 /chaos1 zfs x-systemd.rw-only,x-systemd.requires=hello.service,x-systemd.after=my.device 0 0" + "/dev/test14 /chaos2 zfs x.systemd.wanted-by=foo.service,x-systemd.growfs,x-systemd.makefs 0 0" + "/dev/test15 /fstype/auto auto defaults 0 0" + "/dev/test16 /fsck/me ext4 defaults 0 1" + "/dev/test17 /also/fsck/me ext4 defaults,x-systemd.requires-mounts-for=/var/lib/foo 0 99" + "/dev/test18 /swap swap defaults 0 0" + "/dev/test19 /swap/makefs swap defaults,x-systemd.makefs 0 0" + "/dev/test20 /var xfs defaults,x-systemd.device-timeout=1h 0 0" + "/dev/test21 /usr ext4 defaults 0 1" + "/dev/test22 /initrd/mount ext2 defaults,x-systemd.rw-only,x-initrd.mount 0 1" + "/dev/test23 /initrd/mount/nofail ext3 defaults,nofail,x-initrd.mount 0 1" + "/dev/test24 /initrd/mount/deps ext4 x-initrd.mount,x-systemd.before=early.service,x-systemd.after=late.service 0 1" + + # Incomplete, but valid entries + "/dev/incomplete1 /incomplete1" + "/dev/incomplete2 /incomplete2 ext4" + "/dev/incomplete3 /incomplete3 ext4 defaults" + "/dev/incomplete4 /incomplete4 ext4 defaults 0" + + # Remote filesystems + "/dev/remote1 /nfs nfs bg 0 0" + "/dev/remote2 /nfs4 nfs4 bg 0 0" + "bar.tld:/store /remote/storage nfs ro,x-systemd.wanted-by=store.service 0 0" + "user@host.tld:/remote/dir /remote/top-secret sshfs rw,x-systemd.before=naughty.service 0 0" + "foo.tld:/hello /hello/world ceph defaults 0 0" + "//192.168.0.1/storage /cifs-storage cifs automount,nofail 0 0" +) + +FSTAB_GENERAL_ROOT=( + # rootfs with bunch of options we should ignore and fsck enabled + "/dev/test1 / ext4 noauto,nofail,x-systemd.automount,x-systemd.wanted-by=foo,x-systemd.required-by=bar 0 1" + "${FSTAB_GENERAL[@]}" +) + +FSTAB_MINIMAL=( + "/dev/loop1 /foo/bar ext3 defaults 0 0" +) + +FSTAB_DUPLICATE=( + "/dev/dup1 / ext4 defaults 0 1" + "/dev/dup2 / ext4 defaults,x-systemd.requires=foo.mount 0 2" +) + +FSTAB_INVALID=( + # Ignored entries + "/dev/ignored1 /sys/fs/cgroup/foo ext4 defaults 0 0" + "/dev/ignored2 /sys/fs/selinux ext4 defaults 0 0" + "/dev/ignored3 /dev/console ext4 defaults 0 0" + "/dev/ignored4 /proc/kmsg ext4 defaults 0 0" + "/dev/ignored5 /proc/sys ext4 defaults 0 0" + "/dev/ignored6 /proc/sys/kernel/random/boot_id ext4 defaults 0 0" + "/dev/ignored7 /run/host ext4 defaults 0 0" + "/dev/ignored8 /run/host/foo ext4 defaults 0 0" + "/dev/ignored9 /autofs autofs defaults 0 0" + "/dev/invalid1 not-a-path ext4 defaults 0 0" + "" + "/dev/invalid1" + " " + "\\" + "$" +) + +check_fstab_mount_units() { + local what where fstype opts passno unit + local item opt split_options filtered_options supp service device arg + local array_name="${1:?}" + local out_dir="${2:?}/normal" + # Get a reference to the array from its name + local -n fstab_entries="$array_name" + + # Running the checks in a container is pretty much useless, since we don't + # generate any mounts, but don't skip the whole test to test the "skip" + # paths as well + in_container && return 0 + + for item in "${fstab_entries[@]}"; do + # Don't use a pipe here, as it would make the variables out of scope + read -r what where fstype opts _ passno <<< "$item" + + # Skip non-initrd mounts in initrd + if in_initrd_host && ! [[ "$opts" =~ x-initrd.mount ]]; then + continue + fi + + if [[ "$fstype" == swap ]]; then + unit="$(systemd-escape --suffix=swap --path "${what:?}")" + cat "$out_dir/$unit" + + grep -qE "^What=$what$" "$out_dir/$unit" + if [[ "$opts" != defaults ]]; then + grep -qE "^Options=$opts$" "$out_dir/$unit" + fi + + if [[ "$opts" =~ x-systemd.makefs ]]; then + service="$(systemd-escape --template=systemd-mkswap@.service --path "$what")" + test -e "$out_dir/$service" + fi + + continue + fi + + # If we're parsing host's fstab in initrd, prefix all mount targets + # with /sysroot + in_initrd_host && where="/sysroot${where:?}" + unit="$(systemd-escape --suffix=mount --path "${where:?}")" + cat "$out_dir/$unit" + + # Check the general stuff + grep -qE "^What=$what$" "$out_dir/$unit" + grep -qE "^Where=$where$" "$out_dir/$unit" + if [[ -n "$fstype" ]] && [[ "$fstype" != auto ]]; then + grep -qE "^Type=$fstype$" "$out_dir/$unit" + fi + if [[ -n "$opts" ]] && [[ "$opts" != defaults ]]; then + # Some options are not propagated to the generated unit + if [[ "$where" == / ]]; then + filtered_options="$(opt_filter "$opts" "(noauto|nofail|x-systemd.(wanted-by=|required-by=|automount|device-timeout=))")" + else + filtered_options="$(opt_filter "$opts" "^x-systemd.device-timeout=")" + fi + + if [[ "${filtered_options[*]}" != defaults ]]; then + grep -qE "^Options=.*$filtered_options.*$" "$out_dir/$unit" + fi + fi + + if ! [[ "$opts" =~ (noauto|x-systemd.(wanted-by=|required-by=|automount)) ]]; then + # We don't create the Requires=/Wants= symlinks for noauto/automount mounts + # and for mounts that use x-systemd.wanted-by=/required-by= + if in_initrd_host; then + if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then + link_eq "$out_dir/initrd-fs.target.requires/$unit" "../$unit" + else + link_eq "$out_dir/initrd-fs.target.wants/$unit" "../$unit" + fi + elif [[ "$fstype" =~ $NETWORK_FS_RX || "$opts" =~ _netdev ]]; then + # Units with network filesystems should have a Requires= dependency + # on the remote-fs.target, unless they use nofail or are an nfs "bg" + # mounts, in which case the dependency is downgraded to Wants= + if [[ "$opts" =~ nofail ]] || [[ "$fstype" =~ ^(nfs|nfs4) && "$opts" =~ bg ]]; then + link_eq "$out_dir/remote-fs.target.wants/$unit" "../$unit" + else + link_eq "$out_dir/remote-fs.target.requires/$unit" "../$unit" + fi + else + # Similarly, local filesystems should have a Requires= dependency on + # the local-fs.target, unless they use nofail, in which case the + # dependency is downgraded to Wants=. Rootfs is a special case, + # since we always ignore nofail there + if [[ "$where" == / ]] || ! [[ "$opts" =~ nofail ]]; then + link_eq "$out_dir/local-fs.target.requires/$unit" "../$unit" + else + link_eq "$out_dir/local-fs.target.wants/$unit" "../$unit" + fi + fi + fi + + if [[ "${passno:=0}" -ne 0 ]]; then + # Generate systemd-fsck@.service dependencies, if applicable + if in_initrd && [[ "$where" == / || "$where" == /usr ]]; then + continue + fi + + if [[ "$where" == / ]]; then + link_endswith "$out_dir/local-fs.target.wants/systemd-fsck-root.service" "/lib/systemd/system/systemd-fsck-root.service" + else + service="$(systemd-escape --template=systemd-fsck@.service --path "$what")" + grep -qE "^After=$service$" "$out_dir/$unit" + if [[ "$where" == /usr ]]; then + grep -qE "^Wants=$service$" "$out_dir/$unit" + else + grep -qE "^Requires=$service$" "$out_dir/$unit" + fi + fi + fi + + # Check various x-systemd options + # + # First, split them into an array to make splitting them even further + # easier + IFS="," read -ra split_options <<< "$opts" + # and process them one by one. + # + # Note: the "machinery" below might (and probably does) miss some + # combinations of supported options, so tread carefully + for opt in "${split_options[@]}"; do + if [[ "$opt" =~ ^x-systemd.requires= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^Requires=$service$" "$out_dir/$unit" + grep -qE "^After=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.before= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^Before=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.after= ]]; then + service="$(opt_get_arg "$opt")" + grep -qE "^After=$service$" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd.wanted-by= ]]; then + service="$(opt_get_arg "$opt")" + if [[ "$where" == / ]]; then + # This option is ignored for rootfs mounts + (! link_eq "$out_dir/$service.wants/$unit" "../$unit") + else + link_eq "$out_dir/$service.wants/$unit" "../$unit" + fi + elif [[ "$opt" =~ ^x-systemd.required-by= ]]; then + service="$(opt_get_arg "$opt")" + if [[ "$where" == / ]]; then + # This option is ignored for rootfs mounts + (! link_eq "$out_dir/$service.requires/$unit" "../$unit") + else + link_eq "$out_dir/$service.requires/$unit" "../$unit" + fi + elif [[ "$opt" =~ ^x-systemd.requires-mounts-for= ]]; then + arg="$(opt_get_arg "$opt")" + grep -qE "^RequiresMountsFor=$arg$" "$out_dir/$unit" + elif [[ "$opt" == x-systemd.device-bound ]]; then + # This is implied for fstab mounts + : + elif [[ "$opt" == x-systemd.automount ]]; then + # The $unit should have an accompanying automount unit + supp="$(systemd-escape --suffix=automount --path "$where")" + if [[ "$where" == / ]]; then + # This option is ignored for rootfs mounts + test ! -e "$out_dir/$supp" + (! link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp") + else + test -e "$out_dir/$supp" + link_eq "$out_dir/local-fs.target.requires/$supp" "../$supp" + fi + elif [[ "$opt" =~ ^x-systemd.idle-timeout= ]]; then + # The timeout applies to the automount unit, not the original + # mount one + arg="$(opt_get_arg "$opt")" + supp="$(systemd-escape --suffix=automount --path "$where")" + grep -qE "^TimeoutIdleSec=$arg$" "$out_dir/$supp" + elif [[ "$opt" =~ ^x-systemd.device-timeout= ]]; then + arg="$(opt_get_arg "$opt")" + device="$(systemd-escape --suffix=device --path "$what")" + grep -qE "^JobRunningTimeoutSec=$arg$" "$out_dir/${device}.d/50-device-timeout.conf" + elif [[ "$opt" == x-systemd.makefs ]]; then + service="$(systemd-escape --template=systemd-makefs@.service --path "$what")" + test -e "$out_dir/$service" + link_eq "$out_dir/${unit}.requires/$service" "../$service" + elif [[ "$opt" == x-systemd.rw-only ]]; then + grep -qE "^ReadWriteOnly=yes$" "$out_dir/$unit" + elif [[ "$opt" == x-systemd.growfs ]]; then + service="$(systemd-escape --template=systemd-growfs@.service --path "$where")" + link_endswith "$out_dir/${unit}.wants/$service" "/lib/systemd/system/systemd-growfs@.service" + elif [[ "$opt" == bg ]] && [[ "$fstype" =~ ^(nfs|nfs4)$ ]]; then + # We "convert" nfs bg mounts to fg, so we can do the job-control + # ourselves + grep -qE "^Options=.*\bx-systemd.mount-timeout=infinity\b" "$out_dir/$unit" + grep -qE "^Options=.*\bfg\b.*" "$out_dir/$unit" + elif [[ "$opt" =~ ^x-systemd\. ]]; then + echo >&2 "Unhandled mount option: $opt" + exit 1 + fi + done + done +} + +: "fstab-generator: regular" +printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR" + +# Skip the rest when running in a container, as it makes little sense to check +# initrd-related stuff there and fstab-generator might have a bit strange +# behavior during certain tests, like https://github.com/systemd/systemd/issues/27156 +if in_container; then + echo "Running in a container, skipping the rest of the fstab-generator tests..." + exit 0 +fi + +# In this mode we treat the entries as "regular" ones +: "fstab-generator: initrd - initrd fstab" +printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_SYSROOT_FSTAB=/dev/null check_fstab_mount_units FSTAB_GENERAL "$OUT_DIR" + +# In this mode we prefix the mount target with /sysroot and ignore all mounts +# that don't have the x-initrd.mount flag +: "fstab-generator: initrd - host fstab" +printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_GENERAL_ROOT "$OUT_DIR" + +# Check the default stuff that we (almost) always create in initrd +: "fstab-generator: initrd default" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null run_and_list "$GENERATOR_BIN" "$OUT_DIR" +test -e "$OUT_DIR/normal/sysroot.mount" +test -e "$OUT_DIR/normal/systemd-fsck-root.service" +link_eq "$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount" +link_eq "$OUT_DIR/normal/initrd-root-fs.target.requires/sysroot.mount" "../sysroot.mount" + +: "fstab-generator: run as systemd-sysroot-fstab-check in initrd" +ln -svf "$GENERATOR_BIN" /tmp/systemd-sysroot-fstab-check +(! /tmp/systemd-sysroot-fstab-check foo) +(! SYSTEMD_IN_INITRD=0 /tmp/systemd-sysroot-fstab-check) +printf "%s\n" "${FSTAB_GENERAL[@]}" >"$FSTAB" +SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB="$FSTAB" /tmp/systemd-sysroot-fstab-check + +: "fstab-generator: duplicate" +printf "%s\n" "${FSTAB_DUPLICATE[@]}" >"$FSTAB" +cat "$FSTAB" +(! SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR") + +: "fstab-generator: invalid" +printf "%s\n" "${FSTAB_INVALID[@]}" >"$FSTAB" +cat "$FSTAB" +# Don't care about the exit code here +SYSTEMD_FSTAB="$FSTAB" run_and_list "$GENERATOR_BIN" "$OUT_DIR" || : +# No mounts should get created here +[[ "$(find "$OUT_DIR" -name "*.mount" | wc -l)" -eq 0 ]] + +: "fstab-generator: kernel args - fstab=0" +printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB" +SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") + +: "fstab-generator: kernel args - rd.fstab=0" +printf "%s\n" "${FSTAB_MINIMAL[@]}" >"$FSTAB" +SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="rd.fstab=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +(! SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB="$FSTAB" check_fstab_mount_units FSTAB_MINIMAL "$OUT_DIR") + +: "fstab-generator: kernel args - systemd.swap=0" +printf "%s\n" "${FSTAB_GENERAL_ROOT[@]}" >"$FSTAB" +cat "$FSTAB" +SYSTEMD_FSTAB="$FSTAB" SYSTEMD_PROC_CMDLINE="systemd.swap=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +# No swap units should get created here +[[ "$(find "$OUT_DIR" -name "*.swap" | wc -l)" -eq 0 ]] + +# Possible TODO +# - combine the rootfs & usrfs arguments and mix them with fstab entries +# - systemd.volatile= +: "fstab-generator: kernel args - root= + rootfstype= + rootflags=" +# shellcheck disable=SC2034 +EXPECTED_FSTAB=( + "/dev/disk/by-label/rootfs / ext4 noexec,ro 0 1" +) +CMDLINE="root=LABEL=rootfs rootfstype=ext4 rootflags=noexec" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +# The /proc/cmdline here is a dummy value to tell the in_initrd_host() function +# we're parsing host's fstab, but it's all on the kernel cmdline instead +SYSTEMD_IN_INITRD=1 SYSTEMD_SYSROOT_FSTAB=/proc/cmdline check_fstab_mount_units EXPECTED_FSTAB "$OUT_DIR" + +# This is a very basic sanity test that involves manual checks, since adding it +# to the check_fstab_mount_units() function would make it way too complex +# (yet another possible TODO) +: "fstab-generator: kernel args - mount.usr= + mount.usrfstype= + mount.usrflags=" +CMDLINE="mount.usr=UUID=be780f43-8803-4a76-9732-02ceda6e9808 mount.usrfstype=ext4 mount.usrflags=noexec,nodev" +SYSTEMD_IN_INITRD=1 SYSTEMD_FSTAB=/dev/null SYSTEMD_SYSROOT_FSTAB=/dev/null SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +cat "$OUT_DIR/normal/sysroot-usr.mount" "$OUT_DIR/normal/sysusr-usr.mount" +# The general idea here is to mount the device to /sysusr/usr and then +# bind-mount /sysusr/usr to /sysroot/usr +grep -qE "^What=/dev/disk/by-uuid/be780f43-8803-4a76-9732-02ceda6e9808$" "$OUT_DIR/normal/sysusr-usr.mount" +grep -qE "^Where=/sysusr/usr$" "$OUT_DIR/normal/sysusr-usr.mount" +grep -qE "^Type=ext4$" "$OUT_DIR/normal/sysusr-usr.mount" +grep -qE "^Options=noexec,nodev,ro$" "$OUT_DIR/normal/sysusr-usr.mount" +link_eq "$OUT_DIR/normal/initrd-usr-fs.target.requires/sysusr-usr.mount" "../sysusr-usr.mount" +grep -qE "^What=/sysusr/usr$" "$OUT_DIR/normal/sysroot-usr.mount" +grep -qE "^Where=/sysroot/usr$" "$OUT_DIR/normal/sysroot-usr.mount" +grep -qE "^Options=bind$" "$OUT_DIR/normal/sysroot-usr.mount" +link_eq "$OUT_DIR/normal/initrd-fs.target.requires/sysroot-usr.mount" "../sysroot-usr.mount" diff --git a/test/units/testsuite-81.getty-generator.sh b/test/units/testsuite-81.getty-generator.sh new file mode 100755 index 0000000..103e966 --- /dev/null +++ b/test/units/testsuite-81.getty-generator.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail +# Disable history expansion so we don't have to escape ! in strings below +set +o histexpand + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-getty-generator" +OUT_DIR="$(mktemp -d /tmp/getty-generator.XXX)" + +at_exit() { + rm -frv "${OUT_DIR:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +if in_container; then + # Do a limited test in a container, as writing to /dev is usually restrited + : "getty-generator: \$container_ttys env (container)" + # In a container we allow only /dev/pts/* ptys + PID1_ENVIRON="container_ttys=tty0 pts/0 /dev/tty0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" + + # console-getty.service is always pulled in in containers + link_endswith "$OUT_DIR/normal/getty.target.wants/console-getty.service" "/lib/systemd/system/console-getty.service" + link_endswith "$OUT_DIR/normal/getty.target.wants/container-getty@0.service" "/lib/systemd/system/container-getty@.service" + test ! -e "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service" + test ! -h "$OUT_DIR/normal/getty.target.wants/container-getty@tty0.service" + + exit 0 +fi + +DUMMY_ACTIVE_CONSOLES=( + "hvc99" + "xvc99" + "hvsi99" + "sclp_line99" + "ttysclp99" + "3270!tty99" + "dummy99" +) +DUMMY_INACTIVE_CONSOLES=( + "inactive99" + "xvc199" +) +DUMMY_CONSOLES=( + "${DUMMY_ACTIVE_CONSOLES[@]}" + "${DUMMY_INACTIVE_CONSOLES[@]}" +) +# Create a bunch of dummy consoles +for console in "${DUMMY_CONSOLES[@]}"; do + mknod "/dev/$console" c 4 0 +done +# Sneak in one "not-a-tty" console +touch /dev/notatty99 +# Temporarily replace /sys/class/tty/console/active with our list of dummy +# consoles so getty-generator can process them +echo -ne "${DUMMY_ACTIVE_CONSOLES[@]}" /dev/notatty99 >/tmp/dummy-active-consoles +mount -v --bind /tmp/dummy-active-consoles /sys/class/tty/console/active + +: "getty-generator: no arguments" +# Sneak in an invalid value for $SYSTEMD_GETTY_AUTO to test things out +PID1_ENVIRON="SYSTEMD_GETTY_AUTO=foo" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +for console in "${DUMMY_ACTIVE_CONSOLES[@]}"; do + unit="$(systemd-escape --template serial-getty@.service "$console")" + link_endswith "$OUT_DIR/normal/getty.target.wants/$unit" "/lib/systemd/system/serial-getty@.service" +done +for console in "${DUMMY_INACTIVE_CONSOLES[@]}" /dev/notatty99; do + unit="$(systemd-escape --template serial-getty@.service "$console")" + test ! -e "$OUT_DIR/normal/getty.target.wants/$unit" + test ! -h "$OUT_DIR/normal/getty.target.wants/$unit" +done + +: "getty-generator: systemd.getty_auto=0 on kernel cmdline" +SYSTEMD_PROC_CMDLINE="systemd.getty_auto=foo systemd.getty_auto=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]] + +: "getty-generator: SYSTEMD_GETTY_AUTO=0 in PID1's environment" +PID1_ENVIRON="SYSTEMD_GETTY_AUTO=0" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]] + +# Cleanup +umount /sys/class/tty/console/active +rm -f "${DUMMY_CONSOLES[@]/#//dev/}" /dev/notatty99 diff --git a/test/units/testsuite-81.run-generator.sh b/test/units/testsuite-81.run-generator.sh new file mode 100755 index 0000000..9bd74ef --- /dev/null +++ b/test/units/testsuite-81.run-generator.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-run-generator" +OUT_DIR="$(mktemp -d /tmp/run-generator.XXX)" + +at_exit() { + rm -frv "${OUT_DIR:?}" +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +check_kernel_cmdline_target() { + local out_dir="${1:?}/normal" + + cat "$out_dir/kernel-command-line.target" + grep -qE "^Requires=kernel-command-line.service$" "$out_dir/kernel-command-line.target" + grep -qE "^After=kernel-command-line.service$" "$out_dir/kernel-command-line.target" + + link_eq "$out_dir/default.target" "kernel-command-line.target" +} + +: "run-generator: empty cmdline" +SYSTEMD_PROC_CMDLINE="" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]] + +: "run-generator: single command" +CMDLINE="systemd.run='echo hello world'" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +check_kernel_cmdline_target "$OUT_DIR" +UNIT="$OUT_DIR/normal/kernel-command-line.service" +cat "$UNIT" +systemd-analyze verify --man=no --recursive-errors=no "$UNIT" +grep -qE "^SuccessAction=exit$" "$UNIT" +grep -qE "^FailureAction=exit$" "$UNIT" +grep -qE "^ExecStart=echo hello world$" "$UNIT" + +: "run-generator: multiple commands + success/failure actions" +ARGS=( + # These should be ignored + "systemd.run" + "systemd.run_success_action" + "systemd.run_failure_action" + + # Set actions which we will overwrite later + "systemd.run_success_action=" + "systemd.run_failure_action=" + + "systemd.run=/bin/false" + "systemd.run=" + "systemd.run=/bin/true" + "systemd.run='echo this is a long string'" + + "systemd.run_success_action=reboot" + "systemd.run_failure_action=poweroff-force" +) +CMDLINE="${ARGS[*]}" +SYSTEMD_PROC_CMDLINE="$CMDLINE" run_and_list "$GENERATOR_BIN" "$OUT_DIR" +check_kernel_cmdline_target "$OUT_DIR" +UNIT="$OUT_DIR/normal/kernel-command-line.service" +cat "$UNIT" +systemd-analyze verify --man=no --recursive-errors=no "$UNIT" +grep -qE "^SuccessAction=reboot$" "$UNIT" +grep -qE "^FailureAction=poweroff-force$" "$UNIT" +grep -qE "^ExecStart=/bin/false$" "$UNIT" +grep -qE "^ExecStart=$" "$UNIT" +grep -qE "^ExecStart=/bin/true$" "$UNIT" +grep -qE "^ExecStart=echo this is a long string$" "$UNIT" diff --git a/test/units/testsuite-81.service b/test/units/testsuite-81.service new file mode 100644 index 0000000..3b697b3 --- /dev/null +++ b/test/units/testsuite-81.service @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-81-GENERATORS + +[Service] +ExecStartPre=rm -f /failed /testok +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +Type=oneshot diff --git a/test/units/testsuite-81.sh b/test/units/testsuite-81.sh new file mode 100755 index 0000000..9c2a033 --- /dev/null +++ b/test/units/testsuite-81.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/test-control.sh +. "$(dirname "$0")"/test-control.sh + +run_subtests + +touch /testok diff --git a/test/units/testsuite-81.system-update-generator.sh b/test/units/testsuite-81.system-update-generator.sh new file mode 100755 index 0000000..8ee1fee --- /dev/null +++ b/test/units/testsuite-81.system-update-generator.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +# shellcheck disable=SC2235 +set -eux +set -o pipefail + +# shellcheck source=test/units/generator-utils.sh +. "$(dirname "$0")/generator-utils.sh" + +GENERATOR_BIN="/usr/lib/systemd/system-generators/systemd-system-update-generator" +OUT_DIR="$(mktemp -d /tmp/system-update-generator-generator.XXX)" + +at_exit() { + rm -frv "${OUT_DIR:?}" /system-update +} + +trap at_exit EXIT + +test -x "${GENERATOR_BIN:?}" + +rm -f /system-update + +: "system-update-generator: no /system-update flag" +run_and_list "$GENERATOR_BIN" "$OUT_DIR" +[[ "$(find "$OUT_DIR" ! -type d | wc -l)" -eq 0 ]] + +: "system-update-generator: with /system-update flag" +touch /system-update +run_and_list "$GENERATOR_BIN" "$OUT_DIR" +link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target" + +: "system-update-generator: kernel cmdline warnings" +# We should warn if the default target is overridden on the kernel cmdline +# by a runlevel or systemd.unit=, but still generate the symlink +SYSTEMD_PROC_CMDLINE="systemd.unit=foo.bar 3" run_and_list "$GENERATOR_BIN" "$OUT_DIR" |& tee /tmp/system-update-generator.log +link_endswith "$OUT_DIR/early/default.target" "/lib/systemd/system/system-update.target" +grep -qE "Offline system update overridden .* systemd.unit=" /tmp/system-update-generator.log +grep -qE "Offline system update overridden .* runlevel" /tmp/system-update-generator.log diff --git a/test/units/testsuite-82.service b/test/units/testsuite-82.service new file mode 100644 index 0000000..a8fc4f9 --- /dev/null +++ b/test/units/testsuite-82.service @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-82-SOFTREBOOT +DefaultDependencies=no +After=basic.target + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +FileDescriptorStoreMax=3 +NotifyAccess=all diff --git a/test/units/testsuite-82.sh b/test/units/testsuite-82.sh new file mode 100755 index 0000000..b5e6ded --- /dev/null +++ b/test/units/testsuite-82.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + # Since the soft-reboot drops the enqueued end.service, we won't shutdown + # the test VM if the test fails and have to wait for the watchdog to kill + # us (which may take quite a long time). Let's just forcibly kill the machine + # instead to save CI resources. + if [[ $? -ne 0 ]]; then + echo >&2 "Test failed, shutting down the machine..." + systemctl poweroff -ff + fi +} + +trap at_exit EXIT + +systemd-analyze log-level debug + +export SYSTEMD_LOG_LEVEL=debug + +if [ -f /run/testsuite82.touch3 ]; then + echo "This is the fourth boot!" + systemd-notify --status="Fourth Boot" + + rm /run/testsuite82.touch3 + mount + rmdir /original-root /run/nextroot + + # Check that the fdstore entry still exists + test "$LISTEN_FDS" -eq 3 + read -r x <&5 + test "$x" = "oinkoink" + + # Check that the surviving services are still around + test "$(systemctl show -P ActiveState testsuite-82-survive.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-survive-argv.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive-sigterm.service)" != "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive.service)" != "active" + + # Check journals + journalctl -o short-monotonic --no-hostname --grep '(will soft-reboot|KILL|corrupt)' + assert_eq "$(journalctl -q -o short-monotonic -u systemd-journald.service --grep 'corrupt')" "" + + # All succeeded, exit cleanly now + +elif [ -f /run/testsuite82.touch2 ]; then + echo "This is the third boot!" + systemd-notify --status="Third Boot" + + rm /run/testsuite82.touch2 + + # Check that the fdstore entry still exists + test "$LISTEN_FDS" -eq 2 + read -r x <&4 + test "$x" = "miaumiau" + + # Upload another entry + T="/dev/shm/fdstore.$RANDOM" + echo "oinkoink" >"$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + # Check that the surviving services are still around + test "$(systemctl show -P ActiveState testsuite-82-survive.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-survive-argv.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive-sigterm.service)" != "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive.service)" != "active" + + # Test that we really are in the new overlayfs root fs + read -r x "$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + # Check that the surviving services are still around + test "$(systemctl show -P ActiveState testsuite-82-survive.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-survive-argv.service)" = "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive-sigterm.service)" != "active" + test "$(systemctl show -P ActiveState testsuite-82-nosurvive.service)" != "active" + + # This time we test the /run/nextroot/ root switching logic. (We synthesize a new rootfs from the old via overlayfs) + mkdir -p /run/nextroot /tmp/nextroot-lower /original-root + mount -t tmpfs tmpfs /tmp/nextroot-lower + echo miep >/tmp/nextroot-lower/lower + + # Copy os-release away, so that we can manipulate it and check that it is updated in the propagate + # directory across soft reboots. Try to cover corner cases by truncating it. + mkdir -p /tmp/nextroot-lower/usr/lib + grep ID /etc/os-release >/tmp/nextroot-lower/usr/lib/os-release + echo MARKER=1 >>/tmp/nextroot-lower/usr/lib/os-release + cmp /etc/os-release /run/systemd/propagate/.os-release-stage/os-release + (! grep -q MARKER=1 /etc/os-release) + + mount -t overlay nextroot /run/nextroot -o lowerdir=/tmp/nextroot-lower:/,ro + + # Bind our current root into the target so that we later can return to it + mount --bind / /run/nextroot/original-root + + # Restart the unit that is not supposed to survive + systemd-run --collect --service-type=exec --unit=testsuite-82-nosurvive.service sleep infinity + + # Now issue the soft reboot. We should be right back soon. Given /run/nextroot exists, we should + # automatically do a softreboot instead of normal reboot. + touch /run/testsuite82.touch2 + systemctl --no-block reboot + + # Now block until the soft-boot killing spree kills us + exec sleep infinity +else + # This is the first boot + systemd-notify --status="First Boot" + + # Let's upload an fd to the fdstore, so that we can verify fdstore passing works correctly + T="/dev/shm/fdstore.$RANDOM" + echo "wuffwuff" >"$T" + systemd-notify --fd=3 --pid=parent 3<"$T" + rm "$T" + + survive_sigterm="/dev/shm/survive-sigterm-$RANDOM.sh" + cat >"$survive_sigterm" <"$survive_argv" <&2 + exit 1 + fi +)} + +assert_eq() {( + set +ex + + if [[ "${1?}" != "${2?}" ]]; then + echo "FAIL: expected: '$2' actual: '$1'" >&2 + exit 1 + fi +)} + +assert_in() {( + set +ex + + if ! [[ "${2?}" =~ ${1?} ]]; then + echo "FAIL: '$1' not found in:" >&2 + echo "$2" >&2 + exit 1 + fi +)} + +assert_not_in() {( + set +ex + + if [[ "${2?}" =~ ${1?} ]]; then + echo "FAIL: '$1' found in:" >&2 + echo "$2" >&2 + exit 1 + fi +)} + +assert_rc() {( + set +ex + + local rc exp="${1?}" + + shift + "$@" + rc=$? + assert_eq "$rc" "$exp" +)} + +assert_not_reached() { + echo >&2 "Code should not be reached at ${BASH_SOURCE[1]}:${BASH_LINENO[1]}, function ${FUNCNAME[1]}()" + exit 1 +} + +run_and_grep() {( + set +ex + + local expression + local log ec + local exp_ec=0 + + # Invert the grep condition - i.e. check if the expression is _not_ in command's output + if [[ "${1:?}" == "-n" ]]; then + exp_ec=1 + shift + fi + + expression="${1:?}" + shift + + if [[ $# -eq 0 ]]; then + echo >&2 "FAIL: Not enough arguments for ${FUNCNAME[0]}()" + return 1 + fi + + log="$(mktemp)" + if ! "$@" |& tee "${log:?}"; then + echo >&2 "FAIL: Command '$*' failed" + return 1 + fi + + grep -qE "$expression" "$log" && ec=0 || ec=$? + if [[ "$exp_ec" -eq 0 && "$ec" -ne 0 ]]; then + echo >&2 "FAIL: Expression '$expression' not found in the output of '$*'" + return 1 + elif [[ "$exp_ec" -ne 0 && "$ec" -eq 0 ]]; then + echo >&2 "FAIL: Expression '$expression' found in the output of '$*'" + return 1 + fi + + rm -f "$log" +)} + +get_cgroup_hierarchy() { + case "$(stat -c '%T' -f /sys/fs/cgroup)" in + cgroup2fs) + echo "unified" + ;; + tmpfs) + if [[ -d /sys/fs/cgroup/unified && "$(stat -c '%T' -f /sys/fs/cgroup/unified)" == cgroup2fs ]]; then + echo "hybrid" + else + echo "legacy" + fi + ;; + *) + echo >&2 "Failed to determine host's cgroup hierarchy" + exit 1 + esac +} + +runas() { + local userid="${1:?}" + shift + XDG_RUNTIME_DIR=/run/user/"$(id -u "$userid")" setpriv --reuid="$userid" --init-groups "$@" +} + +coverage_create_nspawn_dropin() { + # If we're collecting coverage, bind mount the $BUILD_DIR into the nspawn + # container so gcov can update the counters. This is mostly for standalone + # containers, as machinectl stuff is handled by overriding the systemd-nspawn@.service + # (see test/test-functions:install_systemd()) + local root="${1:?}" + local container + + if [[ -z "${COVERAGE_BUILD_DIR:-}" ]]; then + return 0 + fi + + container="$(basename "$root")" + mkdir -p "/run/systemd/nspawn" + echo -ne "[Files]\nBind=$COVERAGE_BUILD_DIR\n" >"/run/systemd/nspawn/${container:?}.nspawn" +} + +create_dummy_container() { + local root="${1:?}" + + if [[ ! -d /testsuite-13-container-template ]]; then + echo >&2 "Missing container template, probably not running in TEST-13-NSPAWN?" + exit 1 + fi + + mkdir -p "$root" + cp -a /testsuite-13-container-template/* "$root" + coverage_create_nspawn_dropin "$root" +} + +# Bump the reboot counter and call systemctl with the given arguments +systemctl_final() { + local counter + + if [[ $# -eq 0 ]]; then + echo >&2 "Missing arguments" + exit 1 + fi + + [[ -e /var/tmp/.systemd_reboot_count ]] && counter="$(/var/tmp/.systemd_reboot_count + + systemctl "$@" +} + +cgroupfs_supports_user_xattrs() { + local xattr + + xattr="user.supported_$RANDOM" + # shellcheck disable=SC2064 + trap "setfattr --remove=$xattr /sys/fs/cgroup || :" RETURN + + setfattr --name="$xattr" --value=254 /sys/fs/cgroup + [[ "$(getfattr --name="$xattr" --absolute-names --only-values /sys/fs/cgroup)" -eq 254 ]] +} + +tpm_has_pcr() { + local algorithm="${1:?}" + local pcr="${2:?}" + + [[ -f "/sys/class/tpm/tpm0/pcr-$algorithm/$pcr" ]] +} + +openssl_supports_kdf() { + local kdf="${1:?}" + + # The arguments will need to be adjusted to make this work for other KDFs than SSKDF, + # but let's do that when/if the need arises + openssl kdf -keylen 16 -kdfopt digest:SHA2-256 -kdfopt key:foo -out /dev/null "$kdf" +} + +kernel_supports_lsm() { + local lsm="${1:?}" + local items item + + if [[ ! -e /sys/kernel/security/lsm ]]; then + echo "/sys/kernel/security/lsm doesn't exist, assuming $lsm is not supported" + return 1 + fi + + mapfile -t -d, items