19 Commits
master ... zevo

Author SHA1 Message Date
Darik Horn
45edfd3e7c Merge pull request #9 from mk01/zevo
Zevo
2013-01-14 11:05:37 -08:00
Matus Kral
85dd8dad9a missing quotations, under bash ok, under dash critical 2013-01-14 11:13:47 +01:00
Matus Kral
feea013d3c cosmetic bug which showed filesystem added for regular snapshot as recursive 2013-01-14 11:07:54 +01:00
Matus Kral
ad9a57eeaf better snapshot consistency testing during --send-full and -R 2013-01-14 11:06:03 +01:00
Matus Kral
82ff4b2f80 bug fix (wrong recursive filesystem list expansion)
in case of empty filesystem list going for recursive snapshot all
filesystems have been processed
2013-01-14 10:56:49 +01:00
Matus Kral
9bf3d5b017 Revert "bug fix (wrong recursive filesystem list expansion)"
This reverts commit d6c05fb8f4.
2013-01-14 10:37:32 +01:00
Matus Kral
ba171cc032 Revert "bug fix (snapshot list created for destroy in case of incremental send through replication)"
This reverts commit 8b155cf4da.
2013-01-14 10:37:13 +01:00
Matus Kral
8b155cf4da bug fix (snapshot list created for destroy in case of incremental send through replication) 2013-01-14 10:35:32 +01:00
Matus Kral
d6c05fb8f4 bug fix (wrong recursive filesystem list expansion)
in case of empty filesystem list going for recursive snapshot all
filesystems have been processed
2013-01-14 10:35:03 +01:00
Matus Kral
4bbfa476b1 general cleanup, quotations updates etc 2013-01-09 14:17:49 -06:00
Matus Kral
583fc20e28 error return codes renumbering, parameter checking for --send 2013-01-03 14:49:05 -06:00
Matus Kral
2d789bdf1d detection of system (Linux/Darwin). proper detection of mounted snapshots prior to umount.
detection of system applies for local and remote operations. should run
on linux and macosx with no modifications in the code.
2013-01-03 14:49:05 -06:00
Matus Kral
8aaf5f904b issue #8 should be solved by this patch
all filesystems with snapdir=visible and canmount=on are traversed for
snapshots going to be removed and umount -f is called.

on linux, on /proc/mounts such mounted snapshots are visible (not
listed with 'mount'), but under macosx no proc exists, 'mount' again
does not list them so umount -f is called with no mercy for all
specific combination of fs and snapname  (but should not cause any
problems).

there is no specific logic to utilize /proc/mount on linux, same
approach is taken.
2012-12-28 14:14:23 -06:00
Matus Kral
6f91e10033 mktemp on macosx uses some .Xs by default, which is not the case on linux 2012-12-28 14:14:23 -06:00
Matus Kral
1099c622cb comments update 2012-12-27 20:58:22 -06:00
Matus Kral
70632c00b7 going back to standard /bin/sh (bash 3.x on macosx) 2012-12-27 20:58:22 -06:00
Matus Kral
90825db245 README updates 2012-12-27 20:58:22 -06:00
Matus Kral
676ded6217 fix for filesystem expansion when using '//' 2012-12-27 20:58:22 -06:00
Matus Kral
4d20aa4c79 Zevo support. 2012-12-27 20:58:10 -06:00
3 changed files with 531 additions and 92 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.DS_Store

6
README
View File

@@ -1,12 +1,14 @@
zfs-auto-snapshot: zfs-auto-snapshot:
An alternative implementation of the zfs-auto-snapshot service for Linux An alternative implementation of the zfs-auto-snapshot service for Macosx
that is compatible with zfs-linux and zfs-fuse. that is compatible with ZEVO community zfs.
Automatically create, rotate, and destroy periodic ZFS snapshots. This is Automatically create, rotate, and destroy periodic ZFS snapshots. This is
the utility that creates the @zfs-auto-snap_frequent, @zfs-auto-snap_hourly, the 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 @zfs-auto-snap_daily, @zfs-auto-snap_weekly, and @zfs-auto-snap_monthly
snapshots if it is installed. snapshots if it is installed.
It can backup to remote systems utilizing zfs send command.
This program is a posixly correct bourne shell script. It depends only on 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. the zfs utilities and cron, and can run in the dash shell.

View File

@@ -1,8 +1,9 @@
#!/bin/sh #!/bin/bash
# zfs-auto-snapshot for Linux # zfs-auto-snapshot for Linux and Macosx
# Automatically create, rotate, and destroy periodic ZFS snapshots. # Automatically create, rotate, and destroy periodic ZFS snapshots.
# Copyright 2011 Darik Horn <dajhorn@vanadac.com> # Copyright 2011 Darik Horn <dajhorn@vanadac.com>
# zfs send options and macosx relevant changes by Matus Kral <matuskral@me.com>
# #
# This program is free software; you can redistribute it and/or modify it under # 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 # the terms of the GNU General Public License as published by the Free Software
@@ -30,7 +31,7 @@ opt_default_exclude=''
opt_dry_run='' opt_dry_run=''
opt_event='-' opt_event='-'
opt_keep='' opt_keep=''
opt_label='' opt_label='regular'
opt_prefix='zfs-auto-snap' opt_prefix='zfs-auto-snap'
opt_recursive='' opt_recursive=''
opt_sep='_' opt_sep='_'
@@ -38,35 +39,75 @@ opt_setauto=''
opt_syslog='' opt_syslog=''
opt_skip_scrub='' opt_skip_scrub=''
opt_verbose='' opt_verbose=''
opt_remove=''
opt_fallback='0'
opt_force=''
opt_sendprefix=''
opt_send='no'
opt_atonce='-I'
opt_create='0'
opt_destroy='0'
# if pipe needs to be used, uncomment opt_pipe="|"
opt_sendtocmd='ssh -1 root@media -i /var/root/.ssh/media.rsa1'
opt_pipe='|'
# Global summary statistics. # Global summary statistics.
DESTRUCTION_COUNT='0' DESTRUCTION_COUNT='0'
SNAPSHOT_COUNT='0' SNAPSHOT_COUNT='0'
WARNING_COUNT='0' WARNING_COUNT='0'
CREATION_COUNT='0'
SENT_COUNT='0'
KEEP=''
PLATFORM_LOC=''
PLATFORM_REM=''
# Other global variables. # Other global variables.
SNAPSHOTS_OLD='' declare -a SNAPSHOTS_OLD_LOC=('')
declare -a SNAPSHOTS_OLD_REM=('')
declare -a CREATED_TARGETS=('')
declare -a ZFS_REMOTE_LIST=('')
declare -a ZFS_LOCAL_LIST=('')
declare -a TARGETS_DRECURSIVE=('')
declare -a TARGETS_DREGULAR=('')
declare -a MOUNTED_LIST_LOC=('')
declare -a MOUNTED_LIST_REM=('')
declare -i RC=99
tmp_file_prefix="/tmp/zfs-auto-snapshot.XXXXXXXX"
set -o pipefail
print_usage () print_usage ()
{ {
echo "Usage: $0 [options] [-l label] <'//' | name [name...]> 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.
--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).
-d, --debug Print debugging messages. -d, --debug Print debugging messages.
-e, --event=EVENT Set the com.sun:auto-snapshot-desc property to EVENT. -e, --event=EVENT Set the com.sun:auto-snapshot-desc property to EVENT.
-n, --dry-run Print actions without actually doing anything. -n, --dry-run Print actions without actually doing anything.
-s, --skip-scrub Do not snapshot filesystems in scrubbing pools. -s, --skip-scrub Do not snapshot filesystems in scrubbing pools.
-h, --help Print this usage message. -h, --help Print this usage message.
-k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots. -k, --keep=NUM Keep NUM recent snapshots and destroy older snapshots.
-l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly'. -l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly' (default is 'regular').
-p, --prefix=PRE PRE is 'zfs-auto-snap' by default. -p, --prefix=PRE PRE is 'zfs-auto-snap' by default.
-q, --quiet Suppress warnings and notices at the console. -q, --quiet Suppress warnings and notices at the console.
--send-full=F Send zfs full backup. Unimplemented. -c, --create Create missing filesystems at destination.
--send-incr=F Send zfs incremental backup. Unimplemented. -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. --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. -g, --syslog Write messages into the system log.
-r, --recursive Snapshot named filesystem and all descendants. -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. -v, --verbose Print info messages.
-f, --force Passes -F argument to zfs receive (e.g. makes possible to overwrite remote filesystem during --send-full)
name Filesystem and volume names, or '//' for all ZFS datasets. name Filesystem and volume names, or '//' for all ZFS datasets.
" "
} }
@@ -97,6 +138,7 @@ print_log () # level, message, ...
(war*) (war*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning $* test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.warning $*
test -z "$opt_quiet" && echo Warning: $* 1>&2 test -z "$opt_quiet" && echo Warning: $* 1>&2
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
;; ;;
(not*) (not*)
test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice $* test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.notice $*
@@ -120,13 +162,15 @@ print_log () # level, message, ...
do_run () # [argv] do_run () # [argv]
{ {
if [ -n "$opt_dry_run" ] if [ -n "$opt_dry_run" ]
then then
echo $* print_log notice "... running: $*"
RC="$?" RC="$?"
else else
eval $* eval $*
RC="$?" RC="$?"
if [ "$RC" -eq '0' ] if [ "$RC" -eq '0' ]
then then
print_log debug "$*" print_log debug "$*"
@@ -134,69 +178,345 @@ do_run () # [argv]
print_log warning "$* returned $RC" print_log warning "$* returned $RC"
fi fi
fi fi
return "$RC" 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\t%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\t%s\n" "${umount_list[@]}" | awk -F'\t' '{print $2}'))
test $rsort_cmd -eq '1' && umount_list=($(printf "%s\n" "${umount_list[@]}"))
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"
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"
declare -i ISMEMBER=0
result=$(printf "%s\n" "${ARRAY[@]}"| grep -m1 -x "$MEMBER")
if [ -n "$result" -a -z "${result#$MEMBER}" ]; then ISMEMBER=1; fi
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=( ${list_child[@]} $(printf "%s\n" "${SNAPSHOTS_OLD_REM[@]}" | grep ^"$REMOTEFS@" ) )
fi
for ll in ${list_child[@]}; do
if do_delete "remote" "$ll" ""; then
continue
fi
print_log debug "Can't destroy remote filesystem $REMOTEFS ($ll). Can't continue with send-full."
return 1
done
test $SENDTYPE = "incr" && do_run "zfs send " "$SENDFLAGS" "$opt_atonce $SNAPFROM $SNAPTO" "$opt_pipe" "$opt_sendtocmd" "zfs recv $opt_force -u $REMOTEFS"
test $SENDTYPE = "full" && do_run "zfs send " "$SENDFLAGS" "$SNAPTO" "$opt_pipe" "$opt_sendtocmd" "zfs recv $opt_force -u $REMOTEFS"
return "$RC"
}
do_snapshots () # properties, flags, snapname, oldglob, [targets...] do_snapshots () # properties, flags, snapname, oldglob, [targets...]
{ {
local PROPS="$1" local PROPS="$1"
local FLAGS="$2" local sFLAGS="$2"
local NAME="$3" local NAME="$3"
local GLOB="$4" local GLOB="$4"
local TARGETS="$5" local TARGETS=(${5})
local KEEP='' local KEEP=''
local LAST_REMOTE=''
# global DESTRUCTION_COUNT if test "$sFLAGS" = '-R'; then
# global SNAPSHOT_COUNT FLAGS='-r'
# global WARNING_COUNT SNexp='.*'
# global SNAPSHOTS_OLD else
FLAGS=''
fi
for ii in $TARGETS for ii in ${TARGETS[@]}
do 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 then
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
continue continue
fi fi
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 ))
if [ "$opt_send" = "incr" ]
then
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_LIST_LOC[@]} | grep ^"$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
# no snapshot on remote
FALLBACK='1'
elif [ "$snaps_needed" -ne $(printf "%s\n" ${SNAPSHOTS_OLD_REM[@]} | grep -c -e ^"$opt_sendprefix/$ii$SNexp@$LAST_REMOTE" ) -o \
"$snaps_needed" -ne $(printf "%s\n" ${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
print_log info "Going back to full send, last snapshot on remote is not available on local: $opt_sendprefix/$ii@$LAST_REMOTE"
fi
;;
(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 ))
# Retain at most $opt_keep number of old snapshots of this filesystem, # Retain at most $opt_keep number of old snapshots of this filesystem,
# including the one that was just recently created. # including the one that was just recently created.
test -z "$opt_keep" && continue if [ -z "$opt_keep" ]
KEEP="$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. # ASSERT: The old snapshot list is sorted by increasing age.
for jj in $SNAPSHOTS_OLD for jj in ${SNAPSHOTS_OLD_LOC[@]}
do do
# Check whether this is an old snapshot of the filesystem. # Check whether this is an old snapshot of the filesystem.
if [ -z "${jj#$ii@$GLOB}" ] test -z "${jj#$ii@$GLOB}" -o -z "${jj##$ii@$opt_prefix*}" -a -n "$opt_remove" \
then -a "$opt_send" != "no" && do_delete "local" "$jj" "$FLAGS"
KEEP=$(( $KEEP - 1 ))
if [ "$KEEP" -le '0' ]
then
if do_run "zfs destroy $FLAGS '$jj'"
then
DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT + 1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
fi
fi
done 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 only $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/$ii@$GLOB}" && do_delete "remote" "$jj" "$FLAGS"
done
done 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)
remote_cmd=""
PLATFORM="$PLATFORM_LOC"
;;
esac
case "$PLATFORM" in
(Linux)
MOUNTED_LIST=( $(eval $remote_cmd cat /proc/mounts | grep zfs | awk -F' ' '{OFS="\t"}{print $1,$2}' ) )
;;
(Darwin)
MOUNTED_LIST=( $(eval $remote_cmd zfs mount | awk -F' ' '{OFS="\t"}{print $1,$2}') $(eval $remote_cmd mount -t zfs | grep @ | awk -F' ' '{OFS="\t"}{print $1,$3}') )
;;
esac
printf "%s\t%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=( ${CREATED_TARGETS[@]} "$opt_sendprefix/$ii" )
fi
fi
done
}
# main () # main ()
# { # {
GETOPT=$(getopt \ PLATFORM_LOC=`uname`
--longoptions=default-exclude,dry-run,skip-scrub,recursive \ case "$PLATFORM_LOC" in
--longoptions=event:,keep:,label:,prefix:,sep: \ (Linux)
--longoptions=debug,help,quiet,syslog,verbose \ getopt_cmd='getopt'
--options=dnshe:l:k:p:rs:qgv \ ;;
(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 \
--longoptions=event:,keep:,label:,prefix:,sep:,create,fallback,rollback \
--longoptions=debug,help,quiet,syslog,verbose,send-full:,send-incr:,remove-local:,destroy \
--options=dnshe:l:k:p:rs:qgvfixcXFRb \
-- "$@" ) \ -- "$@" ) \
|| exit 128 || exit 128
@@ -211,7 +531,11 @@ do
opt_verbose='1' opt_verbose='1'
shift 1 shift 1
;; ;;
(--default-exclude) (-c|--create)
opt_create='1'
shift 1
;;
(-x|--default-exclude)
opt_default_exclude='1' opt_default_exclude='1'
shift 1 shift 1
;; ;;
@@ -219,7 +543,7 @@ do
if [ "${#2}" -gt '1024' ] if [ "${#2}" -gt '1024' ]
then then
print_log error "The $1 parameter must be less than 1025 characters." print_log error "The $1 parameter must be less than 1025 characters."
exit 139 exit 239
elif [ "${#2}" -gt '0' ] elif [ "${#2}" -gt '0' ]
then then
opt_event="$2" opt_event="$2"
@@ -242,7 +566,7 @@ do
if ! test "$2" -gt '0' 2>/dev/null if ! test "$2" -gt '0' 2>/dev/null
then then
print_log error "The $1 parameter must be a positive integer." print_log error "The $1 parameter must be a positive integer."
exit 129 exit 229
fi fi
opt_keep="$2" opt_keep="$2"
shift 2 shift 2
@@ -258,7 +582,7 @@ do
case $opt_prefix in case $opt_prefix in
([![:alnum:]_.:\ -]*) ([![:alnum:]_.:\ -]*)
print_log error "The $1 parameter must be alphanumeric." print_log error "The $1 parameter must be alphanumeric."
exit 130 exit 230
;; ;;
esac esac
opt_prefix="${opt_prefix#?}" opt_prefix="${opt_prefix#?}"
@@ -273,7 +597,20 @@ do
shift 1 shift 1
;; ;;
(-r|--recursive) (-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 shift 1
;; ;;
(--sep) (--sep)
@@ -283,25 +620,69 @@ do
;; ;;
('') ('')
print_log error "The $1 parameter must be non-empty." 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." print_log error "The $1 parameter must be one alphanumeric character."
exit 132 exit 232
;; ;;
esac esac
opt_sep="$2" opt_sep="$2"
shift 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) (-g|--syslog)
opt_syslog='1' opt_syslog='1'
shift 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) (-v|--verbose)
opt_quiet='' opt_quiet=''
opt_verbose='1' opt_verbose='1'
shift 1 shift 1
;; ;;
(-f|--force|-b|--rollback)
opt_force='-F'
shift 1
;;
(--) (--)
shift 1 shift 1
break break
@@ -315,6 +696,13 @@ then
exit 133 exit 133
fi fi
if [ -f "${tmp_file_prefix%%X*}"* ]; then
print_log error "another copy is running ..."
exit 99
fi
LOCKFILE=$(mktemp $tmp_file_prefix)
trap "rm -f '$LOCKFILE'; exit $?" INT TERM EXIT
# Count the number of times '//' appears on the command line. # Count the number of times '//' appears on the command line.
SLASHIES='0' SLASHIES='0'
for ii in "$@" for ii in "$@"
@@ -336,12 +724,10 @@ ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 ) \
|| { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 135; } || { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 135; }
ZFS_LIST=$(env LC_ALL=C zfs list -H -t filesystem,volume -s name \ 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") \ -o name,com.sun:auto-snapshot,com.sun:auto-snapshot:"$opt_label",mountpoint,canmount,snapdir) \
|| { print_log error "zfs list $?: $ZFS_LIST"; exit 136; } || { print_log error "zfs list $?: $ZFS_LIST"; exit 136; }
SNAPSHOTS_OLD=$(env LC_ALL=C zfs list -H -t snapshot -S creation -o name) \ ZFS_LOCAL_LIST=($(echo "$ZFS_LIST" | awk -F'\t' '{ORS="\n"}{print $1}'))
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 137; }
# Verify that each argument is a filesystem or volume. # Verify that each argument is a filesystem or volume.
for ii in "$@" for ii in "$@"
@@ -369,28 +755,29 @@ ZPOOLS_NOTREADY=$(echo "$ZPOOL_STATUS" | awk -F ': ' \
$1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \ $1 ~ /^ *state$/ && $2 !~ /ONLINE|DEGRADED/ { print pool } ' \
| sort) | sort)
# Get a list of datasets for which snapshots are explicitly disabled. # Get a list of datasets for which snapshots are not explicitly disabled.
NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \
'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}') 'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}' )
# If the --default-exclude flag is set, then exclude all datasets that lack # If the --default-exclude flag is set, then exclude all datasets that lack
# an explicit com.sun:auto-snapshot* property. Otherwise, include them. # an explicit com.sun:auto-snapshot* property. Otherwise, include them.
if [ -n "$opt_default_exclude" ] if [ -n "$opt_default_exclude" ]
then then
# Get a list of datasets for which snapshots are explicitly enabled. # Get a list of datasets for which snapshots are not explicitly enabled.
CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \
'tolower($2) ~ /true/ || tolower($3) ~ /true/ {print $1}') 'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}')
else else
# Invert the NOAUTO list. # Get a list of datasets for which snapshots are explicitly disabled.
CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \ NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \
'tolower($2) !~ /false/ && tolower($3) !~ /false/ {print $1}') 'tolower($2) ~ /false/ || tolower($3) ~ /false/ {print $1}')
fi fi
# Initialize the list of datasets that will get a recursive snapshot. # Initialize the list of datasets that will get a recursive snapshot.
TARGETS_RECURSIVE='' declare -a TARGETS_DRECURSIVE=('')
declare -a TARGETS_TMP_RECURSIVE=('')
# Initialize the list of datasets that will get a non-recursive snapshot. # Initialize the list of datasets that will get a non-recursive snapshot.
TARGETS_REGULAR='' declare -a TARGETS_DREGULAR=('')
for ii in $CANDIDATES for ii in $CANDIDATES
do do
@@ -403,7 +790,7 @@ do
IN_ARGS='0' IN_ARGS='0'
for jj in "$@" for jj in "$@"
do do
if [ "$jj" = '//' -o "$jj" = "$ii" ] if [ "$jj" = '//' -o "$jj" = "$ii" -o -n "$opt_recursive" -a -z "${ii##$jj/*}" ]
then then
IN_ARGS=$(( $IN_ARGS + 1 )) IN_ARGS=$(( $IN_ARGS + 1 ))
fi fi
@@ -441,29 +828,31 @@ do
fi fi
done done
for jj in $NOAUTO noauto_parent='0'
for jj in $NOAUTO
do do
# Ibid regarding iii. # Ibid regarding iii.
jjj="$jj/" jjj="$jj/"
# The --recursive switch only matters for non-wild arguments. if [ "$jjj" = "$iii" ]
if [ -z "$opt_recursive" -a "$1" != '//' ] then
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 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}" ] elif [ "$jjj" != "${jjj#$iii}" ]
then then
# Snapshot this dataset non-recursively. noauto_parent='1' && break
print_log debug "Including $ii for regular snapshot." fi
TARGETS_REGULAR="${TARGETS_REGULAR:+$TARGETS_REGULAR }$ii" # nb: \t
continue 2
fi
done 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=( ${TARGETS_DREGULAR[@]} $( printf "%s\n" $ii))
continue
fi
for jj in ${TARGETS_TMP_RECURSIVE[@]}
do do
# Ibid regarding iii. # Ibid regarding iii.
jjj="$jj/" jjj="$jj/"
@@ -484,7 +873,8 @@ do
# * Is not the descendant of an already included filesystem. # * Is not the descendant of an already included filesystem.
# #
print_log debug "Including $ii for recursive snapshot." print_log debug "Including $ii for recursive snapshot."
TARGETS_RECURSIVE="${TARGETS_RECURSIVE:+$TARGETS_RECURSIVE }$ii" # nb: \t TARGETS_TMP_RECURSIVE=( ${TARGETS_TMP_RECURSIVE[@]} $( printf "%s\n" $ii) )
done done
# Linux lacks SMF and the notion of an FMRI event, but always set this property # Linux lacks SMF and the notion of an FMRI event, but always set this property
@@ -501,21 +891,66 @@ SNAPNAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}"
# The expression for matching old snapshots. -YYYY-MM-DD-HHMM # The expression for matching old snapshots. -YYYY-MM-DD-HHMM
SNAPGLOB="$opt_prefix${opt_label:+?$opt_label}????????????????" SNAPGLOB="$opt_prefix${opt_label:+?$opt_label}????????????????"
test -n "$TARGETS_REGULAR" \ if test -n "$TARGETS_DREGULAR"; then
&& print_log info "Doing regular snapshots of $TARGETS_REGULAR" print_log info "Doing regular snapshots of ${TARGETS_DREGULAR[@]}"
SNAPSHOTS_OLD_LOC=( $(eval zfs list -r -d 1 -H -t snapshot -S creation -o name $(printf "%s " ${TARGETS_DREGULAR[@]}) ))
fi
test -n "$TARGETS_RECURSIVE" \ if test -n "$TARGETS_TMP_RECURSIVE"; then
&& print_log info "Doing recursive snapshots of $TARGETS_RECURSIVE" print_log info "Doing recursive snapshots of ${TARGETS_TMP_RECURSIVE[@]}"
SNAPSHOTS_OLD_LOC=( ${SNAPSHOTS_OLD_LOC[@]} $(eval zfs list -r -H -t snapshot -S creation -o name $(printf "%s " ${TARGETS_TMP_RECURSIVE[@]} ) )) \
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD_LOC"; exit 137; }
fi
test -n "$opt_dry_run" \ 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..."
do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_REGULAR" # expand FS list if replication is not used
do_snapshots "$SNAPPROP" "-r" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_RECURSIVE" if [ "$opt_recursive" = ' ' -o "$1" = "//" ]
then
for ii in ${TARGETS_TMP_RECURSIVE[@]}; do TARGETS_DRECURSIVE=( ${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_DREGULAR[*]}"
do_snapshots "$SNAPPROP" "$opt_recursive" "$SNAPNAME" "$SNAPGLOB" "${TARGETS_DRECURSIVE[*]}"
print_log notice "@$SNAPNAME," \ print_log notice "@$SNAPNAME," \
"$SNAPSHOT_COUNT created," \ "$SNAPSHOT_COUNT created snapshots," \
"$SENT_COUNT sent snapshots," \
"$DESTRUCTION_COUNT destroyed," \ "$DESTRUCTION_COUNT destroyed," \
"$CREATION_COUNT created filesystems," \
"$WARNING_COUNT warnings." "$WARNING_COUNT warnings."
exit 0 exit 0