author | Olivier Brunel
<jjk@jjacky.com> 2015-02-27 17:50:49 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2015-04-04 12:47:35 UTC |
parent | 8e86b093736fca78eb26a60290216790f57c2932 |
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}