Whamcloud - gitweb
Branch: HEAD
[fs/lustre-release.git] / build / kabi
1 #! /usr/bin/perl
2 # kabi - Linux Kernel Application Binary Interface manager
3 # Copyright (C) 2005  Cluster File Systems, Inc.
4 # All rights reserved.
5 #
6 # Gordon Matzigkeit <gord@clusterfs.com>, 2005-10-21
7
8 use warnings;
9 use strict;
10
11 my $VERSION = '0.2';
12
13 my $CC = $ENV{'CC'} || 'gcc';
14 my $LINUX = '/usr/src/linux';
15 my $MODE;
16 my $OUTPUT;
17 my @ARGS;
18 my $VERBOSE = 0;
19
20 my $progname = $0;
21 $progname =~ s/^.*\///;
22 my $modename = $progname;
23
24 sub usage
25 {
26     my ($status) = @_;
27     if ($status) {
28         print STDERR "Try \`$0 --help' for more information\n";
29     } else {
30         print <<EOF;
31 Usage: [CC=COMPILER] $0 [OPTION]... MODE ARGS...
32
33 Manage binary compatibility between a Linux kernel and kernel modules.
34
35 The CC environment variable specifies the compiler used to build the
36 kernel and modules.
37
38     --help            display this message and exit
39 -o, --output=KABI     specify the name of the KABI file created by the
40                       \`module\' mode [default=strip .ko and add .kabi]
41 -v, --verbose         give reasons for rejecting KABI matches
42     --version         print version information
43     --with-linux=DIR  set the path to the kernel sources
44
45 MODE and ARGS can be one of the following:
46
47     archive DIR KMOD  install a KABI and kernel module in a unique place in DIR
48     match FILE...     print a list of KABI files which are compatible with
49                       the specified kernel and any specified kernel modules
50
51     module KMOD       generate a KABI file for the specified kernel module
52
53 Written by Gordon Matzigkeit <gord\@clusterfs.com> for Cluster File Systems.
54 EOF
55     }
56     exit $status;
57 }
58
59 my @args = @ARGV;
60 while ($#args >= 0) {
61     if ($args[0] =~ /^--with-l(i(n(u(x)?)?)?)?=(.*)/) {
62         $LINUX = $5;
63     } elsif ($args[0] =~ /^--with-l(i(n(u(x)?)?)?)?$/) {
64         shift @args;
65         $LINUX = $args[0];
66     } elsif ($args[0] =~ /^--h(e(l(p)?)?)?$/) {
67         usage(0);
68     } elsif ($args[0] =~ /^--vers(i(o(n)?)?)?$/) {
69         print "KABI $VERSION\n";
70         exit 0;
71     } elsif ($args[0] eq '-v' || $args[0] =~ /^--verb(o(s(e)?)?)?$/) {
72         $VERBOSE = 1;
73     } elsif ($args[0] =~ /^-/) {
74         print STDERR "$progname: unrecognized option \`$args[0]'\n";
75         usage(1);
76     } elsif (!defined $MODE) {
77         $MODE = $args[0];
78     } else {
79         push @ARGS, $args[0];
80     }
81     shift @args;
82 }
83
84
85 if (!defined $MODE) {
86     print STDERR "$progname: you must specify a MODE\n";
87     usage(1);
88 }
89
90 $modename .= ": $MODE";
91 if ($MODE eq 'archive') {
92     if ($#ARGS != 1) {
93         print STDERR "$modename: you must specify a DIR and KMOD\n";
94         usage(1);
95     }
96
97     my $ARCHIVE = $ARGS[0];
98     my $KMOD = $ARGS[1];
99
100     my $KABI = $KMOD;
101     $KABI =~ s/\.k?o$//;
102     $KABI .= '.kabi';
103
104     open(MD5SUM, "md5sum $KABI|") or
105         die "$modename: cannot execute \`md5sum': $!\n";
106     my $hash = <MD5SUM>;
107     close(MD5SUM);
108     $hash =~ s/\s+.*//s;
109
110     my $TAG = '';
111     if (-d 'CVS') {
112         open(TAG, '<CVS/Tag') or
113             die "$modename: cannot read \`CVS/Tag': $!\n";
114         $TAG = <TAG>;
115         close(TAG);
116         chomp $TAG;
117         $TAG = "/$TAG";
118     }
119
120     my ($dir, @sh_c, @cp);
121     if ($ARCHIVE =~ /^([^:][^:]+):(.*)$/) {
122         $dir = $2;
123         @sh_c = ('ssh', '-o', 'BatchMode=yes', $1);
124         @cp = ('scp', '-B');
125     } else {
126         $dir = $ARCHIVE;
127         @sh_c = ('sh', '-c');
128         @cp = ('cp');
129     }
130
131     system(@sh_c, "test -d $dir");
132     if ($? >> 8 != 0) {
133         print STDERR "$modename: warning: \`$dir' is not reachable or does not exist\n";
134         exit 0;
135     }
136
137     print "archiving $KMOD in $ARCHIVE$TAG/$KMOD/$hash\n"
138         if $VERBOSE;
139     foreach my $d ("$dir$TAG", "$dir$TAG/$KMOD", "$dir$TAG/$KMOD/$hash") {
140         system(@sh_c, "test -d $d || mkdir $d");
141         if ($? >> 8 != 0) {
142             exit $? >> 8;
143         }
144     }
145     system(@cp, $KMOD, $KABI, "$ARCHIVE$TAG/$KMOD/$hash");
146     exit $? >> 8;
147 } elsif ($MODE eq 'module') {
148     
149     if ($#ARGS != 0) {
150         print STDERR "$modename: you must specify exactly one KMOD\n";
151         usage(1);
152     }
153
154     my $KMOD = $ARGS[0];
155
156     if (!defined $OUTPUT) {
157         $OUTPUT = $KMOD;
158         $OUTPUT =~ s/\.k?o$//;
159         $OUTPUT .= '.kabi';
160     }
161     print "create $OUTPUT\n" if $VERBOSE;
162     open(OUT, ">$OUTPUT") or
163         die "$modename: cannot create \`$OUTPUT': $!\n";
164
165     my $outname = $OUTPUT;
166     $outname =~ s/^.*\///;
167     print OUT <<EOF;
168 # $outname - Kernel module ABI descriptor file
169 # DO NOT EDIT - Automatically generated by $progname $VERSION
170 EOF
171
172     # Get the kernel version.
173     print OUT "kver " . kernel_version() . "\n";
174
175     # Gather the undefined symbols with version numbers from the
176     # kernel module.
177     my %vers;
178     my @undefs;
179
180     # Gather the version numbers, if any.
181     my $modfile = $KMOD;
182     if ($modfile =~ s/\.ko$/.mod.c/) {
183         open(MOD, "<$modfile") or
184             die "$modename: cannot read \`$modfile': $!\n";
185         my $versions = 0;
186         while ($_ = <MOD>) {
187             if (/\"__versions\"/) {
188                 $versions = 1;
189             } elsif ($versions) {
190                 if (/^\s*\{\s*(0x[0-9a-f]+)\s*,\s*\"([^\"]*)\"\s*\}\s*,\s*$/) {
191                     $vers{$2} = $1;
192                     push(@undefs, $2);
193                 } elsif (/^\s*\}\s*;\s*$/) {
194                     $versions = 0;
195                 }
196             }
197         }
198         close(MOD);
199     } else {
200         open(NM, "nm $KMOD |") or
201             die "$modename: cannot execute \`nm $KMOD': $!\n";
202         while ($_ = <NM>) {
203             if (/^\s*U\s*(.*\S)\s*$/) {
204                 push @undefs, $1;
205             }
206         }
207         close(NM);
208     }
209
210     foreach my $undef (sort @undefs)
211     {
212         print OUT "usym $undef";
213         if (defined $vers{$undef}) {
214             print OUT " ", $vers{$undef};
215         }
216         print OUT "\n";
217     }
218
219     close(OUT) or
220         die "$modename: cannot write \`$OUTPUT': $!\n";
221
222 } elsif ($MODE eq 'match') {
223     my @KABIS;
224     my @KMODS;
225
226     my @todo = @ARGS;
227     while ($#todo >= 0) {
228         my $t = shift @todo;
229         if ($t =~ /\.kabi$/) {
230             push @KABIS, $t;
231         } elsif (-d $t) {
232             # Add all the contents of the directory to our todo list.
233             opendir(DIR, $t);
234             while (my $ent = readdir(DIR)) {
235                 if ($ent =~ /^\./) {
236                     # Skip dotfiles.
237                 } elsif (-d "$t/$ent") {
238                     # Recurse into subdirectories.
239                     unshift @todo, "$t/$ent";
240                 } elsif ($ent =~ /\.k?o$/) {
241                     # Add kernel modules.
242                     unshift @todo, "$t/$ent";
243                 }
244             }
245             closedir(DIR);
246         } else {
247             # It's an explicit kernel module.
248             push @KMODS, $t;
249         }
250     }
251
252     if ($#KABIS < 0) {
253         print STDERR "$modename: you must specify at least one KABI\n";
254         usage(1);
255     }
256
257     my %dsyms;
258
259     if (-f "$LINUX/Module.symvers") {
260         # Look up the version numbers in Module.symvers.
261         open(VERS, "<$LINUX/Module.symvers") or
262             die "$modename: cannot read \`$LINUX/Module.symvers': $!\n";
263         while ($_ = <VERS>) {
264             if (/^(0x[0-9a-f]+)\s+(\S+)/) {
265                 $dsyms{$2} = hex($1);
266             }
267         }
268         close(VERS);
269     } else {
270         # Read in all the non-versioned symbols defined by this kernel.
271         open(MAP, "<$LINUX/System.map") or
272             die "$modename: cannot read \`$LINUX/System.map': $!\n";
273         while ($_ = <MAP>) {
274             if (/^[0-9a-fA-F]*\s+[ABCDGIRSTW]+\s*(.*\S)\s*$/) {
275                 $dsyms{$1} = 0;
276             }
277         }
278         close(MAP);
279     }
280
281     # Find the symbols for the installed modules, too.
282     foreach my $mod (@KMODS) {
283         open(NM, "nm $mod |") or
284             die "$modename: cannot execute \`nm $mod': $!\n";
285         while ($_ = <NM>) {
286             if (/^[0-9a-fA-F]*\s+[ABCDGIRSTW]+\s*(.*\S)\s*$/) {
287                 $dsyms{$1} = 0;
288             }
289         }
290         close(NM);
291     }
292
293     # Also get the kernel version.
294     my $kver = kernel_version();
295
296     # Read each kabi file and print out the ones that are plausible
297     # matches.
298     foreach my $kabi (@KABIS) {
299         open(KABI, "<$kabi") or
300             die "$modename: cannot read \`$kabi': $!\n";
301         my $possible = 1;
302         while ($possible && ($_ = <KABI>)) {
303             if (/^\s*#/) {
304                 # Skip comments.
305             } elsif (/^\s*kver\s+(.*\S)\s*$/) {
306                 my $modkver = $1;
307                 if ($modkver ne $kver) {
308                     print STDERR "$kabi:$.: module version \`$modkver' differs from \`$kver'\n"
309                         if $VERBOSE;
310                     $possible = 0;
311                 }
312             } elsif (/^\s*usym\s+(\S+)\s*(\S+)?\s*$/) {
313                 my ($modsym, $symver) = ($1, hex($2));
314                 if (!defined $dsyms{$modsym}) {
315                     print STDERR "$kabi:$.: module symbol \`$modsym' is not defined\n"
316                         if $VERBOSE;
317                     $possible = 0;
318                 } elsif (defined $symver && $dsyms{$modsym} != 0 && $dsyms{$modsym} != $symver) {
319                     printf STDERR "$kabi:$.: module symbol \`$modsym' is version 0x%x, not 0x%x\n", $dsyms{$modsym}, $symver
320                         if $VERBOSE;
321                     $possible = 0;
322                 }
323             } elsif (/^\s*(\S+)/) {
324                 print STDERR "$kabi:$.: unrecognized descriptor line \`$1'\n";
325             }
326         }
327         close(KABI);
328         
329         if ($possible) {
330             # We got a match.
331             print "$kabi\n";
332         }
333     }
334
335 } else {
336     print STDERR "$progname: unrecognized mode \`$MODE'\n";
337     usage(1);
338 }
339
340
341 # Read the kernel version from its built source tree.
342 sub kernel_version
343 {
344     my $verfile = "$LINUX/include/linux/version.h";
345     open(VERSION, "<$verfile") or
346         die "$modename: cannot read \`$verfile': $!\n";
347
348     my $ver;
349     while ($_ = <VERSION>) {
350         if (/^\s*#\s*define\s+UTS_RELEASE\s+"(.*)"\s*$/) {
351             $ver = $1;
352             last;
353         }
354     }
355
356     close(VERSION);
357
358     if (!defined $ver) {
359         die "$modename: cannot find UTS_RELEASE in \`$verfile'\n";
360     }
361     return "linux-$ver";
362 }
363
364 exit 0;