sysfs contains enough information about block devices to be able to determine the order of stacked devices such as lvm, raid, or crypto. By looking at the device symlinks from the holders/ attributes of a block device, we can walk down each device chain until we reach the most descendant device. For each of these devices at the end of a chain, detect its type and perform the appropriate action to disassemble it. Then, walk back up the device chain, disassembling each parent device. To save ourselves some pain and make sure we're fairly accurate, lsblk is brought in for detection of device types. Signed-off-by: Dave Reisner <dreisner@archlinux.org> --- This was fun... so far, I've tested this with the following root configurations: * lvm+crypt+lvm (don't ask) * raid0 * lvm * crypt (this setup has encrypted home as well) * lvm+raid And it works well for all 5. The idea is that it supports any given number of stacked devices so we don't need to worry about specifically naming what we're willing to support. dmraid isn't tested because i lack the hardware (and im not sure its possible to test in qemu), but it should "just work". The only unresolved issue thus far is with the mdadm hook (rather than mdadm_udev). Since we don't include /sbin/mdadm, there's no way to disassemble devices via mdassemble. I'm inclined to believe that the lifespan of the mdadm hook is limited (as upstream really encourages you to use udev assembly these days), so I think it's okay to just call this setup unsupported. install/shutdown | 2 +- shutdown | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/install/shutdown b/install/shutdown index 5b56f17..600209e 100644 --- a/install/shutdown +++ b/install/shutdown @@ -1,7 +1,7 @@ #!/bin/bash build() { - BINARIES='cp' + BINARIES='cp lsblk' SCRIPT='shutdown' add_file "/usr/lib/initcpio/shutdown" "/shutdown" diff --git a/shutdown b/shutdown index aad0198..6c34ff2 100755 --- a/shutdown +++ b/shutdown @@ -1,5 +1,80 @@ #!/usr/bin/ash +# disassemble a device, $1. +# returns 0 on disassembling a device +# returns 1 when the nothing is done (caller should stop) +disassemble_dev() { + local devtype= + + [ -n "$1" ] || return 0 + + devtype=$(lsblk -drno TYPE "$1") + case $devtype in + disk|part) + return 1 + ;; + crypt) + read devname <"${1##*/}/dm/name" + cryptsetup luksClose "$devname" + return 0 + ;; + dm|lvm) + # rip apart a specific VG. it needs to be translated from the LV + # that's been passed to this function. + read devname <"${1##*/}/dm/name" + devname=$(lvm lvs --noheadings -o vg_name "/dev/mapper/$devname") + + # $devname can't be quoted because lvs will put leading and + # trailing whitespace in the vgname output. "fortunately", + # whitespace is invalid in a vgname, so this is "safe". + lvm vgchange -an $devname + return 0 + ;; + raid*) + mdadm --stop "$1" + return 0 + ;; + dmraid) + # XXX: i have no idea how dmraid works. + dmraid -an + return 0 + ;; + *) + # unexpected device + return 1 + ;; + esac +} + +# get the parent of device $1 from /sys +# return 0 on success (and write device to stdout) +# return 1 on failure +get_parent_dev() { + local parent=$(printf '%s\n' */holders/${1##*/}) + + [ -e "$parent" ] && printf '/dev/%s' "${parent%%/*}" +} + +# get the most descendant device in a device chain +get_child_dev() { + local child= parent=$1 + + while :; do + for child in "$parent"/holders/*; do + if [ -e "$child" ]; then + # found a descendant, check for more children below it + parent=${child##*/} + break + else + # end of a chain + printf '/dev/%s' "$parent" + return + fi + done + done +} + +# unmount everything findmnt -Rruno TARGET /oldroot | awk ' BEGIN { i = 0 } ! /^\/(proc|dev|sys)/ { @@ -15,6 +90,25 @@ END { umount -l "$mount" done +# chdir, so that we can avoid a lot of path chopping +cd /sys/class/block + +# find stacked devices and disassemble them +for holder in */holders/*; do + device=${holder%%/*} + + # walk down the device chain to find the most descendant child + nextdev=$(get_child_dev "$device") + + # check to see that the device still exists in /sys. it may have been taken apart already + [ -e "${nextdev##*/}" ] || continue + + # now walk back up the chain, disassembling each device + while disassemble_dev "$nextdev"; do + nextdev=$(get_parent_dev "$nextdev") || break + done +done + case $1 in poweroff|shutdown|halt) "$1" -f -- 1.7.9.4