#!/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] [filename]\n\n"; print " -d Display the lines that are different (diff output)\n"; print " -h Display this usage\n"; print " -m Use md5sum when checking for file differences"; 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}, "m!" => \$options{md5sum}, "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; if(($1 eq "") || ($2 eq "")) { die "-E- Invalid snapshot name\n"; } 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 ); print "-> Finding files in $fspath to compare against files in $snappath\n"; `find $fspath -name "*" -type f > $filename`; print "-> Performing a diff operation on each file found\n"; my $num_files = `cat $filename | wc | awk '{print \$1}'`; 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; } if($options{md5sum}) { # 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; } } else { my $differ = system("diff \"$file\" \"$diff\" > /dev/null 2>&1"); if($differ) { print "** $file is different\n"; } } 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; } if($options{md5sum}) { 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; } } else { my $differ = system("diff \"$fspath\" \"$snappath\" > /dev/null 2>&1"); if($differ) { print "** Files are different\n"; } } if ($options{diff}) { system("diff \"$fspath\" \"$snappath\""); } }