To get absolutely consistent filesystem backups, it is necessary to snapshot them and do an backup of this snapshot. Especially if you backup database files in use, you get consistent data if all your datafiles reside on the same volume. This config does it for me:


default.conf:

  client: <you local hostname>
  tree: /backup/mnt/ /boot
  pre-client:  source /usr/local/lib/snap_functions.sh && mount_snap /boot /backup/mnt
  post-client: source /usr/local/lib/snap_functions.sh && umount_snap /backup/mnt

/usr/local/lib/snap_functions.sh:

  #!/bin/bash

  VGBASE="/dev/_your_volgroup_dir_"
  VGNAME="${VGBASE#/dev/}"
  VGRETRYC=5 # anzahl versuche + 2 (5 -> 7 versuche)
  KEYFILE=_your_pgp_keyfile_for_crypto_loop_
  LOOPDEV=/dev/loop7
  SNAPFLAGF=".snapped"

  cd /tmp || { echo "ERROR: can not set workdir to /tmp"; exit 1; }
  test -d "$VGBASE" || { echo "ERROR: Volgroup dir not found: $VGBASE"; exit 1; }
  set +o posix

  mount_snap () {
        local mountp
        local snapmnt
        local lvname
        local mntinf
        local fstype
        local i
        local sleepint
        local snapname
        local mntopt
        local mntdev
        declare -i i
        declare -i sleepint
        mountp="$1"
        snapmnt="$2"
        read -a mntinf < <(mount | grep " on $mountp ")
        lvname=${mntinf[0]##*/}
        test -z "$lvname" && { echo "ERROR: not mounted: $mountp"; return 1; }
        if [[ "$lvname" != $VGNAME-* ]]; then
                mount_bind "$mountp" "$snapmnt"
                return
        fi
        lvname=${lvname#$VGNAME-}
        test -d "$snapmnt" || return 1
        test -d "$mountp" || return 1
        test -e "$VGBASE/$lvname" || return 1
        date --rfc-3339=ns > "$mountp/$SNAPFLAGF"
        sync
        i=0
        snapname="${lvname}_$i.backup"
        while [ -e "$VGBASE/$snapname" ]; do
                test $((i++)) -ge 10 && break
                snapname="${lvname}_$i.backup"
        done
        i=$VGRETRYC
        sleepint=0
          # SuSE has shiti race conditionbug in lvcreate snapshot: https://bugzilla.novell.com/show_bug.cgi?id=178321
        while ! lvcreate -A n -s -n "$snapname" -L 1G "$VGBASE/$lvname" 2>/dev/null; do
                sleep $((sleepint=sleepint+1))
                while ! lvremove -A n -f "$VGBASE/$snapname" 2>/dev/null; do
                        sleep $((sleepint=sleepint+1))
                        let i--
                done
                let i-- || break
        done
        test $i -lt 0 && return 1
        fstype=${mntinf[4]}
        case $fstype in
            reiserfs)   mntopt="nolog," ;;
            ext3)       mntopt="" ;;
        esac
          if [[ ${mntinf[5]} == *encryption=AES* ]]; then
                losetup $LOOPDEV 2>/dev/null && { echo "ERROR: loopdev $LOOPDEV already in use"; return 1; }
                gpg -o - $KEYFILE | losetup -e AES -p 0 $LOOPDEV $VGBASE/$snapname || return 1
                mountdev=$LOOPDEV
        else
                mountdev="$VGBASE/$snapname"
        fi
        mount -t $fstype -o ${mntopt}ro,acl,user_xattr $mountdev $snapmnt
  }

  umount_snap () {
        local snapmnt
        local mntinf
        local mntdev
        local lvname
        local tmp
        snapmnt="$1"
        read -a mntinf < <(mount | grep " on $snapmnt ")
        mntdev=${mntinf[0]}
        case $mntdev in
            $LOOPDEV)
                read -a tmp < <(losetup $LOOPDEV)
                mntdev=${tmp[2]//[()]/}
                umount $snapmnt || return 1
                losetup -d $LOOPDEV || return 1
                remove_snap $mntdev
                ;;
            */$VGNAME-*)
                umount $snapmnt || return 1
                remove_snap $mntdev
                ;;
            *)  umount $snapmnt
                ;;
        esac
  }

  remove_snap () {
        local mntdev
        local lvname
        local i
        local sleepint
        declare -i i
        declare -i sleepint
        mntdev="$1"
        lvname=${mntdev##*/}
        lvname=${lvname#$VGNAME-}
        if [[ "$lvname" != *_[0-9].backup ]]; then
                echo "ERROR: $mntdev is not backup LV"
                return 1
        fi
        i=$VGRETRYC
        sleepint=0
        while ! lvremove -A n -f "$VGBASE/$lvname" 2>/dev/null; do
                sleep $((sleepint=sleepint+1))
                let i-- || break
        done
        test $i -lt 0 && return 1
        return 0
  }

  mount_bind () {
        local mountp
        local snapmnt
        mountp="$1"
        snapmnt="$2"
        test -d "$snapmnt" || return 1
        test -d "$mountp" || return 1
        date --rfc-3339=ns > "$mountp/$SNAPFLAGF"
        mount --bind -o ro "$mountp" "$snapmnt"
  }

Optionally:

There is an bug in dirvish 1.2.1 if you use an alias on the tree: config line, resulting in cut of the last char of the src path. You can add an trailing slash or undo an "SpacesInSource fix" like this:

  --- /tmp/dirvish.old  2006-10-30 21:31:42.000000000 +0100
  +++ /tmp/dirvish  2006-11-02 11:19:43.000000000 +0100
  @@ -419,7 +419,7 @@

   #+SIS: KHL 2005-02-18  SpacesInSource fix
   #-SIS: ($srctree, $aliastree) = split(/\s+/, $$Options{tree})
  -($srctree, $aliastree) = split(/[^\\]\s+/, $$Options{tree})
  +($srctree, $aliastree) = split(/\s+/, $$Options{tree})
        or seppuku 228, "ERROR: no source tree defined";
   $srctree =~ s(\\ )( )g;                     #+SIS
   $srctree =~ s(/+$)();

Best Regards, Roland <roland.friedwagner@wu-wien.ac.at>

(with a little bit of spelling help by Keith)


Keith Lofstrom adds:

Good catch! However, this breaks the intent of the SpacesInSource fix, which is intended to preserve names with spaces in them, as is common on non-unix machines (and allowed on Unix machines, though discouraged!). See the SpacesInSource page for a better solution to this problem.

Updated snap_functions.sh library to run on ubuntu:

  #!/bin/bash
  
  VGBASE="/dev/vgsys"
  VGNAME="${VGBASE#/dev/}"
  VGRETRYC=1 # anzahl versuche + 2 (5 -> 7 versuche)
  KEYFILE=/opt/rfried/keys/aes_keys/user_crypt_fs_keyfile_v1.gpg
  LOOPDEV=/dev/loop7
  SNAPFLAGF=".snapped"
  
  cd /tmp || { echo "ERROR: can not set workdir to /tmp"; exit 1; }
  test -d "$VGBASE" || { echo "ERROR: Volgroup dir not found: $VGBASE"; exit 1; }
  set +o posix
  
  mount_snap () {
          local mountp
          local snapmnt
          local lvname
          local mntinf
          local fstype
          local snapname
          local mntopt
          local mntdev
          local is_crypt=""
          mountp="$1"
          snapmnt="$2"
          #while read a b c; do echo "$a - $b - $c"; test $b = /crypt && break; done < <(cat /proc/mounts | grep /tmp)
          #mountp=$(mount | grep "$(echo /dev/vg_sys | rev | cut -f 1 -d / | rev)-$lvname" | cut -f 3 -d ' ')
          #mountp=$(mount | grep "${VGBASE##*/}-$lvname" | cut -f 3 -d ' ')
          #test -n "$mountp"
          #fstype=$(mount | grep "${VGBASE##*/}-$lvname" | sed -r -e "s/.* type ([[:alnum:]]+) \(.*/\1/")
          #test -n "$fstype"
          read -a mntinf < <(mount | grep " on $mountp ")
          lvname=${mntinf[0]##*/}
          test -z "$lvname" && { echo "ERROR: not mounted: $mountp"; return 1; }
          if [[ ${mntinf[0]} == /dev/mapper/* ]] && cryptsetup isLuks $VGBASE/$lvname 2>/dev/null; then
                  is_crypt=y
          elif [[ "$lvname" != $VGNAME-* ]]; then
                  mount_bind "$mountp" "$snapmnt"
                  return
          fi
          lvname=${lvname#$VGNAME-}
          test -d "$snapmnt" || return 1
          test -d "$mountp" || return 1
          test -e "$VGBASE/$lvname" || return 1
          { date --rfc-3339=ns > "$mountp/$SNAPFLAGF"; } 2>/dev/null
          sync
          i=0
          snapname="${lvname}_$i.backup"
          while [ -e "$VGBASE/$snapname" ]; do
                  test $((i++)) -ge 10 && break
                  snapname="${lvname}_$i.backup"
          done
          sleep 1; sync; sleep 1
          lvcreate -A n -s -n "$snapname" -L 1G "$VGBASE/$lvname"
          sleep 1; sync; sleep 1
          fstype=${mntinf[4]}
          case $fstype in
              reiserfs)        mntopt="nolog," ;;
              ext3)        mntopt="" ;;
          esac
          if [[ ${mntinf[5]} == *encryption=AES* ]]; then
                  losetup $LOOPDEV 2>/dev/null && { echo "ERROR: loopdev $LOOPDEV already in use"; return 1; }
                  gpg -o - $KEYFILE | losetup -e AES -p 0 $LOOPDEV $VGBASE/$snapname || return 1
                  mountdev=$LOOPDEV
          elif [ $is_crypt ]; then
                  cryptsetup luksOpen $VGBASE/$snapname $snapname
                  mountdev=/dev/mapper/$snapname
          else
                  mountdev="$VGBASE/$snapname"
          fi
          mount -t $fstype -o ${mntopt}ro,acl,user_xattr $mountdev $snapmnt
  }
  
  umount_snap () {
          local snapmnt
          local mntinf
          local mntdev
          local lvname
          local tmp
          snapmnt="${1%%+(/)}"
          read -a mntinf < <(mount | grep " on $snapmnt ")
          mntdev=${mntinf[0]}
          lvname=${mntdev##*/}
          case $mntdev in
              $LOOPDEV)
                      read -a tmp < <(losetup $LOOPDEV)
                  mntdev=${tmp[2]//[()]/}
                  umount $snapmnt || return 1
                  losetup -d $LOOPDEV || return 1
                  remove_snap $mntdev
                      ;;
              */$VGNAME-*)
                      umount $snapmnt || return 1
                  remove_snap $mntdev
                      ;;
              /dev/mapper/*)
                  if cryptsetup isLuks $VGBASE/$lvname 2>/dev/null; then
                          umount $snapmnt || return 1
                          cryptsetup luksClose $lvname || return 1
                          remove_snap $VGBASE/$lvname
                  fi
                  ;;
              *)        umount $snapmnt
                      ;;
          esac
  }
  
  remove_snap () {
          local mntdev
          local lvname
          mntdev="$1"
          lvname=${mntdev##*/}
          lvname=${lvname#$VGNAME-}
          if [[ "$lvname" != *_[0-9].backup ]]; then
                  echo "ERROR: $mntdev is not backup LV"
                  return 1
          fi
          sleep 1; sync; sleep 1
          lvremove -A n -f "$VGBASE/$lvname"
          sleep 1; sync; sleep 1
  }
  
  mount_bind () {
          local mountp
          local snapmnt
          mountp="$1"
          snapmnt="$2"
          test -d "$snapmnt" || return 1
          test -d "$mountp" || return 1
          { date --rfc-3339=ns > "$mountp/$SNAPFLAGF"; } 2>/dev/null
          mount --bind -o ro "$mountp" "$snapmnt"
  }
  
  aclattr_bak () {
          local src="$1"
          local dst="$2"
  
         if command -v getfacl >/dev/null && command -v getfattr >/dev/null; then
            ( cd "$src"
              getfacl --skip-base -R -P . | gzip > "$dst/../getfacl-R.gz"
              getfattr -d -R -P . | gzip > "$dst/../getfattr-dR.gz"
            )
        else
            echo "WARNING: getfacl or getfattr not found" >&2
        fi
 }

Example dirvish.conf:

  client: titan
  tree: /backup/mnt/ /opt
  pre-client:  ; source /opt/rfried/lib/snap_functions.sh && mount_snap /opt /backup/mnt
  post-client: ; source /opt/rfried/lib/snap_functions.sh; aclattr_bak $DIRVISH_SRC $DIRVISH_DEST; umount_snap /backup/mnt
  rsync-option:
  #        --acls

RFried (last edited 2011-01-24 06:52:16 by KeithLofstrom)