#!/bin/sh : ${ROOT_MKFS_CMD:=mkfs.ext4 -q} : ${ROOT_FS_TYPE:=ext4} losetup() { /sbin/losetup "$@"; } lvm() { # get rid of warnings from lvm because we are holding open these fds command lvm "$@" 3>&- 4>&- } 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 } mount_squashfs_images() { modprobe squashfs find_squashfs_root | while read dirname basename; do [ -n "$dirname" -a -n "$basename" ] || continue local f="$dirname/$basename" [ -f "$f" ] || return local name=${basename%.squashfs} mkdir -p /squashes/$name mount -r -o loop "$f" /squashes/$name done bootdone squashfs-root } lv_exists() { [ -n "$1" ] && lvm lvs "$1" >/dev/null 2>&1 } 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" } vgfree_megs() { local vg="$1" out out=$(lvm vgs -o pv_free --noheadings --nosuffix --units m "$vg") || return echo ${out%.*} } init_samizdat_lvs() { local megs_free cdrom_dev cdrom_sectors cdrom_megs root_megs megs_free=$(vgfree_megs samizdat) || return cdrom_dev="$(mountpoint_to_dev /cdrom)" || return cdrom_sectors=$(blockdev --getsz "$cdrom_dev") || return cdrom_megs=$(ceil4 $(( cdrom_sectors / 2048 ))) root_megs=$(floor4 $(( megs_free - cdrom_megs ))) if [ $root_megs -le 0 ]; then # No room for cdrom mirror. Oh well, charge forward. root_megs=$megs_free fi lvm lvcreate -Z n -L ${root_megs}m -n root samizdat || return $ROOT_MKFS_CMD /dev/mapper/samizdat-root || return } mount_aufs_branches() { local new="$1" mkdir /overlay mount -t${ROOT_FS_TYPE} /dev/mapper/samizdat-root /overlay || return if [ "$new" ]; then cp -a /gpg /overlay || return touch /overlay/samizdat-filesystem-is-new fi bootdone rw-overlay mirror_cdrom || return mount_squashfs_images || return } init_samizdat() { local imgfile="$1" megs="$2" keyfile="$3" dev init_samizdat_vg "$imgfile" "$megs" "$keyfile" || return init_samizdat_lvs || return mount_aufs_branches new } open_samizdat() { open_samizdat_vg "$@" || return lvs=$(lvm lvs --separator / samizdat -o vg_name,lv_name --noheadings) || return lvm lvchange -ay $lvs || return mount_aufs_branches } init_samizdat_lodev() { local imgfile="$1" megs=$(ceil4 "$2") truncate -s ${megs}M "$imgfile" || return dev=$(losetup -f) && losetup "$dev" "$imgfile" || return echo "$dev" } open_samizdat_vg() { 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_vg() { 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 lvm pvcreate /dev/mapper/"$cryptname" || return lvm vgcreate samizdat /dev/mapper/"$cryptname" } grow_samizdat_vg_free() { # Grow the samizdat VG sufficiently to ensure it has at least $want_free_megs free. local want_free_megs=$(ceil4 "$1") free_megs free_megs=$(vgfree_megs samizdat) || return if [ "$free_megs" -lt "$want_free_megs" ]; then grow_samizdat_vg $((want_free_megs - free_megs)) || return fi } 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 } vg_to_pv() { lvm vgs "$1" -o devices --noheadings | ( found= multidev= while read dev; do dev=${dev%(*} if [ "$found" -a "$found" != "$dev" ]; then exit 1 fi found=$dev done readlink -f "$found" ) } 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" } samizdat_backing_file() { local pv pv=$(vg_to_pv samizdat) && [ "$pv" ] || return cryptdev_to_backing_file "$pv" } grow_samizdat_lv() { # Increase the size of the specified LV by $megs MB, creating the LV and resizing the VG as necessary. local lv_name="$1" megs="$2" stat imgfile freemegs imgfile=$(samizdat_backing_file) || return if lv_exists samizdat/"$lv_name"; then grow_samizdat_vg_free "$megs" || return lvm lvresize -r -L +${megs}m samizdat/"$lv_name" || return else grow_samizdat_vg_free "$megs" || return lvm lvcreate -Z n -L ${megs}m -n "$lv_name" samizdat || return fi } 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" } grow_samizdat_vg() { # Increase the size of the samizdat VG by $megs MB, resizing the backing file as necessary. local megs="$1" cryptdev dev imgfile stat cryptdev=$(vg_to_pv samizdat) dev=$(cryptdev_to_dev "$cryptdev") || return [ -b "$dev" ] || return if [ "$(stat -c '%t' "$dev")" = 7 ]; then # this is a loop device imgfile=$(lodev_to_file "$dev") || return stat="$(stat -c 'local du=$((%B*%b)) sz=%s' "$imgfile")" || return eval "$stat" stat=$(stat -f -c 'local df=$((%f*%S))' "$imgfile") || return eval "$stat" local min_free_space=30 if [ $(( df - megs*1024*1024 - sz + du )) -le $((min_free_space * 1024 * 1024)) ]; then echo 'grow_samizdat_vg: Not enough disk space!' >&2 return -1 fi truncate -cs +${megs}M "$imgfile" || return losetup -c "$dev" || return cryptsetup resize "$cryptdev" || return lvm pvresize "$cryptdev" || return else echo 'grow_samizdat_vg: Unimplemented!' >&2 return 1 fi } 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 } mirror_cdrom() { local md_num=55 dev mp rest cdrom_dev sectors cdrom_dev="$(mountpoint_to_dev /cdrom)" || return local lv_name=samizdat/cdrom local lv_dev=/dev/mapper/samizdat-cdrom local md_name=/dev/md$md_num if [ -b $md_name ]; then echo "RAID device already exists: '$md_name'; try removing (mdadm -S $md_name) and retry" >&2 return 1 fi if lv_exists $lv_name.tmp; then lvm lvchange --available n $lv_name.tmp && lvm lvremove $lv_name.tmp || return fi if lv_exists $lv_name; then umount /cdrom || return mount -r "$lv_dev" /cdrom return fi umount /cdrom || return sectors=$(blockdev --getsz "$cdrom_dev") || return grow_samizdat_lv ${lv_name#samizdat/}.tmp $((sectors / 2 / 1024 + 1)) || return # In order to trick mdadm into accepting a read-only device, we need # to create a (read-write) loopback device. # Furthermore, in order to deal with block device errors caused by # TAO "run-out blocks" we may need to discard the last two 2048-byte # sectors. local cdrom_loopdev sizelimit cdrom_loopdev=$(losetup -f) || return sizelimit=$(get_cdrom_sizelimit "$cdrom_dev") # Apparently loopdev sizelimit is not respected by linux md. Nor # blockdev --getsz. Does it even work? Anyway, although this is used # here redundantly, 'mdadm --size' is used as well. /sbin/losetup ${sizelimit:+--sizelimit=$sizelimit} "$cdrom_loopdev" "$cdrom_dev" || return mdadm --build $md_name ${sizelimit:+--size=$((sizelimit / 1024))} \ --level=1 --raid-devices=1 --force --write-mostly "$cdrom_loopdev" || return mdadm -D $md_name >&2 mdadm --add $md_name $lv_dev.tmp mdadm -D $md_name >&2 mdadm --grow $md_name -n 2 mdadm -D $md_name >&2 mount -r $md_name /cdrom || { mount -r "$cdrom_dev" /cdrom; return 1; } chpst -P samizdat-cdrom-copy "$md_name" "$lv_name" "$lv_dev" "$cdrom_loopdev" "$cdrom_dev" & echo "[$$] Launched RAID monitor with pid $!." >&2 } init_gpg() { bootwait samizdat-cdrom export GNUPGHOME=/gpg/gnupghome (umask 077; rsync --exclude '/luks-key*' --ignore-existing -rpP /cdrom/samizdat/gpg/ /gpg/) 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 }