15 Commits

Author SHA1 Message Date
2940f443bc Merge pull request #99 from stuehmer/patch-2
Merge changes fom master and add workaround for DASH shell problems
2019-04-12 22:47:04 +02:00
2d6bf078b1 Avoid syntax error in dash by specifically using bash
Error was:
`/usr/sbin/zfs-auto-snapshot: 207: /usr/sbin/zfs-auto-snapshot: Syntax error: redirection unexpected`
2018-10-22 17:21:22 +02:00
f44d47fb4d Merge changes from master into leecallan 2018-10-22 17:11:52 +02:00
87aec9039a Merge branch 'master' into leecallen
Conflicts:
	src/zfs-auto-snapshot.sh
2014-04-16 16:15:01 -04:00
d625c53af1 Merge pull request #25 from stuehmer/patch-1
Fix SNAPNAME to contain the DATE if label is empty
2014-04-16 10:45:48 -04:00
dc6f5ddcd9 Fix SNAPNAME to contain the DATE if label is empty
When I didn't specify a `--label` on the commandline, the SNAPNAME was missing the $DATE component
2014-04-16 13:47:29 +02:00
087ac3eba2 Update zfs-auto-snapshot.sh 2014-02-10 17:20:22 -05:00
33d53d8cec Update zfs-auto-snapshot.sh
Remove 'label' from snapshot name and place in a snapshot property
2014-02-04 09:59:58 -05:00
39bce83e0f Merge pull request #18 from hawkowl/master
Set zfs-auto-snapshot to use UTC time for snapshots
2013-06-24 14:19:33 -07:00
6ea2d7f5b1 Set the snapshots to use UTC time, which makes the date/time display correctly in Windows 7 Previous Versions. 2013-06-24 21:48:47 +08:00
dd27aa1c56 Update Makefile for the manual page 2013-06-17 08:23:57 -05:00
f5fc21ace0 Add a manpage 2013-06-16 15:31:33 -07:00
bf4e97b07d Add a switch for the fast zfs list invocation.
Implement a `--fast` switch that uses the optimized `zfs list`
invocation instead of the slower regular form.

If the optimized form is generally better, then use it as the
default and reverse the switch to `--slow` sometime later.

Issue: zfsonlinux/zfs-auto-snapshot#16
2013-05-08 19:36:44 -05:00
53ad1dc042 Merge pull request #15 from tisoft/patch-1
Use only name property for zfs list
2013-05-08 15:33:31 -07:00
b6fba51643 Use only name property for zfs list
zfs list is very slow if it needs to retrieve other properties than name. See zfsonlinux/zfs#450 for reference.

This change uses only the name of the snapshot and uses awk to extract the snapshot date from the name and sort to sort it:

1. get all snapshot names, sorted by name
2. use grep to filter out any snapshot, that isn't correctly prefixed (maybe a check for the correct date format could also be added here)
3. use awk to extract the date and put the date before the snapshot
4. use sort to reverse sort this, first by date, then by snapshot name
5. use awk to remove the date, so that just the snapshot name is left

This significally speeds it up on my system (/root/zfs-auto-snapshot is the changed one, running with -n to get only the times for snapshot retrieval):

root@tatooine:/root# time /root/zfs-auto-snapshot -d -v // --label=frequent --keep=4 -n
Debug: Including rpool for regular snapshot.
Debug: Including rpool/DATA for regular snapshot.
Debug: Including rpool/DATA/ldap for recursive snapshot.
Debug: Including rpool/DATA/postgresql for recursive snapshot.
Debug: Including rpool/DATA/vm for regular snapshot.
Debug: Including rpool/DATA/vm/alderaan.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/bespin.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/coruscant.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dagobah.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dantooine.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dev.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/monitor.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/office.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/test.example.net for recursive snapshot.
Debug: Including rpool/ROOT for recursive snapshot.
Debug: Excluding rpool/ROOT/ubuntu-1 because rpool/ROOT includes it recursively.
Doing regular snapshots of rpool rpool/DATA rpool/DATA/vm
Doing recursive snapshots of rpool/DATA/ldap rpool/DATA/postgresql rpool/DATA/vm/alderaan.example.net rpool/DATA/vm/bespin.example.net rpool/DATA/vm/coruscant.example.net rpool/DATA/vm/dagobah.example.net rpool/DATA/vm/dantooine.example.net rpool/DATA/vm/dev.example.net rpool/DATA/vm/monitor.example.net rpool/DATA/vm/office.example.net rpool/DATA/vm/test.example.net rpool/ROOT
Doing a dry run. Not running these commands...
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy  'rpool@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool/DATA@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy  'rpool/DATA@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool/DATA/vm@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy  'rpool/DATA/vm@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/ldap@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/ldap@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/postgresql@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/postgresql@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/alderaan.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/alderaan.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/bespin.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/bespin.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/coruscant.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/coruscant.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dagobah.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/dagobah.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dantooine.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/dantooine.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dev.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/dev.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/monitor.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/monitor.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/office.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/office.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/test.example.net@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/DATA/vm/test.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/ROOT@zfs-auto-snap_frequent-2013-04-16-2344'
zfs destroy -r 'rpool/ROOT@zfs-auto-snap_frequent-2013-04-16-2315'
@zfs-auto-snap_frequent-2013-04-16-2344, 15 created, 15 destroyed, 0 warnings.

real	0m6.936s
user	0m0.076s
sys	0m0.184s
root@tatooine:/root# time /sbin/zfs-auto-snapshot -d -v // --label=frequent --keep=4 -n
Debug: Including rpool for regular snapshot.
Debug: Including rpool/DATA for regular snapshot.
Debug: Including rpool/DATA/ldap for recursive snapshot.
Debug: Including rpool/DATA/postgresql for recursive snapshot.
Debug: Including rpool/DATA/vm for regular snapshot.
Debug: Including rpool/DATA/vm/alderaan.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/bespin.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/coruscant.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dagobah.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dantooine.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/dev.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/monitor.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/office.example.net for recursive snapshot.
Debug: Including rpool/DATA/vm/test.example.net for recursive snapshot.
Debug: Including rpool/ROOT for recursive snapshot.
Debug: Excluding rpool/ROOT/ubuntu-1 because rpool/ROOT includes it recursively.
Doing regular snapshots of rpool rpool/DATA rpool/DATA/vm
Doing recursive snapshots of rpool/DATA/ldap rpool/DATA/postgresql rpool/DATA/vm/alderaan.example.net rpool/DATA/vm/bespin.example.net rpool/DATA/vm/coruscant.example.net rpool/DATA/vm/dagobah.example.net rpool/DATA/vm/dantooine.example.net rpool/DATA/vm/dev.example.net rpool/DATA/vm/monitor.example.net rpool/DATA/vm/office.example.net rpool/DATA/vm/test.example.net rpool/ROOT
Doing a dry run. Not running these commands...
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy  'rpool@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool/DATA@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy  'rpool/DATA@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-'  'rpool/DATA/vm@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy  'rpool/DATA/vm@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/ldap@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/ldap@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/postgresql@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/postgresql@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/alderaan.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/alderaan.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/bespin.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/bespin.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/coruscant.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/coruscant.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dagobah.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/dagobah.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dantooine.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/dantooine.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/dev.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/dev.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/monitor.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/monitor.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/office.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/office.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/DATA/vm/test.example.net@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/DATA/vm/test.example.net@zfs-auto-snap_frequent-2013-04-16-2315'
zfs snapshot -o com.sun:auto-snapshot-desc='-' -r 'rpool/ROOT@zfs-auto-snap_frequent-2013-04-16-2348'
zfs destroy -r 'rpool/ROOT@zfs-auto-snap_frequent-2013-04-16-2315'
@zfs-auto-snap_frequent-2013-04-16-2348, 15 created, 15 destroyed, 0 warnings.

real	3m30.995s
user	0m0.152s
sys	0m0.792s
root@tatooine:/root# 

I'm not an awk god, so I tried a bit until I got a working version. There might be better ways. I also don't know if this catches every adge case, but it is a start for improvement. :)
2013-04-17 01:07:00 +03:00
5 changed files with 231 additions and 533 deletions

2
.gitignore vendored
View File

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

View File

@ -11,5 +11,7 @@ install:
install etc/zfs-auto-snapshot.cron.daily $(DESTDIR)$(PREFIX)/etc/cron.daily/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.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 etc/zfs-auto-snapshot.cron.monthly $(DESTDIR)$(PREFIX)/etc/cron.monthly/zfs-auto-snapshot
install -d $(DESTDIR)$(PREFIX)/share/man/man8
install src/zfs-auto-snapshot.8 $(DESTDIR)$(PREFIX)/share/man/man8/zfs-auto-snapshot.8
install -d $(DESTDIR)$(PREFIX)/sbin install -d $(DESTDIR)$(PREFIX)/sbin
install src/zfs-auto-snapshot.sh $(DESTDIR)$(PREFIX)/sbin/zfs-auto-snapshot install src/zfs-auto-snapshot.sh $(DESTDIR)$(PREFIX)/sbin/zfs-auto-snapshot

6
README
View File

@ -1,14 +1,12 @@
zfs-auto-snapshot: zfs-auto-snapshot:
An alternative implementation of the zfs-auto-snapshot service for Macosx An alternative implementation of the zfs-auto-snapshot service for Linux
that is compatible with ZEVO community zfs. that is compatible with zfs-linux and zfs-fuse.
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.

70
src/zfs-auto-snapshot.8 Normal file
View File

@ -0,0 +1,70 @@
.TH ZFS-AUTO-SNAPSHOT "8" "June 16, 2013" "zfs-auto-snapshot.sh" "System Administration Commands"
.SH NAME
zfs-auto-snapshot \- take regular ZFS snapshots
.SH SYNOPSIS
.B zfs-auto-snapshot
[\fIoptions\fR] [\fI-l label\fR] \fI<'//' | name \fR[\fIname\fR...]\fI>\fR
.SH DESCRIPTION
.B zfs-auto-snapshot
automatically creates, rotates, and destroys snapshots for all your
ZFS datasets, and is compatible with both zfsonlinux and zfs-fuse.
.SH OPTIONS
.TP
\fB\-\-default\-exclude\fR
By default \fBzfs-auto-snapshot\fR will snapshot all datasets except
for those in which the user-property \fBcom.sun:auto-snapshot\fR is
set to \fBfalse\fR. This option reverses the behavior and requires
\fBcom.sun:auto-snapshot\fR to be set to \fBtrue\fR.
.TP
\fB\-d\fR, \fB\-\-debug\fR
Print debugging messages.
.TP
\fB\-e\fR, \fB\-\-event\fR=\fIEVENT\fR
Set the com.sun:auto\-snapshot\-desc property to EVENT.
.TP
\fB\-\-fast\fR
Use a faster zfs list invocation.
.TP
\fB\-n\fR, \fB\-\-dry\-run\fR
Print actions without actually doing anything.
.TP
\fB\-s\fR, \fB\-\-skip\-scrub\fR
Do not snapshot filesystems in scrubbing pools.
.TP
\fB\-h\fR, \fB\-\-help\fR
Print the usage message.
.TP
\fB\-k\fR, \fB\-\-keep\fR=\fINUM\fR
Keep NUM recent snapshots and destroy older snapshots.
.TP
\fB\-l\fR, \fB\-\-label\fR=\fILAB\fR
LAB is usually 'hourly', 'daily', or 'monthly'.
.TP
\fB\-p\fR, \fB\-\-prefix\fR=\fIPRE\fR
PRE is 'zfs\-auto\-snap' by default.
.TP
\fB\-q\fR, \fB\-\-quiet\fR
Suppress warnings and notices at the console.
.TP
\fB\-\-send\-full\fR=\fIF\fR
Send zfs full backup. Unimplemented.
.TP
\fB\-\-send\-incr\fR=\fIF\fR
Send zfs incremental backup. Unimplemented.
.TP
\fB\-\-sep\fR=\fICHAR\fR
Use CHAR to separate date stamps in snapshot names.
.TP
\fB\-g\fR, \fB\-\-syslog\fR
Write messages into the system log.
.TP
\fB\-r\fR, \fB\-\-recursive\fR
Snapshot named filesystem and all descendants.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Print info messages.
.TP
name
Filesystem and volume names, or '//' for all ZFS datasets.
.SH SEE ALSO
.BR zfs (8)

View File

@ -1,9 +1,8 @@
#!/bin/bash #!/bin/bash
# zfs-auto-snapshot for Linux and Macosx # zfs-auto-snapshot for Linux
# 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,8 +29,9 @@ opt_backup_incremental=''
opt_default_exclude='' opt_default_exclude=''
opt_dry_run='' opt_dry_run=''
opt_event='-' opt_event='-'
opt_fast_zfs_list=''
opt_keep='' opt_keep=''
opt_label='regular' opt_label=''
opt_prefix='zfs-auto-snap' opt_prefix='zfs-auto-snap'
opt_recursive='' opt_recursive=''
opt_sep='_' opt_sep='_'
@ -39,75 +39,40 @@ opt_setauto=''
opt_syslog='' opt_syslog=''
opt_skip_scrub='' opt_skip_scrub=''
opt_verbose='' opt_verbose=''
opt_remove='' opt_pre_snapshot=''
opt_fallback='0' opt_post_snapshot=''
opt_force='' opt_do_snapshots=1
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.
declare -a SNAPSHOTS_OLD_LOC=('') SNAPSHOTS_OLD=''
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.
--fast Use a faster zfs list invocation.
-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' (default is 'regular'). -l, --label=LAB LAB is usually 'hourly', 'daily', or 'monthly'.
-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.
-c, --create Create missing filesystems at destination. --send-full=F Send zfs full 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-incr=F Send zfs incremental backup. Unimplemented.
--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) --destroy-only Only destroy older snapshots, do not create new ones.
name Filesystem and volume names, or '//' for all ZFS datasets. name Filesystem and volume names, or '//' for all ZFS datasets.
" "
} }
@ -138,7 +103,6 @@ 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 $*
@ -146,7 +110,7 @@ print_log () # level, message, ...
;; ;;
(inf*) (inf*)
# test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info $* # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.info $*
test -n "$opt_verbose" && echo $* test -z ${opt_quiet+x} && test -n "$opt_verbose" && echo $*
;; ;;
(deb*) (deb*)
# test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug $* # test -n "$opt_syslog" && logger -t "$opt_prefix" -p daemon.debug $*
@ -162,15 +126,13 @@ print_log () # level, message, ...
do_run () # [argv] do_run () # [argv]
{ {
if [ -n "$opt_dry_run" ] if [ -n "$opt_dry_run" ]
then then
print_log notice "... running: $*" echo $*
RC="$?" RC="$?"
else else
eval $* eval $*
RC="$?" RC="$?"
if [ "$RC" -eq '0' ] if [ "$RC" -eq '0' ]
then then
print_log debug "$*" print_log debug "$*"
@ -178,345 +140,84 @@ 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 sFLAGS="$2" local FLAGS="$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='' local LABEL="$6"
local RUNSNAP=1
if test "$sFLAGS" = '-R'; then # global DESTRUCTION_COUNT
FLAGS='-r' # global SNAPSHOT_COUNT
SNexp='.*' # global WARNING_COUNT
else # global SNAPSHOTS_OLD
FLAGS=''
fi
for ii in ${TARGETS[@]} for ii in $TARGETS
do do
if [ -n "$opt_do_snapshots" ]
FALLBACK='0'
SND_RC='1'
print_log debug "--> Snapshooting $ii"
if ! do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'"
then then
if [ "$opt_pre_snapshot" != "" ]
then
do_run "$opt_pre_snapshot $ii $NAME" || RUNSNAP=0
fi
if [ $RUNSNAP -eq 1 ] && do_run "zfs snapshot $PROPS $FLAGS '$ii@$NAME'"
then
[ "$opt_post_snapshot" != "" ] && do_run "$opt_post_snapshot $ii $NAME"
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 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.
if [ -z "$opt_keep" ] test -z "$opt_keep" && continue
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" 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_LOC[@]} # Match on both $GLOB *and* label.
while IFS=' ' read -ra arrjj
do do
# Check whether this is an old snapshot of the filesystem. # Check whether this is an old snapshot of the filesystem.
test -z "${jj#$ii@$GLOB}" -o -z "${jj##$ii@$opt_prefix*}" -a -n "$opt_remove" \ if [ -z "${arrjj[1]#$ii@$GLOB}" ]
-a "$opt_send" != "no" && do_delete "local" "$jj" "$FLAGS" then
done # Check whether the snapshot has the same label property
test "${arrjj[0]}" = "$LABEL" || continue
if [ "$opt_send" = "no" ] KEEP=$(( $KEEP - 1 ))
if [ "$KEEP" -le '0' ]
then then
print_log debug "No sending option specified, skipping remote snapshot removal." if do_run "zfs destroy -d $FLAGS '${arrjj[1]}'"
continue
elif [ "$sFLAGS" = "-R" ]
then then
print_log debug "Replication specified, remote snapshots were removed while sending." DESTRUCTION_COUNT=$(( $DESTRUCTION_COUNT + 1 ))
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 else
KEEP="$opt_keep" WARNING_COUNT=$(( $WARNING_COUNT + 1 ))
print_log debug "Destroying remote snapshots, keeping only $KEEP."
fi fi
fi
# ASSERT: The old snapshot list is sorted by increasing age. fi
for jj in ${SNAPSHOTS_OLD_REM[@]} done <<< "$SNAPSHOTS_OLD"
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 ()
# { # {
PLATFORM_LOC=`uname` GETOPT=$(getopt \
case "$PLATFORM_LOC" in --longoptions=default-exclude,dry-run,fast,skip-scrub,recursive \
(Linux) --longoptions=event:,keep:,label:,prefix:,sep: \
getopt_cmd='getopt' --longoptions=debug,help,quiet,syslog,verbose \
;; --longoptions=pre-snapshot:,post-snapshot:,destroy-only \
(Darwin) --options=dnshe:l:k:p:rs:qgv \
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
@ -531,11 +232,7 @@ do
opt_verbose='1' opt_verbose='1'
shift 1 shift 1
;; ;;
(-c|--create) (--default-exclude)
opt_create='1'
shift 1
;;
(-x|--default-exclude)
opt_default_exclude='1' opt_default_exclude='1'
shift 1 shift 1
;; ;;
@ -543,13 +240,17 @@ 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 239 exit 139
elif [ "${#2}" -gt '0' ] elif [ "${#2}" -gt '0' ]
then then
opt_event="$2" opt_event="$2"
fi fi
shift 2 shift 2
;; ;;
(--fast)
opt_fast_zfs_list='1'
shift 1
;;
(-n|--dry-run) (-n|--dry-run)
opt_dry_run='1' opt_dry_run='1'
shift 1 shift 1
@ -566,7 +267,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 229 exit 129
fi fi
opt_keep="$2" opt_keep="$2"
shift 2 shift 2
@ -582,7 +283,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 230 exit 130
;; ;;
esac esac
opt_prefix="${opt_prefix#?}" opt_prefix="${opt_prefix#?}"
@ -597,20 +298,7 @@ do
shift 1 shift 1
;; ;;
(-r|--recursive) (-r|--recursive)
opt_recursive=' ' opt_recursive='1'
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)
@ -620,67 +308,35 @@ do
;; ;;
('') ('')
print_log error "The $1 parameter must be non-empty." print_log error "The $1 parameter must be non-empty."
exit 231 exit 131
;; ;;
(*) (*)
print_log error "The $1 parameter must be one alphanumeric character." print_log error "The $1 parameter must be one alphanumeric character."
exit 232 exit 132
;; ;;
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) (--pre-snapshot)
opt_force='-F' opt_pre_snapshot="$2"
shift 2
;;
(--post-snapshot)
opt_post_snapshot="$2"
shift 2
;;
(--destroy-only)
opt_do_snapshots=''
shift 1 shift 1
;; ;;
(--) (--)
@ -696,13 +352,6 @@ 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 "$@"
@ -723,11 +372,23 @@ fi
ZPOOL_STATUS=$(env LC_ALL=C zpool status 2>&1 ) \ 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",mountpoint,canmount,snapdir) \ -o name,com.sun:auto-snapshot,com.sun:auto-snapshot:"$opt_label") \
|| { print_log error "zfs list $?: $ZFS_LIST"; exit 136; } || { print_log error "zfs list $?: $ZFS_LIST"; exit 136; }
ZFS_LOCAL_LIST=($(echo "$ZFS_LIST" | awk -F'\t' '{ORS="\n"}{print $1}')) if [ -n "$opt_fast_zfs_list" ]
then
SNAPSHOTS_OLD=$(env LC_ALL=C zfs list -H -t snapshot -o com.sun:auto-snapshot-label,name -s name | \
grep $opt_prefix | \
awk '{ print substr( $0, length($0) - 14, length($0) ) " " $0}' | \
sort -r -k1,1 -k3,2 | \
awk '{ print substr( $0, 17, length($0) )}') \
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 137; }
else
SNAPSHOTS_OLD=$(env LC_ALL=C zfs list -H -t snapshot -S creation -o com.sun:auto-snapshot-label,name) \
|| { print_log error "zfs list $?: $SNAPSHOTS_OLD"; exit 137; }
fi
# Verify that each argument is a filesystem or volume. # Verify that each argument is a filesystem or volume.
for ii in "$@" for ii in "$@"
@ -755,29 +416,28 @@ 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 not explicitly disabled. # 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}')
# 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 not explicitly enabled. # Get a list of datasets for which snapshots are explicitly enabled.
NOAUTO=$(echo "$ZFS_LIST" | awk -F '\t' \ CANDIDATES=$(echo "$ZFS_LIST" | awk -F '\t' \
'tolower($2) !~ /true/ && tolower($3) !~ /true/ {print $1}') 'tolower($2) ~ /true/ || tolower($3) ~ /true/ {print $1}')
else else
# Get a list of datasets for which snapshots are explicitly disabled. # Invert the NOAUTO list.
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}')
fi fi
# Initialize the list of datasets that will get a recursive snapshot. # Initialize the list of datasets that will get a recursive snapshot.
declare -a TARGETS_DRECURSIVE=('') TARGETS_RECURSIVE=''
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.
declare -a TARGETS_DREGULAR=('') TARGETS_REGULAR=''
for ii in $CANDIDATES for ii in $CANDIDATES
do do
@ -786,11 +446,12 @@ do
# Just testing "$ii" != ${ii#$jj} would incorrectly match. # Just testing "$ii" != ${ii#$jj} would incorrectly match.
iii="$ii/" iii="$ii/"
# Exclude datasets that are not named on the command line. # Exclude datasets that are not named on the command line.
IN_ARGS='0' IN_ARGS='0'
for jj in "$@" for jj in "$@"
do do
if [ "$jj" = '//' -o "$jj" = "$ii" -o -n "$opt_recursive" -a -z "${ii##$jj/*}" ] if [ "$jj" = '//' -o "$jj" = "$ii" ]
then then
IN_ARGS=$(( $IN_ARGS + 1 )) IN_ARGS=$(( $IN_ARGS + 1 ))
fi fi
@ -828,31 +489,29 @@ do
fi fi
done done
noauto_parent='0'
for jj in $NOAUTO for jj in $NOAUTO
do do
# Ibid regarding iii. # Ibid regarding iii.
jjj="$jj/" jjj="$jj/"
if [ "$jjj" = "$iii" ] # The --recursive switch only matters for non-wild arguments.
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
noauto_parent='1' && break # Snapshot this dataset non-recursively.
print_log debug "Including $ii for regular snapshot."
TARGETS_REGULAR="${TARGETS_REGULAR:+$TARGETS_REGULAR }$ii" # nb: \t
continue 2
fi fi
done done
# not scrubbing for jj in $TARGETS_RECURSIVE
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/"
@ -873,84 +532,55 @@ 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_TMP_RECURSIVE=( ${TARGETS_TMP_RECURSIVE[@]} $( printf "%s\n" $ii) ) TARGETS_RECURSIVE="${TARGETS_RECURSIVE:+$TARGETS_RECURSIVE }$ii" # nb: \t
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
# because the SUNW program does. The dash character is the default. # because the SUNW program does. The dash character is the default.
SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event'" SNAPPROP="-o com.sun:auto-snapshot-desc='$opt_event' -o com.sun:auto-snapshot-label=$opt_label"
# ISO style date; fifteen characters: YYYY-MM-DD-HHMM # ISO style date; fifteen characters: YYYY-MM-DD-HHMM
# On Solaris %H%M expands to 12h34. # On Solaris %H%M expands to 12h34.
DATE=$(date +%F-%H%M) DATE=$(date --utc +%F-%H%M)
# The snapshot name after the @ symbol. # The snapshot name after the @ symbol.
SNAPNAME="$opt_prefix${opt_label:+$opt_sep$opt_label-$DATE}" SNAPNAME="$opt_prefix$opt_sep$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}????????????????"
if test -n "$TARGETS_DREGULAR"; then if [ -n "$opt_do_snapshots" ]
print_log info "Doing regular snapshots of ${TARGETS_DREGULAR[@]}" then
SNAPSHOTS_OLD_LOC=( $(eval zfs list -r -d 1 -H -t snapshot -S creation -o name $(printf "%s " ${TARGETS_DREGULAR[@]}) )) test -n "$TARGETS_REGULAR" \
fi && print_log info "Doing regular snapshots of $TARGETS_REGULAR"
if test -n "$TARGETS_TMP_RECURSIVE"; then test -n "$TARGETS_RECURSIVE" \
print_log info "Doing recursive snapshots of ${TARGETS_TMP_RECURSIVE[@]}" && print_log info "Doing recursive snapshots of $TARGETS_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; } if test -n "$opt_keep" && [ "$opt_keep" -ge "1" ]
then
print_log info "Destroying all but the newest $opt_keep snapshots of each dataset."
fi
elif test -n "$opt_keep" && [ "$opt_keep" -ge "1" ]
then
test -n "$TARGETS_REGULAR" \
&& print_log info "Destroying all but the newest $opt_keep snapshots of $TARGETS_REGULAR"
test -n "$TARGETS_RECURSIVE" \
&& print_log info "Recursively destroying all but the newest $opt_keep snapshots of $TARGETS_RECURSIVE"
else
print_log notice "Only destroying snapshots, but count of snapshots to preserve not given. Nothing to do."
fi 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..."
# expand FS list if replication is not used do_snapshots "$SNAPPROP" "" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_REGULAR" "$opt_label"
if [ "$opt_recursive" = ' ' -o "$1" = "//" ] do_snapshots "$SNAPPROP" "-r" "$SNAPNAME" "$SNAPGLOB" "$TARGETS_RECURSIVE" "$opt_label"
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 snapshots," \ "$SNAPSHOT_COUNT created," \
"$SENT_COUNT sent snapshots," \
"$DESTRUCTION_COUNT destroyed," \ "$DESTRUCTION_COUNT destroyed," \
"$CREATION_COUNT created filesystems," \
"$WARNING_COUNT warnings." "$WARNING_COUNT warnings."
exit 0 exit 0