summary refs log tree commit diff
path: root/nixos/modules/system/boot/stage-1-init.sh
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/system/boot/stage-1-init.sh')
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh374
1 files changed, 374 insertions, 0 deletions
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
new file mode 100644
index 00000000000..e3e07c08580
--- /dev/null
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -0,0 +1,374 @@
+#! @shell@
+
+targetRoot=/mnt-root
+console=tty1
+
+export LD_LIBRARY_PATH=@extraUtils@/lib
+export PATH=@extraUtils@/bin:@extraUtils@/sbin
+
+
+fail() {
+    if [ -n "$panicOnFail" ]; then exit 1; fi
+
+    # If starting stage 2 failed, allow the user to repair the problem
+    # in an interactive shell.
+    cat <<EOF
+
+An error occured in stage 1 of the boot process, which must mount the
+root filesystem on \`$targetRoot' and then start stage 2.  Press one
+of the following keys:
+
+EOF
+    if [ -n "$allowShell" ]; then cat <<EOF
+  i) to launch an interactive shell
+  f) to start an interactive shell having pid 1 (needed if you want to
+     start stage 2's init manually)
+EOF
+    fi
+    cat <<EOF
+  r) to reboot immediately
+  *) to ignore the error and continue
+EOF
+
+    read reply
+
+    if [ -n "$allowShell" -a "$reply" = f ]; then
+        exec setsid @shell@ -c "@shell@ < /dev/$console >/dev/$console 2>/dev/$console"
+    elif [ -n "$allowShell" -a "$reply" = i ]; then
+        echo "Starting interactive shell..."
+        setsid @shell@ -c "@shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
+    elif [ "$reply" = r ]; then
+        echo "Rebooting..."
+        reboot -f
+    else
+        echo "Continuing..."
+    fi
+}
+
+trap 'fail' 0
+
+
+# Print a greeting.
+echo
+echo "<<< NixOS Stage 1 >>>"
+echo
+
+
+# Mount special file systems.
+mkdir -p /etc
+touch /etc/fstab # to shut up mount
+touch /etc/mtab # to shut up mke2fs
+mkdir -p /proc
+mount -t proc none /proc
+mkdir -p /sys
+mount -t sysfs none /sys
+mount -t devtmpfs -o "size=@devSize@" none /dev
+mkdir -p /run
+mount -t tmpfs -o "mode=0755,size=@runSize@" none /run
+
+
+# Process the kernel command line.
+export stage2Init=/init
+for o in $(cat /proc/cmdline); do
+    case $o in
+        console=*)
+            set -- $(IFS==; echo $o)
+            params=$2
+            set -- $(IFS=,; echo $params)
+            console=$1
+            ;;
+        init=*)
+            set -- $(IFS==; echo $o)
+            stage2Init=$2
+            ;;
+        boot.trace|debugtrace)
+            # Show each command.
+            set -x
+            ;;
+        boot.shell_on_fail)
+            allowShell=1
+            ;;
+        boot.debug1|debug1) # stop right away
+            allowShell=1
+            fail
+            ;;
+        boot.debug1devices) # stop after loading modules and creating device nodes
+            allowShell=1
+            debug1devices=1
+            ;;
+        boot.debug1mounts) # stop after mounting file systems
+            allowShell=1
+            debug1mounts=1
+            ;;
+        boot.panic_on_fail|stage1panic=1)
+            panicOnFail=1
+            ;;
+        root=*)
+            # If a root device is specified on the kernel command
+            # line, make it available through the symlink /dev/root.
+            # Recognise LABEL= and UUID= to support UNetbootin.
+            set -- $(IFS==; echo $o)
+            if [ $2 = "LABEL" ]; then
+                root="/dev/disk/by-label/$3"
+            elif [ $2 = "UUID" ]; then
+                root="/dev/disk/by-uuid/$3"
+            else
+                root=$2
+            fi
+            ln -s "$root" /dev/root
+            ;;
+    esac
+done
+
+
+# Load the required kernel modules.
+mkdir -p /lib
+ln -s @modulesClosure@/lib/modules /lib/modules
+echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
+for i in @kernelModules@; do
+    echo "loading module $(basename $i)..."
+    modprobe $i || true
+done
+
+
+# Create device nodes in /dev.
+echo "running udev..."
+mkdir -p /etc/udev
+ln -sfn @udevRules@ /etc/udev/rules.d
+mkdir -p /dev/.mdadm
+systemd-udevd --daemon
+udevadm trigger --action=add
+udevadm settle || true
+modprobe scsi_wait_scan || true
+udevadm settle || true
+
+
+# Load boot-time keymap before any LVM/LUKS initialization
+@extraUtils@/bin/busybox loadkmap < "@busyboxKeymap@"
+
+
+# XXX: Use case usb->lvm will still fail, usb->luks->lvm is covered
+@preLVMCommands@
+
+
+echo "starting device mapper and LVM..."
+lvm vgchange -ay
+
+if test -n "$debug1devices"; then fail; fi
+
+
+@postDeviceCommands@
+
+
+# Try to resume - all modules are loaded now, and devices exist
+if test -e /sys/power/tuxonice/resume; then
+    if test -n "$(cat /sys/power/tuxonice/resume)"; then
+        echo 0 > /sys/power/tuxonice/user_interface/enabled
+        echo 1 > /sys/power/tuxonice/do_resume || echo "failed to resume..."
+    fi
+fi
+
+if test -e /sys/power/resume -a -e /sys/power/disk; then
+    echo "@resumeDevice@" > /sys/power/resume 2> /dev/null || echo "failed to resume..."
+    echo shutdown > /sys/power/disk
+fi
+
+
+# Return true if the machine is on AC power, or if we can't determine
+# whether it's on AC power.
+onACPower() {
+    ! test -d "/proc/acpi/battery" ||
+    ! ls /proc/acpi/battery/BAT[0-9]* > /dev/null 2>&1 ||
+    ! cat /proc/acpi/battery/BAT*/state | grep "^charging state" | grep -q "discharg"
+}
+
+
+# Check the specified file system, if appropriate.
+checkFS() {
+    local device="$1"
+    local fsType="$2"
+
+    # Only check block devices.
+    if [ ! -b "$device" ]; then return 0; fi
+
+    # Don't check ROM filesystems.
+    if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
+
+    # If we couldn't figure out the FS type, then skip fsck.
+    if [ "$fsType" = auto ]; then
+        echo 'cannot check filesystem with type "auto"!'
+        return 0
+    fi
+
+    # Optionally, skip fsck on journaling filesystems.  This option is
+    # a hack - it's mostly because e2fsck on ext3 takes much longer to
+    # recover the journal than the ext3 implementation in the kernel
+    # does (minutes versus seconds).
+    if test -z "@checkJournalingFS@" -a \
+        \( "$fsType" = ext3 -o "$fsType" = ext4 -o "$fsType" = reiserfs \
+        -o "$fsType" = xfs -o "$fsType" = jfs \)
+    then
+        return 0
+    fi
+
+    # Don't run `fsck' if the machine is on battery power.  !!! Is
+    # this a good idea?
+    if ! onACPower; then
+        echo "on battery power, so no \`fsck' will be performed on \`$device'"
+        return 0
+    fi
+
+    echo "checking $device..."
+
+    fsckFlags=
+    if test "$fsType" != "btrfs"; then
+        fsckFlags="-V -a"
+    fi
+    fsck $fsckFlags "$device"
+    fsckResult=$?
+
+    if test $(($fsckResult | 2)) = $fsckResult; then
+        echo "fsck finished, rebooting..."
+        sleep 3
+        reboot -f
+    fi
+
+    if test $(($fsckResult | 4)) = $fsckResult; then
+        echo "$device has unrepaired errors, please fix them manually."
+        fail
+    fi
+
+    if test $fsckResult -ge 8; then
+        echo "fsck on $device failed."
+        fail
+    fi
+
+    return 0
+}
+
+
+# Function for mounting a file system.
+mountFS() {
+    local device="$1"
+    local mountPoint="$2"
+    local options="$3"
+    local fsType="$4"
+
+    if [ "$fsType" = auto ]; then
+        fsType=$(blkid -o value -s TYPE "$device")
+        if [ -z "$fsType" ]; then fsType=auto; fi
+    fi
+
+    echo "$device /mnt-root$mountPoint $fsType $options" >> /etc/fstab
+
+    checkFS "$device" "$fsType"
+
+    echo "mounting $device on $mountPoint..."
+
+    mkdir -p "/mnt-root$mountPoint" || true
+
+    # For CIFS mounts, retry a few times before giving up.
+    local n=0
+    while true; do
+        mount "/mnt-root$mountPoint" && break
+        if [ "$fsType" != cifs -o "$n" -ge 10 ]; then fail; break; fi
+        echo "retrying..."
+        n=$((n + 1))
+    done
+}
+
+
+# Try to find and mount the root device.
+mkdir /mnt-root
+
+exec 3< @fsInfo@
+
+while read -u 3 mountPoint; do
+    read -u 3 device
+    read -u 3 fsType
+    read -u 3 options
+
+    # !!! Really quick hack to support bind mounts, i.e., where the
+    # "device" should be taken relative to /mnt-root, not /.  Assume
+    # that every device that starts with / but doesn't start with /dev
+    # is a bind mount.
+    pseudoDevice=
+    case $device in
+        /dev/*)
+            ;;
+        //*)
+            # Don't touch SMB/CIFS paths.
+            pseudoDevice=1
+            ;;
+        /*)
+            device=/mnt-root$device
+            ;;
+        *)
+            # Not an absolute path; assume that it's a pseudo-device
+            # like an NFS path (e.g. "server:/path").
+            pseudoDevice=1
+            ;;
+    esac
+
+    # USB storage devices tend to appear with some delay.  It would be
+    # great if we had a way to synchronously wait for them, but
+    # alas...  So just wait for a few seconds for the device to
+    # appear.  If it doesn't appear, try to mount it anyway (and
+    # probably fail).  This is a fallback for non-device "devices"
+    # that we don't properly recognise.
+    if test -z "$pseudoDevice" -a ! -e $device; then
+        echo -n "waiting for device $device to appear..."
+        for try in $(seq 1 20); do
+            sleep 1
+            if test -e $device; then break; fi
+            echo -n "."
+        done
+        echo
+    fi
+
+    # Wait once more for the udev queue to empty, just in case it's
+    # doing something with $device right now.
+    udevadm settle || true
+
+    mountFS "$device" "$mountPoint" "$options" "$fsType"
+done
+
+exec 3>&-
+
+
+@postMountCommands@
+
+
+# Stop udevd.
+udevadm control --exit || true
+
+# Kill any remaining processes, just to be sure we're not taking any
+# with us into stage 2. unionfs-fuse mounts require the unionfs process.
+pkill -9 -v '(1|unionfs)'
+
+
+if test -n "$debug1mounts"; then fail; fi
+
+
+# Restore /proc/sys/kernel/modprobe to its original value.
+echo /sbin/modprobe > /proc/sys/kernel/modprobe
+
+
+# Start stage 2.  `switch_root' deletes all files in the ramfs on the
+# current root.  Note that $stage2Init might be an absolute symlink,
+# in which case "-e" won't work because we're not in the chroot yet.
+if ! test -e "$targetRoot/$stage2Init" -o -L "$targetRoot/$stage2Init"; then
+    echo "stage 2 init script ($targetRoot/$stage2Init) not found"
+    fail
+fi
+
+mkdir -m 0755 -p $targetRoot/proc $targetRoot/sys $targetRoot/dev $targetRoot/run
+
+mount --move /proc $targetRoot/proc
+mount --move /sys $targetRoot/sys
+mount --move /dev $targetRoot/dev
+mount --move /run $targetRoot/run
+
+exec env -i $(type -P switch_root) "$targetRoot" "$stage2Init"
+
+fail # should never be reached