#!/usr/bin/perl

#/*
#*  Copyright (C) 2005-2006 Pontus Fuchs, Giridhar Pemmasani
#*
#*
#*  This program is free software; you can redistribute it and/or modify
#*  it under the terms of the GNU General Public License as published by
#*  the Free Software Foundation; either version 2 of the License, or
#*  (at your option) any later version.
#*
#*  This program is distributed in the hope that it will be useful,
#*  but WITHOUT ANY WARRANTY; without even the implied warranty of
#*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#*  GNU General Public License for more details.
#*
#*/

use strict;
use Fcntl ':mode';
use File::Basename;
use File::Copy;
use File::Path;
use Cwd;

my $WRAP_PCI_BUS = 5;
my $WRAP_PCIMCIA_BUS = 8;
my $WRAP_USB_BUS = 15;

my %sections;
my %parsed_sections;
my $confdir = "/etc/ndiswrapper";
my $src_dir;
my $driver_name;

# fixup list for parameters.
my %param_fixlist = ("EnableRadio|0" => "1",
		     "IBSSGMode|0" => "2",
                     "PrivacyMode|0" => "2",
		     "MapRegisters|256" => "64",
		     "AdhocGMode|1" => "0");

if (@ARGV < 1) {
	usage();
	exit(1);
}

my $modconf;
if (`uname -r` =~ /(\d+)\.(\d+)\.(\d+)/) {
    if ($2 > 4) {
	if (-d "/etc/modprobe.d") {
	    $modconf = "/etc/modprobe.d/ndiswrapper"
	} else {
	    $modconf = "/etc/modprobe.conf"
	}
    } else {
	if (-d "/etc/modutils") {
	    $modconf = "/etc/modutils/ndiswrapper";
	} else {
	    $modconf = "/etc/modules.conf";
	}
    }
}

my $res;
my $dbg_file;

$dbg_file = ">/dev/null";

# "-D" is for development/debugging only
if ($ARGV[0] eq "-D") {
    $dbg_file = ">/tmp/ndiswrapper.dbg";
    $confdir = "/tmp/ndiswrapper";
    shift;
}

open(DBG, $dbg_file) or die "couldn't open $dbg_file: $!";

if ($ARGV[0] eq "-i" and @ARGV == 2) {
    $res = install($ARGV[1]);
} elsif (($ARGV[0] eq "-a" or $ARGV[0] eq "-d") and @ARGV == 3) {
    $res = device_driver_alias($ARGV[1], $ARGV[2]);
} elsif (($ARGV[0] eq "-e" or $ARGV[0] eq "-r") and @ARGV == 2) {
    $res = remove_driver($ARGV[1]);
} elsif ($ARGV[0] eq "-l" and @ARGV == 1) {
    $res = list_drivers();
} elsif ($ARGV[0] eq "-m" and @ARGV == 1) {
    $res = add_module_alias();
} elsif ($ARGV[0] eq "-v" and @ARGV == 1) {
    printf "utils "; system("/sbin/loadndisdriver -v");
    printf "driver ";
    system("modinfo ndiswrapper | grep -E '^version|^vermagic'");
    $res = 0;
} elsif ($ARGV[0] eq "-ma" and @ARGV == 1) {
    $res = generate_module_device_map(0);
} elsif ($ARGV[0] eq "-mi" and @ARGV == 1) {
    $res = generate_module_device_map(1);
} else {
    usage();
}
close(DBG);
exit($res);

sub usage() {
    print "install/manage Windows drivers for ndiswrapper\n\n" .
        "usage: ndiswrapper OPTION\n" .
	"-i inffile       install driver described by 'inffile'\n" .
	"-a devid driver  use installed 'driver' for 'devid'\n" .
	"-r driver        remove 'driver'\n" .
	"-l               list installed drivers\n" .
	"-m               write configuration for modprobe\n" .
	"-ma              write module alias configuration for all devices\n" .
	"-mi              write module install configuration for all devices\n" .
	"-v               report version information\n\n" .
	"where 'devid' is either PCIID or USBID of the form XXXX:XXXX,\n" .
	  "as reported by 'lspci -n' or 'lsusb' for the card\n";
}

sub remove_driver {
    my $driver = shift;
    if (!rmtree("$confdir/$driver", 0, 1)) {
	warn "couldn't delete $confdir/$driver\n";
    }
    return 0;
}

sub install {
    my $inf = shift;
    chomp($inf);
    $src_dir = dirname($inf);
    $driver_name = lc(basename($inf));
    $driver_name =~ s/\.inf//;

    if (! -d $confdir) {
	mkdir($confdir) or die "couldn't create $confdir: $!";
    }
    (-d "$confdir/$driver_name") and
      die "driver $driver_name is already installed\n";
    mkdir("$confdir/$driver_name") or
      die "couldn't create $confdir/$driver_name: $!";
    print "installing $driver_name ...\n";
    read_sections($inf);
    parse_section("Strings");
    parse_section("Version");
    parse_mfr();
    copy_file(basename($inf));
    create_fuzzy_conf($driver_name);
    return 0;
}

# return lines in section
sub get_section {
    my $name = shift;
    foreach my $key (keys %sections) {
	if (lc($key) eq lc($name)) {
	    printf DBG "section: $key\n";
	    return \@{$sections{$key}};
	}
    }
    printf DBG "couldn't find section $name\n";
    return 0;
}

# load inf and split into different sections.
sub read_sections {
    my $filename = shift;
    open(INF, $filename) or die "couldn't open $filename: $!";

    my $name = "none";
    @{$sections{$name}} = ();
    while (my $line = <INF>) {
	# convert from unicode
	$line =~ s/\xff\xfe//;
	$line =~ s/\0//g;

	chomp($line);
	$line = trim($line);
	next if ($line =~ /^$/);
	if ($line =~ /^\[(.+)\]/) {
	    $name = $1;
	    @{$sections{$name}} = ();
	} else {
	    push(@{$sections{$name}}, $line);
	}
    }
    close(INF);
    foreach $name (keys %sections) {
	printf DBG "section: %s\n", $name;
	foreach my $line (@{$sections{$name}}) {
	    printf DBG "%s: %s\n", $name, $line;
	}
    }
}

sub parse_section {
    my $name = shift;
    my $lines = get_section($name);
    if (!$lines) {
	return;
    }
    $parsed_sections{$name} = ();
    foreach my $line (@{$lines}) {
	(my $key, my $val) = parse_key_value($line);
	if ($key) {
	    $val = strip_quotes($val);
	    $parsed_sections{$name}->{$key} = $val;
	    printf DBG "$name: %s = %s\n", $key, $val;
	}
    }
}

sub parse_mfr() {
    my $lines = get_section("Manufacturer");
    $lines or die "couldn't get manufacturer section - " .
      "installation may be incomplete\n";
    foreach  my $line (@{$lines}) {
	(my $strkey, my $val) = parse_key_value($line);
	if ($strkey) {
	    (my $models, my @targets) = split(",", $val);
	    if ($models) {
		printf DBG "mfr: %s, %s\n", $line, $models;
		parse_models($models);
	    }
	}
    }
}

sub parse_models {
    my $models = shift;
    my $lines = get_target_os_section($models);
    if (!$lines) {
	warn "couldn't find models section \"$models\" -\n" .
	  "installation may be incomplete\n";
	return -1;
    }
    foreach my $line (@{$lines}) {
	$line = del_comment($line);
	next if (length($line) eq 0);
	(my $dev_desc, my $val) = parse_key_value($line);
	my @fields = split(",", $val);
	if (@fields le 1) {
	    printf "couldn't find install directive: %s\n", $line;
	    next;
	}
	my $install_section = trim($fields[0]);
	my $hwid = trim($fields[1]);
	# TODO: deal with compatible IDs as hwid?
	(my $bus_type, my $vendor, my $device, my $subvendor, my $subdevice) =
	  parse_hwid($hwid);
	printf DBG "models: %s, %s\n", $install_section, $hwid, $vendor;
	next if (!$vendor);
	parse_install($install_section, $bus_type, $vendor, $device,
		      $subvendor, $subdevice);
    }
}

sub parse_install {
    (my $secn, my $bus_type, my $vendor, my $device,
     my $subvendor, my $subdevice) = @_;
    my $lines = get_target_os_section($secn);
    if (!$lines) {
	warn "couldn't find install section \"$secn\" -\n" .
	  "installation may be incomplete\n";
	return -1;
    }

    my @addregs;
    my @copyfiles;
    foreach my $line (@{$lines}) {
	$line =~ s/^;\s*//;
	$line = trim(del_comment($line));
	(my $key, my $val) = parse_key_value($line);
	my @array;
	if ($key) {
	    if (lc($key) eq "addreg") {
		@array = split(",", $val);
		foreach my $reg (@array) {
		    push @addregs, trim($reg);
		}
	    } elsif (lc($key) eq "copyfiles") {
		printf DBG "copyfiles: %s\n", $val;
		@array = split(",", $val);
		foreach my $copy_file (@array) {
		    my @copy_sec = split(",", $copy_file);
		    foreach my $file (@copy_sec) {
			push @copyfiles, trim($file);
		    }
		}
	    } elsif (lc($key) eq "bustype") {
		printf DBG "bustype: %s\n", $val;
		$bus_type = $val;
	    }
	}
    }

    my $filename = "$vendor:$device";
    if ($subvendor) {
	$filename .= ":$subvendor:$subdevice"
    }
    $filename .= sprintf(".%X.conf", $bus_type);

    open(CONF, ">$confdir/$driver_name/$filename") or
      die "couldn't create file $filename: $!";

    my $version = get_section_value("Version", "DriverVer");
    my $provider = get_section_value("Version", "Provider");
    my $classguid = get_section_value("Version", "ClassGUID");
    my $providerstring = trim(strip_quotes(get_string_value(trim($provider))));
    $classguid =~ s/^\s*{//;
    $classguid =~ s/}\s*$//;

    printf CONF "NdisVersion|0x50001\n";
    printf CONF "Environment|1\n";
    printf CONF "class_guid|%s\n", $classguid;
    printf CONF "NetworkAddress|XX:XX:XX:XX:XX:XX\n";
    printf CONF "driver_version|%s,%s\n", $providerstring, $version;
    printf CONF "BusType|%s\n", $bus_type;
    printf CONF "\n";
    close(CONF);

    open(CONF, "|sort|uniq >>$confdir/$driver_name/$filename") or
      die "couldn't create file $confdir/$driver_name/$filename: $!";

    foreach my $reg (@addregs) {
	parse_registry($reg);
    }
    foreach my $file (@copyfiles) {
	parse_copy_file($file);
    }
    # RTL8185L driver crashes if DriverDesc setting is not found
    if ($vendor == "10EC" && $device == "8185") {
	printf CONF "DriverDesc|Realtek RTL8185 54M Wireless LAN Network Adapter\n";
    }
    close(CONF);
}

sub parse_registry {
    my $reg = shift;
    my $conf = shift;
    my $lines = get_section($reg);
    if (!$lines) {
	warn "couldn't find section \"$reg\" -\n" .
	  "installation may be incomplete\n";
	return -1;
    }

    foreach my $line (@{$lines}) {
	$line = del_comment($line);
	my @fields = split(",", $line);
	next if (@fields lt 4);
	my $value;
	my $param = trim($fields[1]);
	if ($param =~ /ndi\\params\\(.+)/i) {
	    $param = strip_quotes(trim($1));
	    $param =~ s/\\.*$//;
	    next if (lc(trim($fields[2])) ne "default");
	    $value = strip_quotes(trim($fields[4]));
	} else {
	    $param = strip_quotes(trim($fields[2]));
	    next if (length($param) eq 0);
	    $value = strip_quotes(trim($fields[4]));
	}
	$value = get_string_value($value);
	if (length($param) gt 0) {
	    if ($param_fixlist{"$param|$value"}) {
		my $orig_value = $value;
		$value = $param_fixlist{"$param|$value"};
		printf "forcing parameter $param from $orig_value to $value\n";
	    }
	    printf CONF "%s|%s\n", $param, $value;
	}
    }
}

sub parse_copy_file {
    my $copy_name = shift;

    if ($copy_name =~ /^\@/) {
	$copy_name =~ s/^\@//;
	return copy_file($copy_name);
    }

    my $lines = get_section($copy_name);
    if (!$lines) {
	warn "couldn't find section \"$copy_name\" -\n" .
	  "installation may be incomplete\n";
	return -1;
    }
    foreach my $line (@{$lines}) {
	$line = trim($line);

	my @files = split(",", $line);
	if (@files == 0) {
	    printf DBG "copyfiles section $copy_name has no files\n";
	    return -1;
	}
	my $file;
	if (@files > 1 and length($files[1]) > 0) {
	    $file = $files[1];
	} else {
	    $file = $files[0];
	}
	printf DBG "file: '%s'\n", $file;
	$file =~ s/^\s*;//;
	$file = trim($file);
	if ($file and length($file) > 0) {
	    if (valid_file_name($file)) {
		copy_file($file);
	    } else {
		printf DBG "invalid file '%s' ignored\n", $file;
	    }
	}
    }
    return 0;
}

sub parse_hwid {
    my $hwid = uc(shift);
    if ($hwid =~ /(PCI\\)?VEN_(\w+)&DEV_(\w+)&SUBSYS_(\w{4})(\S{4})/) {
	return ($WRAP_PCI_BUS, $2, $3, $4, $5);
    } elsif ($hwid =~ /(PCI\\)?VEN_(\w+)&DEV_(\w+)/) {
	return ($WRAP_PCI_BUS, $2, $3, 0, 0);
    } elsif ($hwid =~ /(USB\\)?VID_(\w+)&PID_(\w+)/) {
	return ($WRAP_USB_BUS, $2, $3, 0, 0);
    } else {
	return 0;
    }
}

sub parse_key_value {
    my $line = shift;

    $line = del_comment($line);
    if ($line =~ /([^=]+)=(.+)/) {
	return (trim($1), trim($2));
    } else {
	return 0;
    }
}

sub get_target_os_section {
    my $secn = shift;
    my $arch = `uname -m`;
    chomp($arch);
    if ($arch =~ /64$/) {
	$arch = "AMD64";
    } else {
	$arch = "X86";
    }
    chomp($secn);
    printf DBG "secn: %s\n", $secn;
    $secn =~ s/\.NT($arch)?(\.5\.1)?$//;
    printf DBG "secn: %s\n", $secn;

    my $lines = get_section($secn . ".NT" . $arch . ".5.1");
    return $lines if ($lines);

    $lines = get_section($secn . ".NT" . $arch);
    return $lines if ($lines);

    $lines = get_section($secn . ".NT.5.1");
    return $lines if ($lines);

    $lines = get_section($secn . ".NT");
    return $lines if ($lines);

    $lines = get_section($secn);
    return $lines if ($lines);

    printf DBG "couldn't find section \"$secn\" for \"$arch\"\n";
    return 0;
}

sub get_section_value {
    (my $secn, my $name) = @_;
    return $parsed_sections{$secn}->{$name};
}

sub get_string_value {
    my $key = shift;
    if ($key =~ /%(.+)%/) {
	$key = $1;
	return get_section_value("Strings", $key);
    } else {
	return $key;
    }
}

sub copy_file {
    my $file = shift;

    $file = trim(del_comment($file));
    my $real_file = get_file($file);
    if (length($real_file) gt 0) {
	$file = lc($real_file);
	copy("$src_dir/$real_file", "$confdir/$driver_name/$file") or
	  warn "couldn't copy \"$src_dir/$real_file\" to " .
	    "\"$confdir/$driver_name\": $! -\n" .
	      "installation may be incomplete\n";
	chmod(0644, "$confdir/$driver_name/$file");
    } else {
	warn "couldn't find \"$file\" in \"$src_dir\"; make sure " .
	  "all driver files, including .inf, .sys (and .bin, if any) " .
	    "are in \"$src_dir\" -\n" .
	      "installation may be incomplete\n";
    }
}

# for conf files with subvendor and subdevice, create conf files with just
# vendor and device
sub create_fuzzy_conf {
    my $driver = shift;
    my $cwd = cwd();
    chdir("$confdir/$driver") or die "couldn't chdir to $confdir/$driver: $!";
    open(LS, "ls -1 . |") or die "couldn't open $confdir/$driver: $!";
    while (my $file = <LS>) {
	chomp($file);
	if ($file =~ /^(.{4}):(.{4}):(.{4}):(.{4})\.([^.]+)\.conf$/) {
	    my $fuzzy_file = "$1:$2.$5.conf";
	    printf DBG "file: $file, fuzzy file: $fuzzy_file\n";
	    if (! -e "$confdir/$driver/$fuzzy_file") {
		symlink("$file", "$fuzzy_file") or
		  warn "couldn't link $confdir/$driver/$file " .
		    "to $confdir/$driver/$fuzzy_file: $!\n";
	    }
	}
    }
    chdir($cwd) or warn "couldn't chdir to $cwd: $!";
    return 0;
}

# find a file in a case-insensitive way.
sub get_file {
    my $file = lc(shift);
    if (opendir(DIR, "$src_dir")) {
	my @allfiles = readdir(DIR);
	foreach my $real_file (@allfiles) {
	    if (lc($real_file) eq $file) {
		closedir(DIR);
		return $real_file;
	    }
	}
	closedir(DIR);
    } else {
	warn "couldn't open \"$src_dir\" - installation may be incomplete\n";
    }
    return "";
}

sub strip_quotes {
    my $s = shift;
    $s =~ s/"(.*)"/$1/;
    return $s;
}

sub del_comment {
    my $s = shift;
    $s =~ s/;.*//;
    return $s;
}

# remove whitsepace at front and end.
sub trim {
    my $s = shift;
    $s =~ s/^\s*//;
    $s =~ s/\s*$//;
    return $s;
}

sub valid_file_name {
    my $file = shift;
    printf DBG "file name: %s\n", $file;
    return 1 if ($file =~ /^[a-zA-Z][a-zA-Z0-9\.]*$/);
    return 0;
}

sub device_driver_alias {
    my ($devid, $driver) = @_;
    my $done = 0;

    $devid = uc($devid);
    if (!($devid =~ /[0-9A-Z]{4}:[0-9A-Z]{4}/)) {
	printf "'$devid' is not a valid device ID\n";
	return 1;
    }
    open(LS, "ls -1 $confdir/$driver/ |") or
      die "couldn't open $confdir/$driver: $!";
    while (my $f = <LS>) {
	chomp($f);
	if ($f =~ /\.([0-9A-F]+).conf$/) {
	    if (symlink("$f", "$confdir/$driver/$devid.$1.conf")) {
		printf "driver '$driver' is used for '$devid'\n";
		$done = 1;
		last;
	    } else {
		warn "couldn't create symlink for \"$f\" -\n" .
		  "installation may be incomplete\n";
	    }
	}
    }
    close(LS);
    if ($done == 0) {
	printf "driver '$driver' is not installed (properly)!\n";
	return 1;
    }
    return 0;
}

sub generate_module_device_map {
    my $mode = shift;
    my $vendor, my $device, my $subvendor, my $subdevice, my $bustype, my $busid;

    my $device_map;
    if (-d "/etc/modprobe.d") {
	$device_map = "/etc/modprobe.d/ndiswrapper";
    } elsif (-d "/etc/modules.d") {
	$device_map = "/etc/modules.d/ndiswrapper";
    } else {
	$device_map = "/etc/ndiswrapper/ndiswrapper";
    }

    open(CONF, "| sort >$device_map") or
      die "couldn't create modules alias file $device_map: $!";
    open(LS, "ls -1 $confdir|") or
      die "couldn't open $confdir: $!";
    while (my $driver = <LS>) {
	chomp($driver);
	my $stat = (stat("$confdir/$driver"))[2];
	if (S_ISDIR($stat)) {
	    open(LS2, "ls -1 $confdir/$driver/ |") or
	      die "couldn't open $confdir/$driver: $!";
	    while (my $file = <LS2>) {
		chomp ($file);
		if ($file =~ /.conf$/) {
		    if ($file =~ /^(.{4}):(.{4}):(.{4}):(.{4})\.([^.]+)\.conf$/) {
			($vendor, $device, $subvendor, $subdevice, $busid) =
			  ($1, $2, "0000$3", "0000$4", $5);
		    } elsif ($file =~ /(.{4}):(.{4})\.([^.]+)\.conf$/) {
			($vendor, $device, $subvendor, $subdevice, $busid) =
			  ($1, $2, "*", "*", "$3");
		    }
		    my $devstring;
		    if ($busid eq $WRAP_USB_BUS) {
			$devstring = sprintf("usb:v%sp%sd*dc*dsc*dp*ic*isc*ip*",
					     $vendor, $device);
		    } elsif ($busid eq $WRAP_PCI_BUS) {
			$devstring = sprintf("pci:v0000%sd0000%ssv%ssd%sbc*sc*i*",
					     $vendor, $device, $subvendor,
					     $subdevice);
		    } else {
			warn "wrong bustype ($busid) for " .
			  "configuration file $file - ignoring it\n";
			next;
		    }
		    if ($mode == 0) {
			printf CONF "alias %s ndiswrapper\n", $devstring;
		    } else {
			printf CONF "install %s /sbin/modprobe ndiswrapper\n",
			  $devstring;
		    }
		}
	    }
	    close(LS2);
	}
    }

    close(CONF);
    close(LS);

    printf "module configuration information is stored in $device_map\n";
    return 0;
}

sub list_drivers {
    my $s;
    my $cards = get_cards();

    open(LS, "ls -1 $confdir|") or die "couldn't open $confdir: $!";
    while (my $driver = <LS>) {
	chomp($driver);
	if (-e "$confdir/$driver") {
	    $s .= "$driver\t". install_status($cards, $driver) . "\n";
	}
    }
    close(LS);
    if ($s) {
	printf "installed drivers:\n$s";
    } else {
	printf "no drivers installed\n$s";
    }
    return 0;
}

sub add_module_alias {
    my $alias = 0;

    open(MODPROBE, "modprobe -c|") or die "couldn't run modprobe: $!";
    while (my $line = <MODPROBE>) {
	if ($line =~ /^alias\s.+\sndiswrapper/) {
	    printf "modprobe config already contains alias directive\n\n";
	    $alias = 1;
	} elsif ($line =~ /^install\s.*ndiswrapper/) {
	    warn "module configuration contains directive $line;" .
	      "you should delete that";
	} elsif ($line =~ /^post-install\s+ndiswrapper/) {
	    warn "module configuration contains directive $line;" .
	      "you should delete that";
	}
    }
    close(MODPROBE);

    if ($alias) {
	return 0;
    }

    printf "adding \"alias wlan0 ndiswrapper\" to $modconf ...\n";
    system("echo \"alias wlan0 ndiswrapper\" >>$modconf") or
      die "couldn't add module alias: $!";
    if (-x "/sbin/update-modules") {
	system("/sbin/update-modules");
    }
    return 0;
}

sub get_cards {
#01:00.0 Class 0300: 1002:4c66 (rev 01)
#        Subsystem: 1043:1732
    my @cards = ();
    my @lspci = ("/sbin/lspci", "/usr/sbin/lspci", "lspci");
    foreach my $cmd (@lspci) {
	if (open(LSPCI, "$cmd -vn|")) {
	    my $card;
	    while (my $line = <LSPCI>) {
		if ($line =~ /^[0-9a-f]+.+\s+([0-9a-f]{4}):([0-9a-f]{4})/i) {
		    $card = {vendor => uc($1), device => uc($2)};
		    printf DBG "card: %s, %s\n", $1, $2;
		} elsif ($line =~ /.+Subsystem:\s*([0-9a-f]{4}):([0-9a-f]{4})/i) {
		    $card->{subvendor} = uc($1);
		    $card->{subdevice} = uc($2);
		    printf DBG "sub: %s, %s\n", $1, $2;
		    push(@cards, $card);
		}
	    }
	    last;
	}
    }

    my @lsusb = ("/sbin/lsusb", "/usr/sbin/lsusb", "lsusb");
    foreach my $cmd (@lsusb) {
	if (open(LSUSB, "$cmd |")) {
	    my $card;
	    while (my $line = <LSUSB>) {
		if ($line =~ /.*: ID\s(.{4}):(.{4}).*/) {
		    $card = {vendor => uc($1), device => uc($2)};
		    push(@cards, $card);
		}
	    }
	    last;
	}
    }
    return \@cards;
}

sub install_status {
    my ($cards, $driver) = @_;

    my $sys = 0;
    my $conf = 0;
    my $inf = 0;
    my $vendor, my $device, my $subvendor, my $subdevice, my $busid;

    if (!$cards) {
	return;
    }

    open(LS2, "ls -1 $confdir/$driver|") or
      die "couldn't open $confdir/$driver: $!";

    while (my $file = <LS2>) 	{
	chomp($file);
	if ($file =~ /\.sys$/) {
	    $sys = 1;
	} elsif ($file =~ /\.inf$/) {
	    $inf = 1;
	} elsif ($file =~ /\.conf$/) {
	    $conf = 1 if ($conf eq 0);
	    if ($file =~ /^(.{4}):(.{4}):(.{4}):(.{4})\.([^.]+)\.conf$/) {
		($vendor, $device, $subvendor, $subdevice, $busid) =
		  (uc($1), uc($2), uc($3), uc($4), uc($5));
		foreach my $card (@{$cards}) {
		    if ($card->{vendor} eq $vendor and
			$card->{device} eq $device and
			$card->{subvendor} eq $subvendor and
			$card->{subdevice} eq $subdevice) {
			$conf = 3;
			last;
		    }
		}
	    } elsif ($file =~ /(.{4}):(.{4})\.([^.]+)\.conf/) {
		($vendor, $device, $subvendor, $subdevice, $busid) =
		  (uc($1), uc($2), "\\*", "\\*", uc($3));
		foreach my $card (@{$cards}) {
		    if ($card->{vendor} eq $vendor and
			$card->{device} eq $device) {
			my $stat = (lstat("$confdir/$driver/$file"))[2];
			if (S_ISLNK($stat)) {
			    $conf = 2;
			} else {
			    $conf = 3;
			}
			last;
		    }
		}
	    }
	}
    }
    close(LS2);

    my $ret;
    if ($sys eq 0 || $inf eq 0 || $conf eq 0) {
	$ret = "invalid driver!";
    } else {
	if ($conf eq 1) {
	    $ret = "\tdriver installed ";
	} else {
	    $ret = "\tdriver installed, hardware ($vendor:$device) present ";
	}

	my $devstring;
	if ($busid eq $WRAP_USB_BUS) {
	    $devstring =
	      sprintf("usb:v%sp%sd", $vendor, $device);
	} elsif ($busid eq $WRAP_PCI_BUS) {
	    $devstring =
	      sprintf("pci:v0000%sd0000%ssv", $vendor, $device);
	}
	my $alt_driver;
	open(MODPROBE, "modprobe -c|") or die "couldn't run modprobe: $!";
	while (my $line = <MODPROBE>) {
	    chomp($line);
	    $alt_driver = (split(' ', $line))[-1] if $line =~ /$devstring/;
	    chomp($alt_driver);
	    if (length($alt_driver) gt 0 and $alt_driver ne "ndiswrapper") {
		$ret .= "(alternate driver: $alt_driver)";
		last;
	    }
	}
	close(MODPROBE);
    }
    return $ret;
}

## Local Variables: ##
## cperl-indent-level: 4 ##
## End: ##
