esac
}
+copy () {
+ prefix=$1
+ ## Copy lines from stdin to stdout, adding PREFIX.
+
+ while IFS= read -r line; do
+ printf "%s %s\n" "$prefix" "$line"
+ done
+}
+
run () {
tag=$1 cmd=$2; shift 2
## Run CMD, logging its output in a pleasing manner.
{ { { ( set +e
"$cmd" "$@" 3>&- 4>&- 5>&- 9>&-
echo $? >&5; ) |
- while IFS= read line; do echo "| $line"; done >&4; } 2>&1 |
- while IFS= read line; do echo "* $line"; done >&4; } 4>&1 |
+ copy "|" >&4; } 2>&1 |
+ copy "*" >&4; } 4>&1 |
cat >&9; } 5>&1 </dev/null
)
case $rc in
## way it will be processed by a shell.
if localp $host; then run "@$host: $tag" sh -c "$cmd"
- else run "@$host: $tag" ssh $host "$cmd"
+ else run "@$host: $tag" ssh $userat$host "$cmd"
fi
}
_hostrun () {
h=$1 cmd=$2
- ## Like hostrun, but without the complicated logging, but targetted at a
+ ## Like hostrun, but without the complicated logging, and targetted at a
## specific host.
if localp $h; then sh -c "$cmd"
## current host is local.
if localp $host; then echo $path
- else echo $host:$path
+ else echo $userat$host:$path
+ fi
+}
+
+###--------------------------------------------------------------------------
+### Database operations.
+
+INDEXDB=@pkglocalstatedir@/index.db
+
+insert_index () {
+ host=$1 fs=$2 date=$3 vol=$4
+
+ if [ -f "$INDEXDB" ]; then
+ sqlite3 "$INDEXDB" <<EOF
+INSERT INTO idx (host, fs, date, vol)
+ VALUES ('$host', '$fs', '$date', '$vol');
+EOF
+ fi
+}
+
+delete_index () {
+ host=$1 fs=$2 date=$3
+
+ if [ -f "$INDEXDB" ]; then
+ sqlite3 "$INDEXDB" <<EOF
+DELETE FROM idx WHERE
+ host = '$host' AND fs = '$fs' AND date = '$date';
+EOF
fi
}
## Get the volume host to create the snapshot.
set +e
- _hostrun >&2 3>&- $lvhost \
+ _hostrun >&2 3>&- $userat$lvhost \
"lvcreate --snapshot -n$lv.bkp $SNAPSIZE $vg/$lv"
snaprc=$?
set -e
case $tok in
"$tok_THAWED") ;;
*)
- _hostrun >&2 3>&- $lvhost "lvremove -f $vg/$lv.bkp" || :
+ _hostrun >&2 3>&- $userat$lvhost "lvremove -f $vg/$lv.bkp" || :
echo >&2 "$quis: unexpected token $tok (rfreezefs $fsdir on $fshost)"
exit 1
;;
esac
## Mount the snapshot on the volume host.
- _hostrun >&2 $lvhost "
+ _hostrun >&2 $userat$lvhost "
mkdir -p $SNAPDIR/$lv
mount -oro /dev/$vg/$lv.bkp $SNAPDIR/$lv"
}
### Actually taking backups of filesystems.
STOREDIR=@mntbkpdir@/store
+METADIR=@mntbkpdir@/meta
MAXLOG=14
HASH=sha256
+unset VOLUME
bkprc=0
remote_fshash () {
- _hostrun $host "
+ _hostrun $userat$host "
umask 077
mkdir -p $fshashdir
cd ${snapmnt#*:}
echo "delete $date"
$verbose -n " expire $date..."
rm -rf $date $date.*
+ delete_index $host $fs $date
$verbose " done"
;;
esac
backup_precommit_hook $host $fs $date
mv new $date
mv new.fshash $date.fshash
+ insert_index $host $fs $date $VOLUME
backup_commit_hook $host $fs $date
mkdir hack
ln -s $date hack/last
$verbose " commit"
## Expire old backups.
+ case "${expire_policy+t},${default_policy+t}" in
+ ,t) expire_policy=$default_policy ;;
+ esac
case "${expire_policy+t},$dryrun" in
t,nil) run "expiry for $host:$fs" expire_backups ;;
t,t) expire_backups ;;
esac
+ clear_policy=t
## Report success.
case $dryrun in
esac
}
+run_backup_cmd () {
+ fs=$1 date=$2 cmd=$3; shift 3
+ ## try_backup FS DATE COMMAND ARGS ...
+ ##
+ ## Run COMMAND ARGS to back up filesystem FS on the current host,
+ ## maintaining a log, and checking whether it worked. The caller has
+ ## usually worked out the DATE in order to set up the filesystem, and we
+ ## need it to name the log file properly.
+
+ ## Find a name for the log file. In unusual circumstances, we may have
+ ## deleted old logs from today, so just checking for an unused sequence
+ ## number is insufficient. Instead, check all of the logfiles for today,
+ ## and use a sequence number that's larger than any of them.
+ case $dryrun in
+ t)
+ log=/dev/null
+ ;;
+ nil)
+ seq=1
+ for i in "$logdir/$host/$fs.$date#"*; do
+ tail=${i##*#}
+ case "$tail" in [!1-9]* | *[!0-9]*) continue ;; esac
+ if [ -f "$i" -a $tail -ge $seq ]; then seq=$(( tail + 1 )); fi
+ done
+ log="$logdir/$host/$fs.$date#$seq"
+ ;;
+ esac
+
+ ## Run the backup command.
+ case $dryrun in nil) mkdir -p $logdir/$host ;; esac
+ if ! "$cmd" "$@" 9>$log 1>&9; then
+ echo >&2
+ echo >&2 "$quis: backup of $host:$fs FAILED!"
+ bkprc=1
+ fi
+
+ ## Count up the logfiles.
+ nlog=0
+ for i in "$logdir/$host/$fs".*; do
+ if [ ! -f "$i" ]; then continue; fi
+ nlog=$(( nlog + 1 ))
+ done
+
+ ## If there are too many, go through and delete some early ones.
+ if [ $dryrun = nil ] && [ $nlog -gt $MAXLOG ]; then
+ n=$(( nlog - MAXLOG ))
+ for i in "$logdir/$host/$fs".*; do
+ if [ ! -f "$i" ]; then continue; fi
+ rm -f "$i"
+ n=$(( n - 1 ))
+ if [ $n -eq 0 ]; then break; fi
+ done
+ fi
+}
+
backup () {
## backup FS[:ARG] ...
##
exit 15
fi
+ ## Read the volume name if we don't have one already. Again, this allows
+ ## the configuration file to provide a volume name.
+ case "${VOLUME+t}${VOLUME-nil}" in
+ nil) VOLUME=$(cat $METADIR/volume) ;;
+ esac
+
## Back up each requested file system in turn.
for fs in "$@"; do
continue
fi
- ## Find a name for the log file. In unusual circumstances, we may have
- ## deleted old logs from today, so just checking for an unused sequence
- ## number is insufficient. Instead, check all of the logfiles for today,
- ## and use a sequence number that's larger than any of them.
- case $dryrun in
- t)
- log=/dev/null
- ;;
- nil)
- seq=1
- for i in "$logdir/$host/$fs.$date#"*; do
- tail=${i##*#}
- case "$tail" in [!1-9]* | *[!0-9]*) continue ;; esac
- if [ -f "$i" -a $tail -ge $seq ]; then seq=$(( tail + 1 )); fi
- done
- log="$logdir/$host/$fs.$date#$seq"
- ;;
- esac
-
## Do the backup of this filesystem.
- case $dryrun in nil) mkdir -p $logdir/$host ;; esac
- if ! do_backup $date $fs $fsarg 9>$log 1>&9; then
- echo >&2
- echo >&2 "$quis: backup of $host:$fs FAILED!"
- bkprc=1
- fi
-
- ## Count up the logfiles.
- nlog=0
- for i in "$logdir/$host/$fs".*; do
- if [ ! -f "$i" ]; then continue; fi
- nlog=$(( nlog + 1 ))
- done
-
- ## If there are too many, go through and delete some early ones.
- if [ $dryrun = nil ] && [ $nlog -gt $MAXLOG ]; then
- n=$(( nlog - MAXLOG ))
- for i in "$logdir/$host/$fs".*; do
- if [ ! -f "$i" ]; then continue; fi
- rm -f "$i"
- n=$(( n - 1 ))
- if [ $n -eq 0 ]; then break; fi
- done
- fi
+ run_backup_cmd $fs $date do_backup $date $fs $fsarg
done
}
###--------------------------------------------------------------------------
### Configuration functions.
-host () { host=$1; like=; $verbose "host $host"; }
+host () {
+ host=$1
+ like= userat=
+ case "${expire_policy+t},${default_policy+t}" in
+ t,) default_policy=$expire_policy ;;
+ esac
+ unset expire_policy
+ $verbose "host $host"
+}
+
snaptype () { snap=$1; shift; snapargs="$*"; retry=0; }
rsyncargs () { rsyncargs="$*"; }
like () { like="$*"; }
retry () { retry="$*"; }
+user () { userat="$*@"; }
retain () {
+ case $clear_policy in t) unset expire_policy; clear_policy=nil ;; esac
expire_policy="${expire_policy+$expire_policy
}$*"
}
exec 8>&1
. "$conf"
+case "$bkprc" in
+ 0) $verbose "All backups successful" ;;
+ *) $verbose "Backups FAILED" ;;
+esac
###----- That's all, folks --------------------------------------------------