]> git.sur5r.net Git - bacula/bacula/blob - regress/scripts/diff.pl
Backport from BEE
[bacula/bacula] / regress / scripts / diff.pl
1 #!/usr/bin/perl -w
2
3 =head1 NAME
4
5     diff.pl -- Helper to diff files (rights, acl and content)
6
7 =head2 USAGE
8
9     diff.pl -s source -d dest [-e exclude ] [--acl | --attr | --wattr]
10
11 =cut
12
13 use strict;
14 use Cwd 'chdir';
15 use File::Find;
16 no warnings 'File::Find';
17 use Digest::MD5;
18 use Getopt::Long ;
19 use Pod::Usage;
20 use Data::Dumper;
21 use Cwd;
22 use POSIX qw/strftime/;
23
24 my ($src, $dst, $help, $acl, $attr, $wattr, 
25     $dest_attrib, $src_attrib, $mtimedir);
26 my %src_attr; 
27 my %dst_attr;
28 my @exclude;
29 my $hash;
30 my $ret=0;
31 my $notop=0;
32
33 GetOptions("src=s"   => \$src,        # source directory
34            "dst=s"   => \$dst,        # dest directory
35            "acl"     => \$acl,        # acl test
36            "attr"    => \$attr,       # attributes test
37            "wattr"   => \$wattr,      # windows attributes
38            "mtime-dir" => \$mtimedir, # check mtime on directories
39            "exclude=s@" => \@exclude, # exclude some files
40            "notop"   => \$notop,      # Exclude top directory
41            "help"    => \$help,
42     ) or pod2usage(-verbose => 1, 
43                    -exitval => 1);
44 if (!$src or !$dst) {
45    pod2usage(-verbose => 1, 
46              -exitval => 1); 
47 }
48
49 if ($help) {
50     pod2usage(-verbose => 2, 
51               -exitval => 0);
52 }
53 my $md5 = Digest::MD5->new;
54
55 my $dir = getcwd;
56
57 chdir($src) or die "ERROR: Can't access to $src";
58 $hash = \%src_attr;
59
60 find(\&wanted_src, '.');
61
62 if ($wattr) {    
63     $src_attrib = `attrib /D /S`;
64     $src_attrib = strip_base($src_attrib, $src);
65 }
66
67 chdir ($dir);
68
69 chdir($dst) or die "ERROR: Can't access to $dst";
70 $hash = \%dst_attr;
71 find(\&wanted_src, '.');
72
73 if ($wattr) {    
74     $dest_attrib = `attrib /D /S`;
75     $dest_attrib = strip_base($dest_attrib, $dst);
76
77     if (lc($src_attrib) ne lc($dest_attrib)) {
78         $ret++;
79         print "diff.pl ERROR: Differences between windows attributes\n",
80               "$src_attrib\n=========\n$dest_attrib\n";
81     } 
82 }
83
84 #print Data::Dumper::Dumper(\%src_attr);
85 #print Data::Dumper::Dumper(\%dst_attr);
86
87 foreach my $f (keys %src_attr)
88 {
89     if ($notop && $f eq '.') {
90         delete $src_attr{$f};
91         delete $dst_attr{$f};
92         next;
93     }
94
95     if (!defined $dst_attr{$f}) {
96         $ret++;
97         print "diff.pl ERROR: Can't find $f in dst\n";
98
99     } else {
100         compare($src_attr{$f}, $dst_attr{$f});
101     }
102     delete $src_attr{$f};
103     delete $dst_attr{$f};
104 }
105
106 foreach my $f (keys %dst_attr)
107 {
108     $ret++;
109     print "diff.pl ERROR: Can't find $f in src\n";
110 }
111
112 if ($ret) {
113     print "diff.pl ERROR: found $ret error(s)\n";
114 }
115
116 exit $ret;
117
118 # convert \ to / and strip the path
119 sub strip_base
120 {
121     my ($data, $path) = @_;
122     $data =~ s!\\!/!sg;
123     $data =~ s!\Q$path!!sig;
124     return $data;
125 }
126
127 sub compare
128 {
129     my ($h1, $h2) = @_;
130     my ($f1, $f2) = ($h1->{file}, $h2->{file});
131     my %attr = %$h2;
132     foreach my $k (keys %$h1) {
133         if (!exists $h2->{$k}) {
134             $ret++;
135             print "diff.pl ERROR: Can't find $k for dest $f2 ($k=$h1->{$k})\n";
136         }
137         if (!defined $h2->{$k}) {
138             $ret++;
139             print "diff.pl ERROR: $k not found in destination ", $h1->{file}, "\n";
140             print Data::Dumper::Dumper($h1, $h2);
141         } elsif ($h2->{$k} ne $h1->{$k}) {
142             $ret++;
143             my ($val1, $val2) = ($h1->{$k}, $h2->{$k});
144             if ($k =~ /time/) {
145                 ($val1, $val2) = 
146                     (map { strftime('%F %T', localtime($_)) } ($val1, $val2));
147             }
148             if ($k =~ /mode/) {
149                 ($val1, $val2) = 
150                     (map { sprintf('%o', $_) } ($val1, $val2));
151             }
152             print "diff.pl ERROR: src and dst $f2 differ on $k ($val1 != $val2)\n";
153         }
154         delete $attr{$k};
155     }
156
157     foreach my $k (keys %attr) {
158         $ret++;
159         print "diff.pl ERROR: Found $k on dst file and not on src ($k=$h2->{$k})\n";
160     }
161 }
162
163 sub wanted_src
164 {
165     my $f = $_;
166     if (grep ($f, @exclude)) {
167         return;
168     }
169     if (-l $f) {
170         my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
171             $atime,$mtime,$ctime,$blksize,$blocks) = lstat($f);
172  
173         my $target = readlink($f);
174         $hash->{$File::Find::name} = {
175             nlink => $nlink,
176             uid => $uid,
177             gid => $gid,
178             mtime => 0,
179             target => $target,
180             type => 'l',
181             file => $File::Find::name,
182         };
183         return;
184     }
185
186     my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
187         $atime,$mtime,$ctime,$blksize,$blocks) = stat($f);
188     
189     if (-f $f)  {
190         $hash->{$File::Find::name} = {
191             mode => $mode,
192             nlink => $nlink,
193             uid => $uid,
194             gid => $gid,
195             size => $size,
196             mtime => $mtime,
197             type => 'f',
198             file => $File::Find::name,
199         };
200         $md5->reset;
201         open(FILE, '<', $f) or die "ERROR: Can't open '$f': $!";
202         binmode(FILE);
203         $hash->{$File::Find::name}->{md5} = $md5->addfile(*FILE)->hexdigest;
204         close(FILE);
205         
206     } elsif (-d $f) {
207         $hash->{$File::Find::name} = {
208             mode => $mode,
209             uid => $uid,
210             gid => $gid,
211             mtime => ($mtimedir)?$mtime:0,
212             type => 'd',
213             file =>  $File::Find::name,
214         };
215
216     } elsif (-b $f or -c $f) { # dev
217         $hash->{$File::Find::name} = {
218             mode => $mode,
219             uid => $uid,
220             gid => $gid,
221             mtime => $mtime,
222             rdev => $rdev,
223             type => (-b $f)?'block':'char',
224             file =>  $File::Find::name,
225         };
226         
227     } elsif (-p $f) { # named pipe
228         $hash->{$File::Find::name} = {
229             mode => $mode,
230             uid => $uid,
231             gid => $gid,
232             mtime => $mtime,
233             type => 'pipe',
234             file =>  $File::Find::name,
235         };
236         
237     } else {                # other than file and directory
238         return;
239     }
240     
241     my $fe = $f;
242     $fe =~ s/"/\\"/g;
243     if ($acl) {
244         $hash->{$File::Find::name}->{acl} = `getfacl "$fe" 2>/dev/null`;
245     }
246     if ($attr) {
247         $hash->{$File::Find::name}->{attr} = `getfattr "$fe" 2>/dev/null`;
248     }
249 }