#!/bin/sh losetup() { /sbin/losetup "$@"; } luks_secret() { local parms=$-; # this junk keeps set -x from being too annoying set +x [ -n "$luks_secret" ] || luks_secret="$(head -c256 /dev/urandom)" printf %s "$luks_secret" case $parms in *x*) set -x; set -x ;; esac } floor4() { # Negatives round up, but aren't used. echo $(($1 / 4 * 4)) } ceil4() { local x="$1" [ $((x % 4)) -eq 0 ] || x=$((x + 4 - x % 4)) printf '%d\n' "$x" } . loop-layer.sh losetup_layers() { bootwait samizdat-cdrom local fs fs_rw for fs in /cdrom/rootfs/*.btrfs; do fs_rw=/"${fs##*/}".rw dd if=/dev/zero of="$fs_rw" bs=1M count=10 losetup_snapshot "$fs" "$fs_rw" || return done } init_samizdat() { local blockdev="$1" imgfile="$2" uuid losetup_layers || return modprobe btrfs || return btrfs device scan || return uuid=$(choose_uuid) || return [ "$uuid" ] || return mount -t btrfs UUID="$uuid" /root || return btrfs device add "$blockdev" /root || return mount -o rw,remount /root || return samizdat_movemounts "$imgfile" || return initialize_root_filesystem || return bootdone root-mounted } samizdat_movemounts() { local imgfile="$1" mountpoint if [ "$imgfile" ]; then mountpoint=$(mountpoint_of "$imgfile") || return mkdir /root/outerfs mount -o move "$mountpoint" /root/outerfs fi mkdir /root/cdrom mount -o move /cdrom /root/cdrom mkdir -p /run/initramfs/samizdat/log cp /var/log/* /run/initramfs/samizdat/log true } mountpoint_of() { local f="$1" while ! mountpoint -q "$f"; do f=$(dirname "$f") [ "$f" != '.' ] || return 1 done printf '%s\n' "$f" } initialize_root_filesystem() { rm -r /root/root btrfs subvolume create /root/root || return mv /gpg/gnupghome /root/root/.gnupg || return rmdir /root/srv btrfs subvolume create /root/srv rm -r /root/var/cache/apt/archives btrfs subvolume create /root/var/cache/apt/archives || return rmdir /root/home btrfs subvolume create /root/home || return [ -x /root/sbin/mdadm ] || cp /sbin/mdadm /root/sbin/ # Copy these over unconditionally, because they ought to remain in sync with # the initrd. cp /bin/mdadm-dup.sh /root/sbin/ cp /bin/samizdat-eject.sh /root/sbin/ sed -i -e 's/^root:x:/root::/' /root/etc/passwd cp /patchroot/* /root/root/ true } # Get the uuid of the filesystem with the most devices, # excluding filesystems that don't incorporate loop devices. # This is used to choose the latest seed -- which should have # the most layers. choose_uuid() { local seen_loop= seen_uuid= seen_devs= btrfs filesystem show | while read line; do case "$line" in Label*) seen_uuid=${line##*uuid: } seen_devs= seen_loop= ;; *Total\ devices*) seen_devs=${line#*Total devices } seen_devs=${seen_devs%% *} ;; *path\ /dev/mapper/*) seen_loop=t;; esac [ "$seen_loop" ] && echo "$seen_devs $seen_uuid" done | uniq | sort -nr | head -n1 | (read _ x; echo $x) } filesystem_incomplete() { local n n=$(btrfs filesystem show "$1" | sed -ne 's/.*Total devices \([^ ]*\) .*/\1/p') [ "$n" != 1 ] } open_samizdat() { local imgfile="$1" keyfile="$2" open_samizdat_blockdev "$imgfile" "$keyfile" || return local blockdev=/dev/mapper/samizdatcrypt fs # For this part, we don't necessarily need the cdrom. # Unfortunately the init_gpg code is still getting the GPG key there. if filesystem_incomplete "$blockdev"; then losetup_layers fi modprobe btrfs || return btrfs device scan || return mount -t btrfs "$blockdev" /root || return samizdat_movemounts "$imgfile" LoSetup -D bootdone root-mounted } init_samizdat_lodev() { local imgfile="$1" megs=$(ceil4 "$2") dev truncate -s ${megs}M "$imgfile" || return dev=$(losetup -f) && losetup "$dev" "$imgfile" || return echo "$dev" } open_samizdat_blockdev() { local imgfile="$1" keyfile="$2" dev local cryptname=samizdatcrypt dev=$(losetup -f) && losetup "$dev" "$imgfile" || return gpg2 --verify "$keyfile" || return # The first --decrypt merely strips the signature. The option is # poorly named for that case. gpg2 --decrypt "$keyfile" | gpg2 --decrypt | cryptsetup --key-file - luksOpen "$dev" "$cryptname" || return [ -b /dev/mapper/"$cryptname" ] || return } init_samizdat_blockdev() { local imgfile="$1" megs="$2" keyfile="$3" dev local cryptname=samizdatcrypt dev=$(init_samizdat_lodev "$imgfile" "$megs") || return [ ! -b /dev/mapper/"$cryptname" ] || return luks_secret >/dev/null luks_secret | gpg2 --default-recipient-self --encrypt --armor | gpg2 --clearsign --output "$keyfile" || return luks_secret | cryptsetup luksFormat "$dev" - || return cryptsetup luksDump "$dev" >&2 luks_secret | cryptsetup --key-file - luksOpen "$dev" "$cryptname" || return [ -b /dev/mapper/"$cryptname" ] || return } majmin() { local dev="$1" major minor eval $(stat -c 'major=%t minor=%T' "$dev") || return [ "$major" -a "$minor" ] || return printf '%d:%d\n' 0x$major 0x$minor } cryptdev_to_dev() { local dev="$1" majmin majmin=$(majmin "$dev") || return set -- /sys/dev/block/$majmin/slaves/* [ $# = 1 ] || return cryptsetup status "$dev" |while read k v; do if [ "$k" = device: ]; then echo $v; break; fi; done } cryptdev_to_backing_file() { local dev="$1" majmin result majmin="$(majmin "$dev")" || return set -- /sys/dev/block/$majmin/slaves/* [ $# = 1 ] || return read result < "$1"/loop/backing_file || return printf '%s\n' "$result" } lodev_to_file() { local result majmin dev="$1" majmin="$(majmin "$dev")" || return read result < /sys/dev/block/$majmin/loop/backing_file || return printf '%s' "$result" } mountpoint_to_dev() { local wantmp="$1" dev mp rest mountpoint -q "$wantmp" || return while read dev mp rest; do if [ "$mp" = "$wantmp" ]; then echo "$dev"; return; fi; done < /proc/mounts return 1 } get_cdrom_sizelimit() { # returns bytes local dev="$1" sectors sectors=$(blockdev --getsz "$dev") || return if dd count=2 if="$dev" bs=2048 skip=$((sectors/4 - 2)) of=/dev/null 2>/dev/null; then return else echo $(((sectors-8)*512)) fi } init_gpg() { bootwait samizdat-cdrom export GNUPGHOME=/gpg/gnupghome mkdir -p "$GNUPGHOME" (umask 077; rsync --exclude '/luks-key*' --ignore-existing -rpP /cdrom/gnupghome/ "$GNUPGHOME") if samizdat-password-agent >/var/log/samizdat-password-agent.log 2>&1; then clear true else false fi } start_meter() { local startmsg="$*" (exec >&4 clear echo -n $startmsg set +x while sleep 2; do echo -n . done) & meterpid=$! } stop_meter() { local endmsg="$*" kill $meterpid echo " $endmsg" >&4 }