LoSetup() { local losetup_binary="$(which LoSetup)" if [ "$losetup_binary" ]; then "$losetup_binary" "$@" else losetup "$@" fi } dm_snapshot() { # TODO: eliminate duplication; this function exists elsewhere in a less generalized form local ro_file rw_file cutoff_size ro_file=$1 rw_file=$2 cutoff_size=$3 local ro_dev rw_dev size new_dev_name persist chunksize if [ -b "$ro_file" ]; then ro_dev=$ro_file else ro_dev=$(LoSetup -r -f --show "$ro_file") || return fi if [ -b "$rw_file" ]; then rw_dev=$rw_file else rw_dev=$(LoSetup -f --show "$rw_file") || return fi if [ "$cutoff_size" -a "$cutoff_size" -gt 0 ]; then size=$cutoff_size else size=$(blockdev --getsz "$ro_dev") || return fi new_dev_name=${ro_dev##*/} persist=p chunksize=16 dmsetup create "$new_dev_name" --table "0 $size snapshot $ro_dev $rw_dev $persist $chunksize" || return wait_for_dm_device /dev/mapper/"$new_dev_name"; echo /dev/mapper/"$new_dev_name" } dm_snapshot_teardown() { local dev="$1" case "$dev" in /dev/dm-*) dmsetup table "$dev" | ( read _ _ snapshot ro_dev rw_dev _ _ [ "$snapshot" = snapshot ] || exit 1 dmsetup remove "$dev" || exit 1 # errors ignored because the loop dev can be configured to be # automatically removed upon disuse losetup -d /dev/block/"$rw_dev" || true eject /dev/block/"$ro_dev" || exit 1 ) || return ;; *) return 1 ;; esac } wait_for_dm_device() { # TODO: improve while ! [ -e "$1" ]; do sleep 1 done } dup_mount_cdrom() { local cdrom_dev="$1" mountpoint="$2" local sectors md_dev=/dev/md55 cdrom_rw_file=/"${cdrom_dev##*/}".rw sectors=$(get_cdrom_sizelimit "$cdrom_dev") || return # TODO: do we even need this backing file? We do need to trick mdadm into # thinking that this is a RW device, but previously we got away with just # creating a loopback device. dd if=/dev/zero of="$cdrom_rw_file" bs=1K count=32 || return cdrom_rw_dev=$(dm_snapshot "$cdrom_dev" "$cdrom_rw_file" "$sectors") || return mdadm_dup "$cdrom_rw_dev" "$md_dev" "$sectors" || return mount -t iso9660 -r $md_dev "$mountpoint" } get_cdrom_sizelimit() { # returns 512-byte sectors local dev="$1" sectors sectors=$(blockdev --getsz "$dev") || return # Check if we can read the last 8 sectors. With a TAO CDROM, we can't -- # these sectors are faux, and not part of the ISO fs. If mdadm is allowed to # read them, it will mark the device failed. if dd count=2 if="$dev" bs=2048 skip=$((sectors/4 - 2)) of=/dev/null 2>/dev/null; then echo $sectors else echo $((sectors - 8)) fi } mdadm_dup() { local input_dev="$1" md_name="$2" sectors="$3" mdadm --build $md_name ${sectors:+--size=$((sectors / 2))} \ --level=1 --raid-devices=1 --force --write-mostly "$input_dev" || return } mdadm_subdevices() { local md_dev="$1" mdadm -D "$md_dev" -Y | sed -ne 's/^MD_DEVICE_.*_DEV=//p' } mdadm_copy_eject() # NOT INITRD; uses non-busybox "losetup" { local md_dev="$1" output_file="$2" [ -b "$md_dev" ] || return [ ! -e "$output_file" ] || return local output_dev sectors old_subdev=$(mdadm_subdevices "$md_dev"|head -n1) || return [ -b "$old_subdev" ] || return sectors=$(blockdev --getsz "$md_dev") || return truncate -s $((sectors * 512)) "$output_file" || return output_dev=$(LoSetup -f --show "$output_file") || return mdadm "$md_dev" --add "$output_dev" || return mdadm "$md_dev" --grow -n2 || return mdadm_wait_remove "$md_dev" "$old_subdev" || return mdadm "$md_dev" --grow -n1 --force || return dm_snapshot_teardown "$old_subdev" } mdadm_wait_remove() { # We should perhaps use mdadm --monitor's RebuildFinished event. local dev="$1" disk="$2" tries if ! mdadm --wait "$dev"; then tries=1000 while ! mdadm --detail --test "$dev"; do [ $tries -gt 0 ] || return 1 sleep 1 tries=$((tries-1)) done fi mdadm "$dev" --fail "$disk" || return 1 tries=100 while ! mdadm "$dev" --remove "$disk"; do [ $tries -gt 0 ] || return 1 sleep 1 tries=$((tries-1)) done return 0 }