diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index 4330c0f2c1..1565a79927 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -12,6 +12,18 @@ #include "terminal-util.h" #include "virt.h" +static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = { + [EMERGENCY_ACTION_NONE] = "none", + [EMERGENCY_ACTION_REBOOT] = "reboot", + [EMERGENCY_ACTION_REBOOT_FORCE] = "reboot-force", + [EMERGENCY_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", + [EMERGENCY_ACTION_POWEROFF] = "poweroff", + [EMERGENCY_ACTION_POWEROFF_FORCE] = "poweroff-force", + [EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate", + [EMERGENCY_ACTION_EXIT] = "exit", + [EMERGENCY_ACTION_EXIT_FORCE] = "exit-force", +}; + static void log_and_status(Manager *m, bool warn, const char *message, const char *reason) { log_full(warn ? LOG_WARNING : LOG_DEBUG, "%s: %s", message, reason); if (warn) @@ -28,10 +40,22 @@ void emergency_action( int exit_status, const char *reason) { + Unit *u; + assert(m); assert(action >= 0); assert(action < _EMERGENCY_ACTION_MAX); + /* Is the special shutdown target active or queued? If so, we are in shutdown state */ + if (IN_SET(action, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_EXIT)) { + u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); + if (u && unit_active_or_pending(u)) { + log_notice("Shutdown is already active. Skipping emergency action request %s.", + emergency_action_table[action]); + return; + } + } + if (action == EMERGENCY_ACTION_NONE) return; @@ -126,17 +150,6 @@ void emergency_action( } } -static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = { - [EMERGENCY_ACTION_NONE] = "none", - [EMERGENCY_ACTION_REBOOT] = "reboot", - [EMERGENCY_ACTION_REBOOT_FORCE] = "reboot-force", - [EMERGENCY_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", - [EMERGENCY_ACTION_POWEROFF] = "poweroff", - [EMERGENCY_ACTION_POWEROFF_FORCE] = "poweroff-force", - [EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate", - [EMERGENCY_ACTION_EXIT] = "exit", - [EMERGENCY_ACTION_EXIT_FORCE] = "exit-force", -}; DEFINE_STRING_TABLE_LOOKUP(emergency_action, EmergencyAction); int parse_emergency_action( diff --git a/test/TEST-52-HONORFIRSTSHUTDOWN/Makefile b/test/TEST-52-HONORFIRSTSHUTDOWN/Makefile new file mode 100644 index 0000000000..a48af97793 --- /dev/null +++ b/test/TEST-52-HONORFIRSTSHUTDOWN/Makefile @@ -0,0 +1,16 @@ +BUILD_DIR=$(shell ../../tools/find-build-dir.sh) + +all setup run clean clean-again: + @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./test.sh --$@ + +# finish option is used to run checks that can only be run outside of +# the test execution. Example case, honor first shutdown, proof is obtained +# from the console output as the image shuts down. This does not show up in +# the journal so the output from the do_test is captured in a file in /tmp. +# Without the use of finish the test will still pass because if it fails +# the test will loop and will be terminated via a command timeout. +# This just provides concrete confirmation. +finish: + @basedir=../.. TEST_BASE_DIR=../ BUILD_DIR=$(BUILD_DIR) ./fini.sh --$@ + +.PHONY: all setup run clean clean-again diff --git a/test/TEST-52-HONORFIRSTSHUTDOWN/fini.sh b/test/TEST-52-HONORFIRSTSHUTDOWN/fini.sh new file mode 100755 index 0000000000..993ada020e --- /dev/null +++ b/test/TEST-52-HONORFIRSTSHUTDOWN/fini.sh @@ -0,0 +1,10 @@ +#!/bin/bash +TEST_DESCRIPTION="test honor first shutdown" + +if grep -q "Shutdown is already active. Skipping emergency action request" /tmp/honorfirstshutdown.log; then + echo "$TEST_DESCRIPTION [pass]" + exit 0 +else + echo "$TEST_DESCRIPTION [fail]" + exit 1 +fi diff --git a/test/TEST-52-HONORFIRSTSHUTDOWN/test.sh b/test/TEST-52-HONORFIRSTSHUTDOWN/test.sh new file mode 100755 index 0000000000..a0848ef672 --- /dev/null +++ b/test/TEST-52-HONORFIRSTSHUTDOWN/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e +. $TEST_BASE_DIR/test-functions +TEST_REQUIRE_INSTALL_TESTS=0 +TEST_DESCRIPTION="testing honor first shutdown" +#INTERACTIVE_DEBUG=1 +TEST_NO_QEMU=1 + +#Using timeout because if the test fails it can loop. +# The reason is because the poweroff executed by end.service +# could turn into a reboot if the test fails. +NSPAWN_TIMEOUT=20 + +#Remove this file if it exists. this is used along with +# the make target "finish". Since concrete confirmaion is +# only found from the console during the poweroff. +rm -f /tmp/honorfirstshutdown.log >/dev/null + +do_test "$@" 52 > /tmp/honorfirstshutdown.log diff --git a/test/meson.build b/test/meson.build index 404b923467..8bda38a2e2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -28,6 +28,8 @@ install_subdir('testsuite-28.units', install_dir : testdata_dir) install_subdir('testsuite-30.units', install_dir : testdata_dir) +install_subdir('testsuite-52.units', + install_dir : testdata_dir) testsuite08_dir = testdata_dir + '/testsuite-08.units' install_data('testsuite-08.units/-.mount', diff --git a/test/testsuite-52.units/testsuite-52.service b/test/testsuite-52.units/testsuite-52.service new file mode 100755 index 0000000000..93f847f044 --- /dev/null +++ b/test/testsuite-52.units/testsuite-52.service @@ -0,0 +1,6 @@ +[Unit] +Description=Testsuite service + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/%N.units/%N.sh +Type=oneshot diff --git a/test/testsuite-52.units/testsuite-52.sh b/test/testsuite-52.units/testsuite-52.sh new file mode 100755 index 0000000000..9cccf1b6c1 --- /dev/null +++ b/test/testsuite-52.units/testsuite-52.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -ex +set -o pipefail + +if ! test -x /usr/lib/systemd/tests/testdata/units/test-honor-first-shutdown.sh ; then + echo "honor-first-shutdown script not found - FAIL" > /testok + exit 0 +fi + +systemd-analyze log-level debug +systemd-analyze log-target console + +systemctl enable test-honor-first-shutdown.service +systemctl start test-honor-first-shutdown.service + +echo OK > /testok + +exit 0 diff --git a/test/units/test-honor-first-shutdown.service b/test/units/test-honor-first-shutdown.service new file mode 100644 index 0000000000..374f1e6b5f --- /dev/null +++ b/test/units/test-honor-first-shutdown.service @@ -0,0 +1,11 @@ +[Unit] +Description=Honor First Shutdown feature +After=multi-user.target + +[Service] +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh +ExecStop=sh -c 'kill -SIGKILL $MAINPID' +FailureAction=reboot + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/test/units/test-honor-first-shutdown.sh b/test/units/test-honor-first-shutdown.sh new file mode 100755 index 0000000000..17c1ec9686 --- /dev/null +++ b/test/units/test-honor-first-shutdown.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Honor first shutdown test script" +sleep infinity;