+#!/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");
+ }
+}