#!/usr/bin/perl
# afsmodname - return the name of the AFS module to load
# usage: afsmodname <path>

use strict;
use vars qw($modbase $DEBUG %SymCache);

$modbase = '/usr/vice/etc/modload';


sub get_ksym ($) {
  my($req_sym) = @_;
  my($addr, $symbol, $module, $version, @answer);

  print STDERR "get_ksym($req_sym)" if $DEBUG;

  if (exists($SymCache{$req_sym})) {
    print STDERR " [cached]\n" if $DEBUG;
    return $SymCache{$req_sym};
  }

  $SymCache{$req_sym} = undef;
  open(KSYMS, '/proc/ksyms') or die "open /proc/ksyms: $!\n";
  while (<KSYMS>) {
    if (/^(\w+)\s+(\w+)\s+\[(.*)\]/) {
      ($addr, $symbol, $module) = ($1, $2, $3)
    } elsif (/^(\w+)\s+(\w+)/) {
      ($addr, $symbol, $module) = ($1, $2, 'KERNEL')
    } else { next }

    if ($symbol =~ /^(.*)_R((?:smp)?(?:2gig)?_?[0-9a-f]{8})$/) {
      ($symbol, $version) = ($1, $2);
    } else { $version = '--none--' }

    if ($symbol eq $req_sym) {
      $SymCache{$req_sym} = [$addr, $version, $module];
      print STDERR " => [addr=$addr, vers=$version, mod=$module]\n" if $DEBUG;
      last;
    }
  }
  close(KSYMS);

  print STDERR " => not found\n" if $DEBUG && !defined($SymCache{$req_sym});
  $SymCache{$req_sym};
}


sub get_cputype () {
  my($cputype, $has_fpu);

  open(CPUINFO, '/proc/cpuinfo') or die "open /proc/cpuinfo: $!\n";
  while (<CPUINFO>) {
    if    (/^cpu\s*\:\s*(\S+)/) { $cputype = $1 }
    elsif (/^fpu\s*\:\s*yes/)   { $has_fpu = 1  }
    elsif (/^fpu\s*\:\s*no/)    { $has_fpu = 0  }
  }
  close(CPUINFO);
  ($cputype, $has_fpu);
}


sub table_lookup ($@) {
  my($cpu, @paths) = @_;
  my($path, $symbol, $version, $mincpu, $module, $info, $found_module);

  foreach $path (@paths) {
    next unless -f $path;
    open(TABLE, $path) or die "open $path: $!\n";
    while (<TABLE>) {
      next if (/^\#/ || /^\s*$/);
      ($symbol, $version, $mincpu, $module) = split;
      next if ($mincpu ne '-' && $cpu && $cpu lt $mincpu);
      $info = get_ksym($symbol);
      next unless $info;
      next unless $version eq $$info[1];
      $found_module = $module;
    }
    close(TABLE);
  }
  $found_module;
}


sub dump_versions ($$) {
  my($cpu, $fpu) = @_;
  my($version);

  print STDERR "CPU Type:       $cpu ($fpu)\n";

  chomp($version = `uname -rv`);
  print STDERR "Linux version:  $version\n";

  if (open(RHR, "/etc/redhat-release")) {
    chomp($version = <RHR>);
     print STDERR "RedHat release: $version\n";
  }
}


sub dump_syms (@) {
  my(@syms) = @_;
  my($sym, $info);

  print STDERR "Symbol versions:\n";
  foreach $sym (@syms) {
    $info = get_ksym($sym);
    printf "  %-10s %s", $sym, $$info[1] if $info;
  }
}


## MAIN PROGRAM

my($cpu, $fpu, $module);

if ($ARGV[0] eq '-d') {
  $DEBUG = 1;
  shift(@ARGV);
}

$modbase = $ARGV[0] if @ARGV;

($cpu, $fpu) = get_cputype();

$module = table_lookup($cpu, "$modbase/SymTable", "$modbase/SymTable.local");

if ($module) {
  print "libafs-$module.o";
  exit(0);
}

print STDERR <<'EOF';
Hmm...  I can't seem to find an AFS kernel module suitable for your Linux
kernel.  That means you will need to build or obtain a suitable module.
The following information may be of some use in obtaining assistance:
EOF

dump_versions($cpu, $fpu);
dump_syms(qw(__iget iget));

exit(1);
