#!/bin/sh . ./var.sh . ./btrfs-functions.sh create_image() { $(ARGS_NE megs filename) [ ! -e "$filename" ] || return truncate -s "$megs"M "$filename" } make_seed_fs() { $(ARGS_NE input_dir img) local mountpoint dev seed_subvol root_subvol mountpoint=$img.mnt mkdir "$mountpoint" || die 'mkdir failed' dev=$(lodev "$img") || die 'lodev failed' mkfs.btrfs "$dev" || die 'mkfs failed' mount "$dev" "$mountpoint" || die 'mount failed' seed_subvol=$mountpoint/SEED root_subvol=$mountpoint/ROOT btrfs subvolume create "$root_subvol" || die rsync -aP "$input_dir"/ "$root_subvol"/ || die 'rsync failed' btrfs subvolume snapshot -r "$root_subvol" "$seed_subvol" || die btrfs_subvolume_set_default "$root_subvol" || die umount "$mountpoint" || die 'umount failed' rmdir "$mountpoint" btrfstune -S 1 "$dev" || die 'btrfstune failed' unlodev "$dev" } btrfs_subvolume_set_default() { $(ARGS_NE path) local subvol_id subvol_id=$(btrfs_show_subvolume_id "$path") || return btrfs subvolume set-default "$subvol_id" "$path" } loop_is_attached() { [ "$(losetup -l "$1" -O BACK-FILE -n 2>/dev/null)" ] } lodev() { lodev_helper false "$@"; } lodev_readonly() { lodev_helper true "$@"; } lodev_helper() { $(ARGS_NE readonly img) local link b dev opt_ro link=$img.b if $readonly; then opt_ro=-r else opt_ro= fi if [ -L "$link" ]; then b="$(readlink "$link")" loop_is_attached "$b" || losetup $opt_ro "$b" "$img" || return else dev=$(unused_dev) || return losetup $opt_ro "$dev" "$img" || return ln -sf "$dev" "$img".b || return fi >/dev/null printf '%s\n' $link } unused_dev() { local do_not_use f d no do_not_use=$(for f in *.b; do [ -L "$f" ] || continue; readlink $f; done) for n in $(seq 10 20); do d=/dev/loop$n for no in $do_not_use; do [ "$d" != "$no" ] || continue 2 done echo $d return done } unlodev() { $(ARGS_NE link) [ -L "$link" ] || return losetup -d "$(readlink "$link")" # && rm -f "$link" } plant_seed() { $(ARGS_NE seed_img overlay_img mountpoint) local overlay_dev seed_dev overlay_dev=$(lodev "$overlay_img") || die 'lodev failed' seed_dev=$(lodev_readonly "$seed_img") || die 'lodev failed' mount "$seed_dev" "$mountpoint" || die 'mount failed' btrfs device add "$overlay_dev" "$mountpoint" || die 'btrfs-device-add failed' mount -o rw,remount "$mountpoint" || die 'remount failed' btrfs device delete "$seed_dev" "$mountpoint" || die 'btrfs-device-delete failed' unlodev "$seed_dev" umount "$mountpoint" unlodev "$overlay_dev" } generate_seed_helper() { $(ARGS_NE seed_layer keep_old_seeds size seed_img snapshot_mountpoint overlay_img_output) local mountpoint overlay_dev seed_dev snapshot_default mountpoint=${overlay_img_output}.mnt mountpoint -q "$snapshot_mountpoint" || die [ -d "$mountpoint" ] || mkdir "$mountpoint" || die filemaker "$overlay_img_output" create_image "$size" || die overlay_dev=$(lodev "$overlay_img_output") || die seed_dev=$(lodev_read_only "$seed_img") || die mount -o subvol=/ "$seed_dev" "$mountpoint" || die btrfs device add "$overlay_dev" "$mountpoint" || die mount -o rw,remount "$mountpoint" || die snapshot_default=$(btrfs_show_default_path "$snapshot_mountpoint") || die push_simple "$snapshot_mountpoint"/SEED_NEW "$snapshot_mountpoint"/SEED "$snapshot_default" "$mountpoint" if $keep_old_seeds; then sex mv -T --backup=numbered "$mountpoint"/SEED_NEW "$mountpoint"/SEED || die else btrfs subvolume delete "$mountpoint"/SEED || die sex mv "$mountpoint"/SEED_NEW "$mountpoint"/SEED || die fi if ! $seed_layer; then btrfs device remove "$seed_dev" "$mountpoint" || die fi umount "$mountpoint" || die rmdir "$mountpoint" btrfstune -S 1 "$overlay_img_output" || die 'btrfstune failed' } generate_seed_overlay() # NB. uses non-local variables set in <..>_helper { generate_seed_helper true true "$@" } regenerate_toplevel_seed() { generate_seed_helper false true "$@" } regenerate_from_master() { regenerate_toplevel_seed 256 seed.img master overlay.img remove_image seed.img || die mv overlay.img seed.img || die mv overlay.img.b seed.img.b || die } plant_seed_mount() { plant_seed "$1" "$2" "$3" mount "$(lodev "$2")" "$3" } plant_seed_mount_rootvol() { plant_seed "$1" "$2" "$3" mount -o subvol=/ "$(lodev "$2")" "$3" } remove_image() { unlodev "$1.b" rm -f "$1" "$1.b" } IMAGES='master.img slave.img seed.img overlay.img' MNTPOINTS='master slave/mnt slave seed.img.mnt overlay.img.mnt' mounter() { $(ARGS_NE mp) shift mountpoint -q "$mp" && return mkdir "$mp" 2>/dev/null "$@" "$mp" } filemaker() { $(ARGS_NE f) shift [ -f "$f" ] && return ! [ -e "$f" ] || return "$@" "$f" } in_dir() { (set -e cd "$1" shift "$@") } pull() { $(ARGS_NE mnt src dst) local src_subvol src_subvol=$(btrfs_show_default_path "$src") || die die 'unimplemented' } push_slave() { local mnt img img_dev mnt=slave/mnt img=slave.img mkdir "$mnt" 2>/dev/null img_dev=$(lodev "$img") || die mounter "$mnt" mount -o subvol=/ "$img_dev" || die push "$mnt" slave master umount "$mnt" losetup -D } main() { local img mkdir inp touch inp/file.txt for img in $IMAGES; do filemaker $img create_image 256 || die # Note: btrfs kernel task hangs if image is too small! done make_seed_fs inp seed.img || die mounter slave plant_seed_mount seed.img slave.img mounter master plant_seed_mount_rootvol seed.img master.img } cleanup() { local img m for img in $IMAGES; do remove_image "$img" done for m in $MNTPOINTS; do umount "$m" 2>/dev/null rmdir "$m" 2>/dev/null done losetup -D 2>/dev/null true } case "$1" in clean|cleanup) cleanup ;; testclean) main; cleanup ;; cleantest) cleanup; main ;; push) push_slave ;; regen) regenerate_from_master ;; '') main ;; *) die "Unknown command: $1" ;; esac