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" ] && [ "$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 _ crypt_dev _ case "$snapshot" in snapshot) 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" || true ;; crypt) cryptsetup remove "$dev" || exit 1 losetup -d /dev/block/"$crypt_dev" || true ;; esac ) || 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' } cryptsetup_temp() { local sectors="$1" cryptname="$2" temp_file="$3" parms=$- secret set +x # Add 4096 sectors for LUKS header truncate -s $(((sectors + 4096) * 512)) "$temp_file" || return cleartext_dev=$(LoSetup -f --show "$temp_file") || return secret="$(head -c256 /dev/urandom)" || return printf %s "$secret" | cryptsetup luksFormat "$cleartext_dev" - || return printf %s "$secret" | cryptsetup --key-file - luksOpen "$cleartext_dev" "$cryptname" || return unset secret set "$parms" wait_for_dm_device /dev/mapper/"$cryptname" rm "$temp_file" echo /dev/mapper/"$cryptname" } mdadm_copy_eject_crypt() { local md_dev="$1" temp_file="$2" [ -b "$md_dev" ] || return local output_dev sectors old_subdev=$(mdadm_subdevices "$md_dev"|head -n1) || return [ -b "$old_subdev" ] || return # TODO: truncate to the ISO fs size if the device is larger sectors=$(blockdev --getsz "$md_dev") || return output_dev=$(cryptsetup_temp "$sectors" samizdatiso "$temp_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_copy_eject() { 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 }