diff --git a/README.md b/README.md index 1c6c9c5..bc8ed83 100644 --- a/README.md +++ b/README.md @@ -202,6 +202,10 @@ As of 1.4.18, syncoid also automatically supports and enables resume of interrup This argument tells syncoid to restrict itself to existing snapshots, instead of creating a semi-ephemeral syncoid snapshot at execution time. Especially useful in multi-target (A->B, A->C) replication schemes, where you might otherwise accumulate a large number of foreign syncoid snapshots. ++ --create-bookmark + + This argument tells syncoid to create a zfs bookmark for the newest snapshot after it got replicated successfully. The bookmark name will be equal to the snapshot name. Only works in combination with the --no-sync-snap option. This can be very useful for irregular replication where the last matching snapshot on the source was already deleted but the bookmark remains so a replication is still possible. + + --no-clone-rollback Do not rollback clones on target diff --git a/syncoid b/syncoid index 823d4aa..43fa6e4 100755 --- a/syncoid +++ b/syncoid @@ -23,6 +23,7 @@ GetOptions(\%args, "no-command-checks", "monitor-version", "compress=s", "dumpsn "source-bwlimit=s", "target-bwlimit=s", "sshkey=s", "sshport=i", "sshcipher|c=s", "sshoption|o=s@", "debug", "quiet", "no-stream", "no-sync-snap", "no-resume", "exclude=s@", "skip-parent", "identifier=s", "no-clone-handling", "no-privilege-elevation", "force-delete", "no-clone-rollback", "no-rollback", + "create-bookmark", "mbuffer-size=s" => \$mbuffer_size) or pod2usage(2); my %compressargs = %{compressargset($args{'compress'} || 'default')}; # Can't be done with GetOptions arg, as default still needs to be set @@ -618,6 +619,7 @@ sub syncdataset { if ($matchingsnap eq $newsyncsnap) { # barf some text but don't touch the filesystem if (!$quiet) { print "INFO: no snapshots on source newer than $newsyncsnap on target. Nothing to do, not syncing.\n"; } + return 0; } else { my $matchingsnapescaped = escapeshellparam($matchingsnap); # rollback target to matchingsnap @@ -716,7 +718,36 @@ sub syncdataset { } } - if (!defined $args{'no-sync-snap'}) { + if (defined $args{'no-sync-snap'}) { + if (defined $args{'create-bookmark'}) { + my $bookmarkcmd; + if ($sourcehost ne '') { + $bookmarkcmd = "$sshcmd $sourcehost " . escapeshellparam("$sourcesudocmd $zfscmd bookmark $sourcefsescaped\@$newsyncsnapescaped $sourcefsescaped\#$newsyncsnapescaped"); + } else { + $bookmarkcmd = "$sourcesudocmd $zfscmd bookmark $sourcefsescaped\@$newsyncsnapescaped $sourcefsescaped\#$newsyncsnapescaped"; + } + if ($debug) { print "DEBUG: $bookmarkcmd\n"; } + system($bookmarkcmd) == 0 or do { + # fallback: assume nameing conflict and try again with guid based suffix + my $guid = $snaps{'source'}{$newsyncsnap}{'guid'}; + $guid = substr($guid, 0, 6); + + if (!$quiet) { print "INFO: bookmark creation failed, retrying with guid based suffix ($guid)...\n"; } + + if ($sourcehost ne '') { + $bookmarkcmd = "$sshcmd $sourcehost " . escapeshellparam("$sourcesudocmd $zfscmd bookmark $sourcefsescaped\@$newsyncsnapescaped $sourcefsescaped\#$newsyncsnapescaped$guid"); + } else { + $bookmarkcmd = "$sourcesudocmd $zfscmd bookmark $sourcefsescaped\@$newsyncsnapescaped $sourcefsescaped\#$newsyncsnapescaped$guid"; + } + if ($debug) { print "DEBUG: $bookmarkcmd\n"; } + system($bookmarkcmd) == 0 or do { + warn "CRITICAL ERROR: $bookmarkcmd failed: $?"; + if ($exitcode < 2) { $exitcode = 2; } + return 0; + } + }; + } + } else { # prune obsolete sync snaps on source and target (only if this run created ones). pruneoldsyncsnaps($sourcehost,$sourcefs,$newsyncsnap,$sourceisroot,keys %{ $snaps{'source'}}); pruneoldsyncsnaps($targethost,$targetfs,$newsyncsnap,$targetisroot,keys %{ $snaps{'target'}}); @@ -1645,6 +1676,7 @@ Options: --mbuffer-size=VALUE Specify the mbuffer size (default: 16M), please refer to mbuffer(1) manual page. --no-stream Replicates using newest snapshot instead of intermediates --no-sync-snap Does not create new snapshot, only transfers existing + --create-bookmark Creates a zfs bookmark for the newest snapshot on the source after replication succeeds (only works with --no-sync-snap) --no-clone-rollback Does not rollback clones on target --no-rollback Does not rollback clones or snapshots on target (it probably requires a readonly target) --exclude=REGEX Exclude specific datasets which match the given regular expression. Can be specified multiple times