forked from mirrors/nixpkgs
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>
This commit is contained in:
parent
dc6d003011
commit
4f796c28d5
|
@ -209,6 +209,7 @@ in rec {
|
|||
tests.bittorrent = callTest tests/bittorrent.nix {};
|
||||
tests.blivet = callTest tests/blivet.nix {};
|
||||
tests.boot = callSubTests tests/boot.nix {};
|
||||
tests.boot-stage1 = callTest tests/boot-stage1.nix {};
|
||||
tests.cadvisor = hydraJob (import tests/cadvisor.nix { system = "x86_64-linux"; });
|
||||
tests.chromium = (callSubTests tests/chromium.nix { system = "x86_64-linux"; }).stable;
|
||||
tests.cjdns = callTest tests/cjdns.nix {};
|
||||
|
|
153
nixos/tests/boot-stage1.nix
Normal file
153
nixos/tests/boot-stage1.nix
Normal file
|
@ -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$\''');
|
||||
'';
|
||||
}
|
Loading…
Reference in a new issue