diff --git a/btrfs/subvolume-delta-file b/btrfs/subvolume-delta-file new file mode 100755 index 0000000..4a8610c --- /dev/null +++ b/btrfs/subvolume-delta-file @@ -0,0 +1,70 @@ +#!/bin/sh +# +# BTRFS delta snapshots with incomplete send handling +# CC0 ~caskd + +: ${SRC:?'Source not defined'} +: ${SNAPDIR:?'Snapdir not defined'} +: ${DEST:?'Destination not defined'} + +CDATE="$(date '+%s')" +SNAPSRC="$SRC" +SNAPPATH="$SNAPSRC/$SNAPDIR" +PREV="$SNAPPATH/prev" +CUR="$SNAPPATH/cur" +TMP="$SNAPPATH/tmp" +SNAPDEST="$SNAPPATH/$CDATE" +PARENTNAME="parent" +DESTNAME="data" + +msg() { + echo "$1" >&2 +} + +compress() { + zstd -T0 -z -9 +} + +delta() { + local TS_PARENT="$(basename "$(realpath "$CUR")")" + local TS_CURRENT="$(basename "$(realpath "$TMP")")" + [ -d "$(realpath "$CUR")" ] || return 1 + [ -e "$DEST/$TS_PARENT/$DATA" ] || return + msg "Attempting to send delta" + + mkdir -vp "$DEST/$TS_CURRENT" + ln -sv "../$TS_PARENT/$DESTNAME" "$DEST/$TS_CURRENT/$PARENTNAME" + btrfs -v send --compressed-data -p "$CUR" "$TMP" | compress > "$DEST/$TS_CURRENT/$DESTNAME.tmp" + mv -v "$DEST/$TS_CURRENT/$DESTNAME.tmp" "$DEST/$TS_CURRENT/$DESTNAME" +} + +full() { + local TS_CURRENT="$(basename "$(realpath "$TMP")")" + msg "Attempting to send full" + + mkdir -vp "$DEST/$TS_CURRENT" + btrfs -v send --compressed-data "$TMP" | compress > "$DEST/$TS_CURRENT/$DESTNAME.tmp" + mv -v "$DEST/$TS_CURRENT/$DESTNAME.tmp" "$DEST/$TS_CURRENT/$DESTNAME" +} + +symdel() { + if [ -L "$1" ]; then + btrfs subvolume delete "$1" >&2 || true + rm -v "$1" >&2 + fi +} + +mkdir -vp "$SNAPPATH" >&2 + +symdel "$TMP" +symdel "$PREV" + +ln -sv "$CDATE" "$TMP" >&2 +btrfs subvolume snapshot -r "$SNAPSRC" "$SNAPDEST" >&2 + +delta || full || return 1 + +# Mark snapshot transmission as complete +[ -L "$CUR" ] && \ + mv -v "$CUR" "$PREV" >&2 +mv -v "$TMP" "$CUR" >&2