summaryrefslogtreecommitdiffstats
path: root/nixos/tests/boot-stage1.nix
diff options
context:
space:
mode:
authoraszlig <aszlig@redmoonstudios.org>2016-05-06 16:06:22 +0200
committeraszlig <aszlig@redmoonstudios.org>2016-05-06 16:56:43 +0200
commit4f796c28d57887cc9812190bc99fb45b2acd6d1c (patch)
tree4ecf8e8f43213bb9d232352f086c01b10a06a3c2 /nixos/tests/boot-stage1.nix
parentdc6d0030110d0ebd7f13a0fdaccfe494fbad0e29 (diff)
nixos/tests: Add a test for boot stage 1
We already have a small regression test for #15226 within the swraid installer test. Unfortunately, we only check there whether the md kthread got signalled but not whether other rampaging processes are still alive that *should* have been killed. So in order to do this we provide multiple canary processes which are checked after the system has booted up: * canary1: It's a simple forking daemon which just sleeps until it's going to be killed. Of course we expect this process to not be alive anymore after boot up. * canary2: Similar to canary1, but tries to mimick a kthread to make sure that it's going to be properly killed at the end of stage 1. * canary3: Like canary2, but this time using a @ in front of its command name to actually prevent it from being killed. * kcanary: This one is a real kthread and it runs until killed, which shouldn't be the case. Tested with and without 67223ee and everything works as expected, at least on my machine. Signed-off-by: aszlig <aszlig@redmoonstudios.org>
Diffstat (limited to 'nixos/tests/boot-stage1.nix')
-rw-r--r--nixos/tests/boot-stage1.nix153
1 files changed, 153 insertions, 0 deletions
diff --git a/nixos/tests/boot-stage1.nix b/nixos/tests/boot-stage1.nix
new file mode 100644
index 000000000000..311acd7bb1c4
--- /dev/null
+++ b/nixos/tests/boot-stage1.nix
@@ -0,0 +1,153 @@
+import ./make-test.nix {
+ name = "boot-stage1";
+
+ machine = { config, pkgs, lib, ... }: {
+ boot.extraModulePackages = let
+ compileKernelModule = name: source: pkgs.runCommand name rec {
+ inherit source;
+ kdev = config.boot.kernelPackages.kernel.dev;
+ kver = config.boot.kernelPackages.kernel.modDirVersion;
+ ksrc = "${kdev}/lib/modules/${kver}/build";
+ } ''
+ echo "obj-m += $name.o" > Makefile
+ echo "$source" > "$name.c"
+ make -C "$ksrc" M=$(pwd) modules
+ install -vD "$name.ko" "$out/lib/modules/$kver/$name.ko"
+ '';
+
+ # This spawns a kthread which just waits until it gets a signal and
+ # terminates if that is the case. We want to make sure that nothing during
+ # the boot process kills any kthread by accident, like what happened in
+ # issue #15226.
+ kcanary = compileKernelModule "kcanary" ''
+ #include <linux/init.h>
+ #include <linux/module.h>
+ #include <linux/kernel.h>
+ #include <linux/kthread.h>
+ #include <linux/sched.h>
+
+ struct task_struct *canaryTask;
+
+ static int kcanary(void *nothing)
+ {
+ allow_signal(SIGINT);
+ allow_signal(SIGTERM);
+ allow_signal(SIGKILL);
+ while (!kthread_should_stop()) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ if (signal_pending(current)) break;
+ }
+ return 0;
+ }
+
+ static int kcanaryInit(void)
+ {
+ kthread_run(&kcanary, NULL, "kcanary");
+ return 0;
+ }
+
+ static void kcanaryExit(void)
+ {
+ kthread_stop(canaryTask);
+ }
+
+ module_init(kcanaryInit);
+ module_exit(kcanaryExit);
+ '';
+
+ in lib.singleton kcanary;
+
+ boot.initrd.kernelModules = [ "kcanary" ];
+
+ boot.initrd.extraUtilsCommands = let
+ compile = name: source: pkgs.runCommand name { inherit source; } ''
+ mkdir -p "$out/bin"
+ echo "$source" | gcc -Wall -o "$out/bin/$name" -xc -
+ '';
+
+ daemonize = name: source: compile name ''
+ #include <stdio.h>
+ #include <unistd.h>
+
+ void runSource(void) {
+ ${source}
+ }
+
+ int main(void) {
+ if (fork() > 0) return 0;
+ setsid();
+ runSource();
+ return 1;
+ }
+ '';
+
+ mkCmdlineCanary = { name, cmdline ? "", source ? "" }: (daemonize name ''
+ char *argv[] = {"${cmdline}", NULL};
+ execvp("${name}-child", argv);
+ '') // {
+ child = compile "${name}-child" ''
+ #include <stdio.h>
+ #include <unistd.h>
+
+ int main(void) {
+ ${source}
+ while (1) sleep(1);
+ return 1;
+ }
+ '';
+ };
+
+ copyCanaries = with lib; concatMapStrings (canary: ''
+ ${optionalString (canary ? child) ''
+ copy_bin_and_libs "${canary.child}/bin/${canary.child.name}"
+ ''}
+ copy_bin_and_libs "${canary}/bin/${canary.name}"
+ '');
+
+ in copyCanaries [
+ # Simple canary process which just sleeps forever and should be killed by
+ # stage 2.
+ (daemonize "canary1" "while (1) sleep(1);")
+
+ # We want this canary process to try mimicking a kthread using a cmdline
+ # with a zero length so we can make sure that the process is properly
+ # killed in stage 1.
+ (mkCmdlineCanary {
+ name = "canary2";
+ source = ''
+ FILE *f;
+ f = fopen("/run/canary2.pid", "w");
+ fprintf(f, "%d\n", getpid());
+ fclose(f);
+ '';
+ })
+
+ # This canary process mimicks a storage daemon, which we do NOT want to be
+ # killed before going into stage 2. For more on root storage daemons, see:
+ # https://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons/
+ (mkCmdlineCanary {
+ name = "canary3";
+ cmdline = "@canary3";
+ })
+ ];
+
+ boot.initrd.postMountCommands = ''
+ canary1
+ canary2
+ canary3
+ # Make sure the pidfile of canary 2 is created so that we still can get
+ # its former pid after the killing spree starts next within stage 1.
+ while [ ! -s /run/canary2.pid ]; do sleep 0.1; done
+ '';
+ };
+
+ testScript = ''
+ $machine->waitForUnit("multi-user.target");
+ $machine->succeed('test -s /run/canary2.pid');
+ $machine->fail('pgrep -a canary1');
+ $machine->fail('kill -0 $(< /run/canary2.pid)');
+ $machine->succeed('pgrep -a -f \'^@canary3$\''');
+ $machine->succeed('pgrep -a -f \'^kcanary$\''');
+ '';
+}