Skip to content

Instantly share code, notes, and snippets.

@oetiker
Last active September 27, 2024 11:49
Show Gist options
  • Save oetiker/ef9d347d1ec2b7c2c3d48f7ac9e9c55d to your computer and use it in GitHub Desktop.
Save oetiker/ef9d347d1ec2b7c2c3d48f7ac9e9c55d to your computer and use it in GitHub Desktop.
ZFS Send/Receive Helper

zfs send helper

If you want to copy a zfs fileset from location a to b you can use the zfs send and zfs receive mechanism. Theoretically very simple, but if the fileset is hirarchical and there are snapshots and you want to be able To resume the copy process, some help may be needed.

The zfs send helper script looks at the zfs filesets actually present in the locations you indicate and it gives you the commands you need to do the actual copying. The script runs zfs but only to investigate it does not do any actual copying.

#!/usr/bin/env perl
use v5.22;
my $src_root = shift @ARGV;
my @dest = split /:/, shift @ARGV;
my $dest_root = pop @dest;
my $dest_host = shift @dest;
if (not $dest_root) {
print "$0 src-root/fileset remote-host:dest-root/filest\n";
exit 1;
}
my $remote = $dest_host ? "ssh $dest_host " : "";
my @remote = $dest_host ? ("ssh",$dest_host) : ();
my %fsCheck;
open my $all, '-|', qw(zfs list -s name -o name -H -r),$src_root;
while (<$all>){
chomp;
my $fileset = $_;
open my $one, '-|', qw(zfs list -o name -s creation -H -d1 -t snapshot), $fileset;
my $first;
my $last;
my %gotSnaps;
while (<$one>){
chomp;
$first //= $_;
$last = $_;
s/.*\@//;
$gotSnaps{$_}=1;
}
if (not $first){
print "# skipping $fileset - no snapshots\n";
next;
}
my $dest_path = $first;
$dest_path =~ s{$src_root}{};
my $dest_path_clean = $dest_path;
$dest_path_clean =~ s/\@.+//g;
open my $dest_snaps, '-|', @remote,qw(zfs list -o name -s creation -H -d1 -t snapshot), $dest_root.$dest_path_clean;
my $lastRemoteSnap;
my $force = "";
while (<$dest_snaps>) {
chomp;
s/.*\@//;
if ($gotSnaps{$_}){
$lastRemoteSnap = $_;
}
elsif ($lastRemoteSnap) {
print "# found remoteSnap $_ which is not present locally. Adding -F\n";
$force = "-F ";
last;
}
}
if ($lastRemoteSnap){
print "# $lastRemoteSnap is already transfered. Adjusting first\n";
$first =~ s/\@.+/\@$lastRemoteSnap/;
}
my $dest_base = "$dest_root$dest_path";
$dest_base =~ s{/[^/]+$}{};
if (not $fsCheck{$dest_base}){
$fsCheck{$dest_base} = 1;
}
print "zfs send $first | mbuffer -m 1G | ${remote}zfs receive -u $dest_root$dest_path\n" if not $lastRemoteSnap;
if ($first ne $last){
$first =~ s/.+\@//;
$dest_path =~ s/\@.+//;
print "zfs send -I $first $last | mbuffer -m 50M | ${remote}zfs receive -u $force$dest_root$dest_path\n";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment