From c21dfaf836cf0eb5317035bc20395c751a205934 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Apr 2018 15:12:52 +0200 Subject: [PATCH] examples/shutdown-1.0: an example of reboot which does not signal init For one, my inits know nothing about the concept of "shutting down the system". Signed-off-by: Denys Vlasenko --- examples/shutdown-1.0/README | 30 ++++ examples/shutdown-1.0/script/do_shutdown | 54 ++++++ examples/shutdown-1.0/script/hardshutdown.c | 168 ++++++++++++++++++ .../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(+) create mode 100644 examples/shutdown-1.0/README create mode 100755 examples/shutdown-1.0/script/do_shutdown create mode 100644 examples/shutdown-1.0/script/hardshutdown.c create mode 100755 examples/shutdown-1.0/script/hardshutdown.make.sh create mode 100755 examples/shutdown-1.0/script/shutdown create mode 100755 examples/shutdown-1.0/script/stop_storage create mode 100755 examples/shutdown-1.0/script/stop_tasks 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 makes sure that on a glibc system + * is included, which again defines __GLIBC__ + */ + +#include +#include /* puts */ +#include /* nanosleep */ +#include +#include +#include + + +/* + * 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 +# 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 + +#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 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 (^J)..." + read junk &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 -- 2.25.1