#!/usr/local/bin/python3.11

#
# LDoms
#
# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
# The Universal Permissive License (UPL), Version 1.0
#
# Subject to the condition set forth below, permission is hereby
# granted to any person obtaining a copy of this software, associated
# documentation and/or data (collectively the "Software"), free of
# charge and under any and all copyright rights in the Software, and
# any and all patent rights owned or freely licensable by each licensor
# hereunder covering either (i) the unmodified Software as contributed
# to or provided by such licensor, or (ii) the Larger Works (as defined
# below), to deal in both
#
# (a) the Software, and
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt
# file if one is included with the Software (each a "Larger Work" to
# which the Software is contributed by such licensors),
#
# without restriction, including without limitation the rights to copy,
# create derivative works of, display, perform, and distribute the
# Software and make, use, sell, offer for sale, import, export, have
# made, and have sold the Software and the Larger Work(s), and to
# sublicense the foregoing rights on either these or other terms.
#
# This license is subject to the following condition: The above
# copyright notice and either this complete permission notice or at a
# minimum a reference to the UPL must be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#

import struct
import ctypes
import fcntl
import sys
import re
import errno
import os
from collections import defaultdict
from optparse import OptionParser

AUTOSAVE_MAGIC = (0xc1, 0xf2, 0xaf, 0xde)
BOOTSET_MAGIC = (0x62, 0x6f, 0x6f, 0x74)

class dspri_info(ctypes.Structure):
    """Represents the C structure used for ds_pri driver ioctl calls.

    The size field is used to determine how large the PRI is before
    we attempt to read it from the driver.
    """
    _fields_ = [
        ('size', ctypes.c_ulonglong),
        ('token', ctypes.c_ulonglong)
    ]

class MDPrintApp:
    """Defaults and global methods for the MD printing App."""

    DefaultFilePath = "/dev/mdesc"
    DefaultPRIFilePath = "/devices/pseudo/ds_pri@0:ds_pri"
    Version = "1.3"
    Name = "mdprint"

    @staticmethod
    def parse_node_specifier(node_specifier):
        """Parse a string specifying a list of nodes and their properties.

        Parse a string of the form of the form
        <node1>[.<node1-prop1>][.<node1-prop2>][,<node2>[.<node2-prop1>]][,...]
        and return a dictionary where node names map to a list of properties
        specified.
        """

        nodes = {}
        node_strings = node_specifier.split(',')
        for node_string in node_strings:
            tokens = node_string.split('.')
            node_name = tokens.pop(0)
            prop_names = [s for s in tokens if s] # remove empty strings
            if node_name not in nodes:
                nodes[node_name] = prop_names
            else:
                nodes[node_name] += prop_names
        return nodes

    @staticmethod
    def err(s):
        sys.stderr.write("Error: " + s + "\n")

    @staticmethod
    def get_pri_size():
        """Using the ds_pri driver ioctl, get and return current PRI size."""
        ioctl = (ord('d') << 24 | (ord('s') << 16) | (ord('p') << 8) | 1)
        arg = dspri_info()
        f = open(MDPrintApp.DefaultPRIFilePath, mode="rb")
        rv = fcntl.ioctl(f, ioctl, arg)
        if rv == 0:
            rv = arg.size
        else:
            rv = -1
        f.close()
        return rv

class Node:
    """Represents a node data structure in the MD graph.

    Fields are all public and accessed directly. The fwds and backs
    attributes are dictionaries that represent forward or back arcs to
    other nodes. These dicts map node ID numbers to instances of the
    Node class representing an arc to that Node. The props attribute is
    a dictionary of the node's properties and maps property names to
    lists of pairs of the form (element-tag-type, property-value).
    Using a list of pairs allows for nodes containing properties that
    have the same name.
    """
    def __init__(self, node_id, node_type=None):
        self.type = node_type
        self.node_id = node_id
        self.fwds = {}
        self.backs = {}
        self.named_arcs = {}
        self.props = defaultdict(list)
        self.in_subgraph = False
        self.printed = False

class Parser(dict):
    """Parses a binary MD allowing access to nodes using array notation."""

    def __init__(self, mdbuffer):
        dict.__init__(self)

        self.mdbuffer = mdbuffer
        self.counts = defaultdict(int)

        # MD header data
        self.major = None
        self.minor = None
        self.node_blk_sz = None
        self.name_blk_sz = None
        self.data_blk_sz = None
        self.node_blk_off = None
        self.name_blk_off = None
        self.data_blk_off = None

    def node_ids(self):
        return list(self.keys())

    def parse_header(self):
        (self.major, self.minor, self.node_blk_sz, self.name_blk_sz,
            self.data_blk_sz) = struct.unpack_from('!HH I I I', self.mdbuffer)

        self.node_blk_off = 16
        self.name_blk_off = self.node_blk_off + self.node_blk_sz
        self.data_blk_off = self.name_blk_off + self.name_blk_sz

        if self.major != 1:
            MDPrintApp.err("Only major version 1 MD's are supported")
            return False
        else:
            return True

    def parse_nodes(self):
        node_offset = 0
        while node_offset is not None:
            node_offset = self.parse_node(node_offset)

    def parse_node(self, next_element_offset):
        node = None

        while True:

            element = self.parse_element(next_element_offset)
            next_element_offset += Element.Size

            if element.tag is Element.NodeTag:

                node_id = element.offset // Element.Size
                self.counts[element.name] += 1

                if node_id in self:
                    node = self[node_id]
                    node.type = element.name
                else:
                    node = Node(node_id, element.name)
                    self[node_id] = node

            elif element.tag is Element.PropArcTag:

                target_node_id = element.val
                target_node = None

                if target_node_id in self:
                    target_node = self[target_node_id]
                else:
                    target_node = Node(target_node_id)
                    self[target_node_id] = target_node

                if element.name == "fwd":
                    node.fwds[target_node_id] = target_node
                elif element.name == "back":
                    node.backs[target_node_id] = target_node
                else:
                    node.named_arcs[(element.name, target_node_id)] = \
                        target_node

            elif element.tag is Element.PropValTag:
                node.props[element.name].append(
                    (Element.PropValTag, element.val))

            elif element.tag is Element.PropStrTag:
                node.props[element.name].append(
                    (Element.PropStrTag, element.string))
                
            elif element.tag is Element.PropDataTag:
                node.props[element.name].append(
                    (Element.PropDataTag, element.data))
                
            elif element.tag is Element.NodeEndTag:
                break

            elif element.tag is Element.ListEndTag:
                next_element_offset = None
                break

        return next_element_offset

    def parse_element(self, element_offset):
        file_offset = self.node_blk_off + element_offset

        e = Element(element_offset)

        # Read the element, assuming the value field is used
        (e.tag, e.name_len, _, e.name_off, e.val) = \
            struct.unpack_from('!B B H I Q', self.mdbuffer, file_offset)

        # Read the element data_len and data_off fields
        (_, _, _, _, e.data_len, e.data_off) = \
            struct.unpack_from('!B B H I I I', self.mdbuffer, file_offset)

        if e.is_named_element():
            # read name string into e.name
            e.name = self.parse_name(e.name_off, e.name_len)

        if e.tag is Element.PropStrTag:
            # read property string into e.string
            e.string = self.parse_data_string(e.data_off, e.data_len)

        elif e.tag is Element.PropDataTag:
            # read property data array into e.data
            e.data = self.parse_data(e.data_off, e.data_len)

        return e

    def parse_name(self, name_off, name_len):
        (name, ) = struct.unpack_from("!%ds" % name_len,
            self.mdbuffer, self.name_blk_off + name_off)
        return name.decode('utf-8')

    def parse_data_string(self, data_off, data_len):
        (name, ) = struct.unpack_from("!%ds" % (data_len - 1),
            self.mdbuffer, self.data_blk_off + data_off)
        return name

    def parse_data(self, data_off, data_len):
        return struct.unpack_from("!%dB" % data_len, self.mdbuffer,
            self.data_blk_off + data_off)

    @staticmethod
    def get_string_list(byte_list):
        ba = bytearray(byte_list)

        # Split on the zero byte, returns list of byte arrays
        strings = ba.split(b'\x00')
        num_strings = len(strings)

        # There should always be at least two strings
        # returned by split. Even if the bytelist is just 0x0.
        # (In that case, two empty strings.)
        if num_strings < 2:
            return False

        # Make sure the last string is the empty string.
        if len(strings[-1]) != 0:
            return False
        strings.pop()

        for s in strings:
            if len(s) == 0:
                return False
            # Only treat as a string if all characters
            # are between 0x20 and 0x7f inclusive
            if re.match(b'[\x20-\x7f]+$', s) is None:
                return False

        return [s.decode('utf-8') for s in strings]

class Element:

    Size = 16 # bytes

    ListEndTag  = 0x00
    NodeTag     = 0x4e
    NodeEndTag  = 0x45
    NoopTag     = 0x20
    PropArcTag  = 0x61
    PropValTag  = 0x76
    PropStrTag  = 0x73
    PropDataTag = 0x64

    def __init__(self, offset):
        # derived fields
        self.offset = offset
        self.name = None
        self.string = None
        self.data = None

        # fields from element struct
        self.tag = None
        self.name_len = None
        self.name_off = None
        self.data_len = None
        self.data_off = None
        self.val = None

    def is_named_element(self):
        """Tests if the element is of a type that uses the name tag."""
        return (self.tag is not Element.NodeEndTag and
            self.tag is not Element.ListEndTag)

class Printer:
    def __init__(self, settings=None):
        self.settings = settings
        
    def print_md(self, md):
        self.md_start(md)
        for node_id in sorted(md.node_ids()):
            self.print_node(md, md[node_id])

    def print_subgraph(self, md, root_id):
        self.mark_subgraph_nodes(md, root_id)
        for node_id in sorted(md.node_ids()):
            if md[node_id].in_subgraph and not md[node_id].printed:
                self.print_node(md, md[node_id], subgraph=True)
                md[node_id].printed = True

    def mark_subgraph_nodes(self, md, root_id):
        if md[root_id].in_subgraph:
            return
        md[root_id].in_subgraph = True
        for target_id in md[root_id].fwds:
            self.mark_subgraph_nodes(md, target_id)
        for (_, target_id) in md[root_id].named_arcs:
            self.mark_subgraph_nodes(md, target_id)

    def print_node(self, md, node, subgraph=False):
        self.node_start(md, node, self.settings)

        for prop_name in sorted(node.props.keys()):
            prop_list = node.props[prop_name]
            for (prop_type, prop_val) in prop_list:
                if prop_type is Element.PropStrTag:
                    self.print_str_prop(md, node, prop_name, prop_val)
                elif prop_type is Element.PropValTag:
                    self.print_val_prop(md, node, prop_name, prop_val)
                elif prop_type is Element.PropDataTag:
                    self.print_data_prop(md, node, prop_name, prop_val)

        # Print arcs, ordered by target node type.
        # Use this lambda function to sort by node type.
        f = lambda node_id_node: node_id_node[1].type
        arc_name_f = lambda t:t[0]

        if self.settings.backlinks:
            for _, target_node in sorted(iter(node.backs.items()), key=f):
                if (not subgraph) or target_node.in_subgraph:
                    self.print_back_arc(md, node, target_node, self.settings)

        for _, target_node in sorted(iter(node.fwds.items()), key=f):
            self.print_fwd_arc(md, node, target_node, self.settings)

        for (arc_name, _), target_node in sorted(iter(node.named_arcs.items()),
            key=arc_name_f):
            self.print_named_arc(md, node, arc_name, target_node, self.settings)

        self.node_end(md, node)

    # Methods to re-implement in a subclass
    def header(self, header):
        pass
    def md_start(self, md):
        pass
    def node_start(self, md, node, settings):
        pass
    def print_str_prop(self, md, node, prop_name, prop_val):
        pass
    def print_val_prop(self, md, node, prop_name, prop_val):
        pass
    def print_data_prop(self, md, node, prop_name, prop_val):
        pass
    def print_fwd_arc(self, md, node, target_node, settings):
        pass
    def print_back_arc(self, md, node, target_node, settings):
        pass
    def print_named_arc(self, md, node, arc_name, target_node, settings):
        pass
    def node_end(self, md, node):
        pass
    def md_end(self, md):
        pass

class NodeFilter:
    def __init__(self, grep=None):
        self.grep = grep
    def filter_node(self, node):
        return self.grep is None or node.type in self.grep
    def filter_prop(self, node, prop_name):
        return (self.filter_node(node) and \
                (self.grep is None or \
                len(self.grep[node.type]) == 0 or \
            prop_name in self.grep[node.type]))

class FilteredPrinter(Printer):
    def __init__(self, settings, node_filter, printer):
        Printer.__init__(self, settings)
        self.f = node_filter
        self.p = printer

    def header(self, header):
        self.p.header(header)

    def md_start(self, md):
        self.p.md_start(md)

    def node_start(self, md, node, settings):
        if self.f.filter_node(node):
            self.p.node_start(md, node, settings)

    def print_str_prop(self, md, node, prop_name, prop_val):
        if self.f.filter_prop(node, prop_name):
            self.p.print_str_prop(md, node, prop_name, prop_val)

    def print_val_prop(self, md, node, prop_name, prop_val):
        if self.f.filter_prop(node, prop_name):
            self.p.print_val_prop(md, node, prop_name, prop_val)

    def print_data_prop(self, md, node, prop_name, prop_val):
        if self.f.filter_prop(node, prop_name):
            self.p.print_data_prop(md, node, prop_name, prop_val)

    def print_fwd_arc(self, md, node, target_node, settings):
        if self.f.filter_prop(node, 'fwd'):
            self.p.print_fwd_arc(md, node, target_node, settings)

    def print_back_arc(self, md, node, target_node, settings):
        if self.f.filter_prop(node, 'bak'):
            self.p.print_back_arc(md, node, target_node, settings)

    def print_named_arc(self, md, node, arc_name, target_node, settings):
        if self.f.filter_prop(node, arc_name):
            self.p.print_named_arc(md, node, arc_name, target_node, settings)

    def node_end(self, md, node):
        if self.f.filter_node(node):
            self.p.node_end(md, node)

    def md_end(self, md):
        self.p.md_end(md)

class ParsablePrinter(Printer):
    def __init__(self):
        Printer.__init__(self, settings)

    def p(self, s):
        sys.stdout.write(s)

    def header(self, header):
        sys.stdout.write(header)

    def node_start(self, md, node, settings):
        if settings.nodeids:
            self.p("%s|0x%x" % (node.type, node.node_id))
        else:
            self.p(node.type)

    def print_str_prop(self, md, node, prop_name, prop_val):
        self.p("|%s=\"%s\"" % (prop_name, prop_val.decode('utf-8')))

    def print_val_prop(self, md, node, prop_name, prop_val):
        self.p("|%s=0x%x" % (prop_name, prop_val))

    def print_data_prop(self, md, node, prop_name, prop_val):

        self.p("|%s={" % prop_name)

        strings = Parser.get_string_list(prop_val)
        if strings:
            for s in strings[:-1]:
                self.p("\"%s\"," % s)
            self.p("\"%s\"" % strings[-1])

        else:
            from codecs import encode
            self.p("0x" + encode(bytes(prop_val), 'hex_codec').decode('utf-8'))

        self.p("}")

    def print_fwd_arc(self, md, node, target_node, settings):
        if settings.nodeids:
            self.p("|fwd->%s(node_0x%x)" %
                (target_node.type, target_node.node_id))
        else:
            self.p("|fwd->%s" % target_node.type)

    def print_back_arc(self, md, node, target_node, settings):
        if settings.nodeids:
            self.p("|bak<-%s(node_0x%x)" %
                (target_node.type, target_node.node_id))
        else:
            self.p("|bak<-%s" % target_node.type)

    def print_named_arc(self, md, node, arc_name, target_node, settings):
        if settings.nodeids:
            self.p("|%s->%s(node_0x%x)" %
                (arc_name, target_node.type, target_node.node_id))
        else:
            self.p("|%s->%s" % (arc_name, target_node.type))

    def node_end(self, md, node):
        self.p("\n")

class TextPrinter(Printer):
    def __init__(self):
        Printer.__init__(self, settings)

    def header(self, header):
        sys.stdout.write(header)

    def node_start(self, md, node, settings):
        if settings.nodeids:
            print(("%s (node 0x%x) {" % (node.type, node.node_id)))
        else:
            print(("%s {" % node.type))
    def print_str_prop(self, md, node, prop_name, prop_val):
        print(("\t%s = \"%s\"" % (prop_name, prop_val.decode('utf-8'))))
    def print_val_prop(self, md, node, prop_name, prop_val):
        print(("\t%s = 0x%x /* %d */" % (prop_name, prop_val, prop_val)))
    def print_data_prop(self, md, node, prop_name, prop_val):

        print("\t%s = {" % prop_name)

        strings = Parser.get_string_list(prop_val)
        if strings:
            for s in strings[:-1]:
                print("\t\t\"%s\"," % s)
            print("\t\t\"%s\"" % strings[-1])

        else:
            print_byte = lambda b:"0x%02x" % b
            bpl = 8 # bytes per line

            # Some magic to print 8 columns of bytes: for each
            # chunk of 8 bytes in the byte array, get an array
            # slice 8-long and join it with a space.
            for i in range(0, (len(prop_val)+bpl-1)//bpl):
                raw = "%s" % ' '.join(map(print_byte,
                    prop_val[i * bpl:(i+1) * bpl]))
                print("\t\t%s" % raw)

        print("\t}")

    def print_named_arc(self, md, node, arc_name, target_node, settings):
        if settings.nodeids:
            print(("\t%s --> %s (node 0x%x)" %
                (arc_name, target_node.type, target_node.node_id)))
        else:
            print(("\t%s --> %s" % (arc_name, target_node.type)))

    def print_fwd_arc(self, md, node, target_node, settings):
        if settings.nodeids:
            print(("\tfwd --> %s (node 0x%x)" %
                (target_node.type, target_node.node_id)))
        else:
            print(("\tfwd --> %s" % target_node.type))

    def print_back_arc(self, md, node, target_node, settings):
        if settings.nodeids:
            print(("\tbak <-- %s (node 0x%x)" %
                (target_node.type, target_node.node_id)))
        else:
            print(("\tbak <-- %s" % target_node.type))

    def node_end(self, md, node):
        print("}")

class HTMLPrinter(Printer):
    def __init__(self):
        Printer.__init__(self, settings)
        self.header_string = None

    def header(self, header):
        self.header_string = header

    def md_start(self, md):
        print("<html><head><title>MD</title><body>")
        if self.header_string:
            print("<pre>" + header + "</pre>")

    def node_start(self, md, node, settings):
        print(("<div class=node><p><b><a href=#0x%x name=0x%x>%s</b></a> "
                "<i>node 0x%x</i> <i>(%d %s nodes)</i>" %
                (node.node_id, node.node_id, node.type, node.node_id,
            md.counts[node.type], node.type)))

    def print_str_prop(self, md, node, prop_name, prop_val):
        print(("<br>%s = \"%s\"" % (prop_name, prop_val.decode('utf-8'))))

    def print_val_prop(self, md, node, prop_name, prop_val):
        print(("<br>%s = 0x%x /* %d */" % (prop_name, prop_val, prop_val)))

    def print_data_prop(self, md, node, prop_name, prop_val):
        print(("<br>%s = {" % prop_name))

        strings = Parser.get_string_list(prop_val)
        if strings:
            for s in strings:
                print("<br>&nbsp;\"%s\"" % s)
        else:
            print_byte = lambda b:"0x%02x" % b
            bpl = 8 # bytes per line

            # Some magic to print 8 columns of bytes: for each
            # chunk of 8 bytes in the byte array, get an array
            # slice 8-long and join it with a space.
            for i in range(0, (len(prop_val)+bpl-1)//bpl):
                print("<br>&nbsp;", ', '.join(map(print_byte,
                    prop_val[i * bpl:(i+1) * bpl])))

        print("<br>}")

    def print_fwd_arc(self, md, node, target_node, settings):
        print(("<br>&rarr; <a href=#0x%x><b>%s</b></a> <i>node_0x%x</i>" %
            (target_node.node_id, target_node.type, target_node.node_id)))

    def print_back_arc(self, md, node, target_node, settings):
        print(("<br>&larr; <a href=#0x%x><b>%s</b></a> <i>node_0x%x</i>" %
            (target_node.node_id, target_node.type, target_node.node_id)))

    def print_named_arc(self, md, node, arc_name, target_node, settings):
        print(("<br>%s &rarr; <a href=#0x%x><b>%s</b></a> <i>node_0x%x</i>" %
            (arc_name, target_node.node_id, target_node.type,
            target_node.node_id)))

    def node_end(self, md, node):
        print("</p>")
        print("</div>")
        print("<hr>")

    def md_end(self, md):
        print('</body></html>')

class PrintSettings:
    def __init__(self, parsable, backlinks, node_ids):
        self.parsable = parsable
        self.backlinks = backlinks
        self.nodeids = node_ids

if __name__ == "__main__":
    usage = "Usage: %prog [-p] [-m] [-q] [-b] [-n] " \
        "\n\t[-g <string>] [-r [0x]<node-ID-number>] <binary-MD-file>" \
        "\n\nUsage: %prog [-P] [-p] [-m] [-q] [-b] [-n] " \
        "\n\t[-g <string>] [-r [0x]<node-ID-number>] " \
        "\n\nUsage: %prog [-P] -B"

    option_parser = OptionParser(usage=usage)

    option_parser.add_option("-m", "--html",
        action="store_true", dest="print_html", default=False,
        help="output the MD in HTML markup")

    option_parser.add_option("-g", "--grep",
        action="store", type="string", dest="grep", default="",
        help='a comma-separated list of node types and properties to include '
            'such as "root,cpu,cpus" to include only nodes named \"root\", '
            '"cpu", or "cpus". To include only specific properties, use '
            'the following format "cpu.id,root.pri-version" or multiple '
            'properties per node with "cpu.id.clock-frequency,root". The '
            'format is '
            "<node1>[.<node1-prop1>][.<node1-prop2>]\n"
            "[,<node2>[.<node2-prop1>]][,...]")

    option_parser.add_option("-r", "--root",
        action="store", type="int", dest="root", default=None,
        help="print a subgraph of the MD rooted at the node with the "
            "provided node ID. In this mode, only back arcs to nodes in "
            "the subgraph are printed. Argument can be in decimal or hex.")

    option_parser.add_option("-P", "--PRI",
        action="store_true", dest="pri", default=False,
        help='read the PRI from /devices/pseudo/ds_pri instead of the '
             'guest MD from /dev/mdesc. The full PRI is only readable from '
             'the primary domain. This option is incompatible with passing '
             'a binary MD input file option.')

    option_parser.add_option("-p", "--parsable",
        action="store_true", dest="parsable", default=False,
        help="print one node per line.")

    option_parser.add_option("-q", "--no-header",
        action="store_false", dest="print_header", default=True,
        help="do not print a header.")

    option_parser.add_option("-b", "--no-back-links",
        action="store_false", dest="backlinks", default=True,
        help="do not print back links. This can speed up subgraph printing "
            "on large MDs.")

    option_parser.add_option("-n", "--no-node-ids",
        action="store_false", dest="nodeids", default=True,
        help="do not print node ID numbers. This is useful with -p/--parsable "
            "for more diff-friendly output.")

    option_parser.add_option("-B", "--binary",
        action="store_true", dest="print_binary", default=False,
        help="emit the complete MD to stdout in binary form. This option "
            "is incompatible with all other options except -P/--PRI. When "
            "used with no other options, the current guest MD is printed. "
            "When used with the -P/--PRI option, the PRI MD is printed. "
            "Redirect stdout to a file to save the MD in binary form.")

    option_parser.epilog = "%s %s. " % (MDPrintApp.Name, MDPrintApp.Version)
    option_parser.epilog += "Prints the contents of the provided binary " \
        "sun4v MD file. If no file is provided, the \"guest MD\" is read " \
        "from the device file \"" + MDPrintApp.DefaultFilePath + "\" which " \
        "is expected to be present on all sun4v guest domains. If no file " \
        "is provided and the -P/--PRI option is used, the PRI " \
        "(physical resource inventory) MD is read from the device " \
        "file \"" + MDPrintApp.DefaultPRIFilePath + "\". The complete PRI " \
        "is only available on the \"primary\" domain.\n\n"

    (flags, args) = option_parser.parse_args()

    # Binary mode can not be used with display options or input files
    if (flags.print_binary and
        # input file?
        ((len (args) != 0) or
        # display options?
        (flags.parsable or flags.print_html or (not flags.nodeids)
            or (not flags.backlinks) or (not flags.print_header)
            or flags.root or flags.grep))):
        MDPrintApp.err("Formatting options and input files can "
            "not be used with the (-B) binary output option.\n")
        option_parser.print_help()
        sys.exit(1)

    # Parsable and HTML modes are are mutually exclusive
    elif flags.parsable and flags.print_html:
        MDPrintApp.err("Parsable (-p) and HTML (-m) options can not "
            "be used together.\n")
        option_parser.print_help()
        sys.exit(1)

    # No input file provided and the PRI option isn't set; use the guest MD.
    # Check the guest MD device file exists and set the mdfilename to the
    # PRI MD device file.
    elif len(args) == 0 and not flags.pri:
        mdfilename = MDPrintApp.DefaultFilePath
        if os.path.exists(mdfilename):
            mdfilename = mdfilename
        else:
            MDPrintApp.err("No input MD file provided and \"%s\" is not "
                "available.\n" % mdfilename)
            option_parser.print_help()
            sys.exit(1)

    # No input and the PRI option is set, check the PRI device file exists
    # and set the mdfilename to the PRI MD device file.
    elif len(args) == 0 and flags.pri:
        mdfilename = MDPrintApp.DefaultPRIFilePath
        if not os.path.exists(mdfilename):
            MDPrintApp.err("PRI option enabled, but the PRI device "
                "\"%s\" does not exist.\n" % mdfilename)
            option_parser.print_help()
            sys.exit(1)

    # Input file provided with PRI option, this is an error
    elif len(args) == 1 and flags.pri:
        mdfilename = args[0]
        MDPrintApp.err("Both an input MD file was provided (\"%s\") "
            "and the PRI option was enabled. Use one or the other.\n" %
            mdfilename)
        option_parser.print_help()
        sys.exit(1)

    # Single input file, make sure it exists
    elif len(args) == 1:
        mdfilename = args[0]
        if not os.path.exists(mdfilename):
            MDPrintApp.err("Input MD file \"%s\" could not "
                "be found.\n" % mdfilename)
            option_parser.print_help()
            sys.exit(1)

    # We didn't get a valid combination of options
    else:
        option_parser.print_help()
        sys.exit(1)

    try:
        if flags.pri:
            pri_size = MDPrintApp.get_pri_size()
            if pri_size == -1:
                MDPrintApp.err("Couldn't read PRI size from \"%s\"\n" %
                    mdfilename)
                option_parser.print_help()
                sys.exit(1)

            buffer_size = 2 * pri_size
            # The PRI driver requires the output buffer to be large
            # enough to read the PRI in one shot, but the python
            # open() syscall only lets us specify the approximate size
            # of the output buffer (using the buffering= argument).
            # We set the buffer size to be double the PRI size and
            # hope for the best.
            mdfile = open(mdfilename, 'rb', buffering=buffer_size)
        else:
            mdfile = open(mdfilename, 'rb')
    except IOError:
        MDPrintApp.err("Couldn't read MD from \"%s\"\n" % mdfilename)
        option_parser.print_help()
        sys.exit(1)

    node_filter = NodeFilter()

    if flags.grep:
        node_filter.grep = MDPrintApp.parse_node_specifier(flags.grep)

    # Skip the extra header if this is an autosave or bootset MD.
    if not flags.pri:
        magic = struct.unpack_from('!BBBB', mdfile.read(4))
        if magic == AUTOSAVE_MAGIC or magic == BOOTSET_MAGIC:
            offset = 296
        else:
            offset = 0
        try:
            mdfile.seek(offset, 0)
        except IOError:
            MDPrintApp.err("Couldn't seek in MD file " + mdfilename + "\n")
            option_parser.print_help()
            sys.exit(1)

    try:
        if flags.pri:
            mdbuf = mdfile.read(pri_size)
        else:
            mdbuf = mdfile.read()
    except IOError:
        MDPrintApp.err("Couldn't read from MD file " + mdfilename + "\n")
        option_parser.print_help()
        sys.exit(1)

    # If we're outputting a binary MD, we don't need to parse the MD,
    # we just need to write the contents to stdout in binary form.
    if flags.print_binary:
        # Python 3 requires sys.stdout.buffer.write() to write binary
        # to stdout, but the stdout.buffer object is not in earlier
        # versions of python. For python 3.x:
        try:
            sys.stdout.buffer.write(mdbuf)
        except AttributeError:
            pass
        except IOError:
            MDPrintApp.err("Failed to write MD to stdout\n")
            option_parser.print_help()
            sys.exit(1)

        # For python 2.x:
        try:
            sys.stdout.write(mdbuf)
        except IOError:
            MDPrintApp.err("Failed to write MD to stdout\n")
            option_parser.print_help()
            sys.exit(1)

        # Done writing to stdout, nothing more to do
        sys.exit(0)

    md = Parser(mdbuf)
    md.parse_header()
    md.parse_nodes()

    if flags.root and flags.root not in md:
        MDPrintApp.err("Subgraph root node ID %s does not "
            "exist in MD\n" % flags.root)
        option_parser.print_help()
        sys.exit(1)

    header = None
    if flags.print_header:
        header = ""
        header += "%s %s\n" % (MDPrintApp.Name, MDPrintApp.Version)
        header += "Input file\n\t\"%s\"\n" % mdfilename
        if flags.root:
            header += "Limiting output to\n"
            header += "\tsubgraph rooted at %s node 0x%x\n" % \
                (md[flags.root].type, flags.root)
        if node_filter.grep:
            header += "Limiting output to\n"
            for node in list(node_filter.grep.keys()):
                if len(node_filter.grep[node]) == 0:
                    props = '*'
                else:
                    props = ', '.join(node_filter.grep[node])
                header += "\t%s . { %s }\n" % (node, props)

    settings = PrintSettings(flags.parsable, flags.backlinks, flags.nodeids)

    output_printer = None

    if flags.print_html:
        output_printer = HTMLPrinter()
    elif flags.parsable:
        output_printer = ParsablePrinter()
    else:
        output_printer = TextPrinter()

    try:
        printer = FilteredPrinter(settings, node_filter, output_printer)

        if flags.print_header: 
            printer.header(header)

        if flags.root:
            printer.print_subgraph(md, flags.root)
        else:
            printer.print_md(md)
    except IOError as e:
        if e.errno != errno.EPIPE:
            raise

    sys.exit(0)
