diff --git a/lib/make-iso9660-image.sh b/lib/make-iso9660-image.sh index 01b0fafa1123..c6996e8db9a5 100644 --- a/lib/make-iso9660-image.sh +++ b/lib/make-iso9660-image.sh @@ -54,7 +54,7 @@ done # Also include a manifest of the closures in a format suitable for # nix-store --load-db. -if [ -n "$object"; ]; then +if [ -n "$object" ]; then printRegistration=1 perl $pathsFromGraph closure-* > nix-path-registration echo "nix-path-registration=nix-path-registration" >> pathlist fi diff --git a/lib/test-driver/Machine.pm b/lib/test-driver/Machine.pm index 5ff7bf6cea19..9f5971d70a1c 100644 --- a/lib/test-driver/Machine.pm +++ b/lib/test-driver/Machine.pm @@ -2,7 +2,6 @@ package Machine; use strict; use threads; -use Thread::Queue; use Socket; use IO::Handle; use POSIX qw(dup2); @@ -28,8 +27,8 @@ sub new { if (!$startCommand) { # !!! merge with qemu-vm.nix. $startCommand = - "qemu-system-x86_64 -m 384 -no-kvm-irqchip " . - "-net nic,model=virtio -net user \$QEMU_OPTS "; + "qemu-system-x86_64 -m 384 " . + "-net nic,model=virtio -net user,\$QEMU_NET_OPTS \$QEMU_OPTS "; $startCommand .= "-drive file=" . Cwd::abs_path($args->{hda}) . ",if=virtio,boot=on,werror=report " if defined $args->{hda}; $startCommand .= "-cdrom $args->{cdrom} " @@ -51,7 +50,6 @@ sub new { booted => 0, pid => 0, connected => 0, - connectedQueue => Thread::Queue->new(), socket => undef, stateDir => "$tmpDir/$name", monitor => undef, @@ -101,6 +99,14 @@ sub start { bind($monitorS, sockaddr_un($monitorPath)) or die "cannot bind monitor socket: $!"; listen($monitorS, 1) or die; + # Create a Unix domain socket to which the root shell in the guest will connect. + my $shellPath = $self->{stateDir} . "/shell"; + unlink $shellPath; + my $shellS; + socket($shellS, PF_UNIX, SOCK_STREAM, 0) or die; + bind($shellS, sockaddr_un($shellPath)) or die "cannot bind shell socket: $!"; + listen($shellS, 1) or die; + # Start the VM. my $pid = fork(); die if $pid == -1; @@ -108,22 +114,21 @@ sub start { if ($pid == 0) { close $serialP; close $monitorS; + close $shellS; open NUL, "{stateDir}; - $ENV{QEMU_OPTS} = "-nographic -no-reboot -redir tcp:65535::514 -monitor unix:./monitor"; + $ENV{USE_TMPDIR} = 1; + $ENV{QEMU_OPTS} = "-nographic -no-reboot -monitor unix:./monitor -chardev socket,id=shell,path=./shell"; + $ENV{QEMU_NET_OPTS} = "guestfwd=tcp:10.0.2.6:23-chardev:shell"; $ENV{QEMU_KERNEL_PARAMS} = "hostTmpDir=$ENV{TMPDIR}"; chdir $self->{stateDir} or die; exec $self->{startCommand}; die; } - # Wait until QEMU connects to the monitor. - accept($self->{monitor}, $monitorS) or die; - $self->waitForMonitorPrompt; - # Process serial line output. close $serialC; @@ -131,17 +136,31 @@ sub start { sub processSerialOutput { my ($self, $serialP) = @_; - $/ = "\r\n"; while (<$serialP>) { chomp; + s/\r$//; print STDERR $self->name, "# $_\n"; - $self->{connectedQueue}->enqueue(1) if $_ eq "===UP==="; } - # If the child dies, wake up connect(). - $self->{connectedQueue}->enqueue(1); } - $self->log("vm running as pid $pid"); + eval { + local $SIG{CHLD} = sub { die "QEMU died prematurely\n"; }; + + # Wait until QEMU connects to the monitor. + accept($self->{monitor}, $monitorS) or die; + + # Wait until QEMU connects to the root shell socket. QEMU + # does so immediately; this doesn't mean that the root shell + # has connected yet inside the guest. + accept($self->{socket}, $shellS) or die; + $self->{socket}->autoflush(1); + }; + die "$@" if $@; + + $self->waitForMonitorPrompt; + + $self->log("QEMU running (pid $pid)"); + $self->{pid} = $pid; $self->{booted} = 1; } @@ -190,27 +209,12 @@ sub connect { $self->start; - # Wait until the processQemuOutput thread signals that the machine - # is up. - retry sub { - return 1 if $self->{connectedQueue}->dequeue_nb(); - }; - - retry sub { - $self->log("trying to connect"); - my $socket = new IO::Handle; - $self->{socket} = $socket; - socket($socket, PF_UNIX, SOCK_STREAM, 0) or die; - connect($socket, sockaddr_un($self->{stateDir} . "/65535.socket")) or die; - $socket->autoflush(1); - print $socket "echo hello\n" or next; - flush $socket; - my $line = readline($socket); - chomp $line; - return 1 if $line eq "hello"; - }; - - $self->log("connected"); + local $SIG{ALRM} = sub { die "timed out waiting for the guest to connect\n"; }; + alarm 300; + readline $self->{socket} or die; + alarm 0; + + $self->log("connected to guest root shell"); $self->{connected} = 1; } @@ -294,7 +298,7 @@ sub waitUntilFails { } -sub mustFail { +sub fail { my ($self, $command) = @_; my ($status, $out) = $self->execute($command); die "command `$command' unexpectedly succeeded" @@ -302,6 +306,11 @@ sub mustFail { } +sub mustFail { + fail @_; +} + + # Wait for an Upstart job to reach the "running" state. sub waitForJob { my ($self, $jobName) = @_; @@ -360,6 +369,16 @@ sub shutdown { } +sub crash { + my ($self) = @_; + return unless $self->{booted}; + + $self->sendMonitorCommand("quit"); + + $self->waitForShutdown; +} + + # Make the machine unreachable by shutting down eth1 (the multicast # interface used to talk to the other VMs). We keep eth0 up so that # the test driver can continue to talk to the machine. diff --git a/lib/test-driver/test-driver.pl b/lib/test-driver/test-driver.pl index 98666d6c775d..ad1af5a6fbf4 100644 --- a/lib/test-driver/test-driver.pl +++ b/lib/test-driver/test-driver.pl @@ -74,6 +74,3 @@ END { runTests; - - -print STDERR "DONE\n"; diff --git a/modules/config/swap.nix b/modules/config/swap.nix new file mode 100644 index 000000000000..a3f241fc8c55 --- /dev/null +++ b/modules/config/swap.nix @@ -0,0 +1,74 @@ +{ config, pkgs, ... }: + +with pkgs.lib; + +{ + + ###### interface + + options = { + + swapDevices = mkOption { + default = []; + example = [ + { device = "/dev/hda7"; } + { device = "/var/swapfile"; } + { label = "bigswap"; } + ]; + description = '' + The swap devices and swap files. These must have been + initialised using mkswap. Each element + should be an attribute set specifying either the path of the + swap device or file (device) or the label + of the swap device (label, see + mkswap -L). Using a label is + recommended. + ''; + + type = types.list types.optionSet; + + options = {config, options, ...}: { + + options = { + + device = mkOption { + example = "/dev/sda3"; + type = types.string; + description = "Path of the device."; + }; + + label = mkOption { + example = "swap"; + type = types.string; + description = '' + Label of the device. Can be used instead of device. + ''; + }; + + cipher = mkOption { + default = false; + example = true; + type = types.bool; + description = '' + Encrypt the swap device to protect swapped data. This option + does not work with labels. + ''; + }; + + }; + + config = { + device = + if options.label.isDefined then + "/dev/disk/by-label/${config.label}" + else + mkNotdef; + }; + + }; + + }; + + }; + +} diff --git a/modules/config/system-path.nix b/modules/config/system-path.nix index d90747324189..7e04af21997e 100644 --- a/modules/config/system-path.nix +++ b/modules/config/system-path.nix @@ -22,7 +22,6 @@ let pkgs.cpio pkgs.curl pkgs.diffutils - pkgs.e2fsprogs pkgs.eject # HAL depends on it anyway pkgs.findutils pkgs.gawk @@ -44,7 +43,6 @@ let pkgs.pciutils pkgs.perl pkgs.procps - pkgs.reiserfsprogs pkgs.rsync pkgs.seccure pkgs.strace diff --git a/modules/installer/grub/grub-menu-builder.sh b/modules/installer/grub/grub-menu-builder.sh index 90bedf9543d1..f23184b0a06a 100644 --- a/modules/installer/grub/grub-menu-builder.sh +++ b/modules/installer/grub/grub-menu-builder.sh @@ -216,6 +216,10 @@ extraEntries=`cat <> $tmp <> $tmp fi diff --git a/modules/installer/grub/grub.nix b/modules/installer/grub/grub.nix index 4217e0d481c4..40c2ca606751 100644 --- a/modules/installer/grub/grub.nix +++ b/modules/installer/grub/grub.nix @@ -12,7 +12,8 @@ let inherit grub; inherit (pkgs) bash; path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; - inherit (config.boot.loader.grub) copyKernels extraEntries extraEntriesBeforeNixOS + inherit (config.boot.loader.grub) copyKernels + extraConfig extraEntries extraEntriesBeforeNixOS splashImage configurationLimit version default timeout; }; @@ -67,6 +68,15 @@ in ''; }; + extraConfig = mkOption { + default = ""; + example = "serial; terminal_output.serial"; + description = '' + Additional GRUB commands inserted in the configuration file + just before the menu entries. + ''; + }; + extraEntries = mkOption { default = ""; example = '' diff --git a/modules/module-list.nix b/modules/module-list.nix index 841a6b11a5fd..7bc98858506d 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -7,6 +7,7 @@ ./config/no-x-libs.nix ./config/nsswitch.nix ./config/power-management.nix + ./config/swap.nix ./config/system-path.nix ./config/timezone.nix ./config/unix-odbc-drivers.nix @@ -152,7 +153,6 @@ ./tasks/kbd.nix ./tasks/lvm.nix ./tasks/network-interfaces.nix - ./tasks/swap.nix ./tasks/swraid.nix ./tasks/tty-backgrounds.nix ] diff --git a/modules/services/databases/mysql.nix b/modules/services/databases/mysql.nix index 19c0bcc418a2..9c97a15d751e 100644 --- a/modules/services/databases/mysql.nix +++ b/modules/services/databases/mysql.nix @@ -100,7 +100,7 @@ in jobs.mysql = { description = "MySQL server"; - startOn = "started network-interfaces"; + startOn = "filesystem"; preStart = '' diff --git a/modules/services/databases/postgresql.nix b/modules/services/databases/postgresql.nix index ae6ac463dad0..38d2568613ec 100644 --- a/modules/services/databases/postgresql.nix +++ b/modules/services/databases/postgresql.nix @@ -28,9 +28,6 @@ let postgresql = postgresqlAndPlugins pkgs.postgresql; - startDependency = if config.services.gw6c.enable then - "gw6c" else "network-interfaces"; - run = "${pkgs.su}/bin/su -s ${pkgs.stdenv.shell} postgres"; flags = optional cfg.enableTCPIP "-i"; @@ -120,10 +117,10 @@ in default = []; example = "pkgs.postgis"; # of course don't use a string here! description = '' - When this list contains elemnts a new store path is created. - Postgresql and the elments are symlinked into it. Then pg_config, + When this list contains elements a new store path is created. + PostgreSQL and the elments are symlinked into it. Then pg_config, postgres and pc_ctl are copied to make them use the new - $out/lib directory as pkglibdir. This make it possible to use postgis + $out/lib directory as pkglibdir. This makes it possible to use postgis without patching the .sql files which reference $libdir/postgis-1.5. ''; # Note: the duplication of executables is about 4MB size. @@ -160,7 +157,7 @@ in jobs.postgresql = { description = "PostgreSQL server"; - startOn = "started ${startDependency}"; + startOn = "filesystem"; environment = { TZ = config.time.timeZone; diff --git a/modules/services/hardware/udev.nix b/modules/services/hardware/udev.nix index d5955f88fbdf..f730e0c1f17c 100644 --- a/modules/services/hardware/udev.nix +++ b/modules/services/hardware/udev.nix @@ -185,8 +185,14 @@ in daemonType = "fork"; exec = "${udev}/sbin/udevd --daemon"; + }; - postStart = + jobs.udevtrigger = + { startOn = "started udev"; + + task = true; + + script = '' # Let udev create device nodes for all modules that have already # been loaded into the kernel (or for which support is built into @@ -200,9 +206,8 @@ in initctl emit -n new-devices ''; - }; - + }; } diff --git a/modules/services/network-filesystems/nfs-kernel.nix b/modules/services/network-filesystems/nfs-kernel.nix index d28aef0ed52e..e9c42237e0d4 100644 --- a/modules/services/network-filesystems/nfs-kernel.nix +++ b/modules/services/network-filesystems/nfs-kernel.nix @@ -8,6 +8,8 @@ let cfg = config.services.nfsKernel; + exports = pkgs.writeText "exports" cfg.server.exports; + in { @@ -19,7 +21,7 @@ in services.nfsKernel = { client.enable = mkOption { - default = false; + default = any (fs: fs.fsType == "nfs" || fs.fsType == "nfs4") config.fileSystems; description = '' Whether to enable the kernel's NFS client daemons. ''; @@ -82,7 +84,7 @@ in }); environment.etc = mkIf cfg.server.enable (singleton - { source = pkgs.writeText "exports" cfg.server.exports; + { source = exports; target = "exports"; }); @@ -100,19 +102,23 @@ in '' export PATH=${pkgs.nfsUtils}/sbin:$PATH mkdir -p /var/lib/nfs + ${config.system.sbin.modprobe}/sbin/modprobe nfsd || true + ${pkgs.sysvtools}/bin/mountpoint -q /proc/fs/nfsd \ + || ${config.system.sbin.mount}/bin/mount -t nfsd none /proc/fs/nfsd + ${optionalString cfg.server.createMountPoints '' # create export directories: # skip comments, take first col which may either be a quoted # "foo bar" or just foo (-> man export) - sed '/^#.*/d;s/^"\([^"]*\)".*/\1/;t;s/[ ].*//' ${cfg.server.exports} \ + sed '/^#.*/d;s/^"\([^"]*\)".*/\1/;t;s/[ ].*//' ${exports} \ | xargs -d '\n' mkdir -p '' } - # exports file is ${cfg.server.exports} + # exports file is ${exports} # keep this comment so that this job is restarted whenever exports changes! exportfs -ra ''; @@ -128,7 +134,9 @@ in startOn = "started nfs-kernel-exports and started portmap"; stopOn = "stopping nfs-kernel-exports"; - exec = "${pkgs.nfsUtils}/sbin/rpc.nfsd ${if cfg.server.hostName != null then "-H ${cfg.server.hostName}" else ""} ${builtins.toString cfg.server.nproc}"; + preStart = "${pkgs.nfsUtils}/sbin/rpc.nfsd ${if cfg.server.hostName != null then "-H ${cfg.server.hostName}" else ""} ${builtins.toString cfg.server.nproc}"; + + postStop = "${pkgs.nfsUtils}/sbin/rpc.nfsd 0"; }; } @@ -138,10 +146,12 @@ in description = "Kernel NFS server - mount daemon"; - startOn = "started nfs-kernel-nfsd and started portmap"; - stopOn = "stopping nfs-kernel-exports"; + startOn = "starting nfs-kernel-nfsd and started portmap"; + stopOn = "stopped nfs-kernel-nfsd"; - exec = "${pkgs.nfsUtils}/sbin/rpc.mountd -F -f /etc/exports"; + daemonType = "fork"; + + exec = "${pkgs.nfsUtils}/sbin/rpc.mountd -f /etc/exports"; }; } @@ -151,15 +161,34 @@ in description = "Kernel NFS server - Network Status Monitor"; - startOn = "${if cfg.server.enable then "started nfs-kernel-nfsd and " else ""} started portmap"; - stopOn = "stopping nfs-kernel-exports"; + startOn = "${if cfg.server.enable then "starting nfs-kernel-nfsd and " else ""} started portmap"; + stopOn = "never"; preStart = '' mkdir -p /var/lib/nfs + mkdir -p /var/lib/nfs/sm + mkdir -p /var/lib/nfs/sm.bak ''; - exec = "${pkgs.nfsUtils}/sbin/rpc.statd -F"; + daemonType = "fork"; + + exec = "${pkgs.nfsUtils}/sbin/rpc.statd --no-notify"; + }; + } + + // optionalAttrs (cfg.client.enable || cfg.server.enable) + { nfs_kernel_sm_notify = + { name = "nfs-kernel-sm-notify"; + + description = "Kernel NFS server - Reboot notification"; + + startOn = "started nfs-kernel-statd" + + (if cfg.client.enable then " and starting mountall" else ""); + + task = true; + + exec = "${pkgs.nfsUtils}/sbin/sm-notify -d"; }; }; diff --git a/modules/services/networking/portmap.nix b/modules/services/networking/portmap.nix index 62d1c459f105..9c0d559f867e 100644 --- a/modules/services/networking/portmap.nix +++ b/modules/services/networking/portmap.nix @@ -54,7 +54,7 @@ in users.extraUsers = singleton { name = "portmap"; inherit uid; - description = "portmap daemon user"; + description = "Portmap daemon user"; home = "/var/empty"; }; @@ -66,14 +66,16 @@ in jobs.portmap = { description = "ONC RPC portmap"; - startOn = "ip-up"; + startOn = "started network-interfaces"; + stopOn = "never"; + + daemonType = "fork"; exec = '' - ${portmap}/sbin/portmap -f \ - ${if config.services.portmap.chroot == "" - then "" - else "-t \"${config.services.portmap.chroot}\""} \ + ${portmap}/sbin/portmap \ + ${optionalString (config.services.portmap.chroot != "") + "-t '${config.services.portmap.chroot}'"} \ ${if config.services.portmap.verbose then "-v" else ""} ''; }; diff --git a/modules/services/ttys/mingetty.nix b/modules/services/ttys/mingetty.nix index 445fdcd78ed7..239231b3699f 100644 --- a/modules/services/ttys/mingetty.nix +++ b/modules/services/ttys/mingetty.nix @@ -55,7 +55,7 @@ with pkgs.lib; # Generate a separate job for each tty. jobs = listToAttrs (map (tty: nameValuePair tty { - startOn = "started udev"; + startOn = "started udev and filesystem"; exec = "${pkgs.mingetty}/sbin/mingetty --loginprog=${pkgs.shadow}/bin/login --noclear ${tty}"; diff --git a/modules/services/x11/display-managers/kdm.nix b/modules/services/x11/display-managers/kdm.nix index 15553ba386b5..a88b34dc5ace 100644 --- a/modules/services/x11/display-managers/kdm.nix +++ b/modules/services/x11/display-managers/kdm.nix @@ -94,7 +94,9 @@ in config = mkIf cfg.enable { services.xserver.displayManager.job = - { execCmd = "PATH=${pkgs.grub}/sbin:$PATH exec ${kdebase_workspace}/bin/kdm -config ${kdmrc} -nodaemon"; + { execCmd = + (optionalString (config.system.boot.loader.id == "grub") "PATH=${config.system.build.grub}/sbin:$PATH ") + + "exec ${kdebase_workspace}/bin/kdm -config ${kdmrc} -nodaemon"; logsXsession = true; }; diff --git a/modules/services/x11/xserver.nix b/modules/services/x11/xserver.nix index a5a4c908befa..97c96ef8be93 100644 --- a/modules/services/x11/xserver.nix +++ b/modules/services/x11/xserver.nix @@ -408,7 +408,7 @@ in optional (elem "virtualbox" driverNames) kernelPackages.virtualboxGuestAdditions; jobs.xserver = - { startOn = if cfg.autorun then "started udev and started hal" else ""; + { startOn = if cfg.autorun then "filesystem and stopped udevtrigger and started hal" else ""; environment = { FONTCONFIG_FILE = "/etc/fonts/fonts.conf"; # !!! cleanup diff --git a/modules/system/activation/activation-script.nix b/modules/system/activation/activation-script.nix index 5327740c6315..637f2503466d 100644 --- a/modules/system/activation/activation-script.nix +++ b/modules/system/activation/activation-script.nix @@ -75,7 +75,6 @@ let var = fullDepEntry '' # Various log/runtime directories. - mkdir -m 0755 -p /var/run touch /var/run/utmp # must exist chgrp ${toString config.ids.gids.utmp} /var/run/utmp @@ -84,16 +83,6 @@ let mkdir -m 0755 -p /var/run/nix/current-load # for distributed builds mkdir -m 0700 -p /var/run/nix/remote-stores - # Use a tmpfs for /var/run/nscd to ensure that / or /var can be - # unmounted or at least remounted read-only during shutdown. - # (Upstart 0.6 apparently uses nscd to do some name lookups, - # resulting in it holding some mmap mapping to deleted files in - # /var/run/nscd.) - if [ ! -e /var/run/nscd ]; then - mkdir -p /var/run/nscd - ${pkgs.utillinux}/bin/mount -t tmpfs -o "mode=755" none /var/run/nscd - fi - mkdir -m 0755 -p /var/log mkdir -m 0755 -p /var/log/upstart diff --git a/modules/system/boot/stage-2-init.sh b/modules/system/boot/stage-2-init.sh index 3047a210506f..aaf135fe51a2 100644 --- a/modules/system/boot/stage-2-init.sh +++ b/modules/system/boot/stage-2-init.sh @@ -109,6 +109,16 @@ rm -rf /var/log/upstart rm -rf /nix/var/nix/chroots # recreated in activate-configuration.sh +# Use a tmpfs for /var/run to ensure that / or /var can be unmounted +# or at least remounted read-only during shutdown. (Upstart 0.6 +# apparently uses nscd to do some name lookups, resulting in it +# holding some mmap mapping to deleted files in /var/run/nscd. +# Similarly, portmap and statd have open files in /var/run and are +# needed during shutdown to unmount NFS volumes.) +mkdir -m 0755 -p /var/run +mount -t tmpfs -o "mode=755" none /var/run + + # Clear the resume device. if test -n "$resumeDevice"; then mkswap "$resumeDevice" || echo 'Failed to clear saved image.' @@ -140,9 +150,11 @@ export MODULE_DIR=@kernel@/lib/modules/ # Run any user-specified commands. @shell@ @postBootCommands@ + # For debugging Upstart. #@shell@ --login < /dev/console > /dev/console 2>&1 & + # Start Upstart's init. echo "starting Upstart..." PATH=/var/run/current-system/upstart/sbin exec init diff --git a/modules/system/upstart-events/runlevel.nix b/modules/system/upstart-events/runlevel.nix index 96864594509c..10d108d9f40a 100644 --- a/modules/system/upstart-events/runlevel.nix +++ b/modules/system/upstart-events/runlevel.nix @@ -4,6 +4,15 @@ with pkgs.lib; { + # After booting, go to runlevel 2. (NixOS doesn't really use + # runlevels, but this keeps wtmp happy.) + jobs.boot = + { name = "boot"; + startOn = "startup"; + task = true; + script = "telinit 2"; + }; + jobs.runlevel = { name = "runlevel"; diff --git a/modules/system/upstart-events/shutdown.nix b/modules/system/upstart-events/shutdown.nix index 9db5fafa46ce..b7a004724be1 100644 --- a/modules/system/upstart-events/shutdown.nix +++ b/modules/system/upstart-events/shutdown.nix @@ -37,14 +37,19 @@ with pkgs.lib; sync - # Kill all remaining processes except init and this one. + # Kill all remaining processes except init, this one and any + # Upstart jobs that don't stop on the "starting shutdown" + # event, as these are necessary to complete the shutdown. + omittedPids=$(initctl list | sed -e 's/.*process \([0-9]\+\)/-o \1/;t;d') + #echo "saved PIDs: $omittedPids" + echo "sending the TERM signal to all processes..." - kill -TERM -1 + ${pkgs.sysvtools}/bin/killall5 -15 $job $omittedPids sleep 1 # wait briefly echo "sending the KILL signal to all processes..." - kill -KILL -1 + ${pkgs.sysvtools}/bin/killall5 -9 $job $omittedPids # If maintenance mode is requested, start a root shell, and @@ -58,38 +63,37 @@ with pkgs.lib; initctl emit -n startup exit 0 fi - - + + + # Write a shutdown record to wtmp while /var/log is still writable. + reboot --wtmp-only + + # Set the hardware clock to the system time. echo "setting the hardware clock..." hwclock --systohc --utc - - - # Unmount helper functions. - getMountPoints() { - cat /proc/mounts \ - | grep -v '^rootfs' \ - | sed 's|^[^ ]\+ \+\([^ ]\+\).*|\1|' \ - | grep -v '/proc\|/sys\|/dev' - } - - getDevice() { - local mountPoint=$1 - cat /proc/mounts \ - | grep -v '^rootfs' \ - | grep "^[^ ]\+ \+$mountPoint \+" \ - | sed 's|^\([^ ]\+\).*|\1|' - } - + + + # Stop all swap devices. + swapoff -a + + # Unmount file systems. We repeat this until no more file systems # can be unmounted. This is to handle loopback devices, file # systems mounted on other file systems and so on. tryAgain=1 while test -n "$tryAgain"; do tryAgain= - - for mp in $(getMountPoints); do - device=$(getDevice $mp) + failed= # list of mount points that couldn't be unmounted/remounted + + cp /proc/mounts /dev/.mounts # don't read /proc/mounts while it's changing + exec 4< /dev/.mounts + while read -u 4 device mp fstype options rest; do + # Skip various special filesystems. Non-existent + # mount points are typically tmpfs/aufs mounts from + # the initrd. + if [ "$mp" = /proc -o "$mp" = /sys -o "$mp" = /dev -o "$device" = "rootfs" -o "$mp" = /var/run -o ! -e "$mp" ]; then continue; fi + echo "unmounting $mp..." # We need to remount,ro before attempting any @@ -99,27 +103,31 @@ with pkgs.lib; # `-i' is to workaround a bug in mount.cifs (it # doesn't recognise the `remount' option, and # instead mounts the FS again). - mount -n -i -o remount,ro "$mp" + success= + if mount -t "$fstype" -n -i -o remount,ro "device" "$mp"; then success=1; fi # Note: don't use `umount -f'; it's very buggy. # (For instance, when applied to a bind-mount it # unmounts the target of the bind-mount.) !!! But # we should use `-f' for NFS. - if umount -n "$mp"; then - if test "$mp" != /; then tryAgain=1; fi - fi - - # Hack: work around a bug in mount (mount -o remount on a - # loop device forgets the loop=/dev/loopN entry in - # /etc/mtab). - if echo "$device" | grep -q '/dev/loop'; then - echo "removing loop device $device..." - losetup -d "$device" + if [ "$mp" != / -a "$mp" != /nix/store ]; then + if umount -n "$mp"; then success=1; tryAgain=1; fi fi + + if [ -z "$success" ]; then failed="$failed $mp"; fi done done - - + + + # Warn about filesystems that could not be unmounted or + # remounted read-only. + if [ -n "$failed" ]; then + echo "warning: the following filesystems could not be unmounted:" + for mp in $failed; do echo " $mp"; done + sleep 5 + fi + + # Final sync. sync diff --git a/modules/tasks/filesystems.nix b/modules/tasks/filesystems.nix index d42a4449edc7..7d53824d050d 100644 --- a/modules/tasks/filesystems.nix +++ b/modules/tasks/filesystems.nix @@ -4,103 +4,8 @@ with pkgs.lib; let - fileSystems = config.fileSystems; - mount = config.system.sbin.mount; - - task = - '' - PATH=${pkgs.e2fsprogs}/sbin:${pkgs.utillinuxng}/sbin:$PATH - - newDevices=1 - - # If we mount any file system, we repeat this loop, because new - # mount opportunities may have become available (such as images - # for loopback mounts). - - while test -n "$newDevices"; do - newDevices= - - ${flip concatMapStrings fileSystems (fs: '' - for dummy in x; do # make `continue' work - mountPoint='${fs.mountPoint}' - device='${if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}"}' - fsType='${fs.fsType}' - - # A device is a pseudo-device (i.e. not an actual device - # node) if it's not an absolute path (e.g. an NFS server - # such as machine:/path), if it starts with // (a CIFS FS), - # a known pseudo filesystem (such as tmpfs), or the device - # is a directory (e.g. a bind mount). - isPseudo= - test "''${device:0:1}" != / -o "''${device:0:2}" = // -o "$fsType" = "tmpfs" \ - -o -d "$device" && isPseudo=1 - - if ! test -n "$isPseudo" -o -e "$device"; then - echo "skipping $device, doesn't exist (yet)" - continue - fi - - # !!! quick hack: if the mount point is already mounted, try - # a remount to change the options but nothing else. - if cat /proc/mounts | grep -F -q " $mountPoint "; then - if test "''${device:0:2}" != //; then - echo "remounting $device on $mountPoint" - ${mount}/bin/mount -t "$fsType" \ - -o remount,"${fs.options}" \ - "$device" "$mountPoint" || true - fi - continue - fi - - # If $device is already mounted somewhere else, unmount it first. - # !!! Note: we use /etc/mtab, not /proc/mounts, because mtab - # contains more accurate info when using loop devices. - - if test -z "$isPseudo"; then - - device=$(readlink -f "$device") - - prevMountPoint=$( - cat /etc/mtab \ - | grep "^$device " \ - | sed 's|^[^ ]\+ \+\([^ ]\+\).*|\1|' \ - ) - - if test "$prevMountPoint" = "$mountPoint"; then - echo "remounting $device on $mountPoint" - ${mount}/bin/mount -t "$fsType" \ - -o remount,"${fs.options}" \ - "$device" "$mountPoint" || true - continue - fi - - if test -n "$prevMountPoint"; then - echo "unmount $device from $prevMountPoint" - ${mount}/bin/umount "$prevMountPoint" || true - fi - - fi - - echo "mounting $device on $mountPoint" - - # !!! should do something with the result; also prevent repeated fscks. - if test -z "$isPseudo"; then - fsck -a "$device" || true - fi - - ${optionalString fs.autocreate - '' - mkdir -p "$mountPoint" - '' - } - - if ${mount}/bin/mount -t "$fsType" -o "${fs.options}" "$device" "$mountPoint"; then - newDevices=1 - fi - done - '')} - done - ''; + # Packages that provide fsck backends. + fsPackages = [ pkgs.e2fsprogs pkgs.reiserfsprogs ]; in @@ -221,14 +126,99 @@ in config = { # Add the mount helpers to the system path so that `mount' can find them. - environment.systemPackages = [pkgs.ntfs3g pkgs.cifs_utils pkgs.nfsUtils]; + environment.systemPackages = + [ pkgs.ntfs3g pkgs.cifs_utils pkgs.nfsUtils pkgs.mountall ] + ++ fsPackages; - jobs.filesystems = - { startOn = [ "new-devices" "ip-up" ]; + environment.etc = singleton + { source = pkgs.writeText "fstab" + '' + # This is a generated file. Do not edit! - script = task; + # Filesystems. + ${flip concatMapStrings config.fileSystems (fs: + (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") + + " " + fs.mountPoint + + " " + fs.fsType + + " " + fs.options + + " 0" + + " " + (if fs.fsType == "none" then "0" else if fs.mountPoint == "/" then "1" else "2") + + "\n" + )} + + # Swap devices. + ${flip concatMapStrings config.swapDevices (sw: + "${sw.device} none swap\n" + )} + ''; + target = "fstab"; + }; + + jobs.mountall = + { startOn = "started udev"; task = true; + + script = + '' + exec > /dev/console 2>&1 + echo "mounting filesystems..." + export PATH=${config.system.sbin.mount}/bin:${makeSearchPath "sbin" ([pkgs.utillinux] ++ fsPackages)}:$PATH + ${pkgs.mountall}/sbin/mountall + ''; + }; + + # The `mount-failed' event is emitted synchronously, but we don't + # want `mountall' to wait for the emergency shell. So use this + # intermediate job to make the event asynchronous. + jobs.mountFailed = + { name = "mount-failed"; + task = true; + startOn = "mount-failed"; + script = + '' + [ -n "$MOUNTPOINT" ] || exit 0 + start --no-wait emergency-shell \ + DEVICE="$DEVICE" MOUNTPOINT="$MOUNTPOINT" + ''; + }; + + jobs.emergencyShell = + { name = "emergency-shell"; + + task = true; + + extraConfig = "console owner"; + + script = + '' + [ -n "$MOUNTPOINT" ] || exit 0 + + exec < /dev/console > /dev/console 2>&1 + + cat <>> + + The filesystem \`$DEVICE' could not be mounted on \`$MOUNTPOINT'. + + Please do one of the following: + + - Repair the filesystem (\`fsck $DEVICE') and exit the emergency + shell to resume booting. + + - Ignore any failed filesystems and continue booting by running + \`initctl emit filesystem'. + + - Remove the failed filesystem from the system configuration in + /etc/nixos/configuration.nix and run \`nixos-rebuild switch'. + + EOF + + ${pkgs.shadow}/bin/login root || false + + initctl start --no-wait mountall + ''; }; }; diff --git a/modules/tasks/swap.nix b/modules/tasks/swap.nix deleted file mode 100644 index ab81185532ea..000000000000 --- a/modules/tasks/swap.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ config, pkgs, ... }: - -with pkgs.lib; - -let - - inherit (pkgs) cryptsetup utillinux; - -in - -{ - - ###### interface - - options = { - - swapDevices = mkOption { - default = []; - example = [ - { device = "/dev/hda7"; } - { device = "/var/swapfile"; } - { label = "bigswap"; } - ]; - description = '' - The swap devices and swap files. These must have been - initialised using mkswap. Each element - should be an attribute set specifying either the path of the - swap device or file (device) or the label - of the swap device (label, see - mkswap -L). Using a label is - recommended. - ''; - - type = types.list types.optionSet; - - options = {config, options, ...}: { - - options = { - device = mkOption { - example = "/dev/sda3"; - type = types.string; - description = '' - Path of the device. - ''; - }; - - label = mkOption { - example = "swap"; - type = types.string; - description = " - Label of the device. Can be used instead of device. - "; - }; - - cipher = mkOption { - default = false; - example = true; - type = types.bool; - description = " - Cipher the swap device to protect swapped data. This option - does not work with labels. - "; - }; - - command = mkOption { - description = " - Command used to activate the swap device. - "; - }; - }; - - config = { - device = - if options.label.isDefined then - "/dev/disk/by-label/${config.label}" - else - mkNotdef; - - command = '' - if test -e "${config.device}"; then - ${if config.cipher then '' - plainDevice="${config.device}" - name="crypt$(echo "$plainDevice" | sed -e 's,/,.,g')" - device="/dev/mapper/$name" - if ! test -e "$device"; then - ${cryptsetup}/sbin/cryptsetup -c aes -s 128 -d /dev/urandom create "$name" "$plainDevice" - ${utillinux}/sbin/mkswap -f "$device" || true - fi - '' - else '' - device="${config.device}" - '' - } - device=$(readlink -f "$device") - # Add new swap devices. - if echo $unused | grep -q "^$device\$"; then - unused="$(echo $unused | grep -v "^$device\$" || true)" - else - ${utillinux}/sbin/swapon "$device" || true - fi - fi - ''; - }; - - }; - - }; - - }; - - ###### implementation - - config = { - - jobs.swap = - { task = true; - - startOn = ["startup" "new-devices"]; - - script = - '' - unused="$(sed '1d; s/ .*//' /proc/swaps)" - - ${toString (map (x: x.command) config.swapDevices)} - - # Remove remaining swap devices. - test -n "$unused" && ${utillinux}/sbin/swapoff $unused || true - ''; - }; - - }; - -} diff --git a/modules/testing/test-instrumentation.nix b/modules/testing/test-instrumentation.nix index 5e62c698f274..b70c6e03f27f 100644 --- a/modules/testing/test-instrumentation.nix +++ b/modules/testing/test-instrumentation.nix @@ -13,6 +13,7 @@ let '' #! ${pkgs.perl}/bin/perl $SIG{CHLD} = 'DEFAULT'; + print "\n"; exec "/bin/sh"; ''; @@ -23,14 +24,9 @@ in config = { jobs.backdoor = - { startOn = "started network-interfaces"; + { startOn = "ip-up"; + stopOn = "never"; - preStart = - '' - echo "guest running" > /dev/ttyS0 - echo "===UP===" > dev/ttyS0 - ''; - script = '' export USER=root @@ -39,10 +35,13 @@ in export GCOV_PREFIX=/tmp/coverage-data source /etc/profile cd /tmp - exec ${pkgs.socat}/bin/socat tcp-listen:514,fork exec:${rootShell} 2> /dev/ttyS0 + echo "connecting to host..." > /dev/ttyS0 + ${pkgs.socat}/bin/socat tcp:10.0.2.6:23 exec:${rootShell} 2> /dev/ttyS0 || poweroff -f ''; + + respawn = false; }; - + boot.postBootCommands = '' # Panic on out-of-memory conditions rather than letting the @@ -75,10 +74,19 @@ in # `xwininfo' is used by the test driver to query open windows. environment.systemPackages = [ pkgs.xorg.xwininfo ]; - # Send all of /var/log/messages to the serial port (except for - # kernel messages through klogd, which already appear on the - # serial port). - services.syslogd.extraConfig = "*.*,kern.none /dev/ttyS0"; + # Send all of /var/log/messages to the serial port. + services.syslogd.extraConfig = "*.* /dev/ttyS0"; + + # Prevent tests from accessing the Internet. + networking.defaultGateway = mkOverride 200 {} ""; + networking.nameservers = mkOverride 200 {} [ ]; + + # Apply a patch to the kernel to increase the 15s CIFS timeout. + nixpkgs.config.packageOverrides = pkgs: { + linux = pkgs.linux.override (orig: { + kernelPatches = orig.kernelPatches ++ [ pkgs.kernelPatches.cifs_timeout ]; + }); + }; }; diff --git a/modules/virtualisation/qemu-vm.nix b/modules/virtualisation/qemu-vm.nix index 6e4e372c1bfa..e054b537d438 100644 --- a/modules/virtualisation/qemu-vm.nix +++ b/modules/virtualisation/qemu-vm.nix @@ -113,20 +113,29 @@ let '' #! ${pkgs.stdenv.shell} - export PATH=${pkgs.samba}/sbin:$PATH - - NIX_DISK_IMAGE=''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}} + NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}) if ! test -e "$NIX_DISK_IMAGE"; then - ${pkgs.qemu_kvm}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" ${toString config.virtualisation.diskSize}M || exit 1 + ${pkgs.qemu_kvm}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \ + ${toString config.virtualisation.diskSize}M || exit 1 fi - - # -no-kvm-irqchip is needed to prevent the CIFS mount from - # hanging the VM on x86_64. - exec ${pkgs.qemu_kvm}/bin/qemu-system-x86_64 -m ${toString config.virtualisation.memorySize} \ - -no-kvm-irqchip \ - -net nic,vlan=0,model=virtio -net user,vlan=0 -smb / \ - -drive file=$NIX_DISK_IMAGE,if=virtio,boot=on,werror=report \ + + # Start Samba (which wants to put its socket and config files in TMPDIR). + if [ -z "$TMPDIR" -o -z "$USE_TMPDIR" ]; then + TMPDIR=$(mktemp -d nix-vm-smbd.XXXXXXXXXX --tmpdir) + fi + cd $TMPDIR + + ${pkgs.vmTools.startSamba} + + # Start QEMU. + exec ${pkgs.qemu_kvm}/bin/qemu-system-x86_64 \ + -name ${vmName} \ + -m ${toString config.virtualisation.memorySize} \ + -net nic,vlan=0,model=virtio \ + -chardev socket,id=samba,path=./samba \ + -net user,vlan=0,guestfwd=tcp:10.0.2.4:139-chardev:samba''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} \ + -drive file=$NIX_DISK_IMAGE,if=virtio,boot=on,cache=writeback,werror=report \ -kernel ${config.system.build.toplevel}/kernel \ -initrd ${config.system.build.toplevel}/initrd \ ${qemuGraphics} \ @@ -167,6 +176,9 @@ in boot.initrd.postDeviceCommands = '' + # Workaround for massive clock drift with the "kvm-clock" clock source. + echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource + # Set up networking. Needed for CIFS mounting. ifconfig eth0 up 10.0.2.15 @@ -212,6 +224,7 @@ in } { mountPoint = "/nix/store"; device = "/hostfs/nix/store"; + fsType = "none"; options = "bind"; neededForBoot = true; } diff --git a/release.nix b/release.nix index 10f9362ec87e..204a636568d3 100644 --- a/release.nix +++ b/release.nix @@ -111,10 +111,10 @@ let options = (import lib/eval-config.nix { inherit nixpkgs; - modules = [ ]; + modules = [ { fileSystems = []; } ]; }).options; - revision = with nixosSrc; - if rev == 1234 then "HEAD" else toString rev; + revision = + if nixosSrc.rev == 1234 then "HEAD" else toString nixosSrc.rev; }; @@ -156,6 +156,8 @@ let installer.swraid = t.installer.swraid.test; kde4 = t.kde4.test; login = t.login.test; + nat = t.nat.test; + nfs = t.nfs.test; openssh = t.openssh.test; proxy = t.proxy.test; quake3 = t.quake3.test; diff --git a/tests/default.nix b/tests/default.nix index dfccce3851c3..015eb071a152 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -12,6 +12,7 @@ with import ../lib/testing.nix { inherit nixpkgs services system; }; kde4 = makeTest (import ./kde4.nix); login = makeTest (import ./login.nix); nat = makeTest (import ./nat.nix); + nfs = makeTest (import ./nfs.nix); openssh = makeTest (import ./openssh.nix); portmap = makeTest (import ./portmap.nix); proxy = makeTest (import ./proxy.nix); diff --git a/tests/installer.nix b/tests/installer.nix index a15b5587511b..bfecca48cc09 100644 --- a/tests/installer.nix +++ b/tests/installer.nix @@ -40,6 +40,7 @@ let boot.loader.grub.version = 2; boot.loader.grub.device = "/dev/vda"; + boot.loader.grub.extraConfig = "serial; terminal_output.serial"; boot.initrd.kernelModules = [ "ext3" ]; fileSystems = [ ${fileSystems} ]; @@ -218,9 +219,7 @@ in { '' $machine->mustSucceed( "parted /dev/vda mklabel msdos", - "udevadm settle", "parted /dev/vda -- mkpart primary linux-swap 1M 1024M", - "udevadm settle", "parted /dev/vda -- mkpart primary ext2 1024M -1s", "udevadm settle", "mkswap /dev/vda1 -L swap", @@ -240,9 +239,7 @@ in { $machine->mustSucceed( "parted /dev/vda mklabel msdos", "parted /dev/vda -- mkpart primary ext2 1M 50MB", # /boot - "udevadm settle", "parted /dev/vda -- mkpart primary linux-swap 50MB 1024M", - "udevadm settle", "parted /dev/vda -- mkpart primary ext2 1024M -1s", # / "udevadm settle", "mkswap /dev/vda2 -L swap", @@ -265,11 +262,8 @@ in { $machine->mustSucceed( "parted /dev/vda mklabel msdos", "parted /dev/vda -- mkpart primary 1M 2048M", # first PV - "udevadm settle", "parted /dev/vda -- set 1 lvm on", - "udevadm settle", "parted /dev/vda -- mkpart primary 2048M -1s", # second PV - "udevadm settle", "parted /dev/vda -- set 2 lvm on", "udevadm settle", "pvcreate /dev/vda1 /dev/vda2", @@ -290,21 +284,25 @@ in { '' $machine->mustSucceed( "parted /dev/vda mklabel msdos", - "parted /dev/vda -- mkpart primary 1M 1000M", # md0 (root), first device - "parted /dev/vda -- mkpart primary 1024M 2024M", # md0 (root), second device - "parted /dev/vda -- mkpart primary 2048M 2548M", # md1 (swap), first device - "parted /dev/vda -- mkpart primary 2560M 3060M", # md1 (swap), second device + "parted /dev/vda -- mkpart primary ext2 1M 30MB", # /boot + "parted /dev/vda -- mkpart extended 30M -1s", # extended partition + "parted /dev/vda -- mkpart logical 30M 1000M", # md0 (root), first device + "parted /dev/vda -- mkpart logical 1024M 2000M", # md0 (root), second device + "parted /dev/vda -- mkpart logical 2048M 2548M", # md1 (swap), first device + "parted /dev/vda -- mkpart logical 2560M 3060M", # md1 (swap), second device "udevadm settle", - # Note that GRUB2 doesn't work with version 1.2 metadata. - "mdadm --create --force /dev/md0 --metadata 0.90 --level=raid1 --raid-devices=2 /dev/vda1 /dev/vda2", - "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda3 /dev/vda4", + "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6", + "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8", "mkswap -f /dev/md1 -L swap", "swapon -L swap", "mkfs.ext3 -L nixos /dev/md0", "mount LABEL=nixos /mnt", + "mkfs.ext3 -L boot /dev/vda1", + "mkdir /mnt/boot", + "mount LABEL=boot /mnt/boot", ); ''; - fileSystems = rootFS; + fileSystems = rootFS + bootFS; }; } diff --git a/tests/nfs.nix b/tests/nfs.nix new file mode 100644 index 000000000000..f8d1cf951f31 --- /dev/null +++ b/tests/nfs.nix @@ -0,0 +1,84 @@ +{ pkgs, ... }: + +let + + client = + { config, pkgs, ... }: + { fileSystems = pkgs.lib.mkOverride 50 {} + [ { mountPoint = "/data"; + device = "server:/data"; + fsType = "nfs"; + options = "bootwait"; + } + ]; + }; + +in + +{ + + nodes = + { client1 = client; + client2 = client; + + server = + { config, pkgs, ... }: + { services.nfsKernel.server.enable = true; + services.nfsKernel.server.exports = + '' + /data 192.168.1.0/255.255.255.0(rw,no_root_squash) + ''; + services.nfsKernel.server.createMountPoints = true; + }; + }; + + testScript = + '' + startAll; + + $server->waitForJob("nfs-kernel-nfsd"); + $server->waitForJob("nfs-kernel-mountd"); + $server->waitForJob("nfs-kernel-statd"); + + $client1->waitForJob("tty1"); # depends on filesystems + $client1->succeed("echo bla > /data/foo"); + $server->succeed("test -e /data/foo"); + + $client2->waitForJob("tty1"); # depends on filesystems + $client2->succeed("echo bla > /data/bar"); + $server->succeed("test -e /data/bar"); + + # Test whether we can get a lock. !!! This step takes about 90 + # seconds because the NFS server waits that long after booting + # before accepting new locks. + $client2->succeed("time flock -n -s /data/lock true"); + + # Test locking: client 1 acquires an exclusive lock, so client 2 + # should then fail to acquire a shared lock. + $client1->succeed("flock -x /data/lock -c 'touch locked; sleep 100000' &"); + $client1->waitForFile("locked"); + $client2->fail("flock -n -s /data/lock true"); + + # Test whether client 2 obtains the lock if we reset client 1. + $client2->succeed("flock -x /data/lock -c 'echo acquired; touch locked; sleep 100000' >&2 &"); + $client1->crash; + $client1->start; + $client2->waitForFile("locked"); + + # Test whether locks survive a reboot of the server. + $client1->waitForJob("tty1"); # depends on filesystems + $server->shutdown; + $server->start; + $client1->succeed("touch /data/xyzzy"); + $client1->fail("time flock -n -s /data/lock true"); + + # Test whether unmounting during shutdown happens quickly. This + # requires portmap and statd to keep running during the + # shutdown. + my $t1 = time; + $client1->shutdown; + my $duration = time - $t1; + die "shutdown took too long ($duration seconds)" if $duration > 30; + ''; + +}