3 # Copyright (C) 2000-2015 Kern Sibbald
4 # License: BSD 2-Clause; see file LICENSE-FOSS
9 diff.pl -- Helper to diff files (rights, acl and content)
13 diff.pl -s source -d dest [-e exclude ] [--acl | --attr | --wattr]
20 no warnings 'File::Find';
26 use POSIX qw/strftime/;
28 my ($src, $dst, $help, $acl, $attr, $wattr,
29 $dest_attrib, $src_attrib, $mtimedir);
37 GetOptions("src=s" => \$src, # source directory
38 "dst=s" => \$dst, # dest directory
39 "acl" => \$acl, # acl test
40 "attr" => \$attr, # attributes test
41 "wattr" => \$wattr, # windows attributes
42 "mtime-dir" => \$mtimedir, # check mtime on directories
43 "exclude=s@" => \@exclude, # exclude some files
44 "notop" => \$notop, # Exclude top directory
46 ) or pod2usage(-verbose => 1,
49 pod2usage(-verbose => 1,
54 pod2usage(-verbose => 2,
57 my $md5 = Digest::MD5->new;
61 chdir($src) or die "ERROR: Can't access to $src";
64 find(\&wanted_src, '.');
67 $src_attrib = `attrib /D /S`;
68 $src_attrib = strip_base($src_attrib, $src);
73 chdir($dst) or die "ERROR: Can't access to $dst";
75 find(\&wanted_src, '.');
78 $dest_attrib = `attrib /D /S`;
79 $dest_attrib = strip_base($dest_attrib, $dst);
81 if (lc($src_attrib) ne lc($dest_attrib)) {
83 print "diff.pl ERROR: Differences between windows attributes\n",
84 "$src_attrib\n=========\n$dest_attrib\n";
88 #print Data::Dumper::Dumper(\%src_attr);
89 #print Data::Dumper::Dumper(\%dst_attr);
91 foreach my $f (keys %src_attr)
93 if ($notop && $f eq '.') {
99 if (!defined $dst_attr{$f}) {
101 print "diff.pl ERROR: Can't find $f in dst\n";
104 compare($src_attr{$f}, $dst_attr{$f});
106 delete $src_attr{$f};
107 delete $dst_attr{$f};
110 foreach my $f (keys %dst_attr)
113 print "diff.pl ERROR: Can't find $f in src\n";
117 print "diff.pl ERROR: found $ret error(s)\n";
122 # convert \ to / and strip the path
125 my ($data, $path) = @_;
127 $data =~ s!\Q$path!!sig;
134 my ($f1, $f2) = ($h1->{file}, $h2->{file});
136 foreach my $k (keys %$h1) {
137 if (!exists $h2->{$k}) {
139 print "diff.pl ERROR: Can't find $k for dest $f2 ($k=$h1->{$k})\n";
141 if (!defined $h2->{$k}) {
143 print "diff.pl ERROR: $k not found in destination ", $h1->{file}, "\n";
144 print Data::Dumper::Dumper($h1, $h2);
145 } elsif ($h2->{$k} ne $h1->{$k}) {
147 my ($val1, $val2) = ($h1->{$k}, $h2->{$k});
150 (map { strftime('%F %T', localtime($_)) } ($val1, $val2));
154 (map { sprintf('%o', $_) } ($val1, $val2));
156 print "diff.pl ERROR: src and dst $f2 differ on $k ($val1 != $val2)\n";
161 foreach my $k (keys %attr) {
163 print "diff.pl ERROR: Found $k on dst file and not on src ($k=$h2->{$k})\n";
170 if (grep ($f, @exclude)) {
174 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
175 $atime,$mtime,$ctime,$blksize,$blocks) = lstat($f);
177 my $target = readlink($f);
178 $hash->{$File::Find::name} = {
185 file => $File::Find::name,
190 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
191 $atime,$mtime,$ctime,$blksize,$blocks) = stat($f);
194 $hash->{$File::Find::name} = {
202 file => $File::Find::name,
205 open(FILE, '<', $f) or die "ERROR: Can't open '$f': $!";
207 $hash->{$File::Find::name}->{md5} = $md5->addfile(*FILE)->hexdigest;
211 $hash->{$File::Find::name} = {
215 mtime => ($mtimedir)?$mtime:0,
217 file => $File::Find::name,
220 } elsif (-b $f or -c $f) { # dev
221 $hash->{$File::Find::name} = {
227 type => (-b $f)?'block':'char',
228 file => $File::Find::name,
231 } elsif (-p $f) { # named pipe
232 $hash->{$File::Find::name} = {
238 file => $File::Find::name,
241 } else { # other than file and directory
248 $hash->{$File::Find::name}->{acl} = `getfacl "$fe" 2>/dev/null`;
251 $hash->{$File::Find::name}->{attr} = `getfattr "$fe" 2>/dev/null`;