chiark / gitweb /
rsync-backup.{in,8}: Introduce `hook' concept.
[rsync-backup] / rsync-backup.in
index ec876ed9ce037a9b876eac6baa1a0c1e2d658b52..e232bc8d766ed51ef85ad84dee64e44db3731799 100644 (file)
@@ -78,6 +78,15 @@ maybe () {
   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.
@@ -94,8 +103,8 @@ run () {
        { { { ( 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
@@ -124,7 +133,7 @@ hostrun () {
   ## 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
 }
 
@@ -144,10 +153,35 @@ hostpath () {
   ## current host is local.
 
   if localp $host; then echo $path
-  else echo $host:$path
+  else echo $userat$host:$path
   fi
 }
 
+defhook () {
+  hook=$1
+  ## Define a hook called HOOK.
+
+  eval hk_$hook=
+}
+
+addhook () {
+  hook=$1 cmd=$2
+  ## Add command CMD to the hook HOOK.
+
+  eval old=\$hk_$hook; new="$old $cmd"
+  eval hk_$hook=\$new
+}
+
+runhook () {
+  hook=$1; shift 1
+  ## Invoke HOOK, passing it the remaining arguments.
+
+  eval cmds=\$hk_$hook
+  for cmd in $cmds; do
+    if ! $cmd "$@"; then return $?; fi
+  done
+}
+
 ###--------------------------------------------------------------------------
 ### Database operations.
 
@@ -301,7 +335,7 @@ do_rfreezefs () {
 
     ## 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
@@ -312,7 +346,7 @@ do_rfreezefs () {
     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
        ;;
@@ -339,7 +373,7 @@ do_rfreezefs () {
   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"
 }
@@ -516,7 +550,7 @@ unset VOLUME
 bkprc=0
 
 remote_fshash () {
-  _hostrun $host "
+  _hostrun $userat$host "
        umask 077
        mkdir -p $fshashdir
        cd ${snapmnt#*:}
@@ -564,19 +598,28 @@ expire_backups () {
   done
 }
 
+## Backup hooks.
+defhook setup
+defhook precommit
+defhook postcommit
+
 backup_precommit_hook () {
   host=$1 fs=$2 date=$3
-  ## Override this hook in the configuration file for special effects.
+  ## Compatibility: You can override this hook in the configuration file for
+  ## special effects; but it's better to use `addhook precommit'.
 
   :
 }
+addhook precommit backup_precommit_hook
 
 backup_commit_hook () {
   host=$1 fs=$2 date=$3
-  ## Override this hook in the configuration file for special effects.
+  ## Compatibility: You can override this hook in the configuration file for
+  ## special effects; but it's better to use `addhook commit'.
 
   :
 }
+addhook commit backup_commit_hook
 
 do_backup () {
   date=$1 fs=$2 fsarg=$3
@@ -585,6 +628,14 @@ do_backup () {
   set -e
   attempt=0
 
+  ## Run a hook beforehand.
+  set +e; runhook setup $host $fs $date; rc=$?; set -e
+  case $? in
+    0) ;;
+    99) log "BACKUP of $host:$fs SKIPPED by hook"; return 0 ;;
+    *) log "BACKUP of $host:$fs FAILED (hook returns $?)"; return $? ;;
+  esac
+
   ## Report the start of this attempt.
   log "START BACKUP of $host:$fs"
 
@@ -684,11 +735,11 @@ do_backup () {
   ## Commit this backup.
   case $dryrun in
     nil)
-      backup_precommit_hook $host $fs $date
+      runhook precommit $host $fs $date
       mv new $date
       mv new.fshash $date.fshash
       insert_index $host $fs $date $VOLUME
-      backup_commit_hook $host $fs $date
+      runhook commit $host $fs $date
       mkdir hack
       ln -s $date hack/last
       mv hack/last .
@@ -714,6 +765,61 @@ do_backup () {
   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] ...
   ##
@@ -767,59 +873,25 @@ backup () {
       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.
 
+defhook start
+defhook end
+
+done_first_host_p=nil
+
 host () {
   host=$1
-  like=
+  like= userat=
+  case $done_first_host_p in
+    nil) runhook start; done_first_host_p=t ;;
+  esac
   case "${expire_policy+t},${default_policy+t}" in
     t,) default_policy=$expire_policy ;;
   esac
@@ -831,6 +903,7 @@ 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
@@ -842,7 +915,7 @@ retain () {
 ### Read the configuration and we're done.
 
 usage () {
-  echo "usage: $quis [-v] [-c CONF]"
+  echo "usage: $quis [-nv] [-c CONF]"
 }
 
 version () {
@@ -876,6 +949,8 @@ case $# in 0) ;; *) usage >&2; exit 1 ;; esac
 exec 8>&1
 
 . "$conf"
+
+runhook end $bkprc
 case "$bkprc" in
   0) $verbose "All backups successful" ;;
   *) $verbose "Backups FAILED" ;;