383126
#! /usr/bin/python
383126
# SPDX-License-Identifier: CC0-1.0
383126
383126
import argparse
383126
import errno
383126
import fnmatch
383126
import io
383126
import itertools
383126
import os
383126
import re
383126
import shutil
383126
import struct
383126
import sys
383126
import tarfile
383126
import tempfile
383126
from subprocess import PIPE, Popen, STDOUT
383126
383126
# Python 3 shims
383126
try:
383126
    from functools import reduce
383126
except:
383126
    pass
383126
try:
383126
    from itertools import zip_longest as izip_longest
383126
except:
383126
    from itertools import izip_longest
383126
383126
# revs:
383126
# [ { "path", "cpuid", "pf", "rev", "date" } ]
383126
383126
# artifacts:
383126
#  * content summary (per-file)
383126
#   * overlay summary (per-fms/pf)
383126
#  * changelog (per-file?)
383126
#  * discrepancies (per-fms/pf)
383126
383126
log_level = 0
383126
print_date = False
383126
file_glob = ["*??-??-??", "*microcode*.dat"]
383126
383126
383126
def log_status(msg, level=0):
383126
    global log_level
383126
383126
    if log_level >= level:
383126
        sys.stderr.write(msg + "\n")
383126
383126
383126
def log_info(msg, level=2):
383126
    global log_level
383126
383126
    if log_level >= level:
383126
        sys.stderr.write("INFO: " + msg + "\n")
383126
383126
383126
def log_warn(msg, level=1):
383126
    global log_level
383126
383126
    if log_level >= level:
383126
        sys.stderr.write("WARNING: " + msg + "\n")
383126
383126
383126
def log_error(msg, level=-1):
383126
    global log_level
383126
383126
    if log_level >= level:
383126
        sys.stderr.write("ERROR: " + msg + "\n")
383126
383126
383126
def remove_prefix(text, prefix):
383126
    if isinstance(prefix, str):
383126
        prefix = [prefix, ]
383126
383126
    for p in prefix:
383126
        pfx = p if p.endswith(os.sep) else p + os.sep
383126
        if text.startswith(pfx):
383126
            return text[len(pfx):]
383126
383126
    return text
383126
383126
383126
def file_walk(args, yield_dirs=False):
383126
    for content in args:
383126
        if os.path.isdir(content):
383126
            if yield_dirs:
383126
                yield ("", content)
383126
            for root, dirs, files in os.walk(content):
383126
                if yield_dirs:
383126
                    for f in dirs:
383126
                        p = os.path.join(root, f)
383126
                        yield (remove_prefix(p, content), p)
383126
                for f in files:
383126
                    p = os.path.join(root, f)
383126
                    yield (remove_prefix(p, content), p)
383126
        elif os.path.exists(content):
383126
            yield ("", content)
383126
        else:
383126
            raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), content)
383126
383126
383126
def cpuid_fname(c):
383126
    # Note that the Extended Family is summed up with the Family,
383126
    # while the Extended Model is concatenated with the Model.
383126
    return "%02x-%02x-%02x" % (
383126
        ((c >> 20) & 0xff) + ((c >> 8) & 0xf),
383126
        ((c >> 12) & 0xf0) + ((c >> 4) & 0xf),
383126
        c & 0xf)
383126
383126
383126
def read_revs_dir(path, args, src=None, ret=None):
383126
    if ret is None:
383126
        ret = []
383126
383126
    ucode_re = re.compile('[0-9a-f]{2}-[0-9a-f]{2}-0[0-9a-f]$')
383126
    ucode_dat_re = re.compile('microcode.*\.dat$')
383126
383126
    for rp, ap in file_walk([path, ]):
383126
        rp_fname = os.path.basename(rp)
383126
        if not ucode_re.match(rp_fname) and not ucode_dat_re.match(rp_fname):
383126
            continue
383126
383126
        # Text-based format
383126
        data = None
383126
        if ucode_dat_re.match(rp_fname):
383126
            data = io.BytesIO()
383126
            with open(ap, "r") as f:
383126
                for line in f:
383126
                    if line.startswith("/"):
383126
                        continue
383126
                    vals = line.split(",")
383126
                    for val in vals:
383126
                        val = val.strip()
383126
                        if not val:
383126
                            continue
383126
                        data.write(struct.pack("
383126
            sz = data.seek(0, os.SEEK_CUR)
383126
            data.seek(0, os.SEEK_SET)
383126
        else:
383126
            sz = os.stat(ap).st_size
383126
383126
        try:
383126
            with data or open(ap, "rb") as f:
383126
                log_info("Processing %s" % ap)
383126
                offs = 0
383126
                while offs < sz:
383126
                    f.seek(offs, os.SEEK_SET)
383126
                    hdr = struct.unpack("
383126
                    ret.append({"path": rp, "src": src or path,
383126
                                "cpuid": hdr[3], "pf": hdr[6], "rev": hdr[1],
383126
                                "date": hdr[2], "offs": offs, "cksum": hdr[4],
383126
                                "data_size": hdr[7], "total_size": hdr[8]})
383126
383126
                    if hdr[8] and hdr[8] - hdr[7] > 48:
383126
                        f.seek(hdr[7], os.SEEK_CUR)
383126
                        ext_tbl = struct.unpack("
383126
                        log_status("Found %u extended signatures for %s:%#x" %
383126
                                   (ext_tbl[0], rp, offs), level=1)
383126
383126
                        cur_offs = offs + hdr[7] + 48 + 20
383126
                        ext_sig_cnt = 0
383126
                        while cur_offs < offs + hdr[8] \
383126
                                and ext_sig_cnt <= ext_tbl[0]:
383126
                            ext_sig = struct.unpack("
383126
                            ignore = args.ignore_ext_dups and \
383126
                                (ext_sig[0] == hdr[3])
383126
                            if not ignore:
383126
                                ret.append({"path": rp, "src": src or path,
383126
                                            "cpuid": ext_sig[0],
383126
                                            "pf": ext_sig[1],
383126
                                            "rev": hdr[1], "date": hdr[2],
383126
                                            "offs": offs, "ext_offs": cur_offs,
383126
                                            "cksum": hdr[4],
383126
                                            "ext_cksum": ext_sig[2],
383126
                                            "data_size": hdr[7],
383126
                                            "total_size": hdr[8]})
383126
                            log_status(("Got ext sig %#x/%#x for " +
383126
                                        "%s:%#x:%#x/%#x%s") %
383126
                                       (ext_sig[0], ext_sig[1],
383126
                                        rp, offs, hdr[3], hdr[6],
383126
                                        " (ignored)" if ignore else ""),
383126
                                       level=2)
383126
383126
                            cur_offs += 12
383126
                            ext_sig_cnt += 1
383126
383126
                    offs += hdr[8] or 2048
383126
        except Exception as e:
383126
            log_error("a problem occurred while processing %s: %s" % (ap, e),
383126
                      level=1)
383126
383126
    return ret
383126
383126
383126
def read_revs_rpm(path, args, ret=None):
383126
    if ret is None:
383126
        ret = []
383126
383126
    dir_tmp = tempfile.mkdtemp()
383126
383126
    log_status("Trying to extract files from RPM \"%s\"..." % path,
383126
               level=1)
383126
383126
    rpm2cpio = Popen(args=["rpm2cpio", path], stdout=PIPE, stderr=PIPE,
383126
                     close_fds=True)
383126
    cpio = Popen(args=["cpio", "-idmv"] + file_glob,
383126
                 cwd=dir_tmp, stdin=rpm2cpio.stdout,
383126
                 stdout=PIPE, stderr=STDOUT)
383126
    out, cpio_stderr = cpio.communicate()
383126
    rpm2cpio_out, rpm2cpio_err = rpm2cpio.communicate()
383126
383126
    rpm2cpio_ret = rpm2cpio.returncode
383126
    cpio_ret = cpio.returncode
383126
383126
    log_info("rpm2cpio exit code: %d, cpio exit code: %d" %
383126
             (rpm2cpio_ret, cpio_ret))
383126
    if rpm2cpio_err:
383126
        log_info("rpm2cpio stderr:\n%s" % rpm2cpio_err, level=3)
383126
    if out:
383126
        log_info("cpio output:\n%s" % out, level=3)
383126
    if cpio_stderr:
383126
        log_info("cpio stderr:\n%s" % cpio_stderr, level=3)
383126
383126
    if rpm2cpio_ret == 0 and cpio_ret == 0:
383126
        ret = read_revs_dir(dir_tmp, args, path)
383126
383126
    shutil.rmtree(dir_tmp)
383126
383126
    return ret
383126
383126
383126
def read_revs_tar(path, args, ret=None):
383126
    if ret is None:
383126
        ret = []
383126
383126
    dir_tmp = tempfile.mkdtemp()
383126
383126
    log_status("Trying to extract files from tarball \"%s\"..." % path,
383126
               level=1)
383126
383126
    try:
383126
        with tarfile.open(path, "r:*") as tar:
383126
            for ti in tar:
383126
                if any(fnmatch.fnmatchcase(ti.name, p) for p in file_glob):
383126
                    d = os.path.normpath(os.path.join("/",
383126
                                         os.path.dirname(ti.name)))
383126
                    # For now, strip exactl one level
383126
                    d = os.path.join(*(d.split(os.path.sep)[2:]))
383126
                    n = os.path.join(d, os.path.basename(ti.name))
383126
383126
                    if not os.path.exists(d):
383126
                        os.makedirs(d)
383126
                    t = tar.extractfile(ti)
383126
                    with open(n, "wb") as f:
383126
                        shutil.copyfileobj(t, f)
383126
                    t.close()
383126
383126
        ret = read_revs_dir(dir_tmp, args, path)
383126
    except Exception as err:
383126
        log_error("Error while reading \"%s\" as a tarball: \"%s\"" %
383126
                  (path, str(err)))
383126
383126
    shutil.rmtree(dir_tmp)
383126
383126
    return ret
383126
383126
383126
def read_revs(path, args, ret=None):
383126
    if ret is None:
383126
        ret = []
383126
    if os.path.isdir(path):
383126
        return read_revs_dir(path, args, ret)
383126
    elif tarfile.is_tarfile(path):
383126
        return read_revs_tar(path, args, ret)
383126
    else:
383126
        return read_revs_rpm(path, args, ret)
383126
383126
383126
def gen_mc_map(mc_data, merge=False, merge_path=False):
383126
    """
383126
    Converts an array of microcode file information to a map with path/sig/pf
383126
    as a key.
383126
383126
    merge: whether to leave only the newest mc variant in the map or leave all
383126
           possible variants.
383126
    """
383126
    res = dict()
383126
383126
    for mc in mc_data:
383126
        key = (None if merge_path else mc["path"], mc["cpuid"], mc["pf"])
383126
383126
        if key not in res:
383126
            res[key] = dict()
383126
383126
        cpuid = mc["cpuid"]
383126
        cur_pf = mc["pf"]
383126
        pid = 1
383126
        while cur_pf > 0:
383126
            if cur_pf & 1 and not (merge and pid in res[key]
383126
                                   and res[key][pid]["rev"][0] >= mc["rev"]):
383126
                if pid not in res[cpuid] or merge:
383126
                    res[cpuid][pid] = []
383126
                res[cpuid][pid].append(mc)
383126
383126
            cur_pf = cur_pf / 2
383126
            pid = pid * 2
383126
383126
    return res
383126
383126
383126
def gen_fn_map(mc_data, merge=False, merge_path=False):
383126
    res = dict()
383126
383126
    for mc in mc_data:
383126
        key = (None if merge_path else mc["path"], mc["cpuid"], mc["pf"])
383126
        if key in res:
383126
            log_warn("Duplicate path/cpuid/pf: %s/%#x/%#x" % key)
383126
        else:
383126
            res[key] = []
383126
        if merge and len(res[key]):
383126
            if mc["rev"] > res[key][0]["rev"]:
383126
                res[key][0] = mc
383126
        else:
383126
            res[key].append(mc)
383126
383126
    return res
383126
383126
383126
def revcmp(a, b):
383126
    return b["rev"] - a["rev"]
383126
383126
383126
class ChangeLogEntry:
383126
    ADDED = 0
383126
    REMOVED = 1
383126
    UPDATED = 2
383126
    DOWNGRADED = 3
383126
    OTHER = 4
383126
383126
383126
def mc_stripped_path(mc):
383126
    paths = ("usr/share/microcode_ctl/ucode_with_caveats/intel",
383126
             "usr/share/microcode_ctl/ucode_with_caveats",
383126
             "usr/share/microcode_ctl",
383126
             "lib/firmware",
383126
             "etc/firmware",
383126
             )
383126
383126
    return remove_prefix(mc["path"], paths)
383126
383126
383126
class mcnm:
383126
    MCNM_ABBREV = 0
383126
    MCNM_FAMILIES = 1
383126
    MCNM_MODELS = 2
383126
    MCNM_FAMILIES_MODELS = 3
383126
    MCNM_CODENAME = 4
383126
383126
383126
def get_mc_cnames(mc, cmap, mode=mcnm.MCNM_ABBREV, stringify=True,
383126
                  segment=False):
383126
    if not isinstance(mc, dict):
383126
        mc = mc_from_mc_key(mc)
383126
    sig = mc["cpuid"]
383126
    pf = mc["pf"]
383126
    res = []
383126
383126
    if not cmap:
383126
        return None
383126
    if sig not in cmap:
383126
        log_info("No codename information for sig %#x" % sig)
383126
        return None
383126
383126
    cnames = cmap[sig]
383126
383126
    if mode in (mcnm.MCNM_FAMILIES, mcnm.MCNM_MODELS,
383126
                mcnm.MCNM_FAMILIES_MODELS):
383126
        for c in cnames:
383126
            if not (pf & c["pf_mask"]):
383126
                continue
383126
            for m, f in ((mcnm.MCNM_FAMILIES, "families"),
383126
                         (mcnm.MCNM_MODELS, "models")):
383126
                if m & mode == 0:
383126
                    continue
383126
                if f not in c or not c[f]:
383126
                    log_info("No %s for sig %#x in %r" % (f, sig, c))
383126
                    continue
383126
383126
                res.append(c[f])
383126
383126
        return ", ".join(res) or None
383126
383126
    steppings = dict()
383126
    suffices = dict()
383126
    for c in cnames:
383126
        if pf and not (pf & c["pf_mask"]):
383126
            continue
383126
383126
        if mode == mcnm.MCNM_ABBREV and "abbrev" in c and c["abbrev"]:
383126
            cname = c["abbrev"]
383126
        else:
383126
            cname = c["codename"]
383126
383126
        if segment:
383126
            cname = c["segment"] + " " + cname
383126
383126
        if cname not in suffices:
383126
            suffices[cname] = set()
383126
        if "variant" in c and c["variant"]:
383126
            suffices[cname] |= set(c["variant"])
383126
383126
        if cname not in steppings:
383126
            steppings[cname] = set()
383126
        if c["stepping"]:
383126
            steppings[cname] |= set(c["stepping"])
383126
383126
    for cname in sorted(steppings.keys()):
383126
        cname_res = [cname]
383126
        if len(suffices[cname]):
383126
            cname_res[0] += "-" + "/".join(sorted(suffices[cname]))
383126
        if len(steppings[cname]):
383126
            cname_res.append("/".join(sorted(steppings[cname])))
383126
        res.append(" ".join(cname_res) if stringify else cname_res)
383126
383126
    return (", ".join(res) or None) if stringify else res
383126
383126
383126
def mc_from_mc_key(k):
383126
    return dict(zip(("path", "cpuid", "pf"), k))
383126
383126
383126
def mc_path(mc, pf_sfx=True, midword=None, cmap=None, cname_segment=False):
383126
    if not isinstance(mc, dict):
383126
        mc = mc_from_mc_key(mc)
383126
    path = mc_stripped_path(mc) if mc["path"] is not None else None
383126
    cpuid_fn = cpuid_fname(mc["cpuid"])
383126
    fname = os.path.basename(mc["path"] or cpuid_fn)
383126
    midword = "" if midword is None else " " + midword
383126
    cname = get_mc_cnames(mc, cmap, segment=cname_segment)
383126
    cname_str = " (" + cname + ")" if cname else ""
383126
383126
    if pf_sfx:
383126
        sfx = "/0x%02x" % mc["pf"]
383126
    else:
383126
        sfx = ""
383126
383126
    if not path or path == os.path.join("intel-ucode", cpuid_fn):
383126
        return "%s%s%s%s" % (fname, sfx, cname_str, midword)
383126
    else:
383126
        return "%s%s%s%s (in %s)" % (cpuid_fn, sfx, cname_str, midword, path)
383126
383126
383126
def gen_changelog_file(old, new):
383126
    pass
383126
383126
383126
def mc_cmp(old_mc, new_mc):
383126
    res = []
383126
383126
    old_mc_revs = [x["rev"] for x in old_mc]
383126
    new_mc_revs = [x["rev"] for x in new_mc]
383126
    common = set(old_mc_revs) & set(new_mc_revs)
383126
    old_rev_list = [x for x in sorted(old_mc_revs) if x not in common]
383126
    new_rev_list = [x for x in sorted(new_mc_revs) if x not in common]
383126
383126
    if len(old_rev_list) != 1 or len(new_rev_list) != 1:
383126
        for i in new_mc:
383126
            if i["rev"] in new_rev_list:
383126
                res.append((ChangeLogEntry.ADDED, None, i))
383126
        for i in old_mc:
383126
            if i["rev"] in old_rev_list:
383126
                res.append((ChangeLogEntry.REMOVED, i, None))
383126
    else:
383126
        for old in old_mc:
383126
            if old["rev"] == old_rev_list[0]:
383126
                break
383126
        for new in new_mc:
383126
            if new["rev"] == new_rev_list[0]:
383126
                break
383126
        if new["rev"] > old["rev"]:
383126
            res.append((ChangeLogEntry.UPDATED, old, new))
383126
        elif new["rev"] < old["rev"]:
383126
            res.append((ChangeLogEntry.DOWNGRADED, old, new))
383126
383126
    return res
383126
383126
383126
def gen_changelog(old, new):
383126
    res = []
383126
383126
    old_map = gen_fn_map(old)
383126
    new_map = gen_fn_map(new)
383126
383126
    old_files = set(old_map.keys())
383126
    new_files = set(new_map.keys())
383126
383126
    both = old_files & new_files
383126
    added = new_files - old_files
383126
    removed = old_files - new_files
383126
383126
    for f in sorted(added):
383126
        p = mc_path(new_map[f][0])
383126
        for old_f in sorted(removed):
383126
            old_p = mc_path(old_map[old_f][0])
383126
            if p == old_p and f[1] == old_f[1] and f[2] == old_f[2]:
383126
                log_info("Matched %s (%s and %s)" %
383126
                         (p, old_map[old_f][0]["path"], new_map[f][0]["path"]))
383126
                added.remove(f)
383126
                removed.remove(old_f)
383126
383126
                res += mc_cmp(old_map[old_f], new_map[f])
383126
383126
    for f in sorted(added):
383126
        for i in new_map[f]:
383126
            res.append((ChangeLogEntry.ADDED, None, i))
383126
    for f in sorted(removed):
383126
        for i in old_map[f]:
383126
            res.append((ChangeLogEntry.REMOVED, i, None))
383126
    for f in sorted(both):
383126
        res += mc_cmp(old_map[f], new_map[f])
383126
383126
    return res
383126
383126
383126
def mc_date(mc):
383126
    if isinstance(mc, dict):
383126
        mc = mc["date"]
383126
    return "%04x-%02x-%02x" % (mc & 0xffff, mc >> 24, (mc >> 16) & 0xff)
383126
383126
383126
def mc_rev(mc, date=None):
383126
    '''
383126
    While revision is signed for comparison purposes, historically
383126
    it is printed as unsigned,  Oh well.
383126
    '''
383126
    global print_date
383126
383126
    if mc["rev"] < 0:
383126
        rev = 2**32 + mc["rev"]
383126
    else:
383126
        rev = mc["rev"]
383126
383126
    if date if date is not None else print_date:
383126
        return "%#x (%s)" % (rev, mc_date(mc))
383126
    else:
383126
        return "%#x" % rev
383126
383126
383126
def print_changelog_rpm(clog, cmap, args):
383126
    for e, old, new in clog:
383126
        mc_str = mc_path(new if e == ChangeLogEntry.ADDED else old,
383126
                         midword="microcode",
383126
                         cmap=cmap, cname_segment=args.segment)
383126
383126
        if e == ChangeLogEntry.ADDED:
383126
            print("Addition of %s at revision %s" % (mc_str, mc_rev(new)))
383126
        elif e == ChangeLogEntry.REMOVED:
383126
            print("Removal of %s at revision %s" % (mc_str, mc_rev(old)))
383126
        elif e == ChangeLogEntry.UPDATED:
383126
            print("Update of %s from revision %s up to %s" %
383126
                  (mc_str, mc_rev(old), mc_rev(new)))
383126
        elif e == ChangeLogEntry.DOWNGRADED:
383126
                print("Downgrade of %s from revision %s down to %s" %
383126
                      (mc_str, mc_rev(old), mc_rev(new)))
383126
        elif e == ChangeLogEntry.OTHER:
383126
            print("Other change in %s:" % old["path"])
383126
            print("  old: %#x/%#x: rev %s (offs %#x)" %
383126
                  (old["cpuid"], old["pf"], mc_rev(old), old["offs"]))
383126
            print("  new: %#x/%#x: rev %s (offs %#x)" %
383126
                  (new["cpuid"], new["pf"], mc_rev(new), new["offs"]))
383126
383126
383126
def print_changelog_intel(clog, cmap, args):
383126
    def clog_sort_key(x):
383126
        res = str(x[0])
383126
383126
        if x[0] != ChangeLogEntry.ADDED:
383126
            res += "%08x%02x" % (x[1]["cpuid"], x[1]["pf"])
383126
        else:
383126
            res += "0" * 10
383126
383126
        if x[0] != ChangeLogEntry.REMOVED:
383126
            res += "%08x%02x" % (x[2]["cpuid"], x[2]["pf"])
383126
        else:
383126
            res += "0" * 10
383126
383126
        return res
383126
383126
    sorted_clog = sorted(clog, key=clog_sort_key)
383126
    sections = (("New Platforms",     (ChangeLogEntry.ADDED, )),
383126
                ("Updated Platforms", (ChangeLogEntry.UPDATED,
383126
                                       ChangeLogEntry.DOWNGRADED)),
383126
                ("Removed Platforms", (ChangeLogEntry.REMOVED, )))
383126
383126
    def print_line(e, old, new, types):
383126
        if e not in types:
383126
            return
383126
383126
        if not print_line.hdr:
383126
            print("""
383126
| Processor      | Stepping | F-M-S/PI    | Old Ver  | New Ver  | Products
383126
|:---------------|:---------|:------------|:---------|:---------|:---------""")
383126
            print_line.hdr = True
383126
383126
        mc = new if e == ChangeLogEntry.ADDED else old
383126
        cnames = get_mc_cnames(mc, cmap, stringify=False,
383126
                               segment=args.segment) or (("???", ""), )
383126
        for cn in cnames:
383126
            cname = cn[0]
383126
            stepping = cn[1] if len(cn) > 1 else ""
383126
        print("| %-14s | %-8s | %8s/%02x | %8s | %8s | %s" %
383126
              (cname,
383126
               stepping,
383126
               cpuid_fname(mc["cpuid"]), mc["pf"],
383126
               ("%08x" % old["rev"]) if e != ChangeLogEntry.ADDED else "",
383126
               ("%08x" % new["rev"]) if e != ChangeLogEntry.REMOVED else "",
383126
               get_mc_cnames(mc, cmap, mode=mcnm.MCNM_FAMILIES,
383126
                             segment=args.segment) or ""))
383126
383126
    for h, types in sections:
383126
        print("\n### %s" % h)
383126
        print_line.hdr = False
383126
        for e, old, new in sorted_clog:
383126
            print_line(e, old, new, types)
383126
383126
383126
def print_changelog(clog, cmap, args):
383126
    if args.format == "rpm":
383126
        print_changelog_rpm(clog, cmap, args)
383126
    elif args.format == "intel":
383126
        print_changelog_intel(clog, cmap, args)
383126
    else:
383126
        log_error(("unknown changelog format: \"%s\". " +
383126
                   "Supported formats are: rpm, intel.") % args.format)
383126
383126
383126
class TableStyles:
383126
    TS_CSV = 0
383126
    TS_FANCY = 1
383126
383126
383126
def print_line(line, column_sz):
383126
    print(" | ".join([str(x).ljust(column_sz[i])
383126
                      for i, x in zip(itertools.count(),
383126
                                      itertools.chain(line,
383126
                                      [""] * (len(column_sz) -
383126
                                              len(line))))]).rstrip())
383126
383126
383126
def print_table(items, header=[], style=TableStyles.TS_CSV):
383126
    if style == TableStyles.TS_CSV:
383126
        for i in items:
383126
            print(";".join(i))
383126
    elif style == TableStyles.TS_FANCY:
383126
        column_sz = list(reduce(lambda x, y:
383126
                                map(max, izip_longest(x, y, fillvalue=0)),
383126
                                [[len(x) for x in i]
383126
                                 for i in itertools.chain(header, items)]))
383126
        for i in header:
383126
            print_line(i, column_sz)
383126
        if header:
383126
            print("-+-".join(["-" * x for x in column_sz]))
383126
        for i in items:
383126
            print_line(i, column_sz)
383126
383126
383126
def print_summary(revs, cmap, args):
383126
    m = gen_fn_map(revs)
383126
    cnames_mode = mcnm.MCNM_ABBREV if args.abbrev else mcnm.MCNM_CODENAME
383126
383126
    header = []
383126
    if args.header:
383126
        header.append(["Path", "Offset", "Ext. Offset", "Data Size",
383126
                       "Total Size", "CPUID", "Platform ID Mask", "Revision",
383126
                       "Date", "Checksum", "Codenames"] +
383126
                      (["Models"] if args.models else []))
383126
    tbl = []
383126
    for k in sorted(m.keys()):
383126
        for mc in m[k]:
383126
            tbl.append([mc_stripped_path(mc),
383126
                        "0x%x" % mc["offs"],
383126
                        "0x%x" % mc["ext_offs"] if "ext_offs" in mc else "-",
383126
                        "0x%05x" % mc["data_size"],
383126
                        "0x%05x" % mc["total_size"],
383126
                        "0x%05x" % mc["cpuid"],
383126
                        "0x%02x" % mc["pf"],
383126
                        mc_rev(mc, date=False),
383126
                        mc_date(mc),
383126
                        "0x%08x" % (mc["ext_cksum"]
383126
                                    if "ext_cksum" in mc else mc["cksum"]),
383126
                        get_mc_cnames(mc, cmap, cnames_mode,
383126
                                      segment=args.segment) or ""] +
383126
                       ([get_mc_cnames(mc, cmap,
383126
                                       mcnm.MCNM_FAMILIES_MODELS,
383126
                                       segment=args.segment)]
383126
                        if args.models else []))
383126
383126
    print_table(tbl, header, style=TableStyles.TS_FANCY)
383126
383126
383126
def read_codenames_file(path):
383126
    '''
383126
    Supports two formats: new and old
383126
     * old: tab-separated. Field order:
383126
       Segment, (unused), Codename, (dash-separated) Stepping,
383126
       Platform ID mask, CPUID, (unused) Update link, (unused) Specs link
383126
     * new: semicolon-separated; support comments.  Distinguished
383126
       by the first line that starts with octothorp.  Field order:
383126
       Segment, Unused, Codename, Stepping, Platform ID mask, CPUID,
383126
       Abbreviation, Variant(s), Families, Models
383126
    '''
383126
    old_fields = ["segment", "_", "codename", "stepping", "pf_mask", "sig",
383126
                  "_update", "_specs"]
383126
    new_fields = ["segment", "_", "codename", "stepping", "pf_mask", "sig",
383126
                  "abbrev", "variant", "families", "models"]
383126
    new_fmt = False
383126
    field_names = old_fields
383126
383126
    res = dict()
383126
383126
    try:
383126
        with open(path, "r") as f:
383126
            for line in f:
383126
                line = line.strip()
383126
                if len(line) == 0:
383126
                    continue
383126
                if line[0] == '#':
383126
                    new_fmt = True
383126
                    field_names = new_fields
383126
                    continue
383126
383126
                fields = line.split(";" if new_fmt else "\t",
383126
                                    1 + len(field_names))
383126
                fields = dict(zip(field_names, fields))
383126
                if "sig" not in fields:
383126
                    log_warn("Skipping %r (from \"%s\")" % (fields, line))
383126
                    continue
383126
383126
                sig = fields["sig"] = int(fields["sig"], 16)
383126
                fields["pf_mask"] = int(fields["pf_mask"], 16)
383126
                fields["stepping"] = fields["stepping"].split(",")
383126
                if "variant" in fields:
383126
                    if fields["variant"]:
383126
                        fields["variant"] = fields["variant"].split(",")
383126
                    else:
383126
                        fields["variant"] = []
383126
383126
                if sig not in res:
383126
                    res[sig] = list()
383126
                res[sig].append(fields)
383126
    except Exception as e:
383126
        log_error("a problem occurred while reading code names: %s" % e)
383126
383126
    return res
383126
383126
383126
def print_discrepancies(rev_map, deps, cmap, args):
383126
    """
383126
    rev_map: dict "name": revs
383126
    deps: list of tuples (name, parent/None)
383126
    """
383126
    sigs = set()
383126
383126
    for p, r in rev_map.items():
383126
        sigs |= set(r.keys())
383126
383126
    if args.header:
383126
        header1 = ["sig"]
383126
        if args.print_vs:
383126
            header2 = [""]
383126
        for p, n, d in deps:
383126
            header1.append(n)
383126
            if args.print_vs:
383126
                add = ""
383126
                if d:
383126
                    for pd, nd, dd in deps:
383126
                        if pd == d:
383126
                            add = "(vs. %s)" % nd
383126
                            break
383126
                header2.append(add)
383126
        if args.models:
383126
            header1.append("Model names")
383126
            if args.print_vs:
383126
                header2.append("")
383126
    header = [header1] + ([header2] if args.print_vs else [])
383126
383126
    tbl = []
383126
    for s in sorted(sigs):
383126
        out = [mc_path(s)]
383126
        print_out = not args.print_filter
383126
        print_date = args.min_date is None
383126
383126
        for p, n, d in deps:
383126
            cur = dict([(x["rev"], x) for x in rev_map[p][s]]) \
383126
                  if s in rev_map[p] else []
383126
            v = "/".join([mc_rev(y) for x, y in sorted(cur.items())]) \
383126
                if cur else "-"
383126
            if d is not None:
383126
                prev = [x["rev"] for x in rev_map[d][s]] if s in rev_map[d] \
383126
                        else []
383126
                if [x for x in cur if x not in prev]:
383126
                    v += " (*)"
383126
                    print_out = True
383126
            if args.min_date is not None and s in rev_map[p]:
383126
                for x in rev_map[p][s]:
383126
                    print_date |= mc_date(x) > args.min_date
383126
            out.append(v)
383126
383126
        if print_out and print_date:
383126
            if args.models:
383126
                out.append(get_mc_cnames(s, cmap, segment=args.segment) or "")
383126
            tbl.append(out)
383126
383126
    print_table(tbl, header, style=TableStyles.TS_FANCY)
383126
383126
383126
def cmd_summary(args):
383126
    revs = []
383126
    for p in args.filelist:
383126
        revs = read_revs(p, args, ret=revs)
383126
383126
    codenames_map = read_codenames_file(args.codenames)
383126
383126
    print_summary(revs, codenames_map, args)
383126
383126
    return 0
383126
383126
383126
def cmd_changelog(args):
383126
    codenames_map = read_codenames_file(args.codenames)
383126
    base_path = args.filelist[0]
383126
    upd_path = args.filelist[1]
383126
383126
    base = read_revs(base_path, args)
383126
    upd = read_revs(upd_path, args)
383126
383126
    print_changelog(gen_changelog(base, upd), codenames_map, args)
383126
383126
    return 0
383126
383126
383126
def cmd_discrepancies(args):
383126
    """
383126
    filenames:
383126
     * "<" prefix (possibly multiple times) to refer to a previous entry
383126
       to compare against
383126
     * "[name]" prefix is a name reference
383126
    """
383126
    codenames_map = read_codenames_file(args.codenames)
383126
    rev_map = dict()
383126
    deps = list()
383126
    cur = -1
383126
383126
    for path in args.filelist:
383126
        orig_path = path
383126
        name = None
383126
        cur += 1
383126
        dep = None
383126
        while True:
383126
            if path[0] == '<':
383126
                path = path[1:]
383126
                dep = cur - 1 if dep is None else dep - 1
383126
            elif path[0] == '[' and path.find(']') > 0:
383126
                pos = path.find(']')
383126
                name = path[1:pos]
383126
                path = path[pos + 1:]
383126
            else:
383126
                break
383126
        if name is None:
383126
            name = path
383126
        if dep is not None and dep < 0:
383126
            log_error("Incorrect dep reference for '%s' (points to index %d)" %
383126
                      (orig_path, dep))
383126
            return 1
383126
        deps.append((path, name, deps[dep][0] if dep is not None else None))
383126
        rev_map[path] = gen_fn_map(read_revs(path, args), merge=args.merge,
383126
                                   merge_path=True)
383126
383126
    print_discrepancies(rev_map, deps, codenames_map, args)
383126
383126
    return 0
383126
383126
383126
def parse_cli():
383126
    root_parser = argparse.ArgumentParser(prog="gen_updates",
383126
                                          description="Intel CPU Microcode " +
383126
                                          "parser")
383126
    root_parser.add_argument("-C", "--codenames", default='codenames',
383126
                             help="Code names file")
383126
    root_parser.add_argument("-v", "--verbose", action="count", default=0,
383126
                             help="Increase output verbosity")
383126
    root_parser.add_argument("-E", "--no-ignore-ext-duplicates",
383126
                             action="store_const", dest="ignore_ext_dups",
383126
                             default=False, const=False,
383126
                             help="Do not ignore duplicates of the main " +
383126
                                  "signature in the extended signature header")
383126
    root_parser.add_argument("-e", "--ignore-ext-duplicates",
383126
                             action="store_const", dest="ignore_ext_dups",
383126
                             const=True,
383126
                             help="Ignore duplicates of the main signature " +
383126
                                   "in the extended signature header")
383126
    root_parser.add_argument("-t", "--print-segment", action="store_const",
383126
                             dest="segment", const=True,
383126
                             help="Print model segment")
383126
    root_parser.add_argument("-T", "--no-print-segment", action="store_const",
383126
                             dest="segment", const=False, default=False,
383126
                             help="Do not print model segment")
383126
383126
    cmdparsers = root_parser.add_subparsers(title="Commands",
383126
                                            help="main gen_updates commands")
383126
383126
    parser_s = cmdparsers.add_parser("summary",
383126
                                     help="Generate microcode summary")
383126
    parser_s.add_argument("-a", "--abbreviate", action="store_const",
383126
                          dest="abbrev", const=True, default=True,
383126
                          help="Abbreviate code names")
383126
    parser_s.add_argument("-A", "--no-abbreviate", action="store_const",
383126
                          dest="abbrev", const=False,
383126
                          help="Do not abbreviate code names")
383126
    parser_s.add_argument("-m", "--print-models", action="store_const",
383126
                          dest="models", const=True, default=False,
383126
                          help="Print models")
383126
    parser_s.add_argument("-M", "--no-print-models",
383126
                          action="store_const", dest="models",
383126
                          const=False, help="Do not print models")
383126
    parser_s.add_argument("-H", "--no-print-header",
383126
                          action="store_const", dest="header",
383126
                          const=False, default=True,
383126
                          help="Do not print hader")
383126
    parser_s.add_argument("filelist", nargs="*", default=[],
383126
                          help="List or RPMs/directories to process")
383126
    parser_s.set_defaults(func=cmd_summary)
383126
383126
    parser_c = cmdparsers.add_parser("changelog",
383126
                                     help="Generate changelog")
383126
    parser_c.add_argument("-F", "--format", choices=["rpm", "intel"],
383126
                          default="rpm", help="Changelog format")
383126
    parser_c.add_argument("filelist", nargs=2,
383126
                          help="RPMs/directories to compare")
383126
    parser_c.set_defaults(func=cmd_changelog)
383126
383126
    parser_d = cmdparsers.add_parser("discrepancies",
383126
                                     help="Generate discrepancies")
383126
    parser_d.add_argument("-s", "--merge-revs", action="store_const",
383126
                          dest="merge", const=True, default=False,
383126
                          help="Merge revisions that come" +
383126
                               " from different files")
383126
    parser_d.add_argument("-S", "--no-merge-revs", action="store_const",
383126
                          dest="merge", const=False,
383126
                          help="Do not Merge revisions that come" +
383126
                               " from different files")
383126
    parser_d.add_argument("-v", "--print-vs", action="store_const",
383126
                          dest="print_vs", const=True, default=False,
383126
                          help="Print base version ")
383126
    parser_d.add_argument("-V", "--no-print-vs", action="store_const",
383126
                          dest="print_vs", const=False,
383126
                          help="Do not Merge revisions that come" +
383126
                               " from different files")
383126
    parser_d.add_argument("-m", "--print-models", action="store_const",
383126
                          dest="models", const=True, default=True,
383126
                          help="Print model names")
383126
    parser_d.add_argument("-M", "--no-print-models", action="store_const",
383126
                          dest="models", const=False,
383126
                          help="Do not print model names")
383126
    parser_d.add_argument("-H", "--no-print-header", action="store_const",
383126
                          dest="header", const=False, default=True,
383126
                          help="Do not print hader")
383126
    parser_d.add_argument("-a", "--print-all-files", action="store_const",
383126
                          dest="print_filter", const=False, default=True,
383126
                          help="Print all files")
383126
    parser_d.add_argument("-c", "--print-changed-files", action="store_const",
383126
                          dest="print_filter", const=True,
383126
                          help="Print only changed files")
383126
    parser_d.add_argument("-d", "--min-date", action="store",
383126
                          help="Minimum date filter")
383126
    parser_d.add_argument("filelist", nargs='*',
383126
                          help="RPMs/directories to compare")
383126
    parser_d.set_defaults(func=cmd_discrepancies)
383126
383126
    args = root_parser.parse_args()
383126
    if not hasattr(args, "func"):
383126
        root_parser.print_help()
383126
        return None
383126
383126
    global log_level
383126
    log_level = args.verbose
383126
383126
    return args
383126
383126
383126
def main():
383126
    args = parse_cli()
383126
    if args is None:
383126
        return 1
383126
383126
    return args.func(args)
383126
383126
383126
if __name__ == "__main__":
383126
    sys.exit(main())