diff options
-rw-r--r--[-rwxr-xr-x] | squashfu | 411 |
1 files changed, 129 insertions, 282 deletions
@@ -14,140 +14,58 @@ die () { exit 1 } -# Source default config, and bail on bad syntax -CONFIG=/etc/squashfu.conf -source $CONFIG -[[ $? -gt 0 ]] && die "Syntax error in config file." - -am_i_root () { - [[ $UID -eq 0 ]] || die "Must be root!" -} +mount_squash () { +# Arguments: none +# Returns: return code of mount command + mount -t loop,ro "$SEED" "${BKUP_ROOT}/ro" + return $? +} + +mount_union_with_bins () { +# Arguments: numbers of bins to be mounted (variable number) +# Returns: 0 on successful mount, non-zero on failure + # Mount first as rw, shift, and mount the rest ro + branches="br=${BINS_DIR}/$1=rw:"; shift + for bin in $*; do + branches="${branches}/bins/$bin=ro:" + done + branches="${branches}${BKUP_ROOT}/ro=ro" -create_directory_structure () { - cd "${BKUP_ROOT}" - mkdir -p {ro,rw,bins/{1..7}} -} + debug "mount -t aufs none "${BKUP_ROOT}/rw" -o udba=reval,$branches" + mount -t aufs none "${BKUP_ROOT}/rw" -o udba=reval,$branches -create_new_seed () { - # Create a new squashfs based on the contents of the union - # It's okay if we make an empty squash, we'll tell the user - # about it at the end and suggest --resquash - debug "Making new squash seed $(basename $SEED)" - [[ $1 == "replace" ]] && { - mksquashfs "${BKUP_ROOT}/rw" "$SEED.replace" -b 65536; - } || { - mksquashfs "${BKUP_ROOT}/rw" "$SEED" -b 65536; - } + return $? } -# The meat and potatoes of this bad agent. -do_backup () { - [[ -w "${BKUP_ROOT}" ]] || - die "Backup root is not accessible. Please check your setting in /etc/squashfu" - - # Sanitize our workspace. - unmount_all - - # Ensure that we have directories to mount and write to - create_directory_structure - - # We need a seed to mount, even if it's going to be empty - [[ -f "$SEED" ]] || { - debug "No seed found -- creating a new one..."; - create_new_seed; - } - - # Mount the necessary bins all as ro - mount_union $(( $(date +%u) + $MODIFIER )) - - # Do the backup! Do doo doo doooo - run_rsync - - # Force flush of pseudo links on branches to ensure clean dismount - # Our umount.aufs helper can do this for us, but it doesn't handle - # rsync hangups very well. - #sleep 5 - #auplink "${BKUP_ROOT}/rw" flush - - # Is there reason to resquash? - [[ $(date +%u) -eq $RESQUASH_DAY || $RESQUASH_AFTER ]] && { - create_new_seed replace - [[ $KEEP_LAST_WEEK ]] && { - save_old_tree; - } || { - rm "$SEED"; - rm -r "${BKUP_ROOT}/bins" - } - - mv "$SEED.replace" "$SEED" - create_directory_structure - } - - [[ $REPORT ]] && print_usage full - - unmount_all +get_next_available_bin () { +# Arguments: none +# Returns: Numeric value of the next unused bin + return $(cut -d: -f1 "$BINVENTORY" | sort -n | tail -1) } -mount_seed () { - debug "Mounting seed" - # Mount the squashed seed, failing if we can't - mount -o loop,ro "${SEED}" "${BKUP_ROOT}/ro" || { - die FATAL: Error mounting $SEED; - } -} +sweep_bins () { +# Arguments: none +# Returns: none + count=1 -mount_union () { - # Account for second arg from rollback function to mount at a different mountpoint - [[ -n "$2" ]] && { - MOUNT_POINT="$(readlink -f $2)"; - echo "$MOUNT_POINT" >> /tmp/squashfu.custom - } || { - MOUNT_POINT="${BKUP_ROOT}/rw"; - } - - # build mount string - # mount the first bin as rw, the rest as ro, and the seed as ro - branches="br=${BKUP_ROOT}/bins/$1=rw:" - for i in $(seq $(($1 - 1)) -1 1); do - branches="${branches}${BKUP_ROOT}/bins/${i}=ro:" + # Make sure bins are numbered in order, clean up if not. In other words, + # if we have 10 bins, make sure they're ordered 1 through 10. + for bin in "${BINS_DIR}/*"; do + if [[ ! -d "${BINS_DIR}/$count" ]]; then + high_bin=$(ls "${BINS_DIR}" | sort -n | tail -1) + mv "${BINS_DIR}/$high_bin" "${BINS_DIR}/$count" + sed -i "/^$high_bin:/s/^$high_bin:/$count:/" "$BINVENTORY" + fi + count=$[ $count + 1 ] done - branches="${branches}${BKUP_ROOT}/ro=ro" - # build and execute mount command - debug "mount -t aufs none "${MOUNT_POINT}" -o udba=reval,$branches" - mount -t aufs none "${MOUNT_POINT}" -o udba=reval,$branches } -print_usage () { - # Do another sanity check -- check sizes of bins versus squash. - total_bin_size=$(du -s ${BKUP_ROOT}/bins 2>/dev/null | awk '{print $1}') - sfs_size=$(stat -c %s $SEED 2>/dev/null) - - # Basic query - info "Overall Usage:" - printf "%25s %10.2f MiB\n" "Seed size:" "$(echo "scale=2;$sfs_size / 1024 / 1024" | bc)" - printf "%25s %10.2f MiB\n" "Total incremental size:" "$(echo "scale=2;$total_bin_size / 1024 / 1024" | bc)" - - # Full query - [[ $1 == "full" ]] && { - echo - info "Detailed Usage By Bin:" - for bin in {1..7}; do - bin_size=$(du -s "${BKUP_ROOT}/bins/$bin" 2>/dev/null | awk '{print $1}'); - printf "%25s %10.2f MiB\n" "Bin $bin" $(echo "scale=2;$bin_size / 1024 / 1024" | bc); - done; - } - - # Compare seed and bin size. Suggest resquash if needed. - [[ $total_bin_size -gt $sfs_size ]] && { - info "Your incrementals are larger than your seed! You might consider resquashing your backup with $0 --resquash"; - } -} +call_rsync () { +# Arguments: none +# Returns: return code from rsync -run_rsync() { - # Gather includes and excludes from config file - # No error checking here -- user better not have - # effed up the config + # Parse includes and excludes from heredocs in config INCLUDES=($(sed -n '/^<<INCLUDES$/,/^INCLUDES$/p' $CONFIG | grep -vE "^<*INCLUDES$")) EXCLUDES=($(sed -n '/^<<EXCLUDES$/,/^EXCLUDES$/p' $CONFIG | \ grep -vE "^<*EXCLUDES$" | \ @@ -155,192 +73,121 @@ run_rsync() { # rsync source to $BKUP_ROOT/rw debug "Rsync executing with:" - debug " Options: ${RSYNC_OPTS[@]}" - debug " Includes: ${INCLUDES[@]}" - debug " Excludes: ${EXCLUDES[@]}" + debug ": Options: ${RSYNC_OPTS[@]}" + debug ": Includes: ${INCLUDES[@]}" + debug ": Excludes: ${EXCLUDES[@]}" rsync ${RSYNC_OPTS[@]} ${INCLUDES[@]} ${EXCLUDES[@]} "${BKUP_ROOT}/rw" - + return $? } -save_old_tree () { - # create new directory, and then set aside old seed -- yes, you can do this while its mounted - mkdir "${BKUP_ROOT}/last-week" - cd "$BKUP_ROOT" && mv {$SEED,bins/} "${BKUP_ROOT}/last-week" -} +create_new_bin () { +# Arguments: 1, the number of the bin to create +# Returns: 0 on success, non-zero on error -unmount_all () { - am_i_root + # Create new directory, fail if it exists (something's wrong) + mkdir "${BINS_DIR}/$1" + if [[ $? -ne 0 ]]; then + return $? + fi + + # Update binventory with new bin name and timestamp + echo "${1}:$(stat -c %u ${BINS_DIR}/$1)" >> "$BINVENTORY" + return $? - #Union must be unmounted first, or bad things happen - unmount_custom - unmount_union - #unmount_seed } -unmount_custom () { - # Unmount any custom mounts from rollback operations - debug "Checking for and unmounting user-specified mount points" - [[ -f /tmp/squashfu.custom ]] && { - while read mount; do - umount $mount - done < /tmp/squashfu.custom; - rm /tmp/squashfu.custom; - } +# Unmounting functions +unmount_union () { +# Args: none +# Returns: return code from umount + umount "${BKUP_ROOT}/rw" + return $? } unmount_seed () { - # Account for possibility of multiple mounts - debug "Checking for and unmounting seed" - while [[ $(mountpoint "${BKUP_ROOT}/ro" | grep "is a mount") ]]; do - umount "${SEED}" - done +# Args: none +# Returns: return code from umount + umount "${BKUP_ROOT}/ro" + return $? } -unmount_union () { - # Account for possibility of multiple mounts - debug "Checking for and unmounting union" - while [[ $(mountpoint "${BKUP_ROOT}/rw" | grep "is a mount") ]]; do - debug "Found mount, attempting to unmount..." - umount "${BKUP_ROOT}/rw" - done +unmount_all () { +# Args: none +# Returns: none + + # Union MUST be unmounted first + unmount_union + unmoun_seed } -usage () { - info "SquashFu: a backup solution hewn out of boredom" - cat <<HELP - -USAGE - squashfu <operation> [options] - -OPERATIONS - -B - Runs a regular backup, using the config file at /etc/squashfu, unless - otherwise specified with the -c option. - - -R <day> [mount] - Rollback to the day described by the following argument. 'day' needs to be - a day of the week between the last resquash and the current day. An - alternate mount point can be specified for the resulting rolled back union. - If unspecified, the union will be mounted at $BKUP_ROOT/rw. - - -U - Displays the size of the seed, the incrementals, and the actual backup. If - you provide no additional options, a basic report will be given. Specifying - "full" will give more detail about individual bins. - - -Z - Use this option to restore sanity, AKA check for the seed or union being - mounted and unmount them. Squashfu will ensure that everything is unmounted - before a backup, but you might use this after restoring a file after a - rollback. - -OPTIONS - -c, --config - Specify an alternate location to a config file that will override defaults - provided in /etc/squashfu.conf. - - --report - Provide a usage report after the backup has completed. - - --resquash - To be used with the -B operation. This forces a new squashed seed to be - created when the backup finishes. The original seed and incrementals are - moved to the directory specified in the config file. Ensure that you have - sufficient space for this operation, or bad things will happen. You can - use the -Q operation to estimate how much free space you will need. - - --resquash-discard-old - Similar to --resquash except the old seed and incrementals are discarded - after the new seed is created. - -HELP - exit 0 +check_for_resquash () { +# Args: none +# Returns: number of bins needing to be merged + local number_of_bins=$(wc -l "$BINVENTORY") + + if [[ $number_of_bins -gt $MAX_BINS ]]; then + return $[ $number_of_bins - $MIN_BINS ] + else + return 0 + fi } -##################### -# Dispatch # -##################### -[[ $# -eq 0 ]] && usage - -dispatch_backup () { - am_i_root - while [[ $# -gt 0 ]]; do - case $1 in - "-c"|"--config") ;; - "--report") REPORT=true ;; - "--resquash") RESQUASH_AFTER=true;KEEP_LAST_WEEK=true ;; - "--resquash-discard-old") RESQUASH_AFTER=true;KEEP_LAST_WEEK=false ;; - "--modifier") shift;MODIFIER=$1 ;; - *) die "Invalid backup option $1"; usage ;; - esac - shift - done +create_new_squash () { +# Args: number of bins to be squashed (as determined by check_for_resquash), -1 on initial creation +# Returns: 0 on success, non-zero on failure - do_backup + # If making first seed, create it empty and return + if [[ $1 -eq -1 ]]; then + mksquashfs "${BKUP_ROOT}/rw" "$SEED" -b 65536 + return $? + fi - exit 0 -} + # Determine oldest $1 bins and mount them with the current squash + local old_bins=($(sort -n -r -t: -k2 "$BINVENTORY" | tail -$1 | cut -d: -f1)) + + mount_union_with_bins ${old_bins[@]} -dispatch_report () { - #am_i_root + # Create new squash with temp name + mksquashfs "${BKUP_ROOT}/rw" "$SEED.replace" -b 65536 + + # If the squash wasn't made correctly, we don't want to continue + if [[ $? -ne 0 ]]; then + return 1 + fi + + unmount_all - # Check for valid arg - case $1 in - "") print_usage ;; - "full") print_usage full ;; - *) die "Invalid report option"; usage ;; - esac; + # Replace old squash + mv "${SEED}.replace" "$SEED" - exit 0 + # Delete old bins, and remove entry from binventory + for bin in $old_bins; do + rm -rf "${BKUP_ROOT}/bins/$bin" + sed -i "/^$bin:/d" "$BINVENTORY" + done } -dispatch_rollback () { - am_i_root - # Check arguments conform - [[ $# -eq 0 || $# -gt 2 ]] && { - die "Invalid rollback option"; - usage; - } - - # Ensure first rollback arg is a valid day of the week - debug "Attempting to rollback to $1" - date --date=$1 >/dev/null 2>&1 - [[ $? -gt 0 ]] && { - die "Invalid day of week '$1'"; - usage; - } - - # TODO: Make sure that user isn't trying to mount in the future? - # This may not be an issue since the bins are cleaned after each - # resquash. - - # Ensure second arg (if supplied) is a valid directory - [[ -n "$2" && ! -d "$2" ]] && { - die "'$2' is not a valid mount point"; - usage; - } - - # Don't mount union multiple times - unmount_custom - unmount_union +create_new_incremental () { +# Args: none +# Returns: 0 on success, non-zero on error + + # Make a new bin for this incremenetal + get_next_available_bin + create_new_bin $? + + # Determine the mount order via binventory + bin_order=($(sort -n -r -t:2 -k2 "$BINVENTORY" | cut -d: -f1)) + + mount_union_with_bins ${BIN_ORDER[@]} - mount_seed + # Die with error on mount, else start rsync + if [[ $? -ne 0 ]]; then + return 1; + fi - # Convert day to numerical day of week and mount - mount_union $(date --date=$1 +%u) $2 + call_rsync - exit 0 + return $? } -# Determine operation and send to appropriate dispatcher -while [[ $# -gt 0 ]]; do - case $1 in - "-B") shift; dispatch_backup $* ;; - "-U") shift; dispatch_report $1 ;; - "-R") shift; dispatch_rollback $* ;; - "-Z") unmount_all ;; - *) usage ;; - esac - shift -done |