11 Commits

Author SHA1 Message Date
74359e51a7 Fix the hourly-daily transposition in the Makefile.
The hourly cron job was installed to cron.daily, and the daily cron
job was installed to cron.hourly, which caused incorrect scheduling.
2011-11-28 00:07:30 -06:00
afdae86271 Remove redundant lines in the cron.d file. 2011-11-28 00:05:43 -06:00
4b7609791b Split the cron file for anacron compatibilty.
Split the cron file so that the hourly, daily, weekly, and monthly
zfs automatic snapshots still happen even if the system is offline
temporarily during the scheduled event, such as during a reboot.

On Debian systems, if anacron is installed, then it is used to run
the /etc/cron.{hourly,daily,weekly,monthly} directories, but not
the /etc/cron.d directory. This means that /etc/cron.d jobs are not
run if the system is offline when crond would usually invoke them.
2011-11-25 14:26:08 -06:00
5ce7a23384 Implement the --event option for :auto-snap-desc.
Set the com.sun:auto-snap-desc property on each snapshot to an
arbitrary value.  On Solaris, this property is set to the dash
character by default -- which is convention for NULL -- but it
can contain an FMRI status comment or other user data.
2011-11-23 10:44:32 -06:00
2e26499f6a Rebase exit codes to above 127.
Most standard shell utilities return errors less than 128. Rebase the
exit codes of this script to reduce the liklihood of overlap.
2011-11-23 10:03:56 -06:00
f7ceb28963 Implement --prefix parameter checking.
Also add -p to the getopt list of short options.
2011-11-23 10:00:24 -06:00
4c14da4130 Consistently quote literal strings.
Apply some syntax hygiene. Quoting everything is a safe habit because
unquoted things can have subtle side-effects.
2011-11-22 23:36:51 -06:00
249e6a4cb3 Fold the main loops into a do_snapshots function.
The two primary `for` loops were identical except for the '-r' flag.
2011-11-22 23:27:24 -06:00
a0a43a42af Invert the --skip-scrub test.
The $opt_skip_scrub option was tested incorrectly, which caused
the --skip-scrub option to operate opposite intent.
2011-11-22 22:09:23 -06:00
d8b1730015 Add an explicit exit 0 to the end of the script.
The implicit return is "$?", which can be set non-zero by
an earlier command.
2011-11-21 21:44:37 -06:00
adebbd6fee Add -q to the getopt list of short options.
This corresponds to the --quiet option.
2011-11-21 21:40:04 -06:00
8 changed files with 148 additions and 125 deletions

View File

@ -2,6 +2,14 @@ all:
install:
install -d $(DESTDIR)$(PREFIX)/etc/cron.d
install etc/zfs-auto-snapshot.cron $(DESTDIR)$(PREFIX)/etc/cron.d/zfs-auto-snapshot
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

View File

@ -1,7 +0,0 @@
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
*/15 * * * * root zfs-auto-snapshot -q -g --label=frequent --keep=4 //
@hourly root zfs-auto-snapshot -q -g --label=hourly --keep=24 //
@daily root zfs-auto-snapshot -q -g --label=daily --keep=31 //
@weekly root zfs-auto-snapshot -q -g --label=weekly --keep=4 //
@monthly root zfs-auto-snapshot -q -g --label=monthly --keep=12 //

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec zfs-auto-snapshot --quiet --syslog --label=daily --keep=31 //

View File

@ -0,0 +1,3 @@
PATH="/usr/bin:/bin:/usr/sbin:/sbin"
*/15 * * * * root zfs-auto-snapshot -q -g --label=frequent --keep=4 //

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec zfs-auto-snapshot --quiet --syslog --label=hourly --keep=24 //

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec zfs-auto-snapshot --quiet --syslog --label=monthly --keep=12 //

View File

@ -0,0 +1,2 @@
#!/bin/sh
exec zfs-auto-snapshot --quiet --syslog --label=weekly --keep=4 //

View File

@ -28,6 +28,7 @@ opt_backup_full=''
opt_backup_incremental=''
opt_default_exclude=''
opt_dry_run=''
opt_event='-'
opt_keep=''
opt_label=''
opt_prefix='zfs-auto-snap'
@ -38,12 +39,21 @@ opt_syslog=''
opt_skip_scrub=''
opt_verbose=''
# Global summary statistics.
DESTRUCTION_COUNT='0'
SNAPSHOT_COUNT='0'
WARNING_COUNT='0'
# Other global variables.
SNAPSHOTS_OLD=''
print_usage ()
{
echo "Usage: $0 [options] [-l label] <'//' | name [name...]>
--default-exclude Exclude objects 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.
@ -108,7 +118,7 @@ print_log () # level, message, ...
}
do_run ()
do_run () # [argv]
{
if [ -n "$opt_dry_run" ]
then
@ -117,7 +127,7 @@ do_run ()
else
eval $*
RC="$?"
if [ "$RC" -eq 0 ]
if [ "$RC" -eq '0' ]
then
print_log debug "$*"
else
@ -127,40 +137,101 @@ do_run ()
return "$RC"
}
do_snapshots () # properties, flags, snapname, oldglob, [targets...]
{
local PROPS="$1"
local FLAGS="$2"
local NAME="$3"
local GLOB="$4"
local TARGETS="$5"
local KEEP=''
# global DESTRUCTION_COUNT
# global SNAPSHOT_COUNT
# global WARNING_COUNT
# global SNAPSHOTS_OLD
for ii in $TARGETS
do
if do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'"
then
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT + 1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
continue
fi
# 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"
# 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}" ]
then
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
}
# main ()
# {
DATE=$(date +%F-%H%M)
GETOPT=$(getopt \
--longoptions=default-exclude,dry-run,skip-scrub,recursive \
--longoptions=keep:,label:,prefix:,sep: \
--longoptions=event:,keep:,label:,prefix:,sep: \
--longoptions=debug,help,quiet,syslog,verbose \
--options=dnshl:k:rs:gv \
--options=dnshe:l:k:p:rs:qgv \
-- "$@" ) \
|| exit 1
|| exit 128
eval set -- "$GETOPT"
while [ "$#" -gt 0 ]
while [ "$#" -gt '0' ]
do
case "$1" in
(-d|--debug)
opt_debug=1
opt_debug='1'
opt_quiet=''
opt_verbose=1
opt_verbose='1'
shift 1
;;
(--default-exclude)
opt_default_exclude='1'
shift 1
;;
(-e|--event)
if [ "${#2}" -gt '1024' ]
then
print_log error "The $1 parameter must be less than 1025 characters."
exit 139
elif [ "${#2}" -gt '0' ]
then
opt_event="$2"
fi
shift 2
;;
(-n|--dry-run)
opt_dry_run='1'
shift 1
;;
(-s|--skip-scrub)
opt_skip_scrub=1
opt_skip_scrub='1'
shift 1
;;
(-h|--help)
@ -168,10 +239,10 @@ do
exit 0
;;
(-k|--keep)
if ! test "$2" -gt 0 2>/dev/null
if ! test "$2" -gt '0' 2>/dev/null
then
print_log error "The $1 parameter must be a positive integer."
exit 2
exit 129
fi
opt_keep="$2"
shift 2
@ -181,8 +252,19 @@ do
shift 2
;;
(-p|--prefix)
# @TODO: Parameter validation. See --sep below for the regex.
opt_prefix="$2"
while test "${#opt_prefix}" -gt '0'
do
case $opt_prefix in
([![:alnum:]_-.:\ ]*)
print_log error "The $1 parameter must be alphanumeric."
exit 130
;;
esac
opt_prefix="${opt_prefix#?}"
done
opt_prefix="$2"
shift 2
;;
(-q|--quiet)
opt_debug=''
@ -191,7 +273,7 @@ do
shift 1
;;
(-r|--recursive)
opt_recursive=1
opt_recursive='1'
shift 1
;;
(--sep)
@ -201,23 +283,23 @@ do
;;
('')
print_log error "The $1 parameter must be non-empty."
exit 3
exit 131
;;
(*)
print_log error "The $1 parameter must be one alphanumeric character."
exit 4
;;
exit 132
;;
esac
opt_sep="$2"
shift 2
;;
(-g|--syslog)
opt_syslog=1
opt_syslog='1'
shift 1
;;
(-v|--verbose)
opt_quiet=''
opt_verbose=1
opt_verbose='1'
shift 1
;;
(--)
@ -227,10 +309,10 @@ do
esac
done
if [ "$#" -eq 0 ]
if [ "$#" -eq '0' ]
then
print_log error "The filesystem argument list is empty."
exit 5
exit 133
fi
# Count the number of times '//' appears on the command line.
@ -240,10 +322,10 @@ do
test "$ii" = '//' && SLASHIES=$(( $SLASHIES + 1 ))
done
if [ "$#" -gt 1 -a "$SLASHIES" -gt 0 ]
if [ "$#" -gt '1' -a "$SLASHIES" -gt '0' ]
then
print_log error "The // must be the only argument if it is given."
exit 6
exit 134
fi
# These are the only times that `zpool status` or `zfs list` are invoked, so
@ -251,14 +333,14 @@ fi
# Solaris implementation.
ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 ) \
|| { print_log error "zpool status $?: $ZPOOL_STATUS"; exit 7; }
|| { 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 8; }
|| { print_log error "zfs list $?: $ZFS_LIST"; exit 136; }
SNAPSHOTS_OLD=$(env LC_ALL=C zfs list -H -t snapshot -S creation -o name) \
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 9; }
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 137; }
# Verify that each argument is a filesystem or volume.
@ -272,7 +354,7 @@ do
$ZFS_LIST
HERE
print_log error "$ii is not a ZFS filesystem or volume."
exit 10
exit 138
done
# Get a list of pools that are being scrubbed.
@ -346,7 +428,7 @@ do
done
# Exclude objects in scrubbing pools if the --skip-scrub flag is set.
test -z "$opt_skip_scrub" && for jj in $ZPOOLS_SCRUBBING
test -n "$opt_skip_scrub" && for jj in $ZPOOLS_SCRUBBING
do
# Ibid regarding iii.
jjj="$jj/"
@ -405,20 +487,19 @@ do
TARGETS_RECURSIVE="${TARGETS_RECURSIVE:+$TARGETS_RECURSIVE }$ii" # nb: \t
done
# Summary statistics.
DESTRUCTION_COUNT='0'
SNAPSHOT_COUNT='0'
WARNING_COUNT='0'
# Linux lacks SMF and the notion of an FMRI event, but always set this property
# because the SUNW program does. The dash character is the default.
SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event'"
# Linux lacks SMF and the notion of an FMRI event.
FMRI_EVENT='-'
# ISO style date; fifteen characters: YYYY-MM-DD-HHMM
# On Solaris %H%M expands to 12h34.
DATE=$(date +%F-%H%M)
# Create the snapshot using these arguments.
SNAPSHOT_PROPERTIES="-o com.sun:auto-snapshot-desc='$FMRI_EVENT'"
SNAPSHOT_NAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}"
# The snapshot name after the @ symbol.
SNAPNAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}"
# The expression for old snapshots. -YYYY-MM-DD-HHMM
SNAPSHOT_MATCH="$opt_prefix${opt_label:+?$opt_label}????????????????"
# 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"
@ -429,83 +510,13 @@ test -n "$TARGETS_RECURSIVE" \
test -n "$opt_dry_run" \
&& print_log info "Doing a dry run. Not running these commands..."
do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_REGULAR"
do_snapshots "$SNAPPROP" "-r" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_RECURSIVE"
for ii in $TARGETS_REGULAR
do
if do_run "zfs snapshot $SNAPSHOT_PROPERTIES '$ii@$SNAPSHOT_NAME'"
then
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT +1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT +1 ))
continue
fi
# 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
continue
fi
KEEP="$opt_keep"
for jj in $SNAPSHOTS_OLD
do
# Check whether this is an old snapshot of the filesystem.
if [ -z "${jj#$ii@$SNAPSHOT_MATCH}" ]
then
KEEP=$(( $KEEP - 1 ))
if [ "$KEEP" -le 0 ]
then
if do_run "zfs destroy '$jj'"
then
DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT +1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
fi
fi
done
done
for ii in $TARGETS_RECURSIVE
do
if do_run "zfs snapshot $SNAPSHOT_PROPERTIES -r '$ii@$SNAPSHOT_NAME'"
then
SNAPSHOT_COUNT=$(( $SNAPSHOT_COUNT +1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT +1 ))
continue
fi
# 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
continue
fi
KEEP="$opt_keep"
# 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@$SNAPSHOT_MATCH}" ]
then
KEEP=$(( $KEEP - 1 ))
if [ "$KEEP" -le 0 ]
then
if do_run "zfs destroy -r '$jj'"
then
DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT +1 ))
else
WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
fi
fi
fi
done
done
print_log notice "@$SNAPSHOT_NAME, \
$SNAPSHOT_COUNT created, $DESTRUCTION_COUNT destroyed, $WARNING_COUNT warnings."
print_log notice "@$SNAPNAME," \
"$SNAPSHOT_COUNT created," \
"$DESTRUCTION_COUNT destroyed," \
"$WARNING_COUNT warnings."
exit 0
# }