Added ability to deal with negative data values.
[zfs-nexenta/.git] / zfs-diff
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5 use File::Temp qw/ tempfile tempdir /;
6
7 my $SSLPATH = '/usr/bin/openssl';
8
9 #Usage
10 sub print_usage {
11   print "ZFS Snapshot diff\n";
12   print "\t$0 [-dhirv] <zfs shapshot name> [filename]\n\n";
13   print " -d  Display the lines that are different (diff output)\n";
14   print " -h  Display this usage\n";
15   print " -i  Ignore files that don't exist in the snapshot (only necessary for recursing)\n";
16   print " -r  Recursively diff every file in the snapshot (filename not required)\n";
17   print " -v  Verbose mode\n\n";
18
19   print " [filename] is the filename RELATIVE to the ZFS snapshot root. For example, if\n";
20   print " I had a filesystem snapshot called pool/data/zone\@initial. The filename '/etc/passwd'\n";
21   print " would refer to the filename /pool/data/zone/etc/passwd in the filesystem and filename\n";
22   print " /pool/data/zone/.zfs/snapshot/initial/etc/passwd in the snapshot.\n\n";
23
24   print " A couple of examples:\n";
25   print "\t$0 -v -r -i pool/zones/lava2019\@Fri\n";
26   print "\t\tChecks the current pool/zones/lava2019 filesystem against the snapshot\n";
27   print "\t\treturning the md5sum difference of any files (ignore files that don't\n";
28   print "\t\texist in the snapshot). With verbose mode\n\n";
29
30   print "\t$0 -d pool/zones/lava2019\@Mon /root/etc/passwd\n";
31   print "\t\tCheck the md5sum for /pool/zones/lava2019/root/etc/passwd and compare\n";
32   print "\t\tit to /pool/zones/lava2019/.zfs/snapshot/Mon/root/etc/passwd. Display\n";
33   print "\t\tthe lines that are different also.\n\n";
34
35   exit(0);
36 }
37
38 use Getopt::Long;
39 my %options = ();
40 my $verbose;
41
42 GetOptions("h" => \$options{help},
43            "r!" => \$options{recurse},
44            "d!" => \$options{diff},
45            "i!" => \$options{ignore},
46            "v!" => \$verbose
47            );
48
49 if ($options{help}) {
50   print_usage();
51 }
52
53 if ($options{recurse}) {
54   recurse_diff(shift || die "Need a ZFS snapshot name\n");
55 } else {
56   my $zfsname = shift || die "Need a ZFS snapshot name\n";
57   my $file = shift || die "Need a filename\n";
58   diff_single_file($zfsname,$file);
59 }
60
61 exit(0);
62
63 sub recurse_diff {
64   my $zfssnap = shift;
65   print "Recursive diff on $zfssnap\n" if $verbose;
66
67   $zfssnap =~ /(.+)\@(.+)/i;
68   my $fsname = "/" . $1;
69   my $snapname = $2;
70   if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; }
71   elsif(! -d $fsname && $fsname =~ /\/\S+?/) { $fsname = "/"; }
72   print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose;
73
74   my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/";
75   my $fspath = $fsname . "/";
76   $fspath =~ s/\/\//\//gi;
77   $snappath =~ s/\/\//\//gi;
78   print "Comparing: $fspath\nto: $snappath\n" if $verbose;
79
80   my $dir = tempdir( CLEANUP => 0 );
81   my ($fh, $filename) = tempfile( DIR => $dir );
82
83   `find $fspath -name "*" -type f > $filename`;
84
85   foreach my $file (<$fh>) {
86     chomp($file);
87     $file =~ /(.*)\/(.*)/;
88     my $shortname = $2;
89     $file =~ /$fspath(.*)/;
90     my $diff = $snappath . $1;
91     if (!-e $diff) {
92       print "$file does not exist in snapshot\n" if !$options{ignore};
93       next;
94     }
95
96     # do the md5 sums
97     my $orig = `$SSLPATH md5 $file`;
98     my $snap = `$SSLPATH md5 $diff`;
99     $orig =~ /[\s\S]+= (.+)/;
100     my $sum1 = $1;
101     $snap =~ /[\s\S]+= (.+)/;
102     my $sum2 = $1;
103     if ($sum1 ne $sum2) {
104       print "** $file is different\n";
105       print "** $orig** $snap" if $verbose;
106     }
107     if ($options{diff}) {
108       system("diff $file $diff");
109     }
110   }
111 }
112
113 sub diff_single_file {
114   my $zfssnap = shift;
115   my $filename = shift;
116   print "Single-file diff on $zfssnap, file: $filename\n" if $verbose;
117
118   $zfssnap =~ /(.+)\@(.+)/i;
119   my $fsname = "/" . $1 . "/";
120   my $snapname = $2;
121   if(! -d $fsname && $fsname =~ /\/\S+?(\/.*)/) { $fsname = $1; }
122   print "Filesystem: $fsname, Snapshot: $snapname\n" if $verbose;
123
124   my $fspath;
125   if($filename !~ /^\//) { $fspath = $ENV{'PWD'} . "/" . $filename; }
126   else { $fspath = $filename; }
127
128   my $snapfspath = $fspath;
129   $snapfspath =~ s/$fsname//g;
130   my $snappath = $fsname . "/.zfs/snapshot/" . $snapname . "/" . $snapfspath;
131   $fspath =~ s/\/\//\//gi;
132   $snappath =~ s/\/\//\//gi;
133   print "Comparing: $fspath\nto: $snappath\n" if $verbose;
134   if(! -f $fspath) { print "-E- Cannot find source file: $fspath\n"; exit 1; }
135   if(! -f $snappath) { print "-E- Cannot find source file: $snappath\n"; exit 1; }
136
137   my $orig = `$SSLPATH md5 $fspath`;
138   my $snap = `$SSLPATH md5 $snappath`;
139   $orig =~ /[\s\S]+= (.+)/;
140   my $sum1 = $1;
141   $snap =~ /[\s\S]+= (.+)/;
142   my $sum2 = $1;
143   if ($sum1 ne $sum2) {
144     print "** Files are different\n";
145     print "** $orig** $snap" if $verbose;
146   }
147   if ($options{diff}) {
148     system("diff $fspath $snappath");
149   }
150 }