--- /dev/null
+#!/bin/bash
+
+# Author: Carl Baldwin & Alan Pippin
+# Description: This script takes a snapshot of the given zfs filesystem.
+# It also employs an intelligent algorithm to roll off,
+# or destroy, old snapshots.
+
+test=0
+
+[ $test == 0 ] && exec >> /var/log/zfs-autosnap.log 2>&1
+
+# This script makes the following assumptions/requirements:
+# * this script only handles one zfs filesystem, a wrapper should be created
+# to handle more
+# * this script handles all snapshots that are named in this format:
+# YYYY-MM-DD.hh.mm
+# * It ignores other snapshots that don't follow this naming convention
+
+# This converts the YYYY-MM-DD.hh.mm format to an integer.
+datetime_to_minutes() {
+ perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; print $1 * 527040 + $2 * 44640 + $3 * 1440 + $4 * 60 + $5,"\n"'
+}
+
+datetime_to_minutes2() {
+ perl -n -e '/(\d+)-(\d+)-(\d+)\.(\d+)\.(\d+)/; $monthadj=int(($2-1)/3)-1; $minadj=int($5/15); $houradj=int($4/3); print $1 * 1048576 + ( $2 + $monthadj ) * 65536 + $3 * 2048 + ( $4 + $houradj ) * 64 + $5 + $minadj,"\n"'
+}
+
+# test: if set to 1, no zfs snapshot commands will be run, they will only be echoed
+# filesystem: This is the zfs filesystem to snapshot
+# mountpoint: This is the mountpoint of the zfs filesystem to snapshot
+# numsnapshots: This number is the number of equally spaced snapshots that should exist over any given period in the past
+# maxagedays: This is the maximum number of days to keep any snapshot around for (0=infinite) (default=0).
+filesystem=$1
+mountpoint=$2
+numsnapshots=${3-20}
+maxagedays=${4-0}
+
+lockdir="/tmp/zfs-admin-lock"
+pool=`echo "$filesystem" | awk -F '/' '{ print $1 }'`
+
+if [ -z "$filesystem" ] || [ -z "$mountpoint" ] || [ -z "$numsnapshots" ] || [ -z "$maxagedays" ]; then
+ echo "-E- Usage: $0 <filesystem> <mountpoint> <numsnapshots> <maxagedays>"
+ exit 1
+fi
+
+if [ ! -d "$mountpoint" ]; then
+ echo "-E- Unable to find the mountpoint: $mountpoint"
+ exit 1
+fi
+
+snapshotdir="${mountpoint}/.zfs/snapshot"
+if [ ! -d "$snapshotdir" ]; then
+ echo "-E- Unable to find the snapshotdir: $snapshotdir"
+ exit 1
+fi
+
+# Check to see if this zfs pool has a scrub being performed on it now.
+# If it does, we cannot perform any snapshot create or destroy operations.
+zpool status $pool | grep scrub: | grep "in progress" > /dev/null 2>&1
+if [ $? == 0 ]; then
+ echo "-W- The zfs pool '$pool' is currently being scrubbed. Skipping all snapshot operations."
+ exit 0
+fi
+
+# Get the various components of the date
+datetime=${ZFSDATETIME:-$(date +%Y-%m-%d.%H.%M)}
+
+# Create the snapshot for this minute
+echo "-I- Creating ${filesystem}@${datetime}"
+[ $test == 0 ] && zfs snapshot "${filesystem}@${datetime}"
+
+minutes=$(echo $datetime | datetime_to_minutes)
+
+# Check to ensure the zfs filesystem has not been locked down.
+# If it has, we cannot perform any snapshot destroy operations.
+if [ -d "$lockdir" ]; then
+ echo "-W- The zfs filesystem has been locked down. Skipping snapshot cleanup."
+ exit 0
+fi
+
+# Trim them down
+snapshots=$(ls -d ${snapshotdir}/????-??-??.??.?? 2>/dev/null)
+for snapshot in $snapshots; do
+ snapminutes=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes)
+ snapminutes2=$(echo "$snapshot" | sed 's,.*/,,' | datetime_to_minutes2)
+ age=$((minutes - snapminutes))
+ window=1
+ while true; do
+ if [ $age -lt $((window * numsnapshots)) ]; then
+ case $((snapminutes2 % window)) in
+ 0) ;;
+ *)
+ snapname=${filesystem}$(echo "$snapshot" |
+ sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,')
+ echo "-I- Destroying $snapname"
+ [ $test == 0 ] && zfs destroy "$snapname"
+ ;;
+ esac
+ break
+ fi
+ window=$((window*2))
+ done
+ if [ $maxagedays -gt 0 ] && [ $age -gt $((maxagedays * 24 * 60)) ]; then
+ snapname=${filesystem}$(echo "$snapshot" |
+ sed 's,/\(.*\)/.zfs/snapshot/\(.*\),@\2,')
+ echo "-I- Destroying old $snapname"
+ [ $test == 0 ] && zfs destroy "$snapname"
+ fi
+done
+
+true
--- /dev/null
+#!/bin/bash
+
+# Author: Alan J. Pippin
+# Description: This script is a wrapper script that calls zfs-autosnap
+# for each filesystem provided below.
+
+# Setup some default values
+autosnap="/usr/local/etc/bin/zfs-autosnap"
+logfile="/var/log/zfs-autosnap.log"
+numsnapshots=20
+maxagedays=365
+
+# Auto snapshot every zfs filesystem on the system specified below
+date >> $logfile
+
+# Special filesystems
+$autosnap storage /storage $numsnapshots 15
+$autosnap tank/usr/videos /usr/videos $numsnapshots 15
+
+# Normal filesystems
+$autosnap tank / $numsnapshots $maxagedays
+$autosnap tank/backup /backup $numsnapshots $maxagedays
+$autosnap tank/usr /usr $numsnapshots $maxagedays
+$autosnap tank/usr/home /usr/home $numsnapshots $maxagedays
+$autosnap tank/usr/local /usr/local $numsnapshots $maxagedays
+$autosnap tank/usr/local/etc /usr/local/etc $numsnapshots $maxagedays
+
--- /dev/null
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+use File::Temp qw/ tempfile tempdir /;
+
+my $SSLPATH = '/usr/bin/openssl';
+
+#Usage
+sub print_usage {
+ print "ZFS Snapshot diff\n";
+ print "\t$0 [-dhirv] <zfs shapshot name> [filename]\n\n";
+ print " -d Display the lines that are different (diff output)\n";
+ print " -h Display this usage\n";
+ print " -i Ignore files that don't exist in the snapshot (only necessary for recursing)\n";
+ print " -r Recursively diff every file in the snapshot (filename not required)\n";
+ print " -v Verbose mode\n\n";
+
+ print " [filename] is the filename RELATIVE to the ZFS snapshot root. For example, if\n";
+ print " I had a filesystem snapshot called pool/data/zone\@initial. The filename '/etc/passwd'\n";
+ print " would refer to the filename /pool/data/zone/etc/passwd in the filesystem and filename\n";
+ print " /pool/data/zone/.zfs/snapshot/initial/etc/passwd in the snapshot.\n\n";
+
+ print " A couple of examples:\n";
+ print "\t$0 -v -r -i pool/zones/lava2019\@Fri\n";
+ print "\t\tChecks the current pool/zones/lava2019 filesystem against the snapshot\n";
+ print "\t\treturning the md5sum difference of any files (ignore files that don't\n";
+ print "\t\texist in the snapshot). With verbose mode\n\n";
+
+ print "\t$0 -d pool/zones/lava2019\@Mon /root/etc/passwd\n";
+ print "\t\tCheck the md5sum for /pool/zones/lava2019/root/etc/passwd and compare\n";
+ print "\t\tit to /pool/zones/lava2019/.zfs/snapshot/Mon/root/etc/passwd. Display\n";
+ print "\t\tthe lines that are different also.\n\n";
+
+ exit(0);
+}
+
+use Getopt::Long;
+my %options = ();
+my $verbose;
+
+GetOptions("h" => \$options{help},
+ "r!" => \$options{recurse},
+ "d!" => \$options{diff},
+ "i!" => \$options{ignore},
+ "v!" => \$verbose
+ );
+
+if ($options{help}) {
+ print_usage();
+}
+
+if ($options{recurse}) {
+ recurse_diff(shift || die "Need a ZFS snapshot name\n");
+} else {
+ my $zfsname = shift || die "Need a ZFS snapshot name\n";
+ my $file = shift || die "Need a filename\n";
+ diff_single_file($zfsname,$file);
+}
+
+exit(0);
+
+sub recurse_diff {
+ my $zfssnap = shift;
+ print "Recursive diff on $zfssnap\n" if $verbose;
+
+ $zfssnap =~ /(.+)\@(.+)/i;
+ my $fsname = "/" . $1;
+ my $snapname = $2;
+ if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; }
+ elsif(! -d $fsname && $fsname =~ /\/\S+?/) { $fsname = "/"; }
+ print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose;
+
+ my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/";
+ my $fspath = $fsname . "/";
+ $fspath =~ s/\/\//\//gi;
+ $snappath =~ s/\/\//\//gi;
+ print "Comparing: $fspath\nto: $snappath\n" if $verbose;
+
+ my $dir = tempdir( CLEANUP => 0 );
+ my ($fh, $filename) = tempfile( DIR => $dir );
+
+ `find $fspath -name "*" -type f > $filename`;
+
+ foreach my $file (<$fh>) {
+ chomp($file);
+ $file =~ /(.*)\/(.*)/;
+ my $shortname = $2;
+ $file =~ /$fspath(.*)/;
+ my $diff = $snappath . $1;
+ if (!-e $diff) {
+ print "$file does not exist in snapshot\n" if !$options{ignore};
+ next;
+ }
+
+ # do the md5 sums
+ my $orig = `$SSLPATH md5 $file`;
+ my $snap = `$SSLPATH md5 $diff`;
+ $orig =~ /[\s\S]+= (.+)/;
+ my $sum1 = $1;
+ $snap =~ /[\s\S]+= (.+)/;
+ my $sum2 = $1;
+ if ($sum1 ne $sum2) {
+ print "** $file is different\n";
+ print "** $orig** $snap" if $verbose;
+ }
+ if ($options{diff}) {
+ system("diff $file $diff");
+ }
+ }
+}
+
+sub diff_single_file {
+ my $zfssnap = shift;
+ my $filename = shift;
+ print "Single-file diff on $zfssnap, file: $filename\n" if $verbose;
+
+ $zfssnap =~ /(.+)\@(.+)/i;
+ my $fsname = "/" . $1 . "/";
+ my $snapname = $2;
+ if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; }
+ print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose;
+
+ my $fspath;
+ if($filename !~ /^\//) { $fspath = $ENV{'PWD'} . "/" . $filename; }
+ else { $fspath = $filename; }
+
+ my $snapfspath = $fspath;
+ $snapfspath =~ s/$fsname//g;
+ my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/" . $snapfspath;
+ $fspath =~ s/\/\//\//gi;
+ $snappath =~ s/\/\//\//gi;
+ print "Comparing: $fspath\nto: $snappath\n" if $verbose;
+ if(! -f $fspath) { print "-E- Cannot find source file: $fspath\n"; exit 1; }
+ if(! -f $snappath) { print "-E- Cannot find source file: $snappath\n"; exit 1; }
+
+ my $orig = `$SSLPATH md5 $fspath`;
+ my $snap = `$SSLPATH md5 $snappath`;
+ $orig =~ /[\s\S]+= (.+)/;
+ my $sum1 = $1;
+ $snap =~ /[\s\S]+= (.+)/;
+ my $sum2 = $1;
+ if ($sum1 ne $sum2) {
+ print "** Files are different\n";
+ print "** $orig** $snap" if $verbose;
+ }
+ if ($options{diff}) {
+ system("diff $fspath $snappath");
+ }
+}
--- /dev/null
+#!/usr/bin/perl
+
+# Author: Alan J. Pippin
+# Description: This script parses logfiles generated by the following zfs scripts:
+# zfs-replicate
+
+$logfile=shift(@ARGV);
+$startdate=shift(@ARGV);
+
+sub usage {
+ print "Usage: $0 <log file> <date>\n";
+ exit 0;
+}
+if(! -f "$logfile") { &usage; }
+
+$kilo = 1024;
+$mega = 1024 * 1024;
+$giga = 1024 * 1024 * 1024;
+
+sub time_to_seconds {
+ my ($hour,$minute,$sec) = @_;
+ $seconds = ($hour * 60 * 60) + ($minute * 60) + ($sec);
+ return($seconds);
+}
+
+sub adjust_duration {
+ my ($duration) = @_;
+ if($duration > 3600) { $duration=int($duration/3600); $duration.="h"; }
+ elsif($duration > 60) { $duration=int($duration/60); $duration.="m"; }
+ else { $duration.="s"; }
+ return $duration;
+}
+
+sub adjust_data {
+ my ($data) = @_;
+ if($data > ($giga)) { $data = int($data / $giga); $data = "$data"."Gb"; }
+ elsif($data > ($mega)) { $data = int($data / $mega); $data = "$data"."Mb"; }
+ elsif($data > ($kilo)) { $data = int($data / $kilo); $data = "$data"."Kb"; }
+ return $data;
+}
+
+sub parse_replicate_logfile {
+ $in_replicate=0;
+ $date="";
+ %totals=();
+ while(<FILE>) {
+ $line = $_;
+ if(($in_replicate == 0) && ("$startdate" ne "") && ($line !~ /$startdate/)) { next; }
+ if($line =~ /(\S+)\s+(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\S+)\s+(\S+)/) {
+ $dayname=$1; $month=$2; $daynum=$3; $hour=$4; $minute=$5; $sec=$6; $year=$8;
+ if(($in_replicate == 0) && ($line =~ /lock obtained/)) {
+ $in_replicate = 1;
+ $date="$dayname $month $daynum $hour:$minute:$sec $year";
+ $totals{$date}{data} = 0;
+ $totals{$date}{transfertime} = 0;
+ $totals{$date}{duration} = time_to_seconds($hour,$minute,$sec);
+ }
+ elsif(($in_replicate == 1) && ($line=~ /lock released/)) {
+ $in_replicate = 0;
+ $totals{$date}{duration} = time_to_seconds($hour,$minute,$sec) - $totals{$date}{duration};
+ }
+ }
+ if(($in_replicate == 1) && ($line =~ /received ([\d\.]+)(\w+)/)) {
+ $data = $1; $size = $2;
+ if($size =~ /Kb/i) { $data = $data * $kilo; }
+ if($size =~ /Mb/i) { $data = $data * $mega; }
+ if($size =~ /Gb/i) { $data = $data * $giga; }
+ chomp($line);
+ $totals{$date}{data} += $data;
+ }
+ if(($in_replicate == 1) && ($line =~ /in (\d+) seconds/)) {
+ $transfertime = $1;
+ $totals{$date}{transfertime} += $transfertime;
+ }
+ }
+
+ foreach $date (keys %totals) {
+ $duration=adjust_duration($totals{$date}{duration});
+ $data=adjust_data($totals{$date}{data});
+ $transfertime=adjust_duration($totals{$date}{transfertime});
+ $rate = adjust_data(int($totals{$date}{data}/$totals{$date}{transfertime}));
+ print "$date: data=${data} transfertime=$transfertime rate=${rate}/sec duration=$duration\n";
+ }
+}
+
+#########
+# MAIN
+#########
+#print "-> Parsing $logfile\n";
+open(FILE,"$logfile") || die "-E- Unable to open $logfile\n";
+
+if($logfile =~ /replicate/) { parse_replicate_logfile(); }
+
+close(FILE);
--- /dev/null
+#!/bin/bash
+
+# Author: Carl Baldwin & Alan Pippin
+# Description: This script replicates a given zfs filesystem to a given zfs pool.
+# This script will keep all snapshots in sync, removing the ones
+# that have been deleted since the last replicate was performed.
+# This script will only send the new, or missing, snapshots since
+# the last replicate was performed.
+
+# In test mode (test=1) commands are echoed, not executed
+test=0
+
+[ $test == 0 ] && exec >> /var/log/zfs-replicate.log 2>&1
+
+# Usage: zfs-backup [filesystem] [destination_pool]
+# This script has a limitation with children under a given filesystem.
+# You must initially backup the parent filesystems first using this script
+# before backing up any of the children filesystems.
+
+fs=$1
+fs=${fs%/}
+fsname=${1#*/}
+srcpool=${1%%/*}
+srcfs="${srcpool}/$fsname"
+srcfs=${srcfs%/}
+dstpool=$2
+dstfs="${dstpool}/$srcfs"
+dstfs=${dstfs%/}
+nodstsnaps=0
+common=""
+
+if [ $test == 1 ]; then
+ echo "fs: $fs"
+ echo "fsname: $fsname"
+ echo "srcpool: $srcpool"
+ echo "srcfs: $srcfs"
+ echo "dstpool: $dstpool"
+ echo "dstfs: $dstfs"
+fi
+
+if ! zpool list -H "$srcpool" >/dev/null 2>&1; then
+ echo >&2 "-E- The source pool, '$srcpool' doesn't seem to exist."
+ exit 1
+fi
+
+if ! zpool list -H "$dstpool" >/dev/null 2>&1; then
+ echo >&2 "-E- The destination pool, '$dstpool' doesn't seem to exist."
+ exit 1
+fi
+
+if ! zfs list -rH -t snapshot "$dstfs" 2>&1 | grep "$dstfs@" > /dev/null 2>&1; then
+ echo >&2 "-W- No snapshots detected on the destination drive for this filesystem"
+ nodstsnaps=1
+fi
+
+if [ $nodstsnaps == 0 ]; then
+ zfs list -rH -t snapshot $srcfs | grep "$srcfs@" | awk '{print $1}' > /tmp/source-list
+ zfs list -rH -t snapshot $dstfs | grep "$dstfs@" | sed "s,$dstpool/,," | awk '{print $1}' > /tmp/destination-list
+ diff -u /tmp/source-list /tmp/destination-list | grep -v '^+++' | awk '/^\+/ {print}' | sed "s,^\+,$dstpool/," > /tmp/obsolete-snapshots
+ rm -f /tmp/source-list /tmp/destination-list
+
+ echo >&2 "Removing obsolete backups from the destination pool"
+ for snapshot in $(cat /tmp/obsolete-snapshots); do
+ echo >&2 "Removing '$snapshot' from destination."
+ [ $test == 0 ] && zfs destroy "$snapshot"
+ done
+
+ echo >&2 "Rolling back to the most recent snapshot on the destination."
+ [ $test == 0 ] && zfs rollback $(zfs list -rH -t snapshot $dstfs | grep "$dstfs@" | awk '{snap=$1} END {print snap}')
+
+ echo >&2 "Calculating the most recent common snapshot between the two filesystems."
+ if zfs list -H "$dstfs" > /dev/null 2>&1; then
+ for snap in $(zfs list -rH -t snapshot "$dstfs" | grep "$dstfs@" |
+ sed 's,.*@,,' | awk '{print$1}'); do
+ if zfs list -rH -t snapshot "$fs" | grep "$fs@" | sed 's,.*@,,' | awk '{print$1}' | grep "^${snap}$" >/dev/null 2>&1; then
+ common=$snap
+ fi
+ done
+ fi
+fi
+
+base=$common
+foundcommon=false
+if [ -z "$common" ]; then
+ foundcommon=true
+fi
+
+for snap in $(zfs list -rH -t snapshot "$fs" | grep "$fs@" |
+ sed 's,.*@,,' | awk '{print$1}'); do
+ if [ "$snap" = "$common" ]; then
+ foundcommon=true
+ continue
+ fi
+
+ if $foundcommon; then
+ if [ -z "$base" ]; then
+ echo >&2 "Sending '$fs@$snap'"
+ [ $test == 0 ] && zfs set readonly=on "$dstpool"
+ [ $test == 0 ] && zfs set atime=off "$dstpool"
+ [ $test == 0 ] && zfs set sharenfs=off "$dstpool"
+ [ $test == 0 ] && zfs set mountpoint=legacy "$dstpool"
+ [ $test == 0 ] && zfs send "$fs@$snap" | zfs recv -v "$dstfs"
+ else
+ echo >&2 "Sending '$fs@$base' -> '$fs@$snap'"
+ [ $test == 0 ] && zfs send -i "$fs@$base" "$fs@$snap" | zfs recv -v "$dstfs"
+ fi
+ base=$snap
+ fi
+done
+
+true
--- /dev/null
+#!/bin/bash
+
+# Author: Alan J. Pippin
+# Description: This script calls zfs-replicate for each filesystem needing
+# to be backed up, or replicated, to another ZFS pool.
+
+# Setup some default values
+replicate="/usr/local/etc/bin/zfs-replicate"
+logfile_parser="/usr/local/etc/bin/zfs-log-parser"
+logfile="/var/log/zfs-replicate.log"
+lockdir="/tmp/zfs-admin-lock"
+destpool="backups"
+maxsleeptime=60
+released_lock_date=0
+
+# Setup our cleanup and exit trap
+cleanup() {
+ rm -rf "$lockdir"
+ if [ $released_lock_date == 0 ]; then
+ echo `date` ZFS admin lock released >> $logfile
+ fi
+}
+trap cleanup EXIT
+
+# Auto snapshot every zfs filesystem on the system specified below
+date=`date`;
+echo "$date Polling for ZFS admin lock" >> $logfile
+
+# Poll for a lock on the zfs subsystem, and make the lock once we can do so
+while true; do
+ if ! mkdir "$lockdir" >/dev/null 2>&1; then
+ # Another zfs admin tool is running.
+ # Wait a random amount of time and try again
+ ransleep=$(($RANDOM % $maxsleeptime))
+ sleep $ransleep
+ else
+ # No other zfs admin tool is running, we can now.
+ break
+ fi
+done
+date=`date`;
+echo "$date ZFS admin lock obtained" >> $logfile
+
+# List the filesystems to replicate
+# The parent filesystems MUST be listed ahead
+# of the children filesystems.
+# Pool root filesystems must end with a slash.
+$replicate tank/ $destpool
+$replicate tank/usr $destpool
+$replicate tank/usr/home $destpool
+$replicate tank/usr/videos $destpool
+$replicate tank/usr/local $destpool
+$replicate tank/usr/local/etc $destpool
+$replicate tank/backup $destpool
+
+# Release our lock
+released_lock_date=1
+echo `date` ZFS admin lock released >> $logfile
+
+# Parse the log file and extract our backup stats
+$logfile_parser "$logfile" "$date" >> $logfile
--- /dev/null
+#!/bin/bash
+
+# Author: Alan J. Pippin
+# Description: This script will attempt to scrub each zfs pool
+# given to it in the for loop. This script ensures
+# That only 1 scrub operation is running at any given time.
+# This serializes the zfs scrub process for each pool.
+
+maxsleeptime=360
+
+for i in tank storage
+do
+ # Check to see if this zfs filesystem has a scrub being performed on it now.
+ # If it does, we cannot perform more than one scrub operation at a time.
+ while true; do
+ /sbin/zpool status | grep scrub: | grep "in progress" > /dev/null 2>&1
+ if [ $? == 0 ]; then
+ # Another zpool scrub operation is already running
+ # Wait until it is done before continuing
+ ransleep=$(($RANDOM % $maxsleeptime))
+ sleep $ransleep
+ else
+ # Another zpool scrub operation is not running
+ break
+ fi
+ done
+
+ echo "Scrubing zfs pool $i"
+ /sbin/zpool scrub $i
+
+done
--- /dev/null
+#!/usr/bin/perl
+
+@snapshots=`zfs list -t snapshot`;
+
+$kilo = 1024;
+$mega = 1024 * 1024;
+$giga = 1024 * 1024 * 1024;
+
+sub adjust_size
+{
+ my ($size) = @_;
+ if($size > ($giga)) { $size = int($size / $giga); $size = "$size"."G"; }
+ elsif($size > ($mega)) { $size = int($size / $mega); $size = "$size"."M"; }
+ elsif($size > ($kilo)) { $size = int($size / $kilo); $size = "$size"."K"; }
+ return $size;
+}
+
+foreach $snapshot (@snapshots)
+{
+ chomp($snapshot);
+ if($snapshot =~ /(\S+)\@(\S+)\s+(\S+)\s+/) {
+ $filesystem = $1;
+ $size = $3;
+ if($size =~ /k/i) { $size = $size * $kilo; }
+ if($size =~ /m/i) { $size = $size * $mega; }
+ if($size =~ /g/i) { $size = $size * $giga; }
+ $totals{$filesystem}{size} += $size;
+ $totals{$filesystem}{snapshots}++;
+ }
+}
+
+printf "%-20s %-15s %-10s\n","ZFS Filesystem","Snapshots Size","Num Snapshots";
+printf "%-20s %-15s %-10s\n","--","--","--";
+foreach $key (keys %totals)
+{
+ $size = $totals{$key}{size};
+ $total_size += $size;
+ $total_snapshots += $totals{$key}{snapshots};
+ $size = &adjust_size($size);
+ printf "%-20s %-15s %-10s\n", $key, $size, $totals{$key}{snapshots};
+}
+
+$total_size = &adjust_size($total_size);
+printf "%-20s %-15s %-10s\n","--","--","--";
+printf "%-20s %-15s %-10s\n","Total Snapshots",$total_size,$total_snapshots;
+