[git commit] examples/shutdown-1.0: an example of reboot which does not signal init

Denys Vlasenko vda.linux at googlemail.com
Fri Apr 20 13:12:52 UTC 2018


commit: https://git.busybox.net/busybox/commit/?id=c21dfaf836cf0eb5317035bc20395c751a205934
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

For one, my inits know nothing about the concept of "shutting down the system".

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 examples/shutdown-1.0/README                      |  30 ++++
 examples/shutdown-1.0/script/do_shutdown          |  54 +++++++
 examples/shutdown-1.0/script/hardshutdown.c       | 168 ++++++++++++++++++++++
 examples/shutdown-1.0/script/hardshutdown.make.sh |   8 ++
 examples/shutdown-1.0/script/shutdown             |  64 +++++++++
 examples/shutdown-1.0/script/stop_storage         |  81 +++++++++++
 examples/shutdown-1.0/script/stop_tasks           |  70 +++++++++
 7 files changed, 475 insertions(+)

diff --git a/examples/shutdown-1.0/README b/examples/shutdown-1.0/README
new file mode 100644
index 000000000..40fe0ebed
--- /dev/null
+++ b/examples/shutdown-1.0/README
@@ -0,0 +1,30 @@
+# Replaces traditional overdesigned shutdown mechanism.
+#
+# No communication with init is necessary:
+# just ask all processes to exit.
+# Then unmount everything. Then reboot or power off.
+
+# Install /sbin/ symlinks named halt, reboot, poweroff
+# (and also possibly shutdown) to e.g.
+# /app/shutdown-1.0/script/shutdown:
+#
+ln -s /app/shutdown-1.0/script/shutdown /sbin/halt
+ln -s /app/shutdown-1.0/script/shutdown /sbin/reboot
+ln -s /app/shutdown-1.0/script/shutdown /sbin/poweroff
+#
+# shutdown spawns do_shutdown in new session, redirected to /dev/null,
+# tells user that shutdown is in progress, and sleeps
+# (for cosmetic reasons: do not confuse user by letting him
+# type more commands in this terminal).
+#
+# do_shutdown tries to switch to a VT console.
+# Then, (only if -r) it spawns a hardshutdown child, to reboot
+# unconditionally in 30 seconds if something later goes seriously bad.
+# Then it runs stop_tasks, writing to /var/log/reboot/YYYYMMDDhhmmss.log,
+# then it runs stop_storage.
+# Then it commands kernel to halt/reboot/poweroff, if requested.
+# Then it sleeps forever.
+#
+# Build the hardshutdown binary:
+#
+cd script && ./hardshutdown.make.sh
diff --git a/examples/shutdown-1.0/script/do_shutdown b/examples/shutdown-1.0/script/do_shutdown
new file mode 100755
index 000000000..0c1e0dce8
--- /dev/null
+++ b/examples/shutdown-1.0/script/do_shutdown
@@ -0,0 +1,54 @@
+#!/bin/sh
+# We are called with stdin/out/err = /dev/null
+
+resetgracetime=60
+
+logfile="/var/log/reboot/`date '+%Y%m%d%H%M%S'`.log"
+mkdir -p /var/log/reboot
+
+PATH=/sbin:/bin
+
+say() {
+	printf "\r%s\n\r" "$*"
+}
+
+# Since there is a potential for various fuckups during umount,
+# we start delayed hard reboot here which will forcibly
+# reboot hung box in a remote datacenter a thousand miles away ;)
+if test "$1" = "-r"; then
+	./hardshutdown -r "$resetgracetime" &
+fi
+
+# Now, (try to) switch away from X and open a console. I've seen reboots
+# hung on open("/dev/console"), therefore we do it _after_ hardshutdown
+exec >/dev/console 2>&1
+
+if test "$1" = "-r"; then
+	say "* `date '+%H:%M:%S'` Scheduled hard reboot in $resetgracetime seconds"
+fi
+
+say "* `date '+%H:%M:%S'` Stopping tasks (see /var/log/reboot/* files)"
+# log reboot event to file. %Y%m%d%H%M%S: YYYYMMDDHHMMSS
+./stop_tasks >"$logfile" 2>&1
+
+# Dying X tends to leave us at semi-random vt. Try to fix that,
+# but if it doesn't work, proceed anyway.
+exec >/dev/null 2>&1
+chvt 1 & sleep 1
+exec >/dev/console 2>&1
+
+command -v ctrlaltdel >/dev/null && {
+	say "* `date '+%H:%M:%S'` Setting Ctrl-Alt-Del to 'hard'"
+	ctrlaltdel hard
+}
+
+say "* `date '+%H:%M:%S'` Stopping storage devices"
+# we can't log this: we are about to unmount everything!
+./stop_storage "$@"
+
+# If we have cmdline params, start hardshutdown with them
+test "$*" && ./hardshutdown "$@"
+
+# Just sleep endlessly...
+say "* `date '+%H:%M:%S'` You may now power off or press Ctrl-Alt-Del to reboot"
+while true; do sleep 32000; done
diff --git a/examples/shutdown-1.0/script/hardshutdown.c b/examples/shutdown-1.0/script/hardshutdown.c
new file mode 100644
index 000000000..c21ddad58
--- /dev/null
+++ b/examples/shutdown-1.0/script/hardshutdown.c
@@ -0,0 +1,168 @@
+/* Including <unistd.h> makes sure that on a glibc system
+ * <features.h> is included, which again defines __GLIBC__
+ */
+
+#include <unistd.h>
+#include <stdio.h>	/* puts */
+#include <time.h>	/* nanosleep */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/*
+ * Magic values required to use _reboot() system call.
+ */
+#define	LINUX_REBOOT_MAGIC1	0xfee1dead
+#define	LINUX_REBOOT_MAGIC2	672274793
+#define	LINUX_REBOOT_MAGIC2A	85072278
+#define	LINUX_REBOOT_MAGIC2B	369367448
+/*
+ * Commands accepted by the _reboot() system call.
+ *
+ * RESTART     Restart system using default command and mode.
+ * HALT        Stop OS and give system control to ROM monitor, if any.
+ * CAD_ON      Ctrl-Alt-Del sequence causes RESTART command.
+ * CAD_OFF     Ctrl-Alt-Del sequence sends SIGINT to init task.
+ * POWER_OFF   Stop OS and remove all power from system, if possible.
+ * RESTART2    Restart system using given command string.
+ */
+#define	LINUX_REBOOT_CMD_RESTART	0x01234567
+#define	LINUX_REBOOT_CMD_HALT		0xCDEF0123
+#define	LINUX_REBOOT_CMD_CAD_ON		0x89ABCDEF
+#define	LINUX_REBOOT_CMD_CAD_OFF	0x00000000
+#define	LINUX_REBOOT_CMD_POWER_OFF	0x4321FEDC
+#define	LINUX_REBOOT_CMD_RESTART2	0xA1B2C3D4
+
+
+#define USE_LIBC
+
+#ifdef USE_LIBC
+
+/* libc version */
+#if defined __GLIBC__ && __GLIBC__ >= 2
+#  include <sys/reboot.h>
+#  define REBOOT(cmd) reboot(cmd)
+#else
+extern int reboot(int, int, int);
+#  define REBOOT(cmd) reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,(cmd))
+#endif
+
+static int my_reboot(int cmd)
+{
+	return REBOOT(cmd);
+}
+
+#else /* no USE_LIBC */
+
+/* direct syscall version */
+#include <linux/unistd.h>
+
+#ifdef _syscall3
+_syscall3(int,  reboot,  int,  magic, int, magic_too, int, cmd);
+#else
+/* Let us hope we have a 3-argument reboot here */
+extern int reboot(int, int, int);
+#endif
+
+static int my_reboot(int cmd)
+{
+	return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd);
+}
+
+#endif
+
+
+static void do_reboot(void)
+{
+	my_reboot(LINUX_REBOOT_CMD_RESTART);
+}
+static void do_poweroff(void)
+{
+	my_reboot(LINUX_REBOOT_CMD_POWER_OFF);
+}
+static void do_halt(void)
+{
+	my_reboot(LINUX_REBOOT_CMD_HALT);
+}
+
+static void usage(void)
+{
+	puts(
+	    "Usage: hardshutdown -h|-r|-p [NN]\n"
+	    "	NN - seconds to sleep before requested action"
+	);
+	exit(1);
+}
+
+enum action_t {
+	SHUTDOWN,	// do nothing
+	HALT,
+	POWEROFF,
+	REBOOT
+};
+
+int main(int argc, char *argv[])
+{
+	struct timespec t = {0,0};
+	enum action_t action = SHUTDOWN;
+	int c, i;
+	char *prog, *ptr;
+
+	//if (*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */
+	prog = argv[0];
+	ptr = strrchr(prog,'/');
+	if (ptr)
+		prog = ptr+1;
+
+	for (c=1; c < argc; c++) {
+		if (argv[c][0] >= '0' && argv[c][0] <= '9') {
+			t.tv_sec = strtol(argv[c], NULL, 10);
+			continue;
+		}
+		if (argv[c][0] != '-') {
+			usage();
+			return 1;
+		}
+		for (i=1; argv[c][i]; i++) {
+			switch (argv[c][i]) {
+			case 'h':
+				action = HALT;
+				break;
+			case 'p':
+				action = POWEROFF;
+				break;
+			case 'r':
+				action = REBOOT;
+				break;
+			default:
+				usage();
+				return 1;
+			}
+		}
+	}
+
+	if (action==SHUTDOWN) {
+		usage();
+		return 1;
+	}
+
+	chdir("/");
+	while (nanosleep(&t,&t)<0)
+		if (errno!=EINTR) break;
+
+	switch (action) {
+	case HALT:
+		do_halt();
+		break;
+	case POWEROFF:
+		do_poweroff();
+		break;
+	case REBOOT:
+		do_reboot();
+		break;
+	default: /* SHUTDOWN */
+		break;
+	}
+	return 1;
+}
diff --git a/examples/shutdown-1.0/script/hardshutdown.make.sh b/examples/shutdown-1.0/script/hardshutdown.make.sh
new file mode 100755
index 000000000..90f8c5456
--- /dev/null
+++ b/examples/shutdown-1.0/script/hardshutdown.make.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+gcc -Wall -Os -o hardshutdown hardshutdown.c
+strip hardshutdown
+
+#or:
+#diet gcc -Wall -o hardshutdown hardshutdown.c
+#elftrunc hardshutdown
diff --git a/examples/shutdown-1.0/script/shutdown b/examples/shutdown-1.0/script/shutdown
new file mode 100755
index 000000000..dbab9d81e
--- /dev/null
+++ b/examples/shutdown-1.0/script/shutdown
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+
+# Usually, /sbin/ has symlinks named halt, reboot, poweroff
+# (and also possibly shutdown) to e.g.
+# /app/shutdown-1.0/script/shutdown (this file).
+cd /app/shutdown-1.0/script || exit 1
+test -x ./do_shutdown || exit 1
+test -x ./hardshutdown || exit 1
+
+# "reboot -f" -> "shutdown -f -r" -> "hardshutdown -r" -> immediate reboot
+# "reboot" -> "shutdown -r" -> "do_shutdown -r"
+# ^^^^^^^^^^^^^^^^^^ similarly for halt, poweroff.
+# "shutdown" -> "do_shutdown" (everything killed/unmounted, but kernel not asked to do any poweroff etc)
+force=""
+test x"$1" = x"-f" && {
+	force="-f"
+	shift
+}
+test ! "$*" && test x"${0##*/}" = x"halt" && exec "$0" $force -h
+test ! "$*" && test x"${0##*/}" = x"reboot" && exec "$0" $force -r
+test ! "$*" && test x"${0##*/}" = x"poweroff" && exec "$0" $force -p
+# We have something else than allowed parameters?
+test x"$*" = x"" || test x"$*" = x"-h" || test x"$*" = x"-r" || test x"$*" = x"-p" || {
+	echo "Syntax: $0 [-f] [-h/-r/-p]"
+	exit 1
+}
+
+# Emergency shutdown?
+test "$force" && {
+	exec ./hardshutdown "$@"
+	exit 1
+}
+
+# Normal shutdown
+
+# We must have these executables on root fs
+# (mount/umount aren't checked, all systems are ok versus that):
+test -x /bin/killall5 -o -x /sbin/killall5 || exit 1
+test -x /bin/ps       -o -x /sbin/ps       || exit 1
+test -x /bin/date     -o -x /sbin/date     || exit 1
+test -x /bin/xargs    -o -x /sbin/xargs    || exit 1
+test -x /bin/wc       -o -x /sbin/wc       || exit 1
+test -x /bin/cat      -o -x /sbin/cat      || exit 1
+test -x /bin/sort     -o -x /sbin/sort     || exit 1
+
+i="`ulimit -n`"
+echo -n "Closing file descriptors $i-3... "
+while test "$i" -ge 3; do
+	eval "exec $i>&-"
+	i=$((i-1))
+done
+
+echo "Shutting down. Please stand by..."
+
+# setsid & /dev/null:
+# make it a process leader & detach it from current tty.
+# Why /dev/null and not /dev/console?
+# I have seen a system which locked up while opening /dev/console
+# due to the bug (?) in keyboard driver.
+setsid env - PATH="$PATH" ./do_shutdown "$@" </dev/null >/dev/null 2>&1 &
+
+while true; do read junk; done
diff --git a/examples/shutdown-1.0/script/stop_storage b/examples/shutdown-1.0/script/stop_storage
new file mode 100755
index 000000000..1be5f735b
--- /dev/null
+++ b/examples/shutdown-1.0/script/stop_storage
@@ -0,0 +1,81 @@
+#!/bin/sh
+# Do unmount/remount-ro. Wait.
+# KILL everybody. Wait.
+# Repeat.
+
+umountcnt=2
+writeout=0	# increase if your kernel doesn ot guarantee writes to complete
+
+# No /usr - we are expecting all binaries to be accessible
+# from root fs alone
+PATH=/sbin:/bin
+
+say() {
+	printf "\r%s\n\r" "$*"
+}
+
+showps() {
+	# sleep 1 ensures that xargs will have time to start up
+	# this makes pslist less prone to random jitter
+	pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs`
+	pscnt=$(( `say "$pslist" | wc -w` + 0 ))
+	if test x"$VERBOSE" = x; then
+		say "* `date '+%H:%M:%S'` $pscnt processes"
+	else
+		say "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist"
+	fi
+}
+
+say "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'"
+
+showps
+
+i="$umountcnt"
+while test "$i" -gt 0; do
+	say "* `date '+%H:%M:%S'` Unmounting filesystems"
+	umount -a -n -r -f
+	# In case we unmounted proc...
+	test -e /proc/version || mount -t proc none /proc
+	# Remounting / RO isn't necessary when /etc/mtab is linked to /proc/mounts:
+	# already done. But let's be more paranoid here...
+	say "* `date '+%H:%M:%S'` Remounting root filesystem read-only"
+	mount -n -o remount,ro /
+	say "* `date '+%H:%M:%S'` Freeing loop devices"
+	for a in /dev/loop*; do
+		test -b "$a" && losetup -d "$a"
+	done
+	say "* `date '+%H:%M:%S'` Syncing"
+	sync
+	say "* `date '+%H:%M:%S'` Executing: killall5 -KILL"
+	killall5 -9
+	showps
+	i=$((i-1))
+done
+
+say "* `date '+%H:%M:%S'` Filesystem status (/proc/mounts)"
+cat /proc/mounts \
+| {
+	bad=false
+	while read dev mntpoint fstype opt n1 n2; do
+		case "$fstype" in
+		( proc | sysfs | usbfs | devpts | rpc_pipefs | binfmt_misc | autofs | rootfs | tmpfs | ramfs )
+			say "$dev $mntpoint $fstype $opt $n1 $n2"
+			continue
+			;;
+		esac
+		if test "${opt:0:2}" = "rw"; then
+			say "$dev $mntpoint $fstype $opt $n1 $n2 - RW!"
+			bad=true
+		else
+			say "$dev $mntpoint $fstype $opt $n1 $n2"
+		fi
+	done
+	if $bad; then
+		say "ERROR: we have filesystems mounted RW! Press <Enter> (^J)..."
+		read junk </dev/console
+		#sh </dev/console >&0 2>&0 # debug
+	fi
+}
+
+# Disk cache writeout
+sleep "$writeout"
diff --git a/examples/shutdown-1.0/script/stop_tasks b/examples/shutdown-1.0/script/stop_tasks
new file mode 100755
index 000000000..2d752a3da
--- /dev/null
+++ b/examples/shutdown-1.0/script/stop_tasks
@@ -0,0 +1,70 @@
+#!/bin/sh
+# We are trying to be nice.
+# TERM everybody. Give them some time to die.
+# KILL might make some filesystems non-unmountable,
+# so we'll do it in stop_storage instead.
+
+killcnt=30
+
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+
+echo "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'"
+
+showps() {
+	# sleep 1 ensures that xargs will have time to start up.
+	# This makes pslist less prone to random jitter.
+	pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs`
+	pscnt=$(( `echo "$pslist" | wc -w` + 0 ))
+	if test x"$VERBOSE" = x; then
+		echo "* `date '+%H:%M:%S'` $pscnt processes"
+	else
+		echo "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist"
+	fi
+}
+
+# Sync.
+# Rationale: sometimes buggy root processes can
+# hang the system when killed (X for example may have problems
+# with restoring text mode on a poorly supported hardware).
+# These are bugs and must be fixed, but until then users will lose
+# dirty data on shutdown! Let's make that less likely.
+sync &
+
+# Send SIGTERMs. If list of processes changes, proceed slower.
+# If it has stabilised (all who wanted to, exited), proceed faster.
+showps
+i="$killcnt"
+while test "$i" -gt 0; do
+	echo "* `date '+%H:%M:%S'` Sending CONT, TERM" #, HUP"
+	# I've seen "killall5 2.86" which doesn't grok signal names!
+	killall5 -18
+	killall5 -15
+	#killall5 -1    # HUP: because interactive bash does not die on TERM...
+	# but init will reread /etc/inittab on HUP and my /etc is on non root fs!
+	# -> umounts will complain.
+	oldpslist="$pslist"
+	showps
+	if test x"$pslist" = x"$oldpslist"; then
+		i=$((i-8))
+	fi
+	i=$((i-2))
+done
+
+echo "* `date '+%H:%M:%S'` Turning off swap"
+swapoff -a
+cat /proc/swaps | grep -v ^Filename | cut -d ' ' -f1 \
+| while read -r line; do
+	test "$line" && {
+		echo swapoff "$line"
+		swapoff "$line"
+	}
+done
+
+echo "* /proc/swaps:"
+cat /proc/swaps
+echo "* /proc/mounts:"
+cat /proc/mounts
+echo "* ps -A e:"
+ps -A e
+echo "* top -bn1:"
+top -bn1


More information about the busybox-cvs mailing list