#!/usr/bin/perl -w # # Author : Sean O'Dell # Version : 1.1 # Updated : 30-Jun-2009 # Description: Perform ZFS snapshots and send to backup zpool use Getopt::Long; use POSIX; use strict; my $srcPoolName; # source ZFS pool name my $destPoolName; # destination ZFS pool name my $fsName; # ZFS file system to backup my $type; # type of backup: full, inc, or list my $quietFlag; # do not print "sending..." messages my $helpFlag; # print the help message GetOptions ("s=s" => \$srcPoolName, # string "d=s" => \$destPoolName, # string "f=s" => \$fsName, # string "t=s" => \$type, # string "q" => \$quietFlag, # flag "help" => \$helpFlag); # flag # check that all required parameters are specified on the command line. if (( !defined $srcPoolName) || (!defined $destPoolName) || (!defined $fsName) || (!defined $type)) { printHelp(); exit(1); } # run the main routine runBackup(); exit; sub runBackup { my ($currentDateTime); # create a time stamp for snapshot name my ($i); $currentDateTime = getCurrentDateTime(); my (@srcFSList) = (); # list of source file systems my (@destFSList) = (); # list of destination file systems my (@srcSnapList) = (); # list of source snapshots my (@destSnapList) = (); # list of destination snapshots my (%lastSrcSnapsHash); # hash of names most recent source snapshots my (%lastDestSnapsHash); # hash of names most recent source snapshots @srcFSList = getFSList ($srcPoolName, $fsName); @destFSList = getFSList ($destPoolName, $fsName); @srcSnapList = getSnapsList($srcPoolName, $fsName); @destSnapList = getSnapsList($destPoolName, $fsName); %lastSrcSnapsHash = getSnapsLast(\@srcSnapList); %lastDestSnapsHash = getSnapsLast(\@destSnapList); if ($type eq "full") { # caution: we delete all snapshots on the backup pool prior # to running a full backup. createSnaps ($fsName, $srcPoolName, $currentDateTime); destroySnaps(\@destSnapList); sendSnaps ($fsName, $srcPoolName, $destPoolName, $currentDateTime); } elsif ($type eq "inc") { createSnaps ($fsName, $srcPoolName, $currentDateTime); sendSnapsInc($fsName, $srcPoolName, $destPoolName, $currentDateTime, \%lastSrcSnapsHash); } elsif ($type eq "list") { print STDOUT "Source File System List\n"; print STDOUT "=========================\n"; for $i ( 0 .. $#{@srcFSList} ) { print STDOUT "Name : $srcFSList[$i]\n"; print STDOUT "Last Snap: " . getSnapLast($srcFSList[$i], \%lastSrcSnapsHash) . "\n"; } print STDOUT "=========================\n"; print STDOUT "Dest File System List\n"; print STDOUT "=========================\n"; for $i ( 0 .. $#{@destFSList} ) { print STDOUT "Name : $destFSList[$i]\n"; print STDOUT "Last Snap: " . getSnapLast($destFSList[$i], \%lastDestSnapsHash) . "\n"; } print STDOUT "=========================\n"; print STDOUT "Source Snapshot List\n"; print STDOUT "=========================\n"; for $i ( 0 .. $#{@srcSnapList} ) { print STDOUT "$srcSnapList[$i]\n"; } print STDOUT "=========================\n"; print STDOUT "Destination Snapshot List\n"; print STDOUT "=========================\n"; for $i ( 0 .. $#{@destSnapList} ) { print STDOUT "$destSnapList[$i]\n"; } print STDOUT "=========================\n"; } } sub printHelp { print STDOUT "\nusage:\n\n"; print STDOUT " zsnap-backup [-q] -t full|inc|list -f file_system\n"; print STDOUT " -s source_pool -d dest_pool\n\n"; print STDOUT " -q : run in quiet mode\n"; print STDOUT " -t : type of backup to run:\n"; print STDOUT " full : send complete snapshot to destination pool\n"; print STDOUT " inc : send incremental snapshot to destination pool\n"; print STDOUT " list : list snapshots on source and destination pools\n"; print STDOUT " -f : name of ZFS file system to backup\n"; print STDOUT " -s : name of the source ZFS pool\n"; print STDOUT " -d : name of the backup ZFS pool\n\n"; } sub getFSList { my ($poolName) = shift; # ZFS pool name my ($fsName) = shift; # ZFS filesystem name my (@fsList) = (); # list of file systems my ($fsCount) = 0; # file system count open (FILESYSTEMS, "zfs list -H -o name -r $poolName/$fsName|") or die "Can not run command."; while () { chomp($_); $fsList[$fsCount] = $_; $fsCount = $fsCount + 1; } close(FILESYSTEMS); return @fsList; } sub getSnapsList { my ($poolName) = shift; # ZFS pool name my ($fsName) = shift; # ZFS filesystem name my (@snapshotList) = (); # list of file systems my ($snapshotCount) = 0; # snapshot count open (SNAPSHOTS, "zfs list -H -o name -t snapshot -r $poolName/$fsName|") or die "Can not run command."; # read and process each snapshot while () { chomp($_); $snapshotList[$snapshotCount] = $_; $snapshotCount = $snapshotCount + 1; } close(SNAPSHOTS); return @snapshotList; } sub getSnapsLast { my ($snapList_ref) = @_; my (@snapList) = @{$snapList_ref}; my (%lastSnapsHash); my ($fsName); my ($i); # read and process each snapshot for $i ( 0 .. $#{@snapList} ) { $fsName = substr ($snapList[$i], 0, index($snapList[$i], "@")); $lastSnapsHash{$fsName} = $snapList[$i]; } return %lastSnapsHash; } sub getSnapLast { my ($fsName, $snapHash_ref) = @_; my (%lastSnapsHash) = %{$snapHash_ref}; my ($snapLast); if ($lastSnapsHash{$fsName}) { $snapLast = $lastSnapsHash{$fsName}; } else { $snapLast = ""; } return $snapLast; } sub createSnaps { my ($fsName, $sourcePool, $currentDateTime) = @_; my ($snapCmd); $snapCmd = "zfs snapshot -r " . $sourcePool . "/" . $fsName . "@" . $currentDateTime; open (CMDRESULT, "$snapCmd|") or die "Can not run command."; while () { chomp($_); print STDERR "$_\n"; } close(CMDRESULT); } sub sendSnaps { my ($fsName, $sourcePool, $destPool, $currentDateTime) = @_; my ($i); my ($sendCmd); $sendCmd = "zfs send -R " . $sourcePool . "/" . $fsName . "@" . $currentDateTime . " | zfs receive -F -d " . $destPool; if (!defined $quietFlag) { print STDOUT "Sending: " . $fsName . "@" . $currentDateTime . "\n"; } open (CMDRESULT, "$sendCmd|") or die "Can not run command."; while () { chomp($_); print STDERR "$_\n"; } close(CMDRESULT); } sub sendSnapsInc { my ($fsName, $sourcePool, $destPool, $currentDateTime, $lastSnapHash_ref) = @_; my (%lastSnapHash) = %{$lastSnapHash_ref}; my ($i); my ($sendCmd); $sendCmd = "zfs send -R -I " . getSnapLast($sourcePool . "/" . $fsName, \%lastSnapHash) . " " . $sourcePool . "/" . $fsName . "@" . $currentDateTime . " | zfs receive -F -d " . $destPool; if (!defined $quietFlag) { print STDOUT "Sending: " . $fsName . "@" . $currentDateTime . "\n"; } open (CMDRESULT, "$sendCmd|") or die "Can not run command."; while () { chomp($_); print STDERR "$_\n"; } close(CMDRESULT); } sub destroySnaps { my ($snapList_ref) = @_; my (@snapList) = @{$snapList_ref}; my ($i); my ($snapCmd); for $i ( 0 .. $#{@snapList} ) { $snapCmd = "zfs destroy " . $snapList[$i]; open (CMDRESULT, "$snapCmd|") or die "Can not run command."; while () { chomp($_); print STDERR "$_\n"; } close(CMDRESULT); } } sub getCurrentDateTime { my ($month); my ($dateTimeStr); my (@timeData) = (); # 0 Seconds past the minute # 1 Minutes past the hour # 2 Hours past midnight # 3 Day of the month # 4 Months past the start of the year # 5 Number of years since 1900 # 6 Number of days since the start of the week (Sunday) # 7 Number of days since the start of the year # 8 Whether or not daylight savings is active @timeData = localtime(time); $month = $timeData[4] + 1; $dateTimeStr = 1900 + $timeData[5]; if ($month < 10) { $dateTimeStr = $dateTimeStr . "0"; } $dateTimeStr = $dateTimeStr . $month; if ($timeData[3] < 10) { $dateTimeStr = $dateTimeStr . "0"; } $dateTimeStr = $dateTimeStr . $timeData[3] . "-"; if ($timeData[2] < 10) { $dateTimeStr = $dateTimeStr . "0"; } $dateTimeStr = $dateTimeStr . $timeData[2]; if ($timeData[1] < 10) { $dateTimeStr = $dateTimeStr . "0"; } $dateTimeStr = $dateTimeStr . $timeData[1]; if ($timeData[0] < 10) { $dateTimeStr = $dateTimeStr . "0"; } $dateTimeStr = $dateTimeStr . $timeData[0]; return ($dateTimeStr); }