#! /usr/local/bin/python2.3
# -*- Mode: Python; tab-width: 4 -*-
#
# Inf Driver parser
#
# Copyright (C) 2005-2007 Gianluigi Tiesi <sherpya@netfarm.it>
# 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, 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 MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
# ======================================================================

from codecs import utf_16_le_decode, BOM_LE, BOM_BE
from sys import argv, exit as sys_exit
from os.path import isfile
from glob import glob
from cPickle import dump

__version__ = '0.9'

### Compatibility with python 2.1
if getattr(__builtins__, 'True', None) is None:
    True=1
    False=0

class_guids = ['{4d36e972-e325-11ce-bfc1-08002be10318}']
classes = ['net']

exclude = ['layout.inf', 'drvindex.inf', 'netclass.inf']

debug = 0
dumpdev = 0

bustype = { 'USB'   :  1,
            'PCI'   :  5,
            'PCMCIA':  8,
            'ISAPNP': 14
            }

def csv2list(value):
    values = value.strip().split(',')
    for i in range(len(values)):
        values[i] = values[i].strip()
    return values

def str_lookup(dc, c_key):
    for key in dc.keys():
        if key.lower() == c_key.lower():
            if len(dc[key])>0:
                return dc[key].pop()
    return 'NoDesc'

def item_lookup(dc, c_key):
    for key in dc.keys():
        if key.lower() == c_key.lower():
            return dc[key]
    return None

def fuzzy_lookup(strlist, pattern, ends=None):
    for s in strlist:
        if ends is not None and not s.endswith('services'): continue
        if s.startswith(pattern): return s
    return None


def unquote(text):
    return ''.join(text.split('"'))

def skip_inf(line):
    ## Check if driver is requested
    if line.find('=') == -1: return False
    key, value = line.split('=', 1)
    key = key.strip().lower()
    value = value.strip().lower()
    if key == 'class' and value not in classes: return True
    if key == 'classguid' and value not in class_guids: return True
    return False

def parse_line(sections, secname, lineno, line):
    equal = line.find('=')
    comma = line.find(',')
    if equal + comma != -2:
        if equal == -1:
            equal = comma+1
        if comma == -1:
            comma = equal+1

    if debug > 2: print '[%d] [%s] equal = %d - comma = %d' % (lineno, secname, equal, comma)

    if len(line) + equal + comma == -1:
        if debug: print '[%d] [%s] Invalid line' % (lineno, secname)
        return True

    ### Values
    if equal < comma:
        if type(sections[secname]) != type({}):
            sections[secname] = {}
        section = sections[secname]
        key, value = line.split('=', 1)
        key = key.strip()

        ### SkipList
        if key == '0':return True

        if section.has_key(key):
            values = csv2list(value)
            ### SkipList
            if (len(values) < 2) or (value.find('VEN_') == -1) or (value.find('DEV_') == -1):
                return True
            oldkey = key
            key = key + '_dev_' + values[1]

            if debug > 1: print '[%d] [%s] Duplicate key %s will be renamed to %s' % \
               (lineno, secname, oldkey, key)

        if secname == 'manufacturer':
            mlist = value.strip().split(',')
            mf = mlist[0].strip().lower()
            if len(mlist) > 1:
                ml = []
                for m in mlist[1:]:
                    ml.append('.'.join([mf, m.strip().lower()]))
                mlist = [mf] + ml
            else:
                mlist = [mf]

            if debug > 0: print 'Preprocessing Manifacturers:', ', '.join(mlist)
            section[key] = mlist
            if debug > 0: print 'Manifacturer %s=%s' % (key, section[key])
            return True

        section[key] = csv2list(value)
        if debug > 1: print '[K] [%d] [%s] %s=%s' % (lineno, secname, key, section[key])
        return True

    values = csv2list(line)
    if debug > 1: print '[V] [%d] [%s] Values = %s' % (lineno, secname, ','.join(values))
    sections[secname] = values
    return True

def parse_inf(filename):
    lineno = 0
    name = ''
    sections = {}
    section = None
    data = open(filename).read()

    ## Cheap Unicode to ascii
    if data[:2] == BOM_LE or data[:2] == BOM_BE:
        data = utf_16_le_decode(data)[0]
        data = data.encode('ascii', 'ignore')

    ## De-inf fixer ;)
    data = 'Copy'.join(data.split(';Cpy'))
    data = '\n'.join(data.split('\r\n'))
    data = ''.join(data.split('\\\n'))

    for line in data.split('\n'):
        lineno = lineno + 1
        line = line.strip()
        line = line.split(';', 1)[0]
        line = line.strip()

        if len(line) < 1: continue # empty lines

        if line[0] == ';': continue # comment

        ## We only need network drivers
        if name == 'version' and skip_inf(line):
            if debug > 0: print 'Skipped %s not a network inf' % filename
            return None

        ## Section start
        if line.startswith('[') and line.endswith(']'):
            name = line[1:-1].lower()
            sections[name] = {}
            section = sections[name]
        else:
            if section is None: continue
            if not parse_line(sections, name, lineno, line):
                break
    return sections

def scan_inf(filename):
    if debug > 0: print 'Parsing ', filename
    inf = parse_inf(filename)
    if inf is None: return {}

    devices = {}
    if inf and inf.has_key('manufacturer'):
        devlist = []
        for sections in inf['manufacturer'].values():
            devlist = devlist + sections
        if debug > 0: print 'Devlist:', ', '.join(devlist)
        for devmap in devlist:
            devmap_k = unquote(devmap.lower())
            if not inf.has_key(devmap_k):
                if debug > 0: print 'Warning: missing [%s] driver section in %s, ignored' % (devmap, filename)
                continue
            devmap = devmap_k
            for dev in inf[devmap].keys():
                if dev.find('%') == -1: continue # bad infs

                device = dev.split('%')[1]
                desc = unquote(str_lookup(inf['strings'], device))

                sec = inf[devmap][dev][0]
                hid = inf[devmap][dev][1]
                sec = sec.lower()

                hid = hid.upper()

                if inf.has_key(sec):
                    mainsec = sec
                else:
                    mainsec = fuzzy_lookup(inf.keys(), sec)
                    if mainsec is None: continue

                if inf.has_key(mainsec + '.services'):
                    serv_sec = mainsec + '.services'
                else:
                    serv_sec = fuzzy_lookup(inf.keys(), mainsec, '.service')
                    if serv_sec is None:
                        if debug > 0: print 'Service section for %s not found, skipping...' % mainsec
                        continue

                if devices.has_key(hid): continue # Multiple sections define same devices

                if dumpdev: print 'Desc:', desc
                if dumpdev: print 'hid:', hid

                tmp = item_lookup(inf[serv_sec], 'addservice')
                service = tmp[0]
                sec_service = tmp[2]

                driver = None
                if (type(inf[mainsec]) == type({})
                    and inf[mainsec].has_key('copyfiles')):
                    sec_files = inf[mainsec]['copyfiles'][0].lower()
                    if type(inf[sec_files]) == type([]):
                        driver = inf[sec_files][0]

                if driver is None:
                    driver = inf[sec_service.lower()]['ServiceBinary'][0].split('\\').pop()

                if dumpdev: print 'Driver', driver

                try:
                    char = eval(inf[mainsec]['Characteristics'][0])
                except:
                    char = 132

                if dumpdev: print 'Characteristics', char
                try:
                    btype = int(inf[mainsec]['BusType'][0])
                except:
                    try:
                        btype = bustype[hid.split('\\')[0]]
                    except:
                        btype = 0

                if dumpdev: print 'BusType', btype
                if dumpdev: print 'Service', service
                if dumpdev: print '-'*78


                devices[hid] = { 'desc' : desc,
                                 'char' : str(char),
                                 'btype': str(btype),
                                 'drv'  : driver,
                                 'svc'  : service,
                                 'inf'  : filename.split('/').pop() }
    return devices


if __name__ == '__main__':
    if len(argv) != 2:
        print 'Usage %s: directory_with_infs or inf file' % argv[0]
        sys_exit(-1)

    if isfile(argv[1]):
        filelist = [ argv[1] ]
    else:
        filelist = glob(argv[1] + '/*.inf')

    devlist = {}
    for inffile in filelist:
        if inffile.split('/').pop() not in exclude:
            devlist.update(scan_inf(inffile))

    fd = open('/var/db/devlist.cache', 'w')
    dump(devlist, fd)
    fd.close()


syntax highlighted by Code2HTML, v. 0.9.1