From c5eebbe81dcc9d205c474873035a3e42202eba5f Mon Sep 17 00:00:00 2001 From: Christoph Klaffl Date: Thu, 31 Jan 2019 18:14:35 +0100 Subject: [PATCH] implemented parsing of provided zfs send/recv options and whitelisting for each use case as needed --- syncoid | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/syncoid b/syncoid index 3f6f9ba..b0a93af 100755 --- a/syncoid +++ b/syncoid @@ -27,14 +27,14 @@ GetOptions(\%args, "no-command-checks", "monitor-version", "compress=s", "dumpsn my %compressargs = %{compressargset($args{'compress'} || 'default')}; # Can't be done with GetOptions arg, as default still needs to be set -my $sendoptions = ''; +my @sendoptions = (); if (length $args{'sendoptions'}) { - $sendoptions = $args{'sendoptions'} + @sendoptions = parsespecialoptions($args{'sendoptions'}); } -my $recvoptions = ''; +my @recvoptions = (); if (length $args{'recvoptions'}) { - $recvoptions = $args{'recvoptions'} + @recvoptions = parsespecialoptions($args{'recvoptions'}); } @@ -352,6 +352,9 @@ sub syncdataset { # with ZFS on Linux (possibly OpenZFS in general) when setting/unsetting readonly. #my $originaltargetreadonly; + my $sendoptions = getoptionsline(\@sendoptions, ('D','L','P','R','c','e','p','v','w')); + my $recvoptions = getoptionsline(\@recvoptions, ('o','x','u','v')); + # sync 'em up. if (! $targetexists) { # do an initial sync from the oldest source snapshot @@ -470,6 +473,7 @@ sub syncdataset { # and because this will ony resume the receive to the next # snapshot, do a normal sync after that if (defined($receivetoken)) { + $sendoptions = getoptionsline(\@sendoptions, ('P','e','v','w')); my $sendcmd = "$sourcesudocmd $zfscmd send $sendoptions -t $receivetoken"; my $recvcmd = "$targetsudocmd $zfscmd receive $recvoptions $receiveextraargs $forcedrecv $targetfsescaped"; my $pvsize = getsendsize($sourcehost,"","",$sourceisroot,$receivetoken); @@ -619,9 +623,9 @@ sub syncdataset { my $pvsize = 0; my $disp_pvsize = "UNKNOWN"; + $sendoptions = getoptionsline(\@sendoptions, ('L','c','e','w')); if ($nextsnapshot) { my $nextsnapshotescaped = escapeshellparam($nextsnapshot); - my $sendcmd = "$sourcesudocmd $zfscmd send $sendoptions -i $sourcefsescaped#$bookmarkescaped $sourcefsescaped\@$nextsnapshotescaped"; my $recvcmd = "$targetsudocmd $zfscmd receive $recvoptions $receiveextraargs $forcedrecv $targetfsescaped"; my $synccmd = buildsynccmd($sendcmd,$recvcmd,$pvsize,$sourceisroot,$targetisroot); @@ -654,6 +658,7 @@ sub syncdataset { # do a normal replication if bookmarks aren't used or if previous # bookmark replication was only done to the next oldest snapshot if (!$bookmark || $nextsnapshot) { + $sendoptions = getoptionsline(\@sendoptions, ('D','L','P','R','c','e','p','v','w')); my $sendcmd = "$sourcesudocmd $zfscmd send $sendoptions $args{'streamarg'} $sourcefsescaped\@$matchingsnapescaped $sourcefsescaped\@$newsyncsnapescaped"; my $recvcmd = "$targetsudocmd $zfscmd receive $recvoptions $receiveextraargs $forcedrecv $targetfsescaped"; my $pvsize = getsendsize($sourcehost,"$sourcefs\@$matchingsnap","$sourcefs\@$newsyncsnap",$sourceisroot); @@ -1398,6 +1403,12 @@ sub getsendsize { $snaps = "-t $receivetoken"; } + my $sendoptions; + if (defined($receivetoken)) { + $sendoptions = getoptionsline(\@sendoptions, ('e', 'w')); + } else { + $sendoptions = getoptionsline(\@sendoptions, ('D','L','R','c','e','p','v','w')); + } my $getsendsizecmd = "$sourcessh $mysudocmd $zfscmd send $sendoptions -nP $snaps"; if ($debug) { print "DEBUG: getting estimated transfer size from source $sourcehost using \"$getsendsizecmd 2>&1 |\"...\n"; } @@ -1479,6 +1490,70 @@ sub getreceivetoken() { return } +sub parsespecialoptions { + my ($line) = @_; + + print("$line\n"); + + my @options = (); + + my @values = split(/ /, $line); + + my $optionValue = 0; + my $lastOption; + + foreach my $value (@values) { + if ($optionValue ne 0) { + my %item = ( + "option" => $lastOption, + "line" => "-$lastOption $value", + ); + + push @options, \%item; + $optionValue = 0; + next; + } + + for my $char (split //, $value) { + if ($optionValue ne 0) { + return undef; + } + + if ($char eq 'o' || $char eq 'x') { + $lastOption = $char; + $optionValue = 1; + } else { + my %item = ( + "option" => $char, + "line" => "-$char", + ); + + push @options, \%item; + } + } + } + + return @options; +} + +sub getoptionsline { + my ($options_ref, @allowed) = @_; + + my $line = ''; + + foreach my $value (@{ $options_ref }) { + if (@allowed) { + if (!grep( /^$$value{'option'}$/, @allowed) ) { + next; + } + } + + $line = "$line$$value{'line'} "; + } + + return $line; +} + __END__ =head1 NAME @@ -1509,8 +1584,8 @@ Options: --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 - --sendoptions=OPTIONS DANGER: Inject OPTIONS into zfs send, e.g. syncoid --sendoptions="-Lce" sets zfs send -Lce ... - --recvoptions=OPTIONS DANGER: Inject OPTIONS into zfs received, e.g. syncoid --recvoptions="-x property" sets zfs receive -x property ... + --sendoptions=OPTIONS Use advanced options for zfs send (the arguments are filterd as needed), e.g. syncoid --sendoptions="Lc e" sets zfs send -L -c -e ... + --recvoptions=OPTIONS Use advanced options for zfs receive (the arguments are filterd as needed), e.g. syncoid --recvoptions="ux recordsize o compression=lz4" sets zfs receive -u -x recordsize -o compression=lz4 ... --sshkey=FILE Specifies a ssh public key to use to connect --sshport=PORT Connects to remote on a particular port --sshcipher|c=CIPHER Passes CIPHER to ssh to use a particular cipher set