Blame SOURCES/gen_updates2.py

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