tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Passing shutdown actions to /etc/rc.shutdown



Hey folks,

I run lots of machines on several UPSes (using sysutils/nut-* from pkgsrc).
When mains fail and the UPS gets low on battery, the ups daemon shuts
down the machine.

So far all fine.

But there is a (tiny) race when mains return after the machine powered off
but the UPS did not run out of battery. This is usually dealt with by
telling the UPS to shutdown after a delay, which means: systems goes
down, after running all shutdown hooks it tells the ups to restart in N seconds
and then filesystems are unmounted and the machine shuts down. When the
counter in the UPS reaches 0 it powers of its output and restarts. After
restart it will only switch on its output if main power is back.

This way the machine controlling the UPS will safely boot even if main power
returns during the shutdown process.

This is all fine, and documented best practices for many kind of UPSes.

However, it is tricky to implement in NetBSD.

I considered doing kernel/init tricks but then played around a bit and came
to a *very* simple solution that works amazingly good.

I also (ab)use it for a minor variant of above sheme in one setup: I
have a machine that is alone on its UPS but has a few peripheral
devices. Whenever I shut down that machine (manually or as described
above via the nut ups daemon) I want the UPS to shut off all outputs
(where the peripherals are connected). So here it is about
all-power-off instead of safe-reboot when mains return.

The trick is the small patch below that modifies /sbin/shutdown to pass an
argument to /etc/rc.shutdown telling it about further actions: either
halt, powerdown or reboot.

On reboot or halt (goint single user) we don't want to trigger the UPS
toggle countdown, or even power off all outputs.

Besides this /sbin/shutdown patch we could enhance /etc/rc.shutdown to 
conditionally read an additional script that contains the local stuff,
so the system provided script would not need to be modified for this
kind of customizations. But that is trivial and a second step.

So here the first step first. What do you think?

Martin


Index: shutdown.c
===================================================================
RCS file: /cvsroot/src/sbin/shutdown/shutdown.c,v
retrieving revision 1.58
diff -u -r1.58 shutdown.c
--- shutdown.c	1 Jul 2022 16:45:12 -0000	1.58
+++ shutdown.c	28 Nov 2024 19:16:13 -0000
@@ -47,6 +47,7 @@
 #include <sys/time.h>
 #include <sys/resource.h>
 #include <sys/syslog.h>
+#include <sys/wait.h>
 
 #include <ctype.h>
 #include <err.h>
@@ -518,10 +519,38 @@
 static void
 dorcshutdown(void)
 {
+	const char *arg = NULL;
+	static char cmd[sizeof(_PATH_RCSHUTDOWN) + 64];
+	int status;
+
+	if (doreboot)
+		arg = "reboot";
+	else if (dopowerdown)
+		arg = "powerdown";
+	else if (dohalt)
+		arg = "halt";
+	if (arg) {
+		snprintf(cmd, sizeof(cmd), "set -- %s && . " _PATH_RCSHUTDOWN,
+		    arg);
+		arg = cmd;
+	} else {
+		arg = ". " _PATH_RCSHUTDOWN;
+	}
+
 	(void)printf("\r\nAbout to run shutdown hooks...\r\n");
 #ifndef DEBUG
 	(void)setuid(0);
-	(void)system(". " _PATH_RCSHUTDOWN);
+	status = system(arg);
+	if (status == -1) {
+		fprintf(stderr, "failed to run " _PATH_RCSHUTDOWN ", error %d\n",
+		    errno);
+		exit(1);
+	}
+	if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+		fprintf(stderr, _PATH_RCSHUTDOWN " exited with status %d\n",
+		    WEXITSTATUS(status));
+		exit(1);
+	}
 #endif
 	(void)sleep(5);		/* Give operator a chance to abort this. */
 	(void)printf("\r\nDone running shutdown hooks.\r\n");


Home | Main Index | Thread Index | Old Index