From 998dd708b3f96cb6658f90d3db8b3a1284c49757 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Mon, 21 Jan 2013 08:35:38 +0100 Subject: [PATCH 01/19] this commit is adding --send options, support for running on darwin, install scripts and "Tower of Hanoi" backup rotation scheme --- Library/org.zfs.snapshot.hourly.plist | 20 + Makefile | 27 +- README | 25 +- makefile.darwin11 | 7 + makefile.darwin12 | 7 + makefile.linux-gnu | 15 + src/zfs-auto-snapshot.sh | 867 ++++++++++++++++++++++---- 7 files changed, 830 insertions(+), 138 deletions(-) create mode 100644 Library/org.zfs.snapshot.hourly.plist create mode 100644 makefile.darwin11 create mode 100644 makefile.darwin12 create mode 100644 makefile.linux-gnu diff --git a/Library/org.zfs.snapshot.hourly.plist b/Library/org.zfs.snapshot.hourly.plist new file mode 100644 index 0000000..96df579 --- /dev/null +++ b/Library/org.zfs.snapshot.hourly.plist @@ -0,0 +1,20 @@ + + + + + Label + org.zfs.snapshot.hourly + ProgramArguments + + /usr/sbin/zfs-auto-snapshot.sh + --syslog + --keep=24 + --label=hourly + // + + RunAtLoad + + StartInterval + 3600 + + diff --git a/Makefile b/Makefile index 8ad02b3..b558b41 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,16 @@ +SUPPORTED_PLATFORMS=linux-gnu darwin12 darwin11 + +ifeq (,$(findstring $(OSTYPE),$(SUPPORTED_PLATFORMS))) + +all %: + @echo The OS environment variable is set to [$(OSTYPE)]. + @echo Please set the OS environment variable to one of the following: + @echo $(SUPPORTED_PLATFORMS) + +else + all: -install: - install -d $(DESTDIR)$(PREFIX)/etc/cron.d - install -d $(DESTDIR)$(PREFIX)/etc/cron.daily - install -d $(DESTDIR)$(PREFIX)/etc/cron.hourly - install -d $(DESTDIR)$(PREFIX)/etc/cron.weekly - install -d $(DESTDIR)$(PREFIX)/etc/cron.monthly - install etc/zfs-auto-snapshot.cron.frequent $(DESTDIR)$(PREFIX)/etc/cron.d/zfs-auto-snapshot - install etc/zfs-auto-snapshot.cron.hourly $(DESTDIR)$(PREFIX)/etc/cron.hourly/zfs-auto-snapshot - install etc/zfs-auto-snapshot.cron.daily $(DESTDIR)$(PREFIX)/etc/cron.daily/zfs-auto-snapshot - install etc/zfs-auto-snapshot.cron.weekly $(DESTDIR)$(PREFIX)/etc/cron.weekly/zfs-auto-snapshot - install etc/zfs-auto-snapshot.cron.monthly $(DESTDIR)$(PREFIX)/etc/cron.monthly/zfs-auto-snapshot - install -d $(DESTDIR)$(PREFIX)/sbin - install src/zfs-auto-snapshot.sh $(DESTDIR)$(PREFIX)/sbin/zfs-auto-snapshot +include makefile.$(OSTYPE) + +endif diff --git a/README b/README index 745cf7d..27d48e1 100644 --- a/README +++ b/README @@ -1,12 +1,27 @@ zfs-auto-snapshot: An alternative implementation of the zfs-auto-snapshot service for Linux -that is compatible with zfs-linux and zfs-fuse. +and Macosx (currently tested and compatible with ZEVO Community Edition). -Automatically create, rotate, and destroy periodic ZFS snapshots. This is -the utility that creates the @zfs-auto-snap_frequent, @zfs-auto-snap_hourly, +It can automatically create, rotate, and destroy periodic ZFS snapshots. This +is utility that creates the @zfs-auto-snap_frequent, @zfs-auto-snap_hourly, @zfs-auto-snap_daily, @zfs-auto-snap_weekly, and @zfs-auto-snap_monthly snapshots if it is installed. -This program is a posixly correct bourne shell script. It depends only on -the zfs utilities and cron, and can run in the dash shell. +It can backup (send) the snapshots to remote systems or external disks, +utilizing zfs send command. On darwin this can replace TimeMachine backups, +currently not running on top of ZFS filesystems. + +This program is a posixly correct bourne shell script. It depends on zfs utilities +only (Linux). Unfortunatelly on Darwin it needs 'getopt' from macports. + +For using --send option, adapt opt_sendtocmd variable accordingly by editing the +script zfs-auto-snapshot.sh. + + sudo make OSTYPE=linux|darwin install + + installs cron / launchd startup scripts and copies script to /usr/sbin +directory. + + On darwin for daily, weekly and monthly stuff, anacron install is highly +recommended. diff --git a/makefile.darwin11 b/makefile.darwin11 new file mode 100644 index 0000000..be3f874 --- /dev/null +++ b/makefile.darwin11 @@ -0,0 +1,7 @@ + +install: + + install Library/org.zfs.snapshot.hourly.plist /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist + install src/zfs-auto-snapshot.sh /usr/sbin/zfs-auto-snapshot.sh + launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist + diff --git a/makefile.darwin12 b/makefile.darwin12 new file mode 100644 index 0000000..be3f874 --- /dev/null +++ b/makefile.darwin12 @@ -0,0 +1,7 @@ + +install: + + install Library/org.zfs.snapshot.hourly.plist /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist + install src/zfs-auto-snapshot.sh /usr/sbin/zfs-auto-snapshot.sh + launchctl load -w /Library/LaunchDaemons/org.zfs.snapshot.hourly.plist + diff --git a/makefile.linux-gnu b/makefile.linux-gnu new file mode 100644 index 0000000..d5f0b5d --- /dev/null +++ b/makefile.linux-gnu @@ -0,0 +1,15 @@ + +install: + + install -d $(DESTDIR)$(PREFIX)/etc/cron.d + install -d $(DESTDIR)$(PREFIX)/etc/cron.daily + install -d $(DESTDIR)$(PREFIX)/etc/cron.hourly + install -d $(DESTDIR)$(PREFIX)/etc/cron.weekly + install -d $(DESTDIR)$(PREFIX)/etc/cron.monthly + install etc/zfs-auto-snapshot.cron.frequent $(DESTDIR)$(PREFIX)/etc/cron.d/zfs-auto-snapshot + install etc/zfs-auto-snapshot.cron.hourly $(DESTDIR)$(PREFIX)/etc/cron.hourly/zfs-auto-snapshot + install etc/zfs-auto-snapshot.cron.daily $(DESTDIR)$(PREFIX)/etc/cron.daily/zfs-auto-snapshot + install etc/zfs-auto-snapshot.cron.weekly $(DESTDIR)$(PREFIX)/etc/cron.weekly/zfs-auto-snapshot + install etc/zfs-auto-snapshot.cron.monthly $(DESTDIR)$(PREFIX)/etc/cron.monthly/zfs-auto-snapshot + install -d $(DESTDIR)$(PREFIX)/sbin + install src/zfs-auto-snapshot.sh $(DESTDIR)$(PREFIX)/sbin/zfs-auto-snapshot diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index fb4c8d1..623d3b4 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1,8 +1,9 @@ #!/bin/sh -# zfs-auto-snapshot for Linux +# zfs-auto-snapshot for Linux and Macosx # Automatically create, rotate, and destroy periodic ZFS snapshots. # Copyright 2011 Darik Horn +# zfs send options and macosx relevant changes by Matus Kral # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software @@ -20,7 +21,7 @@ # # Set the field separator to a literal tab and newline. -IFS=" +IFS=" " # Set default program options. @@ -30,7 +31,7 @@ opt_default_exclude='' opt_dry_run='' opt_event='-' opt_keep='' -opt_label='' +opt_label='regular' opt_prefix='zfs-auto-snap' opt_recursive='' opt_sep='_' @@ -38,35 +39,98 @@ opt_setauto='' opt_syslog='' opt_skip_scrub='' opt_verbose='' +opt_remove='' +opt_fallback='0' +opt_force='' +opt_sendprefix='' +opt_send='no' +opt_atonce='-I' +opt_create='0' +opt_destroy='0' +opt_rotation='rr' +opt_base='day' +opt_namechange='0' +opt_factor='1' + +# if pipe needs to be used, uncomment opt_pipe="|". arcfour or blowfish will reduce cpu load caused by ssh and mbuffer will +# boost network bandwidth and mitigate low and high peaks during transfer +opt_sendtocmd='ssh -2 root@media -c arcfour,blowfish-cbc -i /var/root/.ssh/media.rsa' +opt_buffer='' +#opt_buffer='mbuffer -q -m 250MB |' +opt_pipe='|' # Global summary statistics. DESTRUCTION_COUNT='0' SNAPSHOT_COUNT='0' WARNING_COUNT='0' +CREATION_COUNT='0' +SENT_COUNT='0' +KEEP='' + +PLATFORM_LOC='' +PLATFORM_REM='' # Other global variables. -SNAPSHOTS_OLD='' +SNAPSHOTS_OLD_LOC='' +SNAPSHOTS_OLD_REM='' +CREATED_TARGETS='' +ZFS_REMOTE_LIST='' +ZFS_LOCAL_LIST='' +TARGETS_DRECURSIVE='' +TARGETS_DREGULAR='' +MOUNTED_LIST_LOC='' +MOUNTED_LIST_REM='' +RC='99' +tmp_file_prefix="/tmp/zfs-auto-snapshot.XXXXXXXX" print_usage () { - echo "Usage: $0 [options] [-l label] <'//' | name [name...]> - --default-exclude Exclude datasets if com.sun:auto-snapshot is unset. - -d, --debug Print debugging messages. - -e, --event=EVENT Set the com.sun:auto-snapshot-desc property to EVENT. - -n, --dry-run Print actions without actually doing anything. - -s, --skip-scrub Do not snapshot filesystems in scrubbing pools. - -h, --help Print this usage message. - -k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots. - -l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly'. - -p, --prefix=PRE PRE is 'zfs-auto-snap' by default. - -q, --quiet Suppress warnings and notices at the console. - --send-full=F Send zfs full backup. Unimplemented. - --send-incr=F Send zfs incremental backup. Unimplemented. - --sep=CHAR Use CHAR to separate date stamps in snapshot names. - -g, --syslog Write messages into the system log. - -r, --recursive Snapshot named filesystem and all descendants. - -v, --verbose Print info messages. + echo "Usage: $0 [options] [-l label] <'//' | name [name...]> + + --default-exclude Exclude datasets if com.sun:auto-snapshot is unset. + --remove-local=n Remove local snapshots after successfully sent via + --send-incr or --send-full but still keeps n newest + snapshots (this will destroy snapshots named according + to --prefix, but regardless of --label). Only valid for + round-robin rotation. + -d, --debug Print debugging messages. + -e, --event=EVENT Set the com.sun:auto-snapshot-desc property to EVENT. + -n, --dry-run Print actions without actually doing anything. + -s, --skip-scrub Do not snapshot filesystems in scrubbing pools. + -h, --help Print this usage message. + -k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots. + -l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly' (default + is 'regular'). + -p, --prefix=PRE PRE is 'zfs-auto-snap' by default. + -q, --quiet Suppress warnings and notices at the console. + -c, --create Create missing filesystems at destination. + -i, --send-at-once Send more incremental snapshots at once in one package + (-i argument is passed to zfs send instead of -I). + --send-full=F Send zfs full backup. F is target filesystem. + --send-incr=F Send zfs incremental backup. F is target filesystem. + --sep=CHAR Use CHAR to separate date stamps in snapshot names. + -X, --destroy Destroy remote snapshots to allow --send-full if + destination has snapshots (needed for -F in case + incremental snapshots on local and remote do not match). + -F, --fallback Allow fallback from --send-incr to --send-full, + if incremental sending is not possible (filesystem + on remote just created or snapshots do not match - + see -X). + -g, --syslog Write messages into the system log. + -r, --recursive Snapshot named filesystem and all descendants. + -R, --replication Use zfs's replication (zfs send -R) instead of simple + send over newly created snapshots (check man zfs for + details). -f is used automatically. + -v, --verbose Print info messages. + -f, --force Passes -F argument to zfs receive (e.g. makes possible + to overwrite remote filesystem during --send-full). + -o, --rotation Round-robin (rr) or hanoi (hanoi) rotation (if -l nor -p + is specified, default label will change from 'regular' + to 'hanoi_regular'). + -a, --base Base unit for hanoi cycle. Can be minute, hour, day, + week, month or year (should follow your cron schedule + frequency). Default base is day. name Filesystem and volume names, or '//' for all ZFS datasets. " } @@ -97,6 +161,7 @@ print_log () # level, message, ... (war*) test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning $* test -z "$opt_quiet" && echo Warning: $* 1>&2 + WARNING_COUNT=$(( $WARNING_COUNT + 1 )) ;; (not*) test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice $* @@ -122,7 +187,7 @@ do_run () # [argv] { if [ -n "$opt_dry_run" ] then - echo $* + echo "... Running $*" RC="$?" else eval $* @@ -137,68 +202,474 @@ do_run () # [argv] return "$RC" } +do_unmount () +{ + local TYPE="$1" + local FLAGS="$3" + local FSNAME="$4" + local SNAPNAME="$5" + local rsort_cmd='0' + local remote_cmd='' + + case "$TYPE" in + (remote) + umount_list="$MOUNTED_LIST_REM" + remote_cmd="$opt_sendtocmd" + ;; + (local) + umount_list="$MOUNTED_LIST_LOC" + ;; + esac + + if [ -n "$SNAPNAME" ]; then + SNAPNAME="@$SNAPNAME" + else + rsort_cmd='1' + fi + umount_list=$(printf "%s\n" "$umount_list" | grep ^"$FSNAME$SNAPNAME" ) + + test -z "$umount_list" && return 0 + + # reverse sort the list if unmounting filesystem and not only snapshot + umount_list=$(printf "%s\n" "$umount_list" | awk -F'\t' '{print $2}') + test $rsort_cmd -eq '1' && umount_list=$(printf "%s\n" "$umount_list" | sort -r) + + for kk in $umount_list; do + print_log debug "Trying to unmount '$kk'." + umount_cmd="umount '$kk'" + if ! do_run "$remote_cmd" "$umount_cmd"; then return "$RC"; fi + test "$FLAGS" != "-r" && break + done + + return 0 +} + +do_delete () +{ + + local DEL_TYPE="$1" + local FSSNAPNAME="$2" + local FLAGS="$3" + KEEP="$4" + local FSNAME=$(echo $FSSNAPNAME | awk -F'@' '{print $1}') + local SNAPNAME=$(echo $FSSNAPNAME | awk -F'@' '{print $2}') + local remote_cmd='' + + if [ "$FSSNAPNAME" = "$FSNAME" -a "$FLAGS" = "-r" ]; then + if [ "$opt_destroy" -ne '1' ]; then + print_log warning "Filesystem $FSNAME destroy requested, but option -X not specified. Aborting." + return 1 + else + KEEP='0' + fi + fi + + KEEP=$(( $KEEP - 1 )) + if [ "$KEEP" -le '0' ] + then + if do_unmount "$DEL_TYPE" "" "$FLAGS" "$FSNAME" "$SNAPNAME"; then + if [ "$DEL_TYPE" = "remote" ]; then + remote_cmd="$opt_sendtocmd" + fi + if do_run "$remote_cmd" "zfs destroy $FLAGS '$FSSNAPNAME'"; then + DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT + 1 )) + fi + fi + fi + + return "$RC" +} + +is_member () +{ + local ARRAY="$1" + local MEMBER="$2" + local ISMEMBER='1' + local mm='' + + for mm in $ARRAY; do + if test "$mm" = "$MEMBER"; then + ISMEMBER='0' + break + fi + done + + return "$ISMEMBER" +} + +do_send () +{ + local SENDTYPE="$1" + local SNAPFROM="$2" + local SNAPTO="$3" + local SENDFLAGS="$4" + local REMOTEFS="$5" + local list_child='' + + if [ "$SENDFLAGS" = "-R" -a "$SENDTYPE" = "full" ]; then + # for full send with -R, target filesystem must be with no snapshots (including snapshots on child filesystems) + list_child=$(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$REMOTEFS/" ) + fi + if [ "$SENDTYPE" = "full" ]; then + list_child=$(printf "%s\n%s\n" "$list_child" $(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$REMOTEFS@" ) ) + fi + + for ll in $list_child; do + if [ "$opt_destroy" -eq '1' ]; then + if do_delete "remote" "$ll" ""; then + continue + fi + fi + print_log debug "Can't destroy remote objects $REMOTEFS ($ll). Can't continue with send-full. -X allowed?" + return 1 + done + + test $SENDTYPE = "incr" && do_run "zfs send " "$SENDFLAGS" "$opt_atonce $SNAPFROM $SNAPTO" "$opt_pipe" "$opt_sendtocmd" "'$opt_buffer zfs recv $opt_force -u $REMOTEFS'" + test $SENDTYPE = "full" && do_run "zfs send " "$SENDFLAGS" "$SNAPTO" "$opt_pipe" "$opt_sendtocmd" "'$opt_buffer zfs recv $opt_force -u $REMOTEFS'" + + return "$RC" +} + +pow () +{ + local x="$1" + local y="$2" + local result='1' + local i='1' + + while [ "$i" -le "$y" ]; do + result=$(( result * x )) + i=$(( i + 1 )) + done + + echo $result +} + +delete_rotation_hanoi () +{ + + local SND_RC="$1" + local FALLBACK="$2" + local FSNAME="$3" + local GLOB="$4" + local FLAGS="$5" + + local base_minute='60' + local base_hour=$(($base_minute * 60)) + local base_day=$(($base_hour * 24)) + local base_week=$(($base_day * 7)) + local base_month=$(($base_day * 31)) + local base="base_$opt_base" + + local opt_hbase=$(eval echo \$$base) + opt_hbase=$(($opt_hbase / $opt_factor )) + + isclass () + { + if [ "$2" = $opt_keep ]; then + return 0 + fi + local remainder=$(pow 2 $(( $2 - 1 )) ) + local divisor=$(($remainder * 2)) + + test $(($1 % $divisor)) -eq $remainder + } + + classify () + { + local creation="$1" + local creation_std='' + local snapdate='' + + case $PLATFORM_LOC in + (Linux) + snapdate=$(echo "$creation" | awk -F'-' '{print $1"-"$2"-"$3" "$4$5}') + creation_std=$(($(env LC_ALL=C date -d "$snapdate" +%s ) / $opt_hbase )) + ;; + (Darwin) + creation_std=$(($(env LC_ALL=C date -j -f "%F-%H%M" "$creation" +%s ) / $opt_hbase )) + ;; + esac + + local class="1" + # Find the class $creation_std belongs to. + while ! isclass $creation_std $class; do + class=$(($class+1)) + done + + echo "$class" + } + + destroy () + { + local dlist="$1" + local dprefix="$2" + local dtype="$3" + local dFSNAME="$4" + local dFLAGS="$5" + local class='' + local previous_class='0' + + tmp_table=$(printf "%s\n" "$dlist" |\ + grep -e ^"$dprefix$FSNAME@$opt_prefix.$opt_label" | + while read name; do + echo "$(classify ${name#$dprefix$FSNAME@$opt_prefix${opt_label:+?$opt_label}?}) $name" + done | sort -k 1rn -k 2r | awk '{print $1"\t"$2}') + + for mm in $tmp_table + do + class=$(echo "$mm" | awk -F'\t' '{print $1}') + if [ "$class" -eq "$previous_class" ]; then + do_delete "$dtype" $(echo "$mm" | awk -F'\t' '{print $2}') "$FLAGS" + fi + previous_class="$class" + done + + } + + + destroy "$SNAPSHOTS_OLD_LOC" "" "local" "$FSNAME" "$FLAGS" + if [ "$SND_RC" -eq '0' ] && [ "$opt_send" != "no" ]; then + destroy "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" + fi + +} + +delete_rotation_rr () +{ + + local SND_RC="$1" + local FALLBACK="$2" + local FSNAME="$3" + local GLOB="$4" + local FLAGS="$5" + + # Retain at most $opt_keep number of old snapshots of this filesystem, + # including the one that was just recently created. + if [ -z "$opt_keep" ] + then + print_log debug "Number of snapshots not specified. Keeping all." + continue + elif [ "$opt_send" != "no" ] && [ "$SND_RC" -ne '0' ] + then + print_log debug "Sending of filesystem was requested, but send failed. Ommiting destroy procedures." + continue + elif [ "$opt_send" != "no" -a -n "$opt_remove" ] + then + KEEP="$opt_remove" + else + KEEP="$opt_keep" + fi + print_log debug "Destroying local snapshots, keeping $KEEP." + + # ASSERT: The old snapshot list is sorted by increasing age. + for jj in $SNAPSHOTS_OLD_LOC + do + # Check whether this is an old snapshot of the filesystem. + test -z "${jj#$FSNAME@$GLOB}" -o -z "${jj##$FSNAME@$opt_prefix*}" -a -n "$opt_remove" -a "$opt_send" != "no" && do_delete "local" "$jj" "$FLAGS" "$KEEP" + done + + if [ "$opt_send" = "no" ] + then + print_log debug "No sending option specified, skipping remote snapshot removal." + continue + elif [ "$sFLAGS" = "-R" ] + then + print_log debug "Replication specified, remote snapshots were removed while sending." + continue + elif [ "$opt_destroy" -eq '1' -a "$FALLBACK" -ne '0' -o "$opt_send" = "full" ] + then + print_log debug "Sent full copy, all remote snapshots were already destroyed." + continue + else + KEEP="$opt_keep" + print_log debug "Destroying remote snapshots, keeping $KEEP." + fi + + # ASSERT: The old snapshot list is sorted by increasing age. + for jj in $SNAPSHOTS_OLD_REM + do + # Check whether this is an old snapshot of the filesystem. + test -z "${jj#$opt_sendprefix/$FSNAME@$GLOB}" && do_delete "remote" "$jj" "$FLAGS" "$KEEP" + done + +} do_snapshots () # properties, flags, snapname, oldglob, [targets...] { local PROPS="$1" - local FLAGS="$2" + local sFLAGS="$2" local NAME="$3" local GLOB="$4" local TARGETS="$5" - local KEEP='' + local LAST_REMOTE='' - # global DESTRUCTION_COUNT - # global SNAPSHOT_COUNT - # global WARNING_COUNT - # global SNAPSHOTS_OLD + local FALLBACK='' + + if test "$sFLAGS" = '-R'; then + FLAGS='-r' + SNexp='.*' + else + FLAGS='' + fi for ii in $TARGETS do - if do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'" + FALLBACK='0' + SND_RC='1' + + print_log debug "--> Snapshooting $ii" + + if ! do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'" then - SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 )) - else - WARNING_COUNT=$(( $WARNING_COUNT + 1 )) continue - fi + fi + SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 )) - # Retain at most $opt_keep number of old snapshots of this filesystem, - # including the one that was just recently created. - test -z "$opt_keep" && continue - KEEP="$opt_keep" + if [ "$opt_send" = "incr" ] + then - # ASSERT: The old snapshot list is sorted by increasing age. - for jj in $SNAPSHOTS_OLD - do - # Check whether this is an old snapshot of the filesystem. - if [ -z "${jj#$ii@$GLOB}" ] + LAST_REMOTE=$(printf "%s\n" "$SNAPSHOTS_OLD_REM" | grep ^"$opt_sendprefix/$ii@" | grep -m1 . | awk -F'@' '{print $2}') + + # in case of -R and incremental send, receiving side needs to have $LAST_REMOTE snapshot for each replicated filesystem + if [ "$FLAGS" = "-r" ]; then + snaps_needed=$(( $(printf "%s\n" "$ZFS_LOCAL_LIST" | grep -c ^"$ii/") + 1 )) + else + snaps_needed='1' + fi + + # remote filesystem just created. if -R run + if is_member "$CREATED_TARGETS" "$opt_sendprefix/$ii" + then + FALLBACK='2' + elif [ -z "$LAST_REMOTE" ] then - KEEP=$(( $KEEP - 1 )) - if [ "$KEEP" -le '0' ] - then - if do_run "zfs destroy $FLAGS '$jj'" - then - DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT + 1 )) + # no snapshot on remote + FALLBACK='1' + elif [ "$snaps_needed" -ne $(printf "%s" "$SNAPSHOTS_OLD_REM" | grep -c -e ^"$opt_sendprefix/$ii$SNexp@$LAST_REMOTE" ) -o \ + "$snaps_needed" -ne $(printf "%s" "$SNAPSHOTS_OLD_LOC" | grep -c -e ^"$ii$SNexp@$LAST_REMOTE" ) ] + then + FALLBACK='3' + else + FALLBACK='0' + fi + + case "$FALLBACK" in + (1) + print_log info "Going back to full send, no snapshot exists at destination: $ii" + ;; + (2) + print_log info "Going back to full send, remote filesystem was just created: $ii" + ;; + (3) + if [ "$FLAGS" = "-r" ]; then + print_log info "Going back to full send, last snapshot on remote is not the last one for whole recursion: $opt_sendprefix/$ii@$LAST_REMOTE" else - WARNING_COUNT=$(( $WARNING_COUNT + 1 )) + print_log info "Going back to full send, last snapshot on remote is not available on local: $opt_sendprefix/$ii@$LAST_REMOTE" fi - fi - fi - done + ;; + (0) + do_send "incr" "$ii@$LAST_REMOTE" "$ii@$NAME" "$sFLAGS" "$opt_sendprefix/$ii" + SND_RC="$?" + ;; + esac + fi + + if [ "$opt_send" = "full" -o "$FALLBACK" -ne '0' -a "$opt_fallback" -eq '1' ]; then + do_send "full" "" "$ii@$NAME" "$sFLAGS" "$opt_sendprefix/$ii" + SND_RC="$?" + fi + test "$SND_RC" -eq '0' && SENT_COUNT=$(( $SENT_COUNT + 1 )) + + case $opt_rotation in + (rr) + delete_rotation_rr "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" + ;; + (hanoi) + delete_rotation_hanoi "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" + ;; + esac + done } +do_getmountedfs () +{ + + local MOUNTED_TYPE="$1" + local MOUNTED_LIST='' + local remote_cmd='' + + case "$MOUNTED_TYPE" in + (remote) + remote_cmd="$opt_sendtocmd" + PLATFORM="$PLATFORM_REM" + ;; + (local) + PLATFORM="$PLATFORM_LOC" + ;; + esac + + case "$PLATFORM" in + (Linux) + MOUNTED_LIST=$(eval $remote_cmd cat /proc/mounts | grep zfs | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$2}' ) + ;; + (Darwin) + MOUNTED_LIST=$(printf "%s\n%s\n" $(eval $remote_cmd zfs mount | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$2}') \ + $(eval $remote_cmd mount -t zfs | grep @ | awk -F' ' '{OFS="\t"}{ORS="\n"}{print $1,$3}') ) + ;; + esac + + printf "%s\n" "$MOUNTED_LIST" | sort +} + +do_createfs () +{ + + local FS="$1" + + for ii in $FS; do + + print_log debug "checking: $opt_sendprefix/$ii" + + if ! is_member "$ZFS_REMOTE_LIST" "$opt_sendprefix/$ii" -eq 0 + then + print_log debug "creating: $opt_sendprefix/$ii" + + if do_run "$opt_sendtocmd" "zfs create $opt_sendprefix/$ii" + then + CREATION_COUNT=$(( $CREATION_COUNT + 1 )) + CREATED_TARGETS=$(printf "%s\n%s\n" "$CREATED_TARGETS" "$opt_sendprefix/$ii" ) + fi + fi + done + +} # main () # { -GETOPT=$(getopt \ - --longoptions=default-exclude,dry-run,skip-scrub,recursive \ - --longoptions=event:,keep:,label:,prefix:,sep: \ - --longoptions=debug,help,quiet,syslog,verbose \ - --options=dnshe:l:k:p:rs:qgv \ - -- "$@" ) \ - || exit 128 +PLATFORM_LOC=`uname` +case "$PLATFORM_LOC" in + (Linux) + getopt_cmd='getopt' + ;; + (Darwin) + getopt_cmd='/opt/local/bin/getopt' + ;; + (*) + print_log error "Local system not known ($PLATFORM_LOC) - needs one of Darwin, Linux. Exiting." + exit 300 + ;; +esac + +GETOPT=$("$getopt_cmd" \ + --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation: \ + --longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback,base:,factor: \ + --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy \ + --options=dnshe:l:k:p:rs:qgvfixcXFRbao \ + -- "$@" ) \ + || exit 128 eval set -- "$GETOPT" @@ -211,7 +682,11 @@ do opt_verbose='1' shift 1 ;; - (--default-exclude) + (-c|--create) + opt_create='1' + shift 1 + ;; + (-x|--default-exclude) opt_default_exclude='1' shift 1 ;; @@ -219,7 +694,7 @@ do if [ "${#2}" -gt '1024' ] then print_log error "The $1 parameter must be less than 1025 characters." - exit 139 + exit 239 elif [ "${#2}" -gt '0' ] then opt_event="$2" @@ -242,13 +717,39 @@ do if ! test "$2" -gt '0' 2>/dev/null then print_log error "The $1 parameter must be a positive integer." - exit 129 + exit 229 fi opt_keep="$2" shift 2 ;; + (-a|--base) + case $2 in + (day|week|month|hour|minute) + opt_base="$2" + ;; + (*) + print_log error "The $1 parameter must be one of: minute, hour, day, week, month, year." + exit 244 + ;; + esac + shift 2 + ;; + (-o|--rotation) + case $2 in + (hanoi|rr) + opt_rotation="$2" + ;; + (*) + print_log error "Rotation must be one of hanoi or rr + ." + exit 245 + ;; + esac + shift 2 + ;; (-l|--label) opt_label="$2" + opt_namechange='1' shift 2 ;; (-p|--prefix) @@ -258,22 +759,40 @@ do case $opt_prefix in ([![:alnum:]_.:\ -]*) print_log error "The $1 parameter must be alphanumeric." - exit 130 + exit 230 ;; esac opt_prefix="${opt_prefix#?}" done opt_prefix="$2" + opt_namechange='1' + shift 2 + ;; + (--factor) + opt_factor="$2" shift 2 ;; (-q|--quiet) opt_debug='' - opt_quiet='1' + opt_quiet='1' opt_verbose='' shift 1 ;; (-r|--recursive) - opt_recursive='1' + opt_recursive=' ' + shift 1 + ;; + (-R|--replication) + opt_recursive='-R' + opt_force='-F' + shift 1 + ;; + (-X|--destroy) + opt_destroy='1' + shift 1 + ;; + (-F|--fallback) + opt_fallback='1' shift 1 ;; (--sep) @@ -283,25 +802,69 @@ do ;; ('') print_log error "The $1 parameter must be non-empty." - exit 131 + exit 231 ;; (*) print_log error "The $1 parameter must be one alphanumeric character." - exit 132 + exit 232 ;; esac opt_sep="$2" shift 2 ;; + (--send-full) + if [ -n "$opt_sendprefix" ]; then + print_log error "Only one of --send-incr and --send-full must be specified." + exit 239 + fi + if [ -z "$2" ]; then + print_log error "Target filesystem needs to be specified with --send-full." + exit 243 + fi + opt_sendprefix="$2" + opt_send='full' + shift 2 + ;; + (--send-incr) + opt_sendincr="$2" + if [ -n "$opt_sendprefix" ]; then + print_log error "Only one of --send-incr and --send-full must be specified." + exit 240 + fi + if [ -z "$2" ]; then + print_log error "Target filesystem needs to be specified with --send-incr." + exit 242 + fi + opt_sendprefix="$2" + opt_send='incr' + shift 2 + ;; (-g|--syslog) opt_syslog='1' shift 1 ;; + (-i|--send-atonce) + opt_atonce='-i' + shift 1 + ;; + (--remove-local) + if ! test "$2" -gt '0' 2>/dev/null + then + print_log error "The $1 parameter must be a positive integer." + exit 241 + fi + opt_remove="$2" + shift 2 + ;; (-v|--verbose) opt_quiet='' opt_verbose='1' shift 1 ;; + (-f|--force|-b|--rollback) + opt_force='-F' + shift 1 + ;; (--) shift 1 break @@ -315,6 +878,16 @@ then exit 133 fi +COUNTER='0' +while [ -e "${tmp_file_prefix%%X*}"* ]; do + print_log error "another copy is running ... $COUNTER" + test "$COUNTER" -gt '11' && exit 99 + sleep 5 + COUNTER=$(( $COUNTER + 1 )) +done +LOCKFILE=$(mktemp "$tmp_file_prefix") +trap "rm -f '$LOCKFILE'; exit $?" INT TERM EXIT + # Count the number of times '//' appears on the command line. SLASHIES='0' for ii in "$@" @@ -332,65 +905,62 @@ fi # this program for Linux has a much better runtime complexity than the similar # Solaris implementation. -ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 ) \ - || { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 135; } - -ZFS_LIST=$(env LC_ALL=C zfs list -H -t filesystem,volume -s name \ - -o name,com.sun:auto-snapshot,com.sun:auto-snapshot:"$opt_label") \ - || { print_log error "zfs list $?: $ZFS_LIST"; exit 136; } +ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 )\ + || { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 135; } -SNAPSHOTS_OLD=$(env LC_ALL=C zfs list -H -t snapshot -S creation -o name) \ - || { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 137; } +ZFS_LIST=$(env LC_ALL=C zfs list -H -t filesystem,volume -s name\ + -o name,com.sun:auto-snapshot,com.sun:auto-snapshot:"$opt_label",mountpoint,canmount,snapdir)\ + || { print_log error "zfs list $?: $ZFS_LIST"; exit 136; } +ZFS_LOCAL_LIST=$(echo "$ZFS_LIST" | awk -F'\t' '{print $1}') # Verify that each argument is a filesystem or volume. for ii in "$@" do test "$ii" = '//' && continue 1 - while read NAME PROPERTIES + for jj in $ZFS_LOCAL_LIST do - test "$ii" = "$NAME" && continue 2 - done <<-HERE - $ZFS_LIST - HERE + test "$ii" = "$jj" && continue 2 + done print_log error "$ii is not a ZFS filesystem or volume." exit 138 done # Get a list of pools that are being scrubbed. ZPOOLS_SCRUBBING=$(echo "$ZPOOL_STATUS" | awk -F ': ' \ - '$1 ~ /^ *pool$/ { pool = $2 } ; \ - $1 ~ /^ *scan$/ && $2 ~ /scrub in progress/ { print pool }' \ - | sort ) + '$1 ~ /^ *pool$/ { pool = $2 } ; \ + $1 ~ /^ *scan$/ && $2 ~ /scrub in progress/ { print pool }' \ + | sort ) # Get a list of pools that cannot do a snapshot. ZPOOLS_NOTREADY=$(echo "$ZPOOL_STATUS" | awk -F ': ' \ - '$1 ~ /^ *pool$/ { pool = $2 } ; \ - $1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \ - | sort) + '$1 ~ /^ *pool$/ { pool = $2 } ; \ + $1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \ + | sort ) -# Get a list of datasets for which snapshots are explicitly disabled. -NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') +# Get a list of datasets for which snapshots are not explicitly disabled. +CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}' ) # If the --default-exclude flag is set, then exclude all datasets that lack # an explicit com.sun:auto-snapshot* property. Otherwise, include them. if [ -n "$opt_default_exclude" ] then - # Get a list of datasets for which snapshots are explicitly enabled. - CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) ~ /true/ || tolower($3) ~ /true/ {print $1}') + # Get a list of datasets for which snapshots are not explicitly enabled. + NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}') else - # Invert the NOAUTO list. - CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}') + # Get a list of datasets for which snapshots are explicitly disabled. + NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') fi # Initialize the list of datasets that will get a recursive snapshot. -TARGETS_RECURSIVE='' +TARGETS_DRECURSIVE='' +TARGETS_TMP_RECURSIVE='' # Initialize the list of datasets that will get a non-recursive snapshot. -TARGETS_REGULAR='' +TARGETS_DREGULAR='' for ii in $CANDIDATES do @@ -403,7 +973,7 @@ do IN_ARGS='0' for jj in "$@" do - if [ "$jj" = '//' -o "$jj" = "$ii" ] + if [ "$jj" = '//' -o "$jj" = "$ii" -o -n "$opt_recursive" -a -z "${ii##$jj/*}" ] then IN_ARGS=$(( $IN_ARGS + 1 )) fi @@ -441,29 +1011,31 @@ do fi done + noauto_parent='0' for jj in $NOAUTO do # Ibid regarding iii. jjj="$jj/" - # The --recursive switch only matters for non-wild arguments. - if [ -z "$opt_recursive" -a "$1" != '//' ] + if [ "$jjj" = "$iii" ] then - # Snapshot this dataset non-recursively. - print_log debug "Including $ii for regular snapshot." - TARGETS_REGULAR="${TARGETS_REGULAR:+$TARGETS_REGULAR }$ii" # nb: \t continue 2 - # Check whether the candidate name is a prefix of any excluded dataset name. + # Check whether the candidate name is a prefix of any excluded dataset name. elif [ "$jjj" != "${jjj#$iii}" ] then - # Snapshot this dataset non-recursively. - print_log debug "Including $ii for regular snapshot." - TARGETS_REGULAR="${TARGETS_REGULAR:+$TARGETS_REGULAR }$ii" # nb: \t - continue 2 + noauto_parent='1' && break fi done - for jj in $TARGETS_RECURSIVE + # not scrubbing + if [ -z "$opt_recursive" -a "$1" != '//' -o "$noauto_parent" = '1' ] + then + print_log debug "Including $ii for regular snapshot." + TARGETS_DREGULAR=$(printf "%s\n%s\n" "$TARGETS_DREGULAR" "$ii" ) + continue + fi + + for jj in $TARGETS_TMP_RECURSIVE do # Ibid regarding iii. jjj="$jj/" @@ -484,7 +1056,8 @@ do # * Is not the descendant of an already included filesystem. # print_log debug "Including $ii for recursive snapshot." - TARGETS_RECURSIVE="${TARGETS_RECURSIVE:+$TARGETS_RECURSIVE }$ii" # nb: \t + TARGETS_TMP_RECURSIVE=$( printf "%s\n%s\n" $TARGETS_TMP_RECURSIVE "$ii" ) + done # Linux lacks SMF and the notion of an FMRI event, but always set this property @@ -495,28 +1068,82 @@ SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event'" # On Solaris %H%M expands to 12h34. DATE=$(date +%F-%H%M) +# if hanoi rotation was requested but prefix or label wasn't changed from default, change label to hanoi to avoid mixing of those backup sets. +if [ "$opt_namechange" -eq '0' ] && [ "$opt_rotation" = "hanoi" ]; then + opt_label="hanoi_regular" +fi + # The snapshot name after the @ symbol. SNAPNAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}" # The expression for matching old snapshots. -YYYY-MM-DD-HHMM SNAPGLOB="$opt_prefix${opt_label:+?$opt_label}????????????????" -test -n "$TARGETS_REGULAR" \ - && print_log info "Doing regular snapshots of $TARGETS_REGULAR" +msg_to_log="Using $opt_rotation type rotation, with params keep: $opt_keep" +if test "$opt_rotation" = "hanoi"; then + msg_to_log=$(echo "$msg_to_log," "base: $opt_base") +fi +print_log debug "$msg_to_log." + +test -n "$TARGETS_DREGULAR" && \ + print_log info "Doing regular snapshots of $(echo "$TARGETS_DREGULAR")" + +test -n "$TARGETS_TMP_RECURSIVE" && \ + print_log info "Doing recursive snapshots of $(echo "$TARGETS_TMP_RECURSIVE")" -test -n "$TARGETS_RECURSIVE" \ - && print_log info "Doing recursive snapshots of $TARGETS_RECURSIVE" +SNAPSHOTS_OLD_LOC=$(env LC_ALL=C zfs list -r -H -t snapshot -S creation -o name $(echo "$TARGETS_DREGULAR") $(echo "$TARGETS_TMP_RECURSIVE") ) \ + || { print_log error "zfs list $?: $SNAPSHOTS_OLD_LOC"; exit 137; } test -n "$opt_dry_run" \ - && print_log info "Doing a dry run. Not running these commands..." + && print_log info "Doing a dry run. Not running these commands..." + +# expand FS list if replication is not used +if [ "$opt_recursive" = ' ' -o "$1" = "//" ] +then + for ii in $TARGETS_TMP_RECURSIVE; do TARGETS_DRECURSIVE=$(printf "%s\n%s\n%s\n" "$TARGETS_DRECURSIVE" $(printf "$ii\n") $(printf "%s\n" "$ZFS_LOCAL_LIST" | grep ^"$ii/") ); done +else + TARGETS_DRECURSIVE="$TARGETS_TMP_RECURSIVE" +fi + +MOUNTED_LIST_LOC=$(eval do_getmountedfs "local") + +# initialize remote system parameters, filesystems, mounts and snapshots +if [ "$opt_send" != "no" ] +then + PLATFORM_REM=$(eval "$opt_sendtocmd" "uname") + + case "$PLATFORM_REM" in + (Linux|Darwin) + ;; + (*) + print_log error "Remote system not known ($PLATFORM_REM) - needs one of Darwin, Linux. Exiting." + exit 301 + ;; + esac + + MOUNTED_LIST_REM=$(eval do_getmountedfs "remote") + + SNAPSHOTS_OLD_REM=$(eval "$opt_sendtocmd" zfs list -r -H -t snapshot -S creation -o name "$opt_sendprefix") \ + || { print_log error "zfs list $?: $SNAPSHOTS_OLD_REM"; exit 140; } + + ZFS_REMOTE_LIST=$(eval "$opt_sendtocmd" zfs list -H -t filesystem,volume -s name -o name) \ + || { print_log error "$opt_sendtocmd zfs list $?: $ZFS_REMOTE_LIST"; exit 139; } +fi + +if [ "$opt_create" -eq '1' -a "$opt_send" != "no" ]; then + do_createfs "$TARGETS_DREGULAR" + do_createfs "$TARGETS_DRECURSIVE" +fi -do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_REGULAR" -do_snapshots "$SNAPPROP" "-r" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_RECURSIVE" +do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DREGULAR" +do_snapshots "$SNAPPROP" "$opt_recursive" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DRECURSIVE" print_log notice "@$SNAPNAME," \ - "$SNAPSHOT_COUNT created," \ - "$DESTRUCTION_COUNT destroyed," \ - "$WARNING_COUNT warnings." + "$SNAPSHOT_COUNT created snapshots," \ + "$SENT_COUNT sent snapshots," \ + "$DESTRUCTION_COUNT destroyed," \ + "$CREATION_COUNT created filesystems," \ + "$WARNING_COUNT warnings." exit 0 # } From 3a05fb02ff2e930a3e767c5a4dc6eb855349fc40 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Mon, 21 Jan 2013 08:53:44 +0100 Subject: [PATCH 02/19] fixes to allow filesystem to filesystem copy within local machine and changed hanoi class calculation. is now simplier and faster. --- src/zfs-auto-snapshot.sh | 100 ++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 54 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 623d3b4..874f692 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -113,6 +113,7 @@ print_usage () -X, --destroy Destroy remote snapshots to allow --send-full if destination has snapshots (needed for -F in case incremental snapshots on local and remote do not match). + -f is used automatically. -F, --fallback Allow fallback from --send-incr to --send-full, if incremental sending is not possible (filesystem on remote just created or snapshots do not match - @@ -131,6 +132,8 @@ print_usage () -a, --base Base unit for hanoi cycle. Can be minute, hour, day, week, month or year (should follow your cron schedule frequency). Default base is day. + --local-only Parameters opt_sendtocmd and opt_buffer are not used, + target for --send will be local machine. name Filesystem and volume names, or '//' for all ZFS datasets. " } @@ -305,6 +308,7 @@ do_send () local SENDFLAGS="$4" local REMOTEFS="$5" local list_child='' + local lq='' if [ "$SENDFLAGS" = "-R" -a "$SENDTYPE" = "full" ]; then # for full send with -R, target filesystem must be with no snapshots (including snapshots on child filesystems) @@ -323,28 +327,15 @@ do_send () print_log debug "Can't destroy remote objects $REMOTEFS ($ll). Can't continue with send-full. -X allowed?" return 1 done + + test -n "$opt_buffer" && lq="'" - test $SENDTYPE = "incr" && do_run "zfs send " "$SENDFLAGS" "$opt_atonce $SNAPFROM $SNAPTO" "$opt_pipe" "$opt_sendtocmd" "'$opt_buffer zfs recv $opt_force -u $REMOTEFS'" - test $SENDTYPE = "full" && do_run "zfs send " "$SENDFLAGS" "$SNAPTO" "$opt_pipe" "$opt_sendtocmd" "'$opt_buffer zfs recv $opt_force -u $REMOTEFS'" + test $SENDTYPE = "incr" && do_run "zfs send " "$SENDFLAGS" "$opt_atonce $SNAPFROM $SNAPTO" "$opt_pipe" "$opt_sendtocmd" "$lq$opt_buffer zfs recv $opt_force -u $REMOTEFS$lq" + test $SENDTYPE = "full" && do_run "zfs send " "$SENDFLAGS" "$SNAPTO" "$opt_pipe" "$opt_sendtocmd" "$lq$opt_buffer zfs recv $opt_force -u $REMOTEFS$lq" return "$RC" } -pow () -{ - local x="$1" - local y="$2" - local result='1' - local i='1' - - while [ "$i" -le "$y" ]; do - result=$(( result * x )) - i=$(( i + 1 )) - done - - echo $result -} - delete_rotation_hanoi () { @@ -353,8 +344,9 @@ delete_rotation_hanoi () local FSNAME="$3" local GLOB="$4" local FLAGS="$5" + local SNAPNAME="$6" - local base_minute='60' + local base_minute=$((60 * $opt_factor )) local base_hour=$(($base_minute * 60)) local base_day=$(($base_hour * 24)) local base_week=$(($base_day * 7)) @@ -362,28 +354,28 @@ delete_rotation_hanoi () local base="base_$opt_base" local opt_hbase=$(eval echo \$$base) - opt_hbase=$(($opt_hbase / $opt_factor )) - isclass () + classify () { - if [ "$2" = $opt_keep ]; then - return 0 - fi - local remainder=$(pow 2 $(( $2 - 1 )) ) - local divisor=$(($remainder * 2)) + rec () + { + local class='0' + local nr="$1" - test $(($1 % $divisor)) -eq $remainder - } + while test $(( 1 << $(($class)) )) -le $(($nr>>1)); do + class=$(($class+1)) + done + bla=$(($nr - $(( 1 << $(($class)) )) )) + test "$bla" -eq '0' && echo $(($class+1)) || rec "$bla" + } - classify () - { local creation="$1" local creation_std='' local snapdate='' case $PLATFORM_LOC in (Linux) - snapdate=$(echo "$creation" | awk -F'-' '{print $1"-"$2"-"$3" "$4$5}') + snapdate=$(echo "$creation" | awk -F'-' '{print $1"-"$2"-"$3" "$4}') creation_std=$(($(env LC_ALL=C date -d "$snapdate" +%s ) / $opt_hbase )) ;; (Darwin) @@ -391,13 +383,7 @@ delete_rotation_hanoi () ;; esac - local class="1" - # Find the class $creation_std belongs to. - while ! isclass $creation_std $class; do - class=$(($class+1)) - done - - echo "$class" + echo $(rec $creation_std) } destroy () @@ -427,10 +413,9 @@ delete_rotation_hanoi () } - - destroy "$SNAPSHOTS_OLD_LOC" "" "local" "$FSNAME" "$FLAGS" + destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$FSNAME@$SNAPNAME" )" "" "local" "$FSNAME" "$FLAGS" if [ "$SND_RC" -eq '0' ] && [ "$opt_send" != "no" ]; then - destroy "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" + destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" fi } @@ -586,7 +571,7 @@ do_snapshots () # properties, flags, snapname, oldglob, [targets...] delete_rotation_rr "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" ;; (hanoi) - delete_rotation_hanoi "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" + delete_rotation_hanoi "$SND_RC" "$FALLBACK" "$ii" "$GLOB" "$FLAGS" "$NAME" ;; esac @@ -664,7 +649,7 @@ case "$PLATFORM_LOC" in esac GETOPT=$("$getopt_cmd" \ - --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation: \ + --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation:,local-only \ --longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback,base:,factor: \ --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy \ --options=dnshe:l:k:p:rs:qgvfixcXFRbao \ @@ -713,6 +698,11 @@ do print_usage exit 0 ;; + (--local-only) + opt_sendtocmd='' + opt_buffer='' + shift 1 + ;; (-k|--keep) if ! test "$2" -gt '0' 2>/dev/null then @@ -789,6 +779,7 @@ do ;; (-X|--destroy) opt_destroy='1' + opt_force='-F' shift 1 ;; (-F|--fallback) @@ -878,6 +869,10 @@ then exit 133 fi +# ISO style date; fifteen characters: YYYY-MM-DD-HHMM +# On Solaris %H%M expands to 12h34. +DATE=$(date +%F-%H%M) + COUNTER='0' while [ -e "${tmp_file_prefix%%X*}"* ]; do print_log error "another copy is running ... $COUNTER" @@ -886,7 +881,7 @@ while [ -e "${tmp_file_prefix%%X*}"* ]; do COUNTER=$(( $COUNTER + 1 )) done LOCKFILE=$(mktemp "$tmp_file_prefix") -trap "rm -f '$LOCKFILE'; exit $?" INT TERM EXIT +trap "rm -f '$LOCKFILE'" INT TERM EXIT # Count the number of times '//' appears on the command line. SLASHIES='0' @@ -1064,10 +1059,6 @@ done # because the SUNW program does. The dash character is the default. SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event'" -# ISO style date; fifteen characters: YYYY-MM-DD-HHMM -# On Solaris %H%M expands to 12h34. -DATE=$(date +%F-%H%M) - # if hanoi rotation was requested but prefix or label wasn't changed from default, change label to hanoi to avoid mixing of those backup sets. if [ "$opt_namechange" -eq '0' ] && [ "$opt_rotation" = "hanoi" ]; then opt_label="hanoi_regular" @@ -1123,18 +1114,19 @@ then MOUNTED_LIST_REM=$(eval do_getmountedfs "remote") - SNAPSHOTS_OLD_REM=$(eval "$opt_sendtocmd" zfs list -r -H -t snapshot -S creation -o name "$opt_sendprefix") \ - || { print_log error "zfs list $?: $SNAPSHOTS_OLD_REM"; exit 140; } - ZFS_REMOTE_LIST=$(eval "$opt_sendtocmd" zfs list -H -t filesystem,volume -s name -o name) \ || { print_log error "$opt_sendtocmd zfs list $?: $ZFS_REMOTE_LIST"; exit 139; } -fi -if [ "$opt_create" -eq '1' -a "$opt_send" != "no" ]; then - do_createfs "$TARGETS_DREGULAR" - do_createfs "$TARGETS_DRECURSIVE" + if [ "$opt_create" -eq '1' ]; then + do_createfs "$TARGETS_DREGULAR" + do_createfs "$TARGETS_DRECURSIVE" + fi + + SNAPSHOTS_OLD_REM=$(eval "$opt_sendtocmd" zfs list -r -H -t snapshot -S creation -o name "$opt_sendprefix") \ + || { print_log error "zfs remote list $?: $SNAPSHOTS_OLD_REM"; exit 140; } fi + do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DREGULAR" do_snapshots "$SNAPPROP" "$opt_recursive" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_DRECURSIVE" From 743f6f4996eeb398da14864760b5f404178223e6 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 3 Feb 2013 06:53:55 +0100 Subject: [PATCH 03/19] avoid parsing log message by logger as additional parameters --- src/zfs-auto-snapshot.sh | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 874f692..0059fc3 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -146,41 +146,41 @@ print_log () # level, message, ... case $LEVEL in (eme*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.emerge $* - echo Emergency: $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.emerge "$*" + echo Emergency: "$*" 1>&2 ;; (ale*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.alert $* - echo Alert: $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.alert "$*" + echo Alert: "$*" 1>&2 ;; (cri*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.crit $* - echo Critical: $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.crit "$*" + echo Critical: "$*" 1>&2 ;; (err*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.err $* - echo Error: $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.err "$*" + echo Error: "$*" 1>&2 ;; (war*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning $* - test -z "$opt_quiet" && echo Warning: $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning "$*" + test -z "$opt_quiet" && echo Warning: "$*" 1>&2 WARNING_COUNT=$(( $WARNING_COUNT + 1 )) ;; (not*) - test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice $* - test -z "$opt_quiet" && echo $* + test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice "$*" + test -z "$opt_quiet" && echo "$*" ;; (inf*) - # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info $* - test -n "$opt_verbose" && echo $* + # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info "$*" + test -n "$opt_verbose" && echo "$*" ;; (deb*) - # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug $* - test -n "$opt_debug" && echo Debug: $* + # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug "$*" + test -n "$opt_debug" && echo Debug: "$*" ;; (*) - test -n "$opt_syslog" && logger -t "$opt_prefix" $* - echo $* 1>&2 + test -n "$opt_syslog" && logger -t "$opt_prefix" "$*" + echo "$*" 1>&2 ;; esac } From 32e912fad467a5d1a3cca9dda33105f6160745a1 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 3 Feb 2013 06:55:49 +0100 Subject: [PATCH 04/19] some short options have been defined without option value, what was wrong. the long options were ok. --- src/zfs-auto-snapshot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 0059fc3..a11273c 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -652,7 +652,7 @@ GETOPT=$("$getopt_cmd" \ --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation:,local-only \ --longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback,base:,factor: \ --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy \ - --options=dnshe:l:k:p:rs:qgvfixcXFRbao \ + --options=dnshe:l:k:p:rs:qgvfixcXFRba:o: \ -- "$@" ) \ || exit 128 From dd60d63fe4f51040324be89608aaa43c1e4d047b Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 3 Feb 2013 06:56:47 +0100 Subject: [PATCH 05/19] it's better to create remote datasets as canmount=off and snapdir=hidden --- src/zfs-auto-snapshot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index a11273c..1120854 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -621,7 +621,7 @@ do_createfs () then print_log debug "creating: $opt_sendprefix/$ii" - if do_run "$opt_sendtocmd" "zfs create $opt_sendprefix/$ii" + if do_run "$opt_sendtocmd" "zfs create -p -o canmount=off -o snapdir=hidden $opt_sendprefix/$ii" then CREATION_COUNT=$(( $CREATION_COUNT + 1 )) CREATED_TARGETS=$(printf "%s\n%s\n" "$CREATED_TARGETS" "$opt_sendprefix/$ii" ) From 15f6537dcd9f1ce891c953db767f098143e7688b Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 3 Feb 2013 13:14:52 +0100 Subject: [PATCH 06/19] a typo which was causing remote snapshots not deleting on hanoi rotation --- src/zfs-auto-snapshot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 1120854..90c7823 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -415,7 +415,7 @@ delete_rotation_hanoi () destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$FSNAME@$SNAPNAME" )" "" "local" "$FSNAME" "$FLAGS" if [ "$SND_RC" -eq '0' ] && [ "$opt_send" != "no" ]; then - destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" + destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" fi } From 3eeb48ede296ef4cb9db2d86d9d0a33824cb644c Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 3 Feb 2013 13:19:03 +0100 Subject: [PATCH 07/19] list of datasets ready for snapshoting should not be displayed multiline (no functional change) --- src/zfs-auto-snapshot.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 90c7823..aa12805 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1077,10 +1077,10 @@ fi print_log debug "$msg_to_log." test -n "$TARGETS_DREGULAR" && \ - print_log info "Doing regular snapshots of $(echo "$TARGETS_DREGULAR")" + print_log info "Doing regular snapshots of $(echo $TARGETS_DREGULAR)" test -n "$TARGETS_TMP_RECURSIVE" && \ - print_log info "Doing recursive snapshots of $(echo "$TARGETS_TMP_RECURSIVE")" + print_log info "Doing recursive snapshots of $(echo $TARGETS_TMP_RECURSIVE)" SNAPSHOTS_OLD_LOC=$(env LC_ALL=C zfs list -r -H -t snapshot -S creation -o name $(echo "$TARGETS_DREGULAR") $(echo "$TARGETS_TMP_RECURSIVE") ) \ || { print_log error "zfs list $?: $SNAPSHOTS_OLD_LOC"; exit 137; } From 5962b845b7613b4990fce4b3728faea1269d1170 Mon Sep 17 00:00:00 2001 From: A Weinlein Date: Sun, 3 Feb 2013 22:43:51 +0100 Subject: [PATCH 08/19] Added homebrew path to getopt as fallback --- src/zfs-auto-snapshot.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index aa12805..45f6313 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -640,7 +640,11 @@ case "$PLATFORM_LOC" in getopt_cmd='getopt' ;; (Darwin) + # macports path as default. Homebrew as fallback getopt_cmd='/opt/local/bin/getopt' + if [ ! -f $getopt_cmd ]; then + getopt_cmd='/usr/local/opt/gnu-getopt/bin/getopt' + fi ;; (*) print_log error "Local system not known ($PLATFORM_LOC) - needs one of Darwin, Linux. Exiting." From e8b1bd880fa24c4c2912653a208e4c93b52ad8e5 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 9 Feb 2013 19:40:40 +0100 Subject: [PATCH 09/19] locking code rewrite. --- src/zfs-auto-snapshot.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 45f6313..20afb94 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -82,7 +82,7 @@ MOUNTED_LIST_LOC='' MOUNTED_LIST_REM='' RC='99' -tmp_file_prefix="/tmp/zfs-auto-snapshot.XXXXXXXX" +tmp_dir="/tmp/zfs-auto-snapshot.lock" print_usage () { @@ -878,14 +878,14 @@ fi DATE=$(date +%F-%H%M) COUNTER='0' -while [ -e "${tmp_file_prefix%%X*}"* ]; do +while true; do + if do_run "mkdir '${tmp_dir}'"; then break; fi print_log error "another copy is running ... $COUNTER" test "$COUNTER" -gt '11' && exit 99 sleep 5 COUNTER=$(( $COUNTER + 1 )) done -LOCKFILE=$(mktemp "$tmp_file_prefix") -trap "rm -f '$LOCKFILE'" INT TERM EXIT +trap "rm -fr '${tmp_dir}'" INT TERM EXIT # Count the number of times '//' appears on the command line. SLASHIES='0' From 53256bc59ac1dceab889fc8f239eaa789085e879 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 9 Feb 2013 19:54:52 +0100 Subject: [PATCH 10/19] doc updates --- src/zfs-auto-snapshot.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 20afb94..6e03521 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -3,7 +3,9 @@ # zfs-auto-snapshot for Linux and Macosx # Automatically create, rotate, and destroy periodic ZFS snapshots. # Copyright 2011 Darik Horn -# zfs send options and macosx relevant changes by Matus Kral +# +# zfs send, hanoi rotation, macosx/linux multiplatform changes - +# Matus Kral # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software From 80e6fdb50bf8e23b87b354a02471fd432acaedd1 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Fri, 29 Mar 2013 13:05:12 +0100 Subject: [PATCH 11/19] Added load checking on remote machine. If you have few machines which are using the same server for backups, could be handy. During early connect to backup destination, check for load is performed. If it's over the limit, process sleeps for 5 minutes. This will happen 3 times, then script will continue with local run only. So we are not going to miss backup schedule. Based on the -i/-I parameter, this snapshots will be transferred to remote system on next run. --- src/zfs-auto-snapshot.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 6e03521..ab6a512 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -53,6 +53,7 @@ opt_rotation='rr' opt_base='day' opt_namechange='0' opt_factor='1' +opt_limit='3' # if pipe needs to be used, uncomment opt_pipe="|". arcfour or blowfish will reduce cpu load caused by ssh and mbuffer will # boost network bandwidth and mitigate low and high peaks during transfer @@ -1117,6 +1118,22 @@ then exit 301 ;; esac + + if [ -n $opt_limit ]; then + runs='1' + condition='1' + while ( $condition -eq '1' ); do + load=$(eval "$opt_sendtocmd" "uptime") + load=$(echo ${load%%\.*} | awk '{print $10}') + if [ $load -ge $opt_limit -a $runs -lt '3' ]; then + print_log error "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" + sleep 300 + else + condition='0' + fi + runs=$(( $runs + '1' )) + done + fi MOUNTED_LIST_REM=$(eval do_getmountedfs "remote") From 5b18718ce8d5ff5a2e66fe05bed8866e32583b8c Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Fri, 29 Mar 2013 13:14:03 +0100 Subject: [PATCH 12/19] forgot the change to local only snapshoting if remote still under high load and we wasted too much time waiting. --- src/zfs-auto-snapshot.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index ab6a512..bcb917d 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1129,12 +1129,15 @@ then print_log error "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" sleep 300 else + test $load -ge $opt_limit && opt_send="no" condition='0' fi runs=$(( $runs + '1' )) done fi +fi +if [ "$opt_send" != "no" ]; then MOUNTED_LIST_REM=$(eval do_getmountedfs "remote") ZFS_REMOTE_LIST=$(eval "$opt_sendtocmd" zfs list -H -t filesystem,volume -s name -o name) \ From ef052807c68303655bb13efd083eaabca7cd29ba Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 6 Apr 2013 01:57:22 +0200 Subject: [PATCH 13/19] fixes to 5b18718ce8d5ff5a2e66fe05bed8866e32583b8c --- src/zfs-auto-snapshot.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index bcb917d..7a70e33 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1122,17 +1122,18 @@ then if [ -n $opt_limit ]; then runs='1' condition='1' - while ( $condition -eq '1' ); do + while [ $condition -eq '1' ]; do load=$(eval "$opt_sendtocmd" "uptime") - load=$(echo ${load%%\.*} | awk '{print $10}') + load=$(echo ${load##*"load average"}} | awk '{print $2}' | awk -F'.' '{print $1}') if [ $load -ge $opt_limit -a $runs -lt '3' ]; then print_log error "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" sleep 300 else test $load -ge $opt_limit && opt_send="no" + opt_keep='' condition='0' fi - runs=$(( $runs + '1' )) + runs=$(( $runs + 1 )) done fi fi From d7e10be6d5d7ff1ab65f1190cf56ef2fa0d14d28 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 6 Apr 2013 10:05:58 +0200 Subject: [PATCH 14/19] again fixes to 5b18718ce8d5ff5a2e66fe05bed8866e32583b8c --- src/zfs-auto-snapshot.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 7a70e33..99cff43 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1126,11 +1126,14 @@ then load=$(eval "$opt_sendtocmd" "uptime") load=$(echo ${load##*"load average"}} | awk '{print $2}' | awk -F'.' '{print $1}') if [ $load -ge $opt_limit -a $runs -lt '3' ]; then - print_log error "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" + print_log warning "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" sleep 300 else - test $load -ge $opt_limit && opt_send="no" - opt_keep='' + if [ $load -ge $opt_limit ]; then + opt_send="no" + opt_keep='' + print_log warning "Over load limit on remote machine. Will not send to remote. (run #$runs, load still $load)" + fi condition='0' fi runs=$(( $runs + 1 )) From 1e9fbbc2d2bbfcb078a2a936f352ce80a166c8ed Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sun, 7 Apr 2013 01:11:18 +0200 Subject: [PATCH 15/19] a typo correction --- src/zfs-auto-snapshot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 99cff43..cce80e9 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -1124,7 +1124,7 @@ then condition='1' while [ $condition -eq '1' ]; do load=$(eval "$opt_sendtocmd" "uptime") - load=$(echo ${load##*"load average"}} | awk '{print $2}' | awk -F'.' '{print $1}') + load=$(echo ${load##*"load average"} | awk '{print $2}' | awk -F'.' '{print $1}') if [ $load -ge $opt_limit -a $runs -lt '3' ]; then print_log warning "Over load limit on remote machine. Going for sleep for 5 minutes. (run #$runs, load still $load)" sleep 300 From c3ddca4032a6e4d7370b53a31e94b1ab4d59b388 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 13 Apr 2013 03:33:49 +0300 Subject: [PATCH 16/19] Update README --- README | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README b/README index 27d48e1..4a6c959 100644 --- a/README +++ b/README @@ -12,8 +12,9 @@ It can backup (send) the snapshots to remote systems or external disks, utilizing zfs send command. On darwin this can replace TimeMachine backups, currently not running on top of ZFS filesystems. -This program is a posixly correct bourne shell script. It depends on zfs utilities -only (Linux). Unfortunatelly on Darwin it needs 'getopt' from macports. +This program is a posixly correct bourne shell script. It depends on zfs +utilities only (Linux). Unfortunatelly on Darwin it needs 'getopt' from +macports or homebrew. For using --send option, adapt opt_sendtocmd variable accordingly by editing the script zfs-auto-snapshot.sh. From 7b7642c9cc5356e1dce781c544972967a6bcb4c9 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Sat, 13 Apr 2013 09:50:30 +0200 Subject: [PATCH 17/19] bug fix: it could happen that local snapshot was deleted without successful send during hanoi rotation and --send-xxxx. --- src/zfs-auto-snapshot.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index cce80e9..9c166dd 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -416,9 +416,11 @@ delete_rotation_hanoi () } - destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$FSNAME@$SNAPNAME" )" "" "local" "$FSNAME" "$FLAGS" - if [ "$SND_RC" -eq '0' ] && [ "$opt_send" != "no" ]; then - destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" + if [ "$SND_RC" -eq '0' -a "$opt_send" != "no" -o "$opt_send" = "no" ]; then + destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_LOC" "$FSNAME@$SNAPNAME" )" "" "local" "$FSNAME" "$FLAGS" + if [ "$opt_send" != "no" ]; then + destroy "$(printf "%s\n%s\n" "$SNAPSHOTS_OLD_REM" "$opt_sendprefix/$FSNAME@$SNAPNAME" )" "$opt_sendprefix/" "remote" "$FSNAME" "$FLAGS" + fi fi } From 48e168c5b60a0b9318ae8587d5c63b9a83de12b0 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Wed, 8 May 2013 18:54:12 +0200 Subject: [PATCH 18/19] doc update --- src/zfs-auto-snapshot.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index 9c166dd..f84eb59 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -91,7 +91,8 @@ print_usage () { echo "Usage: $0 [options] [-l label] <'//' | name [name...]> - --default-exclude Exclude datasets if com.sun:auto-snapshot is unset. + --default-exclude Exclude datasets if com.sun:auto-snapshot is unset + (not explicitly set to true). --remove-local=n Remove local snapshots after successfully sent via --send-incr or --send-full but still keeps n newest snapshots (this will destroy snapshots named according From 45007b8d3927bf38227d7f771f7ab5ee6beb0346 Mon Sep 17 00:00:00 2001 From: Matus Kral Date: Wed, 8 May 2013 18:57:19 +0200 Subject: [PATCH 19/19] possibility to ad-hoc override =false for com.sun:auto-snapshot (--include-all parameter) --- src/zfs-auto-snapshot.sh | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/zfs-auto-snapshot.sh b/src/zfs-auto-snapshot.sh index f84eb59..fad8b9a 100755 --- a/src/zfs-auto-snapshot.sh +++ b/src/zfs-auto-snapshot.sh @@ -54,6 +54,7 @@ opt_base='day' opt_namechange='0' opt_factor='1' opt_limit='3' +opt_includeall='0' # if pipe needs to be used, uncomment opt_pipe="|". arcfour or blowfish will reduce cpu load caused by ssh and mbuffer will # boost network bandwidth and mitigate low and high peaks during transfer @@ -93,6 +94,8 @@ print_usage () --default-exclude Exclude datasets if com.sun:auto-snapshot is unset (not explicitly set to true). + --include-all Include datasets even if com.sun:auto-snapshot is set + to false. --remove-local=n Remove local snapshots after successfully sent via --send-incr or --send-full but still keeps n newest snapshots (this will destroy snapshots named according @@ -661,7 +664,7 @@ esac GETOPT=$("$getopt_cmd" \ --longoptions=default-exclude,dry-run,skip-scrub,recursive,send-atonce,rotation:,local-only \ --longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback,base:,factor: \ - --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy \ + --longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy,include-all \ --options=dnshe:l:k:p:rs:qgvfixcXFRba:o: \ -- "$@" ) \ || exit 128 @@ -713,6 +716,11 @@ do opt_buffer='' shift 1 ;; + (--include-all) + opt_includeall='1' + print_log debug "Not considering com.sun:auto-snapshot." + shift 1 + ;; (-k|--keep) if ! test "$2" -gt '0' 2>/dev/null then @@ -943,21 +951,25 @@ ZPOOLS_NOTREADY=$(echo "$ZPOOL_STATUS" | awk -F ': ' \ $1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \ | sort ) -# Get a list of datasets for which snapshots are not explicitly disabled. -CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}' ) - # If the --default-exclude flag is set, then exclude all datasets that lack # an explicit com.sun:auto-snapshot* property. Otherwise, include them. -if [ -n "$opt_default_exclude" ] -then - # Get a list of datasets for which snapshots are not explicitly enabled. - NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}') +if [ "$opt_includeall" -eq '0' ]; then + # Get a list of datasets for which snapshots are not explicitly disabled. + CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}' ) + + if [ -n "$opt_default_exclude" ] + then + # Get a list of datasets for which snapshots are not explicitly enabled. + NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}') + else + # Get a list of datasets for which snapshots are explicitly disabled. + NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ + 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') + fi else - # Get a list of datasets for which snapshots are explicitly disabled. - NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ - 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') + CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' '{print $1}') fi # Initialize the list of datasets that will get a recursive snapshot.