#!/usr/bin/perl -w

###############################################################################
# Copyright 2003-2006 VMware, Inc.  All rights reserved. -- VMware Confidential
###############################################################################
#
# esxcfg-pciid [WORKING_DIR]
#
#  Generate the following files from pci.xml:
#   pci.ids
#   pcitable
#   pcitable.Linux
#   vmware-devices.map
#
#  Optional argument is the working directory where pci.xml can be found,
#  and to which the output files will be written.
#
#  This script does nothing if all the output files are newer than pci.xml.
#
#  This script will not run if any <ERROR> tags are found in pci.xml.
#  Please correct the errors before running this script.
#

use lib "/usr/lib/vmware/esx-perl/perl5/site_perl/5.8.0";

use File::Copy;
use XML::Simpler qw(:strict);
use Data::Dumper;

use strict;

#
# === Argument processing
#
my $verbose;
my $extract;
my $quiet;
my $boot;

my $dir = "/etc/vmware";

foreach my $arg (@ARGV) {
    if ($arg eq '--extract') {
	$extract = 1;
    } elsif ($arg eq '--verbose') {
	$verbose = 1;
    } elsif ($arg eq '--quiet' || $arg eq '-q') {
	$quiet = 1;
    } elsif ($arg eq '--boot') {
	$boot = 1;
    } else {
	$dir = $arg;
    }
}

#
# === Parameters
#
my $file_xml = "$dir/pci.xml";
my $file_ids = "$dir/pci.ids";
my @file_pcitable = ("pcitable", "pcitable.Linux");
my $classfile = "$dir/pci.classlist";
my $file_map = "$dir/vmware-devices.map";
my $suppress_file = "/tmp/suppress-esxcfg-pciid";

my $header = '#
# This file is automatically generated by esxcfg-pciid
# Any changes you make manually will be lost at the next build.
# Please edit <driver>.xml for permanent changes.
#
';

my $vendorlist_header = '# Vendors, devices and subsystems.

# Syntax:
# vendor  vendor_name
#	device  device_name				<-- single tab
#		subvendor subdevice  subsystem_name	<-- two tabs

';

my $classlist_header = '#
# List of known device classes, subclasses and programming interfaces

# Syntax:
# C class	class_name
#	subclass	subclass_name  		<-- single tab
#		prog-if  prog-if_name  	<-- two tabs

';

my $mapcfg_header = '
# Database about the devices.  Collected in order of priority to be used.
# Commas are used as delimiters.  Therefore, no commas can be used.  
# No exceptions! Escape sequences are NOT supported.

# Configuration globals
# Fields: config,key,value
';

my $mapven_header = '
# Vendor table contains all the vendor that are supported.
#   Fields: vendor,vendor id,short name,long name
';

my $mapdev_header = '
# Device table
#   Fields: device,vendor id,device id,equiv,description,driver,constraints
#
# Devices with the same equiv field will be named the same way by vmkernel. 
# Constraints are specified as a semicolon delimited list of key-value pairs
# The syntax for a constraint:
#   constraint := key=value
#   key := subsys_vendorid | sybsys_deviceid
#   value := some hex string
';

#
# === Globals
#

# XML::Simple reads and writes xml files to/from Perl data structures.
# Therefore we have structured our nested hash tree to generate the XML
#  that we want.  Specifically, any field not in an array will get stuffed
#  as an attribute;  also, the vendor / device ID is used as both the hash
#  key and as the key attribute in our .xml file.
#
# $hTable = {
#    config => {
#      mod_root => [ blah ],
#      vendor => [ venID, venID, ... ]
#    },
#    vendor => {
#       venID => {
#          name   => [ 'Vendor name string' ],
#          short  => [ 'Abbrev. vendor name' ],
#          device => {
#            devID => {
#              name       => [ 'device desc' ],
#              table      => {
#                "pcitable" => {
#                  module     => 'Linux device-driver',
#                  desc       => [ 'desc if diff from device name' ],
#                  prefix     => "*"
#                },
#                "pcitable.Linux" => { ... },
#              },
#              vmware     => {
#                label  => 'vmnic/vmhba',
#                desc   => [ 'optional device desc if different' ],
#                driver => [ 'driver1', 'driver2', ... ]
#              }
#            }    # each device
#          }
#       }    # each vendor
#       venID = { ... },
#    }
#  }
#
# Every listed vendor and device goes in pci.ids
# Every <table> entry generates a corresponding line in pcitable*, with the
#   module and desc. if present; otherwise vendor and device names are used.
# A <vmware> entry indicates a device supported by one or more VMware drivers,
#   causing lines to be generated for that device in vmware-devices.map.
 
my $hTable;                # ref to hash containing vendor/device tree

#
# === Functions
#

# makePciIdsFile($outputFile) --
#  Create pci.ids file listing vendors and devices corresponding 
#  to PCI ID's, as well as the device classification table.
#  Missing device/vendor names cause an error message to be printed out,
#  but it goes on.
#  Device classes comes from $classfile.
#
# Returns nothing.
#
sub makePciIdsFile {
    my $out = shift;
    my $venID;
    my $devID;
    my $hDev;

    if (-e "$out") {
        print "Replacing $out file...\n";
        unlink($out);
    } else {
        print "Creating new $out file...\n";
    }

    open(F, ">$out")
        or die "Cannot open $out!\n";

    print F $header;
    print F $vendorlist_header;

    # Go through each vendor, sorted by ID
    foreach $venID (sort keys %{ $hTable->{vendor} }) {
        if(!defined($hTable->{vendor}{$venID}{name})) {
            print STDERR "Error no 'name' field for vendor $venID\n";
            next;
        }

        print F "$venID  $hTable->{vendor}{$venID}{name}[0]\n";

        if (!defined($hTable->{vendor}{$venID}{device})) {
            next;
        }

        # Go through each device for a vendor, sorted by dev ID
        foreach $devID (sort keys %{ $hTable->{vendor}{$venID}{device} }) {
            $hDev = $hTable->{vendor}{$venID}{device}{$devID};

            if (!defined($hDev->{name})) {
                print STDERR "Error no 'name' field for device $venID:$devID\n";
                next;
            }

            my @ids = split ':', $devID;
            if (@ids == 3) {
                # Make sure we print an extra pci id line if someone added an
                # entry with a sub vendor/sub id which doesn't have a proper
                # pci id entry
                if (!$hTable->{vendor}{$venID}{device}{$ids[0]}) {
                    print F "\t$ids[0]  $hDev->{name}[0]\n";
                }
                print F "\t\t$ids[1] $ids[2]  $hDev->{name}[0]\n";
            } elsif (@ids == 1) {
                print F "\t$ids[0]  $hDev->{name}[0]\n";
            } else {
                print STDERR "Illegal device ID $venID:$devID\n";
                next;
            }
        }
    }

    print F $classlist_header;

    open(C, "$classfile")
        or die "Cannot open $classfile!\n";

    while (<C>) {
        print F $_;
    }
    
    close(C);
    close(F);
}


# makeVmwareMapFile($outputFile) --
#  Generate vmware-devices.map file.
#
# Returns nothing.
#
sub makeVmwareMapFile {
    my $out = shift;
    my $venID;
    my $devID;

    if (-e "$out") {
        print "Replacing $out file...\n";
        unlink($out);
    } else {
        print "Creating new $out file...\n";
    }

    open(F, ">$out")
        or die "Cannot open $out!\n";

    print F $header;
    print F $mapcfg_header;

    # Go through and print config lines
    foreach (keys %{ $hTable->{config} }) {
        next if (/^supported_vendor$/);
        printf F ("config,%s,%s\n", $_, $hTable->{config}{$_});
    }

    print F $mapven_header;

    # Print list of supported vendors, remove commas
    foreach $venID (@{ $hTable->{config}{supported_vendor} }) {
        my $short = $hTable->{vendor}{$venID}{short}[0];
        my $name = $hTable->{vendor}{$venID}{name}[0];

        if (defined $short) {
	    $short =~ s/,//g;
	} else {
	    $short = "";
	}

        $name =~ s/,//g;
        print F "vendor,0x$venID,$short,$name\n"; 
    }

    print F $mapdev_header;

    # Print line for every device with <vmware> entry
    foreach $venID (sort keys %{ $hTable->{vendor} }) {

        if (!defined($hTable->{vendor}{$venID}{device})) {
            next;
        }

        foreach $devID (sort keys %{ $hTable->{vendor}{$venID}{device} }) {
            my $hDev = $hTable->{vendor}{$venID}{device}{$devID};

            next if (!defined($hDev->{vmware}));

            my @ids = split /:/, $devID;

            # Loop through each listed driver for device
            foreach (@{ $hDev->{vmware}{driver} }) {
                $hDev->{name}[0] =~ s/,/;/g;
                printf F ("device,0x$venID,0x%s,%s,%s,%s.o", $ids[0],
                          $hDev->{vmware}{label},
                          $hDev->{name}[0],
                          $_);
                if (@ids > 1) {
                    print F ",subsys_vendorid=0x$ids[1]";
                    if (@ids > 2) {
                        print F ";subsys_deviceid=0x$ids[2]";
                    }
                }
                print F "\n";
            }
        }
    }

    close(F);
}


# makePcitableFile($outputFile) --
#  Generate one of the pcitable* files, where every line contains
#  the PCI device ID,  name of driver module, and description.
#
# Returns nothing.
#
sub makePcitableFile {
    my $out = shift;
    my $venID;
    my $venShort;
    my $devID;

    if (-e "$dir/$out") {
        print "Replacing $dir/$out file...\n";
        unlink("$dir/$out");
    } else {
        print "Creating new $dir/$out file...\n";
    }

    open(F, ">$dir/$out")
        or die "Cannot open $dir/$out!\n";

    print F $header;

    foreach $venID (sort keys %{ $hTable->{vendor} }) {

        if (defined($hTable->{vendor}{$venID}{short})) {
            $venShort = $hTable->{vendor}{$venID}{short}[0];
        } else {
            $venShort = "";
        }

        if (!defined($hTable->{vendor}{$venID}{device})) {
            next;
        }

        foreach $devID (sort keys %{ $hTable->{vendor}{$venID}{device} }) {
            my $hDev = $hTable->{vendor}{$venID}{device}{$devID};
            my $desc = "";

            # skip if no table entry for this output file
            if (!defined($hDev->{table})) {
                next;
            }
            if (!defined($hDev->{table}{$out})) {
                next;
            }

            $devID =~ s/:/\t0x/g;

            # build device description 
            if (defined($hDev->{table}{$out}{desc})) {
                $desc = $hDev->{table}{$out}{desc}[0];
            } else {
                if (defined($hDev->{table}{$out}{prefix})) {
                    $desc = $hDev->{table}{$out}{prefix};
                }

                $desc .= "$venShort|";
                $desc .= $hDev->{name}[0];
            }

            printf F ("0x$venID\t0x$devID\t\"%s\"\t\"%s\"\n",
                      $hDev->{table}{$out}{module}, $desc);
        }
    }

    close(F);
}


sub readXML {
    my ($file) = @_;

    print "loading $file...\n";

    my $table = XMLin($file,
                    ForceArray => ['vendor', 'device', 'name', 'desc', 'table', 'ERROR', 'short', 'driver'],
                    KeyAttr => { vendor => "id", device => "id", table => "file" }
                    );

    return $table;
}

sub writeXML {
    my ($file, $table) = @_;

    my $header = "<?xml version='1.0' encoding='iso-8859-1'?>\n";

    print "writing $file\n";

    open my $fh, "> $file" or die "open($file): $!";

    print $fh $header;

    XMLout ($table, 
	    rootname => 'pcitable', 
	    outputfile => $fh, 
	    KeyAttr => { vendor => "id", device => "id", table => "file" },
	    keeproot => 0);

    close $fh;
}

my $mapDeviceToKernelDriver;
my $mapDeviceToTableDriver;

sub mergevmware {
    my ($tabledevice, $device, $deviceKey) = @_;

    if (exists $device -> {'vmware'}) {
	if (exists $tabledevice -> {'vmware'}) {
	    return $mapDeviceToKernelDriver -> {$deviceKey};
	}

	copyField ('vmware', $device, $tabledevice);
	$mapDeviceToKernelDriver -> {$deviceKey} = $device -> {'vmware'} -> {'driver'} -> [0];
    }

    return;
}

sub mergepcitables {
    my ($tabledevice, $device, $deviceKey) = @_;

    foreach my $table ('pcitable', 'pcitable.Linux') {
	if (exists $device -> {'table'} -> {$table}) {
	    if (exists $tabledevice -> {'table'} -> {$table}) {
		if ($device -> {'table'} -> {$table} -> {'module'} ne $tabledevice -> {'table'} -> {$table} -> {'module'}) {
		    return $mapDeviceToTableDriver -> {$table} -> {$deviceKey};
		}
	    }
	}
    }

    foreach my $table ('pcitable', 'pcitable.Linux') {
	if (exists $device -> {'table'} -> {$table}) {
	    $tabledevice -> {'table'} -> {$table} = $device -> {'table'} -> {$table};
	    my $module = $device -> {'table'} -> {$table} -> {'module'};
	    $mapDeviceToTableDriver -> {$table} -> {$deviceKey} = $module;
	}
    }

    return;
}

sub mergeTables {
    my ($table, $snippetname) = @_;

    my $snippet = readXML ($snippetname);

    my $vendors = $snippet -> {'vendor'};

    my $isSnippet = exists $table -> {'vendor'};

    foreach my $vendorId (sort keys %$vendors) {
	$isSnippet and $verbose and print "processing vendor information for vendor id $vendorId\n";

	my $vendor = $vendors -> {$vendorId};

	my ($vendorName, $vendorShort);

	foreach my $vendorField (sort keys %$vendor) {
	    if ($vendorField eq 'device') {
	    }
	    elsif ($vendorField eq 'name') {
		$vendorName = $vendor -> {'name'}[0];
	    }
	    elsif ($vendorField eq 'short') {
		$vendorShort = $vendor -> {'short'}[0];
	    }
	    else {
		print "no logic implemented for vendor field $vendorField\n";
	    }
	}

	my $tableVendor;

	if (exists $table -> {'vendor'} -> {lc $vendorId}) {
	    $tableVendor = $table -> {'vendor'} -> {lc $vendorId};

	    if (defined $vendorName) {
		if ($vendorName ne $tableVendor -> {'name'}[0]) {
		    print "Vendor name $vendorName does not match existing vendor name $tableVendor->{'name'}[0]\n";
		}
	    }

	    if (defined $vendorShort) {
		if (defined $tableVendor -> {'short'} and ($vendorShort ne $tableVendor -> {'short'}[0])) {
		    print "Vendor short name $vendorShort does not match existing vendor name $tableVendor->{'short'}[0]\n";
		}
	    }

	    $isSnippet and $verbose and print "adding new device information to existing vendor information for vendor $table->{'vendor'}->{$vendorId}->{'name'}[0]\n";
	}
	else {
	    $isSnippet and $verbose and print "creating new vendor entry for vendor $vendorId\n";

	    $tableVendor = {};

	    if (!defined $vendorName) {
		print "no vendor name defined for vendor $vendorId\n";
		$vendorName = "Vendor $vendorId";
	    }

	    $tableVendor -> {'name'}[0] = $vendorName;

	    if (!defined $vendorShort) {
		$isSnippet and print "no short vendor name defined for vendor $vendorId\n";
	    }
	    else {
		$tableVendor -> {'short'}[0] = $vendorShort;
	    }

	    $table -> {'vendor'} -> {lc $vendorId} = $tableVendor;
	    $tableVendor -> {'device'} = {};
	}

	my $devices = $vendor -> {'device'};
	my $tableDevices;

	if (defined $tableVendor -> {'device'}) {
	    $tableDevices = $tableVendor -> {'device'};
	} else {
	    $tableDevices = {};
	    $tableVendor -> {'device'} = $tableDevices;
	}

	foreach my $deviceId (sort keys %$devices) {
	    my $deviceKey = lc $vendorId.':'.lc $deviceId;

	    my $device = $devices -> {$deviceId};

	    $isSnippet and $verbose and print "processing device information for device id $deviceKey\n";

	    my @deviceIdWords = split (':', $deviceKey);

	    if (!exists $tableDevices -> {lc $deviceId}) {
		$tableDevices -> {lc $deviceId} = {}
	    }

	    my $tableDevice = $tableDevices -> {lc $deviceId};

	    copyField ('name', $device, $tableDevice);

	    my $conflict = mergevmware ($tableDevice, $device, $deviceKey);

	    if (defined $conflict) {
		print "kernel driver mapping for device id $deviceKey in $snippetname conflicts with definition for $conflict\n";
	    }

	    $conflict = mergepcitables ($tableDevice, $device, $deviceKey);

	    if (defined $conflict) {
		print "kernel driver mapping for device id $deviceKey in $snippetname conflicts with definition for $conflict\n";
	    }

	    if (scalar @deviceIdWords == 4) {
		my $deviceKeyDouble = lc $vendorId.':'.lc $deviceIdWords[1];

		if (!exists $tableDevices -> {$deviceIdWords [1]}) {
		    $verbose and print "No catchall entry for device id $deviceKeyDouble defined\n";
		    $verbose and print "Creating catch all entry based on device id $deviceKey\n";
		    my $catchall = {name => ($device -> {'name'})};
		    $tableDevices -> {$deviceIdWords [1]} = $catchall;
		}
	    }
	}
    }

    if (exists $snippet -> {'config'} -> {'mod_root'}) {
	if (exists $table-> {'config'} -> {'mod_root'}) {
	    if ($snippet -> {'config'} -> {'mod_root'} eq $table -> {'config'} -> {'mod_root'}) {
	    } else {
		die "config mod_root in table conflicts with root table";
	    }
	} else {
	    $table -> {'config'} -> {'mod_root'} = $snippet -> {'config'} -> {'mod_root'};
	}
    }

    if (exists $snippet -> {'config'} -> {'supported_vendor'}) {
	if (exists $table -> {'config'} -> {'supported_vendor'}) {
	    my @tableVendorIds = @ {$table -> {'config'} -> {'supported_vendor'}};

	    my $ids = {};

	    foreach my $id (map {lc} @tableVendorIds) {
		$ids -> {$id} = $id;
	    }

	    my @vendorIds = @ {$snippet -> {'config'} -> {'supported_vendor'}};

	    foreach my $id (map {lc} @vendorIds) {
		if (exists $ids -> {$id}) {
		} else {
		    $verbose and print "adding vendor id $id to supported vendors list\n";
		    $ids -> {$id} = $id;
		}
	    }

	    $table -> {'config'} -> {'supported_vendor'} = [keys (%$ids)];
	} else {
	    $table -> {'config'} -> {'supported_vendor'} = $snippet -> {'config'} -> {'supported_vendor'};
	}
    }

    if (!$extract) {
	foreach my $vendorId (map {lc} @ {$table -> {'config'} -> {'supported_vendor'}}) {
	    if (!exists $table -> {'vendor'} -> {$vendorId}) {
		die "no vendor information provided for supported vendor id $vendorId";
	    }
	}
    }
}

sub copyField {
    my ($field, $source, $target) = @_;

    if (defined $source -> {$field}) {
	$target -> {$field} = $source -> {$field};
    }
}

sub getVendorSnippet {
    my ($drivers, $driver, $vendorID) = @_;

    if (!defined $drivers -> {$driver}) {
	$verbose and print "creating snippet for driver $driver\n";
    }

    if (!defined $drivers -> {$driver} -> {'vendor'} -> {$vendorID}) {
	$drivers -> {$driver} -> {'vendor'} -> {$vendorID}= {};
    }

    my $vendor = $hTable -> {'vendor'} -> {$vendorID};
    my $snippet = $drivers -> {$driver} -> {'vendor'} -> {$vendorID};

    copyField ('name', $vendor, $snippet);
    copyField ('short', $vendor, $snippet);

    return $snippet;
}

sub getDeviceSnippet {
    my ($vendorSnippet, $vendor, $deviceID) = @_;

    my $deviceSnippet = $vendorSnippet -> {'device'} -> {$deviceID} = {};
    my $device = $vendor -> {'device'} -> {$deviceID};

    copyField ('name', $device, $deviceSnippet);

    return $deviceSnippet;
}

sub setupVmware {
    my ($deviceSnippet, $device, $driver) = @_;

    if (exists $device -> {'vmware'}) {
	my $vmware = $device -> {'vmware'};
	my $driverlist = $vmware -> {'driver'};
	my $vmwareSnippet = $deviceSnippet -> {'vmware'} = {};

	copyField ('label', $vmware, $vmwareSnippet);
	copyField ('desc', $vmware, $vmwareSnippet);

	for (my $index = 0; $index < (scalar @$driverlist); $index++)
	{
	    if ($driver eq $driverlist -> [$index]) {
		$vmwareSnippet -> {'driver'} = [$driver];
		last;
	    }
	}
    }
}

sub setupTable {
    my ($deviceSnippet, $device, $driver) = @_;

    if (exists $device -> {'table'}) {
	my $table = $device -> {'table'};

	foreach my $tablename ('pcitable','pcitable.Linux') {
	    if (exists $table -> {$tablename}) {
		if (($table -> {$tablename} -> {'module'} eq $driver) or
		    ($table -> {$tablename} -> {'module'} eq 'unknown') or
		    ($table -> {$tablename} -> {'module'} eq 'ignore')){
		    $deviceSnippet -> {'table'} -> {$tablename} = $table -> {$tablename};
		}
	    }
	}
    }
}

sub isTableNeeded {
    my ($drivers, $module, $driverlist) = @_;

    foreach my $driver (@$driverlist) {
	if ($driver eq $module) {
	    return 0;
	}
    }

    if (($module eq 'unknown') or ($module  eq 'ignore')) {
	return 0;
    }

    if ($module =~ m/^Card:/) {
	return 0;
    }

    return 1;
}

sub extractAllDriverSnippets {
    my $drivers = {};

    my $vendorlist = $hTable -> {'vendor'};

    foreach my $vendorID (sort keys %$vendorlist) {
	my $vendor = $hTable -> {'vendor'} -> {$vendorID};
	my $devicelist = $vendor -> {'device'};
	my $deleteDevice = 0;
	my $driverlist;

	foreach my $deviceID (sort keys %$devicelist) {
	    my $device = $devicelist -> {$deviceID};

	    if (defined $device -> {'vmware'}) {
		$driverlist = $device -> {'vmware'} -> {'driver'};

		foreach my $driver (@$driverlist) {
		    my $vendorSnippet = getVendorSnippet ($drivers, $driver,$vendorID);
		    my $deviceSnippet = getDeviceSnippet ($vendorSnippet, $vendor, $deviceID);
		    setupVmware ($deviceSnippet, $device, $driver, @$driverlist);
		    setupTable ($deviceSnippet, $device, $driver);
		    $deleteDevice = 1;
		}

		delete $devicelist -> {$deviceID};
	    }
	}

	if ((scalar keys %$devicelist) == 0) {
	    delete $vendor -> {'device'};
	}
    }

    foreach my $driver (sort keys %$drivers) {
	writeXML ("$driver.xml", $drivers -> {$driver});
    }
}

sub checkForSuppressFile {
    my $fh;
    
    # If /tmp/suppress-esxcfg-pciid exists and contains a valid
    # PID, then refuse to execute. This is used by the installer
    # to handle mass RPM transactions.
    #
    # Also print a warning to STDERR in case the user somehow
    # ends up with this file on an otherwise normal system.
    
    open ($fh, "<$suppress_file") or return;
    my $pid = <$fh>;
    close ($fh);

    chomp ($pid);

    if ($pid && $pid =~ /^\d+$/ && -d "/proc/$pid") {
        unless ($quiet) {
                print STDERR "Suppressing normal execution due to existence\n";
                print STDERR "of $suppress_file. You must remove this file\n";
                print STDERR "and re-run esxcfg-pciid if you wish to continue.\n";
        }
        exit(0);
    }
}

sub main {
    checkForSuppressFile ();

    $hTable = {};

    mergeTables ($hTable, $file_xml);

    if (!$extract) {

	my $snippetsProcessed = {};

	my @snippets = glob ("$dir/pciid/*.xml");

	foreach my $snippetname (sort @snippets) {
	    chomp $snippetname;
	    my ($path, $filename) = $snippetname =~ m:(.*)/(.+)$:;

	    if ($filename ne 'pci.xml') {
		$snippetsProcessed -> {$filename} = 1;
		mergeTables ($hTable, $snippetname);
	    }
	}

	my @legacy = ();

	if (-d "$dir/pciid/legacy") {
	    @legacy = glob ("$dir/pciid/legacy/*.xml");
	}

	foreach my $snippetname (sort @legacy) {
	    chomp $snippetname;
	    my ($path, $filename) = $snippetname =~ m:(.*)/(.+)$:;

	    if (($filename ne 'pci.xml') and (!exists $snippetsProcessed -> {$filename})) {
		mergeTables ($hTable, $snippetname);
	    }
	}

    } else {
	extractAllDriverSnippets ();
    }

    writeXML ("$file_xml.merged", $hTable);

    if (!$extract) {
	makePciIdsFile($file_ids);

	foreach (@file_pcitable) {
	    makePcitableFile($_);
	}

	makeVmwareMapFile($file_map);
    }

    copy ("$dir/pci.ids", '/usr/share/hwdata/pci.ids') or die "copy of pci.ids to /usr/share/hwdata failed at";
    copy ("$dir/pcitable", '/usr/share/hwdata/pcitable') or die "copy of pcitable to /usr/share/hwdata failed at";

    if ($boot) {
	print "rebuilding initrd ... ";
	my $bootStatus = `esxcfg-boot --sched-rdbuild 2>&1`;

	if ($bootStatus =~ /Unknown option/) {
	    $bootStatus = `esxcfg-boot -b 2>&1`;
	}

	if ($bootStatus) {
	    print "[exception detected]\n$bootStatus\n";
	} else {
	    print "[done]\n";
	}
    }

#    print Dumper($hTable);
}

#
# === Main function: Read in XML, parse options, etc.
#

# disable output buffering, so messages get written in sync
$| = 1;

# check for XML file presence & errors
(-e $file_xml)
    or die "Cannot find $file_xml!\n";

# don't do anything more if output files are newer than pci.xml
my @outfiles = (@file_pcitable, $file_ids, $file_map);
foreach (@outfiles) {

    # if file doesn't exist or older than pci.xml, then it's worth it
    # to read in pci.xml and regenerate files.  XMLin() takes a couple
    # seconds even on faster machines.
#   if (!(-e $_) or ((-M $_) > (-M $file_xml))) {
        main();
        exit(0);
#   }
}

print "Nothing to be done.\n";
