| |
| use strict; |
| use warnings; |
| |
| use Getopt::Long; |
| use Data::Dumper; |
| |
| |
| my @rules; |
| my ($opt_verbose, $opt_rules); |
| |
| |
| my $use_namespaces; |
| |
| sub load_rules { |
| my $file = shift; |
| my $errors = 0; |
| |
| xopen(my $fh, '<', $file); |
| while (<$fh>) { |
| chomp; |
| s/#.*//; |
| next if /^\s*$/; |
| my ($pattern, $verdict) = split(/\s+/); |
| my $new = {}; |
| if (uc($verdict) eq "PASS") { |
| $new->{fail} = 0; |
| } elsif (uc($verdict) eq "FAIL") { |
| $new->{fail} = 1; |
| } else { |
| print STDERR "$file:$.: invalid verdict \"$verdict\", must be either PASS or FAIL.\n"; |
| $errors++; |
| next; |
| } |
| |
| $pattern =~ s/\*/.*/g; |
| $pattern =~ s/\?/./g; |
| $pattern =~ s/.*/^$&\$/; |
| |
| |
| if ($pattern =~ /\/|^vmlinux$/) { |
| $new->{mod} = $pattern; |
| |
| } elsif ($use_namespaces && |
| $pattern !~ /\// && $pattern eq uc($pattern)) { |
| $new->{namespace} = $pattern; |
| } else { |
| $new->{sym} = $pattern; |
| } |
| push(@rules, $new); |
| } |
| if ($errors && !@rules) { |
| print STDERR "error: only garbage found in $file.\n"; |
| exit 1; |
| } |
| close($fh); |
| } |
| |
| |
| sub symvers_uses_namespaces { |
| my $file = shift; |
| xopen(my $fh, '<', $file); |
| my $line = <$fh>; |
| chomp $line; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| my $num_tabs = $line =~ tr/\t//; |
| if ($num_tabs > 3) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| sub load_symvers { |
| my $file = shift; |
| my %res; |
| my $errors = 0; |
| my $new; |
| |
| xopen(my $fh, '<', $file); |
| while (<$fh>) { |
| chomp; |
| my @l = split(/\t/, $_, -1); |
| if (@l < 4) { |
| print STDERR "$file:$.: unknown line\n"; |
| $errors++; |
| next; |
| } |
| if ($use_namespaces) { |
| $new = { crc => $l[0], mod => $l[2], type => $l[3], namespace => $l[4] }; |
| } else { |
| $new = { crc => $l[0], mod => $l[2], type => $l[3] }; |
| } |
| $res{$l[1]} = $new; |
| } |
| if (!%res) { |
| print STDERR "error: no symvers found in $file.\n"; |
| exit 1; |
| } |
| close($fh); |
| return %res; |
| } |
| |
| |
| |
| my $type_GPL = 0x1; |
| my $type_NOW = 0x2; |
| my $type_UNUSED = 0x4; |
| my %types = ( |
| EXPORT_SYMBOL => 0x0, |
| EXPORT_SYMBOL_GPL => $type_GPL | $type_NOW, |
| EXPORT_SYMBOL_GPL_FUTURE => $type_GPL, |
| EXPORT_UNUSED_SYMBOL => $type_UNUSED, |
| EXPORT_UNUSED_SYMBOL_GPL => $type_UNUSED | $type_GPL | $type_NOW |
| ); |
| |
| sub type_compatible { |
| my ($old, $new) = @_; |
| |
| for my $type ($old, $new) { |
| if (!exists($types{$type})) { |
| print STDERR "error: unrecognized export type $type.\n"; |
| exit 1; |
| } |
| } |
| |
| return !(~$types{$old} & $types{$new}); |
| } |
| |
| my $kabi_errors = 0; |
| sub kabi_change { |
| my ($sym, $symvers, $message) = @_; |
| my $fail = 1; |
| |
| for my $rule (@rules) { |
| if ($rule->{mod} && $symvers->{mod} =~ $rule->{mod} || |
| $rule->{sym} && $sym =~ $rule->{sym} || |
| ($use_namespaces && $rule->{namespace} && |
| $symvers->{namespace} =~ $rule->{namespace})) { |
| $fail = $rule->{fail}; |
| last; |
| } |
| } |
| return unless $fail or $opt_verbose; |
| |
| print STDERR "KABI: symbol $sym(mod:$symvers->{mod}"; |
| if ($use_namespaces && $symvers->{namespace}) { |
| print STDERR " ns:$symvers->{namespace}"; |
| } |
| print STDERR ") $message"; |
| if ($fail) { |
| $kabi_errors++; |
| print STDERR "\n"; |
| } else { |
| print STDERR " (tolerated)\n"; |
| } |
| } |
| |
| sub xopen { |
| open($_[0], $_[1], @_[2..$#_]) or die "$_[2]: $!\n"; |
| } |
| |
| my $res = GetOptions( |
| 'verbose|v' => \$opt_verbose, |
| 'rules|r=s' => \$opt_rules, |
| ); |
| if (!$res || @ARGV != 2) { |
| print STDERR "Usage: $0 [--rules <rules file>] Module.symvers.old Module.symvers\n"; |
| exit 1; |
| } |
| |
| |
| $use_namespaces = symvers_uses_namespaces($ARGV[0]); |
| |
| if (defined($opt_rules)) { |
| load_rules($opt_rules); |
| } |
| my %old = load_symvers($ARGV[0]); |
| my %new = load_symvers($ARGV[1]); |
| |
| for my $sym (sort keys(%old)) { |
| if (!$new{$sym}) { |
| kabi_change($sym, $old{$sym}, "lost"); |
| } elsif ($old{$sym}->{crc} ne $new{$sym}->{crc}) { |
| kabi_change($sym, $old{$sym}, "changed crc from " . |
| "$old{$sym}->{crc} to $new{$sym}->{crc}"); |
| } elsif (!type_compatible($old{$sym}->{type}, $new{$sym}->{type})) { |
| kabi_change($sym, $old{$sym}, "changed type from " . |
| "$old{$sym}->{type} to $new{$sym}->{type}"); |
| } |
| } |
| if ($kabi_errors) { |
| print STDERR "KABI: aborting due to kabi changes.\n"; |
| exit 1; |
| } |
| exit 0; |