[virt-tools-list] [PATCH RFC virtio-win-pkg-scripts v2 2/2] add script to lay out drivers based on their catalog files

Roman Kagan rkagan at virtuozzo.com
Fri Jan 22 15:05:03 UTC 2016


This script recursively discovers the driver packages (that is, a
directory containing an .inf file, an .cat file, and possibly other
files) in the source directory.  For each such package, it parses the
catalog file and determines what architectures and windows flavors this
driver is suitable for.  Then it copies or links the driver into
appropriate subdirectories of the destination directory.

As a byproduct it also verifies signatures where it can.

The resulting structure is supposed to be convenient for consupmtion by
both humans and programs.

Signed-off-by: Roman Kagan <rkagan at virtuozzo.com>
---
changes since v1:
 - fix pep8 warnings except for spaces in osMap which I want to preserve
   fore nicer alignment
 - add debugging print of what driver package is being processed
 - switch to argparse
 - pass all parsed args to copyDrivers() to facilitate using as a module

 util/cpdrivers.py | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 220 insertions(+)
 create mode 100644 util/cpdrivers.py

diff --git a/util/cpdrivers.py b/util/cpdrivers.py
new file mode 100644
index 0000000..09b2b63
--- /dev/null
+++ b/util/cpdrivers.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+#
+# Copyright 2016 Parallels IP Holdings GmbH
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+"""
+Recurse the source directory and, for every subdirectory containing a windows
+driver (i.e. an .inf, an .cat, and a few other files), copy or link it into a
+subdirectory corresponding to the arch/os flavor the driver is suitable for.
+
+The suitability is based on the info extracted from the .cat file, so
+re-signing affects that as expected.
+"""
+
+import os
+import sys
+import hashlib
+import struct
+import mmap
+import shutil
+import argparse
+from parsecat import parseCat
+
+osMap = {
+    'XPX86':                ( '5.1',    'x86',  'xp'     ),
+    'XPX64':                ( '5.2',  'amd64',  'xp'     ),
+    'Server2003X86':        ( '5.2',    'x86',  '2k3'    ),
+    'Server2003X64':        ( '5.2',  'amd64',  '2k3'    ),
+    'VistaX86':             ( '6.0',    'x86',  'vista'  ),
+    'VistaX64':             ( '6.0',  'amd64',  'vista'  ),
+    'Server2008X86':        ( '6.0',    'x86',  '2k8'    ),
+    'Server2008X64':        ( '6.0',  'amd64',  '2k8'    ),
+    '7X86':                 ( '6.1',    'x86',  'w7'     ),
+    '7X64':                 ( '6.1',  'amd64',  'w7'     ),
+    'Server2008R2X64':      ( '6.1',  'amd64',  '2k8R2'  ),
+    '8X86':                 ( '6.2',    'x86',  'w8'     ),
+    '8X64':                 ( '6.2',  'amd64',  'w8'     ),
+    'Server2012X64':        ( '6.2',  'amd64',  '2k12'   ),
+    '_v63':                 ( '6.3',    'x86',  'w8.1'   ),
+    '_v63_X64':             ( '6.3',  'amd64',  'w8.1'   ),
+    '_v63_Server_X64':      ( '6.3',  'amd64',  '2k12R2' ),
+    '_v100':                ('10.0',    'x86',  'w10'    ),
+    '_v100_X64':            ('10.0',  'amd64',  'w10'    ),
+}
+
+
+def calcFileHash(data, hashobj):
+    hashobj.update(data)
+
+
+def calcPEHash(data, hashobj):
+    # DOS header: magic, ..., PE header offset
+    mz, pehdr = struct.unpack_from("2s58xI", data, 0)
+    assert mz == b'MZ'
+    # PE header: magic, ..., magic in optional header
+    pe, pemagic = struct.unpack_from("4s20xH", data, pehdr)
+    assert pe == b'PE\0\0'
+    # security directory entry in optional header
+    secdir = pehdr + {
+        0x10b: 152,    # PE32
+        0x20b: 168     # PE32+
+        }[pemagic]
+    sec, seclen = struct.unpack_from("2I", data, secdir)
+    # signature is always the tail part
+    assert sec + seclen == len(data)
+
+    hashobj.update(data[:pehdr + 88])        # skip checksum
+    hashobj.update(data[pehdr + 92:secdir])  # skip security directory entry
+    hashobj.update(data[secdir + 8:sec])     # skip signature
+
+
+def vrfySig(fname, kind, algo, digest):
+    meth = {
+        'spcLink': calcFileHash,
+        'spcPEImageData': calcPEHash
+        }[kind]
+
+    fd = os.open(fname, os.O_RDONLY)
+    m = mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
+    os.close(fd)
+
+    h = hashlib.new(algo)
+    meth(m, h)
+    m.close()
+    assert h.digest() == digest
+
+
+# locate file case-insensitively
+def casedFname(dname, fname):
+    fns = [f for f in os.listdir(dname) if f.lower() == fname.lower()]
+    assert len(fns) == 1
+    return fns[0]
+
+
+def processCat(dname, catname):
+    catname = casedFname(dname, catname)
+
+    attributes, members = parseCat(os.path.join(dname, catname))
+    oses = attributes['OS'].split(',')
+
+    # validate catalog members just because we can
+    kernels = set(('2:' + osMap[o][0]) for o in oses)
+
+    for member in members:
+        fn = casedFname(dname, member['File'])
+
+        assert kernels.issubset(member['OSAttr'].split(','))
+
+        sig = member.get('signature')
+        if sig:
+            vrfySig(os.path.join(dname, fn),
+                    sig['kind'], sig['digestAlgorithm'], sig['digest'])
+
+    return set(os.path.join(osMap[o][1], osMap[o][2]) for o in oses)
+
+dryrun = True
+cpTree = None
+cp = None
+
+
+def doMkdir(d):
+    print("mkdir -p \"%s\"" % d)
+    if not dryrun and not os.path.isdir(d):
+        os.makedirs(d)
+
+
+def doCopy(src, dst):
+    print("cp -f \"%s\" \"%s\"" % (src, dst))
+    if not dryrun:
+        if os.path.exists(dst):
+            os.unlink(dst)
+        shutil.copy(src, dst)
+
+
+def doLink(src, dst):
+    print("ln -f \"%s\" \"%s\"" % (src, dst))
+    if not dryrun:
+        if os.path.exists(dst):
+            os.unlink(dst)
+        os.link(src, dst)
+
+
+def doSymlink(src, dst):
+    if not os.path.isabs(src):
+        src = os.path.relpath(src, os.path.dirname(dst))
+    print("ln -sf \"%s\" \"%s\"" % (src, dst))
+    if not dryrun:
+        if os.path.exists(dst):
+            os.unlink(dst)
+        os.symlink(src, dst)
+
+
+def cpRecursive(src, dst):
+    doMkdir(dst)
+    for f in os.listdir(src):
+        fsrc = os.path.join(src, f)
+        fdst = os.path.join(dst, f)
+        if os.path.isdir(fsrc):
+            cpRecursive(fsrc, fdst)
+        else:
+            cp(fsrc, fdst)
+
+
+def copyDriver(drvdir, pkgname, dstroot):
+    print("# processing %s" % drvdir)
+    dstsubdirs = processCat(drvdir, pkgname + ".cat")
+    for d in dstsubdirs:
+        dstdir = os.path.join(dstroot, d)
+        doMkdir(dstdir)
+        cpTree(drvdir, os.path.join(dstdir, pkgname))
+
+
+modes = {
+    'copy':       (cpRecursive, doCopy),
+    'link':       (cpRecursive, doLink),
+    'symlink':    (cpRecursive, doSymlink),
+    'link-dirs':  (doSymlink,   None)
+}
+
+
+def updateMode(mode, dry_run):
+    global cpTree
+    global cp
+    cpTree, cp = modes[mode]
+    global dryrun
+    dryrun = dry_run
+
+
+def copyDrivers(srcroot, dstroot, mode, dryrun):
+    updateMode(mode, dryrun)
+    for root, dirs, files in os.walk(srcroot):
+        for f in files:
+            fn, fe = os.path.splitext(f)
+            if fe.lower() == ".inf":
+                copyDriver(root, fn, dstroot)
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='Lay out Windows drivers',
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument('srcroot',
+                        help='original directory tree with drivers')
+    parser.add_argument('dstroot',
+                        help='where to lay out drivers canonically')
+    parser.add_argument("-m", "--mode",
+                        choices=modes.keys(),
+                        default='copy',
+                        help="how to put data in destination")
+    parser.add_argument("-n", "--dry-run", action="store_true", default=False,
+                        help="only print what would be done")
+
+    args = parser.parse_args()
+
+    copyDrivers(args.srcroot, args.dstroot, args.mode, args.dry_run)
+
+
+if __name__ == "__main__":
+    main()
-- 
2.5.0




More information about the virt-tools-list mailing list