#!/usr/bin/perl
#
# This module parses NT .INF files containing printer driver information.
# The information is turned into a magical set of data strucutures
# allowing the driver programs to do something useful with the printer
# driver.

package inf_nt;

use strict;
use vars qw($VERSION @ISA @EXPORT_OK);

use Carp qw(carp croak);
use INF qw(inf_get_key inf_get_keys inf_has_key);

require Exporter;

$VERSION = "0.01";
@ISA = qw(Exporter);
@EXPORT_OK = qw(parse_inf_nt get_models);


# Fill in the info hash with information on what the destination directories
# for the different file sections will be. The default destination directory
# is also determined here.

sub fill_destination_dirs
{
   croak("fill_destination_dirs(INF,INFO)") if @_ != 2;
   my($inf, $info) = @_;

   # The DestDir member will hold the destination for any file section.
   $info->{"DestDir"} = {};

   # If a DestinationDirs section isn't present, bail out.
   if (not inf_has_key($inf,"DestinationDirs")) {
       $info->{"DefaultDestDir"} = "66000";
       return;
   }

   # Find and handle a DefaultDestDir if it is present.
   if (exists $inf->{"DestinationDirs"}{"DefaultDestDir"}) {
       my($num, $subdir) = split(/,/, $inf->{"DestinationDirs"}{"DefaultDestDir"});
       $info->{"DefaultDestDir"} = $num;
       $info->{"DefaultDestDir"} .= "\\$subdir" if $subdir ne '';
   }

   # Loop for each file section and add it to the DestDir hash.
   foreach my $key (keys %{$inf->{"DestinationDirs"}}) {
       next if $key eq 'DefaultDestDir';

       my $str = $inf->{"DestinationDirs"}{$key};
       my($num, $subdir) = split(/,/, $str);

       my $value = $num;
       $value .= "\\$subdir" if $subdir ne '';

       $info->{"DestDir"}{$key} = $value;
   }
}

# The files to be copied for a particular driver are stored as hash refs
# inside an array ref. Each hash ref corresponds to a single file. The
# array ref stores all the files that will be copied.

sub handle_file
{
   croak("handle_file(INFO,SRC_FILENAME,DST_FILENAME,DESTDIR)") if @_ != 4;
   my($info, $src_filename, $dst_filename, $destdir) = @_;

   # Create the CopyFiles node if it doesn't exists yet.
   if (not exists $info->{"CopyFiles"}) {
       $info->{"CopyFiles"} = [];
   }

   my $filehash = {};
   $filehash->{"SrcFilename"} = $src_filename;
   $filehash->{"DstFilename"} = $dst_filename;
   $filehash->{"DestDir"} = $destdir;

   push(@{$info->{"CopyFiles"}}, $filehash);
}

sub handle_CopyFiles
{
   croak("handle_CopyFiles(INFO,INF,FILELIST)") if @_ != 3;
   my($info, $inf, $filelist) = @_;

   foreach my $specifier (split(/\s*,\s*/, $filelist)) {
       if ($specifier =~ /^@/) {
           # A single file was specified.

           my $fname = substr($specifier, 1);
           handle_file($info, $fname, $fname, $info->{DefaultDestDir});
       } else {
           # An entire section containing files to copy was specified.
           if (not exists $inf->{$specifier}) {
               print "CopyFiles wants section $specifier not present.\n";
               exit(1);
           }
           foreach my $key (keys %{$inf->{$specifier}}) {
               my($dst_filename, $src_filename) = split(/\s*,\s*/, $key);
               my $destdir;

               if (exists $info->{"DestDir"}{$specifier}) {
                   $destdir = $info->{"DestDir"}{$specifier};
               } else {
                   $destdir = $info->{DefaultDestDir};
               }

               $dst_filename = $src_filename if
                   $dst_filename eq '' and $src_filename ne '';
               $src_filename = $dst_filename if $src_filename eq '';

               handle_file($info, $src_filename, $dst_filename, $destdir);
           }
       }
   }
}

sub handle_AddReg
{
   croak("handle_AddReg(INFO, SECTION_HASH)") if @_ != 2;
   my($info, $section) = @_;

   foreach my $entry (keys %{$section}) {
       my @data = split(/\s*,\s*/, $entry);
       if (not exists $info->{"AddReg"}) {
           $info->{"AddReg"} = [];
       }
       push(@{$info->{"AddReg"}}, [ @data ]);
   }
}

sub handle_DelReg
{
   croak("handle_DelReg(INFO, SECTION_HASH)") if @_ != 2;
   my($info, $section) = @_;

   foreach my $entry (keys %{$section}) {
       my @data = split(/\s*,\s*/, $entry);
       if (not exists $info->{"DelReg"}) {
           $info->{"DelReg"} = [];
       }
       push(@{$info->{"DelReg"}}, [ @data ]);
   }
}

sub handle_SourceDisksNames
{
   croak("handle_SourceDisksNames(INFO,SECTION_HASH)") if @_ != 2;
   my($info, $section) = @_;

   foreach my $ordinal (keys %{$section}) {
       my($descr, $tagfile, undef, $path) =
           split(/\s*,\s*/, $section->{$ordinal});

       if (not exists $info->{"SourceDisksNames"}) {
           $info->{"SourceDisksNames"} = {};
       }

       # If the ordinal already exists, skip over to allow overrides by
       # the platform-specific sections.
       next if exists $info->{"SourceDisksNames"}->{$ordinal};

       # Insert an entry for this source disk.
       $info->{"SourceDisksNames"}->{$ordinal} = {
           'description' => $descr,
           'tagfile' => $tagfile,
           'path' => $path
       };
   }
}

sub handle_SourceDisksFiles
{
   croak("handle_SourceDisksFiles(INFO,SECTION_HASH)") if @_ != 2;
   my($info, $section) = @_;

   foreach my $fname (keys %{$section}) {
       my($ordinal, $subdir, $size) = split(/\s*,\s*/, $section->{$fname});
       $fname =~ tr/a-z/A-Z/;

       if (not exists $info->{"SourceDisksFiles"}) {
           $info->{"SourceDisksFiles"} = {};
       }

       # If the ordinal already exists, skip over to allow overrides by
       # the platform-specific sections.
       next if exists $info->{"SourceDisksFiles"}->{$fname};

       # Insert an entry for this source file.
       $info->{"SourceDisksFiles"}->{$fname} = {
           'ordinal' => $ordinal
       };
   }
}

sub get_models
{
   croak("get_manufacturers(INF, MANUFACTURER)") if @_ != 2;
   my($inf, $manufacturer) = @_;
   my($device_section_name);

   if (inf_has_key(inf_get_key($inf, "Manufacturer"), $manufacturer)) {
       if (inf_get_keys($inf, "Manufacturer", $manufacturer) eq '') {
           $device_section_name = $manufacturer;
       } else {
           $device_section_name = inf_get_keys($inf, "Manufacturer", $manufacturer);
       }
   } else {
       return undef;
   }

   if (inf_has_key($inf, $device_section_name)) {
       return keys %{inf_get_key($inf, $device_section_name)};
   } else {
       return undef;
   }
}

sub parse_inf_nt
{
   croak("parse_inf_nt(INF,MANUFACTURER,MODEL,ARCH)") if @_ != 4;
   my($inf, $manufacturer, $model, $arch) = @_;
   my(%info, $device_section_name);

   # Make sure that the [Manufacturer] section exists.
   if (not inf_has_key($inf, "Manufacturer")) {
       print STDERR "[Manufacturer] section not present.\n";
       return undef;
   }

   # Make sure that the manufacturer is actually listed.
   if (inf_has_key(inf_get_key($inf, "Manufacturer"), $manufacturer)) {
       if (inf_get_key(inf_get_key($inf, "Manufacturer"), $manufacturer) eq '') {
           $device_section_name = $manufacturer;
       } else {
           $device_section_name = inf_get_key(inf_get_key($inf, "Manufacturer"), $manufacturer);
       }
   } else {
       print STDERR "ERROR: Manufacturer $manufacturer not listed.\n";
       return undef;
   }

   # We now know the name of the device section. Try to find the device that
   # the caller gave us in this section.
   if (not inf_has_key($inf, $device_section_name)) {
       print STDERR "Device section `$device_section_name' does not exist.\n";
       return undef;
   }
   if (not inf_has_key(inf_get_key($inf, $device_section_name), $model)) {
       print STDERR "Specified model ($model) not present in .INF file.\n";
       return undef;
   }

   # This model exists so fill in the device name.
   $info{pName} = $model;

   # Figure out the name of the install section. The install section is the
   # heart of the .INF file and determines all actions to be taken.
   my($install_section_name, @DEVICE_IDS) = split(/\s*,\s*/, inf_get_key(inf_get_key($inf, $device_section_name), $model));
   if ($install_section_name eq '') {
       print STDERR "Install section name is blank.\n";
       return undef;
   }

   # Determine if this INF is for NT.
   my $is_NT = 0;
   $is_NT = 1 if $arch =~ /^W32X86$/i;
   $is_NT = 1 if $arch =~ /^W32alpha$/i;
   $is_NT = 1 if $arch =~ /^W32mips$/i;
   $is_NT = 1 if $arch =~ /^W32ppc$/i;

   # Make sure the install section exists.
   if (not inf_has_key($inf, $install_section_name)
       and not inf_has_key($inf, $install_section_name . ".nt")
       and not inf_has_key($inf, $install_section_name . ".ntx86")) {
       print STDERR "Install section ($install_section_name) not present.\n";
       return undef;
   }

   my $install_section;
   if ($is_NT and inf_has_key($inf, $install_section_name . ".ntx86")) {
       $install_section = inf_get_key($inf, $install_section_name . ".ntx86");
   } elsif ($is_NT and inf_has_key($inf, $install_section_name . ".nt")) {
       $install_section = inf_get_key($inf, $install_section_name . ".nt");
   } else {
       $install_section = inf_get_key($inf, $install_section_name);
   }

   # See if there is an optional data section.
   my $data_section = {};
   if (inf_has_key($install_section, "DataSection")) {
       if (not inf_has_key($inf, inf_get_key($install_section, "DataSection"))) {
           print STDERR "DataSection directive given but section missing.\n";
           return undef;
       }
       $data_section = inf_get_key($inf, inf_get_key($install_section, "DataSection"));
   }

   # Handle the LanguageMonitor directive.
   if (inf_has_key($data_section, "LanguageMonitor")) {
       $info{"LanguageMonitor"} = inf_get_key($data_section, "LanguageMonitor");
   } elsif (inf_has_key($install_section, "LanguageMonitor")) {
       $info{"LanguageMonitor"} = inf_get_key($install_section, "LanguageMonitor");
   }

   # Handle the DriverFile directive.
   if (inf_has_key($data_section, "DriverFile")) {
       $info{"DriverFile"} = inf_get_key($data_section, "DriverFile");
   } elsif (inf_has_key($install_section, "DriverFile")) {
       $info{"DriverFile"} = inf_get_key($install_section, "DriverFile");
   }
   if ($info{"DriverFile"} eq '') {
       $info{"DriverFile"} = $install_section_name;
   }

   # Handle the DataFile directive.
   if (inf_has_key($data_section, "DataFile")) {
       $info{"DataFile"} = inf_get_key($data_section, "DataFile");
   } elsif (inf_has_key($install_section, "DataFile")) {
       $info{"DataFile"} = inf_get_key($install_section, "DataFile");
   }
   if ($info{"DataFile"} eq '') {
       $info{"DataFile"} = $install_section_name;
   }

   # Handle the ConfigFile directive.
   if (inf_has_key($data_section, "ConfigFile")) {
       $info{"ConfigFile"} = inf_get_key($data_section, "ConfigFile");
   } elsif (inf_has_key($install_section, "ConfigFile")) {
       $info{"ConfigFile"} = inf_get_key($install_section, "ConfigFile");
   }
   ## Windows NT always splits the driver and config file up
   ## Windows 9x places them in the same file
   if ($info{"ConfigFile"} eq '') {
       # $info{"ConfigFile"} = $install_section_name;
       $info{"ConfigFile"} = $info{"DriverFile"};
   }

   # Handle the HelpFile directive.
   if (inf_has_key($data_section, "HelpFile")) {
       $info{"HelpFile"} = inf_get_key($data_section, "HelpFile");
   } elsif (inf_has_key($install_section, "HelpFile")) {
       $info{"HelpFile"} = inf_get_key($install_section, "HelpFile");
   }

   # Parse the destination directory information.
   fill_destination_dirs($inf, \%info);

   # Handle any files that need to be copied.
   if (inf_has_key($data_section, "CopyFiles")) {
       handle_CopyFiles(\%info, $inf, inf_get_key($data_section, "CopyFiles"));
   }
   if (inf_has_key($install_section, "CopyFiles")) {
       handle_CopyFiles(\%info, $inf, inf_get_key($install_section, "CopyFiles"));
   }

   # Handle any AddReg sections.
   if (inf_has_key($install_section, "AddReg")) {
       foreach my $addreg_name (split(/\s*,\s*/,
                                inf_get_key($install_section, "AddReg"))) {
           if (not inf_has_key($inf, $addreg_name)) {
               print STDERR "ERROR: Section given in AddReg doesn't exist.\n";
               return undef;
           }
           handle_AddReg(\%info, inf_get_key($inf, $addreg_name));
       }
   }

   # Handle any DelReg sections.
   if (inf_has_key($install_section, "DelReg")) {
       foreach my $delreg_name (split(/\s*,\s*/,
                                inf_get_key($install_section, "DelReg"))) {
           if (not inf_has_key($inf, $delreg_name)) {
               print STDERR "ERROR: Section given in DelReg doesn't exist.\n";
               return undef;
           }
           handle_DelReg(\%info, inf_get_key($inf, $delreg_name));
       }
   }

   # Handle any SourceDisksNames sections.
   if (inf_has_key($inf, "SourceDisksNames.x86")) {
       handle_SourceDisksNames(\%info, inf_get_key($inf, "SourceDisksNames.x86"));
   }
   if (inf_has_key($inf, "SourceDisksNames")) {
       handle_SourceDisksNames(\%info, inf_get_key($inf, "SourceDisksNames"));
   }

   # Handle any SourceDisksFiles section.
   if (inf_has_key($inf, "SourceDisksFiles.x86")) {
       handle_SourceDisksFiles(\%info, inf_get_key($inf, "SourceDisksFiles.x86"));
   }
   if (inf_has_key($inf, "SourceDisksFiles")) {
       handle_SourceDisksFiles(\%info, inf_get_key($inf, "SourceDisksFiles"));
   }

   return %info;
}

1;