Initial commit/revision of a number of ZFS scripts and utilities.
authorAlan J. Pippin <ajp@server.pippins.net>
Sat, 1 Mar 2008 17:31:26 +0000 (10:31 -0700)
committerAlan J. Pippin <ajp@pippins.net>
Sat, 1 Mar 2008 17:31:26 +0000 (10:31 -0700)
zfs-autosnap [new file with mode: 0755]
zfs-autosnap-all [new file with mode: 0755]
zfs-diff [new file with mode: 0755]
zfs-log-parser [new file with mode: 0755]
zfs-replicate [new file with mode: 0755]
zfs-replicate-all [new file with mode: 0755]
zfs-scrub [new file with mode: 0755]
zfs-snapshot-totals [new file with mode: 0755]

diff --git a/zfs-autosnap b/zfs-autosnap
new file mode 100755 (executable)
index 0000000..7273e57
--- /dev/null
@@ -0,0 +1,111 @@
+#!/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
diff --git a/zfs-autosnap-all b/zfs-autosnap-all
new file mode 100755 (executable)
index 0000000..5892976
--- /dev/null
@@ -0,0 +1,27 @@
+#!/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
+
diff --git a/zfs-diff b/zfs-diff
new file mode 100755 (executable)
index 0000000..44f355c
--- /dev/null
+++ b/zfs-diff
@@ -0,0 +1,150 @@
+#!/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");
+  }
+}
diff --git a/zfs-log-parser b/zfs-log-parser
new file mode 100755 (executable)
index 0000000..7149609
--- /dev/null
@@ -0,0 +1,94 @@
+#!/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);
diff --git a/zfs-replicate b/zfs-replicate
new file mode 100755 (executable)
index 0000000..f878518
--- /dev/null
@@ -0,0 +1,111 @@
+#!/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
diff --git a/zfs-replicate-all b/zfs-replicate-all
new file mode 100755 (executable)
index 0000000..4d7ff14
--- /dev/null
@@ -0,0 +1,61 @@
+#!/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
diff --git a/zfs-scrub b/zfs-scrub
new file mode 100755 (executable)
index 0000000..f022a54
--- /dev/null
+++ b/zfs-scrub
@@ -0,0 +1,31 @@
+#!/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
diff --git a/zfs-snapshot-totals b/zfs-snapshot-totals
new file mode 100755 (executable)
index 0000000..53bcd7b
--- /dev/null
@@ -0,0 +1,46 @@
+#!/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;
+