7a9fdf0decc458da7416224be38cc458955969c2
[xfstests-dev.git] / tools / srcdiff
1 #!/usr/bin/perl -w
2 use strict;
3 #
4 # srcdiff is used to compare current user level code with the current
5 # kernel code and advise of any differences between files which are
6 # sharing some or all of their content.
7 #
8 # There are two classes of sharing which we will check - header files
9 # in the include directory, which must be exactly the same (use diff)
10 # and source files which contain routines which must be exactly the
11 # same (but the userland file is always a subset of the kernel file,
12 # and hence a more flexible mechanism to "diff" is required).
13 #
14 # NB: to cross check that srcdiff is finding all the functions in the
15 #     user source file, providing you have "mkproto" installed, you
16 #     can "cd xfsprogs/libxfs" and cut&paste this in a bourne shell:
17 #     $ for file in xfs_*.c; do
18 #     > mkproto -nps < $file | perl -ne '
19 #     > END { print "    $count\t- " }
20 #     > s/^.* (xfs\w+|\*xfs\w+|xlog\w+|\*xlog\w+) \(.*/\1/ && { $count++ }'
21 #     > echo $file
22 #     > done
23 # (compare this to "srcdiff | fgrep Total:")
24 #
25
26 die "WORKAREA not set" unless defined $ENV{'WORKAREA'};
27 die "KWORKAREA not set" unless defined $ENV{'KWORKAREA'};
28 die "DMWORKAREA not set" unless defined $ENV{'DMWORKAREA'};
29 chdir $ENV{'WORKAREA'};
30 my $dmworkarea = $ENV{'DMWORKAREA'};
31 my $kworkarea = $ENV{'KWORKAREA'};
32 my $xdiff = $ENV{'XDIFF'};
33 my $quiet = 0;
34 my $usage = 0;
35
36 if ( ! -f "$kworkarea/xfs_fs.h" ) {
37         $kworkarea .= '/fs/xfs';
38         die "Cannot find XFS in KWORKAREA" unless -f "$kworkarea/xfs_fs.h";
39 }
40 die "Cannot find DMAPI in DMWORKAREA" unless -f "$dmworkarea/dmapi.h";
41
42 foreach (@ARGV) {
43         if (/^-q$/) {
44                 $quiet++;
45         } else {
46                 print STDERR "Illegal option $_\n";
47                 $usage++;
48         }
49 }
50
51 if ($usage) {
52     print STDERR "Usage: $0 [-q]\n";
53     exit 1;
54 }
55
56 my @pkglist = qw( xfstests attr acl dmapi xfsdump xfsprogs );
57 my @difflist = qw(
58         xfs_ag.h  xfs_alloc.h  xfs_alloc_btree.h xfs_arch.h
59         xfs_attr_leaf.h  xfs_attr_sf.h  xfs_bit.h  xfs_bmap.h
60         xfs_bmap_btree.h  xfs_btree.h  xfs_buf_item.h
61         xfs_da_btree.h  xfs_dfrag.h  xfs_dinode.h
62         xfs_dir2.h  xfs_dir2_block.h  xfs_dir2_data.h
63         xfs_dir2_leaf.h  xfs_dir2_node.h  xfs_dir2_sf.h
64         xfs_extfree_item.h  xfs_ialloc.h
65         xfs_imap.h  xfs_ialloc_btree.h  xfs_inode.h  xfs_inode_item.h
66         xfs_inum.h  xfs_log.h  xfs_log_priv.h  xfs_log_recover.h
67         xfs_mount.h  xfs_quota.h  xfs_rtalloc.h
68         xfs_sb.h  xfs_trans.h  xfs_trans_space.h  xfs_types.h  xfs_fs.h
69 );
70
71 sub straightdiff {
72         my ( $file, $prefix1, $prefix2 ) = @_;
73
74         `diff $prefix1/$file $prefix2/$file >/dev/null 2>&1`;
75         if (!$quiet) {
76                 print sprintf("\t%-35s ... ", $file);
77                 if ($? != 0)    { printf("FAILED\n(%s/%s differs to %s/%s)\n",
78                                          $prefix1, $file, $prefix2, $file); }
79                 else            { print "ok\n"; }
80         } elsif ($? != 0) {
81                 printf("\t%-35s ... FAILED\n(%s/%s differs to %s/%s)\n",
82                         $file, $prefix1, $file, $prefix2, $file);
83                 if (defined($xdiff)) {
84                         `$xdiff $prefix1/$file $prefix2/$file`;
85                 }
86         }
87 }
88
89 #
90 # xfstests directory m4 directory is a repository of all of the
91 # custom m4 macros used in the packages we look after.
92 #
93 sub m4macrodiff {
94         my ( $package ) = @_;
95
96         foreach (`ls $package/m4/*.m4`) {
97                 my $m4 = `basename $_`;
98                 chomp($m4);
99                 straightdiff $m4, "$package/m4", "xfstests/m4";
100         }
101 }
102
103 my $first = shift @pkglist;
104 foreach (@pkglist) {
105         print "\n=== Checking $_ package ===\n";
106         m4macrodiff $_;
107         straightdiff 'buildrules', "$first/include", "$_/include";
108         straightdiff 'buildmacros', "$first/include", "$_/include";
109         straightdiff 'Makefile', "$first/build", "$_/build";
110         straightdiff 'Makefile', "$first/build/rpm", "$_/build/rpm";
111         straightdiff 'Makefile', "$first/build/tar", "$_/build/tar";
112 }
113 print "\n=== Checking headers ===\n";
114 foreach (@difflist) {
115         straightdiff $_, 'xfsprogs/include', "$kworkarea";
116 }
117 straightdiff 'dmapi_kern.h', 'dmapi/include', "$dmworkarea";
118 straightdiff 'dmapi.h', 'dmapi/include', "$dmworkarea";
119
120 #
121 # setstate
122 # Implements a tri-state FSA, see comments for state transitions
123 #  (knows about the way the XFS kernel code is written, & makes
124 #   some assumptions so as to not need to parse generic C code).
125 # Accepts one line at a time from a source file, picking out the
126 #   function bodies so they can be subsequently compared.
127 #
128
129 my $line;       # line number in current source file
130 my $state;      # current FSA state
131 my $funcbody;   # current function body (contents)
132
133 sub setstate {
134         my ( $newline ) = @_;
135         $line++;
136
137         # - state 0:
138         #       if line looks like start of a function, transition to 1
139         #               & squirrel line away as line 1 of current function
140         if ($state == 0) {
141                 if ($newline =~ m/^[xfs|xlog]/) {
142                         $state = 1;
143                         $funcbody = $newline;
144                 }
145         }
146
147         # - state 1:
148         #       if line looks like start of a function, stay here
149         #               & squirrel line away as line 1 of current function
150         #       otherwise if line isn't start of function body,
151         #               squirrel line away as next line of current function
152         #               (args/..., but not sure this is a real function yet)
153         #       otherwise (start of function)
154         #               squirrel line away as next line of current function
155         #               transition to state 2
156         elsif ($state == 1) {
157                 if ($newline =~ m/^[xfs|xlog]/) {
158                         $funcbody = $newline;
159                 }
160                 elsif ($newline =~ m/^\{/) {
161                         $state = 2;
162                         $funcbody .= $newline;
163                 }
164         }
165
166         # - state 2:
167         #       if line looks like end of function body,
168         #               squirrel line away as last line of current function
169         #               tell someone we have a complete function ready
170         #               transition to state 0
171         #       otherwise
172         #               squirrel line away as next line of current function
173         elsif ($state == 2) {
174                 $funcbody .= $newline;
175                 if ($newline =~ m/^\}/) {
176                         $state = 0;
177                         return $funcbody;
178                 }
179         }
180
181         else {
182                 die "unknown state transition";
183         }
184         return undef;   # i.e. not at end of a function
185 }
186
187 sub listfuncs {
188         my ( $file ) = @_;
189         my @funcs;
190
191         $funcbody = '';
192         $state = $line = 0;
193
194         open(USER, "$file") || die "cannot open $file";
195         while (<USER>) {
196                 my $func = setstate($_);
197                 push @funcs, $func if (defined($func)); # store function away
198         }
199         close USER;
200         return @funcs;
201 }
202
203 sub hashfuncs {
204         my ( $file ) = @_;
205         my %funcs;
206
207         $funcbody = '';
208         $state = $line = 0;
209
210         open(KERN, "$file") || die "cannot open $file";
211         while (<KERN>) {
212                 my $func = setstate($_);
213                 if (defined($func)) {
214                         $func =~ m/^([xfs|xlog]\w+)\s*\(/;
215                         next unless defined($1);
216                         my $name = $1;
217                         if (defined($func)) {
218                                 $funcs{$name} = $func;  # store function away
219                         }
220                 }
221         }
222         close KERN;
223         return %funcs;
224 }
225
226 sub diffme {
227         my ( $sa, $sb ) = @_;
228
229         return unless defined($xdiff);
230
231         open(FILEA, "> /tmp/diff.user.$$") || die "cannot write to /tmp/diff.user.$$";
232         open(FILEB, "> /tmp/diff.kern.$$") || die "cannot write to /tmp/diff.kern.$$";
233         print FILEA $sa;
234         print FILEB $sb;
235         close FILEA;
236         close FILEB;
237         `$xdiff /tmp/diff.user.$$ /tmp/diff.kern.$$`;
238         unlink ("/tmp/diff.user.$$","/tmp/diff.kern.$$");
239 }
240
241 sub functiondiff {
242         my ( $file, $prefix1, $prefix2 ) = @_;
243         my $plural = '';
244         my $count = 0;
245         my $name;
246         my $found = 0;
247
248         print "\n=== Checking $file routines ===\n" unless ($quiet);
249
250         # iterate over user funcs, match up to kernel funcs
251         #
252         my @user = listfuncs "$prefix1/$file";
253         my %kern = hashfuncs "$prefix2/$file";
254
255         foreach my $userfunc (@user) {
256
257                 $userfunc =~ m/^([xfs|xlog]\w+)\s*\(/;
258                 next unless (defined($1));
259                 $name = $1;
260                 $count++;
261
262                 if (exists($kern{$name})) {
263                         if ($userfunc ne $kern{$name}) {
264                                 print "\n=== $file routines ===\n"
265                                     if (!$found++ && $quiet);
266
267                                 printf("\t%-35s ... ", $name);
268                                 print "FAILED\n";
269                                 diffme $userfunc, $kern{$name};
270                         }
271                         elsif (!$quiet) {
272                                 printf("\t%-35s ... ", $name);
273                                 print "ok\n";
274                         }
275                 }
276                 else {
277                         print "Cannot find kernel function $userfunc";
278                         print " in file $prefix2/$file\n";
279                 }
280         }
281         ($count != 1) && ( $plural = 's' );
282         print "( Total: $count routine$plural checked in $file )\n" unless ($quiet);
283 }
284
285 # xfsprogs/{libxfs,libxlog}/* fs/xfs/*
286 my @funclist = qw(
287         xfs_alloc.c  xfs_alloc_btree.c  xfs_attr.c  xfs_attr_leaf.c
288         xfs_bmap.c  xfs_bmap_btree.c  xfs_btree.c  xfs_da_btree.c
289         xfs_dir2.c  xfs_dir2_block.c  xfs_dir2_data.c xfs_dir2_leaf.c
290         xfs_dir2_node.c  xfs_dir2_sf.c xfs_ialloc.c  xfs_ialloc_btree.c
291         xfs_inode.c  xfs_mount.c  xfs_rtalloc.c xfs_trans.c
292 );
293
294 print "\n=== Checking libxfs code ===\n";
295 foreach (@funclist) {
296         functiondiff $_, 'xfsprogs/libxfs', "$kworkarea";
297 }
298 print "\n=== Checking libxlog code ===\n";
299 functiondiff 'xfs_log_recover.c', 'xfsprogs/libxlog', "$kworkarea";