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

Roman Kagan rkagan at virtuozzo.com
Mon Jan 25 19:57:47 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.

Note that if there happen to be multiple packages of the same driver
which claim compatibility with a particular architecture and windows
flavor, the one with the most recent timestamp takes over (where the
timestamp is either the signing time, if present, or the catalog
creation time).

Signed-off-by: Roman Kagan <rkagan at virtuozzo.com>
---
changes since v2:
 - compare the timestamps to decide if the package is to replace the
   already copied one (the most recent wins)
 - minor refactoring for better readability

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 | 238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 238 insertions(+)
 create mode 100644 util/cpdrivers.py

diff --git a/util/cpdrivers.py b/util/cpdrivers.py
new file mode 100644
index 0000000..7d273ea
--- /dev/null
+++ b/util/cpdrivers.py
@@ -0,0 +1,238 @@
+#!/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 itertools import chain
+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 maxTimestamp(attributes):
+    return max(chain(attributes['signingTimes'], [attributes['timestamp']]))
+
+
+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:%d.%d' % 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'])
+
+    dstsubdirs = set(os.path.join(osMap[o][1], osMap[o][2]) for o in oses)
+    timestamp = maxTimestamp(attributes)
+    return dstsubdirs, timestamp
+
+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 isCatNewer(catfile, timestamp):
+    if not os.path.exists(catfile):
+        return False
+    attributes, _ = parseCat(catfile)
+    return maxTimestamp(attributes) > timestamp
+
+
+def copyDriver(drvdir, pkgname, dstroot):
+    print("# processing %s" % drvdir)
+    dstsubdirs, timestamp = processCat(drvdir, pkgname + ".cat")
+    for d in dstsubdirs:
+        dstdir = os.path.join(dstroot, d)
+        doMkdir(dstdir)
+        pkgdir = os.path.join(dstdir, pkgname)
+        if isCatNewer(os.path.join(pkgdir, pkgname + ".cat"), timestamp):
+            print("# %s is newer than %s: skipping" % (pkgdir, drvdir))
+        else:
+            cpTree(drvdir, pkgdir)
+
+
+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