summary refs log tree commit diff
path: root/modules/system/upstart-events/shutdown.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/system/upstart-events/shutdown.nix')
-rw-r--r--modules/system/upstart-events/shutdown.nix162
1 files changed, 162 insertions, 0 deletions
diff --git a/modules/system/upstart-events/shutdown.nix b/modules/system/upstart-events/shutdown.nix
new file mode 100644
index 000000000000..59fbcc0d878b
--- /dev/null
+++ b/modules/system/upstart-events/shutdown.nix
@@ -0,0 +1,162 @@
+{ config, pkgs, ... }:
+
+with pkgs.lib;
+
+{
+
+  jobs.shutdown =
+    { name = "shutdown";
+
+      task = true;
+
+      stopOn = ""; # must override the default ("starting shutdown")
+
+      environment = { MODE = "poweroff"; };
+
+      extraConfig = "console owner";
+
+      script =
+        ''
+          set +e # continue in case of errors
+
+          ${pkgs.kbd}/bin/chvt 1
+
+          exec < /dev/console > /dev/console 2>&1
+          echo ""
+          if test "$MODE" = maintenance; then
+              echo "<<< Entering maintenance mode >>>"
+          else
+              echo "<<< System shutdown >>>"
+          fi
+          echo ""
+
+          ${config.powerManagement.powerDownCommands}
+
+          export PATH=${pkgs.utillinux}/bin:${pkgs.utillinux}/sbin:$PATH
+
+
+          # Do an initial sync just in case.
+          sync
+
+
+          # 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..."
+          ${pkgs.sysvtools}/bin/killall5 -15 $job $omittedPids
+
+          sleep 1 # wait briefly
+
+          echo "sending the KILL signal to all processes..."
+          ${pkgs.sysvtools}/bin/killall5 -9 $job $omittedPids
+
+
+          # If maintenance mode is requested, start a root shell, and
+          # afterwards emit the "startup" event to bring everything
+          # back up.
+          if test "$MODE" = maintenance; then
+              echo ""
+              echo "<<< Maintenance shell >>>"
+              echo ""
+              ${pkgs.shadow}/bin/login root
+              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 ${if config.time.hardwareClockInLocalTime then "--localtime" else "--utc"}
+
+
+          # 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=
+              failed= # list of mount points that couldn't be unmounted/remounted
+
+              # Get rid of loopback devices.
+              loDevices=$(losetup -a | sed 's#^\(/dev/loop[0-9]\+\).*#\1#')
+              if [ -n "$loDevices" ]; then
+                  echo "removing loopback devices $loDevices..."
+                  losetup -d $loDevices
+              fi
+
+              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" = /run -o "$mp" = /var/run -o "$mp" = /var/lock -o ! -e "$mp" ]; then continue; fi
+
+                  echo "unmounting $mp..."
+
+                  # We need to remount,ro before attempting any
+                  # umount, or bind mounts may get confused, with
+                  # the fs not being properly flushed at the end.
+
+                  # `-i' is to workaround a bug in mount.cifs (it
+                  # doesn't recognise the `remount' option, and
+                  # instead mounts the FS again).
+                  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 [ "$mp" != / -a "$mp" != /nix -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
+              echo Enter 'i' to launch a shell, or wait 10 seconds to continue.
+              read -t 10 A
+              if [ "$A" == "i" ]; then
+                ${pkgs.bashInteractive}/bin/bash -i < /dev/console &> /dev/console
+              fi
+              sleep 5
+          fi
+
+
+          # Final sync.
+          sync
+
+
+          # Either reboot or power-off the system.
+          if test "$MODE" = reboot; then
+              echo "rebooting..."
+              sleep 1
+              exec reboot -f
+          else
+              echo "powering off..."
+              sleep 1
+              exec halt -f -p
+          fi
+        '';
+    };
+
+}