Welcome to little lamb

Code » anopa » commit 06bf843

Add stage{0..4}

author Olivier Brunel
2015-02-27 17:50:49 UTC
committer Olivier Brunel
2015-04-04 12:47:35 UTC
parent 8e86b093736fca78eb26a60290216790f57c2932

Add stage{0..4}

doc/aa-stage0.pod +42 -0
doc/aa-stage1.pod +27 -0
doc/aa-stage2.pod +28 -0
doc/aa-stage3.pod +30 -0
doc/aa-stage4.pod +26 -0
doc/anopa.pod +7 -3
package/modes +5 -0
package/targets.mak +12 -0
src/scripts/aa-stage0 +53 -0
src/scripts/aa-stage1 +51 -0
src/scripts/aa-stage2 +46 -0
src/scripts/aa-stage3 +68 -0
src/scripts/aa-stage4 +17 -0

diff --git a/doc/aa-stage0.pod b/doc/aa-stage0.pod
new file mode 100644
index 0000000..9420883
--- /dev/null
+++ b/doc/aa-stage0.pod
@@ -0,0 +1,42 @@
+=head1 NAME
+
+aa-stage0 - Stage 0: Mounting root file system
+
+=head1 SYNOPSIS
+
+B<aa-stage0>
+
+=head1 DESCRIPTION
+
+B<aa-stage0>(1) is an B<execline> script meant to be used as I<init> inside an
+initramfs.
+
+It loads the environment from I</etc/anopa/env> (via B<s6-envdir>), and will log
+messages into file I</boot.log>, prefixing them with a TAI timestamp via
+B<s6-tai64n>.
+
+It doesn't do much in itself, the bulk of the work being done via services, much
+like during system init (stage 1/2). It will simply call B<aa-start>(1) using
+I</servives> as repodir, and I</etc/anopa/onboot> as listdir for services to
+start.
+
+Services are meant to mount the root file system on to I</root-fs> and mount all
+API file systems in there as well: I</root-fs/dev>, I</root-fs/proc>,
+I</root-fs/sys> and I</root-fs/run>
+
+It is also up to them to load required kernel modules in order to do so.
+
+Once B<aa-start>(1) is done, B<aa-stage0>(1) will mount bind the rootfs (of the
+initramfs) onto I</root-fs/run/initramfs> (so it's possible to pivot back into
+it at the end of stage 3) before doing a mount move of I</root-fs> onto I</> and
+executing (via B<aa-chroot>(1)) into I</sbin/init>, or whatever was specified
+via I<init> on the kernel command line.
+
+If B<aa-start>(1) exits non-zero, e.g. if an essential service failed to be
+started, B<aa-stage0>(1) assumes the root file system couldn't be mounted, and
+tries to open a shell (I<sh -i>) to give you a possibility to fix things.
+Exiting the shell will resume the process.
+
+You can also specify I<break> on the kernel command line to open a shell after
+B<aa-start>(1) succesfully completed, before moving I</root-fs> to I</> (also
+before mount binding the rootfs).
diff --git a/doc/aa-stage1.pod b/doc/aa-stage1.pod
new file mode 100644
index 0000000..b8aebfa
--- /dev/null
+++ b/doc/aa-stage1.pod
@@ -0,0 +1,27 @@
+=head1 NAME
+
+aa-stage1 - Stage 1: Preparing system
+
+=head1 SYNOPSIS
+
+B<aa-stage1>
+
+=head1 DESCRIPTION
+
+B<aa-stage1>(1) is an B<execline> script meant to be used as I<init> for the
+system.
+
+It loads the environment from I</etc/anopa/env> (via B<s6-envdir>), and will log
+messages into file I</run/initramfs/boot.log>, prefixing them with a TAI
+timestamp via B<s6-tai64n>.
+
+It doesn't do much in itself, the bulk of the work being done via services. It
+will create the runtime repository I</run/services> using B<aa-enable>(1) and
+I</etc/anopa/enabled> as listdir.
+
+A service I<uncaught-logs> must exist and be a logger that will be used to catch
+all "uncaught logs" (i.e. anything not redirected to a service logger). A FIFO
+must be created as I</run/services/.scandir/uncaught-logs/fifo> and will be
+automatically started by B<s6-svscan>; It will also be used as trigger for
+B<aa-stage2>(1).
+
diff --git a/doc/aa-stage2.pod b/doc/aa-stage2.pod
new file mode 100644
index 0000000..98f37ce
--- /dev/null
+++ b/doc/aa-stage2.pod
@@ -0,0 +1,28 @@
+=head1 NAME
+
+aa-stage2 - Stage 2: Initialyzing system
+
+=head1 SYNOPSIS
+
+B<aa-stage2>
+
+=head1 DESCRIPTION
+
+B<aa-stage2>(1) is an B<execline> script called by B<aa-stage1> once the runtime
+repository has been created and B<s6-svscan> is running as PID 1, to actually
+perform all initialization tasks.
+
+It will log messages into file I</run/initramfs/boot.log>, prefixing them with a
+TAI timestamp via B<s6-tai64n>.
+
+It starts all services via B<aa-start>(1) using I</etc/anopa/onboot/default> as
+listdir, unless argument B<aa> was specified on kernel command line, in which
+case I</etc/anopa/onboot/ARGVALUE> is used as listdir intead.
+
+If B<aa-start>(1) exits non-zero, it assumes there might not even be a getty
+running, and will try to open a shell (I<sh -i>).
+
+If B<aa-start>(1) exit zero, the system is assumed to be ready, and the log file
+I</run/initramfs/boot.log> will be moved to persistent storage, into
+I</var/log/boot>, via B<aa-mvlog>(1). If that fails, a warning will be shown
+(and added to the log file).
diff --git a/doc/aa-stage3.pod b/doc/aa-stage3.pod
new file mode 100644
index 0000000..e2d8de1
--- /dev/null
+++ b/doc/aa-stage3.pod
@@ -0,0 +1,30 @@
+=head1 NAME
+
+aa-stage3 - Stage 3: Preparing reboot/poweroff/halt
+
+=head1 SYNOPSIS
+
+B<aa-stage3>
+
+=head1 DESCRIPTION
+
+B<aa-stage3>(1) is an B<execline> script B<s6-svscan> will exec into when told,
+e.g. via B<aa-shutdown>(1).
+
+It will log messages into file I</var/log/boot/current>, prefixing them with a
+TAI timestamp via B<s6-tai64n>, also sending them to I</dev/console>.
+
+It stops all services via B<aa-stop>(1) using option B<--all>, and skipping
+service I<uncaught-logs> to catch all possible logs as long as possible. The
+B<s6-supervise> process of that service will be sent command 'x' so it simply
+exits when the supervised process will.
+
+Signal SIGTERM is then sent to (almost) all processes, using B<aa-kill>(1) with
+option B<--skip>; Then SIGKILL is sent similarly.
+
+It will then pivot root (back) into I</run/initramfs> via B<aa-pivot>(1) and
+exec into I</shutdown> will the same argument it received from B<s6-svscan>, so
+either "halt", "reboot" or "poweroff".
+
+If B<aa-pivot>(1) fails, it will try to open a shell (I<sh -i>) to let you deal
+with things manually.
diff --git a/doc/aa-stage4.pod b/doc/aa-stage4.pod
new file mode 100644
index 0000000..1cdcc6f
--- /dev/null
+++ b/doc/aa-stage4.pod
@@ -0,0 +1,26 @@
+=head1 NAME
+
+aa-stage4 - Stage 4: Unmounting root file system
+
+=head1 SYNOPSIS
+
+B<aa-stage4>
+
+=head1 DESCRIPTION
+
+B<aa-stage4>(1) is an B<execline> script meant to be used as I<shutdown> inside
+an initramfs.
+
+It loads the environment from I</etc/anopa/env> (via B<s6-envdir>).
+
+It doesn't do much in itself, the bulk of the work being done via services, much
+like during system shutdown (stage 3). It will simply call B<aa-stop>(1) using
+I</servives> as repodir, with option B<--all>.
+
+Services are meant to unmount the root file system from I</root-fs> as well as
+all API file systems in there: I</root-fs/dev>, I</root-fs/proc>,
+I</root-fs/sys> and I</root-fs/run>
+
+Then it will mount I</dev> and I</proc> in order to run B<aa-terminate>(1) to
+make sure everything is closed/unmounted. It then performs the requested action
+(on stage 3) via B<aa-reboot>(1).
diff --git a/doc/anopa.pod b/doc/anopa.pod
index f492f50..2122219 100644
--- a/doc/anopa.pod
+++ b/doc/anopa.pod
@@ -281,12 +281,12 @@ to enable on boot.
 In the later case, the content of the folder will also be merged/copied over
 into the servicedir.
 
-Used by B<aa-enable>(1).
+Used by B<aa-enable>(1) from B<aa-stage1>(1).
 
 =item I</etc/anopa/onboot/default>
 
 List directory containing empty regular files, whose name is the name of a
-service to start on boot. Used by B<aa-start>(1).
+service to start on boot. Used by B<aa-start>(1) from B<aa-stage2>(1).
 
 =item I</run/services>
 
@@ -310,7 +310,11 @@ Refer to B<aa-enable>(1) for more on how it works and how the copy operation
 takes place.
 
 Then, B<aa-start>(1) is used to start all services in order, getting the system
-up & ready. Refer to B<aa-start>(1) for more on how it works.
+up & ready. Refer to B<aa-start>(1) for more on how it works; Refer to
+B<aa-stage1>(1) and B<aa-stage2>(1) for more on this init process on boot works.
+
+You can also see B<aa-stage0>(1) and B<aa-stage4>(1) for how to use B<anopa> in
+your initramfs.
 
 Note that this is of course only a possible solution to set up your system, you
 are of course free to organize things differently, only the tool(s) you need
diff --git a/package/modes b/package/modes
index 7dead11..2b3c40d 100644
--- a/package/modes
+++ b/package/modes
@@ -8,6 +8,11 @@ aa-mvlog                0755
 aa-pivot                0755
 aa-reboot               0755
 aa-shutdown             0755
+aa-stage0               0755
+aa-stage1               0755
+aa-stage2               0755
+aa-stage3               0755
+aa-stage4               0755
 aa-start                0755
 aa-stop                 0755
 aa-sync                 0755
diff --git a/package/targets.mak b/package/targets.mak
index 4d54b1b..ec9efcc 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -18,6 +18,13 @@ aa-umount
 BIN_SCRIPTS_TARGET := \
 aa-shutdown
 
+LIBEXEC_SCRIPTS_TARGET := \
+aa-stage0 \
+aa-stage1 \
+aa-stage2 \
+aa-stage3 \
+aa-stage4
+
 DOC_TARGETS := \
 anopa.1 \
 aa-chroot.1 \
@@ -30,6 +37,11 @@ aa-mvlog.1 \
 aa-pivot.1 \
 aa-reboot.1 \
 aa-shutdown.1 \
+aa-stage0.1 \
+aa-stage1.1 \
+aa-stage2.1 \
+aa-stage3.1 \
+aa-stage4.1 \
 aa-start.1 \
 aa-stop.1 \
 aa-sync.1 \
diff --git a/src/scripts/aa-stage0 b/src/scripts/aa-stage0
new file mode 100755
index 0000000..f362b60
--- /dev/null
+++ b/src/scripts/aa-stage0
@@ -0,0 +1,53 @@
+#!/bin/execlineb -P
+/bin/emptyenv /bin/s6-envdir /etc/anopa/env /bin/exec
+
+# Set up a pipe to log messages
+piperw 3 4
+background
+{
+    fdclose 4 fdmove 0 3
+    redirfd -a 1 /boot.log
+    fdmove -c 2 1
+    s6-tai64n
+}
+
+# aa-* tools will send output to both 1 & 2 via -D
+fdclose 3
+fdmove 2 4
+foreground { aa-echo -D "Stage 0: Mounting root file system..." }
+
+# Safety
+cd /
+umask 022
+
+# Start services
+foreground { if -n
+{
+  if { emptyenv -c aa-start -D -r /services -l /etc/anopa/onboot }
+  foreground {
+    # if "break" was specified on kernel cmdline, let's open a shell
+    if -t { aa-incmdline -qf /root-fs/proc/cmdline break }
+    foreground { aa-echo -DB "Break requested" }
+    foreground { aa-echo -Dt "Trying to open a shell; " +g exit +w " to continue" }
+    emptyenv -c sh -i
+  }
+}
+
+# aa-start failed (i.e. an essential service failed to be started), so we assume
+# the root fs wasn't mounted: try a shell so user has a chance to fix things
+foreground { aa-echo -DBe "Mouting root file system failed" }
+foreground { aa-echo -Dt "Trying to open a shell; " +g exit +w " to continue" }
+fdmove -c 2 1
+emptyenv -c sh -i
+}
+
+if { aa-echo -DB "Moving /root-fs to /..." }
+# First we mount bind the rootfs (initramfs) onto /run/initramfs so we can come
+# back to it for stage 4
+if { aa-mount -Bd / /root-fs/run/initramfs }
+fdmove -c 2 1
+backtick -n -D /sbin/init INIT { aa-incmdline -rf /root-fs/proc/cmdline init }
+import -u INIT
+cd /root-fs
+if { aa-mount -M . / }
+./run/initramfs/bin/emptyenv -c ./run/initramfs/bin/aa-chroot . ${INIT}
diff --git a/src/scripts/aa-stage1 b/src/scripts/aa-stage1
new file mode 100755
index 0000000..f920366
--- /dev/null
+++ b/src/scripts/aa-stage1
@@ -0,0 +1,51 @@
+#!@BINDIR@/execlineb -P
+@BINDIR@/emptyenv @BINDIR@/s6-envdir /etc/anopa/env @BINDIR@/exec
+
+# Set up a pipe to log messages
+piperw 3 4
+background
+{
+    fdclose 4 fdmove 0 3
+    redirfd -a 1 /run/initramfs/boot.log
+    fdmove -c 2 1
+    s6-tai64n
+}
+
+# aa-* tools will send output to both 1 & 2 via -D
+fdclose 3
+fdmove 2 4
+foreground { aa-echo -DB "Stage 1: Preparing system..." }
+
+# Safety
+cd /
+umask 022
+
+# Create the repository
+foreground { emptyenv -c
+aa-enable -Dl /etc/anopa/enabled -k uncaught-logs -f @LIBEXECDIR@/aa-stage3 }
+
+# Make sure the FIFO needed for the switch to stage 2 is there
+ifelse -X -n { aa-test -p /run/services/.scandir/uncaught-logs/fifo }
+{
+    foreground { aa-echo -DBe "Cannot start s6-svscan: No uncaught-logs fifo found" }
+    fdmove -c 2 1
+    foreground { aa-echo -t "Trying to open a shell..." }
+    emptyenv -c sh -i
+}
+
+# Reopen stdin/stdout/stderr to make them point to the right places
+redirfd -r 0 /dev/null
+redirfd -wnb 1 /run/services/uncaught-logs/fifo # (black magic: doesn't block)
+fdmove -c 2 1
+
+# Fork the stage2 script and have it ready to start as soon as the catch-all
+# logger is in place
+background
+{
+    s6-setsid
+    redirfd -w 1 /run/services/uncaught-logs/fifo # (blocks until the logger reads)
+    @LIBEXECDIR@/aa-stage2
+}
+
+# Start the "real" stage 2 (as far as PID 1 goes)
+emptyenv -c s6-svscan -t0 /run/services/.scandir
diff --git a/src/scripts/aa-stage2 b/src/scripts/aa-stage2
new file mode 100755
index 0000000..10d24b3
--- /dev/null
+++ b/src/scripts/aa-stage2
@@ -0,0 +1,46 @@
+#!@BINDIR@/execlineb -P
+
+# Set up a pipe to log messages
+piperw 3 4
+background
+{
+  fdclose 4 fdmove 0 3
+  redirfd -a 1 /run/initramfs/boot.log
+  fdmove -c 2 1
+  s6-tai64n
+}
+fdclose 3
+fdmove 2 4
+
+if -n -t
+{
+  # Reopen the console for stdin/stdout
+  redirfd -r 0 /dev/console
+  redirfd -w 1 /dev/console
+  # And start everything
+  foreground { aa-echo -DB "Stage 2: Initializing system..." }
+  backtick -n -D default LISTDIR { aa-incmdline -rs aa }
+  import -u LISTDIR
+  if { emptyenv -c aa-start -D -l /etc/anopa/onboot/${LISTDIR} }
+  foreground { aa-echo -DB "System ready." }
+  # close logger
+  fdmove -c 2 1
+  # no more input
+  redirfd -r 0 /dev/null
+  # move log to file system
+  if -n -t { aa-mvlog /run/initramfs/boot.log /var/log/boot }
+  # show (& log) a warning
+  pipeline { aa-echo -DBw "Failed to save /run/initramfs/boot.log to /var/log/boot" }
+  redirfd -a 1 /run/initramfs/boot.log
+  s6-tai64n
+}
+
+# Something went wrong, likely aa-start failed (i.e. an essential service failed
+# to be started), so we assume there's not event a getty: try a shell so user
+# has a chance to fix things
+redirfd -r 0 /dev/console
+redirfd -w 1 /dev/console
+foreground { aa-echo -DBe "System initialization failed" }
+fdmove -c 2 1
+foreground { aa-echo -t "Trying to open a shell..." }
+emptyenv -c sh -i
diff --git a/src/scripts/aa-stage3 b/src/scripts/aa-stage3
new file mode 100755
index 0000000..e502b46
--- /dev/null
+++ b/src/scripts/aa-stage3
@@ -0,0 +1,68 @@
+#!@BINDIR@/execlineb -S0
+
+# Set up a pipe to log messages
+piperw 3 4
+background
+{
+    fdclose 4 fdmove 0 3
+    redirfd -a 1 /var/log/boot/current
+    fdmove -c 2 1
+    s6-tai64n
+}
+fdclose 3
+fdmove 2 4
+
+# Make sure we're sane
+cd /
+redirfd -r 0 /dev/console
+redirfd -w 1 /dev/console
+
+foreground { aa-echo -DB "Stage 3: Preparing ${1}..." }
+# Stop all running services -- s6-svscan did only exec into us, leaving the
+# whole supervised tree intact. Here we stop everything (longrun & oneshot) in
+# order.
+foreground { emptyenv -c aa-stop -D -ak uncaught-logs }
+# We left the catch-all running (in case), make sure everything will exit
+# properly when we send TERM
+foreground { s6-svc -x /run/services/uncaught-logs }
+
+# Kill everything left
+foreground { aa-echo -DB "Killing remaining processes..." }
+foreground { aa-echo -Dt "Sending SIGTERM to all processes..." }
+foreground { aa-kill -st }
+wait -r { } # Reap zombies
+# logger was killed, and we won't open it back
+fdmove -c 2 1
+foreground { pipeline { aa-echo -Dt "Sending SIGKILL to all processes..." }
+    redirfd -a 1 /var/log/boot/current
+    s6-tai64n
+}
+foreground { aa-kill -sk }
+wait { } # Wait for all children
+
+foreground { aa-sync }
+foreground { pipeline { aa-echo -DB "Pivoting root..." }
+    redirfd -a 1 /var/log/boot/current
+    s6-tai64n
+}
+
+cd /run/initramfs
+foreground {
+    if -n
+    {
+        # grab the chroot binary for after the pivot
+        if { cp /bin/aa-chroot . }
+        # PIVOT!
+        if { aa-pivot . root-fs }
+    }
+
+    # Something went wrong
+    foreground { pipeline { aa-echo -DBe "Unable to ${1}: Failed to pivot root" }
+        redirfd -a 1 /var/log/boot/current
+        s6-tai64n
+    }
+    foreground { aa-echo -t "Trying to open a shell..." }
+    emptyenv -c sh -i
+}
+# And let initramfs end things ($1 is halt/reboot/poweroff)
+./aa-chroot . /shutdown ${1}
diff --git a/src/scripts/aa-stage4 b/src/scripts/aa-stage4
new file mode 100755
index 0000000..cf6176d
--- /dev/null
+++ b/src/scripts/aa-stage4
@@ -0,0 +1,17 @@
+#!/bin/execlineb -S0
+/bin/emptyenv /bin/s6-envdir /etc/anopa/env /bin/exec
+
+foreground { aa-echo -B "Stage 4: Unmounting root file system..." }
+foreground { emptyenv -c aa-stop -r /services -a }
+
+# At this point everything should be unmounted, so we need to remount what's
+# needed for aa-terminate to work. We can't not umount /proc & /dev in aa-stop
+# above and then assume they're here, since e.g. moving them back (from root-fs)
+# might have failed. (And trying to mount /dev on top of it won't prevent
+# aa-terminate from doing its thing properly.)
+foreground { aa-echo -B "Mounting /dev & /proc and terminating..." }
+foreground { aa-mount -t devtmpfs dev /dev }
+foreground { aa-mount -t proc proc /proc }
+foreground { aa-terminate -la }
+	foreground { sh -i }
+aa-reboot --${1}