aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--[-rwxr-xr-x]squashfu411
1 files changed, 129 insertions, 282 deletions
diff --git a/squashfu b/squashfu
index b4fa5f3..ff070eb 100755..100644
--- a/squashfu
+++ b/squashfu
@@ -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