Merge branch 'master' into pr447

This commit is contained in:
Christoph Klaffl 2020-02-11 17:45:54 +01:00
commit 34d6d44fa8
No known key found for this signature in database
GPG Key ID: 8FC1D76EED4970D2
12 changed files with 212 additions and 48 deletions

View File

@ -1,5 +1,7 @@
2.0.3 [sanoid] reverted DST handling and improved it as quickfix (@phreaker0)
2.0.2 [overall] documentation updates, new dependencies, small fixes, more warnings (@benyanke, @matveevandrey, @RulerOf, @klemens-u, @johnramsden, @danielewood, @g-a-c, @hartzell, @fryfrog, @phreaker0) 2.0.2 [overall] documentation updates, new dependencies, small fixes, more warnings (@benyanke, @matveevandrey, @RulerOf, @klemens-u, @johnramsden, @danielewood, @g-a-c, @hartzell, @fryfrog, @phreaker0)
[syncoid] changed and simplified DST handling (@shodanshok) [sanoid] changed and simplified DST handling (@shodanshok)
[syncoid] reset partially resume state automatically (@phreaker0) [syncoid] reset partially resume state automatically (@phreaker0)
[syncoid] handle some zfs erros automatically by parsing the stderr outputs (@phreaker0) [syncoid] handle some zfs erros automatically by parsing the stderr outputs (@phreaker0)
[syncoid] fixed ordering of snapshots with the same creation timestamp (@phreaker0) [syncoid] fixed ordering of snapshots with the same creation timestamp (@phreaker0)

View File

@ -175,4 +175,6 @@ pkg install p5-Config-Inifiles p5-Capture-Tiny pv mbuffer lzop
## Sanoid ## Sanoid
Take a look at the files `sanoid.defaults.conf` and` sanoid.conf.example` for all possible configuration options. Also have a look at the README.md Take a look at the files `sanoid.defaults.conf` and `sanoid.conf` for all possible configuration options.
Also have a look at the README.md for a simpler suggestion for `sanoid.conf`.

View File

@ -49,6 +49,14 @@ Which would be enough to tell sanoid to take and keep 36 hourly snapshots, 30 da
Specify a location for the config file named sanoid.conf. Defaults to /etc/sanoid Specify a location for the config file named sanoid.conf. Defaults to /etc/sanoid
+ --cache-dir
Specify a directory to store the zfs snapshot cache. Defaults to /var/cache/sanoid
+ --run-dir
Specify a directory for temporary files such as lock files. Defaults to /var/run/sanoid
+ --take-snapshots + --take-snapshots
This will process your sanoid.conf file, create snapshots, but it will NOT purge expired ones. (Note that snapshots taken are atomic in an individual dataset context, <i>not</i> a global context - snapshots of pool/dataset1 and pool/dataset2 will each be internally consistent and atomic, but one may be a few filesystem transactions "newer" than the other.) This will process your sanoid.conf file, create snapshots, but it will NOT purge expired ones. (Note that snapshots taken are atomic in an individual dataset context, <i>not</i> a global context - snapshots of pool/dataset1 and pool/dataset2 will each be internally consistent and atomic, but one may be a few filesystem transactions "newer" than the other.)

View File

@ -1 +1 @@
2.0.2 2.0.3

23
findoid
View File

@ -8,7 +8,7 @@
use strict; use strict;
use warnings; use warnings;
my $zfs = '/sbin/zfs'; my $zfs = 'zfs';
my %args = getargs(@ARGV); my %args = getargs(@ARGV);
my $progversion = '1.4.7'; my $progversion = '1.4.7';
@ -64,6 +64,10 @@ sub getversions {
my $filename = "$dataset/$snappath/$snap/$relpath"; my $filename = "$dataset/$snappath/$snap/$relpath";
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
if (!defined $size) {
next;
}
# only push to the $versions hash if this size and mtime aren't already present (simple dedupe) # only push to the $versions hash if this size and mtime aren't already present (simple dedupe)
my $duplicate = 0; my $duplicate = 0;
foreach my $version (keys %versions) { foreach my $version (keys %versions) {
@ -77,6 +81,14 @@ sub getversions {
} }
} }
my $filename = "$dataset/$relpath";
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
if (defined $size) {
$versions{$filename}{'size'} = $size;
$versions{$filename}{'mtime'} = $mtime;
}
return %versions; return %versions;
} }
@ -102,14 +114,19 @@ sub getdataset {
my ($path) = @_; my ($path) = @_;
open FH, "$zfs list -Ho mountpoint |"; open FH, "$zfs list -H -t filesystem -o mountpoint,mounted |";
my @datasets = <FH>; my @datasets = <FH>;
close FH; close FH;
my @matchingdatasets; my @matchingdatasets;
foreach my $dataset (@datasets) { foreach my $dataset (@datasets) {
chomp $dataset; chomp $dataset;
if ( $path =~ /^$dataset/ ) { push @matchingdatasets, $dataset; } my ($mountpoint, $mounted) = ($dataset =~ m/([^\t]*)\t*(.*)/);
if ($mounted ne "yes") {
next;
}
if ( $path =~ /^$mountpoint/ ) { push @matchingdatasets, $mountpoint; }
} }
my $bestmatch = ''; my $bestmatch = '';

View File

@ -1,3 +1,9 @@
sanoid (2.0.3) unstable; urgency=medium
[sanoid] reverted DST handling and improved it as quickfix (@phreaker0)
-- Jim Salter <github@jrs-s.net> Wed, 02 Oct 2019 17:00:00 +0100
sanoid (2.0.2) unstable; urgency=medium sanoid (2.0.2) unstable; urgency=medium
[overall] documentation updates, new dependencies, small fixes, more warnings (@benyanke, @matveevandrey, @RulerOf, @klemens-u, @johnramsden, @danielewood, @g-a-c, @hartzell, @fryfrog, @phreaker0) [overall] documentation updates, new dependencies, small fixes, more warnings (@benyanke, @matveevandrey, @RulerOf, @klemens-u, @johnramsden, @danielewood, @g-a-c, @hartzell, @fryfrog, @phreaker0)

View File

@ -12,7 +12,6 @@ Package: sanoid
Architecture: all Architecture: all
Depends: libcapture-tiny-perl, Depends: libcapture-tiny-perl,
libconfig-inifiles-perl, libconfig-inifiles-perl,
systemd,
zfsutils-linux | zfs, zfsutils-linux | zfs,
${misc:Depends}, ${misc:Depends},
${perl:Depends} ${perl:Depends}

4
packages/debian/postinst Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# remove old cache file
[ -f /var/cache/sanoidsnapshots.txt ] && rm /var/cache/sanoidsnapshots.txt || true

View File

@ -1,4 +1,4 @@
AUX sanoid.cron 45 BLAKE2B 3f6294bbbf485dc21a565cd2c8da05a42fb21cdaabdf872a21500f1a7338786c60d4a1fd188bbf81ce85f06a376db16998740996f47c049707a5109bdf02c052 SHA512 7676b32f21e517e8c84a097c7934b54097cf2122852098ea756093ece242125da3f6ca756a6fbb82fc348f84b94bfd61639e86e0bfa4bbe7abf94a8a4c551419 AUX sanoid.cron 45 BLAKE2B 3f6294bbbf485dc21a565cd2c8da05a42fb21cdaabdf872a21500f1a7338786c60d4a1fd188bbf81ce85f06a376db16998740996f47c049707a5109bdf02c052 SHA512 7676b32f21e517e8c84a097c7934b54097cf2122852098ea756093ece242125da3f6ca756a6fbb82fc348f84b94bfd61639e86e0bfa4bbe7abf94a8a4c551419
DIST sanoid-2.0.2.tar.gz 115797 BLAKE2B d00a038062df3dd8e77d3758c7b80ed6da0bac4931fb6df6adb72eeddb839c63d5129e0a281948a483d02165dad5a8505e1a55dc851360d3b366371038908142 SHA512 9d999b0f071bc3c3ca956df11e1501fd72a842f7d3315ede3ab3b5e0a36351100b6edbab8448bba65a2e187e4e8f77ff24671ed33b28f2fca9bb6ad0801aba9d DIST sanoid-2.0.2.tar.gz 115797 BLAKE2B d00a038062df3dd8e77d3758c7b80ed6da0bac4931fb6df6adb72eeddb839c63d5129e0a281948a483d02165dad5a8505e1a55dc851360d3b366371038908142 SHA512 73e3d25dbdd58a78ffc4384584304e7230c5f31a660ce6d2a9b9d52a92a3796f1bc25ae865dbc74ce586cbd6169dbb038340f4a28e097e77ab3eb192b15773db
EBUILD sanoid-2.0.2.ebuild 796 BLAKE2B f3d633289d66c60fd26cb7731bc6b63533019f527aaec9ca8e5c0e748542d391153dbb55b17b8c981ca4fa4ae1fc8dc202b5480c13736fca250940b3b5ebb793 SHA512 d0143680c029ffe4ac37d97a979ed51527b4b8dd263d0c57e43a4650bf8a9bb8 EBUILD sanoid-2.0.2.ebuild 796 BLAKE2B f3d633289d66c60fd26cb7731bc6b63533019f527aaec9ca8e5c0e748542d391153dbb55b17b8c981ca4fa4ae1fc8dc202b5480c13736fca250940b3b5ebb793 SHA512 d0143680c029ffe4ac37d97a979ed51527b4b8dd263d0c57e43a4650bf8a9bb8
EBUILD sanoid-9999.ebuild 776 BLAKE2B 416b8d04a9e5a84bce46d2a6f88eaefe03804944c03bc7f49b7a5b284b844212a6204402db3de3afa5d9c0545125d2631e7231c8cb2a3537bdcb10ea1be46b6a SHA512 98d8a30a13e75d7847ae9d60797d54078465bf75c6c6d9b6fd86075e342c0374 EBUILD sanoid-9999.ebuild 776 BLAKE2B 416b8d04a9e5a84bce46d2a6f88eaefe03804944c03bc7f49b7a5b284b844212a6204402db3de3afa5d9c0545125d2631e7231c8cb2a3537bdcb10ea1be46b6a SHA512 98d8a30a13e75d7847ae9d60797d54078465bf75c6c6d9b6fd86075e342c0374

View File

@ -1,4 +1,4 @@
%global version 2.0.2 %global version 2.0.3
%global git_tag v%{version} %global git_tag v%{version}
# Enable with systemctl "enable sanoid.timer" # Enable with systemctl "enable sanoid.timer"
@ -111,6 +111,8 @@ echo "* * * * * root %{_sbindir}/sanoid --cron" > %{buildroot}%{_docdir}/%{name}
%endif %endif
%changelog %changelog
* Wed Oct 02 2019 Christoph Klaffl <christoph@phreaker.eu> - 2.0.3
- Bump to 2.0.3
* Wed Sep 25 2019 Christoph Klaffl <christoph@phreaker.eu> - 2.0.2 * Wed Sep 25 2019 Christoph Klaffl <christoph@phreaker.eu> - 2.0.2
- Bump to 2.0.2 - Bump to 2.0.2
* Wed Dec 04 2018 Christoph Klaffl <christoph@phreaker.eu> - 2.0.0 * Wed Dec 04 2018 Christoph Klaffl <christoph@phreaker.eu> - 2.0.0

53
sanoid
View File

@ -4,22 +4,27 @@
# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this # from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this
# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE. # project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE.
$::VERSION = '2.0.2'; $::VERSION = '2.0.3';
my $MINIMUM_DEFAULTS_VERSION = 2; my $MINIMUM_DEFAULTS_VERSION = 2;
use strict; use strict;
use warnings; use warnings;
use Config::IniFiles; # read samba-style conf file use Config::IniFiles; # read samba-style conf file
use Data::Dumper; # debugging - print contents of hash use Data::Dumper; # debugging - print contents of hash
use File::Path; # for rmtree command in use_prune use File::Path 'make_path';
use Getopt::Long qw(:config auto_version auto_help); use Getopt::Long qw(:config auto_version auto_help);
use Pod::Usage; # pod2usage use Pod::Usage; # pod2usage
use Time::Local; # to parse dates in reverse use Time::Local; # to parse dates in reverse
use Capture::Tiny ':all'; use Capture::Tiny ':all';
my %args = ("configdir" => "/etc/sanoid"); my %args = (
"configdir" => "/etc/sanoid",
"cache-dir" => "/var/cache/sanoid",
"run-dir" => "/var/run/sanoid"
);
GetOptions(\%args, "verbose", "debug", "cron", "readonly", "quiet", GetOptions(\%args, "verbose", "debug", "cron", "readonly", "quiet",
"monitor-health", "force-update", "configdir=s", "configdir=s", "cache-dir=s", "run-dir=s",
"monitor-health", "force-update",
"monitor-snapshots", "take-snapshots", "prune-snapshots", "force-prune", "monitor-snapshots", "take-snapshots", "prune-snapshots", "force-prune",
"monitor-capacity" "monitor-capacity"
) or pod2usage(2); ) or pod2usage(2);
@ -41,9 +46,15 @@ my $default_conf_file = "$args{'configdir'}/sanoid.defaults.conf";
# parse config file # parse config file
my %config = init($conf_file,$default_conf_file); my %config = init($conf_file,$default_conf_file);
my $cache_dir = $args{'cache-dir'};
my $run_dir = $args{'run-dir'};
make_path($cache_dir);
make_path($run_dir);
# if we call getsnaps(%config,1) it will forcibly update the cache, TTL or no TTL # if we call getsnaps(%config,1) it will forcibly update the cache, TTL or no TTL
my $forcecacheupdate = 0; my $forcecacheupdate = 0;
my $cache = '/var/cache/sanoidsnapshots.txt'; my $cache = "$cache_dir/snapshots.txt";
my $cacheTTL = 900; # 15 minutes my $cacheTTL = 900; # 15 minutes
my %snaps = getsnaps( \%config, $cacheTTL, $forcecacheupdate ); my %snaps = getsnaps( \%config, $cacheTTL, $forcecacheupdate );
my %pruned; my %pruned;
@ -159,16 +170,16 @@ sub monitor_snapshots {
if ($elapsed == -1) { if ($elapsed == -1) {
push @msgs, "CRIT: $path has no $type snapshots at all!"; push @msgs, "CRIT: $path has no $type snapshots at all!";
} else { } else {
push @msgs, "CRIT: $path\'s newest $type snapshot is $dispelapsed old (should be < $dispcrit)"; push @msgs, "CRIT: $path newest $type snapshot is $dispelapsed old (should be < $dispcrit)";
} }
} }
} elsif ($elapsed > $warn) { } elsif ($elapsed > $warn) {
if ($warn > 0) { if ($warn > 0) {
if (! $config{$section}{'monitor_dont_warn'} && ($errorlevel < 2) ) { $errorlevel = 1; } if (! $config{$section}{'monitor_dont_warn'} && ($errorlevel < 2) ) { $errorlevel = 1; }
push @msgs, "WARN: $path\'s newest $type snapshot is $dispelapsed old (should be < $dispwarn)"; push @msgs, "WARN: $path newest $type snapshot is $dispelapsed old (should be < $dispwarn)";
} }
} else { } else {
# push @msgs .= "OK: $path\'s newest $type snapshot is $dispelapsed old \n"; # push @msgs .= "OK: $path newest $type snapshot is $dispelapsed old \n";
} }
} }
@ -1369,10 +1380,10 @@ sub get_zpool_capacity {
sub checklock { sub checklock {
# take argument $lockname. # take argument $lockname.
# #
# read /var/run/$lockname.lock for a pid on first line and a mutex on second line. # read $run_dir/$lockname.lock for a pid on first line and a mutex on second line.
# #
# check process list to see if the pid from /var/run/$lockname.lock is still active with # check process list to see if the pid from $run_dir/$lockname.lock is still active with
# the original mutex found in /var/run/$lockname.lock. # the original mutex found in $run_dir/$lockname.lock.
# #
# return: # return:
# 0 if lock is present and valid for another process # 0 if lock is present and valid for another process
@ -1384,7 +1395,7 @@ sub checklock {
# #
my $lockname = shift; my $lockname = shift;
my $lockfile = "/var/run/$lockname.lock"; my $lockfile = "$run_dir/$lockname.lock";
if (! -e $lockfile) { if (! -e $lockfile) {
# no lockfile # no lockfile
@ -1393,7 +1404,9 @@ sub checklock {
# make sure lockfile contains something # make sure lockfile contains something
if ( -z $lockfile) { if ( -z $lockfile) {
# zero size lockfile, something is wrong # zero size lockfile, something is wrong
die "ERROR: something is wrong! $lockfile is empty\n"; warn "WARN: deleting invalid/empty $lockfile\n";
unlink $lockfile;
return 1
} }
# lockfile exists. read pid and mutex from it. see if it's our pid. if not, see if # lockfile exists. read pid and mutex from it. see if it's our pid. if not, see if
@ -1404,7 +1417,9 @@ sub checklock {
close FH; close FH;
# if we didn't get exactly 2 items from the lock file there is a problem # if we didn't get exactly 2 items from the lock file there is a problem
if (scalar(@lock) != 2) { if (scalar(@lock) != 2) {
die "ERROR: $lockfile is invalid.\n" warn "WARN: deleting invalid $lockfile\n";
unlink $lockfile;
return 1
} }
my $lockmutex = pop(@lock); my $lockmutex = pop(@lock);
@ -1437,11 +1452,11 @@ sub checklock {
sub removelock { sub removelock {
# take argument $lockname. # take argument $lockname.
# #
# make sure /var/run/$lockname.lock actually belongs to me (contains my pid and mutex) # make sure $run_dir/$lockname.lock actually belongs to me (contains my pid and mutex)
# and remove it if it does, die if it doesn't. # and remove it if it does, die if it doesn't.
my $lockname = shift; my $lockname = shift;
my $lockfile = "/var/run/$lockname.lock"; my $lockfile = "$run_dir/$lockname.lock";
if (checklock($lockname) == 2) { if (checklock($lockname) == 2) {
unlink $lockfile; unlink $lockfile;
@ -1456,11 +1471,11 @@ sub removelock {
sub writelock { sub writelock {
# take argument $lockname. # take argument $lockname.
# #
# write a lockfile to /var/run/$lockname.lock with first line # write a lockfile to $run_dir/$lockname.lock with first line
# being my pid and second line being my mutex. # being my pid and second line being my mutex.
my $lockname = shift; my $lockname = shift;
my $lockfile = "/var/run/$lockname.lock"; my $lockfile = "$run_dir/$lockname.lock";
# die honorably rather than overwriting a valid, existing lock # die honorably rather than overwriting a valid, existing lock
if (! checklock($lockname)) { if (! checklock($lockname)) {
@ -1663,6 +1678,8 @@ Assumes --cron --verbose if no other arguments (other than configdir) are specif
Options: Options:
--configdir=DIR Specify a directory to find config file sanoid.conf --configdir=DIR Specify a directory to find config file sanoid.conf
--cache-dir=DIR Specify a directory to store the zfs snapshot cache
--run-dir=DIR Specify a directory for temporary files such as lock files
--cron Creates snapshots and purges expired snapshots --cron Creates snapshots and purges expired snapshots
--verbose Prints out additional information during a sanoid run --verbose Prints out additional information during a sanoid run

149
syncoid
View File

@ -4,7 +4,7 @@
# from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this # from http://www.gnu.org/licenses/gpl-3.0.html on 2014-11-17. A copy should also be available in this
# project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE. # project's Git repository at https://github.com/jimsalterjrs/sanoid/blob/master/LICENSE.
$::VERSION = '2.0.2'; $::VERSION = '2.0.3';
use strict; use strict;
use warnings; use warnings;
@ -16,6 +16,7 @@ use Sys::Hostname;
use Capture::Tiny ':all'; use Capture::Tiny ':all';
my $mbuffer_size = "16M"; my $mbuffer_size = "16M";
my $pvoptions = "-p -t -e -r -b";
# Blank defaults to use ssh client's default # Blank defaults to use ssh client's default
# TODO: Merge into a single "sshflags" option? # TODO: Merge into a single "sshflags" option?
@ -24,7 +25,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@", "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", "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", "no-clone-handling", "no-privilege-elevation", "force-delete", "no-clone-rollback", "no-rollback",
"create-bookmark", "create-bookmark", "pv-options=s" => \$pvoptions,
"mbuffer-size=s" => \$mbuffer_size) or pod2usage(2); "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 my %compressargs = %{compressargset($args{'compress'} || 'default')}; # Can't be done with GetOptions arg, as default still needs to be set
@ -285,11 +286,20 @@ sub syncdataset {
if ($debug) { print "DEBUG: syncing source $sourcefs to target $targetfs.\n"; } if ($debug) { print "DEBUG: syncing source $sourcefs to target $targetfs.\n"; }
my $sync = getzfsvalue($sourcehost,$sourcefs,$sourceisroot,'syncoid:sync'); my ($sync, $error) = getzfsvalue($sourcehost,$sourcefs,$sourceisroot,'syncoid:sync');
if (!defined $sync) { if (!defined $sync) {
# zfs already printed the corresponding error # zfs already printed the corresponding error
if ($exitcode < 2) { $exitcode = 2; } if ($error =~ /\bdataset does not exist\b/) {
if (!$quiet) { print "WARN Skipping dataset (dataset no longer exists): $sourcefs...\n"; }
return 0;
}
else {
# print the error out and set exit code
print "ERROR: $error\n";
if ($exitcode < 2) { $exitcode = 2 }
}
return 0; return 0;
} }
@ -1143,17 +1153,22 @@ sub getzfsvalue {
my $mysudocmd; my $mysudocmd;
if ($isroot) { $mysudocmd = ''; } else { $mysudocmd = $sudocmd; } if ($isroot) { $mysudocmd = ''; } else { $mysudocmd = $sudocmd; }
if ($debug) { print "$rhost $mysudocmd $zfscmd get -H $property $fsescaped\n"; } if ($debug) { print "$rhost $mysudocmd $zfscmd get -H $property $fsescaped\n"; }
open FH, "$rhost $mysudocmd $zfscmd get -H $property $fsescaped |"; my ($value, $error, $exit) = capture {
my $value = <FH>; system("$rhost $mysudocmd $zfscmd get -H $property $fsescaped");
close FH; };
if (!defined $value) {
return undef;
}
my @values = split(/\t/,$value); my @values = split(/\t/,$value);
$value = $values[2]; $value = $values[2];
return $value;
my $wantarray = wantarray || 0;
# If we are in scalar context and there is an error, print it out.
# Otherwise we assume the caller will deal with it.
if (!$wantarray and $error) {
print "ERROR getzfsvalue $fs $property: $error\n";
}
return $wantarray ? ($value, $error) : $value;
} }
sub readablebytes { sub readablebytes {
@ -1226,13 +1241,13 @@ sub buildsynccmd {
} }
if ($avail{'sourcembuffer'}) { $synccmd .= " $mbuffercmd $bwlimit $mbufferoptions |"; } if ($avail{'sourcembuffer'}) { $synccmd .= " $mbuffercmd $bwlimit $mbufferoptions |"; }
if ($avail{'localpv'} && !$quiet) { $synccmd .= " $pvcmd -s $pvsize |"; } if ($avail{'localpv'} && !$quiet) { $synccmd .= " $pvcmd $pvoptions -s $pvsize |"; }
$synccmd .= " $recvcmd"; $synccmd .= " $recvcmd";
} elsif ($sourcehost eq '') { } elsif ($sourcehost eq '') {
# local source, remote target. # local source, remote target.
#$synccmd = "$sendcmd | $pvcmd | $compressargs{'cmd'} | $mbuffercmd | $sshcmd $targethost '$compressargs{'decomcmd'} | $mbuffercmd | $recvcmd'"; #$synccmd = "$sendcmd | $pvcmd | $compressargs{'cmd'} | $mbuffercmd | $sshcmd $targethost '$compressargs{'decomcmd'} | $mbuffercmd | $recvcmd'";
$synccmd = "$sendcmd |"; $synccmd = "$sendcmd |";
if ($avail{'localpv'} && !$quiet) { $synccmd .= " $pvcmd -s $pvsize |"; } if ($avail{'localpv'} && !$quiet) { $synccmd .= " $pvcmd $pvoptions -s $pvsize |"; }
if ($avail{'compress'}) { $synccmd .= " $compressargs{'cmd'} |"; } if ($avail{'compress'}) { $synccmd .= " $compressargs{'cmd'} |"; }
if ($avail{'sourcembuffer'}) { $synccmd .= " $mbuffercmd $args{'source-bwlimit'} $mbufferoptions |"; } if ($avail{'sourcembuffer'}) { $synccmd .= " $mbuffercmd $args{'source-bwlimit'} $mbufferoptions |"; }
$synccmd .= " $sshcmd $targethost "; $synccmd .= " $sshcmd $targethost ";
@ -1255,7 +1270,7 @@ sub buildsynccmd {
$synccmd .= " | "; $synccmd .= " | ";
if ($avail{'targetmbuffer'}) { $synccmd .= "$mbuffercmd $args{'target-bwlimit'} $mbufferoptions | "; } if ($avail{'targetmbuffer'}) { $synccmd .= "$mbuffercmd $args{'target-bwlimit'} $mbufferoptions | "; }
if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; } if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; }
if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd -s $pvsize | "; } if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd $pvoptions -s $pvsize | "; }
$synccmd .= "$recvcmd"; $synccmd .= "$recvcmd";
} else { } else {
#remote source, remote target... weird, but whatever, I'm not here to judge you. #remote source, remote target... weird, but whatever, I'm not here to judge you.
@ -1269,7 +1284,7 @@ sub buildsynccmd {
$synccmd .= " | "; $synccmd .= " | ";
if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; } if ($avail{'compress'}) { $synccmd .= "$compressargs{'decomcmd'} | "; }
if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd -s $pvsize | "; } if ($avail{'localpv'} && !$quiet) { $synccmd .= "$pvcmd $pvoptions -s $pvsize | "; }
if ($avail{'compress'}) { $synccmd .= "$compressargs{'cmd'} | "; } if ($avail{'compress'}) { $synccmd .= "$compressargs{'cmd'} | "; }
if ($avail{'localmbuffer'}) { $synccmd .= "$mbuffercmd $mbufferoptions | "; } if ($avail{'localmbuffer'}) { $synccmd .= "$mbuffercmd $mbufferoptions | "; }
$synccmd .= "$sshcmd $targethost "; $synccmd .= "$sshcmd $targethost ";
@ -1455,11 +1470,19 @@ sub getsnaps() {
$fsescaped = escapeshellparam($fsescaped); $fsescaped = escapeshellparam($fsescaped);
} }
my $getsnapcmd = "$rhost $mysudocmd $zfscmd get -Hpd 1 -t snapshot guid,creation $fsescaped |"; my $getsnapcmd = "$rhost $mysudocmd $zfscmd get -Hpd 1 -t snapshot guid,creation $fsescaped";
if ($debug) { print "DEBUG: getting list of snapshots on $fs using $getsnapcmd...\n"; } if ($debug) {
$getsnapcmd = "$getsnapcmd |";
print "DEBUG: getting list of snapshots on $fs using $getsnapcmd...\n";
} else {
$getsnapcmd = "$getsnapcmd 2>/dev/null |";
}
open FH, $getsnapcmd; open FH, $getsnapcmd;
my @rawsnaps = <FH>; my @rawsnaps = <FH>;
close FH or die "CRITICAL ERROR: snapshots couldn't be listed for $fs (exit code $?)"; close FH or do {
# fallback (solaris for example doesn't support the -t option)
return getsnapsfallback($type,$rhost,$fs,$isroot,%snaps);
};
# this is a little obnoxious. get guid,creation returns guid,creation on two separate lines # this is a little obnoxious. get guid,creation returns guid,creation on two separate lines
# as though each were an entirely separate get command. # as though each were an entirely separate get command.
@ -1510,6 +1533,89 @@ sub getsnaps() {
return %snaps; return %snaps;
} }
sub getsnapsfallback() {
# fallback (solaris for example doesn't support the -t option)
my ($type,$rhost,$fs,$isroot,%snaps) = @_;
my $mysudocmd;
my $fsescaped = escapeshellparam($fs);
if ($isroot) { $mysudocmd = ''; } else { $mysudocmd = $sudocmd; }
if ($rhost ne '') {
$rhost = "$sshcmd $rhost";
# double escaping needed
$fsescaped = escapeshellparam($fsescaped);
}
my $getsnapcmd = "$rhost $mysudocmd $zfscmd get -Hpd 1 type,guid,creation $fsescaped |";
warn "snapshot listing failed, trying fallback command";
if ($debug) { print "DEBUG: FALLBACK, getting list of snapshots on $fs using $getsnapcmd...\n"; }
open FH, $getsnapcmd;
my @rawsnaps = <FH>;
close FH or die "CRITICAL ERROR: snapshots couldn't be listed for $fs (exit code $?)";
my %creationtimes=();
my $state = 0;
foreach my $line (@rawsnaps) {
if ($state < 0) {
$state++;
next;
}
if ($state eq 0) {
if ($line !~ /\Q$fs\E\@.*type\s*snapshot/) {
# skip non snapshot type object
$state = -2;
next;
}
} elsif ($state eq 1) {
if ($line !~ /\Q$fs\E\@.*guid/) {
die "CRITICAL ERROR: snapshots couldn't be listed for $fs (guid parser error)";
}
chomp $line;
my $guid = $line;
$guid =~ s/^.*\tguid\t*(\d*).*/$1/;
my $snap = $line;
$snap =~ s/^.*\@(.*)\tguid.*$/$1/;
$snaps{$type}{$snap}{'guid'}=$guid;
} elsif ($state eq 2) {
if ($line !~ /\Q$fs\E\@.*creation/) {
die "CRITICAL ERROR: snapshots couldn't be listed for $fs (creation parser error)";
}
chomp $line;
my $creation = $line;
$creation =~ s/^.*\tcreation\t*(\d*).*/$1/;
my $snap = $line;
$snap =~ s/^.*\@(.*)\tcreation.*$/$1/;
# the accuracy of the creation timestamp is only for a second, but
# snapshots in the same second are highly likely. The list command
# has an ordered output so we append another three digit running number
# to the creation timestamp and make sure those are ordered correctly
# for snapshot with the same creation timestamp
my $counter = 0;
my $creationsuffix;
while ($counter < 999) {
$creationsuffix = sprintf("%s%03d", $creation, $counter);
if (!defined $creationtimes{$creationsuffix}) {
$creationtimes{$creationsuffix} = 1;
last;
}
$counter += 1;
}
$snaps{$type}{$snap}{'creation'}=$creationsuffix;
$state = -1;
}
$state++;
}
return %snaps;
}
sub getbookmarks() { sub getbookmarks() {
my ($rhost,$fs,$isroot,%bookmarks) = @_; my ($rhost,$fs,$isroot,%bookmarks) = @_;
my $mysudocmd; my $mysudocmd;
@ -1602,9 +1708,9 @@ sub getsendsize {
if (defined($receivetoken)) { if (defined($receivetoken)) {
$sendoptions = getoptionsline(\@sendoptions, ('e')); $sendoptions = getoptionsline(\@sendoptions, ('e'));
} else { } else {
$sendoptions = getoptionsline(\@sendoptions, ('D','L','R','c','e','h','p','v','w')); $sendoptions = getoptionsline(\@sendoptions, ('D','L','R','c','e','h','p','w'));
} }
my $getsendsizecmd = "$sourcessh $mysudocmd $zfscmd send $sendoptions -nP $snaps"; my $getsendsizecmd = "$sourcessh $mysudocmd $zfscmd send $sendoptions -nvP $snaps";
if ($debug) { print "DEBUG: getting estimated transfer size from source $sourcehost using \"$getsendsizecmd 2>&1 |\"...\n"; } if ($debug) { print "DEBUG: getting estimated transfer size from source $sourcehost using \"$getsendsizecmd 2>&1 |\"...\n"; }
open FH, "$getsendsizecmd 2>&1 |"; open FH, "$getsendsizecmd 2>&1 |";
@ -1792,6 +1898,7 @@ Options:
--source-bwlimit=<limit k|m|g|t> Bandwidth limit in bytes/kbytes/etc per second on the source transfer --source-bwlimit=<limit k|m|g|t> Bandwidth limit in bytes/kbytes/etc per second on the source transfer
--target-bwlimit=<limit k|m|g|t> Bandwidth limit in bytes/kbytes/etc per second on the target transfer --target-bwlimit=<limit k|m|g|t> Bandwidth limit in bytes/kbytes/etc per second on the target transfer
--mbuffer-size=VALUE Specify the mbuffer size (default: 16M), please refer to mbuffer(1) manual page. --mbuffer-size=VALUE Specify the mbuffer size (default: 16M), please refer to mbuffer(1) manual page.
--pv-options=OPTIONS Configure how pv displays the progress bar, default '-p -t -e -r -b'
--no-stream Replicates using newest snapshot instead of intermediates --no-stream Replicates using newest snapshot instead of intermediates
--no-sync-snap Does not create new snapshot, only transfers existing --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) --create-bookmark Creates a zfs bookmark for the newest snapshot on the source after replication succeeds (only works with --no-sync-snap)