From 0e797502f58df6f7bc06202de5b30c3a2df52437 Mon Sep 17 00:00:00 2001
From: Natanael Copa <ncopa@alpinelinux.org>
Date: Fri, 17 Jan 2025 09:12:08 +0100
Subject: [PATCH] init: add support for separate reboot action

Make it possible for service managers to differentiate a reboot vs
shutdown event.

Allows user to configure what should happen on SIGTERM (sent by
/sbin/reboot).

For example:

    ::shutdown:/sbin/openrc shutdown

If no shutdown action is specified in inittab, fall back to shutdown
action, so we don't break backwards compatibility.

downstream-issue: https://gitlab.alpinelinux.org/alpine/aports/-/issues/16695

function                                             old     new   delta
run                                                  256     686    +430
check_delayed_sigs                                   362     447     +85
run_actions                                          126     141     +15
static.actions                                        64      71      +7
parse_inittab                                        477     473      -4
reset_sighandlers_and_unblock_sigs                    22       -     -22
open_stdio_to_tty                                     85       -     -85
run_shutdown_and_kill_processes                      111       -    -111
init_exec                                            396       -    -396
------------------------------------------------------------------------------
(add/remove: 0/4 grow/shrink: 4/1 up/down: 537/-618)          Total: -81 bytes
   text	   data	    bss	    dec	    hex	filename
 824050	  14108	   2008	 840166	  cd1e6	busybox_old
 823969	  14108	   2008	 840085	  cd195	busybox_unstripped

Signed-off-by: Natanael Copa <ncopa@alpinelinux.org>
---
 init/init.c | 52 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 17 deletions(-)

diff --git a/init/init.c b/init/init.c
index 2ee1e4cde..47809a1e8 100644
--- a/init/init.c
+++ b/init/init.c
@@ -184,17 +184,27 @@
 /*
  * Start these before killing all processes in preparation for
  * running RESTART actions or doing low-level halt/reboot/poweroff
- * (initiated by SIGUSR1/SIGTERM/SIGUSR2).
+ * (initiated by SIGUSR1/SIGUSR2).
+ * Will also run as fallback if no REBOOT actions were execute on
+ * SIGTERM.
  * Wait for completion before proceeding.
  */
 #define SHUTDOWN    0x40
+/*
+ * Start these before killing all processes in preparation for
+ * doing low-level reboot (initiated by SIGTERM).
+ * If no reboot actions were executed, it will execute the SHUTDOWN
+ * actions as fallback.
+ * Wait for completion before proceeding.
+ */
+#define REBOOT      0x80
 /*
  * exec() on SIGQUIT. SHUTDOWN actions are started and waited for,
  * then all processes are killed, then init exec's 1st RESTART action,
  * replacing itself by it. If no RESTART action specified,
  * SIGQUIT has no effect.
  */
-#define RESTART     0x80
+#define RESTART     0x100
 
 /* A linked list of init_actions, to be read from inittab */
 struct init_action {
@@ -591,17 +601,19 @@ static void waitfor(pid_t pid)
 }
 
 /* Run all commands of a particular type */
-static void run_actions(int action_type)
+/* return 0 if no actions were run */
+static int run_actions(int action_type)
 {
 	struct init_action *a;
+	pid_t pid = 0;
 
 	for (a = G.init_action_list; a; a = a->next) {
 		if (!(a->action_type & action_type))
 			continue;
 
-		if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN)) {
-			pid_t pid = run(a);
-			if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN))
+		if (a->action_type & (SYSINIT | WAIT | ONCE | CTRLALTDEL | SHUTDOWN | REBOOT)) {
+			pid = run(a);
+			if (a->action_type & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | REBOOT))
 				waitfor(pid);
 		}
 		if (a->action_type & (RESPAWN | ASKFIRST)) {
@@ -612,9 +624,10 @@ static void run_actions(int action_type)
 				a->pid = run(a);
 		}
 	}
+	return pid != 0;
 }
 
-static void new_init_action(uint8_t action_type, const char *command, const char *cons)
+static void new_init_action(uint32_t action_type, const char *command, const char *cons)
 {
 	struct init_action *a, **nextp;
 
@@ -705,7 +718,7 @@ static void parse_inittab(void)
 		/* order must correspond to SYSINIT..RESTART constants */
 		static const char actions[] ALIGN1 =
 			"sysinit\0""wait\0""once\0""respawn\0""askfirst\0"
-			"ctrlaltdel\0""shutdown\0""restart\0";
+			"ctrlaltdel\0""shutdown\0""reboot\0""restart\0";
 		int action;
 		char *tty = token[0];
 
@@ -755,12 +768,14 @@ static void pause_and_low_level_reboot(unsigned magic)
 	_exit_SUCCESS();
 }
 
-static void run_shutdown_and_kill_processes(void)
+static void run_shutdown_and_kill_processes(int sig)
 {
 	/* Run everything to be run at "shutdown".  This is done _prior_
 	 * to killing everything, in case people wish to use scripts to
 	 * shut things down gracefully... */
-	run_actions(SHUTDOWN);
+	if ((sig != SIGTERM) || (run_actions(REBOOT) == 0)) {
+		(void)run_actions(SHUTDOWN);
+	}
 
 	message(L_CONSOLE | L_LOG, "The system is going down NOW!");
 
@@ -821,7 +836,7 @@ static void halt_reboot_pwoff(int sig)
 	 */
 	reset_sighandlers_and_unblock_sigs();
 
-	run_shutdown_and_kill_processes();
+	run_shutdown_and_kill_processes(sig);
 
 	m = "halt";
 	rb = RB_HALT_SYSTEM;
@@ -854,7 +869,7 @@ static void exec_restart_action(void)
 
 		reset_sighandlers_and_unblock_sigs();
 
-		run_shutdown_and_kill_processes();
+		run_shutdown_and_kill_processes(SIGQUIT);
 
 #ifdef RB_ENABLE_CAD
 		/* Allow Ctrl-Alt-Del to reboot the system.
@@ -986,7 +1001,7 @@ static void check_delayed_sigs(struct timespec *ts)
 		reload_inittab();
 #endif
 	if (sig == SIGINT)
-		run_actions(CTRLALTDEL);
+		(void)run_actions(CTRLALTDEL);
 	if (sig == SIGQUIT) {
 		exec_restart_action();
 		/* returns only if no restart action defined */
@@ -1179,18 +1194,18 @@ int init_main(int argc UNUSED_PARAM, char **argv)
 
 	/* Now run everything that needs to be run */
 	/* First run the sysinit command */
-	run_actions(SYSINIT);
+	(void)run_actions(SYSINIT);
 	check_delayed_sigs(&G.zero_ts);
 	/* Next run anything that wants to block */
-	run_actions(WAIT);
+	(void)run_actions(WAIT);
 	check_delayed_sigs(&G.zero_ts);
 	/* Next run anything to be run only once */
-	run_actions(ONCE);
+	(void)run_actions(ONCE);
 
 	/* Now run the looping stuff for the rest of forever */
 	while (1) {
 		/* (Re)run the respawn/askfirst stuff */
-		run_actions(RESPAWN | ASKFIRST);
+		(void)run_actions(RESPAWN | ASKFIRST);
 
 		/* Wait for any signal (typically it's SIGCHLD) */
 		check_delayed_sigs(NULL); /* NULL timespec makes it wait */
@@ -1355,5 +1370,8 @@ int init_main(int argc UNUSED_PARAM, char **argv)
 //usage:	"	\n"
 //usage:	"	# Stuff to do before rebooting\n"
 //usage:	"	::ctrlaltdel:/sbin/reboot\n"
+//usage:	"	::reboot:/bin/umount -a -r\n"
+//usage:	"	::reboot:/sbin/swapoff -a\n"
+//usage:	"	# Stuff to do before poweroff (also fallback when no reboot actions specified)\n"
 //usage:	"	::shutdown:/bin/umount -a -r\n"
 //usage:	"	::shutdown:/sbin/swapoff -a\n"
-- 
2.48.1