From c7873bd72086ce1a48a5310ed6b78def4cd86380 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sun, 21 Nov 2021 19:35:28 +0000 Subject: [PATCH 01/15] automated test harness --- tests/setup_zrep.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/setup_zrep.sh b/tests/setup_zrep.sh index 8e6054e..e7a4ba6 100755 --- a/tests/setup_zrep.sh +++ b/tests/setup_zrep.sh @@ -10,6 +10,7 @@ echo doing $ZREP_PATH init $SRC localhost $NEWSRC now $ZREP_PATH init $SRC localhost $NEWSRC + # this is normally not neccessary, but because we will be doing someting # "weird", with a zrep refresh, we have to force this From fdf7063ed557ca46f3150a7a51dfcead5a273a7b Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sun, 21 Nov 2021 20:04:57 +0000 Subject: [PATCH 02/15] the big echo conversion, and VERSION 2.0 bump --- zrep_top | 102 +++++++++++++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/zrep_top b/zrep_top index 6d18241..6f69634 100644 --- a/zrep_top +++ b/zrep_top @@ -1,9 +1,11 @@ -#!/bin/ksh -p +#!/bin/bash --noprofile # For more detailed documentation, see zrep.txt or zrep.overview.txt - # note to self: always make this 3 fields -ZREP_VERSION=1.8.0 +ZREP_VERSION=2.0.0 + +################################################ + ######## start of included files from zrep_top here AWKinclude zrep_vars @@ -15,51 +17,49 @@ AWKinclude zrep_failover ######## zrep_top continues here usage(){ - print zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another - print in an ongoing basis. - print - print " (c) Philip Brown, 2012-2020" - print - print Simple usage summary: - print 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' - print 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' - print 'zrep (sync|-S) [-f] [-v] [-q seconds] all' - print 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' - print 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' - print 'zrep failover [-L] ZFS/fs [remotehost]' - print 'zrep takeover [-L] ZFS/fs [remotehost]' - print - print "Status and side operations" - print 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' - print 'zrep (status|-s) -m [ZFS/fs]' - print 'zrep (list|-l) [-Lv] [fs/names]' - print 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' - print 'zrep getlastsent ZFS/fs -- prints out snapshot of last successful sync sent' -# print 'zrep setlastsent ZFS/fs snapname -- overrides value of last known snapshot synced' - print 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' - print 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' - print 'zrep version' - print 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' - print 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' - print 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' - print - print ' -q option to sync says to Quietly ignore locked filesystems' - print ' that have synced more recently than the given amount of seconds' - print - print 'Paired commands for high-transaction systems:' - print ' zrep snaponly (ZFS/fs ... |all)' - print ' zrep synconly (ZFS/fs ...|all)' - print 'The above two commands split the simple sync subcommand, into two' - print 'separate steps, so that a database, etc. may resume while the sync' - print 'completes in the background' - print '' - print 'zrep defaults to using ssh. However, if remotehost is set to localhost,' - print 'zrep will use a simple pipe instead.' - print '' - print ' More detailed examples can be found at:' - print http://www.bolthole.com/solaris/zrep/zrep.documentation.html - print '' - print 'See the above documentation for details on using the -t flag to use zrep tags' + echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another + echo in an ongoing basis. + echo + echo " Philip Brown, 2012-2019" + echo + echo Simple usage summary: + echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' + echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' + echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' + echo 'zrep failover [-L] ZFS/fs' + echo 'zrep takeover [-L] ZFS/fs' + echo + echo "Status and side operations" + echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' + echo 'zrep (list|-l) [-Lv] [fs/names]' + echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' + echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' + echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' + echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' + echo 'zrep version' + echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' + echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' + echo + echo ' -q option to sync says to Quietly ignore locked filesystems' + echo ' that have synced more recently than the given amount of seconds' + echo + echo 'Paired commands for high-transaction systems:' + echo ' zrep snaponly (ZFS/fs ... |all)' + echo ' zrep synconly (ZFS/fs ...|all)' + echo 'The above two commands split the simple sync subcommand, into two' + echo 'separate steps, so that a database, etc. may resume while the sync' + echo 'completes in the background' + echo '' + echo 'zrep defaults to using ssh. However, if remotehost is set to localhost,' + echo 'zrep will use a simple pipe instead.' + echo '' + echo ' More detailed examples can be found at:' + echo http://www.bolthole.com/solaris/zrep/zrep.documentation.html + echo '' + echo 'See the above documentation for details on using the -t flag to use zrep tags' } # @@ -237,9 +237,9 @@ case "$1" in exit $? ;; version) - print "zrep $ZREP_VERSION" - print "http://www.bolthole.com/solaris/zrep" - print "http://www.github.com/bolthole/zrep" + echo "zrep $ZREP_VERSION" + echo "http://www.bolthole.com/solaris/zrep" + echo "http://www.github.com/bolthole/zrep" exit ;; From 7b09b3c22454bc7d0e2815d8fda1b432ee963f80 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sun, 21 Nov 2021 20:05:11 +0000 Subject: [PATCH 03/15] the big echo conversion --- zrep | 279 +++++++++++++++++++++++++------------------------- zrep.spec | 2 +- zrep_failover | 32 +++--- zrep_snap | 51 +++++---- zrep_status | 18 ++-- zrep_sync | 52 +++++----- zrep_vars | 24 ++--- 7 files changed, 228 insertions(+), 230 deletions(-) diff --git a/zrep b/zrep index 00d9369..0291dd5 100755 --- a/zrep +++ b/zrep @@ -1,9 +1,11 @@ -#!/bin/ksh -p +#!/bin/bash --noprofile # For more detailed documentation, see zrep.txt or zrep.overview.txt - # note to self: always make this 3 fields -ZREP_VERSION=1.8.0 +ZREP_VERSION=2.0.0 + +################################################ + ######## start of included files from zrep_top here ########### File: zrep_vars @@ -164,7 +166,7 @@ ZREP_DATEFORMAT=${ZREP_DATEFORMAT:-%Y/%m/%d-%H:%M:%S} _debugprint(){ if [[ "$DEBUG" != "" ]] ; then - print DEBUG: $@ + echo DEBUG: $@ fi } @@ -172,9 +174,9 @@ _debugprint(){ # to make dealing with github issue #22 easier, about redirecting stderr _errprint(){ # I thought /dev/fd was ksh builtin, so safest. But it glitches on some linuxen. - # print $@ >/dev/fd/2 + # echo $@ >/dev/fd/2 - print $@ >&2 + echo $@ >&2 } @@ -345,8 +347,8 @@ else DEPTHCAP="-d 1" # limits "list -r" else DEPTHCAP="" - print WARNING: old ZFS version detected with no depth protection - print WARNING: You may not nest zrep managed filesystems + echo WARNING: old ZFS version detected with no depth protection + echo WARNING: You may not nest zrep managed filesystems fi fi @@ -443,8 +445,8 @@ zrep_get_global_lock(){ done - print Failed to acquire global lock - print Error message was: $errmsg + echo Failed to acquire global lock + echo Error message was: $errmsg return 1 } @@ -454,7 +456,7 @@ zrep_release_global_lock(){ rm $Z_GLOBAL_LOCKFILE return $? else - print ERROR: zrep_release_global_lock called, but do not own lock + echo ERROR: zrep_release_global_lock called, but do not own lock return 1 fi } @@ -550,8 +552,8 @@ zrep_errquit(){ if zrep_has_global_lock ; then if [[ "$$" -ne "$Z_GLOBAL_PID" ]] ; then - print EXTRA-ERROR: Running in child proc. - print 'Not sure whether to release global lock. NOT releasing!' + echo EXTRA-ERROR: Running in child proc. + echo 'Not sure whether to release global lock. NOT releasing!' exit 1 else @@ -608,7 +610,7 @@ zrep_gettimeinseconds(){ if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. - seconds=`perl -e 'print int(time);' 2>/dev/null` + seconds=`perl -e 'echo int(time);' 2>/dev/null` fi if [[ -z "$seconds" ]] ; then # attempt fallback if no perl present (eg: stock FreeBSD) @@ -617,7 +619,7 @@ zrep_gettimeinseconds(){ if [[ -z "$seconds" ]] ; then zrep_errquit zrep_gettimeinseconds doesnt know what to do fi - print $seconds + echo $seconds } ###### File: zrep_status @@ -632,8 +634,8 @@ zrep_gettimeinseconds(){ # #Give this a top level zrep registered filesystem, NOT snapshot. -# Will print out various status points, such as last sync date. -# Or if given no args, will print out sync date for all zrep mastered fs +# Will echo out various status points, such as last sync date. +# Or if given no args, will echo out sync date for all zrep mastered fs # Note that the date given is time of SNAPSHOT, not time sync completed. # zrep_status(){ @@ -706,7 +708,7 @@ zrep_status(){ printf "%-25s->%-35s %s\n" $srcfs "$desthost:$destfs" "$vdate" else printf "%-52s " $srcfs - print "last: $date" + echo "last: $date" fi shift done @@ -733,7 +735,7 @@ zrep_list_master(){ for fs in `_master_fs_names "$@"` ; do srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` if [[ "$srchost" == "$Z_LOCAL_HOST" ]] ; then - print $fs + echo $fs fi done } @@ -742,13 +744,13 @@ zrep_list_master(){ # Given ONE filesystem, print all zrep properties for it. # Note that this is internal routine. we do not validate input. list_verbose(){ - print $1: + echo $1: # sneaky cheat: only user-set properties will # match these 'source' types. So "grep zrep:" is not # neccessary. Although we may pick up other user-set values, # but that is not neccessarily a bad thing zfs get -H -o property,value -s $PROPTYPES all $1 - print "last snapshot synced: `getlastsnapsent $1`" + echo "last snapshot synced: `getlastsnapsent $1`" } @@ -820,13 +822,13 @@ zrep_list(){ # oh well. have to step through them one by one now, to - # print out the properties associated with each zrep filesystem + # echo out the properties associated with each zrep filesystem fslist=`zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs` for fs in $fslist ; do $printcmd $fs - print "" + echo "" done @@ -861,7 +863,7 @@ zrep_getlastsent(){ exit 1 fi - print `getlastsnapsent $1` + echo `getlastsnapsent $1` } @@ -945,7 +947,7 @@ getlastsnapsent(){ lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1 | sort | tail -1` if [[ "$lastsent" != "" ]] ; then - print $lastsent + echo $lastsent return fi @@ -997,7 +999,7 @@ getunsentcount(){ zrep_errquit Error: cannot parse $lastsnap fi - print $(( $lastsnapseq - $lastsyncedseq )) + echo $(( $lastsnapseq - $lastsyncedseq )) } @@ -1010,7 +1012,7 @@ getunsentcount(){ # # In future, may take optional argument of which HOST to check # sync with. But since I currently only suport one host per fs... oh well. -# If never synced, will return 1, and print "" +# If never synced, will return 1, and echo "" # getlastsynctime(){ typeset fs lastsent senttime @@ -1027,11 +1029,11 @@ getlastsynctime(){ lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $fs | sort | tail -1` senttime=`zfs get -H -o value ${ZREPTAG}:sent $lastsent` - if [[ "$senttime" != "" ]] ; then print $senttime ; return 0; fi + if [[ "$senttime" != "" ]] ; then echo $senttime ; return 0; fi # ooops. try fallback to nasty old zfs-compat style senttime=`zfs get -H -o value ${ZREPTAG}:lastsent $fs` - print $senttime + echo $senttime if [[ "$senttime" != "" ]] ; then return 0; fi return 1 } @@ -1097,7 +1099,7 @@ makesnap(){ check="`$ZFSGETVAL ${ZREPTAG}:src-host $1`" if [[ "$check" != "$Z_LOCAL_HOST" ]] ; then _errprint ERROR: we are not master host for $1 - _errprint print master is $check, we are $Z_LOCAL_HOST + _errprint master is $check, we are $Z_LOCAL_HOST exit 1 fi @@ -1119,7 +1121,7 @@ makesnap(){ zfs snapshot $Z_SNAP_R $newsnap if [[ $? -eq 0 ]] ; then - print $newsnap; return 0 + echo -n $newsnap; return 0 else return 1 fi @@ -1136,12 +1138,12 @@ zrep_clear(){ zrep_errquit clear command requires an argument fi - print "WARNING: Removing all zrep configs and snapshots from $1" - print " (for TAG=${ZREPTAG})" + echo "WARNING: Removing all zrep configs and snapshots from $1" + echo " (for TAG=${ZREPTAG})" if [[ "$ZREP_FORCE" != "-f" ]] ; then - print Continuing in 10 seconds + echo Continuing in 10 seconds sleep 10 fi @@ -1150,7 +1152,7 @@ zrep_clear(){ } _clearsnaps(){ - print Destroying any zrep-related snapshots from $1 + echo Destroying any zrep-related snapshots from $1 snaplist=`list_autosnaps $1` for snap in $snaplist ; do zfs destroy -r $snap @@ -1158,7 +1160,7 @@ _clearsnaps(){ } _clearvars(){ - print Removing zrep-related properties from $1 + echo Removing zrep-related properties from $1 proplist=`zfs get -H -o property all $1|grep ${ZREPTAG}:` for prop in $proplist ; do zfs inherit $prop $1 @@ -1230,8 +1232,8 @@ setfsconfigs(){ # With our curent workflow, that can ONLY WORK # if recent versions of ZFS are in use. if (( Z_HAS_REC_U )) ; then - print WARNING: forcing override sync to top level pool $desthost:$destfs - print Not using a usual sub-filesystem + echo WARNING: forcing override sync to top level pool $desthost:$destfs + echo Not using a usual sub-filesystem sleep 5 else zrep_errquit older zfs version, cannot initialize top level pool @@ -1274,7 +1276,7 @@ zrep_init(){ #sanity checks check="`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs`" if [[ "$check" != "-" ]] ; then - print "$srcfs is at least partially configured by zrep" + echo "$srcfs is at least partially configured by zrep" check="`$ZFSGETLVAL ${ZREPTAG}:master $srcfs`" if [[ "$check" != "" ]] ; then zrep_errquit "${ZREPTAG}:master detected!!" @@ -1295,7 +1297,7 @@ zrep_init(){ zrep_errquit Partial init of $src detected but no resume token found. Suggest you zrep clear and start again fi - print "Partially complete init detected. Attempting to resume send" + echo "Partially complete init detected. Attempting to resume send" if [[ "$BBCP" != "" ]] ; then SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" @@ -1344,7 +1346,7 @@ zrep_init(){ recv_s=-s fi - print Setting zrep properties on $srcfs + echo Setting zrep properties on $srcfs setfsconfigs $srcfs $desthost $destfs #setfsconfigs may do some "smarts" to adjust value, so get it again. @@ -1355,10 +1357,10 @@ zrep_init(){ READONLYPROP="-o readonly=on" else READONLYPROP="" - print Warning: zfs recv lacking -o readonly + echo Warning: zfs recv lacking -o readonly fi if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - print Creating destination filesystem as separate step + echo Creating destination filesystem as separate step # normally would want to use -o readonly here. # however, that breaks when -R is used. # set it after transfer instead @@ -1366,15 +1368,14 @@ zrep_init(){ fi snap="${srcfs}@${ZREPTAG}_000000" - print Creating snapshot $snap + echo Creating snapshot $snap zfs snapshot $Z_SNAP_R $snap || clearquit $srcfs "Cannot create initial snapshot $snap" # Note that we may not want to use -p for normal zrep syncs # We also should not use -F for normal recv. See workflow.txt # Note: we may have to reset readonly=on, if we used -p on send... # - print Sending initial replication stream to $desthost:$destfs - + echo Sending initial replication stream to $desthost:$destfs if (( Z_HAS_REC_U )) ; then # This is the nice, clean, modern codepath, to send # zrep settings over automatically at first init. @@ -1436,7 +1437,7 @@ zrep_init(){ fi if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - print setting readonly on $desthost:$destfs manually + echo setting readonly on $desthost:$destfs manually zrep_ssh $desthost zfs set readonly=on $destfs fi @@ -1461,7 +1462,7 @@ zrep_init(){ zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${snap} $destfs fi if [[ "$mountpoint" != "" ]] ; then - print "clearing mountpoint value for remote" + echo "clearing mountpoint value for remote" zrep_ssh $desthost zfs inherit mountpoint $destfs fi @@ -1477,9 +1478,9 @@ zrep_init(){ # Make sure value this matches zrep_sentsync zfs set ${ZREPTAG}:master=yes $srcfs - print Initialization copy of $srcfs to $desthost:$destfs complete + echo Initialization copy of $srcfs to $desthost:$destfs complete if (( Z_HAS_REC_U )) ; then - print Filesystem will not be mounted + echo Filesystem will not be mounted fi } @@ -1522,7 +1523,7 @@ zrep_changeconfig(){ ## file-internal routine that gets used a lot in zrep_sync. but not always _gensentprop(){ typeset timeinsec=`zrep_gettimeinseconds` - print "${ZREPTAG}:sent=$timeinsec" + echo "${ZREPTAG}:sent=$timeinsec" } @@ -1622,7 +1623,7 @@ synctosnap(){ typeset srcfs snapname destsnap if [[ "$desthost" == "" ]] ; then - print ERROR: synctosnap did not receive all required args + echo ERROR: synctosnap did not receive all required args zrep_errquit "args=$@" fi srcfs=${srcsnap%@*} @@ -1643,29 +1644,29 @@ synctosnap(){ - print Validating remote snap + echo Validating remote snap zrep_ssh $desthost zfs list -t snapshot $destfs@$destsnap >/dev/null if [[ $? -ne 0 ]] ; then zrep_errquit $destfs@$destsnap does not exist. Cannot roll to snap fi - print "WARNING: We will be rolling back $destfs, on $desthost" - print -n " to $snapname, made at: " + echo "WARNING: We will be rolling back $destfs, on $desthost" + echo -n " to $snapname, made at: " $ZFSGETVAL creation $srcsnap - print "" - print "All newer snapshots on remote side will be destroyed" - print "You should have paused ongoing sync jobs for $destfs before continuing" - print "Continuing in 20 seconds...." + echo "" + echo "All newer snapshots on remote side will be destroyed" + echo "You should have paused ongoing sync jobs for $destfs before continuing" + echo "Continuing in 20 seconds...." sleep 10 - print "Continuing in 10 seconds...." + echo "Continuing in 10 seconds...." sleep 10 zrep_lock_fs $srcfs || zrep_errquit "Cannot lock $srcfs" zrep_ssh $desthost zfs rollback -Rr $destfs@$destsnap || zrep_errquit roll failed - print $desthost:$destfs rolled back successfully to $destsnap - print Now cleaning up local snapshots + echo $desthost:$destfs rolled back successfully to $destsnap + echo Now cleaning up local snapshots # need to undo whatever zrep_sync does newsentlist=`getallsnaps $srcfs|sed "1,/@$snapname/d"` @@ -1723,7 +1724,7 @@ _snapandsync(){ # if we cant do incremental anyway sentsnap=`getlastsnapsent $srcfs` if [[ "$sentsnap" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi @@ -1780,7 +1781,7 @@ _sync(){ if [[ "$lastsent" == "" ]] ; then lastsent=`getlastsnapsent $srcfs` if [[ "$lastsent" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi fi @@ -1788,13 +1789,13 @@ _sync(){ if [[ "$newsnap" == "" ]] ; then newsnap=`getlastsnap $srcfs` if [[ "$newsnap" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi fi if [[ "$newsnap" == "$lastsent" ]] ; then - print $newsnap already sent + echo $newsnap already sent return 0 fi @@ -1812,7 +1813,7 @@ _sync(){ typeset timeinsec=`zrep_gettimeinseconds` typeset senttimeprop="${ZREPTAG}:sent=$timeinsec" - print sending $newsnap to $desthost:$destfs + echo sending $newsnap to $desthost:$destfs if [[ "$ZREP_RESUME" != "" ]] ; then token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` @@ -1870,8 +1871,8 @@ _sync(){ # So try newer way first. zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname if [[ $? -ne 0 ]] ; then - print WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname - print Using fallback methods. You should go patch $destfs to have newer ZFS version + echo WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname + echo Using fallback methods. You should go patch $destfs to have newer ZFS version zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${newsnap} $destfs zrep_ssh $desthost zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $destfs fi @@ -1969,7 +1970,7 @@ zrep_sync(){ set -- `zrep_list_master` if [[ "$1" == "" ]] ; then # Stay quiet, so we dont spew if in cron - #print No zrep mastered filesystems found + #echo No zrep mastered filesystems found exit fi fi @@ -2019,7 +2020,7 @@ zrep_sync(){ _debugprint $elapsed seconds have elapsed since last sync of $srcfs zrep_errquit quiet time limit of $quiettime seconds exceeded for busy fs $srcfs else - print Quiet mode: skipping busy fs $srcfs at `date` + echo Quiet mode: skipping busy fs $srcfs at `date` return fi fi @@ -2028,7 +2029,7 @@ zrep_sync(){ # Make this message match what zrep_expire uses.. - print Expiring zrep snaps on $srcfs + echo Expiring zrep snaps on $srcfs _expire $srcfs zrep_unlock_fs $srcfs @@ -2358,7 +2359,7 @@ _expire(){ remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` - print Also running expire on $remotehost:$remotefs now... + echo Also running expire on $remotehost:$remotefs now... sanity=`zrep_ssh $remotehost $ZFSGETLVAL ${ZREPTAG}:master $remotefs` # Normally, dont quit on error. But this is super-bad. @@ -2367,7 +2368,7 @@ _expire(){ zrep_errquit "ERROR: Split brain scenario detected" fi - zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||print REMOTE expire failed + zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||echo REMOTE expire failed } @@ -2407,8 +2408,8 @@ zrep_expire() # while [[ "$1" != "" ]] ; do zrep_lock_fs $1 - print Expiring zrep snaps on $1 - _expire $local $1 || print WARNING: expire failed for $1 + echo Expiring zrep snaps on $1 + _expire $local $1 || echo WARNING: expire failed for $1 zrep_unlock_fs $1 shift done @@ -2460,18 +2461,18 @@ zrep_failover(){ remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` - print Setting readonly on local $fs, then syncing + echo Setting readonly on local $fs, then syncing zfs set readonly=on $fs if ((local ==1)) ; then - print Failover for $1 in LOCAL mode + echo Failover for $1 in LOCAL mode if [[ "$snap" == "" ]] ; then snap=`getlastsnapsent $1` zfs list $1 >/dev/null || zrep_errquit No last synced snap found for $1. Cannot fail over - print Rolling back to last sync $snap + echo Rolling back to last sync $snap else - print Rolling back to specified snap $snap + echo Rolling back to specified snap $snap fi zfs rollback -Rr $snap ||zrep_errquit Rollback to $snap failed @@ -2485,9 +2486,9 @@ zrep_failover(){ typeset snapname snapname=${snap#*@} - print Rolling back to local $snap + echo Rolling back to local $snap zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed - print Rolling back $remotehost to $remotefs@$snapname + echo Rolling back $remotehost to $remotefs@$snapname zrep_ssh $remotehost zfs rollback $remotefs@$snapname || zrep_errquit remote rollback failed @@ -2499,7 +2500,7 @@ zrep_failover(){ fi - print Reversing master properties for $Z_LOCAL_HOST:$fs + echo Reversing master properties for $Z_LOCAL_HOST:$fs zfs set ${ZREPTAG}:dest-fs=$fs $fs zfs set ${ZREPTAG}:dest-host=$Z_LOCAL_HOST $fs @@ -2511,7 +2512,7 @@ zrep_failover(){ zrep_unlock_fs $fs if (( local ==0)) ;then - print Setting master on $remotehost:$remotefs + echo Setting master on $remotehost:$remotefs zrep_ssh $remotehost $ZREP_PATH takeover -L $remotefs fi @@ -2556,7 +2557,7 @@ zrep_takeover(){ if (( local == 0 )) ; then - print Starting failover from remote side $remotehost + echo Starting failover from remote side $remotehost zrep_ssh $remotehost $ZREP_PATH failover $remotefs # This will ssh back into us to set src host property, # and the other stuff, so we dont have to do that now. @@ -2580,18 +2581,18 @@ zrep_takeover(){ zfs inherit readonly $fs if [[ "$snap" != "" ]] ; then - print "WARNING: Before takeover, we will be rolling $fs" - print -n " to $snapname, made at: " + echo "WARNING: Before takeover, we will be rolling $fs" + echo -n " to $snapname, made at: " $ZFSGETVAL creation $snap - print "" - print "All newer snapshots will be destroyed" - print Continuing in 10 seconds... + echo "" + echo "All newer snapshots will be destroyed" + echo Continuing in 10 seconds... sleep 10 zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed fi - print Setting master properties for $Z_LOCAL_HOST:$fs + echo Setting master properties for $Z_LOCAL_HOST:$fs zfs set ${ZREPTAG}:src-fs=$fs $fs zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $fs @@ -2603,7 +2604,7 @@ zrep_takeover(){ # Since we default to creating replicas unmounted... mount it now if [[ "`$ZFSGETVAL type $fs`" == "filesystem" ]] ; then if [[ "`$ZFSGETVAL mounted $fs`" == "no" ]] ; then - print Mounting $Z_LOCAL_HOST:$fs + echo eMounting $Z_LOCAL_HOST:$fs zfs mount $fs fi fi @@ -2616,51 +2617,49 @@ zrep_takeover(){ ######## zrep_top continues here usage(){ - print zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another - print in an ongoing basis. - print - print " (c) Philip Brown, 2012-2020" - print - print Simple usage summary: - print 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' - print 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' - print 'zrep (sync|-S) [-f] [-v] [-q seconds] all' - print 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' - print 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' - print 'zrep failover [-L] ZFS/fs [remotehost]' - print 'zrep takeover [-L] ZFS/fs [remotehost]' - print - print "Status and side operations" - print 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' - print 'zrep (status|-s) -m [ZFS/fs]' - print 'zrep (list|-l) [-Lv] [fs/names]' - print 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' - print 'zrep getlastsent ZFS/fs -- prints out snapshot of last successful sync sent' -# print 'zrep setlastsent ZFS/fs snapname -- overrides value of last known snapshot synced' - print 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' - print 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' - print 'zrep version' - print 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' - print 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' - print 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' - print - print ' -q option to sync says to Quietly ignore locked filesystems' - print ' that have synced more recently than the given amount of seconds' - print - print 'Paired commands for high-transaction systems:' - print ' zrep snaponly (ZFS/fs ... |all)' - print ' zrep synconly (ZFS/fs ...|all)' - print 'The above two commands split the simple sync subcommand, into two' - print 'separate steps, so that a database, etc. may resume while the sync' - print 'completes in the background' - print '' - print 'zrep defaults to using ssh. However, if remotehost is set to localhost,' - print 'zrep will use a simple pipe instead.' - print '' - print ' More detailed examples can be found at:' - print http://www.bolthole.com/solaris/zrep/zrep.documentation.html - print '' - print 'See the above documentation for details on using the -t flag to use zrep tags' + echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another + echo in an ongoing basis. + echo + echo " Philip Brown, 2012-2019" + echo + echo Simple usage summary: + echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' + echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' + echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' + echo 'zrep failover [-L] ZFS/fs' + echo 'zrep takeover [-L] ZFS/fs' + echo + echo "Status and side operations" + echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' + echo 'zrep (list|-l) [-Lv] [fs/names]' + echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' + echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' + echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' + echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' + echo 'zrep version' + echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' + echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' + echo + echo ' -q option to sync says to Quietly ignore locked filesystems' + echo ' that have synced more recently than the given amount of seconds' + echo + echo 'Paired commands for high-transaction systems:' + echo ' zrep snaponly (ZFS/fs ... |all)' + echo ' zrep synconly (ZFS/fs ...|all)' + echo 'The above two commands split the simple sync subcommand, into two' + echo 'separate steps, so that a database, etc. may resume while the sync' + echo 'completes in the background' + echo '' + echo 'zrep defaults to using ssh. However, if remotehost is set to localhost,' + echo 'zrep will use a simple pipe instead.' + echo '' + echo ' More detailed examples can be found at:' + echo http://www.bolthole.com/solaris/zrep/zrep.documentation.html + echo '' + echo 'See the above documentation for details on using the -t flag to use zrep tags' } # @@ -2838,9 +2837,9 @@ case "$1" in exit $? ;; version) - print "zrep $ZREP_VERSION" - print "http://www.bolthole.com/solaris/zrep" - print "http://www.github.com/bolthole/zrep" + echo "zrep $ZREP_VERSION" + echo "http://www.bolthole.com/solaris/zrep" + echo "http://www.github.com/bolthole/zrep" exit ;; diff --git a/zrep.spec b/zrep.spec index 636fab6..fcfed18 100644 --- a/zrep.spec +++ b/zrep.spec @@ -2,7 +2,7 @@ Name: zrep # change version AND nversion to "master" to get latest raw git commits -%define nversion 1.8.0 +%define nversion 2.0.0 #this would need to be updated for every version as well, if it actualy worked %global commit e02f8ba5475c8d45ebfb09d8c53021a3d2dab537 diff --git a/zrep_failover b/zrep_failover index 89356a3..1d3a3cf 100644 --- a/zrep_failover +++ b/zrep_failover @@ -44,18 +44,18 @@ zrep_failover(){ remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` - print Setting readonly on local $fs, then syncing + echo Setting readonly on local $fs, then syncing zfs set readonly=on $fs if ((local ==1)) ; then - print Failover for $1 in LOCAL mode + echo Failover for $1 in LOCAL mode if [[ "$snap" == "" ]] ; then snap=`getlastsnapsent $1` zfs list $1 >/dev/null || zrep_errquit No last synced snap found for $1. Cannot fail over - print Rolling back to last sync $snap + echo Rolling back to last sync $snap else - print Rolling back to specified snap $snap + echo Rolling back to specified snap $snap fi zfs rollback -Rr $snap ||zrep_errquit Rollback to $snap failed @@ -69,9 +69,9 @@ zrep_failover(){ typeset snapname snapname=${snap#*@} - print Rolling back to local $snap + echo Rolling back to local $snap zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed - print Rolling back $remotehost to $remotefs@$snapname + echo Rolling back $remotehost to $remotefs@$snapname zrep_ssh $remotehost zfs rollback $remotefs@$snapname || zrep_errquit remote rollback failed @@ -83,7 +83,7 @@ zrep_failover(){ fi - print Reversing master properties for $Z_LOCAL_HOST:$fs + echo Reversing master properties for $Z_LOCAL_HOST:$fs zfs set ${ZREPTAG}:dest-fs=$fs $fs zfs set ${ZREPTAG}:dest-host=$Z_LOCAL_HOST $fs @@ -95,7 +95,7 @@ zrep_failover(){ zrep_unlock_fs $fs if (( local ==0)) ;then - print Setting master on $remotehost:$remotefs + echo Setting master on $remotehost:$remotefs zrep_ssh $remotehost $ZREP_PATH takeover -L $remotefs fi @@ -140,7 +140,7 @@ zrep_takeover(){ if (( local == 0 )) ; then - print Starting failover from remote side $remotehost + echo Starting failover from remote side $remotehost zrep_ssh $remotehost $ZREP_PATH failover $remotefs # This will ssh back into us to set src host property, # and the other stuff, so we dont have to do that now. @@ -164,18 +164,18 @@ zrep_takeover(){ zfs inherit readonly $fs if [[ "$snap" != "" ]] ; then - print "WARNING: Before takeover, we will be rolling $fs" - print -n " to $snapname, made at: " + echo "WARNING: Before takeover, we will be rolling $fs" + echo -n " to $snapname, made at: " $ZFSGETVAL creation $snap - print "" - print "All newer snapshots will be destroyed" - print Continuing in 10 seconds... + echo "" + echo "All newer snapshots will be destroyed" + echo Continuing in 10 seconds... sleep 10 zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed fi - print Setting master properties for $Z_LOCAL_HOST:$fs + echo Setting master properties for $Z_LOCAL_HOST:$fs zfs set ${ZREPTAG}:src-fs=$fs $fs zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $fs @@ -187,7 +187,7 @@ zrep_takeover(){ # Since we default to creating replicas unmounted... mount it now if [[ "`$ZFSGETVAL type $fs`" == "filesystem" ]] ; then if [[ "`$ZFSGETVAL mounted $fs`" == "no" ]] ; then - print Mounting $Z_LOCAL_HOST:$fs + echo eMounting $Z_LOCAL_HOST:$fs zfs mount $fs fi fi diff --git a/zrep_snap b/zrep_snap index 8301f0c..137850c 100644 --- a/zrep_snap +++ b/zrep_snap @@ -55,7 +55,7 @@ getlastsnapsent(){ lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1 | sort | tail -1` if [[ "$lastsent" != "" ]] ; then - print $lastsent + echo $lastsent return fi @@ -107,7 +107,7 @@ getunsentcount(){ zrep_errquit Error: cannot parse $lastsnap fi - print $(( $lastsnapseq - $lastsyncedseq )) + echo $(( $lastsnapseq - $lastsyncedseq )) } @@ -120,7 +120,7 @@ getunsentcount(){ # # In future, may take optional argument of which HOST to check # sync with. But since I currently only suport one host per fs... oh well. -# If never synced, will return 1, and print "" +# If never synced, will return 1, and echo "" # getlastsynctime(){ typeset fs lastsent senttime @@ -137,11 +137,11 @@ getlastsynctime(){ lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $fs | sort | tail -1` senttime=`zfs get -H -o value ${ZREPTAG}:sent $lastsent` - if [[ "$senttime" != "" ]] ; then print $senttime ; return 0; fi + if [[ "$senttime" != "" ]] ; then echo $senttime ; return 0; fi # ooops. try fallback to nasty old zfs-compat style senttime=`zfs get -H -o value ${ZREPTAG}:lastsent $fs` - print $senttime + echo $senttime if [[ "$senttime" != "" ]] ; then return 0; fi return 1 } @@ -207,7 +207,7 @@ makesnap(){ check="`$ZFSGETVAL ${ZREPTAG}:src-host $1`" if [[ "$check" != "$Z_LOCAL_HOST" ]] ; then _errprint ERROR: we are not master host for $1 - _errprint print master is $check, we are $Z_LOCAL_HOST + _errprint master is $check, we are $Z_LOCAL_HOST exit 1 fi @@ -229,7 +229,7 @@ makesnap(){ zfs snapshot $Z_SNAP_R $newsnap if [[ $? -eq 0 ]] ; then - print $newsnap; return 0 + echo -n $newsnap; return 0 else return 1 fi @@ -246,12 +246,12 @@ zrep_clear(){ zrep_errquit clear command requires an argument fi - print "WARNING: Removing all zrep configs and snapshots from $1" - print " (for TAG=${ZREPTAG})" + echo "WARNING: Removing all zrep configs and snapshots from $1" + echo " (for TAG=${ZREPTAG})" if [[ "$ZREP_FORCE" != "-f" ]] ; then - print Continuing in 10 seconds + echo Continuing in 10 seconds sleep 10 fi @@ -260,7 +260,7 @@ zrep_clear(){ } _clearsnaps(){ - print Destroying any zrep-related snapshots from $1 + echo Destroying any zrep-related snapshots from $1 snaplist=`list_autosnaps $1` for snap in $snaplist ; do zfs destroy -r $snap @@ -268,7 +268,7 @@ _clearsnaps(){ } _clearvars(){ - print Removing zrep-related properties from $1 + echo Removing zrep-related properties from $1 proplist=`zfs get -H -o property all $1|grep ${ZREPTAG}:` for prop in $proplist ; do zfs inherit $prop $1 @@ -340,8 +340,8 @@ setfsconfigs(){ # With our curent workflow, that can ONLY WORK # if recent versions of ZFS are in use. if (( Z_HAS_REC_U )) ; then - print WARNING: forcing override sync to top level pool $desthost:$destfs - print Not using a usual sub-filesystem + echo WARNING: forcing override sync to top level pool $desthost:$destfs + echo Not using a usual sub-filesystem sleep 5 else zrep_errquit older zfs version, cannot initialize top level pool @@ -384,7 +384,7 @@ zrep_init(){ #sanity checks check="`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs`" if [[ "$check" != "-" ]] ; then - print "$srcfs is at least partially configured by zrep" + echo "$srcfs is at least partially configured by zrep" check="`$ZFSGETLVAL ${ZREPTAG}:master $srcfs`" if [[ "$check" != "" ]] ; then zrep_errquit "${ZREPTAG}:master detected!!" @@ -405,7 +405,7 @@ zrep_init(){ zrep_errquit Partial init of $src detected but no resume token found. Suggest you zrep clear and start again fi - print "Partially complete init detected. Attempting to resume send" + echo "Partially complete init detected. Attempting to resume send" if [[ "$BBCP" != "" ]] ; then SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" @@ -454,7 +454,7 @@ zrep_init(){ recv_s=-s fi - print Setting zrep properties on $srcfs + echo Setting zrep properties on $srcfs setfsconfigs $srcfs $desthost $destfs #setfsconfigs may do some "smarts" to adjust value, so get it again. @@ -465,10 +465,10 @@ zrep_init(){ READONLYPROP="-o readonly=on" else READONLYPROP="" - print Warning: zfs recv lacking -o readonly + echo Warning: zfs recv lacking -o readonly fi if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - print Creating destination filesystem as separate step + echo Creating destination filesystem as separate step # normally would want to use -o readonly here. # however, that breaks when -R is used. # set it after transfer instead @@ -476,15 +476,14 @@ zrep_init(){ fi snap="${srcfs}@${ZREPTAG}_000000" - print Creating snapshot $snap + echo Creating snapshot $snap zfs snapshot $Z_SNAP_R $snap || clearquit $srcfs "Cannot create initial snapshot $snap" # Note that we may not want to use -p for normal zrep syncs # We also should not use -F for normal recv. See workflow.txt # Note: we may have to reset readonly=on, if we used -p on send... # - print Sending initial replication stream to $desthost:$destfs - + echo Sending initial replication stream to $desthost:$destfs if (( Z_HAS_REC_U )) ; then # This is the nice, clean, modern codepath, to send # zrep settings over automatically at first init. @@ -546,7 +545,7 @@ zrep_init(){ fi if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - print setting readonly on $desthost:$destfs manually + echo setting readonly on $desthost:$destfs manually zrep_ssh $desthost zfs set readonly=on $destfs fi @@ -571,7 +570,7 @@ zrep_init(){ zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${snap} $destfs fi if [[ "$mountpoint" != "" ]] ; then - print "clearing mountpoint value for remote" + echo "clearing mountpoint value for remote" zrep_ssh $desthost zfs inherit mountpoint $destfs fi @@ -587,9 +586,9 @@ zrep_init(){ # Make sure value this matches zrep_sentsync zfs set ${ZREPTAG}:master=yes $srcfs - print Initialization copy of $srcfs to $desthost:$destfs complete + echo Initialization copy of $srcfs to $desthost:$destfs complete if (( Z_HAS_REC_U )) ; then - print Filesystem will not be mounted + echo Filesystem will not be mounted fi } diff --git a/zrep_status b/zrep_status index 35ec609..62535be 100644 --- a/zrep_status +++ b/zrep_status @@ -10,8 +10,8 @@ # #Give this a top level zrep registered filesystem, NOT snapshot. -# Will print out various status points, such as last sync date. -# Or if given no args, will print out sync date for all zrep mastered fs +# Will echo out various status points, such as last sync date. +# Or if given no args, will echo out sync date for all zrep mastered fs # Note that the date given is time of SNAPSHOT, not time sync completed. # zrep_status(){ @@ -84,7 +84,7 @@ zrep_status(){ printf "%-25s->%-35s %s\n" $srcfs "$desthost:$destfs" "$vdate" else printf "%-52s " $srcfs - print "last: $date" + echo "last: $date" fi shift done @@ -111,7 +111,7 @@ zrep_list_master(){ for fs in `_master_fs_names "$@"` ; do srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` if [[ "$srchost" == "$Z_LOCAL_HOST" ]] ; then - print $fs + echo $fs fi done } @@ -120,13 +120,13 @@ zrep_list_master(){ # Given ONE filesystem, print all zrep properties for it. # Note that this is internal routine. we do not validate input. list_verbose(){ - print $1: + echo $1: # sneaky cheat: only user-set properties will # match these 'source' types. So "grep zrep:" is not # neccessary. Although we may pick up other user-set values, # but that is not neccessarily a bad thing zfs get -H -o property,value -s $PROPTYPES all $1 - print "last snapshot synced: `getlastsnapsent $1`" + echo "last snapshot synced: `getlastsnapsent $1`" } @@ -198,13 +198,13 @@ zrep_list(){ # oh well. have to step through them one by one now, to - # print out the properties associated with each zrep filesystem + # echo out the properties associated with each zrep filesystem fslist=`zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs` for fs in $fslist ; do $printcmd $fs - print "" + echo "" done @@ -239,7 +239,7 @@ zrep_getlastsent(){ exit 1 fi - print `getlastsnapsent $1` + echo `getlastsnapsent $1` } diff --git a/zrep_sync b/zrep_sync index 55b6bb5..ea27b56 100644 --- a/zrep_sync +++ b/zrep_sync @@ -9,7 +9,7 @@ ## file-internal routine that gets used a lot in zrep_sync. but not always _gensentprop(){ typeset timeinsec=`zrep_gettimeinseconds` - print "${ZREPTAG}:sent=$timeinsec" + echo "${ZREPTAG}:sent=$timeinsec" } @@ -109,7 +109,7 @@ synctosnap(){ typeset srcfs snapname destsnap if [[ "$desthost" == "" ]] ; then - print ERROR: synctosnap did not receive all required args + echo ERROR: synctosnap did not receive all required args zrep_errquit "args=$@" fi srcfs=${srcsnap%@*} @@ -130,29 +130,29 @@ synctosnap(){ - print Validating remote snap + echo Validating remote snap zrep_ssh $desthost zfs list -t snapshot $destfs@$destsnap >/dev/null if [[ $? -ne 0 ]] ; then zrep_errquit $destfs@$destsnap does not exist. Cannot roll to snap fi - print "WARNING: We will be rolling back $destfs, on $desthost" - print -n " to $snapname, made at: " + echo "WARNING: We will be rolling back $destfs, on $desthost" + echo -n " to $snapname, made at: " $ZFSGETVAL creation $srcsnap - print "" - print "All newer snapshots on remote side will be destroyed" - print "You should have paused ongoing sync jobs for $destfs before continuing" - print "Continuing in 20 seconds...." + echo "" + echo "All newer snapshots on remote side will be destroyed" + echo "You should have paused ongoing sync jobs for $destfs before continuing" + echo "Continuing in 20 seconds...." sleep 10 - print "Continuing in 10 seconds...." + echo "Continuing in 10 seconds...." sleep 10 zrep_lock_fs $srcfs || zrep_errquit "Cannot lock $srcfs" zrep_ssh $desthost zfs rollback -Rr $destfs@$destsnap || zrep_errquit roll failed - print $desthost:$destfs rolled back successfully to $destsnap - print Now cleaning up local snapshots + echo $desthost:$destfs rolled back successfully to $destsnap + echo Now cleaning up local snapshots # need to undo whatever zrep_sync does newsentlist=`getallsnaps $srcfs|sed "1,/@$snapname/d"` @@ -210,7 +210,7 @@ _snapandsync(){ # if we cant do incremental anyway sentsnap=`getlastsnapsent $srcfs` if [[ "$sentsnap" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi @@ -267,7 +267,7 @@ _sync(){ if [[ "$lastsent" == "" ]] ; then lastsent=`getlastsnapsent $srcfs` if [[ "$lastsent" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi fi @@ -275,13 +275,13 @@ _sync(){ if [[ "$newsnap" == "" ]] ; then newsnap=`getlastsnap $srcfs` if [[ "$newsnap" == "" ]] ; then - print zrep_sync could not find sent snap for $srcfs. + echo zrep_sync could not find sent snap for $srcfs. zrep_errquit You must initialize $srcfs for zrep fi fi if [[ "$newsnap" == "$lastsent" ]] ; then - print $newsnap already sent + echo $newsnap already sent return 0 fi @@ -299,7 +299,7 @@ _sync(){ typeset timeinsec=`zrep_gettimeinseconds` typeset senttimeprop="${ZREPTAG}:sent=$timeinsec" - print sending $newsnap to $desthost:$destfs + echo sending $newsnap to $desthost:$destfs if [[ "$ZREP_RESUME" != "" ]] ; then token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` @@ -357,8 +357,8 @@ _sync(){ # So try newer way first. zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname if [[ $? -ne 0 ]] ; then - print WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname - print Using fallback methods. You should go patch $destfs to have newer ZFS version + echo WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname + echo Using fallback methods. You should go patch $destfs to have newer ZFS version zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${newsnap} $destfs zrep_ssh $desthost zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $destfs fi @@ -456,7 +456,7 @@ zrep_sync(){ set -- `zrep_list_master` if [[ "$1" == "" ]] ; then # Stay quiet, so we dont spew if in cron - #print No zrep mastered filesystems found + #echo No zrep mastered filesystems found exit fi fi @@ -506,7 +506,7 @@ zrep_sync(){ _debugprint $elapsed seconds have elapsed since last sync of $srcfs zrep_errquit quiet time limit of $quiettime seconds exceeded for busy fs $srcfs else - print Quiet mode: skipping busy fs $srcfs at `date` + echo Quiet mode: skipping busy fs $srcfs at `date` return fi fi @@ -515,7 +515,7 @@ zrep_sync(){ # Make this message match what zrep_expire uses.. - print Expiring zrep snaps on $srcfs + echo Expiring zrep snaps on $srcfs _expire $srcfs zrep_unlock_fs $srcfs @@ -845,7 +845,7 @@ _expire(){ remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` - print Also running expire on $remotehost:$remotefs now... + echo Also running expire on $remotehost:$remotefs now... sanity=`zrep_ssh $remotehost $ZFSGETLVAL ${ZREPTAG}:master $remotefs` # Normally, dont quit on error. But this is super-bad. @@ -854,7 +854,7 @@ _expire(){ zrep_errquit "ERROR: Split brain scenario detected" fi - zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||print REMOTE expire failed + zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||echo REMOTE expire failed } @@ -894,8 +894,8 @@ zrep_expire() # while [[ "$1" != "" ]] ; do zrep_lock_fs $1 - print Expiring zrep snaps on $1 - _expire $local $1 || print WARNING: expire failed for $1 + echo Expiring zrep snaps on $1 + _expire $local $1 || echo WARNING: expire failed for $1 zrep_unlock_fs $1 shift done diff --git a/zrep_vars b/zrep_vars index 2905d27..780c022 100644 --- a/zrep_vars +++ b/zrep_vars @@ -156,7 +156,7 @@ ZREP_DATEFORMAT=${ZREP_DATEFORMAT:-%Y/%m/%d-%H:%M:%S} _debugprint(){ if [[ "$DEBUG" != "" ]] ; then - print DEBUG: $@ + echo DEBUG: $@ fi } @@ -164,9 +164,9 @@ _debugprint(){ # to make dealing with github issue #22 easier, about redirecting stderr _errprint(){ # I thought /dev/fd was ksh builtin, so safest. But it glitches on some linuxen. - # print $@ >/dev/fd/2 + # echo $@ >/dev/fd/2 - print $@ >&2 + echo $@ >&2 } @@ -337,8 +337,8 @@ else DEPTHCAP="-d 1" # limits "list -r" else DEPTHCAP="" - print WARNING: old ZFS version detected with no depth protection - print WARNING: You may not nest zrep managed filesystems + echo WARNING: old ZFS version detected with no depth protection + echo WARNING: You may not nest zrep managed filesystems fi fi @@ -435,8 +435,8 @@ zrep_get_global_lock(){ done - print Failed to acquire global lock - print Error message was: $errmsg + echo Failed to acquire global lock + echo Error message was: $errmsg return 1 } @@ -446,7 +446,7 @@ zrep_release_global_lock(){ rm $Z_GLOBAL_LOCKFILE return $? else - print ERROR: zrep_release_global_lock called, but do not own lock + echo ERROR: zrep_release_global_lock called, but do not own lock return 1 fi } @@ -542,8 +542,8 @@ zrep_errquit(){ if zrep_has_global_lock ; then if [[ "$$" -ne "$Z_GLOBAL_PID" ]] ; then - print EXTRA-ERROR: Running in child proc. - print 'Not sure whether to release global lock. NOT releasing!' + echo EXTRA-ERROR: Running in child proc. + echo 'Not sure whether to release global lock. NOT releasing!' exit 1 else @@ -600,7 +600,7 @@ zrep_gettimeinseconds(){ if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. - seconds=`perl -e 'print int(time);' 2>/dev/null` + seconds=`perl -e 'echo int(time);' 2>/dev/null` fi if [[ -z "$seconds" ]] ; then # attempt fallback if no perl present (eg: stock FreeBSD) @@ -609,6 +609,6 @@ zrep_gettimeinseconds(){ if [[ -z "$seconds" ]] ; then zrep_errquit zrep_gettimeinseconds doesnt know what to do fi - print $seconds + echo $seconds } From 19d0b52219e09c392b3e5346c2f9c12c54e6be75 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sun, 21 Nov 2021 20:48:11 +0000 Subject: [PATCH 04/15] forced src-host to localhost for both sides, for testing purposes --- tests/setup_zrep.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/setup_zrep.sh b/tests/setup_zrep.sh index e7a4ba6..8e6054e 100755 --- a/tests/setup_zrep.sh +++ b/tests/setup_zrep.sh @@ -10,7 +10,6 @@ echo doing $ZREP_PATH init $SRC localhost $NEWSRC now $ZREP_PATH init $SRC localhost $NEWSRC - # this is normally not neccessary, but because we will be doing someting # "weird", with a zrep refresh, we have to force this From ee011ea92c07170f8a9267236851d3805fb197bf Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sun, 21 Nov 2021 20:49:51 +0000 Subject: [PATCH 05/15] fixed the timestamp for bash --- zrep | 5 ++++- zrep_vars | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/zrep b/zrep index 0291dd5..ae04765 100755 --- a/zrep +++ b/zrep @@ -606,7 +606,10 @@ zrep_ssh(){ zrep_gettimeinseconds(){ typeset seconds typeset PATH=$PERL_BIN:$PATH - seconds=`printf '%(%s)T' now` + # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 + # seconds=`printf '%(%s)T' now` + seconds=`date '+%s'` + if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. diff --git a/zrep_vars b/zrep_vars index 780c022..a099976 100644 --- a/zrep_vars +++ b/zrep_vars @@ -596,7 +596,10 @@ zrep_ssh(){ zrep_gettimeinseconds(){ typeset seconds typeset PATH=$PERL_BIN:$PATH - seconds=`printf '%(%s)T' now` + # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 + # seconds=`printf '%(%s)T' now` + seconds=`date '+%s'` + if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. From e0b4f159328ade9de521db282c37b037ed4c1698 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 22 Nov 2021 17:56:35 +0000 Subject: [PATCH 06/15] extra debug option --- zrep_vars | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/zrep_vars b/zrep_vars index a099976..a025a74 100644 --- a/zrep_vars +++ b/zrep_vars @@ -477,7 +477,13 @@ zrep_lock_fs(){ typeset check=`zrep_fs_lock_pid $1` newcheck if [[ "$check" != "" ]] ; then # See if owning process still exists. - kill -0 $check 2>/dev/null && return 1 + kill -0 $check 2>/dev/null + if [[ $? -eq 0 ]] ; then + _debugprint lock is still held by $check + return 1 + else + _debugprint lock is no longer held by $check + fi fi zrep_get_global_lock From c9349aecd57bfb37c56c44468df9094685aba909 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 22 Nov 2021 22:12:50 +0000 Subject: [PATCH 07/15] bash querks fix in makesnap and zrep_lock_fs --- zrep | 8 +++++++- zrep_snap | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/zrep b/zrep index ae04765..b75cff0 100755 --- a/zrep +++ b/zrep @@ -487,7 +487,13 @@ zrep_lock_fs(){ typeset check=`zrep_fs_lock_pid $1` newcheck if [[ "$check" != "" ]] ; then # See if owning process still exists. - kill -0 $check 2>/dev/null && return 1 + kill -0 $check 2>/dev/null + if [[ $? -eq 0 ]] ; then + _debugprint lock is still held by $check + return 1 + else + _debugprint lock is no longer held by $check + fi fi zrep_get_global_lock diff --git a/zrep_snap b/zrep_snap index 137850c..c3f85c9 100644 --- a/zrep_snap +++ b/zrep_snap @@ -229,6 +229,8 @@ makesnap(){ zfs snapshot $Z_SNAP_R $newsnap if [[ $? -eq 0 ]] ; then + # ksh doesnt need -n, but for some reason, bash does + # otherwise other side gets a leading CR ?? echo -n $newsnap; return 0 else return 1 From 586e004be3cdeb6d9609eee22effa59feb1cf2d6 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 22 Nov 2021 22:13:56 +0000 Subject: [PATCH 08/15] bash querks fix in makesnap and zrep_lock_fs --- zrep | 2871 ---------------------------------------------------------- 1 file changed, 2871 deletions(-) delete mode 100755 zrep diff --git a/zrep b/zrep deleted file mode 100755 index b75cff0..0000000 --- a/zrep +++ /dev/null @@ -1,2871 +0,0 @@ -#!/bin/bash --noprofile - -# For more detailed documentation, see zrep.txt or zrep.overview.txt -# note to self: always make this 3 fields -ZREP_VERSION=2.0.0 - -################################################ - - -######## start of included files from zrep_top here -########### File: zrep_vars - -# This should basically be included as common vars before all zrep stuff. -# It contains all 'constant' definitions, as well as a few crucial -# shared routines, such as lock handling ones. - -######################################################################## -# User tunable section. These may be overridden in user environment vars -# -# Additionally, we check /etc/default/zrep first. -# If you want to allow users to override the options set there, then be sure -# to use similar syntax to below: -# VAR=${VAR:-yourvalue} -# - -if [[ -f /etc/default/zrep ]] ; then - . /etc/default/zrep -fi - -SSH=${SSH:-ssh} -ZREP_PATH=${ZREP_PATH:-zrep} #Set to /full/path/to/zrep, if needed, for remote - -# Set this if you want to use a different zfs property to store zrep info. -# default has things like zrep:dest-host and zrep:dest-fs -# Change this to non-default, if you want to have multiple destinations. -# You then need to run a separate zrep for each dest. -# In this case, I suggest all runs use non-default value. -# eg: ZREPTAG=zrep-1, ZREPTAG=zrep-2. -# or, ZREPTAG=zrep-uk, ZREPTAG=zrep-us -# ** make sure value can be used in a snapshot name as well, because it -# ** will be ! -# !! also, make sure variable is EXPORTED !! -ZREPTAG=${ZREPTAG:-zrep} - -# If you'd like a little extra debug output define this env var to something -#DEBUG=1 - -#If set to yes(default), renames failed sync snapshots to "zrep_#####_unsent" -ZREP_RENAME_UNSENT=${ZREP_RENAME_UNSENT:-yes} - -# The default behavior for up-to-date ZFS filesystems, is that zrep will call -# a zfs send/receieve that creates the remote filesystem with pretty much the same options. -# However, sometimes, it is important to have the remote side have a diferent set of -# options, BEFORE data has been transferred. ie: with remote using compression. -# Note1: If set, this will stop the typical inheritance of src filesystem properties -# Note2: It will force the "old zfs creation style" codepath for init. But not -# for the rest of zrep -#ZREP_CREATE_FLAGS="-o compression=on,xx=yy" - -#Convenience hook. -#By default, the remote fs should start with the same properties that the master has. -#However, sometimes people want different ones. -#If this var is set, a post-init ssh will be made, to set the requested properties. -#While some zfs implementations support multiple values in a single set command, -#Others do not. So if multiple are set here, multiple ssh calls will be made. -# syntax: "prop1=value [prop2=value prop3=value ... ]" -#ZREP_INIT_REMOTE_PROPERTIES="compression=on" - - -# The fastest alternative transport, IF you have multicore/thread CPUs, -# would seem to be bbcp. If you have both, then you probably want to -# define something like this in your environment: -#BBCP="bbcp -s 8" - - -# You can apply filters to tweak throughput in various ways -# Sometimes it helps receive performance to use mbuffer -# Other times, you may have highly compressible data, and custom -# compression routines such as lzop or lz4 may show significant -# gains over ssh builtin compression -# -# lz4 example: -# ZREP_OUTFILTER="lz4 -c" -# ZREP_INFILTER="lz4 -d" -# -# mbuffer example: -# ZREP_OUTFILTER="mbuffer -q -m 1G -s 128k" -# ZREP_INFILTER="mbuffer -q -m 1G -s 128k" - - - -# If you want to recursively create/send snapshots, set this to -R -# (or use the -R option to BOTH zrep init and zrep sync -#ZREP_R=-R - - -# Sometimes, people may want to add extra flags to "zfs send". -# If your system supports it, then adding -c means that -# compressed filesystems will be sent in compressed form, rather -# then autouncompressing -# --raw is sometime used for encrypted filesystems -#ZREP_SEND_FLAGS="-c --raw" - -# Theoretically identical in purpose to ZREP_SEND_FLAGS, but it turns out that when using -# zfs send -t (resume-token) -# you are only allowed to use a restricted set of flags. It normally uses what was -# set the first time. -# So, for resume sends, we clear send flags, and only use what is set here below -#ZREP_RESUME_FLAGS=-v - - - -# There are two types of zfs incremental snapshots. -# The default incremental flag is -I. -# UNLESS you set ZREPTAG to something other than zrep, in which case -# you will have multiple zrep snapshot names probably going to different -# places, and expiration wont work properly on the remote sides -# So we will autochange incremental type to -i. .. unless you explicitly -# set an override value for INC_FLAG in either case. -# Probably should have named this INCR_FLAG, but it's in use now -#ZREP_INC_FLAG=-I - -# This currently doesnt do much, and is probably best not user-set. -# I should probably make use of this more standardized. -# But you can set it if you want -#ZREP_VERBOSE=yes - -# Some odd people like to configure a non-root user for zrep, -# and not give it expire permissions. -# Well.. okay then... -#ZREP_SKIP_EXPIRE=1 - -# If you want to override uname -n, with an official -# canonical name for zrep:src-host, use this environment variable when -# you run "zrep init" -#ZREP_SRC_HOST=somehost.your.dom - -# Solaris hack to use native perl, which isnt always in $PATH,but should -# always be there. It's also simple, straightforward, and non-extended. -# On other OSs this path will not exist, so it will just fall back to -# use the system default perl. Unless user wants to specify perl path. -# If you dont have /usr/perl5, this wont hurt you so just ignore it. -PERL_BIN=${PERL_BIN:-/usr/perl5/bin} - - -# Hidden var, that isnt really meant to be used directly. -# It gets set if you use "zrep sync -c". -# But you could theoretically set this directly instead if you prefer -#ZREP_CHANGEDONLY=yes - - -# This only gets used at init time -ZREP_SAVE_COUNT=${ZREP_SAVE_COUNT:-5} - -# This currently is only used in zrep status -# If you change it to %s then zrep will display -# seconds since the most resent snapshot was synced. -ZREP_DATEFORMAT=${ZREP_DATEFORMAT:-%Y/%m/%d-%H:%M:%S} - - -######################################################################### -######################################################################### -# Everything else below here, should not need to be touched. -######################################################################### -######################################################################### - -_debugprint(){ - if [[ "$DEBUG" != "" ]] ; then - echo DEBUG: $@ - fi -} - -# This consolidated function is both for prettiness, and also -# to make dealing with github issue #22 easier, about redirecting stderr -_errprint(){ - # I thought /dev/fd was ksh builtin, so safest. But it glitches on some linuxen. - # echo $@ >/dev/fd/2 - - echo $@ >&2 - -} - - -# First we have some "special" internal vars. -# Then autodetect routines, -# and then internal utilities such as locking functions. - - -# zfs get syntax is so long and ugly, this is just an internal convenience -# Get a zfs property on fs or snap. Get JUST the value, and only -# a "locally set" value rather than an inherited one -ZFSGETLVAL="zfs get -H -o value -s local" -# But.. sometimes you want to allow propagated values. like -# the ones sent via the zrep_init setup -ZFSGETVAL="zfs get -H -o value" -# **warning** !! -# $ZFSGETLVAL returns "" on value not set. However, -# $ZFSGETVAL returns "-" on value not set. Grrr @zfs writers. - - -# Work around a bug in gentoo ksh that breaks "ls -l" builtin. -# It follows symlinks. -# Would prefer to just override with _AST_FEATURES, but apparently, -# that only gets checked when ksh first starts or something -# Maybe use getconf itself somehow, if safe? -# Trick would be to do -# export _AST_FEATURES="PATH_RESOLVE = physical" -# but cant. -#### This is not even used any more!! But I'm keeping it in as -#### documentation for historical knowlege -#if getconf PATH_RESOLVE > /dev/null 2>&1 ; then -# LS=/bin/ls -#else -# LS=ls -#fi - -# -n enforces "no dereference" of existing symlink, which is default -# behaviour on some, but not all systems, apparently -#LN_S="ln -n -s" -# I dont use ln any more, but leaving this for useful historical info. -# side note: ksh built-in ln, DOES NOT SUPPORT -n ! - - -if [[ "$ZREP_SRC_HOST" != "" ]] ; then - Z_LOCAL_HOST=${ZREP_SRC_HOST} -else - Z_LOCAL_HOST=`uname -n` - Z_LOCAL_HOST=${Z_LOCAL_HOST%%.*} -fi - - -# Slightly ugly to implement this as a global flag. But... it makes -# a lot of things simpler, like "ssh zrep xyhz" for multiple things. -if [[ "$1" == "-R" ]] ; then - ZREP_R="-R" - shift -fi - -if [[ "$ZREP_R" == "-R" ]] ; then - # ZREP_R is a user-settable env var. It also gets used in - # "zfs send" commandlines. However.. we also need to call - # "zfs snap".. which requires LOWERCASE R. So this is an automatically set - # mirror of that. - Z_SNAP_R="-r" -fi - -if [[ "$MBUFFER" != "" ]] ; then - _errprint WARNING: MBUFER variable deprecated in zrep - _errprint WARNING: use ZREP_OUTFILTER and ZREP_INFILTER instead - ZREP_OUTFILTER="$ZREP_OUTFILTER $MBUFFER" - ZREP_INFILTER="$MBUFFER $ZREP_INFILTER" -fi - -# I HATE having to use a global for this. -# However, there is apparently a bug in typeset behaviour -# introduced in ksh93. UGHHH. -#ZREP_FORCE="-f" - -# Sneaky vars to avoid having to use if clauses in the core code -# HOWEVER! Note that ksh doesnt seem to evaluate pipe symbols when normaly -# expanded in a command line. So, have to use 'eval' to get them to register -if [[ "$ZREP_OUTFILTER" != "" ]] ; then - Z_F_OUT="| $ZREP_OUTFILTER" -fi -if [[ "$ZREP_INFILTER" != "" ]] ; then - Z_F_IN="$ZREP_INFILTER |" -fi - - -# used to have polymorphic assign of ZREP_INC_FLAG here, but had -# to move it to AFTER checking if -t option used - -# full name for this should probably be something like, -# PROPTYPES_THAT_ZREP_STATUS_AND_LIST_CAN_USE. But that's too long :) -# Not easy to check if property types allow type "received". -# Ancient systems do not allow it -# So, just tie this to MU6 related check,like HAS_SNAPPROPS, lower down -PROPTYPES="local,received" - - - -# dump the usage message, and check for capabilities -# make sure we dont spew for non-root, so that "zrep status" works -case `id` in - *'(root)'*) - ZREP_RUNDIR=${ZREP_RUNDIR:-/var/run} - ;; - *) - ZREP_RUNDIR=${ZREP_RUNDIR:-/tmp} - ;; -esac -# allow override, for code test utility -zrep_checkfile=${_ZREP_CHECKFILE:-$ZREP_RUNDIR/zrep.check.$$} - - -zfs >$zrep_checkfile 2>&1 -# Previously did a bit of a hack job for feature detection. -# Now attempting to make it smarter, -# at the expense of some startup speed :( -Z_HAS_X=0 # can use recv -x -Z_HAS_REC_U=0 # can use recv -u -Z_HAS_REC_O=0 # can use recv -o (note: SmartOS -o is NOT WHAT WE WANT) -Z_HAS_SNAPPROPS=0 - -if grep 'help' $zrep_checkfile >/dev/null ;then - # Presume Solaris 11, which has all features, but - # does not allow line-by-line feature detection easily - Z_HAS_X=1 # can use recv -x - Z_HAS_REC_U=1 # can use recv -u - Z_HAS_REC_O=1 # can use recv -o - Z_HAS_SNAPPROPS=1 # can set properties on snapshots - # This also lets me set "last synced" timestamps - # otherwise cant use zrep:sent sanely. - # Would lose information on rollbacks - - DEPTHCAP="-d 1" # limits "list -r" - -else - if grep 'receive[ |].*-[a-zA-Z]*x' $zrep_checkfile >/dev/null ;then - Z_HAS_X=1 # can use recv -x - fi - if grep 'receive .*-[a-zA-Z]*u' $zrep_checkfile >/dev/null ;then - Z_HAS_REC_U=1 # can use recv -u - fi - # This bit is unfortunately ugly. Two problems: - # SmartOS and FreeBSD implemented recv -o WRONG! - # They use -o to set "origin", not to set options - # So no Z_HAS_REC_O for it! - # But also, -o doesnt even show in the output of solaris zfs usage. sigh. - # So have to be creative. - # Note that some systems have '-o' directly after create, and some do not. - if grep 'create .*-o prop' $zrep_checkfile >/dev/null ;then - # This is probably nested under the create check, because we - # only use recv -o, right after using create -o. - # However, we now ALWAYS use create -o, so.. may be unneccesary - # to nest - # Skip zfs that uses recv -o origin - if ! grep 'rec[ev].*-o origin' $zrep_checkfile >/dev/null && - grep 'rec[ev].*-o ' $zrep_checkfile >/dev/null ; then - Z_HAS_REC_O=1 # can use recv -o - fi - fi - if grep 'set .*snapshot' $zrep_checkfile >/dev/null ;then - Z_HAS_SNAPPROPS=1 # can set properties on snapshots - fi - if grep 'list.*-d' $zrep_checkfile >/dev/null ;then - DEPTHCAP="-d 1" # limits "list -r" - else - DEPTHCAP="" - echo WARNING: old ZFS version detected with no depth protection - echo WARNING: You may not nest zrep managed filesystems - fi -fi - -if ((!Z_HAS_SNAPPROPS)) ; then - PROPTYPES="local" -fi - -rm $zrep_checkfile - - -Z_LOCK_RETRY=${Z_LOCK_RETRY:-10} # default 10 second retry, 1 per sec - -# This is named like a global override. and CAN be overridden by user. -# But should only be used in zrep_vars module -# Note: This path is why you should only give zfs privileges to a SINGLE USER. -Z_GLOBAL_LOCKFILE=$ZREP_RUNDIR/zrep.lock - -if [[ "$Z_GLOBAL_PID" == "" ]] ; then - export Z_GLOBAL_PID=$$ -fi - -Z_SETHOLD=${Z_SETHOLD:-"zfs hold"} -# if your zfs isnt new enough, and you like to live dangerously, -# you can skip setting holds by using this instead. -# Although I may not have gotten around to using this in the code either! -#Z_SETHOLD="echo skipping zfs hold on" - - -# return PID of proc holding global lock, or nothing -zrep_global_lock_pid(){ - cat $Z_GLOBAL_LOCKFILE 2>/dev/null -} - -# return 0 if "we" are holding lock, 1 otherwise -# Note that we check for "us, OR our global parent", if different -# -zrep_has_global_lock(){ - lockpid=`zrep_global_lock_pid` - - if [[ "$lockpid" == "" ]] ; then return 1 ; fi - if [[ "$lockpid" != "$Z_GLOBAL_PID" ]] ; then - if [[ "$lockpid" != "$$" ]] ; then - _debugprint 'has_global_lock? no. lock held by PID' $lockpid - return 1 - fi - fi - - return 0 -} - - - -#Note: it is an ERROR to call this if you already have lock -#It is binary, not recursive ownership. -#We do NOT try to clean up stale global lock. -#This is a shortterm lock. It should never be stale. If it is, -#it could indicate a more serious system/zrep problem happening. -zrep_get_global_lock(){ - typeset retry_count=$Z_LOCK_RETRY - typeset lockpid - - set -C #noclobber - # ignore error this time, because we retry anyway. - echo $Z_GLOBAL_PID > $Z_GLOBAL_LOCKFILE 2>/dev/null && return 0 - - # Otherwise, deal with fail/retry. - # Careful of race conditions on stale CLEAN UP! - # How to resolve problem where - # * multiple instances running - # * one instance detects stale - # * multiple instances decide to remove it - # * ONE removes it and creates new symlink - # * SECOND one was paused between detection and removal.. so removes - # * VALID lockfile?!?! - # For now, must request manual cleanup - while (( retry_count > 0 )); do - sleep 1 - errmsg=`echo $Z_GLOBAL_PID 2>&1 > $Z_GLOBAL_LOCKFILE ` - if [[ $? -eq 0 ]] ; then return 0 ; fi - - retry_count=$((retry_count-1)) - lockpid=`zrep_global_lock_pid` - if [[ "$lockpid" -le 0 ]] ; then - zrep_errquit ERROR: invalid contents for global lock file $Z_GLOBAL_LOCKFILE - fi - # Does the process holding the lock actually still exist? - # In theory, teenietiny chance of race condition for false stale. That's okay. - kill -0 $lockpid 2>/dev/null - if [[ $? -ne 0 ]] ; then - _errprint ERROR: stale global lock file - _errprint ERROR: shut down ALL zrep instances, then manually remove - _errprint $Z_GLOBAL_LOCKFILE - fi - - done - - echo Failed to acquire global lock - echo Error message was: $errmsg - return 1 - -} - -zrep_release_global_lock(){ - if zrep_has_global_lock ; then - rm $Z_GLOBAL_LOCKFILE - return $? - else - echo ERROR: zrep_release_global_lock called, but do not own lock - return 1 - fi -} - -# returns PID of zrep process holding a lock on filesystem, if there is one. -# NOTE: If "-s local" used, prints "" if lock unheld -# If no -s specified, prints "-" if lock unheld -zrep_fs_lock_pid(){ - $ZFSGETLVAL ${ZREPTAG}:lock-pid $1 -} -zrep_has_fs_lock(){ - typeset check=`$ZFSGETLVAL ${ZREPTAG}:lock-pid $1` - if ((check == $$)) ; then - return 0 - else - return 1 - fi -} - -# use global lock first (if not already), then -# grab lock on individual fs -# return 1 on fail, 0 on lock acquired -# Note that it is an ERROR to call this, if you already have lock -# Note2: if a dead process has lock, it will forcibly override and -# acqure lock -zrep_lock_fs(){ - # global lock is slow. so do quickcheck first. - typeset check=`zrep_fs_lock_pid $1` newcheck - if [[ "$check" != "" ]] ; then - # See if owning process still exists. - kill -0 $check 2>/dev/null - if [[ $? -eq 0 ]] ; then - _debugprint lock is still held by $check - return 1 - else - _debugprint lock is no longer held by $check - fi - fi - - zrep_get_global_lock - if [[ $? -ne 0 ]] ; then - if [[ "$DEBUG" != "" ]] ; then - _errprint zrep_lock_fs: failed to get global lock. PID=$$ fs=$1 - fi - return 1 - fi - - # Yes we already checked this, but we didnt have global lock. - # Avoid race condition and doublecheck now that we have global lock. - if [[ "$check" != "" ]] ; then - newcheck=`zrep_fs_lock_pid $1` - if [[ "$newcheck" != "$check" ]] && [[ "$newcheck" != "" ]] - then - # oops. someone else beat us to it. - # Better luck next time. - zrep_release_global_lock - return 1 - fi - # Keep in mind that stdin/out could be busy - # Cant use regular debugprint - if [[ "$DEBUG" != "" ]] ; then - _errprint overiding stale lock on $1 from pid $check - fi - fi - - zfs set ${ZREPTAG}:lock-pid=$$ $1 - zfs set ${ZREPTAG}:lock-time=`date +%Y%m%d%H%M%S` $1 - if [[ "$DEBUG" != "" ]] ; then - _errprint DEBUG: zrep_lock_fs: set lock on $1 - fi - zrep_release_global_lock -} - -# release lock, if we have it. -# Since this could be called by an exit cleanup routine blindly, -# dont exit program if we dont have lock. But do return error -zrep_unlock_fs(){ - typeset lockpid=`zrep_fs_lock_pid $1` - if ((lockpid != $$)) ; then return 1; fi - - #since "we" already have it locked, no need to get global lock first - zfs inherit ${ZREPTAG}:lock-time $1 - zfs inherit ${ZREPTAG}:lock-pid $1 - if [[ "$DEBUG" != "" ]] ; then - _errprint zrep_unlock_fs: unset lock on $1 - fi - - return 0 -} - - -# Quit whole program with error status, outputting args to stderr -# Release global lock if we are holding it -# Unless we're running in parallel batch mode -# I'll need to plan that out more carefully! -# -zrep_errquit(){ - _errprint Error: "$@" - - if zrep_has_global_lock ; then - if [[ "$$" -ne "$Z_GLOBAL_PID" ]] ; then - echo EXTRA-ERROR: Running in child proc. - echo 'Not sure whether to release global lock. NOT releasing!' - exit 1 - else - - zrep_release_global_lock - fi - fi - exit 1 -} - -# Optimization wrapper for ssh: if destination host is ourself, dont use ssh. -# Just run the local command mentioned -# Be careful about quotes here. In fact, try not to use any. -# Usage: zrep_ssh desthost commands_for_ssh go_here -zrep_ssh(){ - typeset ssh_cmd - - case "$1" in - localhost|$Z_LOCAL_HOST) - ssh_cmd="eval" - shift - $ssh_cmd "$@" - return $? - ;; - esac - - if [[ "$2" == "$ZREP_PATH "* ]] && [[ "$DEBUG" != "" ]] - then - #okay yes this is horrible. sigh. - #we normally go to great lengths to preserve ssh arg as single quoted string, - # to identically match passed in arg quoting. - #but this next line undoes that - - set -- $* - - ssh_cmd="$SSH $1 $ZREP_PATH -D" - shift - shift - $ssh_cmd "$@" - - return $? - fi - - ssh_cmd="$SSH $1" - shift - $ssh_cmd "$@" - return $? - -} - -zrep_gettimeinseconds(){ - typeset seconds - typeset PATH=$PERL_BIN:$PATH - # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 - # seconds=`printf '%(%s)T' now` - seconds=`date '+%s'` - - if [[ -z "$seconds" ]] ; then - # Unfortunately, solaris date doesnt do '%s', so try perl first. - # It's more consistent. - seconds=`perl -e 'echo int(time);' 2>/dev/null` - fi - if [[ -z "$seconds" ]] ; then - # attempt fallback if no perl present (eg: stock FreeBSD) - seconds=`date +%s` - fi - if [[ -z "$seconds" ]] ; then - zrep_errquit zrep_gettimeinseconds doesnt know what to do - fi - echo $seconds -} - -###### File: zrep_status - -# be sure to have included zrep_vars - -# This file contains all "status" related routines. -# It should be folded into final "zrep" script - - - - -# -#Give this a top level zrep registered filesystem, NOT snapshot. -# Will echo out various status points, such as last sync date. -# Or if given no args, will echo out sync date for all zrep mastered fs -# Note that the date given is time of SNAPSHOT, not time sync completed. -# -zrep_status(){ - typeset check fs srcfs desthost destfs date lastsynced - typeset verbose=0 vdate="" monitorr=0 - typeset printall=0 - - if [[ "$1" == "-v" ]] ; then - verbose=1 ; shift - fi - if [[ "$1" == "-m" ]] ; then - monitor=1 ; shift - fi - - if [[ "$1" == "" ]] ; then - set -- `zrep_list_master` - elif [[ "$1" == "-a" ]] ; then - set -- `zrep_list` - printall=1 - fi - - while [[ "$1" != "" ]] ; do - fs="$1" - - destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` - if [[ "$destfs" == "-" ]] || [[ "$destfs" == "" ]]; then - zrep_errquit "$fs is not a zrep registered filesystem" - fi - - if ((monitor)) ; then - # have to allow for ONE unsent, since it might be in progress - typeset count=`getunsentcount $fs` - if [[ "$count" -gt 1 ]] ; then - echo WARNNING: unset snapshot count at $count - fi - continue - fi - - lastsynced=`getlastsnapsent $fs` - if [[ "$lastsynced" == "" ]] ; then - date="[NEVER]" - else - if (( Z_HAS_SNAPPROPS )) ; then - typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsynced` - date=`printf "%(${ZREP_DATEFORMAT})T" "#$sentseconds"` - vdate=${date%:*} - fi - - #This is also a fallback for no-perl FreeBSD systems - if [[ "$vdate" == "" ]] ; then - date=`$ZFSGETVAL creation $lastsynced` - vdate=${date#????} - fi - fi - if ((printall)) && ((verbose)) ; then - # If we are printing out ALL filesystems, - # then we have to make sure left side is always - # "src filesystem", not "named filesystem" - # then we have to check what the src fs is - srcfs=`$ZFSGETVAL ${ZREPTAG}:src-fs $fs` - else - # Yes, okay, if -a is used, then - # technically, this isnt always "src". - # but it prints out right, so close enough :) - srcfs="$fs" - fi - - if ((verbose)) ; then - desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` - printf "%-25s->%-35s %s\n" $srcfs "$desthost:$destfs" "$vdate" - else - printf "%-52s " $srcfs - echo "last: $date" - fi - shift - done -} - - -_master_fs_names(){ - zfs get -H -o name -s local ${ZREPTAG}:master "$@" -} - - -# convenience function to list only local filesystems for which we are -# zrep master for. -# In contrast, zrep_list, lists ALL zrep registered filesystem, at the moment. -# -# Annoyingly... it would be way faster if we could just stick with the -# pure "zfs get" implementation, but we also need to deal with the zone -# issue. When a single zfs filesystem is visible aross multiple zones, -# we dont want them all thinking they are master -# -# Durn. Individual validation required. -zrep_list_master(){ - typeset srchost - for fs in `_master_fs_names "$@"` ; do - srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` - if [[ "$srchost" == "$Z_LOCAL_HOST" ]] ; then - echo $fs - fi - done -} - - -# Given ONE filesystem, print all zrep properties for it. -# Note that this is internal routine. we do not validate input. -list_verbose(){ - echo $1: - # sneaky cheat: only user-set properties will - # match these 'source' types. So "grep zrep:" is not - # neccessary. Although we may pick up other user-set values, - # but that is not neccessarily a bad thing - zfs get -H -o property,value -s $PROPTYPES all $1 - echo "last snapshot synced: `getlastsnapsent $1`" - -} - - -# Note: called by both user, AND by zrep_status -# -# Usage: -# zrep_list [-v] -# zrep_list [-L] -# zrep_list [-v] fs1 fs2 -#(also zrep_list -s which passes to zrep_list_snaps) -# -# list all zrep-initialized filesystems (NOT snapshots..) -# If no specific fs listed, will show master, AND received filesystems, -# unless -L given (in which case, only local masters will be shown) -# -# Normal output is one line per fs. -# -# -v gives all properties of each filesystem -# Give only one of -L or -v -# -zrep_list(){ - typeset fslist="" verbose=0 - # This works because we only set this property on the actual fs. - # "source type" on snapshots for this property is "inherited" not local - # or "received" - typeset printcmd="zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs" - - case $1 in - -v) - verbose=1 - printcmd=list_verbose - shift - ;; - -L) - # reminder: cant have this, AND verbose. - printcmd="zrep_list_master" - shift - ;; - -s) - shift - zrep_list_snaps "$@" - return - ;; - esac - - - # If specific fs(s) named, iterate over them and quit - if [[ "$1" != "" ]] ; then - while [[ "$1" != "" ]] ; do - if zfs list -t filesystem,volume $1 >/dev/null 2>&1 ; then - $printcmd $1 - else - zrep_errquit "Expecting filesystem, but got $1" - fi - shift - done - return - fi - - # Must be "list all" now. But which output format? - - # If not verbose, we have a nice shortcut to just list - # all filesystems that zrep has marked. - if (( $verbose == 0)) ; then - $printcmd - return - fi - - - # oh well. have to step through them one by one now, to - # echo out the properties associated with each zrep filesystem - - fslist=`zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs` - - for fs in $fslist ; do - $printcmd $fs - echo "" - done - - -} - -# Similar to zrep_list, but lists SNAPSHOTS instead of filesystems -# The purpose is to allow a sysadmin to see easily when snapshots have -# been created. -# Either give a list of specific filesystems, or no args, which -# will attempt to list all zrep-related snapshots -# It will list only zrep MASTER filesystem snapshots, in that case. -zrep_list_snaps(){ - if [[ "$1" == "" ]] ; then - set -- `_master_fs_names` - if [[ "$1" == "" ]] ; then - _errprint "No zrep master filesystems found" - return 0 - fi - fi - - while [[ "$1" != "" ]] ; do - zfs list -r -t snapshot -o name,creation $1 - shift - done - -} - -# Given a filesytem name, prints out full snapshot name of last successfully synced snap -zrep_getlastsent(){ - if [[ "$1" == "" ]] ; then - _errprint ERROR: zrep uptodate requires the name of a zrep managed fs - exit 1 - fi - - echo `getlastsnapsent $1` - -} - -# Give a filesystem name. -# Gets last sent snapshot, and determines if there have -# been any writes since then. -# If not, then file sytem is "up to date" -zrep_uptodate(){ - if [[ "$1" == "" ]] ; then - _errprint ERROR: zrep uptodate requires the name of a zrep managed fs - exit 1 - fi - - typeset bytecount - bytecount=`$ZFSGETVAL written "$1"` - # two fail conditions are: - # 1. not zrep filesystem - # 2. system does not support "zfs get written" - # Either way we count it as "not up to date" - - if [[ $bytecount == 0 ]] ; then - return 0 - else - return 1 - fi -} -################ File: zrep_snap -# be sure to have included zrep_vars - -# This file contains routines related to -# "make new snapshot, using next sequence number". -# So it thus includes all snap sequence related routines -# It may contain "sync snapshot" related routines for now. -# It also is definitive for the format of snapshot names -# It also contains most "query status of snaps" type routines, -# such as "getlastsnapsent" -# -# Normal style for making a snapshot and syncing it: -# 1. create a snapshot. -# 2. sync it over -# 3. set "zrep:sent" on *snapshot*, with timestamp in seconds -# Old-nasty-zfs compat mode: -# Step 3. Add/update "zrep:lastsent->snapname", and -# "zrep:lastsenttime->timestamp", on *filesystem* -# -###################################################################### - - -#pass in a zrep ZFS snapshot name. strip our our sequence number and echo in back -_getseqnum(){ - echo "$1" | sed 's/.*@'${ZREPTAG}'_\(......\).*/\1/' -} - -# By observation, 'zfs list' shows snapshots order of creation. -# last listed, should be last in sequence. -# But, dont take chances!! -getlastsequence(){ - typeset lastval - #remember, filesystems can have '_' in them - _getseqnum `getlastsnap $1` - -} - -# prints out last snapshot zrep created, going purely by sequence. -# Note: "last created", which may or may NOT be "last successfully synced". -# This is basically "getallsnaps |tail -1" -getlastsnap(){ - zfs list -t snapshot -H -o name $DEPTHCAP -r $1 | - sed -n "/@${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/"p | - sort | tail -1 -} - -# Usage: getlastsnapsent zpool/FSNAME -# note to self: in modern zrep, ALL successfuly synced snapshots have -# a timestamp value, zrep:sent=TIMESTAMPHERE -# Thats why the sort and tail is neccessary -getlastsnapsent(){ - # arg. more efficient if we can just return value directly, - # but i'm using backwards compat :( - typeset lastsent - lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1 | - sort | tail -1` - if [[ "$lastsent" != "" ]] ; then - echo $lastsent - return - fi - - # Fallback method, for backwards compat with older ZFS code, - # since it cant set properties on snapshots - zfs get -H -o value -s local ${ZREPTAG}:lastsent $1 -} - -# HORRIBLY DANGEROUS -# ONLY should be used by zrep_sentsync. -# Kind of the inverse to getlastsnapsent. But should NOT use "fallback method" in getlastsnapsent. -# This is only for zrep_sentsync, which only uses newer method. -_clearlast(){ - typeset lastsent - lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1|tail -1` - - while [[ "$lastsent" != "" ]] ; do - _errprint WARNING: clearing sent value from $lastsent - zfs inherit ${ZREPTAG}:sent $lastsent - lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1|tail -1` - done - -} - -# return a number, which is the difference between the lastsnapshot counter, and -# the last successfully synced snapshot counter. -# In theory, can only be positive. -# -getunsentcount(){ - typeset lastsynced lastsnap lastsyncedseq lastsnapseq - lastsynced=`getlastsnapsent $1` - lastsnap=`getlastsnap $1` - - if [[ "$lastsynced" == "$lastsnap" ]] ; then - return 1 - fi - - lastsyncedseq=`getseqnum $lastsynced` - lastsnapseq=`getseqnum $lastsnap` - - lastsyncedseq=$((16#$lastsyncedseq)) - lastsnapseq=$((16#$lastsnapseq)) - - - if [[ "$lastsyncedseq -lt 1 ]] ; then - zrep_errquit Error: cannot parse $lastsynced - fi - if [[ "$lastsnapseq -lt 1 ]] ; then - zrep_errquit Error: cannot parse $lastsnap - fi - - echo $(( $lastsnapseq - $lastsyncedseq )) - -} - -# outputs time in seconds, of when the last successful sync for the -# filesystem was done. (in format compatible with zrep_gettimeinseconds() ) -# Note that this is time of actual sync, not snapshot creation time. -# -# This unfortunately needs to be compatible with both new way, and -# old-nasty-hack-way -# -# In future, may take optional argument of which HOST to check -# sync with. But since I currently only suport one host per fs... oh well. -# If never synced, will return 1, and echo "" -# -getlastsynctime(){ - typeset fs lastsent senttime - - if [[ "$1" == "" ]] ; then - zrep_errquit Internal error: no arg to getlastsynctime - fi - fs="$1" - - # Deal with possibly upgraded system; - # Check "lastsent", only as fallback. - - # copy from getlastsnapsent, but only using newest method - lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $fs | - sort | tail -1` - senttime=`zfs get -H -o value ${ZREPTAG}:sent $lastsent` - if [[ "$senttime" != "" ]] ; then echo $senttime ; return 0; fi - - # ooops. try fallback to nasty old zfs-compat style - senttime=`zfs get -H -o value ${ZREPTAG}:lastsent $fs` - echo $senttime - if [[ "$senttime" != "" ]] ; then return 0; fi - return 1 -} - -#This is for synctosnap, and also zrep_expire -getallsnaps(){ - zfs list -t snapshot -H -o name $DEPTHCAP -r $1 | - sed -n "/@${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/"p | - sort -} - -# list all snapshots of the given filesystem, that are made by this prog -# arg: fs -list_autosnaps(){ - if [[ "$1" == "" ]] ; then - zrep_errquit "zrep internalerror: no arg for list_autosnaps" - fi - zfs list $DEPTHCAP -r -H -o name -t snapshot $1 | - grep "@${ZREPTAG}_[0-9a-f][0-9a-f]" - # Make sure this format matches other routines in here - # Okay to just check first few digits though -} - -# User entrypoint. Part of pair: snaponly, sendonly -# Just makes snapshot. -zrep_snaponly(){ - typeset srcfs - - if [[ "$1" == "all" ]] ; then - set -- `zrep_list_master` - if [[ "$1" == "" ]] ; then - exit - fi - fi - - while [[ "$1" != "" ]] ; do - srcfs="$1" ;shift - zrep_lock_fs $srcfs - if [[ $? -ne 0 ]] ; then - # this function is supposed to be coordinated by user - # therefore, if something else is competing, - # coordination has failed. no retry. - zrep_errquit zrep snaponly failed for $srcfs: cannot get lock - - fi - - makesnap $srcfs ||zrep_errquit snaponly for $srcfs failed - - zrep_unlock_fs $srcfs - - done -} - -# -# creates next snapshot in sequence -# consider holding lock here -# Caller must have zrep lock on filesystem: -# we verify with zrep_has_fs_lock -makesnap(){ - typeset check oldseq newseq="" newseqX newsnap - - #sanity checks first! - check="`$ZFSGETVAL ${ZREPTAG}:src-host $1`" - if [[ "$check" != "$Z_LOCAL_HOST" ]] ; then - _errprint ERROR: we are not master host for $1 - _errprint master is $check, we are $Z_LOCAL_HOST - exit 1 - fi - - zrep_has_fs_lock $1 - if [[ $? -ne 0 ]] ; then - _errprint Internal error: makesnap fail, no lock on $1 - exit 1 - fi - - oldseq=`getlastsequence $1` - # This means input is base 16 - newseq=$((16#$oldseq)) - - newseqX=$(printf "%.6x" $(($newseq + 1)) ) - - - #_errprint DEBUG old=$oldseq new=$newseqX - newsnap="$1@${ZREPTAG}_$newseqX" - - zfs snapshot $Z_SNAP_R $newsnap - if [[ $? -eq 0 ]] ; then - echo -n $newsnap; return 0 - else - return 1 - fi - -} - -## This is the implentation for the "zrep clear" command -## Purpose is to remove all zrep related hooks from a local filesystem. -## (NOT delete it) -## Will remove zrep snapshots and zfs zrep: properties -zrep_clear(){ - - if [[ "$1" == "" ]] ; then - zrep_errquit clear command requires an argument - fi - - echo "WARNING: Removing all zrep configs and snapshots from $1" - echo " (for TAG=${ZREPTAG})" - - - if [[ "$ZREP_FORCE" != "-f" ]] ; then - echo Continuing in 10 seconds - sleep 10 - fi - - _clearsnaps $1 - _clearvars $1 -} - -_clearsnaps(){ - echo Destroying any zrep-related snapshots from $1 - snaplist=`list_autosnaps $1` - for snap in $snaplist ; do - zfs destroy -r $snap - done -} - -_clearvars(){ - echo Removing zrep-related properties from $1 - proplist=`zfs get -H -o property all $1|grep ${ZREPTAG}:` - for prop in $proplist ; do - zfs inherit $prop $1 - done -} - -## This is a special internal routine, used only by zrep_init, -## to reset target fs to pre-zrep state. -## call with "srcfs errmsg1 errmsg2..." -## It will also REMOVE REMOTEFS if set in PROPERTIES!! -clearquit(){ - remhost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` - remfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` - - if [[ $? -eq 0 ]] && [[ "$remhost" != "-" ]] && [[ "$remfs" != "-" ]]; then - zrep_ssh $remhost zfs destroy -r $remfs - fi - _clearsnaps $1 - _clearvars $1 - - shift - zrep_errquit "$@" -} - - -# Shared internal routine. -# -# Set the to/from properties on a fs for zrep -# Called by zrep_init and zrep_changeconfig -# Usage: -# setfsconfigs srcfs desthost destfs -# setfsconfigs -d destfs srchost srcfs -# -setfsconfigs(){ - typeset srchost srcfs desthost destfs fsname - - if [[ "$1" == "-d" ]] ; then - srchost="$3" - srcfs="$4" - desthost=$Z_LOCAL_HOST - destfs="$2" - fsname=${destfs} - else - srchost=$Z_LOCAL_HOST - srcfs="$1" - desthost="$2" - destfs="$3" - fsname=${srcfs} - fi - - - if [[ "$destfs" == "" ]] ; then - zrep_errquit "zrep: no dest fs specified" - fi - zfs list $fsname >/dev/null ||zrep_errquit "filesystem $fsname must exist already" - - # - # This is really only for when we are called from init I think? - # - case $destfs in - # Originally, I had this passthrough only if fsname was at end - # However,we must allow destfs to have different leaf name, - # for circumstances such as replication to same host - */*) - : - ;; - *) - # Only the pool name given. - # With our curent workflow, that can ONLY WORK - # if recent versions of ZFS are in use. - if (( Z_HAS_REC_U )) ; then - echo WARNING: forcing override sync to top level pool $desthost:$destfs - echo Not using a usual sub-filesystem - sleep 5 - else - zrep_errquit older zfs version, cannot initialize top level pool - fi - ;; - esac - - zfs set ${ZREPTAG}:src-fs=$srcfs $fsname - zfs set ${ZREPTAG}:src-host=$srchost $fsname - zfs set ${ZREPTAG}:dest-fs=$destfs $fsname - zfs set ${ZREPTAG}:dest-host=$desthost $fsname - zfs set ${ZREPTAG}:savecount=$ZREP_SAVE_COUNT $fsname -} - - -# -# Old way used to use recv -x, but not all systems have that. so -# preferred method is now to use recv -u instead. -# To init from existing snapshot instead, see documention at bolthole.com -# Note that remote fs must share same stem name as source. (for now?) -# -zrep_init(){ - typeset srcfs="$1" desthost="$2" destfs="$3" snap check vol=0 - typeset mountpoint vflags verbose - typeset token - - if [[ "$ZREP_FORCE" != "-f" ]] ; then - zrep_ssh $desthost zfs list destfs >/dev/null && - zrep_errquit "$desthost:$destfs already exists! Will not overwrite without -f" - fi - - if [[ "$ZREP_VERBOSE" != "" ]] ; then - verbose=-v - fi - - if [[ "$srcfs" == "" ]] ; then - zrep_errquit "zrep: no fs specified" - fi - - #sanity checks - check="`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs`" - if [[ "$check" != "-" ]] ; then - echo "$srcfs is at least partially configured by zrep" - check="`$ZFSGETLVAL ${ZREPTAG}:master $srcfs`" - if [[ "$check" != "" ]] ; then - zrep_errquit "${ZREPTAG}:master detected!!" - fi - - if [[ "$ZREP_RESUME" == "" ]] ; then - zrep_errquit "To re-initialize, first use zrep clear $srcfs" - fi - - # if it exists already, presume we need to resume an init. - # if we cant, then its an error. - - - - token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` - if [[ "$token" == "-" ]] ; then token="" ; fi - if [[ "$token" == "" ]] ; then - zrep_errquit Partial init of $src detected but no resume token found. Suggest you zrep clear and start again - fi - - echo "Partially complete init detected. Attempting to resume send" - - if [[ "$BBCP" != "" ]] ; then - SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" - $BBCP -N io "$SENDCMD" \ - "$desthost:zfs recv $destfs" - - else - eval zfs send ${ZREP_RESUME_FLAGS} -t $token ${Z_F_OUT} | - zrep_ssh $desthost "${Z_F_IN} zfs recv $destfs" - fi - if [[ $? -ne 0 ]] ; then - zrep_errquit resume send of zrep init $srcfs failed - fi - fi - - - check="`$ZFSGETVAL type $srcfs`" - if [[ "$check" == "volume" ]] ; then - vol=1 - if (( ! Z_HAS_REC_O )) ; then - echo WARNING: no proper recv -o detected - echo WARNING: extremely old versions of ZFS crash with volume init - echo Continuing in 5 seconds.... - sleep 5 - vflags="`$ZFSGETVAL volsize $srcfs`" - vflags="-V $vflags" - - - fi - # for details,see - # https://groups.google.com/forum/#!topic/comp.unix.solaris/-5bcZFInozk - # subject:"solaris 11 crash when zfs send/receive of volume" - fi - - - #get this for later - mountpoint=`$ZFSGETLVAL mountpoint $srcfs` - - # Make this section conditional, so that we keep shared codepath for completion - # of initial sync. - # This is actually the "normal" snapshot and sync path. - # - if [[ "$token" == "" ]] ; then - - if [[ "$ZREP_RESUME" != "" ]] ; then - recv_s=-s - fi - - echo Setting zrep properties on $srcfs - setfsconfigs $srcfs $desthost $destfs - - #setfsconfigs may do some "smarts" to adjust value, so get it again. - # yes only check for LOCAL this time. Paranoia.... - destfs=`$ZFSGETLVAL ${ZREPTAG}:dest-fs $srcfs` - - if (( Z_HAS_REC_O )) ; then - READONLYPROP="-o readonly=on" - else - READONLYPROP="" - echo Warning: zfs recv lacking -o readonly - fi - if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - echo Creating destination filesystem as separate step - # normally would want to use -o readonly here. - # however, that breaks when -R is used. - # set it after transfer instead - zrep_ssh $desthost zfs create $ZREP_CREATE_FLAGS $vflags $destfs || zrep_errquit "Cannot create $desthost:$destfs" - fi - - snap="${srcfs}@${ZREPTAG}_000000" - echo Creating snapshot $snap - zfs snapshot $Z_SNAP_R $snap || clearquit $srcfs "Cannot create initial snapshot $snap" - - # Note that we may not want to use -p for normal zrep syncs - # We also should not use -F for normal recv. See workflow.txt - # Note: we may have to reset readonly=on, if we used -p on send... - # - echo Sending initial replication stream to $desthost:$destfs - if (( Z_HAS_REC_U )) ; then - # This is the nice, clean, modern codepath, to send - # zrep settings over automatically at first init. - # Note that we use "zfs send -p" to preserve properties. - - if [[ "$BBCP" != "" ]] ; then - $BBCP -N io "zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} -p $snap" \ - "$desthost:zfs recv -u $READONLYPROP $recv_s -F $destfs" - else - eval zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} -p $snap ${Z_F_OUT}| - zrep_ssh $desthost "${Z_F_IN} zfs recv -u $READONLYPROP $recv_s -F $destfs" - fi - - else - ## arg.. Update your systems!! - # without -u, risky to use send -p if mountpoint set. - # (So we cant avoid it potentially WRONGLY setting mountpoint) - # This means we have to manually set props lower down as well. - # (yeah okay I could check if $mountpoint set, but - # there are too many code branches here already, so too bad! - # Update your system! :p ) - - if [[ "$BBCP" != "" ]] ; then - $BBCP -N io "zfs send ${ZREP_R} ${ZREP_SEND_FLAGS} $snap" \ - "$desthost:zfs recv $READONLYPROP $recv_s -F $destfs" - else - eval zfs send ${ZREP_R} ${ZREP_SEND_FLAGS} $snap ${Z_F_OUT}| - zrep_ssh $desthost "${Z_F_IN} zfs recv $READONLYPROP $recv_s -F $destfs" - fi - fi - if [[ $? -ne 0 ]] ; then - if [[ "$ZREP_RESUME" == "" ]] ; then - clearquit $srcfs "Error transferring $snap to $desthost:$destfs. Resetting" - else - zrep_errquit "Error transferring $snap to $desthost:$destfs. RESUME set. Not clearing $srcfs" - fi - fi - - fi #resume token set - - - # Successful initial sync! Woo! okay, record that fact and complete setting properties - # ... after stupid old-zfs-compat junk, that is - if (( ! Z_HAS_REC_U )) || [[ "$ZREP_CREATE_FLAGS" != "" ]] ; then - _debugprint Because your zfs does not have recv -u, - _debugprint or maybe because ZREP_CREATE_FLAGS set, - _debugprint setting remote properties by hand - zrep_ssh $desthost zfs set readonly=on $destfs - if [[ $? -ne 0 ]] ; then - errquit Could not set readonly for $desthost:$destfs. However, snapshot has been synced. \ - Manual intervention required if you do not wish to simply clear and start again. - fi - - zrep_ssh $desthost zfs set ${ZREPTAG}:src-fs=$srcfs $destfs - zrep_ssh $desthost zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $destfs - zrep_ssh $desthost zfs set ${ZREPTAG}:dest-fs=$destfs $destfs - zrep_ssh $desthost zfs set ${ZREPTAG}:dest-host=$desthost $destfs - zrep_ssh $desthost zfs set ${ZREPTAG}:savecount=$ZREP_SAVE_COUNT $destfs - - fi - if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then - echo setting readonly on $desthost:$destfs manually - zrep_ssh $desthost zfs set readonly=on $destfs - fi - - # Extra, non-symmeric properties the user might want on the remote side. - if [[ "$ZREP_INIT_REMOTE_PROPERTIES" != "" ]] ; then - for prop in $ZREP_INIT_REMOTE_PROPERTIES ; do - zrep_ssh $desthost zfs set $prop $destfs - done - fi - - - # Success! So need to set success marker on remote side. - # Make sure to set format to match what zrep_sync() looks for! - if (( Z_HAS_SNAPPROPS )) ; then - typeset sentprop="${ZREPTAG}:sent=`zrep_gettimeinseconds`" - zfs set $sentprop ${snap} - else - # Arg stupidold stuff cant set props on a snapshot - # So we have to manually set these on both sides also, - # "Just in case" - zfs set ${ZREPTAG}:lastsent=${snap} $srcfs - zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${snap} $destfs - fi - if [[ "$mountpoint" != "" ]] ; then - echo "clearing mountpoint value for remote" - zrep_ssh $desthost zfs inherit mountpoint $destfs - fi - - # make sure the above ' set 's (sent, lastsent) - # match what zrep_sync() does !!! - - - # Note: we have to set master property NOW, not before, - # because "recv -x zrep:master" Does Not Work properly - # Also, it avoids things like "zrep sync all" from attempting - # to sync it before initial sync has been done. - # We don't even have to zrep_lock_fs until this is set - # Make sure value this matches zrep_sentsync - zfs set ${ZREPTAG}:master=yes $srcfs - - echo Initialization copy of $srcfs to $desthost:$destfs complete - if (( Z_HAS_REC_U )) ; then - echo Filesystem will not be mounted - fi - -} - - -zrep_changeconfig(){ - if [[ "$1" == "-f" ]] ; then - # skip safety checks - shift - setfsconfigs $@ - return - fi - typeset srcfs="$1" check - - if [[ "$srcfs" == "" ]] ; then - zrep_errquit "zrep: no fs specified" - fi - - check=`getlastsnap $srcfs` - if [[ "$check" == "" ]] ; then - _errprint "No pre-existing zrep snapshots found on $srcfs" - _errprint $srcfs is not initialized for zrep. cannot change config. - zrep_errquit Use zrep init on $srcfs instead - fi - - setfsconfigs $@ - -} - - - -##### File: zrep_sync - -# contains meat of the "sync" level operations, which deal with -# data transfer. -# basic snap routines, and init routines, are in zrep_snap - - - -## file-internal routine that gets used a lot in zrep_sync. but not always -_gensentprop(){ - typeset timeinsec=`zrep_gettimeinseconds` - echo "${ZREPTAG}:sent=$timeinsec" -} - - -# This is a RECOVERY ROUTINE ONLY. -# I put lots of sanity checking in here, that doesnt make sense to keep -# with a more general case internal routine. -# This exists, because certain people say that for some odd reason on their systems, -# the zfs send completes, but zrep gets killed before zrep updates properties. -# To help people save the time on resyncing hundreds of TB, -# give them a way to update the sent property. -# -# This only works with newstyle ZFS that allows property setting on snapshots -# Needs to follow whatever is done in _sync(), after the zfs send -# (and therefore getlastsnapsent() as well) -# -# Usage: zrep_sentsync [-L] fs@snap -# -# WARNING: If you have nested zrep filesystem below this one.. you just hosed yourself. -# -zrep_sentsync(){ - typeset local=0 - if [[ "$1" == "-L" ]] ; then - local=1; - shift - fi - - typeset srcsnap="$1" - typeset srcfs=${srcsnap%@*} - typeset snapname=${srcsnap#*@} - - if (( ! Z_HAS_SNAPPROPS )) ; then - zrep_errquit This sentsync operation only supported with modern ZFS implementations - fi - - zfs list -t snapshot "$1" >/dev/null 2>&1 - if [[ $? -ne 0 ]] ; then - zrep_errquit Expected snapshot for $1. Cannot continue - fi - case "$1" in - *@${ZREPTAG}_[0-9a-f]*) - : - ;; - *) - zrep_errquit $1 does not follow zrep naming standards. Cannot continue - ;; - esac - - typeset sentcheck=`$ZFSGETLVAL ${ZREPTAG}:sent $srcfs` - if [[ "$sentcheck" != "" ]] ; then - zrep_errquit ${ZREPTAG}:sent already present on $srcfs - fi - - - _clearlast $srcfs - typeset senttimeprop="`_gensentprop`" - - if ((local == 0)) ; then - typeset desthost destfs - desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` - destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` - zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname - fi - - zfs set $senttimeprop ${srcsnap} - # This will be redundant for recovery, but crucial for when - # user is trying to convert existing snapshot to - # zrep snapshot. - # Make sure it matches zrep_init - zfs set ${ZREPTAG}:master=yes ${srcfs} - - -} - -#################### -# synctosnap: called by zrep_sync, if a specific snapshot is specified. -# -# This LOCAL side, *and* REMOTE side, match up with local zrep_created -# snapshot. ... -# -# Note that it uses zrep_lock_fs -# -# WARNING: if we force other side to roll to snap.... -# we should NOT BE SYNCING ANY more. -# At the moment, it is up to the user to ensure that nothing is going on -# locally, and future zrep syncs wont just effectively roll forward again -# on the remote side. -# zrep sync jobs should probably be halted, until it is decided that -# you want to sync again. -# -# In the future, I should support some kind of "pause" option, for -# zrep sync all to ignore a rolled back filesystem -# -# -synctosnap(){ - typeset srcsnap=$1 destfs=$2 desthost=$3 - typeset newsentlist - - typeset srcfs snapname destsnap - if [[ "$desthost" == "" ]] ; then - echo ERROR: synctosnap did not receive all required args - zrep_errquit "args=$@" - fi - srcfs=${srcsnap%@*} - snapname=${srcsnap#*@} - destsnap=${snapname} - - # Have to enforce OUR syntax. otherwise, any future attempt to - # continue sync will fail. - # ( getlastsnap() wont find it! ) - # - case $snapname in - ${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) - : ;; - *) - zrep_errquit $srcsnap is not zrep snapshot. Cannot roll with it. - ;; - esac - - - - echo Validating remote snap - zrep_ssh $desthost zfs list -t snapshot $destfs@$destsnap >/dev/null - if [[ $? -ne 0 ]] ; then - zrep_errquit $destfs@$destsnap does not exist. Cannot roll to snap - fi - - - echo "WARNING: We will be rolling back $destfs, on $desthost" - echo -n " to $snapname, made at: " - $ZFSGETVAL creation $srcsnap - echo "" - echo "All newer snapshots on remote side will be destroyed" - echo "You should have paused ongoing sync jobs for $destfs before continuing" - echo "Continuing in 20 seconds...." - sleep 10 - echo "Continuing in 10 seconds...." - sleep 10 - - zrep_lock_fs $srcfs || zrep_errquit "Cannot lock $srcfs" - zrep_ssh $desthost zfs rollback -Rr $destfs@$destsnap || zrep_errquit roll failed - - echo $desthost:$destfs rolled back successfully to $destsnap - echo Now cleaning up local snapshots - - # need to undo whatever zrep_sync does - newsentlist=`getallsnaps $srcfs|sed "1,/@$snapname/d"` - for snap in $newsentlist ; do - zfs inherit ${ZREPTAG}:sent $snap - done - - zrep_unlock_fs $srcfs - -} - -# -# called by _sync -# Check if there have been changes since specified snap -# If no, then return 0 == true [no changes ] -# -_sync_nochanges(){ - typeset changecheck tmpfile="$ZREP_RUNDIR/zrep.$$.c" - - rm -f $tmpfile - zfs diff -H $1 >$tmpfile - if [[ $? -ne 0 ]] ; then - _errprint _sync_nochanges: zfs diff command unrecognized - rm -f $tmpfile - return 1 - fi - if test -s $tmpfile ; then - rm -f $tmpfile - return 1 - fi - - _debugprint sync_nochanges did not find any changes - rm -f $tmpfile - return 0 -} - -# Usage: _snapandsync fs desthost destfs -# internal routine called by zrep_sync and zrep_failover, -# to do an incremental send. -# You must hold filesystem lock before calling this -# WE DO NOT DO ANY SAFETY OR LOCK CHECKS HERE. -# Caller is expected to have done them. -# -# Wil create a new snap on srcfs, and sync it over to given destination -# Sets our 'synced' marker on it as well. -# -_snapandsync(){ - typeset srcfs=$1 desthost=$2 destfs=$3 - - typeset sentsnap newsnap snapname - - - # Find incremental send starting point - # Do this BEFORE creating new snap, because we should make new snap - # if we cant do incremental anyway - sentsnap=`getlastsnapsent $srcfs` - if [[ "$sentsnap" == "" ]] ; then - echo zrep_sync could not find sent snap for $srcfs. - zrep_errquit You must initialize $srcfs for zrep - fi - - if [[ "$ZREP_CHANGEDONLY" != "" ]] ; then - if _sync_nochanges $sentsnap ; then - _debugprint No changes found in $srcfs. Updating timestamp only - - if (( Z_HAS_SNAPPROPS )) ; then - typeset senttimeprop="`_gensentprop`" - zfs set $senttimeprop ${sentsnap} - else - #note that this is only for old-ZFS compatibility. - # We dont really want to use this style if possible! - typeset timeinsec=`zrep_gettimeinseconds` - zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $srcfs - fi - return 0 - fi - fi - - newsnap=`makesnap $srcfs` - if [[ "$newsnap" == "" ]] ; then - zrep_errquit zrep_sync could not create new snapshot for $srcfs - fi - - _sync $srcfs $desthost $destfs $sentsnap $newsnap - -} - - - -# called by _snapandsync, and also zrep_synconly -# Usage: _sync sourcefs destinationhost destinationfs (lastsent (newsnap)) -# This is the level that calls zfs directly. -# see also _refreshpull, since it also calls directly. -_sync(){ - typeset force verbose - typeset token="" recv_s="" - - if [[ "$ZREP_FORCE" == "-f" ]] ; then - force=-F - fi - if [[ "$ZREP_VERBOSE" != "" ]] ; then - verbose=-v - fi - if [[ "$ZREP_RESUME" != "" ]] ; then - recv_s=-s - fi - - typeset srcfs=$1 desthost=$2 destfs=$3 - typeset lastsent=$4 newsnap=$5 - typeset snapname - - if [[ "$lastsent" == "" ]] ; then - lastsent=`getlastsnapsent $srcfs` - if [[ "$lastsent" == "" ]] ; then - echo zrep_sync could not find sent snap for $srcfs. - zrep_errquit You must initialize $srcfs for zrep - fi - fi - - if [[ "$newsnap" == "" ]] ; then - newsnap=`getlastsnap $srcfs` - if [[ "$newsnap" == "" ]] ; then - echo zrep_sync could not find sent snap for $srcfs. - zrep_errquit You must initialize $srcfs for zrep - fi - fi - - if [[ "$newsnap" == "$lastsent" ]] ; then - echo $newsnap already sent - return 0 - fi - - typeset remotemaster - remotemaster=`zrep_ssh $desthost $ZFSGETLVAL ${ZREPTAG}:master $destfs` - if [[ $? -ne 0 ]] ; then - zrep_errquit "$desthost is not reachable via ssh? Cannot sync" - fi - if [[ "$remotemaster" == "yes" ]] ; then - zrep_errquit "Other side ($desthost:$destfs) is also master. Split brain detected" - fi - - snapname=${newsnap#*@} - # do this manually, not using gensentprop because we want consistant timestamp - typeset timeinsec=`zrep_gettimeinseconds` - typeset senttimeprop="${ZREPTAG}:sent=$timeinsec" - - echo sending $newsnap to $desthost:$destfs - - if [[ "$ZREP_RESUME" != "" ]] ; then - token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` - if [[ "$token" == "-" ]] ; then token="" ; fi - fi - - # Note: doing "-o $senttimeprop" sets prop on FILESYSTEM, not snap. - # So we dont do that usually - - # other than zrep_init, this should be the ONLY place we do a send - # Sigh. but now we also do in _refreshpull - if [[ "$BBCP" != "" ]] ; then - - if [[ "$token" != "" ]] ; then - SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" - else - SENDCMD="zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $newsnap" - - fi - $BBCP -N io "$SENDCMD" \ - "$desthost:zfs recv $recv_s $force $destfs" - else - if [[ "$token" != "" ]] ; then - eval zfs send ${ZREP_RESUME_FLAGS} -t $token ${Z_F_OUT} | - zrep_ssh $desthost "${Z_F_IN} zfs recv $recv_s $force $destfs" - else - eval zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $newsnap ${Z_F_OUT} | - zrep_ssh $desthost "${Z_F_IN} zfs recv $recv_s $force $destfs" - fi - fi - - # I rename this to _unsent rather than just delete, in case people are using zrep - # for the DUAL use, of replication, - # plus convenient user-based "oops" recovery from the automatic .zfs/snapshots directory - # But if resume support enabled, should auto-retry next time sync called - if [[ $? -ne 0 ]] ; then - if [[ "$ZREP_RESUME" == "" ]] ; then - if [[ "$ZREP_RENAME_UNSENT" == "yes" ]] ; then - zfs rename ${Z_SNAP_R} ${newsnap} ${newsnap}_unsent - zrep_errquit Problem doing sync for $newsnap. Renamed to ${newsnap}_unsent - else - zfs destroy ${Z_SNAP_R} ${newsnap} - fi - fi - - zrep_errquit Problem doing sync for $newsnap. - fi - - - ################################################## - ##### Okay. data sync completed. Now register that fact with ZFS properties. - ##### If you modify below here, you also need to update zrep_sentsync - - #Even if we are "old mode", other side may not be. - # So try newer way first. - zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname - if [[ $? -ne 0 ]] ; then - echo WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname - echo Using fallback methods. You should go patch $destfs to have newer ZFS version - zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${newsnap} $destfs - zrep_ssh $desthost zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $destfs - fi - - if (( Z_HAS_SNAPPROPS )) ; then - zfs set $senttimeprop ${newsnap} - else - #note that this is only for old-ZFS compatibility. - # We dont really want to use this style if possible! - zfs set ${ZREPTAG}:lastsent=${newsnap} $srcfs - zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $srcfs - fi -} - - -#User entrypoint, for synconly, which is the pair of snaponly -#Keep it paired with zrep_sync -zrep_synconly(){ - # annoyingly..need to make this almost identical to our current full - # zrep_sync. but just skipping first steps :( - # we can skip retries, though. - typeset srcfs desthost destfs - - if [[ "$1" == "all" ]] ; then - set -- `zrep_list_master` - if [[ "$1" == "" ]] ; then - exit - fi - fi - - [[ "$1" == "" ]] && zrep_errquit No fileystem specified for synconly - - while [[ "$1" != "" ]] ; do - srcfs=$1 - - check=`$ZFSGETLVAL ${ZREPTAG}:master $srcfs` - if [[ "$check" != "yes" ]] ; then - zrep_errquit $srcfs not master. Cannot sync - fi - - desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` - destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` - if [[ $? -ne 0 ]] || [[ "$desthost" == "-" ]] || [[ "$destfs" == "-" ]]; - then - zrep_errquit Problem getting zrep properties for fs $srcfs - fi - - zrep_lock_fs $srcfs - if [[ $? -ne 0 ]] ; then - zrep_errquit Failed to acquire zrep lock for $srcfs - fi - - _sync $srcfs $desthost $destfs || zrep_errquit sync failed for $srcfs - - _expire $srcfs #dont care so much if this fails - - zrep_unlock_fs $srcfs - - shift - done -} - - -#zrep_sync -# User entrypoint -# Make a new snapshot and copy it over. -# Usage: zrep_sync [-q quiettime] (all|fs1 .. fsX) -# See workflow.txt -# SPECIAL CASE: Will call synctosnap if a snapshot is given instead of fsname -# Normally, will bail out if another instance of zrep holds lock. -# -q option says to check last update time of locked filesystems. -# If sync more recent than given quiettime, then quietly ignore -# -zrep_sync(){ - # If you make changes in here, check if needed in zrep_synconly!! - - typeset srcfs destfs desthost sentsnap newsnap - typeset quiettime=0 - - if [[ "$1" == "-c" ]] ; then - export ZREP_CHANGEDONLY="yes" - shift - fi - - if [[ "$1" == "-q" ]] ; then - quiettime="$2" - shift - shift - if (( quiettime < 30 )) ; then - zrep_errquit "-q must use value greater than 30" - fi - fi - - if [[ "$1" == "all" ]] ; then - set -- `zrep_list_master` - if [[ "$1" == "" ]] ; then - # Stay quiet, so we dont spew if in cron - #echo No zrep mastered filesystems found - exit - fi - fi - - [[ "$1" == "" ]] && zrep_errquit No fileystem specified for sync - - # Special Case. User can force sync from specific snapshot - case $1 in - *@*) - synctosnap $srcfs $destfs $desthost - return - ;; - esac - - while [[ "$1" != "" ]] ; do - srcfs="$1" - - check=`$ZFSGETLVAL ${ZREPTAG}:master $srcfs` - if [[ "$check" != "yes" ]] ; then - zrep_errquit $srcfs not master. Cannot sync - fi - - desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` - destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` - if [[ $? -ne 0 ]] || [[ "$desthost" == "-" ]] || [[ "$destfs" == "-" ]]; - then - zrep_errquit Problem getting zrep properties for fs $srcfs - fi - - zrep_lock_fs $srcfs - - if [[ $? -ne 0 ]] ; then - # retry for lock for a while, if (quiettime>0 ) - - if ((quiettime==0)); then - zrep_errquit Cannot lock $srcfs. Cannot continue - fi - typeset currtime=`zrep_gettimeinseconds` snaptime elapsed - - snaptime=`getlastsynctime $srcfs` - if (( snaptime == 0 )) ; then - zrep_errquit quiet mode set, but no last snap for $srcfs - fi - - elapsed=$((currtime - snaptime)) - if ((elapsed > quiettime)) ; then - _debugprint $elapsed seconds have elapsed since last sync of $srcfs - zrep_errquit quiet time limit of $quiettime seconds exceeded for busy fs $srcfs - else - echo Quiet mode: skipping busy fs $srcfs at `date` - return - fi - fi - - _snapandsync $srcfs $desthost $destfs - - - # Make this message match what zrep_expire uses.. - echo Expiring zrep snaps on $srcfs - _expire $srcfs - - zrep_unlock_fs $srcfs - - shift - done - -} - - - -# Usage: zrep_refresh fsname -# -# zrep_refresh is a "pull" version of "zrep_sync" -# The concept is a bit of a hack. -# It primarily exists so people can run a secure backup server, that -# has ssh access to all hosts, but not vice versa -# -# Implementation is a bit sketchy. -# For initial, non-optimal run, perhaps take advantage of -# ssh host zrep synconly -# to avoid too much duplication of things? -# but will still need to set all the perms n things. Nastyyy.. -# The MAIN nastiness, is that all our locks are on the "master" side. -# Which depends on the PID still being there!! -# But if we start now running things on the "slave" side.. -# There is potential for problems -# Examine critical points and reasons for lock: -# 1. while doing analysis of which snap to send -# 2. to avoid paralel "zfs send"s running. -# 3. for update of timestamp -# -# We can still wrap #1 and #2 in a single lock call. -# (and still on the src side!) -# The ugly comes when updating zrep:sent. Dont want to update wrong snap! -# So long as we do some kind of check to see that we're not going -# backwards when we get lock a second time ... we should be relatively okay. -# However.. for simplicity... going to just cross fingers and wrap -# all three in single remote lock call, through _refreshpull -# -zrep_refresh(){ - typeset srcfs destfs desthost newsnap newseq master - typeset force - - if [[ "$ZREP_FORCE" == "-f" ]] ; then - force=-F - fi - - if [[ "$1" == "-r" ]] ; then - export ZREP_RESUME=1 - shift - fi - - # for now, just handle ONE fs, not multiple fs list - - destfs="$1" - if [[ "$1" == "" ]] ; then - _errprint Error: no filesystems specified for refresh - return 1 - fi - - master=`$ZFSGETLVAL ${ZREPTAG}:master $destfs` - if [[ "$master" == "yes" ]] ; then - zrep_errquit Sorry, you cant run refresh on a master mode fs $destfs - fi - - srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $destfs` - srcfs=`$ZFSGETVAL ${ZREPTAG}:src-fs $destfs` - - zrep_lock_fs $destfs - if [[ $? -ne 0 ]] ; then - zrep_errquit Cannot lock $destfs. Cannot continue - fi - - _debugprint refresh step 1: Going to $srchost to snapshot $destfs - newsnap=`zrep_ssh $srchost $ZREP_PATH ${ZREP_R} snaponly $srcfs` - if [[ $? -ne 0 ]] ; then - zrep_errquit snap of src $srcfs on $srchost failed - fi - # yes, MORE paranoia.. - case $newsnap in - *@${ZREPTAG}_*) - newseq=${newsnap#*@} - ;; - *) - zrep_errquit Unrecognized output from src snap. Cannot continue - ;; - esac - - typeset senttimeprop="`_gensentprop`" - - _debugprint refresh step 2: Pulling $newsnap - if [[ "$ZREP_RESUME" != "" ]] ; then - token=`$ZFSGETVAL receive_resume_token $destfs` - if [[ "$token" == "-" ]] ; then token="" ; fi - fi - - if [[ "$BBCP" != "" ]] ; then - $BBCP -N io "$srchost:$ZREP_PATH _refreshpull $newsnap $token" \ - "zfs recv $force $destfs" - else - zrep_ssh $srchost "$ZREP_PATH ${ZREP_R} _refreshpull $newsnap $token ${Z_F_OUT}" | - eval ${Z_F_IN} zfs recv $force $destfs - fi - if [[ $? -ne 0 ]] ; then - zrep_errquit Unforseen error pulling snapshot $newsnap from $srchost - fi - - zfs set $senttimeprop $destfs@$newseq - if [[ $? -ne 0 ]] ; then - _errprint WARNING: expected local copy $destfs@newseq does not exist - fi - - - zrep_ssh $srchost $ZREP_PATH _refreshcomplete $newsnap $senttimeprop - - - _debugprint Running local expires on $destfs - _expire $destfs - - _debugprint Running remote expires on $srcfs - sleep 1 # avoid race condition on samehost situation - zrep_ssh $srchost "$ZREP_PATH expire -L $srcfs" - - zrep_unlock_fs $destfs - -} - -# Implementation for hidden command-line option, "zrep _refreshpull" -# This is called remotely by zrep refresh -# ( aka zrep_refresh ) -# We dont just call "ssh zfs send", because we want to use zrep locking -# -# This routine is definitely not supposed to be user visible -# .. eh... maybe someday. but initial design is "private" -# -# Note that this is only called on a per-filesystem name -# -# Syntax: _refreshpull fs/name [optional resume token] -# -_refreshpull(){ - typeset fs snapname lastsent latest verbose - typeset token="" - - if [[ "$2" != "" ]] ; then - token="$2" - fi - - if [[ "$ZREP_VERBOSE" != "" ]] ; then - verbose=-v - fi - - snapname="$1" - fs=${snapname%@*} - - # Keep in mind that stdin/out is busy so have to use stderr. - # Cant use regular debugprint - if [[ "$DEBUG" != "" ]] ; then - _errprint _refreshpull: snapname=$snapname, fs=$fs - fi - - zrep_lock_fs $fs - if [[ $? -ne 0 ]] ; then - zrep_errquit Could not lock $fs - fi - - #We should now; - # 1. compare to latest snap. quit if not latest - # 2. get timestamp - # 3. trigger a zfs send - # 4. set timestamp if no errors. - # I think it is reasonable to presume that if the receive failed, - # we will see an error by the pipe blowing up. - # - - lastsent=`getlastsnapsent $fs` - if [[ "$lastsent" == "" ]] ; then - zrep_errquit Canthappen: _refreshpull cant findlastsent snap - fi - latest=`getlastsnap $fs` - - if [[ "$latest" != "$snapname" ]] ; then - zrep_errquit Sync error: $snapname is not latest snap for $fs - fi - - - if (( Z_HAS_SNAPPROPS ==0)) ; then - zrep_errquit Error: we currently only support modern ZFS that allows setting props on snaps - fi - - if [[ "$token" != "" ]] ; then - zfs send ${ZREP_RESUME_FLAGS} -t $token - else - zfs send $verbose $token ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $latest - fi - - if [[ $? -ne 0 ]] ; then - zrep_errquit Some kind of error during sending. Bailing out of _refreshpull - fi - - - zrep_unlock_fs $fs -} - - -# INTERNAL-ONLY HOOK for "zrep refresh" -# -# This used to be in _refreshpull. However, that led to a race condition where a -# "zfs send" had been completed, and "last sent" had been updated prematurely. -# if the zfs receive didnt complete, then things were messed up. -# Usage: -# _refreshcomplete fs@snapshot propertysetting=value -# -_refreshcomplete(){ - zfs set ${2} ${1} -} - -# usage: -# snap_olderthan snapname minutes -# returns true (0) if snap older than given number of minutes, -# based on 'creation' property. -# (technically this should work on regular fs as well) -# -# **NOTE** -# MINUTES, not seconds, because property format looks like this; -# $ zfs get -H creation scratch -# scratch creation Sun Dec 1 21:55 2019 - -# -snap_olderthan(){ - typeset cstamp=`$ZFSGETVAL creation $1` - typeset ageinseconds=`printf "%(%s)T" "$cstamp"` - - typeset secondmark=`printf "%(%s)T" "$2 minutes ago"` - - if (( $secondmark < 1 )) ; then - zrep_errquit ERROR: snap_olderthan passed invalid minute limit: $2 - fi - - if (( ageinseconds < secondmark )) ; then - return 0 - fi - - - return 1 -} - -# _expire: -# get rid of "old" snapshots for a specifically named filesystem -# -# Note0: you must hold local(master) fs lock first -# -# Note1: expire BOTH SIDES, if we are master -# Keep in mind that sometimes master and dest are on same system -# -# Note2: Be sure to NEVER delete most recent sent snapshot!! - -# INTERNAL routine. For external-facing routine, see zrep_expire -_expire(){ - - if [[ "$ZREP_SKIP_EXPIRE" != "" ]] ; then - _debugprint _expire doing nothing since ZREP_SKIP_EXPIRE set - return - fi - - typeset savecount currcount lastsent remotehost remotefs sanity - typeset tmpfile=$ZREP_RUNDIR/zrep_expire.$$ - typeset local=0 master - - if [[ "$1" == "-L" ]] ; then - local=1; - shift - fi - - master=`$ZFSGETLVAL ${ZREPTAG}:master $1` - - zrep_has_fs_lock $1 || zrep_errquit zrep_expire Internal Err caller did not hold fs lock on $1 - - # Allow propagated values as well as local, for savecount - savecount=`$ZFSGETVAL ${ZREPTAG}:savecount $1` - # do not use (()) in case value unset - if [[ $savecount < 1 ]] ; then - zrep_errquit ${ZREPTAG}:savecount on $1 set to improper value $savecount - fi - - rm -f $tmpfile - if [[ "$master" == "yes" ]] ; then - lastsent=`getlastsnapsent $1` - if [[ "$lastsent" == "" ]] ; then - zrep_errquit corrupted zrep data: no last sent detected. Stopping expire - fi - - # Note that getallsnaps does an explicit sort already. - # We want to not expire lastsent, OR any later unsent ones! - getallsnaps $1 | awk '$1 == "'$lastsent'"{exit} {print}' >$tmpfile - savecount=$((savecount-1)) - else - getallsnaps $1 >$tmpfile - fi - currcount=`wc -l < $tmpfile` - - if ((currcount > savecount )) ; then - currcount=$((currcount - savecount)) - - head -$currcount $tmpfile >$tmpfile.2 - mv $tmpfile.2 $tmpfile - for snap in `cat $tmpfile` ; do - _debugprint expiring $snap - # Paranoia is good. - case $snap in - *@*) - zfs destroy -r $snap - ;; - *) - zrep_errquit "Expire was about to destroy NON-snapshot $snap" - ;; - esac - done - fi - rm $tmpfile - - - if [[ "$master" != "yes" ]] || ((local ==1)) ; then - #This fs is dest fs. We are done. - return - #otherwise, go expire on remote side as well - fi - - remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` - remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` - echo Also running expire on $remotehost:$remotefs now... - sanity=`zrep_ssh $remotehost $ZFSGETLVAL ${ZREPTAG}:master $remotefs` - - # Normally, dont quit on error. But this is super-bad. - if [[ "$sanity" == "yes" ]] ; then - _errprint "ERROR: Remote side $remotehost also marked as master." - zrep_errquit "ERROR: Split brain scenario detected" - fi - - zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||echo REMOTE expire failed -} - - -# top-level user-facing routine. -# expire old snaps for some or all zrep filesystems. -# Different ways of calling: -# zrep expire all Run expire on all zrep fs -# zrep expire Run expire on zrep fs we are master for, plus remote -# zrep expire -L Run expire on zrep fs we are master for. SKIP remote -# zrep expire fs .. Run expire only on fs, plus remote if it is a master -# zrep expire -L fs Run expire only on fs. Skip remote -# -# If no arg given, expire only filesystems we are master for -# If "all" given, expire literally all. -# -zrep_expire() -{ - if [[ "$ZREP_SKIP_EXPIRE" != "" ]] ; then - zrep_errquit 'You explicitly called zrep expire.. and also have ZREP_SKIP_EXPIRE set?? How about no.' - fi - - typeset local - if [[ "$1" == "-L" ]] ; then - local="-L" - shift - fi - - if [[ "$1" == "all" ]] ; then - set -- `zrep_list` - elif [[ "$1" == "" ]] ; then - set -- `zrep_list_master` - fi - - # Note: we should continue if we hit problems with an individual - # filesystem. Otherwise we risk letting server selfdestruct fill - # over one troublesome filesystem - # - while [[ "$1" != "" ]] ; do - zrep_lock_fs $1 - echo Expiring zrep snaps on $1 - _expire $local $1 || echo WARNING: expire failed for $1 - zrep_unlock_fs $1 - shift - done - -} -#### File: zrep_failover - - - -# run this on 'master' side, to make other side master -# Usage: zrep_failover fs[@snap] [remotehost] -# -zrep_failover(){ - typeset local=0 fs snap="" remotehost remotefs check - - if [[ "$1" == "-L" ]] ; then - local=1 - shift - fi - - if [[ "$1" == "" ]] ; then - usage - exit 1 - fi - - zfs list $1 >/dev/null || zrep_errquit invalid filesystem $1 - check=`$ZFSGETLVAL ${ZREPTAG}:master $1` - if [[ "$check" != "yes" ]] ; then - zrep_errquit $1 not master. Cannot fail over - fi - - fs="$1" - - case $fs in - *@*) - snap=$fs - fs=${srcsnap%@*} - ;; - esac - - zrep_lock_fs $fs ||zrep_errquit could not lock $fs - - if [[ "$2" != "" ]] ; then - remotehost="$2" - else - remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $fs` - fi - - - remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` - - echo Setting readonly on local $fs, then syncing - zfs set readonly=on $fs - - if ((local ==1)) ; then - echo Failover for $1 in LOCAL mode - if [[ "$snap" == "" ]] ; then - snap=`getlastsnapsent $1` - zfs list $1 >/dev/null || - zrep_errquit No last synced snap found for $1. Cannot fail over - echo Rolling back to last sync $snap - else - echo Rolling back to specified snap $snap - fi - zfs rollback -Rr $snap ||zrep_errquit Rollback to $snap failed - - else - - ## Need to sync both sides before mode switch! - ## If named snap, roll back. - ## otherwise, "roll forward" by doing one last sync - - if [[ "$snap" != "" ]] ; then - typeset snapname - snapname=${snap#*@} - - echo Rolling back to local $snap - zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed - echo Rolling back $remotehost to $remotefs@$snapname - zrep_ssh $remotehost zfs rollback $remotefs@$snapname || - zrep_errquit remote rollback failed - - else - # makes new snapshot, and syncs - _snapandsync $fs $remotehost $remotefs || zrep_errquit final sync failed. failover failed. - fi - - - fi - - echo Reversing master properties for $Z_LOCAL_HOST:$fs - - zfs set ${ZREPTAG}:dest-fs=$fs $fs - zfs set ${ZREPTAG}:dest-host=$Z_LOCAL_HOST $fs - zfs set ${ZREPTAG}:src-fs=$remotefs $fs - zfs set ${ZREPTAG}:src-host=$remotehost $fs - - zfs inherit ${ZREPTAG}:master $fs - - zrep_unlock_fs $fs - - if (( local ==0)) ;then - echo Setting master on $remotehost:$remotefs - zrep_ssh $remotehost $ZREP_PATH takeover -L $remotefs - fi - -} - -# run this on 'dest' side, to promote it to master -# Usage: zrep_takeover fs[@snap] [remotehost] -# -zrep_takeover(){ - typeset fs snap remotehost remotefs check local=0 - - if [[ "$1" == "-L" ]] ; then - local=1 - shift - fi - - if [[ "$1" == "" ]] ; then - usage - exit 1 - fi - - fs="$1" - - zfs list $fs >/dev/null || zrep_errquit invalid filesystem $fs - - check=`$ZFSGETLVAL ${ZREPTAG}:master $fs` - if [[ "$check" = "yes" ]] ; then - _errprint WARNING: $fs is already master on this host. - _errprint Presuming split-brain recovery mode ... - sleep 5 - - fi - - - if [[ "$2" != "" ]] ; then - remotehost="$2" - else - remotehost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` - fi - remotefs=`$ZFSGETVAL ${ZREPTAG}:src-fs $fs` - - - if (( local == 0 )) ; then - - echo Starting failover from remote side $remotehost - zrep_ssh $remotehost $ZREP_PATH failover $remotefs - # This will ssh back into us to set src host property, - # and the other stuff, so we dont have to do that now. - exit $? - fi - - # If here, we must be in local mode. - # So... just set properties! - # (and roll back, if desired) - - case $fs in - *@*) - snap=$fs - fs=${srcsnap%@*} - ;; - esac - - - - zrep_lock_fs $fs - zfs inherit readonly $fs - - if [[ "$snap" != "" ]] ; then - echo "WARNING: Before takeover, we will be rolling $fs" - echo -n " to $snapname, made at: " - $ZFSGETVAL creation $snap - echo "" - echo "All newer snapshots will be destroyed" - echo Continuing in 10 seconds... - sleep 10 - - zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed - fi - - echo Setting master properties for $Z_LOCAL_HOST:$fs - - zfs set ${ZREPTAG}:src-fs=$fs $fs - zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $fs - zfs set ${ZREPTAG}:dest-fs=$remotefs $fs - zfs set ${ZREPTAG}:dest-host=$remotehost $fs - - zfs set ${ZREPTAG}:master=yes $fs - - # Since we default to creating replicas unmounted... mount it now - if [[ "`$ZFSGETVAL type $fs`" == "filesystem" ]] ; then - if [[ "`$ZFSGETVAL mounted $fs`" == "no" ]] ; then - echo eMounting $Z_LOCAL_HOST:$fs - zfs mount $fs - fi - fi - - zrep_unlock_fs $fs - - -} - -######## zrep_top continues here - -usage(){ - echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another - echo in an ongoing basis. - echo - echo " Philip Brown, 2012-2019" - echo - echo Simple usage summary: - echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' - echo 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' - echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' - echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' - echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' - echo 'zrep failover [-L] ZFS/fs' - echo 'zrep takeover [-L] ZFS/fs' - echo - echo "Status and side operations" - echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' - echo 'zrep (list|-l) [-Lv] [fs/names]' - echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' - echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' - echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' - echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' - echo 'zrep version' - echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' - echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' - echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' - echo - echo ' -q option to sync says to Quietly ignore locked filesystems' - echo ' that have synced more recently than the given amount of seconds' - echo - echo 'Paired commands for high-transaction systems:' - echo ' zrep snaponly (ZFS/fs ... |all)' - echo ' zrep synconly (ZFS/fs ...|all)' - echo 'The above two commands split the simple sync subcommand, into two' - echo 'separate steps, so that a database, etc. may resume while the sync' - echo 'completes in the background' - echo '' - echo 'zrep defaults to using ssh. However, if remotehost is set to localhost,' - echo 'zrep will use a simple pipe instead.' - echo '' - echo ' More detailed examples can be found at:' - echo http://www.bolthole.com/solaris/zrep/zrep.documentation.html - echo '' - echo 'See the above documentation for details on using the -t flag to use zrep tags' -} - -# -# Special global flags that must always be processed FIRST, before normal command args. -# -while [[ "$1" == -[tD] ]] ; do - case "$1" in - -D) - DEBUG=1 - shift - ;; - -t) - if [ "$2" == "" ] ; then - usage - fi - #deliberately dont quote this to avoid stupidity or malice by user - ZREPTAG=$2 - shift - shift - ;; - esac -done - - -if [[ "$ZREPTAG" != "zrep" ]] ; then - # If custom zrep tag, then probably multiple layers of snapshots. - # In this case, DO NOT send all intermediate snapshots for replication - ZREP_INC_FLAG=${ZREP_INC_FLAG:-"-i"} - - if [ "$ZREPTAG" != "" ] ; then - ZREP_PATH="$ZREP_PATH -t $ZREPTAG" - fi -fi -# ensure it is set to SOMETHING by default. Default is to send all -ZREP_INC_FLAG=${ZREP_INC_FLAG:-"-I"} - - - - -case "$1" in - "") - usage - ;; - changeconfig|-C) - shift - zrep_changeconfig "$@" - ;; - clear) - shift - # only actually allows ONE fs - if [[ "$1" == "-f" ]] ; then - shift - ZREP_FORCE=-f - fi - zrep_clear "$@" - ;; - expire|-e) - shift - zrep_expire "$@" - ;; - init|-i) - shift - while [ "$1" != "" ] ; do - case $1 in - "-f") - shift - ZREP_FORCE=-f - ;; - "-v") - shift - ZREP_VERBOSE=yes - ;; - *) - break - ;; - esac - done - zrep_init "$@" - ;; - sentsync) - shift - # Note that this will NOT accept multiple snaps, for safety - zrep_sentsync "$@" - ;; - snaponly) - shift - zrep_snaponly "$@" - ;; - sync|-S) #remember, this is inverse of refresh - shift - while [ "$1" != "" ] ; do - case $1 in - "-f") - shift - ZREP_FORCE=-f - ;; - "-v") - shift - ZREP_VERBOSE=yes - ;; - "-r") - shift - ZREP_RESUME=yes - ;; - *) - break - ;; - esac - done - zrep_sync "$@" - ;; - synconly) - shift - zrep_synconly "$@" - ;; - refresh) # yes keep this in this order - shift - while [ "$1" != "" ] ; do - case $1 in - "-f") - shift - ZREP_FORCE=-f - ;; - "-v") - shift - ZREP_VERBOSE=yes - ;; - "-r") - shift - ZREP_RESUME=yes - ;; - *) - break - ;; - esac - done - zrep_refresh "$@" - ;; - status|-s) - shift - zrep_status "$@" - ;; - list|-l) - shift - zrep_list "$@" - ;; - lastsent|getlastsent) - # lastsent is a backward compability hook. - shift - zrep_getlastsent "$@" - ;; - failover) - shift - if [[ "$1" == "-f" ]] ; then - shift - ZREP_FORCE=-f - fi - zrep_failover "$@" - ;; - setlastsent) - shift - #zrep_setlastsent "$@" - echo Nothing to see here. You might want sentsync. You might not. - # This kind of overlaps with "sentsync". Which may be poorly named - # - exit 1 - ;; - takeover) - shift - zrep_takeover "$@" - ;; - uptodate) - shift - zrep_uptodate "$@" - exit $? - ;; - version) - echo "zrep $ZREP_VERSION" - echo "http://www.bolthole.com/solaris/zrep" - echo "http://www.github.com/bolthole/zrep" - exit - ;; - - _refreshpull) # Secret option DO NOT PUT IN USAGE!! - shift - _refreshpull $@ - ;; - _refreshcomplete) # Secret option DO NOT PUT IN USAGE!! - shift - _refreshcomplete $@ - ;; - *) - print "ERROR: unrecognized zrep subcommand $1" - echo " Dont know what to do with: $0 $@" - print "" - usage - ;; -esac - - From 4acebed330a640f9ede3e03eaef58208e7cebb43 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Thu, 24 Mar 2022 03:57:36 +0000 Subject: [PATCH 09/15] end-of-function tweaks --- zrep | 2873 +++++++++++++++++++++++++++++++++++++++++++++++++++++ zrep_snap | 2 - zrep_sync | 2 + 3 files changed, 2875 insertions(+), 2 deletions(-) create mode 100755 zrep diff --git a/zrep b/zrep new file mode 100755 index 0000000..4c4757a --- /dev/null +++ b/zrep @@ -0,0 +1,2873 @@ +#!/bin/bash --noprofile + +# For more detailed documentation, see zrep.txt or zrep.overview.txt +# note to self: always make this 3 fields +ZREP_VERSION=2.0.0 + +################################################ + + +######## start of included files from zrep_top here +########### File: zrep_vars + +# This should basically be included as common vars before all zrep stuff. +# It contains all 'constant' definitions, as well as a few crucial +# shared routines, such as lock handling ones. + +######################################################################## +# User tunable section. These may be overridden in user environment vars +# +# Additionally, we check /etc/default/zrep first. +# If you want to allow users to override the options set there, then be sure +# to use similar syntax to below: +# VAR=${VAR:-yourvalue} +# + +if [[ -f /etc/default/zrep ]] ; then + . /etc/default/zrep +fi + +SSH=${SSH:-ssh} +ZREP_PATH=${ZREP_PATH:-zrep} #Set to /full/path/to/zrep, if needed, for remote + +# Set this if you want to use a different zfs property to store zrep info. +# default has things like zrep:dest-host and zrep:dest-fs +# Change this to non-default, if you want to have multiple destinations. +# You then need to run a separate zrep for each dest. +# In this case, I suggest all runs use non-default value. +# eg: ZREPTAG=zrep-1, ZREPTAG=zrep-2. +# or, ZREPTAG=zrep-uk, ZREPTAG=zrep-us +# ** make sure value can be used in a snapshot name as well, because it +# ** will be ! +# !! also, make sure variable is EXPORTED !! +ZREPTAG=${ZREPTAG:-zrep} + +# If you'd like a little extra debug output define this env var to something +#DEBUG=1 + +#If set to yes(default), renames failed sync snapshots to "zrep_#####_unsent" +ZREP_RENAME_UNSENT=${ZREP_RENAME_UNSENT:-yes} + +# The default behavior for up-to-date ZFS filesystems, is that zrep will call +# a zfs send/receieve that creates the remote filesystem with pretty much the same options. +# However, sometimes, it is important to have the remote side have a diferent set of +# options, BEFORE data has been transferred. ie: with remote using compression. +# Note1: If set, this will stop the typical inheritance of src filesystem properties +# Note2: It will force the "old zfs creation style" codepath for init. But not +# for the rest of zrep +#ZREP_CREATE_FLAGS="-o compression=on,xx=yy" + +#Convenience hook. +#By default, the remote fs should start with the same properties that the master has. +#However, sometimes people want different ones. +#If this var is set, a post-init ssh will be made, to set the requested properties. +#While some zfs implementations support multiple values in a single set command, +#Others do not. So if multiple are set here, multiple ssh calls will be made. +# syntax: "prop1=value [prop2=value prop3=value ... ]" +#ZREP_INIT_REMOTE_PROPERTIES="compression=on" + + +# The fastest alternative transport, IF you have multicore/thread CPUs, +# would seem to be bbcp. If you have both, then you probably want to +# define something like this in your environment: +#BBCP="bbcp -s 8" + + +# You can apply filters to tweak throughput in various ways +# Sometimes it helps receive performance to use mbuffer +# Other times, you may have highly compressible data, and custom +# compression routines such as lzop or lz4 may show significant +# gains over ssh builtin compression +# +# lz4 example: +# ZREP_OUTFILTER="lz4 -c" +# ZREP_INFILTER="lz4 -d" +# +# mbuffer example: +# ZREP_OUTFILTER="mbuffer -q -m 1G -s 128k" +# ZREP_INFILTER="mbuffer -q -m 1G -s 128k" + + + +# If you want to recursively create/send snapshots, set this to -R +# (or use the -R option to BOTH zrep init and zrep sync +#ZREP_R=-R + + +# Sometimes, people may want to add extra flags to "zfs send". +# If your system supports it, then adding -c means that +# compressed filesystems will be sent in compressed form, rather +# then autouncompressing +# --raw is sometime used for encrypted filesystems +#ZREP_SEND_FLAGS="-c --raw" + +# Theoretically identical in purpose to ZREP_SEND_FLAGS, but it turns out that when using +# zfs send -t (resume-token) +# you are only allowed to use a restricted set of flags. It normally uses what was +# set the first time. +# So, for resume sends, we clear send flags, and only use what is set here below +#ZREP_RESUME_FLAGS=-v + + + +# There are two types of zfs incremental snapshots. +# The default incremental flag is -I. +# UNLESS you set ZREPTAG to something other than zrep, in which case +# you will have multiple zrep snapshot names probably going to different +# places, and expiration wont work properly on the remote sides +# So we will autochange incremental type to -i. .. unless you explicitly +# set an override value for INC_FLAG in either case. +# Probably should have named this INCR_FLAG, but it's in use now +#ZREP_INC_FLAG=-I + +# This currently doesnt do much, and is probably best not user-set. +# I should probably make use of this more standardized. +# But you can set it if you want +#ZREP_VERBOSE=yes + +# Some odd people like to configure a non-root user for zrep, +# and not give it expire permissions. +# Well.. okay then... +#ZREP_SKIP_EXPIRE=1 + +# If you want to override uname -n, with an official +# canonical name for zrep:src-host, use this environment variable when +# you run "zrep init" +#ZREP_SRC_HOST=somehost.your.dom + +# Solaris hack to use native perl, which isnt always in $PATH,but should +# always be there. It's also simple, straightforward, and non-extended. +# On other OSs this path will not exist, so it will just fall back to +# use the system default perl. Unless user wants to specify perl path. +# If you dont have /usr/perl5, this wont hurt you so just ignore it. +PERL_BIN=${PERL_BIN:-/usr/perl5/bin} + + +# Hidden var, that isnt really meant to be used directly. +# It gets set if you use "zrep sync -c". +# But you could theoretically set this directly instead if you prefer +#ZREP_CHANGEDONLY=yes + + +# This only gets used at init time +ZREP_SAVE_COUNT=${ZREP_SAVE_COUNT:-5} + +# This currently is only used in zrep status +# If you change it to %s then zrep will display +# seconds since the most resent snapshot was synced. +ZREP_DATEFORMAT=${ZREP_DATEFORMAT:-%Y/%m/%d-%H:%M:%S} + + +######################################################################### +######################################################################### +# Everything else below here, should not need to be touched. +######################################################################### +######################################################################### + +_debugprint(){ + if [[ "$DEBUG" != "" ]] ; then + echo DEBUG: $@ + fi +} + +# This consolidated function is both for prettiness, and also +# to make dealing with github issue #22 easier, about redirecting stderr +_errprint(){ + # I thought /dev/fd was ksh builtin, so safest. But it glitches on some linuxen. + # echo $@ >/dev/fd/2 + + echo $@ >&2 + +} + + +# First we have some "special" internal vars. +# Then autodetect routines, +# and then internal utilities such as locking functions. + + +# zfs get syntax is so long and ugly, this is just an internal convenience +# Get a zfs property on fs or snap. Get JUST the value, and only +# a "locally set" value rather than an inherited one +ZFSGETLVAL="zfs get -H -o value -s local" +# But.. sometimes you want to allow propagated values. like +# the ones sent via the zrep_init setup +ZFSGETVAL="zfs get -H -o value" +# **warning** !! +# $ZFSGETLVAL returns "" on value not set. However, +# $ZFSGETVAL returns "-" on value not set. Grrr @zfs writers. + + +# Work around a bug in gentoo ksh that breaks "ls -l" builtin. +# It follows symlinks. +# Would prefer to just override with _AST_FEATURES, but apparently, +# that only gets checked when ksh first starts or something +# Maybe use getconf itself somehow, if safe? +# Trick would be to do +# export _AST_FEATURES="PATH_RESOLVE = physical" +# but cant. +#### This is not even used any more!! But I'm keeping it in as +#### documentation for historical knowlege +#if getconf PATH_RESOLVE > /dev/null 2>&1 ; then +# LS=/bin/ls +#else +# LS=ls +#fi + +# -n enforces "no dereference" of existing symlink, which is default +# behaviour on some, but not all systems, apparently +#LN_S="ln -n -s" +# I dont use ln any more, but leaving this for useful historical info. +# side note: ksh built-in ln, DOES NOT SUPPORT -n ! + + +if [[ "$ZREP_SRC_HOST" != "" ]] ; then + Z_LOCAL_HOST=${ZREP_SRC_HOST} +else + Z_LOCAL_HOST=`uname -n` + Z_LOCAL_HOST=${Z_LOCAL_HOST%%.*} +fi + + +# Slightly ugly to implement this as a global flag. But... it makes +# a lot of things simpler, like "ssh zrep xyhz" for multiple things. +if [[ "$1" == "-R" ]] ; then + ZREP_R="-R" + shift +fi + +if [[ "$ZREP_R" == "-R" ]] ; then + # ZREP_R is a user-settable env var. It also gets used in + # "zfs send" commandlines. However.. we also need to call + # "zfs snap".. which requires LOWERCASE R. So this is an automatically set + # mirror of that. + Z_SNAP_R="-r" +fi + +if [[ "$MBUFFER" != "" ]] ; then + _errprint WARNING: MBUFER variable deprecated in zrep + _errprint WARNING: use ZREP_OUTFILTER and ZREP_INFILTER instead + ZREP_OUTFILTER="$ZREP_OUTFILTER $MBUFFER" + ZREP_INFILTER="$MBUFFER $ZREP_INFILTER" +fi + +# I HATE having to use a global for this. +# However, there is apparently a bug in typeset behaviour +# introduced in ksh93. UGHHH. +#ZREP_FORCE="-f" + +# Sneaky vars to avoid having to use if clauses in the core code +# HOWEVER! Note that ksh doesnt seem to evaluate pipe symbols when normaly +# expanded in a command line. So, have to use 'eval' to get them to register +if [[ "$ZREP_OUTFILTER" != "" ]] ; then + Z_F_OUT="| $ZREP_OUTFILTER" +fi +if [[ "$ZREP_INFILTER" != "" ]] ; then + Z_F_IN="$ZREP_INFILTER |" +fi + + +# used to have polymorphic assign of ZREP_INC_FLAG here, but had +# to move it to AFTER checking if -t option used + +# full name for this should probably be something like, +# PROPTYPES_THAT_ZREP_STATUS_AND_LIST_CAN_USE. But that's too long :) +# Not easy to check if property types allow type "received". +# Ancient systems do not allow it +# So, just tie this to MU6 related check,like HAS_SNAPPROPS, lower down +PROPTYPES="local,received" + + + +# dump the usage message, and check for capabilities +# make sure we dont spew for non-root, so that "zrep status" works +case `id` in + *'(root)'*) + ZREP_RUNDIR=${ZREP_RUNDIR:-/var/run} + ;; + *) + ZREP_RUNDIR=${ZREP_RUNDIR:-/tmp} + ;; +esac +# allow override, for code test utility +zrep_checkfile=${_ZREP_CHECKFILE:-$ZREP_RUNDIR/zrep.check.$$} + + +zfs >$zrep_checkfile 2>&1 +# Previously did a bit of a hack job for feature detection. +# Now attempting to make it smarter, +# at the expense of some startup speed :( +Z_HAS_X=0 # can use recv -x +Z_HAS_REC_U=0 # can use recv -u +Z_HAS_REC_O=0 # can use recv -o (note: SmartOS -o is NOT WHAT WE WANT) +Z_HAS_SNAPPROPS=0 + +if grep 'help' $zrep_checkfile >/dev/null ;then + # Presume Solaris 11, which has all features, but + # does not allow line-by-line feature detection easily + Z_HAS_X=1 # can use recv -x + Z_HAS_REC_U=1 # can use recv -u + Z_HAS_REC_O=1 # can use recv -o + Z_HAS_SNAPPROPS=1 # can set properties on snapshots + # This also lets me set "last synced" timestamps + # otherwise cant use zrep:sent sanely. + # Would lose information on rollbacks + + DEPTHCAP="-d 1" # limits "list -r" + +else + if grep 'receive[ |].*-[a-zA-Z]*x' $zrep_checkfile >/dev/null ;then + Z_HAS_X=1 # can use recv -x + fi + if grep 'receive .*-[a-zA-Z]*u' $zrep_checkfile >/dev/null ;then + Z_HAS_REC_U=1 # can use recv -u + fi + # This bit is unfortunately ugly. Two problems: + # SmartOS and FreeBSD implemented recv -o WRONG! + # They use -o to set "origin", not to set options + # So no Z_HAS_REC_O for it! + # But also, -o doesnt even show in the output of solaris zfs usage. sigh. + # So have to be creative. + # Note that some systems have '-o' directly after create, and some do not. + if grep 'create .*-o prop' $zrep_checkfile >/dev/null ;then + # This is probably nested under the create check, because we + # only use recv -o, right after using create -o. + # However, we now ALWAYS use create -o, so.. may be unneccesary + # to nest + # Skip zfs that uses recv -o origin + if ! grep 'rec[ev].*-o origin' $zrep_checkfile >/dev/null && + grep 'rec[ev].*-o ' $zrep_checkfile >/dev/null ; then + Z_HAS_REC_O=1 # can use recv -o + fi + fi + if grep 'set .*snapshot' $zrep_checkfile >/dev/null ;then + Z_HAS_SNAPPROPS=1 # can set properties on snapshots + fi + if grep 'list.*-d' $zrep_checkfile >/dev/null ;then + DEPTHCAP="-d 1" # limits "list -r" + else + DEPTHCAP="" + echo WARNING: old ZFS version detected with no depth protection + echo WARNING: You may not nest zrep managed filesystems + fi +fi + +if ((!Z_HAS_SNAPPROPS)) ; then + PROPTYPES="local" +fi + +rm $zrep_checkfile + + +Z_LOCK_RETRY=${Z_LOCK_RETRY:-10} # default 10 second retry, 1 per sec + +# This is named like a global override. and CAN be overridden by user. +# But should only be used in zrep_vars module +# Note: This path is why you should only give zfs privileges to a SINGLE USER. +Z_GLOBAL_LOCKFILE=$ZREP_RUNDIR/zrep.lock + +if [[ "$Z_GLOBAL_PID" == "" ]] ; then + export Z_GLOBAL_PID=$$ +fi + +Z_SETHOLD=${Z_SETHOLD:-"zfs hold"} +# if your zfs isnt new enough, and you like to live dangerously, +# you can skip setting holds by using this instead. +# Although I may not have gotten around to using this in the code either! +#Z_SETHOLD="echo skipping zfs hold on" + + +# return PID of proc holding global lock, or nothing +zrep_global_lock_pid(){ + cat $Z_GLOBAL_LOCKFILE 2>/dev/null +} + +# return 0 if "we" are holding lock, 1 otherwise +# Note that we check for "us, OR our global parent", if different +# +zrep_has_global_lock(){ + lockpid=`zrep_global_lock_pid` + + if [[ "$lockpid" == "" ]] ; then return 1 ; fi + if [[ "$lockpid" != "$Z_GLOBAL_PID" ]] ; then + if [[ "$lockpid" != "$$" ]] ; then + _debugprint 'has_global_lock? no. lock held by PID' $lockpid + return 1 + fi + fi + + return 0 +} + + + +#Note: it is an ERROR to call this if you already have lock +#It is binary, not recursive ownership. +#We do NOT try to clean up stale global lock. +#This is a shortterm lock. It should never be stale. If it is, +#it could indicate a more serious system/zrep problem happening. +zrep_get_global_lock(){ + typeset retry_count=$Z_LOCK_RETRY + typeset lockpid + + set -C #noclobber + # ignore error this time, because we retry anyway. + echo $Z_GLOBAL_PID > $Z_GLOBAL_LOCKFILE 2>/dev/null && return 0 + + # Otherwise, deal with fail/retry. + # Careful of race conditions on stale CLEAN UP! + # How to resolve problem where + # * multiple instances running + # * one instance detects stale + # * multiple instances decide to remove it + # * ONE removes it and creates new symlink + # * SECOND one was paused between detection and removal.. so removes + # * VALID lockfile?!?! + # For now, must request manual cleanup + while (( retry_count > 0 )); do + sleep 1 + errmsg=`echo $Z_GLOBAL_PID 2>&1 > $Z_GLOBAL_LOCKFILE ` + if [[ $? -eq 0 ]] ; then return 0 ; fi + + retry_count=$((retry_count-1)) + lockpid=`zrep_global_lock_pid` + if [[ "$lockpid" -le 0 ]] ; then + zrep_errquit ERROR: invalid contents for global lock file $Z_GLOBAL_LOCKFILE + fi + # Does the process holding the lock actually still exist? + # In theory, teenietiny chance of race condition for false stale. That's okay. + kill -0 $lockpid 2>/dev/null + if [[ $? -ne 0 ]] ; then + _errprint ERROR: stale global lock file + _errprint ERROR: shut down ALL zrep instances, then manually remove + _errprint $Z_GLOBAL_LOCKFILE + fi + + done + + echo Failed to acquire global lock + echo Error message was: $errmsg + return 1 + +} + +zrep_release_global_lock(){ + if zrep_has_global_lock ; then + rm $Z_GLOBAL_LOCKFILE + return $? + else + echo ERROR: zrep_release_global_lock called, but do not own lock + return 1 + fi +} + +# returns PID of zrep process holding a lock on filesystem, if there is one. +# NOTE: If "-s local" used, prints "" if lock unheld +# If no -s specified, prints "-" if lock unheld +zrep_fs_lock_pid(){ + $ZFSGETLVAL ${ZREPTAG}:lock-pid $1 +} +zrep_has_fs_lock(){ + typeset check=`$ZFSGETLVAL ${ZREPTAG}:lock-pid $1` + if ((check == $$)) ; then + return 0 + else + return 1 + fi +} + +# use global lock first (if not already), then +# grab lock on individual fs +# return 1 on fail, 0 on lock acquired +# Note that it is an ERROR to call this, if you already have lock +# Note2: if a dead process has lock, it will forcibly override and +# acqure lock +zrep_lock_fs(){ + # global lock is slow. so do quickcheck first. + typeset check=`zrep_fs_lock_pid $1` newcheck + if [[ "$check" != "" ]] ; then + # See if owning process still exists. + kill -0 $check 2>/dev/null + if [[ $? -eq 0 ]] ; then + _debugprint lock is still held by $check + return 1 + else + _debugprint lock is no longer held by $check + fi + fi + + zrep_get_global_lock + if [[ $? -ne 0 ]] ; then + if [[ "$DEBUG" != "" ]] ; then + _errprint zrep_lock_fs: failed to get global lock. PID=$$ fs=$1 + fi + return 1 + fi + + # Yes we already checked this, but we didnt have global lock. + # Avoid race condition and doublecheck now that we have global lock. + if [[ "$check" != "" ]] ; then + newcheck=`zrep_fs_lock_pid $1` + if [[ "$newcheck" != "$check" ]] && [[ "$newcheck" != "" ]] + then + # oops. someone else beat us to it. + # Better luck next time. + zrep_release_global_lock + return 1 + fi + # Keep in mind that stdin/out could be busy + # Cant use regular debugprint + if [[ "$DEBUG" != "" ]] ; then + _errprint overiding stale lock on $1 from pid $check + fi + fi + + zfs set ${ZREPTAG}:lock-pid=$$ $1 + zfs set ${ZREPTAG}:lock-time=`date +%Y%m%d%H%M%S` $1 + if [[ "$DEBUG" != "" ]] ; then + _errprint DEBUG: zrep_lock_fs: set lock on $1 + fi + zrep_release_global_lock +} + +# release lock, if we have it. +# Since this could be called by an exit cleanup routine blindly, +# dont exit program if we dont have lock. But do return error +zrep_unlock_fs(){ + typeset lockpid=`zrep_fs_lock_pid $1` + if ((lockpid != $$)) ; then return 1; fi + + #since "we" already have it locked, no need to get global lock first + zfs inherit ${ZREPTAG}:lock-time $1 + zfs inherit ${ZREPTAG}:lock-pid $1 + if [[ "$DEBUG" != "" ]] ; then + _errprint zrep_unlock_fs: unset lock on $1 + fi + + return 0 +} + + +# Quit whole program with error status, outputting args to stderr +# Release global lock if we are holding it +# Unless we're running in parallel batch mode +# I'll need to plan that out more carefully! +# +zrep_errquit(){ + _errprint Error: "$@" + + if zrep_has_global_lock ; then + if [[ "$$" -ne "$Z_GLOBAL_PID" ]] ; then + echo EXTRA-ERROR: Running in child proc. + echo 'Not sure whether to release global lock. NOT releasing!' + exit 1 + else + + zrep_release_global_lock + fi + fi + exit 1 +} + +# Optimization wrapper for ssh: if destination host is ourself, dont use ssh. +# Just run the local command mentioned +# Be careful about quotes here. In fact, try not to use any. +# Usage: zrep_ssh desthost commands_for_ssh go_here +zrep_ssh(){ + typeset ssh_cmd + + case "$1" in + localhost|$Z_LOCAL_HOST) + ssh_cmd="eval" + shift + $ssh_cmd "$@" + return $? + ;; + esac + + if [[ "$2" == "$ZREP_PATH "* ]] && [[ "$DEBUG" != "" ]] + then + #okay yes this is horrible. sigh. + #we normally go to great lengths to preserve ssh arg as single quoted string, + # to identically match passed in arg quoting. + #but this next line undoes that + + set -- $* + + ssh_cmd="$SSH $1 $ZREP_PATH -D" + shift + shift + $ssh_cmd "$@" + + return $? + fi + + ssh_cmd="$SSH $1" + shift + $ssh_cmd "$@" + return $? + +} + +zrep_gettimeinseconds(){ + typeset seconds + typeset PATH=$PERL_BIN:$PATH + # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 + # seconds=`printf '%(%s)T' now` + seconds=`date '+%s'` + + if [[ -z "$seconds" ]] ; then + # Unfortunately, solaris date doesnt do '%s', so try perl first. + # It's more consistent. + seconds=`perl -e 'echo int(time);' 2>/dev/null` + fi + if [[ -z "$seconds" ]] ; then + # attempt fallback if no perl present (eg: stock FreeBSD) + seconds=`date +%s` + fi + if [[ -z "$seconds" ]] ; then + zrep_errquit zrep_gettimeinseconds doesnt know what to do + fi + echo $seconds +} + +###### File: zrep_status + +# be sure to have included zrep_vars + +# This file contains all "status" related routines. +# It should be folded into final "zrep" script + + + + +# +#Give this a top level zrep registered filesystem, NOT snapshot. +# Will echo out various status points, such as last sync date. +# Or if given no args, will echo out sync date for all zrep mastered fs +# Note that the date given is time of SNAPSHOT, not time sync completed. +# +zrep_status(){ + typeset check fs srcfs desthost destfs date lastsynced + typeset verbose=0 vdate="" monitorr=0 + typeset printall=0 + + if [[ "$1" == "-v" ]] ; then + verbose=1 ; shift + fi + if [[ "$1" == "-m" ]] ; then + monitor=1 ; shift + fi + + if [[ "$1" == "" ]] ; then + set -- `zrep_list_master` + elif [[ "$1" == "-a" ]] ; then + set -- `zrep_list` + printall=1 + fi + + while [[ "$1" != "" ]] ; do + fs="$1" + + destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` + if [[ "$destfs" == "-" ]] || [[ "$destfs" == "" ]]; then + zrep_errquit "$fs is not a zrep registered filesystem" + fi + + if ((monitor)) ; then + # have to allow for ONE unsent, since it might be in progress + typeset count=`getunsentcount $fs` + if [[ "$count" -gt 1 ]] ; then + echo WARNNING: unset snapshot count at $count + fi + continue + fi + + lastsynced=`getlastsnapsent $fs` + if [[ "$lastsynced" == "" ]] ; then + date="[NEVER]" + else + if (( Z_HAS_SNAPPROPS )) ; then + typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsynced` + date=`printf "%(${ZREP_DATEFORMAT})T" "#$sentseconds"` + vdate=${date%:*} + fi + + #This is also a fallback for no-perl FreeBSD systems + if [[ "$vdate" == "" ]] ; then + date=`$ZFSGETVAL creation $lastsynced` + vdate=${date#????} + fi + fi + if ((printall)) && ((verbose)) ; then + # If we are printing out ALL filesystems, + # then we have to make sure left side is always + # "src filesystem", not "named filesystem" + # then we have to check what the src fs is + srcfs=`$ZFSGETVAL ${ZREPTAG}:src-fs $fs` + else + # Yes, okay, if -a is used, then + # technically, this isnt always "src". + # but it prints out right, so close enough :) + srcfs="$fs" + fi + + if ((verbose)) ; then + desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` + printf "%-25s->%-35s %s\n" $srcfs "$desthost:$destfs" "$vdate" + else + printf "%-52s " $srcfs + echo "last: $date" + fi + shift + done +} + + +_master_fs_names(){ + zfs get -H -o name -s local ${ZREPTAG}:master "$@" +} + + +# convenience function to list only local filesystems for which we are +# zrep master for. +# In contrast, zrep_list, lists ALL zrep registered filesystem, at the moment. +# +# Annoyingly... it would be way faster if we could just stick with the +# pure "zfs get" implementation, but we also need to deal with the zone +# issue. When a single zfs filesystem is visible aross multiple zones, +# we dont want them all thinking they are master +# +# Durn. Individual validation required. +zrep_list_master(){ + typeset srchost + for fs in `_master_fs_names "$@"` ; do + srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` + if [[ "$srchost" == "$Z_LOCAL_HOST" ]] ; then + echo $fs + fi + done +} + + +# Given ONE filesystem, print all zrep properties for it. +# Note that this is internal routine. we do not validate input. +list_verbose(){ + echo $1: + # sneaky cheat: only user-set properties will + # match these 'source' types. So "grep zrep:" is not + # neccessary. Although we may pick up other user-set values, + # but that is not neccessarily a bad thing + zfs get -H -o property,value -s $PROPTYPES all $1 + echo "last snapshot synced: `getlastsnapsent $1`" + +} + + +# Note: called by both user, AND by zrep_status +# +# Usage: +# zrep_list [-v] +# zrep_list [-L] +# zrep_list [-v] fs1 fs2 +#(also zrep_list -s which passes to zrep_list_snaps) +# +# list all zrep-initialized filesystems (NOT snapshots..) +# If no specific fs listed, will show master, AND received filesystems, +# unless -L given (in which case, only local masters will be shown) +# +# Normal output is one line per fs. +# +# -v gives all properties of each filesystem +# Give only one of -L or -v +# +zrep_list(){ + typeset fslist="" verbose=0 + # This works because we only set this property on the actual fs. + # "source type" on snapshots for this property is "inherited" not local + # or "received" + typeset printcmd="zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs" + + case $1 in + -v) + verbose=1 + printcmd=list_verbose + shift + ;; + -L) + # reminder: cant have this, AND verbose. + printcmd="zrep_list_master" + shift + ;; + -s) + shift + zrep_list_snaps "$@" + return + ;; + esac + + + # If specific fs(s) named, iterate over them and quit + if [[ "$1" != "" ]] ; then + while [[ "$1" != "" ]] ; do + if zfs list -t filesystem,volume $1 >/dev/null 2>&1 ; then + $printcmd $1 + else + zrep_errquit "Expecting filesystem, but got $1" + fi + shift + done + return + fi + + # Must be "list all" now. But which output format? + + # If not verbose, we have a nice shortcut to just list + # all filesystems that zrep has marked. + if (( $verbose == 0)) ; then + $printcmd + return + fi + + + # oh well. have to step through them one by one now, to + # echo out the properties associated with each zrep filesystem + + fslist=`zfs get -H -o name -s $PROPTYPES ${ZREPTAG}:dest-fs` + + for fs in $fslist ; do + $printcmd $fs + echo "" + done + + +} + +# Similar to zrep_list, but lists SNAPSHOTS instead of filesystems +# The purpose is to allow a sysadmin to see easily when snapshots have +# been created. +# Either give a list of specific filesystems, or no args, which +# will attempt to list all zrep-related snapshots +# It will list only zrep MASTER filesystem snapshots, in that case. +zrep_list_snaps(){ + if [[ "$1" == "" ]] ; then + set -- `_master_fs_names` + if [[ "$1" == "" ]] ; then + _errprint "No zrep master filesystems found" + return 0 + fi + fi + + while [[ "$1" != "" ]] ; do + zfs list -r -t snapshot -o name,creation $1 + shift + done + +} + +# Given a filesytem name, prints out full snapshot name of last successfully synced snap +zrep_getlastsent(){ + if [[ "$1" == "" ]] ; then + _errprint ERROR: zrep uptodate requires the name of a zrep managed fs + exit 1 + fi + + echo `getlastsnapsent $1` + +} + +# Give a filesystem name. +# Gets last sent snapshot, and determines if there have +# been any writes since then. +# If not, then file sytem is "up to date" +zrep_uptodate(){ + if [[ "$1" == "" ]] ; then + _errprint ERROR: zrep uptodate requires the name of a zrep managed fs + exit 1 + fi + + typeset bytecount + bytecount=`$ZFSGETVAL written "$1"` + # two fail conditions are: + # 1. not zrep filesystem + # 2. system does not support "zfs get written" + # Either way we count it as "not up to date" + + if [[ $bytecount == 0 ]] ; then + return 0 + else + return 1 + fi +} +################ File: zrep_snap +# be sure to have included zrep_vars + +# This file contains routines related to +# "make new snapshot, using next sequence number". +# So it thus includes all snap sequence related routines +# It may contain "sync snapshot" related routines for now. +# It also is definitive for the format of snapshot names +# It also contains most "query status of snaps" type routines, +# such as "getlastsnapsent" +# +# Normal style for making a snapshot and syncing it: +# 1. create a snapshot. +# 2. sync it over +# 3. set "zrep:sent" on *snapshot*, with timestamp in seconds +# Old-nasty-zfs compat mode: +# Step 3. Add/update "zrep:lastsent->snapname", and +# "zrep:lastsenttime->timestamp", on *filesystem* +# +###################################################################### + + +#pass in a zrep ZFS snapshot name. strip our our sequence number and echo in back +_getseqnum(){ + echo "$1" | sed 's/.*@'${ZREPTAG}'_\(......\).*/\1/' +} + +# By observation, 'zfs list' shows snapshots order of creation. +# last listed, should be last in sequence. +# But, dont take chances!! +getlastsequence(){ + typeset lastval + #remember, filesystems can have '_' in them + _getseqnum `getlastsnap $1` + +} + +# prints out last snapshot zrep created, going purely by sequence. +# Note: "last created", which may or may NOT be "last successfully synced". +# This is basically "getallsnaps |tail -1" +getlastsnap(){ + zfs list -t snapshot -H -o name $DEPTHCAP -r $1 | + sed -n "/@${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/"p | + sort | tail -1 +} + +# Usage: getlastsnapsent zpool/FSNAME +# note to self: in modern zrep, ALL successfuly synced snapshots have +# a timestamp value, zrep:sent=TIMESTAMPHERE +# Thats why the sort and tail is neccessary +getlastsnapsent(){ + # arg. more efficient if we can just return value directly, + # but i'm using backwards compat :( + typeset lastsent + lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1 | + sort | tail -1` + if [[ "$lastsent" != "" ]] ; then + echo $lastsent + return + fi + + # Fallback method, for backwards compat with older ZFS code, + # since it cant set properties on snapshots + zfs get -H -o value -s local ${ZREPTAG}:lastsent $1 +} + +# HORRIBLY DANGEROUS +# ONLY should be used by zrep_sentsync. +# Kind of the inverse to getlastsnapsent. But should NOT use "fallback method" in getlastsnapsent. +# This is only for zrep_sentsync, which only uses newer method. +_clearlast(){ + typeset lastsent + lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1|tail -1` + + while [[ "$lastsent" != "" ]] ; do + _errprint WARNING: clearing sent value from $lastsent + zfs inherit ${ZREPTAG}:sent $lastsent + lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $1|tail -1` + done + +} + +# return a number, which is the difference between the lastsnapshot counter, and +# the last successfully synced snapshot counter. +# In theory, can only be positive. +# +getunsentcount(){ + typeset lastsynced lastsnap lastsyncedseq lastsnapseq + lastsynced=`getlastsnapsent $1` + lastsnap=`getlastsnap $1` + + if [[ "$lastsynced" == "$lastsnap" ]] ; then + return 1 + fi + + lastsyncedseq=`getseqnum $lastsynced` + lastsnapseq=`getseqnum $lastsnap` + + lastsyncedseq=$((16#$lastsyncedseq)) + lastsnapseq=$((16#$lastsnapseq)) + + + if [[ "$lastsyncedseq -lt 1 ]] ; then + zrep_errquit Error: cannot parse $lastsynced + fi + if [[ "$lastsnapseq -lt 1 ]] ; then + zrep_errquit Error: cannot parse $lastsnap + fi + + echo $(( $lastsnapseq - $lastsyncedseq )) + +} + +# outputs time in seconds, of when the last successful sync for the +# filesystem was done. (in format compatible with zrep_gettimeinseconds() ) +# Note that this is time of actual sync, not snapshot creation time. +# +# This unfortunately needs to be compatible with both new way, and +# old-nasty-hack-way +# +# In future, may take optional argument of which HOST to check +# sync with. But since I currently only suport one host per fs... oh well. +# If never synced, will return 1, and echo "" +# +getlastsynctime(){ + typeset fs lastsent senttime + + if [[ "$1" == "" ]] ; then + zrep_errquit Internal error: no arg to getlastsynctime + fi + fs="$1" + + # Deal with possibly upgraded system; + # Check "lastsent", only as fallback. + + # copy from getlastsnapsent, but only using newest method + lastsent=`zfs get -H -o name -r -s local ${ZREPTAG}:sent $fs | + sort | tail -1` + senttime=`zfs get -H -o value ${ZREPTAG}:sent $lastsent` + if [[ "$senttime" != "" ]] ; then echo $senttime ; return 0; fi + + # ooops. try fallback to nasty old zfs-compat style + senttime=`zfs get -H -o value ${ZREPTAG}:lastsent $fs` + echo $senttime + if [[ "$senttime" != "" ]] ; then return 0; fi + return 1 +} + +#This is for synctosnap, and also zrep_expire +getallsnaps(){ + zfs list -t snapshot -H -o name $DEPTHCAP -r $1 | + sed -n "/@${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]/"p | + sort +} + +# list all snapshots of the given filesystem, that are made by this prog +# arg: fs +list_autosnaps(){ + if [[ "$1" == "" ]] ; then + zrep_errquit "zrep internalerror: no arg for list_autosnaps" + fi + zfs list $DEPTHCAP -r -H -o name -t snapshot $1 | + grep "@${ZREPTAG}_[0-9a-f][0-9a-f]" + # Make sure this format matches other routines in here + # Okay to just check first few digits though +} + +# User entrypoint. Part of pair: snaponly, sendonly +# Just makes snapshot. +zrep_snaponly(){ + typeset srcfs + + if [[ "$1" == "all" ]] ; then + set -- `zrep_list_master` + if [[ "$1" == "" ]] ; then + exit + fi + fi + + while [[ "$1" != "" ]] ; do + srcfs="$1" ;shift + zrep_lock_fs $srcfs + if [[ $? -ne 0 ]] ; then + # this function is supposed to be coordinated by user + # therefore, if something else is competing, + # coordination has failed. no retry. + zrep_errquit zrep snaponly failed for $srcfs: cannot get lock + + fi + + makesnap $srcfs ||zrep_errquit snaponly for $srcfs failed + + zrep_unlock_fs $srcfs + + done +} + +# +# creates next snapshot in sequence +# consider holding lock here +# Caller must have zrep lock on filesystem: +# we verify with zrep_has_fs_lock +makesnap(){ + typeset check oldseq newseq="" newseqX newsnap + + #sanity checks first! + check="`$ZFSGETVAL ${ZREPTAG}:src-host $1`" + if [[ "$check" != "$Z_LOCAL_HOST" ]] ; then + _errprint ERROR: we are not master host for $1 + _errprint master is $check, we are $Z_LOCAL_HOST + exit 1 + fi + + zrep_has_fs_lock $1 + if [[ $? -ne 0 ]] ; then + _errprint Internal error: makesnap fail, no lock on $1 + exit 1 + fi + + oldseq=`getlastsequence $1` + # This means input is base 16 + newseq=$((16#$oldseq)) + + newseqX=$(printf "%.6x" $(($newseq + 1)) ) + + + #_errprint DEBUG old=$oldseq new=$newseqX + newsnap="$1@${ZREPTAG}_$newseqX" + + zfs snapshot $Z_SNAP_R $newsnap + if [[ $? -eq 0 ]] ; then + echo -n $newsnap; return 0 + else + return 1 + fi + +} + +## This is the implentation for the "zrep clear" command +## Purpose is to remove all zrep related hooks from a local filesystem. +## (NOT delete it) +## Will remove zrep snapshots and zfs zrep: properties +zrep_clear(){ + + if [[ "$1" == "" ]] ; then + zrep_errquit clear command requires an argument + fi + + echo "WARNING: Removing all zrep configs and snapshots from $1" + echo " (for TAG=${ZREPTAG})" + + + if [[ "$ZREP_FORCE" != "-f" ]] ; then + echo Continuing in 10 seconds + sleep 10 + fi + + _clearsnaps $1 + _clearvars $1 +} + +_clearsnaps(){ + echo Destroying any zrep-related snapshots from $1 + snaplist=`list_autosnaps $1` + for snap in $snaplist ; do + zfs destroy -r $snap + done +} + +_clearvars(){ + echo Removing zrep-related properties from $1 + proplist=`zfs get -H -o property all $1|grep ${ZREPTAG}:` + for prop in $proplist ; do + zfs inherit $prop $1 + done +} + +## This is a special internal routine, used only by zrep_init, +## to reset target fs to pre-zrep state. +## call with "srcfs errmsg1 errmsg2..." +## It will also REMOVE REMOTEFS if set in PROPERTIES!! +clearquit(){ + remhost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` + remfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` + + if [[ $? -eq 0 ]] && [[ "$remhost" != "-" ]] && [[ "$remfs" != "-" ]]; then + zrep_ssh $remhost zfs destroy -r $remfs + fi + _clearsnaps $1 + _clearvars $1 + + shift + zrep_errquit "$@" +} + + +# Shared internal routine. +# +# Set the to/from properties on a fs for zrep +# Called by zrep_init and zrep_changeconfig +# Usage: +# setfsconfigs srcfs desthost destfs +# setfsconfigs -d destfs srchost srcfs +# +setfsconfigs(){ + typeset srchost srcfs desthost destfs fsname + + if [[ "$1" == "-d" ]] ; then + srchost="$3" + srcfs="$4" + desthost=$Z_LOCAL_HOST + destfs="$2" + fsname=${destfs} + else + srchost=$Z_LOCAL_HOST + srcfs="$1" + desthost="$2" + destfs="$3" + fsname=${srcfs} + fi + + + if [[ "$destfs" == "" ]] ; then + zrep_errquit "zrep: no dest fs specified" + fi + zfs list $fsname >/dev/null ||zrep_errquit "filesystem $fsname must exist already" + + # + # This is really only for when we are called from init I think? + # + case $destfs in + # Originally, I had this passthrough only if fsname was at end + # However,we must allow destfs to have different leaf name, + # for circumstances such as replication to same host + */*) + : + ;; + *) + # Only the pool name given. + # With our curent workflow, that can ONLY WORK + # if recent versions of ZFS are in use. + if (( Z_HAS_REC_U )) ; then + echo WARNING: forcing override sync to top level pool $desthost:$destfs + echo Not using a usual sub-filesystem + sleep 5 + else + zrep_errquit older zfs version, cannot initialize top level pool + fi + ;; + esac + + zfs set ${ZREPTAG}:src-fs=$srcfs $fsname + zfs set ${ZREPTAG}:src-host=$srchost $fsname + zfs set ${ZREPTAG}:dest-fs=$destfs $fsname + zfs set ${ZREPTAG}:dest-host=$desthost $fsname + zfs set ${ZREPTAG}:savecount=$ZREP_SAVE_COUNT $fsname +} + + +# +# Old way used to use recv -x, but not all systems have that. so +# preferred method is now to use recv -u instead. +# To init from existing snapshot instead, see documention at bolthole.com +# Note that remote fs must share same stem name as source. (for now?) +# +zrep_init(){ + typeset srcfs="$1" desthost="$2" destfs="$3" snap check vol=0 + typeset mountpoint vflags verbose + typeset token + + if [[ "$ZREP_FORCE" != "-f" ]] ; then + zrep_ssh $desthost zfs list destfs >/dev/null && + zrep_errquit "$desthost:$destfs already exists! Will not overwrite without -f" + fi + + if [[ "$ZREP_VERBOSE" != "" ]] ; then + verbose=-v + fi + + if [[ "$srcfs" == "" ]] ; then + zrep_errquit "zrep: no fs specified" + fi + + #sanity checks + check="`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs`" + if [[ "$check" != "-" ]] ; then + echo "$srcfs is at least partially configured by zrep" + check="`$ZFSGETLVAL ${ZREPTAG}:master $srcfs`" + if [[ "$check" != "" ]] ; then + zrep_errquit "${ZREPTAG}:master detected!!" + fi + + if [[ "$ZREP_RESUME" == "" ]] ; then + zrep_errquit "To re-initialize, first use zrep clear $srcfs" + fi + + # if it exists already, presume we need to resume an init. + # if we cant, then its an error. + + + + token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` + if [[ "$token" == "-" ]] ; then token="" ; fi + if [[ "$token" == "" ]] ; then + zrep_errquit Partial init of $src detected but no resume token found. Suggest you zrep clear and start again + fi + + echo "Partially complete init detected. Attempting to resume send" + + if [[ "$BBCP" != "" ]] ; then + SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" + $BBCP -N io "$SENDCMD" \ + "$desthost:zfs recv $destfs" + + else + eval zfs send ${ZREP_RESUME_FLAGS} -t $token ${Z_F_OUT} | + zrep_ssh $desthost "${Z_F_IN} zfs recv $destfs" + fi + if [[ $? -ne 0 ]] ; then + zrep_errquit resume send of zrep init $srcfs failed + fi + fi + + + check="`$ZFSGETVAL type $srcfs`" + if [[ "$check" == "volume" ]] ; then + vol=1 + if (( ! Z_HAS_REC_O )) ; then + echo WARNING: no proper recv -o detected + echo WARNING: extremely old versions of ZFS crash with volume init + echo Continuing in 5 seconds.... + sleep 5 + vflags="`$ZFSGETVAL volsize $srcfs`" + vflags="-V $vflags" + + + fi + # for details,see + # https://groups.google.com/forum/#!topic/comp.unix.solaris/-5bcZFInozk + # subject:"solaris 11 crash when zfs send/receive of volume" + fi + + + #get this for later + mountpoint=`$ZFSGETLVAL mountpoint $srcfs` + + # Make this section conditional, so that we keep shared codepath for completion + # of initial sync. + # This is actually the "normal" snapshot and sync path. + # + if [[ "$token" == "" ]] ; then + + if [[ "$ZREP_RESUME" != "" ]] ; then + recv_s=-s + fi + + echo Setting zrep properties on $srcfs + setfsconfigs $srcfs $desthost $destfs + + #setfsconfigs may do some "smarts" to adjust value, so get it again. + # yes only check for LOCAL this time. Paranoia.... + destfs=`$ZFSGETLVAL ${ZREPTAG}:dest-fs $srcfs` + + if (( Z_HAS_REC_O )) ; then + READONLYPROP="-o readonly=on" + else + READONLYPROP="" + echo Warning: zfs recv lacking -o readonly + fi + if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then + echo Creating destination filesystem as separate step + # normally would want to use -o readonly here. + # however, that breaks when -R is used. + # set it after transfer instead + zrep_ssh $desthost zfs create $ZREP_CREATE_FLAGS $vflags $destfs || zrep_errquit "Cannot create $desthost:$destfs" + fi + + snap="${srcfs}@${ZREPTAG}_000000" + echo Creating snapshot $snap + zfs snapshot $Z_SNAP_R $snap || clearquit $srcfs "Cannot create initial snapshot $snap" + + # Note that we may not want to use -p for normal zrep syncs + # We also should not use -F for normal recv. See workflow.txt + # Note: we may have to reset readonly=on, if we used -p on send... + # + echo Sending initial replication stream to $desthost:$destfs + if (( Z_HAS_REC_U )) ; then + # This is the nice, clean, modern codepath, to send + # zrep settings over automatically at first init. + # Note that we use "zfs send -p" to preserve properties. + + if [[ "$BBCP" != "" ]] ; then + $BBCP -N io "zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} -p $snap" \ + "$desthost:zfs recv -u $READONLYPROP $recv_s -F $destfs" + else + eval zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} -p $snap ${Z_F_OUT}| + zrep_ssh $desthost "${Z_F_IN} zfs recv -u $READONLYPROP $recv_s -F $destfs" + fi + + else + ## arg.. Update your systems!! + # without -u, risky to use send -p if mountpoint set. + # (So we cant avoid it potentially WRONGLY setting mountpoint) + # This means we have to manually set props lower down as well. + # (yeah okay I could check if $mountpoint set, but + # there are too many code branches here already, so too bad! + # Update your system! :p ) + + if [[ "$BBCP" != "" ]] ; then + $BBCP -N io "zfs send ${ZREP_R} ${ZREP_SEND_FLAGS} $snap" \ + "$desthost:zfs recv $READONLYPROP $recv_s -F $destfs" + else + eval zfs send ${ZREP_R} ${ZREP_SEND_FLAGS} $snap ${Z_F_OUT}| + zrep_ssh $desthost "${Z_F_IN} zfs recv $READONLYPROP $recv_s -F $destfs" + fi + fi + if [[ $? -ne 0 ]] ; then + if [[ "$ZREP_RESUME" == "" ]] ; then + clearquit $srcfs "Error transferring $snap to $desthost:$destfs. Resetting" + else + zrep_errquit "Error transferring $snap to $desthost:$destfs. RESUME set. Not clearing $srcfs" + fi + fi + + fi #resume token set + + + # Successful initial sync! Woo! okay, record that fact and complete setting properties + # ... after stupid old-zfs-compat junk, that is + if (( ! Z_HAS_REC_U )) || [[ "$ZREP_CREATE_FLAGS" != "" ]] ; then + _debugprint Because your zfs does not have recv -u, + _debugprint or maybe because ZREP_CREATE_FLAGS set, + _debugprint setting remote properties by hand + zrep_ssh $desthost zfs set readonly=on $destfs + if [[ $? -ne 0 ]] ; then + errquit Could not set readonly for $desthost:$destfs. However, snapshot has been synced. \ + Manual intervention required if you do not wish to simply clear and start again. + fi + + zrep_ssh $desthost zfs set ${ZREPTAG}:src-fs=$srcfs $destfs + zrep_ssh $desthost zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $destfs + zrep_ssh $desthost zfs set ${ZREPTAG}:dest-fs=$destfs $destfs + zrep_ssh $desthost zfs set ${ZREPTAG}:dest-host=$desthost $destfs + zrep_ssh $desthost zfs set ${ZREPTAG}:savecount=$ZREP_SAVE_COUNT $destfs + + fi + if [[ "$ZREP_CREATE_FLAGS" != "" ]] || [[ "$READONLYPROP" == "" ]] ; then + echo setting readonly on $desthost:$destfs manually + zrep_ssh $desthost zfs set readonly=on $destfs + fi + + # Extra, non-symmeric properties the user might want on the remote side. + if [[ "$ZREP_INIT_REMOTE_PROPERTIES" != "" ]] ; then + for prop in $ZREP_INIT_REMOTE_PROPERTIES ; do + zrep_ssh $desthost zfs set $prop $destfs + done + fi + + + # Success! So need to set success marker on remote side. + # Make sure to set format to match what zrep_sync() looks for! + if (( Z_HAS_SNAPPROPS )) ; then + typeset sentprop="${ZREPTAG}:sent=`zrep_gettimeinseconds`" + zfs set $sentprop ${snap} + else + # Arg stupidold stuff cant set props on a snapshot + # So we have to manually set these on both sides also, + # "Just in case" + zfs set ${ZREPTAG}:lastsent=${snap} $srcfs + zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${snap} $destfs + fi + if [[ "$mountpoint" != "" ]] ; then + echo "clearing mountpoint value for remote" + zrep_ssh $desthost zfs inherit mountpoint $destfs + fi + + # make sure the above ' set 's (sent, lastsent) + # match what zrep_sync() does !!! + + + # Note: we have to set master property NOW, not before, + # because "recv -x zrep:master" Does Not Work properly + # Also, it avoids things like "zrep sync all" from attempting + # to sync it before initial sync has been done. + # We don't even have to zrep_lock_fs until this is set + # Make sure value this matches zrep_sentsync + zfs set ${ZREPTAG}:master=yes $srcfs + + echo Initialization copy of $srcfs to $desthost:$destfs complete + if (( Z_HAS_REC_U )) ; then + echo Filesystem will not be mounted + fi + +} + + +zrep_changeconfig(){ + if [[ "$1" == "-f" ]] ; then + # skip safety checks + shift + setfsconfigs $@ + return + fi + typeset srcfs="$1" check + + if [[ "$srcfs" == "" ]] ; then + zrep_errquit "zrep: no fs specified" + fi + + check=`getlastsnap $srcfs` + if [[ "$check" == "" ]] ; then + _errprint "No pre-existing zrep snapshots found on $srcfs" + _errprint $srcfs is not initialized for zrep. cannot change config. + zrep_errquit Use zrep init on $srcfs instead + fi + + setfsconfigs $@ + +} + + + +##### File: zrep_sync + +# contains meat of the "sync" level operations, which deal with +# data transfer. +# basic snap routines, and init routines, are in zrep_snap + + + +## file-internal routine that gets used a lot in zrep_sync. but not always +_gensentprop(){ + typeset timeinsec=`zrep_gettimeinseconds` + echo "${ZREPTAG}:sent=$timeinsec" +} + + +# This is a RECOVERY ROUTINE ONLY. +# I put lots of sanity checking in here, that doesnt make sense to keep +# with a more general case internal routine. +# This exists, because certain people say that for some odd reason on their systems, +# the zfs send completes, but zrep gets killed before zrep updates properties. +# To help people save the time on resyncing hundreds of TB, +# give them a way to update the sent property. +# +# This only works with newstyle ZFS that allows property setting on snapshots +# Needs to follow whatever is done in _sync(), after the zfs send +# (and therefore getlastsnapsent() as well) +# +# Usage: zrep_sentsync [-L] fs@snap +# +# WARNING: If you have nested zrep filesystem below this one.. you just hosed yourself. +# +zrep_sentsync(){ + typeset local=0 + if [[ "$1" == "-L" ]] ; then + local=1; + shift + fi + + typeset srcsnap="$1" + typeset srcfs=${srcsnap%@*} + typeset snapname=${srcsnap#*@} + + if (( ! Z_HAS_SNAPPROPS )) ; then + zrep_errquit This sentsync operation only supported with modern ZFS implementations + fi + + zfs list -t snapshot "$1" >/dev/null 2>&1 + if [[ $? -ne 0 ]] ; then + zrep_errquit Expected snapshot for $1. Cannot continue + fi + case "$1" in + *@${ZREPTAG}_[0-9a-f]*) + : + ;; + *) + zrep_errquit $1 does not follow zrep naming standards. Cannot continue + ;; + esac + + typeset sentcheck=`$ZFSGETLVAL ${ZREPTAG}:sent $srcfs` + if [[ "$sentcheck" != "" ]] ; then + zrep_errquit ${ZREPTAG}:sent already present on $srcfs + fi + + + _clearlast $srcfs + typeset senttimeprop="`_gensentprop`" + + if ((local == 0)) ; then + typeset desthost destfs + desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` + destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` + zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname + fi + + zfs set $senttimeprop ${srcsnap} + # This will be redundant for recovery, but crucial for when + # user is trying to convert existing snapshot to + # zrep snapshot. + # Make sure it matches zrep_init + zfs set ${ZREPTAG}:master=yes ${srcfs} + + +} + +#################### +# synctosnap: called by zrep_sync, if a specific snapshot is specified. +# +# This LOCAL side, *and* REMOTE side, match up with local zrep_created +# snapshot. ... +# +# Note that it uses zrep_lock_fs +# +# WARNING: if we force other side to roll to snap.... +# we should NOT BE SYNCING ANY more. +# At the moment, it is up to the user to ensure that nothing is going on +# locally, and future zrep syncs wont just effectively roll forward again +# on the remote side. +# zrep sync jobs should probably be halted, until it is decided that +# you want to sync again. +# +# In the future, I should support some kind of "pause" option, for +# zrep sync all to ignore a rolled back filesystem +# +# +synctosnap(){ + typeset srcsnap=$1 destfs=$2 desthost=$3 + typeset newsentlist + + typeset srcfs snapname destsnap + if [[ "$desthost" == "" ]] ; then + echo ERROR: synctosnap did not receive all required args + zrep_errquit "args=$@" + fi + srcfs=${srcsnap%@*} + snapname=${srcsnap#*@} + destsnap=${snapname} + + # Have to enforce OUR syntax. otherwise, any future attempt to + # continue sync will fail. + # ( getlastsnap() wont find it! ) + # + case $snapname in + ${ZREPTAG}_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) + : ;; + *) + zrep_errquit $srcsnap is not zrep snapshot. Cannot roll with it. + ;; + esac + + + + echo Validating remote snap + zrep_ssh $desthost zfs list -t snapshot $destfs@$destsnap >/dev/null + if [[ $? -ne 0 ]] ; then + zrep_errquit $destfs@$destsnap does not exist. Cannot roll to snap + fi + + + echo "WARNING: We will be rolling back $destfs, on $desthost" + echo -n " to $snapname, made at: " + $ZFSGETVAL creation $srcsnap + echo "" + echo "All newer snapshots on remote side will be destroyed" + echo "You should have paused ongoing sync jobs for $destfs before continuing" + echo "Continuing in 20 seconds...." + sleep 10 + echo "Continuing in 10 seconds...." + sleep 10 + + zrep_lock_fs $srcfs || zrep_errquit "Cannot lock $srcfs" + zrep_ssh $desthost zfs rollback -Rr $destfs@$destsnap || zrep_errquit roll failed + + echo $desthost:$destfs rolled back successfully to $destsnap + echo Now cleaning up local snapshots + + # need to undo whatever zrep_sync does + newsentlist=`getallsnaps $srcfs|sed "1,/@$snapname/d"` + for snap in $newsentlist ; do + zfs inherit ${ZREPTAG}:sent $snap + done + + zrep_unlock_fs $srcfs + +} + +# +# called by _sync +# Check if there have been changes since specified snap +# If no, then return 0 == true [no changes ] +# +_sync_nochanges(){ + typeset changecheck tmpfile="$ZREP_RUNDIR/zrep.$$.c" + + rm -f $tmpfile + zfs diff -H $1 >$tmpfile + if [[ $? -ne 0 ]] ; then + _errprint _sync_nochanges: zfs diff command unrecognized + rm -f $tmpfile + return 1 + fi + if test -s $tmpfile ; then + rm -f $tmpfile + return 1 + fi + + _debugprint sync_nochanges did not find any changes + rm -f $tmpfile + return 0 +} + +# Usage: _snapandsync fs desthost destfs +# internal routine called by zrep_sync and zrep_failover, +# to do an incremental send. +# You must hold filesystem lock before calling this +# WE DO NOT DO ANY SAFETY OR LOCK CHECKS HERE. +# Caller is expected to have done them. +# +# Wil create a new snap on srcfs, and sync it over to given destination +# Sets our 'synced' marker on it as well. +# +_snapandsync(){ + typeset srcfs=$1 desthost=$2 destfs=$3 + + typeset sentsnap newsnap snapname + + + # Find incremental send starting point + # Do this BEFORE creating new snap, because we should make new snap + # if we cant do incremental anyway + sentsnap=`getlastsnapsent $srcfs` + if [[ "$sentsnap" == "" ]] ; then + echo zrep_sync could not find sent snap for $srcfs. + zrep_errquit You must initialize $srcfs for zrep + fi + + if [[ "$ZREP_CHANGEDONLY" != "" ]] ; then + if _sync_nochanges $sentsnap ; then + _debugprint No changes found in $srcfs. Updating timestamp only + + if (( Z_HAS_SNAPPROPS )) ; then + typeset senttimeprop="`_gensentprop`" + zfs set $senttimeprop ${sentsnap} + else + #note that this is only for old-ZFS compatibility. + # We dont really want to use this style if possible! + typeset timeinsec=`zrep_gettimeinseconds` + zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $srcfs + fi + return 0 + fi + fi + + newsnap=`makesnap $srcfs` + # workaround wierd bash newline problem + newsnap=$(echo $newsnap) + if [[ "$newsnap" == "" ]] ; then + zrep_errquit zrep_sync could not create new snapshot for $srcfs + fi + + _sync $srcfs $desthost $destfs $sentsnap $newsnap + +} + + + +# called by _snapandsync, and also zrep_synconly +# Usage: _sync sourcefs destinationhost destinationfs (lastsent (newsnap)) +# This is the level that calls zfs directly. +# see also _refreshpull, since it also calls directly. +_sync(){ + typeset force verbose + typeset token="" recv_s="" + + if [[ "$ZREP_FORCE" == "-f" ]] ; then + force=-F + fi + if [[ "$ZREP_VERBOSE" != "" ]] ; then + verbose=-v + fi + if [[ "$ZREP_RESUME" != "" ]] ; then + recv_s=-s + fi + + typeset srcfs=$1 desthost=$2 destfs=$3 + typeset lastsent=$4 newsnap=$5 + typeset snapname + + if [[ "$lastsent" == "" ]] ; then + lastsent=`getlastsnapsent $srcfs` + if [[ "$lastsent" == "" ]] ; then + echo zrep_sync could not find sent snap for $srcfs. + zrep_errquit You must initialize $srcfs for zrep + fi + fi + + if [[ "$newsnap" == "" ]] ; then + newsnap=`getlastsnap $srcfs` + if [[ "$newsnap" == "" ]] ; then + echo zrep_sync could not find sent snap for $srcfs. + zrep_errquit You must initialize $srcfs for zrep + fi + fi + + if [[ "$newsnap" == "$lastsent" ]] ; then + echo $newsnap already sent + return 0 + fi + + typeset remotemaster + remotemaster=`zrep_ssh $desthost $ZFSGETLVAL ${ZREPTAG}:master $destfs` + if [[ $? -ne 0 ]] ; then + zrep_errquit "$desthost is not reachable via ssh? Cannot sync" + fi + if [[ "$remotemaster" == "yes" ]] ; then + zrep_errquit "Other side ($desthost:$destfs) is also master. Split brain detected" + fi + + snapname=${newsnap#*@} + # do this manually, not using gensentprop because we want consistant timestamp + typeset timeinsec=`zrep_gettimeinseconds` + typeset senttimeprop="${ZREPTAG}:sent=$timeinsec" + + echo sending $newsnap to $desthost:$destfs + + if [[ "$ZREP_RESUME" != "" ]] ; then + token=`zrep_ssh $desthost $ZFSGETVAL receive_resume_token $destfs` + if [[ "$token" == "-" ]] ; then token="" ; fi + fi + + # Note: doing "-o $senttimeprop" sets prop on FILESYSTEM, not snap. + # So we dont do that usually + + # other than zrep_init, this should be the ONLY place we do a send + # Sigh. but now we also do in _refreshpull + if [[ "$BBCP" != "" ]] ; then + + if [[ "$token" != "" ]] ; then + SENDCMD="zfs send -t $token ${ZREP_RESUME_FLAGS}" + else + SENDCMD="zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $newsnap" + + fi + $BBCP -N io "$SENDCMD" \ + "$desthost:zfs recv $recv_s $force $destfs" + else + if [[ "$token" != "" ]] ; then + eval zfs send ${ZREP_RESUME_FLAGS} -t $token ${Z_F_OUT} | + zrep_ssh $desthost "${Z_F_IN} zfs recv $recv_s $force $destfs" + else + eval zfs send $verbose ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $newsnap ${Z_F_OUT} | + zrep_ssh $desthost "${Z_F_IN} zfs recv $recv_s $force $destfs" + fi + fi + + # I rename this to _unsent rather than just delete, in case people are using zrep + # for the DUAL use, of replication, + # plus convenient user-based "oops" recovery from the automatic .zfs/snapshots directory + # But if resume support enabled, should auto-retry next time sync called + if [[ $? -ne 0 ]] ; then + if [[ "$ZREP_RESUME" == "" ]] ; then + if [[ "$ZREP_RENAME_UNSENT" == "yes" ]] ; then + zfs rename ${Z_SNAP_R} ${newsnap} ${newsnap}_unsent + zrep_errquit Problem doing sync for $newsnap. Renamed to ${newsnap}_unsent + else + zfs destroy ${Z_SNAP_R} ${newsnap} + fi + fi + + zrep_errquit Problem doing sync for $newsnap. + fi + + + ################################################## + ##### Okay. data sync completed. Now register that fact with ZFS properties. + ##### If you modify below here, you also need to update zrep_sentsync + + #Even if we are "old mode", other side may not be. + # So try newer way first. + zrep_ssh $desthost zfs set $senttimeprop $destfs@$snapname + if [[ $? -ne 0 ]] ; then + echo WARNING: setting ${ZREPTAG}:sent failed on $desthost:$destfs@$snapname + echo Using fallback methods. You should go patch $destfs to have newer ZFS version + zrep_ssh $desthost zfs set ${ZREPTAG}:lastsent=${newsnap} $destfs + zrep_ssh $desthost zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $destfs + fi + + if (( Z_HAS_SNAPPROPS )) ; then + zfs set $senttimeprop ${newsnap} + else + #note that this is only for old-ZFS compatibility. + # We dont really want to use this style if possible! + zfs set ${ZREPTAG}:lastsent=${newsnap} $srcfs + zfs set ${ZREPTAG}:lastsenttime=${timeinsec} $srcfs + fi +} + + +#User entrypoint, for synconly, which is the pair of snaponly +#Keep it paired with zrep_sync +zrep_synconly(){ + # annoyingly..need to make this almost identical to our current full + # zrep_sync. but just skipping first steps :( + # we can skip retries, though. + typeset srcfs desthost destfs + + if [[ "$1" == "all" ]] ; then + set -- `zrep_list_master` + if [[ "$1" == "" ]] ; then + exit + fi + fi + + [[ "$1" == "" ]] && zrep_errquit No fileystem specified for synconly + + while [[ "$1" != "" ]] ; do + srcfs=$1 + + check=`$ZFSGETLVAL ${ZREPTAG}:master $srcfs` + if [[ "$check" != "yes" ]] ; then + zrep_errquit $srcfs not master. Cannot sync + fi + + desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` + destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` + if [[ $? -ne 0 ]] || [[ "$desthost" == "-" ]] || [[ "$destfs" == "-" ]]; + then + zrep_errquit Problem getting zrep properties for fs $srcfs + fi + + zrep_lock_fs $srcfs + if [[ $? -ne 0 ]] ; then + zrep_errquit Failed to acquire zrep lock for $srcfs + fi + + _sync $srcfs $desthost $destfs || zrep_errquit sync failed for $srcfs + + _expire $srcfs #dont care so much if this fails + + zrep_unlock_fs $srcfs + + shift + done +} + + +#zrep_sync +# User entrypoint +# Make a new snapshot and copy it over. +# Usage: zrep_sync [-q quiettime] (all|fs1 .. fsX) +# See workflow.txt +# SPECIAL CASE: Will call synctosnap if a snapshot is given instead of fsname +# Normally, will bail out if another instance of zrep holds lock. +# -q option says to check last update time of locked filesystems. +# If sync more recent than given quiettime, then quietly ignore +# +zrep_sync(){ + # If you make changes in here, check if needed in zrep_synconly!! + + typeset srcfs destfs desthost sentsnap newsnap + typeset quiettime=0 + + if [[ "$1" == "-c" ]] ; then + export ZREP_CHANGEDONLY="yes" + shift + fi + + if [[ "$1" == "-q" ]] ; then + quiettime="$2" + shift + shift + if (( quiettime < 30 )) ; then + zrep_errquit "-q must use value greater than 30" + fi + fi + + if [[ "$1" == "all" ]] ; then + set -- `zrep_list_master` + if [[ "$1" == "" ]] ; then + # Stay quiet, so we dont spew if in cron + #echo No zrep mastered filesystems found + exit + fi + fi + + [[ "$1" == "" ]] && zrep_errquit No fileystem specified for sync + + # Special Case. User can force sync from specific snapshot + case $1 in + *@*) + synctosnap $srcfs $destfs $desthost + return + ;; + esac + + while [[ "$1" != "" ]] ; do + srcfs="$1" + + check=`$ZFSGETLVAL ${ZREPTAG}:master $srcfs` + if [[ "$check" != "yes" ]] ; then + zrep_errquit $srcfs not master. Cannot sync + fi + + desthost=`$ZFSGETVAL ${ZREPTAG}:dest-host $srcfs` + destfs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $srcfs` + if [[ $? -ne 0 ]] || [[ "$desthost" == "-" ]] || [[ "$destfs" == "-" ]]; + then + zrep_errquit Problem getting zrep properties for fs $srcfs + fi + + zrep_lock_fs $srcfs + + if [[ $? -ne 0 ]] ; then + # retry for lock for a while, if (quiettime>0 ) + + if ((quiettime==0)); then + zrep_errquit Cannot lock $srcfs. Cannot continue + fi + typeset currtime=`zrep_gettimeinseconds` snaptime elapsed + + snaptime=`getlastsynctime $srcfs` + if (( snaptime == 0 )) ; then + zrep_errquit quiet mode set, but no last snap for $srcfs + fi + + elapsed=$((currtime - snaptime)) + if ((elapsed > quiettime)) ; then + _debugprint $elapsed seconds have elapsed since last sync of $srcfs + zrep_errquit quiet time limit of $quiettime seconds exceeded for busy fs $srcfs + else + echo Quiet mode: skipping busy fs $srcfs at `date` + return + fi + fi + + _snapandsync $srcfs $desthost $destfs + + + # Make this message match what zrep_expire uses.. + echo Expiring zrep snaps on $srcfs + _expire $srcfs + + zrep_unlock_fs $srcfs + + shift + done + +} + + + +# Usage: zrep_refresh fsname +# +# zrep_refresh is a "pull" version of "zrep_sync" +# The concept is a bit of a hack. +# It primarily exists so people can run a secure backup server, that +# has ssh access to all hosts, but not vice versa +# +# Implementation is a bit sketchy. +# For initial, non-optimal run, perhaps take advantage of +# ssh host zrep synconly +# to avoid too much duplication of things? +# but will still need to set all the perms n things. Nastyyy.. +# The MAIN nastiness, is that all our locks are on the "master" side. +# Which depends on the PID still being there!! +# But if we start now running things on the "slave" side.. +# There is potential for problems +# Examine critical points and reasons for lock: +# 1. while doing analysis of which snap to send +# 2. to avoid paralel "zfs send"s running. +# 3. for update of timestamp +# +# We can still wrap #1 and #2 in a single lock call. +# (and still on the src side!) +# The ugly comes when updating zrep:sent. Dont want to update wrong snap! +# So long as we do some kind of check to see that we're not going +# backwards when we get lock a second time ... we should be relatively okay. +# However.. for simplicity... going to just cross fingers and wrap +# all three in single remote lock call, through _refreshpull +# +zrep_refresh(){ + typeset srcfs destfs desthost newsnap newseq master + typeset force + + if [[ "$ZREP_FORCE" == "-f" ]] ; then + force=-F + fi + + if [[ "$1" == "-r" ]] ; then + export ZREP_RESUME=1 + shift + fi + + # for now, just handle ONE fs, not multiple fs list + + destfs="$1" + if [[ "$1" == "" ]] ; then + _errprint Error: no filesystems specified for refresh + return 1 + fi + + master=`$ZFSGETLVAL ${ZREPTAG}:master $destfs` + if [[ "$master" == "yes" ]] ; then + zrep_errquit Sorry, you cant run refresh on a master mode fs $destfs + fi + + srchost=`$ZFSGETVAL ${ZREPTAG}:src-host $destfs` + srcfs=`$ZFSGETVAL ${ZREPTAG}:src-fs $destfs` + + zrep_lock_fs $destfs + if [[ $? -ne 0 ]] ; then + zrep_errquit Cannot lock $destfs. Cannot continue + fi + + _debugprint refresh step 1: Going to $srchost to snapshot $destfs + newsnap=`zrep_ssh $srchost $ZREP_PATH ${ZREP_R} snaponly $srcfs` + if [[ $? -ne 0 ]] ; then + zrep_errquit snap of src $srcfs on $srchost failed + fi + # yes, MORE paranoia.. + case $newsnap in + *@${ZREPTAG}_*) + newseq=${newsnap#*@} + ;; + *) + zrep_errquit Unrecognized output from src snap. Cannot continue + ;; + esac + + typeset senttimeprop="`_gensentprop`" + + _debugprint refresh step 2: Pulling $newsnap + if [[ "$ZREP_RESUME" != "" ]] ; then + token=`$ZFSGETVAL receive_resume_token $destfs` + if [[ "$token" == "-" ]] ; then token="" ; fi + fi + + if [[ "$BBCP" != "" ]] ; then + $BBCP -N io "$srchost:$ZREP_PATH _refreshpull $newsnap $token" \ + "zfs recv $force $destfs" + else + zrep_ssh $srchost "$ZREP_PATH ${ZREP_R} _refreshpull $newsnap $token ${Z_F_OUT}" | + eval ${Z_F_IN} zfs recv $force $destfs + fi + if [[ $? -ne 0 ]] ; then + zrep_errquit Unforseen error pulling snapshot $newsnap from $srchost + fi + + zfs set $senttimeprop $destfs@$newseq + if [[ $? -ne 0 ]] ; then + _errprint WARNING: expected local copy $destfs@newseq does not exist + fi + + + zrep_ssh $srchost $ZREP_PATH _refreshcomplete $newsnap $senttimeprop + + + _debugprint Running local expires on $destfs + _expire $destfs + + _debugprint Running remote expires on $srcfs + sleep 1 # avoid race condition on samehost situation + zrep_ssh $srchost "$ZREP_PATH expire -L $srcfs" + + zrep_unlock_fs $destfs + +} + +# Implementation for hidden command-line option, "zrep _refreshpull" +# This is called remotely by zrep refresh +# ( aka zrep_refresh ) +# We dont just call "ssh zfs send", because we want to use zrep locking +# +# This routine is definitely not supposed to be user visible +# .. eh... maybe someday. but initial design is "private" +# +# Note that this is only called on a per-filesystem name +# +# Syntax: _refreshpull fs/name [optional resume token] +# +_refreshpull(){ + typeset fs snapname lastsent latest verbose + typeset token="" + + if [[ "$2" != "" ]] ; then + token="$2" + fi + + if [[ "$ZREP_VERBOSE" != "" ]] ; then + verbose=-v + fi + + snapname="$1" + fs=${snapname%@*} + + # Keep in mind that stdin/out is busy so have to use stderr. + # Cant use regular debugprint + if [[ "$DEBUG" != "" ]] ; then + _errprint _refreshpull: snapname=$snapname, fs=$fs + fi + + zrep_lock_fs $fs + if [[ $? -ne 0 ]] ; then + zrep_errquit Could not lock $fs + fi + + #We should now; + # 1. compare to latest snap. quit if not latest + # 2. get timestamp + # 3. trigger a zfs send + # 4. set timestamp if no errors. + # I think it is reasonable to presume that if the receive failed, + # we will see an error by the pipe blowing up. + # + + lastsent=`getlastsnapsent $fs` + if [[ "$lastsent" == "" ]] ; then + zrep_errquit Canthappen: _refreshpull cant findlastsent snap + fi + latest=`getlastsnap $fs` + + if [[ "$latest" != "$snapname" ]] ; then + zrep_errquit Sync error: $snapname is not latest snap for $fs + fi + + + if (( Z_HAS_SNAPPROPS ==0)) ; then + zrep_errquit Error: we currently only support modern ZFS that allows setting props on snaps + fi + + if [[ "$token" != "" ]] ; then + zfs send ${ZREP_RESUME_FLAGS} -t $token + else + zfs send $verbose $token ${ZREP_R} ${ZREP_SEND_FLAGS} ${ZREP_INC_FLAG} $lastsent $latest + fi + + if [[ $? -ne 0 ]] ; then + zrep_errquit Some kind of error during sending. Bailing out of _refreshpull + fi + + + zrep_unlock_fs $fs +} + + +# INTERNAL-ONLY HOOK for "zrep refresh" +# +# This used to be in _refreshpull. However, that led to a race condition where a +# "zfs send" had been completed, and "last sent" had been updated prematurely. +# if the zfs receive didnt complete, then things were messed up. +# Usage: +# _refreshcomplete fs@snapshot propertysetting=value +# +_refreshcomplete(){ + zfs set ${2} ${1} +} + +# usage: +# snap_olderthan snapname minutes +# returns true (0) if snap older than given number of minutes, +# based on 'creation' property. +# (technically this should work on regular fs as well) +# +# **NOTE** +# MINUTES, not seconds, because property format looks like this; +# $ zfs get -H creation scratch +# scratch creation Sun Dec 1 21:55 2019 - +# +snap_olderthan(){ + typeset cstamp=`$ZFSGETVAL creation $1` + typeset ageinseconds=`printf "%(%s)T" "$cstamp"` + + typeset secondmark=`printf "%(%s)T" "$2 minutes ago"` + + if (( $secondmark < 1 )) ; then + zrep_errquit ERROR: snap_olderthan passed invalid minute limit: $2 + fi + + if (( ageinseconds < secondmark )) ; then + return 0 + fi + + + return 1 +} + +# _expire: +# get rid of "old" snapshots for a specifically named filesystem +# +# Note0: you must hold local(master) fs lock first +# +# Note1: expire BOTH SIDES, if we are master +# Keep in mind that sometimes master and dest are on same system +# +# Note2: Be sure to NEVER delete most recent sent snapshot!! + +# INTERNAL routine. For external-facing routine, see zrep_expire +_expire(){ + + if [[ "$ZREP_SKIP_EXPIRE" != "" ]] ; then + _debugprint _expire doing nothing since ZREP_SKIP_EXPIRE set + return + fi + + typeset savecount currcount lastsent remotehost remotefs sanity + typeset tmpfile=$ZREP_RUNDIR/zrep_expire.$$ + typeset local=0 master + + if [[ "$1" == "-L" ]] ; then + local=1; + shift + fi + + master=`$ZFSGETLVAL ${ZREPTAG}:master $1` + + zrep_has_fs_lock $1 || zrep_errquit zrep_expire Internal Err caller did not hold fs lock on $1 + + # Allow propagated values as well as local, for savecount + savecount=`$ZFSGETVAL ${ZREPTAG}:savecount $1` + # do not use (()) in case value unset + if [[ $savecount < 1 ]] ; then + zrep_errquit ${ZREPTAG}:savecount on $1 set to improper value $savecount + fi + + rm -f $tmpfile + if [[ "$master" == "yes" ]] ; then + lastsent=`getlastsnapsent $1` + if [[ "$lastsent" == "" ]] ; then + zrep_errquit corrupted zrep data: no last sent detected. Stopping expire + fi + + # Note that getallsnaps does an explicit sort already. + # We want to not expire lastsent, OR any later unsent ones! + getallsnaps $1 | awk '$1 == "'$lastsent'"{exit} {print}' >$tmpfile + savecount=$((savecount-1)) + else + getallsnaps $1 >$tmpfile + fi + currcount=`wc -l < $tmpfile` + + if ((currcount > savecount )) ; then + currcount=$((currcount - savecount)) + + head -$currcount $tmpfile >$tmpfile.2 + mv $tmpfile.2 $tmpfile + for snap in `cat $tmpfile` ; do + _debugprint expiring $snap + # Paranoia is good. + case $snap in + *@*) + zfs destroy -r $snap + ;; + *) + zrep_errquit "Expire was about to destroy NON-snapshot $snap" + ;; + esac + done + fi + rm $tmpfile + + + if [[ "$master" != "yes" ]] || ((local ==1)) ; then + #This fs is dest fs. We are done. + return + #otherwise, go expire on remote side as well + fi + + remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $1` + remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $1` + echo Also running expire on $remotehost:$remotefs now... + sanity=`zrep_ssh $remotehost $ZFSGETLVAL ${ZREPTAG}:master $remotefs` + + # Normally, dont quit on error. But this is super-bad. + if [[ "$sanity" == "yes" ]] ; then + _errprint "ERROR: Remote side $remotehost also marked as master." + zrep_errquit "ERROR: Split brain scenario detected" + fi + + zrep_ssh $remotehost "$ZREP_PATH expire $remotefs" ||echo REMOTE expire failed +} + + +# top-level user-facing routine. +# expire old snaps for some or all zrep filesystems. +# Different ways of calling: +# zrep expire all Run expire on all zrep fs +# zrep expire Run expire on zrep fs we are master for, plus remote +# zrep expire -L Run expire on zrep fs we are master for. SKIP remote +# zrep expire fs .. Run expire only on fs, plus remote if it is a master +# zrep expire -L fs Run expire only on fs. Skip remote +# +# If no arg given, expire only filesystems we are master for +# If "all" given, expire literally all. +# +zrep_expire() +{ + if [[ "$ZREP_SKIP_EXPIRE" != "" ]] ; then + zrep_errquit 'You explicitly called zrep expire.. and also have ZREP_SKIP_EXPIRE set?? How about no.' + fi + + typeset local + if [[ "$1" == "-L" ]] ; then + local="-L" + shift + fi + + if [[ "$1" == "all" ]] ; then + set -- `zrep_list` + elif [[ "$1" == "" ]] ; then + set -- `zrep_list_master` + fi + + # Note: we should continue if we hit problems with an individual + # filesystem. Otherwise we risk letting server selfdestruct fill + # over one troublesome filesystem + # + while [[ "$1" != "" ]] ; do + zrep_lock_fs $1 + echo Expiring zrep snaps on $1 + _expire $local $1 || echo WARNING: expire failed for $1 + zrep_unlock_fs $1 + shift + done + +} +#### File: zrep_failover + + + +# run this on 'master' side, to make other side master +# Usage: zrep_failover fs[@snap] [remotehost] +# +zrep_failover(){ + typeset local=0 fs snap="" remotehost remotefs check + + if [[ "$1" == "-L" ]] ; then + local=1 + shift + fi + + if [[ "$1" == "" ]] ; then + usage + exit 1 + fi + + zfs list $1 >/dev/null || zrep_errquit invalid filesystem $1 + check=`$ZFSGETLVAL ${ZREPTAG}:master $1` + if [[ "$check" != "yes" ]] ; then + zrep_errquit $1 not master. Cannot fail over + fi + + fs="$1" + + case $fs in + *@*) + snap=$fs + fs=${srcsnap%@*} + ;; + esac + + zrep_lock_fs $fs ||zrep_errquit could not lock $fs + + if [[ "$2" != "" ]] ; then + remotehost="$2" + else + remotehost=`$ZFSGETVAL ${ZREPTAG}:dest-host $fs` + fi + + + remotefs=`$ZFSGETVAL ${ZREPTAG}:dest-fs $fs` + + echo Setting readonly on local $fs, then syncing + zfs set readonly=on $fs + + if ((local ==1)) ; then + echo Failover for $1 in LOCAL mode + if [[ "$snap" == "" ]] ; then + snap=`getlastsnapsent $1` + zfs list $1 >/dev/null || + zrep_errquit No last synced snap found for $1. Cannot fail over + echo Rolling back to last sync $snap + else + echo Rolling back to specified snap $snap + fi + zfs rollback -Rr $snap ||zrep_errquit Rollback to $snap failed + + else + + ## Need to sync both sides before mode switch! + ## If named snap, roll back. + ## otherwise, "roll forward" by doing one last sync + + if [[ "$snap" != "" ]] ; then + typeset snapname + snapname=${snap#*@} + + echo Rolling back to local $snap + zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed + echo Rolling back $remotehost to $remotefs@$snapname + zrep_ssh $remotehost zfs rollback $remotefs@$snapname || + zrep_errquit remote rollback failed + + else + # makes new snapshot, and syncs + _snapandsync $fs $remotehost $remotefs || zrep_errquit final sync failed. failover failed. + fi + + + fi + + echo Reversing master properties for $Z_LOCAL_HOST:$fs + + zfs set ${ZREPTAG}:dest-fs=$fs $fs + zfs set ${ZREPTAG}:dest-host=$Z_LOCAL_HOST $fs + zfs set ${ZREPTAG}:src-fs=$remotefs $fs + zfs set ${ZREPTAG}:src-host=$remotehost $fs + + zfs inherit ${ZREPTAG}:master $fs + + zrep_unlock_fs $fs + + if (( local ==0)) ;then + echo Setting master on $remotehost:$remotefs + zrep_ssh $remotehost $ZREP_PATH takeover -L $remotefs + fi + +} + +# run this on 'dest' side, to promote it to master +# Usage: zrep_takeover fs[@snap] [remotehost] +# +zrep_takeover(){ + typeset fs snap remotehost remotefs check local=0 + + if [[ "$1" == "-L" ]] ; then + local=1 + shift + fi + + if [[ "$1" == "" ]] ; then + usage + exit 1 + fi + + fs="$1" + + zfs list $fs >/dev/null || zrep_errquit invalid filesystem $fs + + check=`$ZFSGETLVAL ${ZREPTAG}:master $fs` + if [[ "$check" = "yes" ]] ; then + _errprint WARNING: $fs is already master on this host. + _errprint Presuming split-brain recovery mode ... + sleep 5 + + fi + + + if [[ "$2" != "" ]] ; then + remotehost="$2" + else + remotehost=`$ZFSGETVAL ${ZREPTAG}:src-host $fs` + fi + remotefs=`$ZFSGETVAL ${ZREPTAG}:src-fs $fs` + + + if (( local == 0 )) ; then + + echo Starting failover from remote side $remotehost + zrep_ssh $remotehost $ZREP_PATH failover $remotefs + # This will ssh back into us to set src host property, + # and the other stuff, so we dont have to do that now. + exit $? + fi + + # If here, we must be in local mode. + # So... just set properties! + # (and roll back, if desired) + + case $fs in + *@*) + snap=$fs + fs=${srcsnap%@*} + ;; + esac + + + + zrep_lock_fs $fs + zfs inherit readonly $fs + + if [[ "$snap" != "" ]] ; then + echo "WARNING: Before takeover, we will be rolling $fs" + echo -n " to $snapname, made at: " + $ZFSGETVAL creation $snap + echo "" + echo "All newer snapshots will be destroyed" + echo Continuing in 10 seconds... + sleep 10 + + zfs rollback -Rr $snap || zrep_errquit Rollback to $snap failed + fi + + echo Setting master properties for $Z_LOCAL_HOST:$fs + + zfs set ${ZREPTAG}:src-fs=$fs $fs + zfs set ${ZREPTAG}:src-host=$Z_LOCAL_HOST $fs + zfs set ${ZREPTAG}:dest-fs=$remotefs $fs + zfs set ${ZREPTAG}:dest-host=$remotehost $fs + + zfs set ${ZREPTAG}:master=yes $fs + + # Since we default to creating replicas unmounted... mount it now + if [[ "`$ZFSGETVAL type $fs`" == "filesystem" ]] ; then + if [[ "`$ZFSGETVAL mounted $fs`" == "no" ]] ; then + echo eMounting $Z_LOCAL_HOST:$fs + zfs mount $fs + fi + fi + + zrep_unlock_fs $fs + + +} + +######## zrep_top continues here + +usage(){ + echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another + echo in an ongoing basis. + echo + echo " Philip Brown, 2012-2019" + echo + echo Simple usage summary: + echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] ZFS/fs' + echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' + echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' + echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' + echo 'zrep failover [-L] ZFS/fs' + echo 'zrep takeover [-L] ZFS/fs' + echo + echo "Status and side operations" + echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' + echo 'zrep (list|-l) [-Lv] [fs/names]' + echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' + echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' + echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' + echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' + echo 'zrep version' + echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' + echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' + echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' + echo + echo ' -q option to sync says to Quietly ignore locked filesystems' + echo ' that have synced more recently than the given amount of seconds' + echo + echo 'Paired commands for high-transaction systems:' + echo ' zrep snaponly (ZFS/fs ... |all)' + echo ' zrep synconly (ZFS/fs ...|all)' + echo 'The above two commands split the simple sync subcommand, into two' + echo 'separate steps, so that a database, etc. may resume while the sync' + echo 'completes in the background' + echo '' + echo 'zrep defaults to using ssh. However, if remotehost is set to localhost,' + echo 'zrep will use a simple pipe instead.' + echo '' + echo ' More detailed examples can be found at:' + echo http://www.bolthole.com/solaris/zrep/zrep.documentation.html + echo '' + echo 'See the above documentation for details on using the -t flag to use zrep tags' +} + +# +# Special global flags that must always be processed FIRST, before normal command args. +# +while [[ "$1" == -[tD] ]] ; do + case "$1" in + -D) + DEBUG=1 + shift + ;; + -t) + if [ "$2" == "" ] ; then + usage + fi + #deliberately dont quote this to avoid stupidity or malice by user + ZREPTAG=$2 + shift + shift + ;; + esac +done + + +if [[ "$ZREPTAG" != "zrep" ]] ; then + # If custom zrep tag, then probably multiple layers of snapshots. + # In this case, DO NOT send all intermediate snapshots for replication + ZREP_INC_FLAG=${ZREP_INC_FLAG:-"-i"} + + if [ "$ZREPTAG" != "" ] ; then + ZREP_PATH="$ZREP_PATH -t $ZREPTAG" + fi +fi +# ensure it is set to SOMETHING by default. Default is to send all +ZREP_INC_FLAG=${ZREP_INC_FLAG:-"-I"} + + + + +case "$1" in + "") + usage + ;; + changeconfig|-C) + shift + zrep_changeconfig "$@" + ;; + clear) + shift + # only actually allows ONE fs + if [[ "$1" == "-f" ]] ; then + shift + ZREP_FORCE=-f + fi + zrep_clear "$@" + ;; + expire|-e) + shift + zrep_expire "$@" + ;; + init|-i) + shift + while [ "$1" != "" ] ; do + case $1 in + "-f") + shift + ZREP_FORCE=-f + ;; + "-v") + shift + ZREP_VERBOSE=yes + ;; + *) + break + ;; + esac + done + zrep_init "$@" + ;; + sentsync) + shift + # Note that this will NOT accept multiple snaps, for safety + zrep_sentsync "$@" + ;; + snaponly) + shift + zrep_snaponly "$@" + ;; + sync|-S) #remember, this is inverse of refresh + shift + while [ "$1" != "" ] ; do + case $1 in + "-f") + shift + ZREP_FORCE=-f + ;; + "-v") + shift + ZREP_VERBOSE=yes + ;; + "-r") + shift + ZREP_RESUME=yes + ;; + *) + break + ;; + esac + done + zrep_sync "$@" + ;; + synconly) + shift + zrep_synconly "$@" + ;; + refresh) # yes keep this in this order + shift + while [ "$1" != "" ] ; do + case $1 in + "-f") + shift + ZREP_FORCE=-f + ;; + "-v") + shift + ZREP_VERBOSE=yes + ;; + "-r") + shift + ZREP_RESUME=yes + ;; + *) + break + ;; + esac + done + zrep_refresh "$@" + ;; + status|-s) + shift + zrep_status "$@" + ;; + list|-l) + shift + zrep_list "$@" + ;; + lastsent|getlastsent) + # lastsent is a backward compability hook. + shift + zrep_getlastsent "$@" + ;; + failover) + shift + if [[ "$1" == "-f" ]] ; then + shift + ZREP_FORCE=-f + fi + zrep_failover "$@" + ;; + setlastsent) + shift + #zrep_setlastsent "$@" + echo Nothing to see here. You might want sentsync. You might not. + # This kind of overlaps with "sentsync". Which may be poorly named + # + exit 1 + ;; + takeover) + shift + zrep_takeover "$@" + ;; + uptodate) + shift + zrep_uptodate "$@" + exit $? + ;; + version) + echo "zrep $ZREP_VERSION" + echo "http://www.bolthole.com/solaris/zrep" + echo "http://www.github.com/bolthole/zrep" + exit + ;; + + _refreshpull) # Secret option DO NOT PUT IN USAGE!! + shift + _refreshpull $@ + ;; + _refreshcomplete) # Secret option DO NOT PUT IN USAGE!! + shift + _refreshcomplete $@ + ;; + *) + print "ERROR: unrecognized zrep subcommand $1" + echo " Dont know what to do with: $0 $@" + print "" + usage + ;; +esac + + diff --git a/zrep_snap b/zrep_snap index c3f85c9..137850c 100644 --- a/zrep_snap +++ b/zrep_snap @@ -229,8 +229,6 @@ makesnap(){ zfs snapshot $Z_SNAP_R $newsnap if [[ $? -eq 0 ]] ; then - # ksh doesnt need -n, but for some reason, bash does - # otherwise other side gets a leading CR ?? echo -n $newsnap; return 0 else return 1 diff --git a/zrep_sync b/zrep_sync index ea27b56..8294e01 100644 --- a/zrep_sync +++ b/zrep_sync @@ -232,6 +232,8 @@ _snapandsync(){ fi newsnap=`makesnap $srcfs` + # workaround wierd bash newline problem + newsnap=$(echo $newsnap) if [[ "$newsnap" == "" ]] ; then zrep_errquit zrep_sync could not create new snapshot for $srcfs fi From c3a0743200640e0cb9733b5dd87d5e07f236b2ba Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 21 Mar 2022 13:26:40 +0000 Subject: [PATCH 10/15] Fixed missing print/echo transitions, plus formatting for printf Time. With thanks to ngoralski for digging out the bugs, and proposed solution --- zrep | 15 +++++++-------- zrep_status | 4 ++-- zrep_top | 4 ++-- zrep_vars | 7 +++---- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/zrep b/zrep index 4c4757a..233f4bc 100755 --- a/zrep +++ b/zrep @@ -612,14 +612,13 @@ zrep_ssh(){ zrep_gettimeinseconds(){ typeset seconds typeset PATH=$PERL_BIN:$PATH - # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 - # seconds=`printf '%(%s)T' now` - seconds=`date '+%s'` + + seconds=`printf '%(%s)T'` if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. - seconds=`perl -e 'echo int(time);' 2>/dev/null` + seconds=`perl -e 'print int(time);' 2>/dev/null` fi if [[ -z "$seconds" ]] ; then # attempt fallback if no perl present (eg: stock FreeBSD) @@ -688,8 +687,8 @@ zrep_status(){ date="[NEVER]" else if (( Z_HAS_SNAPPROPS )) ; then - typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsynced` - date=`printf "%(${ZREP_DATEFORMAT})T" "#$sentseconds"` + typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsnap` + date=`printf "%(%Y/%m/%d-%H:%M:%S)T" "$sentseconds"` vdate=${date%:*} fi @@ -2653,10 +2652,10 @@ usage(){ echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' - echo + echo '' echo ' -q option to sync says to Quietly ignore locked filesystems' echo ' that have synced more recently than the given amount of seconds' - echo + echo '' echo 'Paired commands for high-transaction systems:' echo ' zrep snaponly (ZFS/fs ... |all)' echo ' zrep synconly (ZFS/fs ...|all)' diff --git a/zrep_status b/zrep_status index 62535be..8fbdbbb 100644 --- a/zrep_status +++ b/zrep_status @@ -55,8 +55,8 @@ zrep_status(){ date="[NEVER]" else if (( Z_HAS_SNAPPROPS )) ; then - typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsynced` - date=`printf "%(${ZREP_DATEFORMAT})T" "#$sentseconds"` + typeset sentseconds=`$ZFSGETVAL ${ZREPTAG}:sent $lastsnap` + date=`printf "%(%Y/%m/%d-%H:%M:%S)T" "$sentseconds"` vdate=${date%:*} fi diff --git a/zrep_top b/zrep_top index 6f69634..327753b 100644 --- a/zrep_top +++ b/zrep_top @@ -42,10 +42,10 @@ usage(){ echo 'zrep clear [-f] ZFS/fs -- REMOVE ZREP CONFIG AND SNAPS FROM FILESYSTEM' echo 'zrep (changeconfig|-C) [-f] ZFS/fs remotehost remoteZFSpool/fs' echo 'zrep (changeconfig|-C) [-f] [-d] ZFS/fs srchost srcZFSpool/fs' - echo + echo '' echo ' -q option to sync says to Quietly ignore locked filesystems' echo ' that have synced more recently than the given amount of seconds' - echo + echo '' echo 'Paired commands for high-transaction systems:' echo ' zrep snaponly (ZFS/fs ... |all)' echo ' zrep synconly (ZFS/fs ...|all)' diff --git a/zrep_vars b/zrep_vars index a025a74..a850904 100644 --- a/zrep_vars +++ b/zrep_vars @@ -602,14 +602,13 @@ zrep_ssh(){ zrep_gettimeinseconds(){ typeset seconds typeset PATH=$PERL_BIN:$PATH - # This SHOULD work in bash as well as ksh. but testing fails with bash 4.4 - # seconds=`printf '%(%s)T' now` - seconds=`date '+%s'` + + seconds=`printf '%(%s)T'` if [[ -z "$seconds" ]] ; then # Unfortunately, solaris date doesnt do '%s', so try perl first. # It's more consistent. - seconds=`perl -e 'echo int(time);' 2>/dev/null` + seconds=`perl -e 'print int(time);' 2>/dev/null` fi if [[ -z "$seconds" ]] ; then # attempt fallback if no perl present (eg: stock FreeBSD) From b2896adef9c0e46fc235d0f947a94548c4e37512 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 21 Mar 2022 13:34:21 +0000 Subject: [PATCH 11/15] conversion notes --- README.bashconversion | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 README.bashconversion diff --git a/README.bashconversion b/README.bashconversion new file mode 100644 index 0000000..e86e897 --- /dev/null +++ b/README.bashconversion @@ -0,0 +1,14 @@ +Notes on things that had to be done to convert zrep +from ksh to bash + +- Replace print with echo + +- for some function outputs, forced use of "echo -n" instead of just echo + +- for printf %(xx)T "#seconds", convert to + printf %(xx)T "seconds" + +- also printf T: + printf '%(%s)T' now + becomes + printf '%(%s)T' From 5f3528e998d8d4ac46f93b70903ec0a4f8c24684 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Mon, 21 Mar 2022 14:09:24 +0000 Subject: [PATCH 12/15] tests readme --- tests/README.tests | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/README.tests diff --git a/tests/README.tests b/tests/README.tests new file mode 100644 index 0000000..4167b58 --- /dev/null +++ b/tests/README.tests @@ -0,0 +1,6 @@ +The main test loop is "run_tests.sh" + +however, before the first run on a system, you will need to run, +in this order: +./setup_pools.sh +./setup_zrep.sh From 7310aa7724dfd422ff750d2c84c92a74dd6f2557 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Thu, 24 Mar 2022 05:15:22 +0000 Subject: [PATCH 13/15] zrep2 reconciliation. I think I have rebased all the changes in master, since original branch. I may have completely messed up the diffs. But at least the branch RIGHT NOW, should be functional and up to date as of today. Dunno if it actualy WORKS yet. But at least it is feature-for-feature complete with main. In theory. --- tests/zodiff | 4 ++++ zrep | 15 +++++++-------- zrep_sync | 2 -- zrep_top | 13 +++++++------ 4 files changed, 18 insertions(+), 16 deletions(-) create mode 100755 tests/zodiff diff --git a/tests/zodiff b/tests/zodiff new file mode 100755 index 0000000..2c3aa90 --- /dev/null +++ b/tests/zodiff @@ -0,0 +1,4 @@ + +# tool to make it easier to compare master branch to zrep2 branch file +sed 's/\([\t ]\)print /\1echo /g' ../zrep/$1 >/tmp/$1 +diff /tmp/$1 $1 |less diff --git a/zrep b/zrep index 233f4bc..b4c02be 100755 --- a/zrep +++ b/zrep @@ -1754,8 +1754,6 @@ _snapandsync(){ fi newsnap=`makesnap $srcfs` - # workaround wierd bash newline problem - newsnap=$(echo $newsnap) if [[ "$newsnap" == "" ]] ; then zrep_errquit zrep_sync could not create new snapshot for $srcfs fi @@ -2630,7 +2628,7 @@ usage(){ echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another echo in an ongoing basis. echo - echo " Philip Brown, 2012-2019" + echo " Philip Brown, 2012-2022" echo echo Simple usage summary: echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' @@ -2638,14 +2636,15 @@ usage(){ echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' - echo 'zrep failover [-L] ZFS/fs' - echo 'zrep takeover [-L] ZFS/fs' + echo 'zrep failover [-L] ZFS/fs [remotehost]' + echo 'zrep takeover [-L] ZFS/fs [remotehost]' echo echo "Status and side operations" echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' + echo 'zrep (status|-s) -m [ZFS/fs]' echo 'zrep (list|-l) [-Lv] [fs/names]' echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' - echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' + echo 'zrep getlastsent ZFS/fs -- prints out snapshot of last successful sync sent' echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' echo 'zrep version' @@ -2862,9 +2861,9 @@ case "$1" in _refreshcomplete $@ ;; *) - print "ERROR: unrecognized zrep subcommand $1" + echo "ERROR: unrecognized zrep subcommand $1" echo " Dont know what to do with: $0 $@" - print "" + echo "" usage ;; esac diff --git a/zrep_sync b/zrep_sync index 8294e01..ea27b56 100644 --- a/zrep_sync +++ b/zrep_sync @@ -232,8 +232,6 @@ _snapandsync(){ fi newsnap=`makesnap $srcfs` - # workaround wierd bash newline problem - newsnap=$(echo $newsnap) if [[ "$newsnap" == "" ]] ; then zrep_errquit zrep_sync could not create new snapshot for $srcfs fi diff --git a/zrep_top b/zrep_top index 327753b..b548286 100644 --- a/zrep_top +++ b/zrep_top @@ -20,7 +20,7 @@ usage(){ echo zrep v${ZREP_VERSION}: a program to replicate a zfs filesystem to another echo in an ongoing basis. echo - echo " Philip Brown, 2012-2019" + echo " Philip Brown, 2012-2022" echo echo Simple usage summary: echo 'zrep (init|-i) [-f] [-v] ZFS/fs remotehost remoteZFSpool/fs' @@ -28,14 +28,15 @@ usage(){ echo 'zrep (sync|-S) [-f] [-v] [-q seconds] all' echo 'zrep (sync|-S) [-f] [-v] ZFS/fs snapshot -- temporary retroactive sync' echo 'zrep refresh [-f] [-v] ZFS/fs -- pull partner of sync' - echo 'zrep failover [-L] ZFS/fs' - echo 'zrep takeover [-L] ZFS/fs' + echo 'zrep failover [-L] ZFS/fs [remotehost]' + echo 'zrep takeover [-L] ZFS/fs [remotehost]' echo echo "Status and side operations" echo 'zrep (status|-s) [-v] [(-a|ZFS/fs)]' + echo 'zrep (status|-s) -m [ZFS/fs]' echo 'zrep (list|-l) [-Lv] [fs/names]' echo 'zrep (list|-l) -s [fs/names] -- list all relevant zrep snapshots' - echo 'zrep lastsent ZFS/fs -- prints out snapshot of last successful sync sent' + echo 'zrep getlastsent ZFS/fs -- prints out snapshot of last successful sync sent' echo 'zrep (expire|-e) [-L] (ZFS/fs ...)|(all)|()' echo 'zrep uptodate ZFS/fs -- returns 0 if no writes since last sync.' echo 'zrep version' @@ -252,9 +253,9 @@ case "$1" in _refreshcomplete $@ ;; *) - print "ERROR: unrecognized zrep subcommand $1" + echo "ERROR: unrecognized zrep subcommand $1" echo " Dont know what to do with: $0 $@" - print "" + echo "" usage ;; esac From 2d7656986b5a9ebad828da37535dcfba0919e248 Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Tue, 29 Mar 2022 14:30:37 +0000 Subject: [PATCH 14/15] fix typo zfs list destfs --- zrep | 2 +- zrep_snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zrep b/zrep index b4c02be..124e635 100755 --- a/zrep +++ b/zrep @@ -1269,7 +1269,7 @@ zrep_init(){ typeset token if [[ "$ZREP_FORCE" != "-f" ]] ; then - zrep_ssh $desthost zfs list destfs >/dev/null && + zrep_ssh $desthost zfs list $destfs 2>/dev/null && zrep_errquit "$desthost:$destfs already exists! Will not overwrite without -f" fi diff --git a/zrep_snap b/zrep_snap index 137850c..f3c7800 100644 --- a/zrep_snap +++ b/zrep_snap @@ -369,7 +369,7 @@ zrep_init(){ typeset token if [[ "$ZREP_FORCE" != "-f" ]] ; then - zrep_ssh $desthost zfs list destfs >/dev/null && + zrep_ssh $desthost zfs list $destfs 2>/dev/null && zrep_errquit "$desthost:$destfs already exists! Will not overwrite without -f" fi From c9cd8d51d532a0e32b5f3c048e25cbadf3d9942a Mon Sep 17 00:00:00 2001 From: Philip Brown Date: Sat, 9 Apr 2022 20:32:53 +0000 Subject: [PATCH 15/15] change log updated for v2.0.0 --- CHANGELOG | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1b28bae..ae36fc3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -160,3 +160,20 @@ version 1.7.7 version 1.8.0 added zrep lastsent bugfixes for resume + + +version 2.0 + # These are almost all just changes between v1.8 and v1.9 + # but lets be thorough moving forward + added check for /etc/default/zrep + added ZREP_CREATE_FLAGS and ZREP_INIT_REMOTE_PROPERTIES + fixed definition of ZREP_SEND_FLAGS + added ZREP_DATEFORMAT + added ZREP_R fixes + added zrep status -m + more resume tweaks + fixes for zrep refresh and allowed zrep -R refresh + safeguard check for zrep takeover + fixed an expire bug + CONVERTED TO USE BASH!!! +