Merge pull request #343 from phreaker0/whitelist-send-recv-options

implemented parsing of provided zfs send/recv options and whitelisting for each use case as needed
This commit is contained in:
Jim Salter 2019-03-29 11:57:13 -04:00 committed by GitHub
commit 31e46da29b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 90 additions and 7 deletions

97
syncoid
View File

@ -27,14 +27,24 @@ 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'});
if (! defined($sendoptions[0])) {
warn "invalid send options!";
pod2usage(2);
exit 127;
}
}
my $recvoptions = '';
my @recvoptions = ();
if (length $args{'recvoptions'}) {
$recvoptions = $args{'recvoptions'}
@recvoptions = parsespecialoptions($args{'recvoptions'});
if (! defined($recvoptions[0])) {
warn "invalid receive options!";
pod2usage(2);
exit 127;
}
}
@ -364,6 +374,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','h','p','v','w'));
my $recvoptions = getoptionsline(\@recvoptions, ('h','o','x','u','v'));
# sync 'em up.
if (! $targetexists) {
# do an initial sync from the oldest source snapshot
@ -488,6 +501,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);
@ -637,9 +651,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);
@ -672,6 +686,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','h','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);
@ -1430,6 +1445,12 @@ sub getsendsize {
$snaps = "-t $receivetoken";
}
my $sendoptions;
if (defined($receivetoken)) {
$sendoptions = getoptionsline(\@sendoptions, ('e'));
} else {
$sendoptions = getoptionsline(\@sendoptions, ('D','L','R','c','e','h','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"; }
@ -1511,6 +1532,68 @@ sub getreceivetoken() {
return
}
sub parsespecialoptions {
my ($line) = @_;
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
@ -1541,8 +1624,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