Current File : //bin/svcbundle
#!/usr/bin/python2.7

#
# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
#

"""svcbundle is a program to generate SMF manifests.

svcbundle accepts multiple occurrences of -s name=value.  Legal values of
name are:

        Name                    Value
        ----                    -----

        bundle-type             Type of service bundle to generate.  Legal
                                values are "manifest" and "profile".
                                manifest is the default.

        day                     The day parameter of a scheduled service
                                (see svc.periodicd(1M)).

        day_of_month            The day_of_month parameter of a scheduled
                                service (see svc.periodicd(1M)).

        duration                Synonym for model.

        enabled                 Indicates whether or not the instance
                                should be enabled.  Legal values are "true"
                                and "false".  The default value is true.

        frequency               The frequency parameter of a scheduled
                                service (see svc.periodicd(1M)).

        hour                    The hour parameter of a scheduled service
                                (see svc.periodicd(1M)).

        interval                The interval parameter of a scheduled
                                service (see svc.periodicd(1M)).  This
                                NV pair, when combined with the start-method
                                pair, will cause svcbundle to emit a
                                manifest for a scheduled service.

        minute                  The minute parameter of a scheduled service
                                (see svc.periodicd(1M)).

        model                   Sets the service model.  This is the value
                                of the startd/duration property.  Refer to
                                svc.startd(1M).  Model can be set to one of
                                the following values:
                                        contract
                                        daemon - synonym for contract
                                        child
                                        wait - synonym for child
                                        transient
                                The default is transient.

        month                   The month parameter of a scheduled service
                                (see svc.periodicd(1M)).

        instance-name           Name of the instance.  The default value is
                                "default".

        instance-property=pg_name:prop_name:prop_type:value
        service-property=pg_name:prop_name:prop_type:value
                                These options are used to create a property
                                group named pg_name in the instance or
                                service, and it will have a type of
                                application.  The PG will have a single
                                property named prop_name with a single
                                value that is of type prop_type.  Property
                                groups with multiple properties can be
                                created by invoking *-property multiple
                                times.  Zero or more *-property=
                                declarations can be used.

                                The property type can be defaulted by using
                                two consecutive colons.  See example 7.
                                For manifests a default property type of
                                astring will be used.  Profiles do not
                                require that the property be specified,
                                since it can usually be determined from
                                other sources of information.

        period                  The period of a periodic service (see
                                svc.periodicd(1M)).  This NV pair, when
                                combined with the start-method pair, will
                                cause svcbundle to emit a manifest for a
                                periodic service.

        rc-script=script_path:run_level
                                This NV pair causes svcbundle to emit a
                                manifest that facilitates conversion of a
                                legacy rc script to an SMF service.
                                script_path is the path to the rc script
                                and run_level is the run level (see
                                init(1M)) where the rc script runs.
                                script_path is used to generate the start
                                and stop exec_method elements in the
                                manifest.  The exec attribute will be set
                                to

                                    script_path %m

                                run_level is used to generate depencencies,
                                so that the script runs at the appropriate
                                time during booting.

        service-name            Name of the service.  This NV pair is
                                required.

        start-method            The command to execute when the service is
                                started.  White space is allowed in the
                                value.  The method tokens that are
                                introduced by % as documented in
                                smf_method(5) are allowed and will be
                                placed in the manifest for expansion by the
                                restarter.  ":true" is allowed.  This NV
                                pair is required for manifests unless the
                                rc-script NV pair is specified.  It is not
                                required for profiles.

        stop-method             The command to execute when the service is
                                stopped.  It accepts values like
                                start-method and also accepts ":kill".
                                :true is the default value for transient
                                services and :kill for contract and child
                                services.

        timezone                The timezone parameter of a scheduled
                                service (see svc.periodicd(1M)).

        weekday_of_month        The weekday_of_month parameter of a
                                scheduled service (see svc.periodicd(1M)).

        week_of_year            The week_of_year parameter of a scheduled
                                service (see svc.periodicd(1M)).

        year                    The year parameter of a scheduled
                                service (see svc.periodicd(1M)).

There can be multiple occurrences of instance-property and
service-property, but the rest of the names can occur only once.  The
command line parameters will be represented as a dictionary where the name
is the key.  The values for instance-property and service-property will be
represented as a CLProperties object, since there can be multiple
occurrences of these names.
"""

try:
    import calendar
    from collections import namedtuple
    import errno
    from gettext import gettext
    from optparse import OptionParser, SUPPRESS_USAGE
    import os
    import re
    import socket
    import string
    import subprocess
    import sys
    import textwrap
    import time
    import warnings
    from xml.dom.minidom import getDOMImplementation
    from xml.dom.minidom import Node
except KeyboardInterrupt:
    # Avoid obnoxious Python traceback on interrupt.
    import sys
    sys.exit(1)

# Turn python warnings into errors, so that we get tracebacks:

warnings.simplefilter("error")

# Declare strings for the command line names:

NM_BUNDLE_TYPE = "bundle-type"
NM_DAY = "day"
NM_DAY_OF_MONTH = "day_of_month"
NM_DURATION = "duration"
NM_ENABLED = "enabled"
NM_FREQUENCY = "frequency"
NM_HOUR = "hour"
NM_INST_NAME = "instance-name"
NM_INST_PROP = "instance-property"
NM_INTERVAL = "interval"
NM_MINUTE = "minute"
NM_MODEL = "model"
NM_MONTH = "month"
NM_PERIOD = "period"
NM_RC_SCRIPT = "rc-script"
NM_REFRESH = "refresh-method"
NM_START = "start-method"
NM_STOP = "stop-method"
NM_SVC_NAME = "service-name"
NM_SVC_PROP = "service-property"
NM_TIMEZONE = "timezone"
NM_WEEK_OF_YEAR = "week_of_year"
NM_WEEKDAY_OF_MONTH = "weekday_of_month"
NM_YEAR = "year"

# NM_OUTPUT_FILE is not an actual name on the command line.  It is set by
# by -o or -i.
NM_OUTPUT_FILE = "output-file"


def check_hostname(host):
    length = len(host)
    if (length > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH) or (length > 255):
        raise PropValueError(gettext("Host name is too long"))
    if host[-1:] == ".":
        # Strip one period from right side
        host = host[:-1]
    for dom in host.split("."):
        if not check_name(NVPair.comp_domain_re, dom):
            raise PropValueError(
                gettext(
                    "\"{0}\" is an invalid hostname component".format(dom)))


def check_ip_addr_by_type(addr_type, addr):
    """Verify that addr is a valid IP address of the addr_type specified."""

    if addr_type not in [socket.AF_INET, socket.AF_INET6]:
        raise SvcbundleError(gettext("Invalid network type"))
    comp = addr.split("/", 1)
    if (len(comp) > 1) and (len(comp[1]) > 0):
        # We have a net mask designator.
        try:
            bits = long(comp[1])
        except ValueError:
            raise PropValueError(gettext("Invalid net mask specifier"))
        if addr_type == socket.AF_INET:
            maxbits = 32
        else:
            maxbits = 128
        if (bits < 0) or (bits > maxbits):
            raise PropValueError(gettext("Invalid net mask specifier"))
    if len(comp[0]) == 0:
        raise PropValueError(gettext("Network address has 0 length"))
    try:
        socket.inet_pton(addr_type, comp[0])
    except socket.error:
        raise PropValueError(gettext("Network address is invalid"))


def check_ip_addr(addr):
    """Verify that addr is either a v4 or v6 IP address."""
    for addr_type in [socket.AF_INET, socket.AF_INET6]:
        try:
            check_ip_addr_by_type(addr_type, addr)
            return
        except PropValueError:
            continue
    else:
        raise PropValueError(gettext("Value is an invalid IP address"))


def check_name(cre, name):
    """Return True if name matches the regular expression.

    cre is a compiled regular expression.  This function checks to see
    if the re covers all the characters in name.  check_name() returns
    True if the re does.
    """
    mo = cre.match(name)
    if mo is None:
        return False
    else:
        return mo.group() == name


def check_utf8(str):
    """Verify that str is a valid utf8 string."""
    try:
        str.decode("utf-8", "strict")
    except UnicodeError:
        raise PropValueError(gettext("Invalid UTF-8 string"))


def extract_pg_info(parent, params, key):
    """Return a list of the property group information at key in params.

    The params entry at key is a CLProperties object which contains the
    information from all of *-properties for either instance or service.
    From the CLProperties object we get a list of property group trees.
    For each PG in the list we create a SmfPropertyGroup object and then
    form a tuple consisiting of the SmfPropertyGroup object and the
    property group tree.  We return a list of these tuples.
    """
    rv = []
    properties = params.get(key)
    if properties == None:
        return rv
    pg_list = properties.get_property_groups()
    for pg_tree in pg_list:
        pg = SmfPropertyGroup(parent)
        rv.append((pg, pg_tree))
    return rv


def is_manifest():
    """Return True if we are generating a manifest."""
    return SmfNode.is_manifest


def safe_so(*text):
    """Send text to stdout while handling pipe breakage."""
    try:
        print ' '.join([str(s) for s in text])
    except IOError as e:
        if e.errno == errno.EPIPE:
            return
        raise


def safe_se(*text):
    """Send text to stderr while handling pipe breakage."""
    try:
        sys.stderr.write(' '.join([str(s) for s in text]))
    except IOError as e:
        if e.errno == errno.EPIPE:
            return
        raise


def fmri_extract_scope(fmri):
    """Extract the scope from the fmri.

    Returns a tuple consisting of (scope, remainder of FMI).
    """
    if fmri.startswith(ValueType.SCF_FMRI_SCOPE_PREFIX):
        # Remove the scope prefix.
        fmri = fmri[len(ValueType.SCF_FMRI_SCOPE_PREFIX):]
        # The service prefix (/) acts as the terminator of the scope.
        parts = fmri.split(ValueType.SCF_FMRI_SERVICE_PREFIX, 1)
        scope = parts[0]
        if len(parts) > 1:
            # Found the service prefix.  The text after the prefix is the
            # remainder of the FMRI.
            fmri = parts[1]
        else:
            # The entire fmri is the scope.  Nothing remains after it.
            fmri = ""
        # If the scope ends with the scope suffix, remove it.
        index = scope.rfind(ValueType.SCF_FMRI_SCOPE_SUFFIX)
        if index >= 0:
            scope = scope[0:index]
    else:
        scope = None
        # Bypass the service prefix if it exists.
        if fmri.startswith(ValueType.SCF_FMRI_SERVICE_PREFIX):
            fmri = fmri[len(ValueType.SCF_FMRI_SERVICE_PREFIX):]
    return (scope, fmri)


def fmri_extract_service_instance(fmri):
    """Extract the service and instance part of fmri.

    Returns a tuple consisting of (service, instance, remainder).  The
    remainder will be positioned just past the property group prefix if it
    exists or at the end of the string if it doesn't.
    """
    svc = None
    inst = None
    # Extract the service.  It can be terminated by instance prefix,
    # property group prefix (if there is no instance specification) or
    # the end of the string.
    inst_idx = fmri.find(ValueType.SCF_FMRI_INSTANCE_PREFIX)
    pg_idx = fmri.find(ValueType.SCF_FMRI_PROPERTYGRP_PREFIX)
    if inst_idx >= 0:
        if pg_idx >= 0:
            if inst_idx < pg_idx:
                # We have an instance.  Service ends at inst. prefix.
                svc = fmri[0:inst_idx]
                # Instance ends at property group prefix.
                inst = fmri[
                    inst_idx +
                    len(ValueType.SCF_FMRI_INSTANCE_PREFIX):pg_idx]
            else:
                # No instance.  We just found the : in the PF prefix.
                # Service ends at PG prefix.
                svc = fmri[0:pg_idx]
            # Remainder is everthing past the property group prefix.
            fmri = fmri[
                pg_idx + len(ValueType.SCF_FMRI_PROPERTYGRP_PREFIX):]
        else:
            # We have an instance, but no PG.
            svc = fmri[0:inst_idx]
            inst = fmri[inst_idx + len(ValueType.SCF_FMRI_INSTANCE_PREFIX):]
            fmri = ""
    else:
        # No instance.
        if pg_idx >= 0:
            # We have a PG.  Service ends at PG prefix.
            svc = fmri[0:pg_idx]
            # Remainder is everthing past the property group prefix.
            fmri = fmri[
                pg_idx + len(ValueType.SCF_FMRI_PROPERTYGRP_PREFIX):]
        else:
            # No instance or PG.
            svc = fmri
            fmri = ""
    return (svc, inst, fmri)


def validate_svc_fmri(fmri):
    """Validate the supplied service FMRI.

    This function is based on scf_parse_svc_fmri() in lowlevel.c.
    """
    svc = None
    inst = None
    pg = None
    prop = None

    if len(fmri) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH:
        raise PropValueError(gettext("FMRI is too long"))
    # Skip service prefix (svc:) if it exists.
    if fmri.startswith(ValueType.SCF_FMRI_SVC_PREFIX):
        fmri = fmri[len(ValueType.SCF_FMRI_SVC_PREFIX):]
    scope, fmri = fmri_extract_scope(fmri)
    svc, inst, fmri = fmri_extract_service_instance(fmri)
    # If fmri is not empty, it now starts with the PG name.
    if len(fmri) > 0:
        parts = fmri.split(ValueType.SCF_FMRI_PROPERTY_PREFIX, 1)
        pg = parts[0]
        if len(parts) > 1:
            prop = parts[1]

    # Now do the validations:
    if (scope is not None) and (len(scope) > 0):
        if not check_name(NVPair.comp_ident_re, scope):
            raise PropValueError(
                gettext("FMRI contains invalid scope"))
    if (svc is None) or (len(svc) == 0):
            raise PropValueError(
                gettext("Service name is empty in FMRI"))
    else:
        if not check_name(NVPair.comp_service_re, svc):
            raise PropValueError(
                gettext("Illegal service name in FMRI"))
    if inst is not None:
        if len(inst) == 0:
            raise PropValueError(
                gettext("Instance name is empty in FMRI"))
        if not check_name(NVPair.comp_name_re, inst):
            raise PropValueError(
                gettext("Illegal instance name in FMRI"))
    if pg is not None:
        if len(pg) == 0:
            raise PropValueError(
                gettext("Property group name is empty in FMRI"))
        if not check_name(NVPair.comp_name_re, pg):
            raise PropValueError(
                gettext("Illegal property group name in FMRI"))
    if prop is not None:
        if len(prop) == 0:
            raise PropValueError(
                gettext("Property name is empty in FMRI"))
        if not check_name(NVPair.comp_name_re, prop):
            raise PropValueError(
                gettext("Illegal property name in FMRI"))


def validate_file_fmri(fmri):
    """Validate a file type fmri.

    This function is based on scf_parse_file_fmri() in lowlevel.c.
    """
    if fmri.startswith(ValueType.SCF_FMRI_FILE_PREFIX):
        fmri = fmri[len(ValueType.SCF_FMRI_FILE_PREFIX):]
    if fmri.startswith(ValueType.SCF_FMRI_SCOPE_PREFIX):
        fmri = fmri[len(ValueType.SCF_FMRI_SCOPE_PREFIX):]
        scope_end = fmri.find("/")
        if scope_end >= 0:
            scope = fmri[0:scope_end]
            fmri = fmri[scope_end:]
        else:
            scope = fmri
        if (len(scope) > 0) and (scope != ValueType.SCF_FMRI_LOCAL_SCOPE):
            raise PropValueError(
                gettext("FMRI contains invalid scope"))
    else:
        # Path must be absolute
        if not fmri.startswith("/"):
            raise PropValueError(
                gettext("File FMRI paths must be absolute"))


"""
We define the following base classes for use by svcbundle:

    PropDef - is a namedtuple holding the information from command line
              names instance-property and service-property.

    RcDef -   is a namedtuple holding the from the rc-script command line
              name.

    SmfNode - Represents SMF object in the Document Object Model (DOM)
              representation of a manifest or profile.

    NVPair  - Represents the name=value parameters of the -s option on the
              command line.

    SvcbundleError - Base class for exceptions for internal use.
"""

# PropDef holds the property group name, property name, property type and
# value for a property.
PropDef = namedtuple("PropDef", "pg_name prop_name prop_type value")

# RcDef holds the rc script path and run level from the rc-script name.
RcDef = namedtuple("RcDef", "script run_level")


class SvcbundleError(Exception):
    """Base class for program specific exceptions"""


class CLError(SvcbundleError):
    """Errors on the command line"""


class PropValueError(SvcbundleError):
    """Property value is invalid"""


class Tree(object):
    def __init__(self, value):
        self.value = value
        self.branches = []

    def __getitem__(self, index):
        """Index through the branches."""
        return self.branches[index]

    def __len__(self):
        return len(self.branches)

    def find_or_add(self, value):
        for branch in self.branches:
            if branch.value == value:
                return branch
        branch = Tree(value)
        self.branches.append(branch)
        return branch


class CLProperties(object):
    """Store property definitions in a tree structure.

    PropDefs which contain PG name, property name, property type and value
    are converted and stored in a tree structure.  This allows us to detect
    declaration of multi-valued properties and PGs with multiple
    properties.  The top tier of the tree contains PG names, the second
    layer property names, the third layer property types and the fourth
    layer contains the property values.  The property value nodes have no
    branches.
    """

    # UNSPECIFIED_PROP_TYPE is used when the user does not specify a
    # property type.  DEFAULT_PROP_TYPE is the default property type when
    # none is specified.
    DEFAULT_PROP_TYPE = "astring"
    UNSPECIFIED_PROP_TYPE = "default"

    def __init__(self):
        self.root = Tree(None)

    def __getitem__(self, index):
        """Return the tree in our branches at the specified index."""
        return self.root.branches[index]

    def add_propdef(self, pd):
        pg = self.root.find_or_add(pd.pg_name)
        prop = pg.find_or_add(pd.prop_name)
        type_tree = prop.find_or_add(pd.prop_type)
        type_tree.find_or_add(pd.value)

    def get_property_groups(self):
        """Return a list of property group trees."""
        return self.root.branches


# Classes that encapsulate DOM objects:


class SmfNode(object):
    """
    Each SmfNode encapsulates a Node object from minidom.  From a
    programming point of view, it would have been nice to just inherit
    directly from Node.  Unfortunately, most Node objects come into being
    by calling create* functions, and these functions would bypass our
    constructors.

    Thus, we use the minidom Node objects to hold the DOM tree structure
    and the SmfNode objects to understand the details of the SMF DTD.  Each
    SmfNode references a minidom object in the node attribute.  We also add
    an attribute, smf_node, to each Node that points to the corresponding
    SmfNode object.

    The SmfNode objects use the following attributes:

        node    - corresponding minidom Node object
        parent  - minidom Node object that is the parent of node

    The SmfNode objects have the following methods:

    intro_comment   - provides a comment to be displayed as an introduction
                      to the element in the generated manifest.

    propagate       - driven by the command line parameters, create the
                      offspring SmfNodes

    translate_attribute - Intended to be overriden by classes that output
                      values to handle translation of special characters to
                      entities.

    writexml        - The xml that is generated by the minidom module is
                      ugly in that it does not handle long lines well.
                      Thus, we provide our own writexml method to produce
                      more readable manifests.

    We provide a few class attributes to provide easy access throughout the
    program.

    document        - Document node.  This is the root of the document.
    implementation  - DOM implementation
    installing      - A boolean to indicate that -i was on the command
                      line.
    is_manifest     - True if we are creating a manifest.
    method_timeout  - Default timeout for exec_methods.
    parser          - The object that is used to parse the command line.
                      We keep it here to give easy access to parser.error()
                      for generation error messages.
    pname           - Program name for use in error messages.
    wrapper         - A TextWrapper object to beautify long lines of
                      output.
    """

    document = None
    implementation = None
    installing = False
    is_manifest = True
    method_timeout = "60"
    parser = None
    pname = "svcbundle"
    wrapper = textwrap.TextWrapper(
        width=75, break_long_words=False, break_on_hyphens=False)

    def __init__(self, parent, element_type):
        """Create a DOM element and setup linkages.

        Parameters:
            parent  - minidom Node that is the parent of the created node
            element_type - string specify the element type

        A new minidom element is created and appeneded to parent.  The node
        is saved in our node attribute and an smf_node attribute is added
        to node to point to us.
        """
        self.parent = parent
        node = self.document.createElement(element_type)
        node.smf_node = self
        self.node = node
        parent.appendChild(node)

    def intro_comment(self, text):
        """Create a comment to introduce this element in the manifest.

        A comment node is created containing text, and it is inserted in
        front of our node in the DOM.
        """
        SmfComment(self.parent, text, self.node)

    def translate_attribute(self, name, value):
        return value

    def writexml(self, writer, indent="", addindent="", newl=""):
        """Write the XML for this node and its descandents.

        Parameters:
            writer      - Output channel
            indent      - String preceed the output of this node
            addindent   - Additional indentation for descendant nodes
            newl        - String to be output at the end of a line

        Output the opening XML tag of this node along with any attributes.
        Descend into any child elements to write them out and then write
        the closing tag.

        We can't use wrapper here, because an attribute value might
        have white space in it.  In this case the TextWrapper code might
        break the line at that white space.
        """
        node = self.node
        assert node.nodeType == node.ELEMENT_NODE
        width = self.wrapper.width
        line = "{0}<{1}".format(indent, node.tagName)
        attr_indent = indent + addindent
        attributes = node.attributes
        if attributes is not None:
            for i in range(attributes.length):
                att = attributes.item(i)
                node_value = att.nodeValue
                # Convert special XML characters to character entities
                node_value = self.translate_attribute(att.nodeName, node_value)
                next_attr = "{0}=\"{1}\"".format(
                    att.nodeName, node_value)
                if len(line) + len(next_attr) + 1 > width:
                    # Need to write the current line before this attribute
                    writer.write("{0}{1}".format(line, newl))
                    line = attr_indent + next_attr
                else:
                    line += " " + next_attr
        if node.hasChildNodes():
            terminator = ">"
        else:
            terminator = "/>"
        if len(line) + len(terminator) > width:
            # Write the line before adding the terminator
            writer.write("{0}{1}".format(line, newl))
            line = indent
        writer.write("{0}{1}{2}".format(line, terminator, newl))

        #
        # Process the children
        #
        if node.hasChildNodes():
            # Write the children
            for n in node.childNodes:
                n.smf_node.writexml(
                    writer, indent + addindent, addindent, newl)
            # Write the closing tag
            writer.write("{0}</{1}>{2}".format(
                    indent, node.tagName, newl))


class SmfComment(SmfNode):
    """ This class encapsulates a DOM Comment node"""

    def __init__(self, parent, text, before_me=None):
        """Create an SmfComment containing text.

        If before_me is None, the comment node will be appended to parent.
        Otherwise, before_me must be a node in parent, and the comment node
        will be inserted in front of before_me.
        """
        com = self.document.createComment(text)
        com.smf_node = self
        self.node = com
        if (before_me is None):
            parent.appendChild(com)
        else:
            parent.insertBefore(com, before_me)

    def writexml(self, writer, indent="", addindent="", newl=""):
        """Write an XML comment on writer.

        Parameters:
            writer      - Output channel
            indent      - String preceed the output of this node
            addindent   - Additional indentation for descendant nodes
            newl        - String to be output at the end of a line

        Print the XML opening comment delimiter.  Then output the text of
        the comment with addindent additional indentation wrapping the text
        at column 75.  Finally, write the XML closing comment delimiter on
        a line by itself.
        """
        node = self.node
        if "--" in node.data:
            raise ValueError("'--' is not allowed in a comment node.")
        assert node.nodeType == Node.COMMENT_NODE
        writer.write("{0}<!--\n".format(indent))
        self.wrapper.initial_indent = indent + addindent
        self.wrapper.subsequent_indent = self.wrapper.initial_indent
        writer.write(self.wrapper.fill(node.data))
        writer.write("{0}{1}-->{2}".format(newl, indent, newl))


class SmfDocument(SmfNode):
    """This class encapsulates a DOM Document node.

    See http://docs.python.org/release/2.7/library/xml.dom.html for
    details of the parameters.
    """

    def __init__(self, qualifiedName, doc_type):
        doc = self.implementation.createDocument(
            None, qualifiedName, doc_type.node)
        self.node = doc
        doc.smf_node = self

    def writexml(self, writer, indent="", addindent="", newl=""):
        writer.write('<?xml version="1.0" ?>' + newl)
        for node in self.node.childNodes:
            node.smf_node.writexml(writer, indent, addindent, newl)


class SmfDocumentType(SmfNode):
    """This class encapsulates a DOM DocumentType node.

    See http://docs.python.org/release/2.7/library/xml.dom.html for
    details of the parameters.
    """

    def __init__(self):
        doc_type = self.implementation.createDocumentType("service_bundle", "",
            "/usr/share/lib/xml/dtd/service_bundle.dtd.1")
        doc_type.smf_node = self
        self.node = doc_type

    def writexml(self, writer, indent="", addindent="", newl=""):
        self.node.writexml(writer, indent, addindent, newl)


# Classes that represent parts of the SMF DTD:

class SmfBundle(SmfNode):
    """Implement the service_bundle element from the DTD"""

    def __init__(self, parent, element):
        """Initialize an SmfBundle.

        SmfBundle is unique in that the corresponding DOM element is
        created automatically by createDocument().  Thus, the element is
        passed in as a parameter rather than being created here.
        """
        self.parent = parent
        element.smf_node = self
        self.node = element

    def propagate(self, params):
        """Add our descendants to the DOM.

        Add our attributes and a service to the DOM.  Then ask the service
        to propagate itself.
        """
        bundle = self.node

        # First set our attributes.  We set name from the service name and
        # type from the command line parameters.  The default type is
        # manifest.
        name = params.get(NM_SVC_NAME, None)
        if name is None:
            raise CLError(gettext("No {0} specified.").format(NM_SVC_NAME))
        bundle.setAttribute("name", name)
        bundle_type = params.get(NM_BUNDLE_TYPE, "manifest")
        bundle.setAttribute("type", bundle_type)

        # Since we have a service name, create the service and tell it to
        # propagate.
        service = SmfService(bundle)
        service.propagate(params)


class SmfDependency(SmfNode):
    """Base class for generating dependencies."""

    # Inheriting classes are permitted to override these variables.
    _grouping = "require_all"
    _restart_on = "none"

    def __init__(self, parent, name, fmri):
        """Create a dependency node.

        Parameters are:
            parent  - Parent for this node in the DOM.
            name    - Name of the dependency.
            fmri    - FMRI to be dependent upon.
        """
        SmfNode.__init__(self, parent, "dependency")
        self.fmri = fmri
        self.dependency_name = name

    def propagate(self, params):
        """Generate the dependency in the DOM."""

        # First set the attributes -- name, gouping, restart_on and type.
        dep = self.node
        dep.setAttribute("name", self.dependency_name)
        dep.setAttribute("grouping", self._grouping)
        dep.setAttribute("restart_on", self._restart_on)
        dep.setAttribute("type", "service")

        # Create the service_fmri for the service that we are dependent
        # upon.
        svc_fmri = SmfServiceFmri(dep, self.fmri)
        svc_fmri.propagate(params)


class SmfAutoDependency (SmfDependency):
    """Generate the automatic dependency on svc:/milestone/multi-user."""

    def __init__(self, parent):
        SmfDependency.__init__(
            self, parent, "multi_user_dependency",
            "svc:/milestone/multi-user")

    def propagate(self, params):
        """Add an explanatory comment and our attributes to the DOM.

        The depencency is hard coded.  There are no command line
        parameters to alter it.
        """
        self.intro_comment(gettext(
                "The following dependency keeps us from starting "
                "until the multi-user milestone is reached."))
        SmfDependency.propagate(self, params)


class SmfSingleUserDependency(SmfDependency):
    def __init__(self, parent):
        SmfDependency.__init__(
            self, parent, "single_user_dependency",
            "svc:/milestone/single-user")


class SmfMultiUserDependency(SmfDependency):
    def __init__(self, parent):
        SmfDependency.__init__(
            self, parent, "multi_user_dependency",
            "svc:/milestone/multi-user")


class SmfMultiUserServerDependency(SmfDependency):
    def __init__(self, parent):
        SmfDependency.__init__(
            self, parent, "multi_user_server_dependency",
            "svc:/milestone/multi-user-server")


class SmfDependent(SmfNode):
    """Base class for dependent elements."""

    # Inheriting classes are permitted to override these variables.
    _grouping = "optional_all"
    _restart_on = "none"

    def __init__(self, parent, name, fmri):
        """Create a dependent node.

        Parameters are:
            parent  - Parent for this node in the DOM.
            name    - Name of the dependent.
            fmri    - FMRI of the service that depends on us.
        """
        SmfNode.__init__(self, parent, "dependent")
        self.fmri = fmri
        self.dependent_name = name

    def propagate(self, params):
        dep = self.node
        dep.setAttribute("name", self.dependent_name)
        dep.setAttribute("grouping", self._grouping)
        dep.setAttribute("restart_on", self._restart_on)
        # Create the service_fmri for the service that depends on us.
        svc_fmri = SmfServiceFmri(dep, self.fmri)
        svc_fmri.propagate(params)


class SmfMultiUserDependent(SmfDependent):
    def __init__(self, parent):
        SmfDependent.__init__(
            self, parent, "multiuser_dependent", "svc:/milestone/multi-user")


class SmfMultiUserServerDependent(SmfDependent):
    def __init__(self, parent):
        SmfDependent.__init__(
            self, parent, "multiuser_server_dependent",
            "svc:/milestone/multi-user-server")


class SmfExecMethod(SmfNode):
    """Base class for the start, stop and refresh exec_method elements"""

    def __init__(self, parent, name):
        """Create an exec_method element in the DOM.

        name which is passed in by one of the overriding classes is the
        name of the exec method.
        """
        SmfNode.__init__(self, parent, "exec_method")
        self.method_name = name

    def propagate(self, params):
        """Add our attributes to the node."""
        em = self.node
        em.setAttribute("name", self.method_name)
        em.setAttribute("type", "method")
        em.setAttribute("timeout_seconds", self.method_timeout)


class SmfPeriodicMethod(SmfNode):
    """Base class for periodic method elements"""

    def __init__(self, parent):
        """Create a periodic_method element in the DOM."""
        SmfNode.__init__(self, parent, "periodic_method")

    def propagate(self, params):
        """Add our attributes to the DOM."""
        em = self.node
        em.setAttribute("delay", "0")
        em.setAttribute("jitter", "0")
        em.setAttribute("recover", "false")
        em.setAttribute("persistent", "false")
        em.setAttribute("timeout_seconds", "0")

        start = params.get(NM_START)
        if (start is None):
            raise CLError(gettext("{0} is required").format(NM_START))
        em.setAttribute("exec", start)

        period = params.get(NM_PERIOD)
        if (period is None):
            raise CLError(gettext("{0} is required").format(NM_PERIOD))
        em.setAttribute("period", period)


class SmfScheduledMethod(SmfNode):
    """Base class for scheduled method elements"""

    def __init__(self, parent):
        """Create a scheduled_method element in the DOM."""
        SmfNode.__init__(self, parent, "scheduled_method")

    def propagate_optional_param(self, name):
        """Add optional attributes to this element"""
        em = self.node

        val = self.my_params.get(name)
        if (val is not None):
            em.setAttribute(name, val)

    def propagate(self, params):
        """Add our attributes to the DOM."""
        self.my_params = params
        em = self.node
        em.setAttribute("recover", "false")

        start = params.get(NM_START)
        if (start is None):
            raise CLError(gettext("{0} is required").format(NM_START))
        em.setAttribute("exec", start)

        interval = params.get(NM_INTERVAL)
        if (interval is None):
            raise CLError(gettext("{0} is required").format(NM_INTERVAL))
        em.setAttribute("interval", interval)

        exclusive = [(NM_MONTH, (NM_WEEK_OF_YEAR)),
                     (NM_WEEK_OF_YEAR, (NM_MONTH, NM_DAY_OF_MONTH,
                      NM_WEEKDAY_OF_MONTH)), (NM_DAY, (NM_DAY_OF_MONTH))]

        # make sure that there aren't any conflicting attributes
        for (key, excluded) in exclusive:
            if (params.get(key) is not None):
                for excl in excluded:
                    if (params.get(excl) is not None):
                        raise CLError(gettext("{0} cannot be used with {1}").
                                      format(key, excl))

        self.propagate_optional_param(NM_FREQUENCY)
        self.propagate_optional_param(NM_TIMEZONE)
        self.propagate_optional_param(NM_YEAR)
        self.propagate_optional_param(NM_WEEK_OF_YEAR)
        self.propagate_optional_param(NM_MONTH)
        self.propagate_optional_param(NM_DAY_OF_MONTH)
        self.propagate_optional_param(NM_WEEKDAY_OF_MONTH)
        self.propagate_optional_param(NM_DAY)
        self.propagate_optional_param(NM_HOUR)
        self.propagate_optional_param(NM_MINUTE)

        em.setAttribute("timeout_seconds", "0")


class SmfExecRcMethod(SmfExecMethod):
    """Base class for rc script start and stop methods"""

    def __init__(self, parent, name):
        SmfExecMethod.__init__(self, parent, name)

    def propagate(self, params):
        """Add our attributes to the DOM.

        Use our base class to generate the standard attributes.  Then we'll
        set the exec method using the path to the rc script.
        """
        SmfExecMethod.propagate(self, params)
        em = self.node
        rc_params = params.get(NM_RC_SCRIPT)
        assert rc_params is not None
        em.setAttribute("exec", "{0} %m".format(rc_params.script))


class SmfExecMethodRcStart(SmfExecRcMethod):
    """Implement start exec_method for rc scripts."""

    def __init__(self, parent):
        SmfExecRcMethod.__init__(self, parent, "start")


class SmfExecMethodRcStop(SmfExecRcMethod):
    """Implement stop exec_method for rc scripts."""

    def __init__(self, parent):
        SmfExecRcMethod.__init__(self, parent, "stop")


class SmfExecMethodRefresh(SmfExecMethod):
    """Implements the refresh exec_method element."""

    def __init__(self, parent):
        SmfExecMethod.__init__(self, parent, "refresh")

    def propagate(self, params):
        """Generate attribute nodes and an introductory comment.

        Use our base class to generate the standard attributes.  Then
        handle the exec attribute ourself.  If a refresh method was
        specified on the command line, we'll use that as our exec
        attribute.  Otherwise, we generate a comment explaining the exec
        attribute and generate an exec attribute of :true.
        """
        SmfExecMethod.propagate(self, params)
        em = self.node
        refresh = params.get(NM_REFRESH)
        if (refresh is not None):
            em.setAttribute("exec", refresh)
        else:
            self.intro_comment(
                gettext(
                    "The exec attribute below can be changed to a "
                    "command that SMF should execute when the service is "
                    "refreshed.  Services are typically refreshed when "
                    "their properties are changed in the SMF repository.  "
                    "See smf_method(5) for more details.  It is common "
                    "to retain the value of :true which means that SMF "
                    "will take no action when the service is refreshed.  "
                    "Alternatively, you may wish to provide a method to "
                    "reread the SMF repository and act on any "
                    "configuration changes."))
            em.setAttribute("exec", ":true")


class SmfExecMethodStart(SmfExecMethod):
    """Implements the start exec_method element."""

    def __init__(self, parent):
        SmfExecMethod.__init__(self, parent, "start")

    def propagate(self, params):
        """Add our attributes to the DOM.

        Use our base class to generate the standard attributes.  Then
        handle the exec attribute ourself.  The start-method is required to
        be specified.
        """
        SmfExecMethod.propagate(self, params)
        em = self.node
        start = params.get(NM_START)
        if (start is None):
            raise CLError(gettext("{0} is required").format(NM_START))
        em.setAttribute("exec", start)


class SmfExecMethodStop(SmfExecMethod):
    """Implements the stop exec_method element."""

    def __init__(self, parent):
        SmfExecMethod.__init__(self, parent, "stop")

    def propagate(self, params):
        """Add our attributes to the DOM.

        Use our base class to generate the standard attributes.  Then
        handle the exec attribute ourself.  If a stop method was specified
        on the command line, we'll use that as our exec attribute.
        Otherwise, we generate a comment explaining the exec attribute and
        generate an exec attribute of :true for transient duration and
        :kill for other durations.
        """
        SmfExecMethod.propagate(self, params)
        em = self.node

        # If we have a stop method, go ahead and use it.  Otherwise, emit a
        # comment explaining what to do with the exec method.
        stop = params.get(NM_STOP, None)
        if (stop is not None):
            em.setAttribute("exec", stop)
        else:
            self.intro_comment(
                gettext(
                    "The exec attribute below can be changed to a "
                    "command that SMF should execute to stop the "
                    "service.  See smf_method(5) for more details."))
            duration = params.get(NM_DURATION, "transient")
            if duration == "transient":
                # Transient services cannot be :killed.
                stop = ":true"
            else:
                stop = ":kill"
            em.setAttribute("exec", stop)


class SmfLoctext(SmfNode):
    """Implements the loctext element of the DTD."""
    def __init__(self, parent):
        SmfNode.__init__(self, parent, "loctext")

    def propagate(self, text):
        """text will be placed as a comment in the loctext element."""
        node = self.node
        node.setAttribute("xml:lang", "C")
        comment = SmfComment(node, text)


class SmfInstance(SmfNode):
    """Implements the instance element of the DTD."""

    def __init__(self, parent):
        SmfNode.__init__(self, parent, "instance")

    def propagate(self, params):
        """Set our attributes.  Generate and propagate our children.

        Create the name and enabled attributes in the DOM.  Additional
        subelements that are added to the DOM and propagated are:
            dependency
            start, stop and refresh methods
            duration property group
        Then we create and propagate any property groups that were
        specified on the command line.
        """
        manifest = is_manifest()

        # First set our attributes
        inst = self.node
        name = params.get(NM_INST_NAME, "default")
        inst.setAttribute("name", name)

        # If we are creating a profile and if -i (installing) was
        # specified, make sure that the instance already exists.  If it
        # doesn't exist the user is trying to create an instance via a
        # profile, but there will be no complete attribute.  This will most
        # likely be frustrating, because svcs will not show the instance
        # without the complete attribute.  The complete attribute is an
        # advanced concept and not supported by this program.
        if (not manifest) and SmfNode.installing:
            fmri = "svc:/{0}:{1}".format(params.get(NM_SVC_NAME), name)
            cmd = ["/usr/bin/svcs", "-H", fmri]
            try:
                inst_check = subprocess.Popen(
                    cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            except OSError as e:
                SmfNode.parser.error(
                    gettext(
                        "Unable to run svcs.  {0}.").format(
                        os.strerror(e.errno)))
            if inst_check.returncode != 0:
                SmfNode.parser.error(
                    gettext(
                        "Service instance {0} does not exist on this "
                        "system, and you are trying to create it with the "
                        "generated profile.  {1} does not support instance "
                        "creation with profiles, so you should use a "
                        "manifest instead.").format(fmri, SmfNode.pname))

        # For manifests we generate the enabled attribute with a default
        # value of true if it was not specified on the command line.  For
        # profiles we are not required to set the enabled attribute.
        enabled = params.get(NM_ENABLED)
        if enabled is None:
            if manifest:
                inst.setAttribute("enabled", "true")
        else:
            inst.setAttribute("enabled", enabled)

        # Propagate the property groups:
        pg_propagate = extract_pg_info(inst, params, NM_INST_PROP)
        for pg, pg_def in pg_propagate:
            pg.propagate(pg_def)


class SmfValueNode(SmfNode):
    """Implement value_node element."""
    def __init__(self, parent, property_type):
        self.property_type = property_type
        SmfNode.__init__(self, parent, "value_node")

    def translate_attribute(self, name, value):
        # The only attribute that we set is the value attribute.
        assert name == "value"
        return ValueType.translate_output(self.property_type, value)

    def propagate(self, value):
        self.node.setAttribute("value", value)


class ValueList(SmfNode):
    """Base class for value lists"""

    _list_type = None

    def __init__(self, parent):
        assert self._list_type is not None
        element_name = self._list_type + "_list"
        SmfNode.__init__(self, parent, element_name)

    def propagate(self, params):
        """Generate the value elements.

        params is a PropDef object where params.value is Tree object whose
        branches contain the values.
        """
        value_list_node = self.node
        values = params.value
        for value_tree in values:
            node = SmfValueNode(value_list_node, self._list_type)
            node.propagate(value_tree.value)


# Value list classes derrived from ValueList


class SmfAstringList(ValueList):
    _list_type = "astring"


class SmfBooleanList(ValueList):
    _list_type = "boolean"


class SmfCountList(ValueList):
    _list_type = "count"


class SmfFmriList(ValueList):
    _list_type = "fmri"


class SmfHostList(ValueList):
    _list_type = "host"


class SmfHostnameList(ValueList):
    _list_type = "hostname"


class SmfIntegerList(ValueList):
    _list_type = "Integer"


class SmfNetAddressList(ValueList):
    _list_type = "net_address"


class SmfNetAddressV4List(ValueList):
    _list_type = "net_address_v4"


class SmfNetAddressV6List(ValueList):
    _list_type = "net_address_v6"


class SmfOpaqueList(ValueList):
    _list_type = "opaque"


class SmfTimeList(ValueList):
    _list_type = "time"


class SmfUriList(ValueList):
    _list_type = "uri"


class SmfUstringList(ValueList):
    _list_type = "ustring"


class SmfPropertyGroup(SmfNode):
    """Implements property_group element of the DTD"""

    def __init__(self, parent, pg_type="application"):
        SmfNode.__init__(self, parent, "property_group")
        self.pg_type = pg_type

    def propagate(self, tree):
        """Create our attributes and our propval element.

        tree is a Tree containing PG name, property names, property types
        and values.

        Attributes are name and type.  name comes from the command line and
        type comes from our pg_type attribute.  pg_type defaults to
        application but can be overridden by a subclass.  After creating
        the attributes, create and propagate the properties.
        """
        pg = self.node
        pg_name = tree.value
        pg.setAttribute("name", pg_name)
        pg.setAttribute("type", self.pg_type)

        # Create the properties

        # The branches of tree contain the property names.
        for prop_tree in tree:
            prop_name = prop_tree.value

            # The branches of prop_tree contain property types.
            for type_tree in prop_tree:
                prop_type = type_tree.value

                # The branches of the type tree contain the values.
                value_tree = type_tree.branches
                if len(value_tree) == 1:
                    # Only 1 value.  Create an propval element.
                    prop_element = SmfPropVal(pg)
                    prop_element.propagate(
                        PropDef(pg_name, prop_name, prop_type,
                                value_tree[0].value))
                else:
                    # For multiple values create a property element.
                    prop_element = SmfProperty(pg)
                    prop_element.propagate(
                        PropDef(pg_name, prop_name, prop_type, value_tree))


class SmfDurationPg(SmfPropertyGroup):
    """Implement the special case duration property group for startd.

    There are a few things that make this class special.  First the PG type
    is "framework" rather "application".  We handle this by setting
    self.pg_type in the constructor.

    For manifests the second thing that makes duration interesting is that
    we do not always need to generate the duration PG in the XML.  If the
    user has specified a duration of contract (daemon is a synonym), there
    is no need to generate the property group.  That is because contract is
    the default duration.  In this case we merely generate a comment.

    For profiles we always generate a duration if it was specified on the
    command line.  We are assuming that the user wants to override what is
    in the manifest.
    """

    def __init__(self, parent, params):
        """Either create a comment or a property group node."""
        duration = params.get(NM_DURATION, "transient")
        if duration == "daemon":
            duration = "contract"
        if duration == "wait":
            duration = "child"
        if (duration == "contract") and is_manifest():
            SmfComment(
                parent, gettext(
                    "We do not need a duration property group, because "
                    "contract is the default.  Search for duration in "
                    "svc.startd(1M). "))
            self.duration = "contract"
        else:
            SmfPropertyGroup.__init__(self, parent, "framework")
            self.duration = duration

    def propagate(self, params):
        """Propagate the duration PG.

        If we are generating a manifest and the duration is contract, we
        don't need to do anything.  In this case a commend has already been
        generated and contract is the default.

        If the duration is not contract or if we're generating a profile,
        go ahead and propagate the duration PG.
        """
        if (self.duration == "contract") and is_manifest():
            return
        properties = CLProperties()
        properties.add_propdef(
            PropDef("startd", "duration", "astring", self.duration))
        SmfPropertyGroup.propagate(self, properties[0])


class ValueType(object):
    """Provide facilities for manipulating property values of vaious types."""

    Implementors = namedtuple(
        "Implementors",
        "list_class validator out_translator")

    # XXX we need an interface to scf_limit(3SCF) and libscf #defines

    SCF_LIMIT_MAX_FMRI_LENGTH = 628
    SCF_LIMIT_MAX_PG_TYPE_LENGTH = 119
    SCF_LIMIT_MAX_NAME_LENGTH = 119
    SCF_LIMIT_MAX_VALUE_LENGTH = 4095

    # Strings for use in constructing FMRIs
    SCF_FMRI_SVC_PREFIX = "svc:"
    SCF_FMRI_FILE_PREFIX = "file:"
    SCF_FMRI_SCOPE_PREFIX = "//"
    SCF_FMRI_LOCAL_SCOPE = "localhost"
    SCF_FMRI_SCOPE_SUFFIX = "@localhost"
    SCF_FMRI_SERVICE_PREFIX = "/"
    SCF_FMRI_INSTANCE_PREFIX = ":"
    SCF_FMRI_PROPERTYGRP_PREFIX = "/:properties/"
    SCF_FMRI_PROPERTY_PREFIX = "/"
    SCF_FMRI_LEGACY_PREFIX = "lrc:"

    # Map special XML characters to their entities.  Don't need to check
    # for apostrophe, because we don't use that as a delimiter when writing
    # attributes.  &amp; must be first in the list to avoid converting the
    # ampersand in other entities.
    out_map = [
        ("&", "&amp;"),
        ('"', "&quot;"),
        ("<", "&lt;"),
        (">", "&gt;")]

    def _char_to_entity(value):
        """Convert XML special characters in value to an entity."""
        for c, entity in ValueType.out_map:
            value = value.replace(c, entity)
        return value

    # The following validation functions are modeled on
    # valid_encoded_value() in scf_type.c.

    def _validate_astring(value):
        if len(value) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH:
            raise PropValueError(gettext("Value is too long"))

    def _validate_boolean(value):
        if value != "true" and value != "false":
            raise PropValueError(
                gettext("Value must be true or false"))

    def _validate_count(value):
        try:
            i = long(value)
        except ValueError:
            raise PropValueError(gettext("Value is not a number"))
        if i < 0:
            raise PropValueError(gettext("Value is negative"))

    def _validate_fmri(fmri):
        """Validate the provided FMRI."""
        if fmri.startswith(ValueType.SCF_FMRI_FILE_PREFIX):
            validate_file_fmri(fmri)
        else:
            validate_svc_fmri(fmri)

    def _validate_host(host):
        """host must be either a hostname or an IP address."""
        exceptions = 0
        try:
            check_hostname(host)
        except PropValueError:
            exceptions += 1
        try:
            check_ip_addr(host)
        except:
            exceptions += 1
        if exceptions >= 2:
            raise PropValueError(
                gettext("Value is neither a valid host name nor IP address"))

    def _validate_hostname(host):
        check_hostname(host)

    def _validate_integer(value):
        try:
            i = long(value)
        except ValueError:
            raise PropValueError(gettext("Value is not a number"))

    def _validate_opaque(str):
        """Verify the str is opaque.

        All characters must be hex, there must be an even number, and
        length must be < 2 * SCF_LIMIT_MAX_VALUE_LENGTH."""
        strlen = len(str)
        if strlen % 2 != 0:
            raise PropValueError(
                gettext(
                    "Opaque string must contain an even number of characters"))
        if strlen >= 2 * ValueType.SCF_LIMIT_MAX_VALUE_LENGTH:
            raise PropValueError(gettext("Opaque string is too long."))
        for c in str:
            if c not in string.hexdigits:
                raise PropValueError(
                    gettext("Opaque string must only contain hex digits"))

    def _validate_v4addr(addr):
        check_ip_addr_by_type(socket.AF_INET, addr)

    def _validate_v6addr(addr):
        check_ip_addr_by_type(socket.AF_INET6, addr)

    def _validate_ipaddr(addr):
        check_ip_addr(addr)

    def _validate_time(time):
        """time is of the form seconds.nanoseconds."""
        if len(time) <= 0:
            raise PropValueError(gettext("Time value is empty"))
        comp = time.split(".")
        if len(comp) >= 1:
            if len(comp[0]) <= 0:
                raise PropValueError(gettext("No seconds specified"))
            try:
                sec = long(comp[0])
            except ValueError:
                raise PropValueError(
                    gettext("Time contains non-numeric values"))
        if len(comp) == 2:
            if len(comp[1]) > 9:
                raise PropValueError(
                    gettext(
                        "Fractional part of time must contain no more than "
                        "9 digits"))
            try:
                nanosec = long(comp[1])
            except ValueError:
                raise PropValueError(
                    gettext("Time contains non-numeric values"))
        if len(comp) > 2:
            raise PropValueError(
                gettext("Time is not of the form seconds.fraction"))

    def _validate_uri(str):
        """Verify that str is a valid URI.

        The following is quoted from RFC 2396:

          As described in Section 4.3, the generic URI syntax is not
          sufficient to disambiguate the components of some forms of URI.
          Since the "greedy algorithm" described in that section is
          identical to the disambiguation method used by POSIX regular
          expressions, it is natural and commonplace to use a regular
          expression for parsing the potential four components and fragment
          identifier of a URI reference.

          The following line is the regular expression for breaking-down a
          URI reference into its components.

             ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
              12            3  4          5       6  7        8 9

          The numbers in the second line above are only to assist
          readability; they indicate the reference points for each
          subexpression (i.e., each paired parenthesis).  We refer to the
          value matched for subexpression <n> as $<n>.  For example,
          matching the above expression to

             http://www.ics.uci.edu/pub/ietf/uri/#Related

          results in the following subexpression matches:

             $1 = http:
             $2 = http
             $3 = //www.ics.uci.edu
             $4 = www.ics.uci.edu
             $5 = /pub/ietf/uri/
             $6 = <undefined>
             $7 = <undefined>
             $8 = #Related
             $9 = Related

          where <undefined> indicates that the component is not present, as
          is the case for the query component in the above example.
          Therefore, we can determine the value of the four components and
          fragment as

             scheme    = $2
             authority = $4
             path      = $5
             query     = $7
             fragment  = $9

        We will consider the URI to be valid if the length of the path
        component is greater than 0 and if the length of the entire URI is
        less than SCF_LIMIT_MAX_VALUE_LENGTH.
        """
        if len(str) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH:
            raise PropValueError(gettext("URI is too long"))
        if len(str) == 0:
            raise PropValueError(gettext("URI has zero length"))
        mo = NVPair.comp_uri_re.match(str)
        if mo is None:
            raise PropValueError(gettext("URI is invalid"))
        if mo.group() != str:
            # Didn't match the whole string.
            raise PropValueError(gettext("URI is invalid"))
        if len(mo.group(5)) == 0:
            raise PropValueError(
                gettext("URI does not contain a path component"))

    def _validate_ustring(str):
        """Validate UTF-8 strings."""
        check_utf8(str)

    _value_type_map = {
        "astring": Implementors(
            SmfAstringList, _validate_astring, _char_to_entity),
        "boolean": Implementors(SmfBooleanList, _validate_boolean, None),
        "count": Implementors(SmfCountList, _validate_count, None),
        "fmri": Implementors(SmfFmriList, _validate_fmri, None),
        "host": Implementors(SmfHostList, _validate_host, None),
        "hostname": Implementors(
            SmfHostnameList, _validate_hostname, None),
        "integer": Implementors(SmfIntegerList, _validate_integer, None),
        "net_address": Implementors(
            SmfNetAddressList, _validate_ipaddr, None),
        "net_address_v4": Implementors(
            SmfNetAddressV4List, _validate_v4addr, None),
        "net_address_v6": Implementors(
            SmfNetAddressV6List, _validate_v6addr, None),
        "opaque": Implementors(
            SmfOpaqueList, _validate_opaque, None),
        "time": Implementors(SmfTimeList, _validate_time, None),
        "uri": Implementors(
            SmfUriList, _validate_uri, _char_to_entity),
        "ustring": Implementors(
            SmfUstringList, _validate_ustring, _char_to_entity)
    }

    def valid_type(cls, prop_type):
        """Return True if prop_type is a valid property type."""
        return prop_type in cls._value_type_map

    valid_type = classmethod(valid_type)

    def list_class(cls, prop_type):
        """Return the class that implements value lists for prop_type."""
        implementors = cls._value_type_map.get(prop_type)
        if implementors is None:
            return None
        return implementors.list_class

    list_class = classmethod(list_class)

    def translate_output(cls, prop_type, value):
        """Translate internal representation for output."""
        implementors = cls._value_type_map.get(prop_type)
        if implementors is None:
            return value
        trans = implementors.out_translator
        if trans is None:
            return value
        return trans(value)

    translate_output = classmethod(translate_output)

    def valid_value(cls, prop_type, value):
        """Insure that value is valid for prop_type."""
        implementor = cls._value_type_map.get(prop_type)
        if implementor is None:
            raise CLError(
                gettext(
                    "\"{0}\" is not a valid property type.").format(prop_type))
        func = implementor.validator
        if func is None:
            return True
        return func(value)

    valid_value = classmethod(valid_value)


class SmfProperty(SmfNode):
    """Implements the property DTD element."""
    def __init__(self, parent):
        SmfNode.__init__(self, parent, "property")

    def propagate(self, params):
        """Set attributes of element and generate enclosed value list.

        params is a PropDef object.
        """
        node = self.node
        node.setAttribute("name", params.prop_name)
        prop_type = params.prop_type
        if prop_type == CLProperties.UNSPECIFIED_PROP_TYPE:
            prop_type = CLProperties.DEFAULT_PROP_TYPE
        node.setAttribute("type", prop_type)
        cls = ValueType.list_class(prop_type)
        if cls is None:
            raise CLError(
                gettext("\"{0}\" is an unsupported property type.").format(
                    params.prop_type))
        value_list = cls(node)
        value_list.propagate(params)


class SmfPropVal(SmfNode):
    """Implements the propval DTD element."""

    def __init__(self, parent):
        SmfNode.__init__(self, parent, "propval")

    def propagate(self, params):
        """Set the attributes of the propval node.

        params is a PropDef.
        """
        prop = self.node
        prop.setAttribute("name", params.prop_name)
        prop_type = params.prop_type
        if prop_type == CLProperties.UNSPECIFIED_PROP_TYPE:
            if is_manifest():
                # For manifests use the default prop type.
                # Profiles don't require a prop type
                prop_type = CLProperties.DEFAULT_PROP_TYPE
                prop.setAttribute("type", prop_type)
        else:
            prop.setAttribute("type", prop_type)
        self.property_type = prop_type
        prop.setAttribute("value", params.value)

    def translate_attribute(self, name, value):
        if name == "value":
            return ValueType.translate_output(self.property_type, value)
        else:
            return value


# Classes for generating the templates section:

class SmfCommonName(SmfNode):
    """Generate the common name element."""
    def __init__(self, parent):
        SmfNode.__init__(self, parent, "common_name")

    def propagate(self, params):
        """Generate a common_name element with comment to explain."""
        cn = self.node
        loctext = SmfLoctext(cn)
        loctext.propagate(
            gettext("Replace this comment with a short name for the service."))


class SmfDescription(SmfNode):
    """Generate the description element"""
    def __init__(self, parent):
        SmfNode.__init__(self, parent, "description")

    def propagate(self, params):
        """Generate a description element with comment to explain."""
        desc = self.node
        loctext = SmfLoctext(desc)
        loctext.propagate(
            gettext(
                "Replace this comment with a brief description of the "
                "service"))


class SmfTemplate(SmfNode):
    def __init__(self, parent):
        SmfNode.__init__(self, parent, "template")

    def propagate(self, params):
        """Generate a simple template."""
        template = self.node
        cn = SmfCommonName(template)
        cn.propagate(params)
        desc = SmfDescription(template)
        desc.propagate(params)


class SmfService(SmfNode):
    """Implements the service element of the DTD."""

    # If we're generating a profile, we don't always need to generate an
    # instance specification.  We only need to do it if one of the
    # following appears on the command line.
    instance_generators = [NM_ENABLED, NM_INST_NAME, NM_INST_PROP]

    # Map to tell us the dependency (first item in the list) and dependent
    # (second item in the list) to generate for rc-script manifests.  Index
    # into the map is the run level.
    run_level_map = {
        "s": [SmfSingleUserDependency, SmfMultiUserDependent],
        "S": [SmfSingleUserDependency, SmfMultiUserDependent],
        "0": [None, None],
        "1": [SmfSingleUserDependency, SmfMultiUserDependent],
        "2": [SmfMultiUserDependency, SmfMultiUserServerDependent],
        "3": [SmfMultiUserServerDependency, None],
        "4": [SmfMultiUserServerDependency, None],
        "5": [None, None],
        "6": [None, None]
    }

    def __init__(self, parent):
        SmfNode.__init__(self, parent, "service")

    def propagate(self, params):
        """Add service attributes and child elements to the DOM.

        First set our name, version and type attributes.  Name comes from
        the command line parameters at params.  Version and type are always
        set to 1 and service

        Additional subelements that are added to the DOM and propagated
        are:
            dependency
            dependents
            start, stop and refresh methods
            duration property group

        Next add any property groups specified on the command line to the
        DOM.  Propagate the property groups.  Finally add an instance to
        the DOM and propagate it.
        """
        manifest = is_manifest()
        svc = self.node
        name = params.get(NM_SVC_NAME, None)
        if name is None:
            raise CLError(gettext("No {0} specified.".format(NM_SVC_NAME)))
        svc.setAttribute("name", name)
        svc.setAttribute("version", "1")
        svc.setAttribute("type", "service")

        # Create the subelements
        #
        # For manifests we automatically generate a number of elements in
        # order to have a working manifest.  For profiles, on the other
        # hand, we only want to generate elements that were specified on
        # the command line.
        if manifest:
            is_periodic = False
            # The list of classes to propagate varies if we are doing an rc
            # script.
            rc_params = params.get(NM_RC_SCRIPT)
            if rc_params is None:
                # Not an rc script

                # If we have a "period" pair, then we
                # want to emit a periodic_method element
                # as opposed to an exec_method for the
                # start method.
                #
                # Additionally, periodic services don't
                # get stop or refresh methods
                if (params.get(NM_PERIOD) is None and
                   params.get(NM_INTERVAL) is None):
                    propagate = [
                        SmfAutoDependency(svc),
                        SmfExecMethodStart(svc),
                        SmfExecMethodStop(svc),
                        SmfExecMethodRefresh(svc)]
                elif params.get(NM_PERIOD) is not None:
                    is_periodic = True
                    propagate = [
                        SmfAutoDependency(svc),
                        SmfPeriodicMethod(svc)]
                else:
                    is_periodic = True
                    propagate = [
                        SmfAutoDependency(svc),
                        SmfScheduledMethod(svc)]
            else:
                propagate = []

                # We're processing an rc script.  Figure out if we need to
                # generate a milestone dependency and dependent.
                run_level = rc_params.run_level
                milestones = self.run_level_map.get(run_level)
                if milestones is None:
                    raise CLError(
                        gettext(
                            "\"{0}\" is an invalid run level for {1}").format(
                            run_level, NM_RC_SCRIPT))
                for milestone in milestones:
                    if milestone is not None:
                        propagate.append(milestone(svc))
                propagate.append(SmfExecMethodRcStart(svc))
                propagate.append(SmfExecMethodRcStop(svc))
                propagate.append(SmfExecMethodRefresh(svc))
            if is_periodic is False:
                propagate.append(SmfDurationPg(svc, params))
        else:
            propagate = []
            if NM_START in params:
                propagate.append(SmfExecMethodStart(svc))
            if NM_STOP in params:
                propagate.append(SmfExecMethodStop(svc))
            if NM_REFRESH in params:
                propagate.append(SmfExecMethodRefresh(svc))

        # Propagate the elements
        for element in propagate:
            element.propagate(params)

        # Create any property groups
        pg_propagate = extract_pg_info(svc, params, NM_SVC_PROP)
        for pg, pg_def in pg_propagate:
            pg.propagate(pg_def)

        # Create and propagate an instance if necessary.  If we're
        # producing a manifest, we always generate the instance.  For
        # profiles, we only generate the instance if we have something to
        # put in it.
        if not manifest:
            for name in self.instance_generators:
                if name in params:
                    break
            else:
                # No instance generators.
                return
        inst = SmfInstance(svc)
        inst.propagate(params)

        # If we're doing a manifest, generate a simple template.
        if manifest:
            template = SmfTemplate(svc)
            template.propagate(params)


class SmfServiceFmri(SmfNode):
    """Implements the service_fmri element of the DTD."""

    def __init__(self, parent, fmri):
        SmfNode.__init__(self, parent, "service_fmri")
        self.fmri = fmri

    def propagate(self, params):
        self.node.setAttribute("value", self.fmri)


# Classes for processing command line name value pairs


class NVPair(object):
    """Base class for processing command line -s name=value pairs:

    The purpose of the NVPair class is to process the name/value pairs
    specified with the -s option on the command line and add them to the
    params dictionary.  The SmfNode classes then obtain command line
    information from the params dictionary.  The work is done by the
    constructor with the help of the update_params() method.

    In most cases the update_params() method provided by the base class
    will suffice, but it can be overriden as is done by the Property class.
    update_params() is driven by 5 class attributes -- _count, _max, _name,
    _key and _legal_values.

    The objects add the value to a dictionary using the name as the key.
    This work is done in the constructor with the help of update_params()
    method.  Inheriting classes are expected to override update_params() to
    handle special cases.

    The default implementation of update_values() calls validate() to
    check the provided value.  This class also provides a default
    implementation of validate() which merely checks to see if value is in
    the list of _legal_values.  Inheriting classes should feel free to
    override this method.

    A class method, describe_yourself(), is used by the help subcommand.
    It writes a description of the name to stdout using the safe_so()
    function.  It is driven by the _description and _value_mnemonic
    attributes.  All subclasses should override _description and
    _value_mnemonic.

    Most subclasses will be able to get this base class to do their work by
    merely overriding these class attributes.  Inheriting classes must
    create their own definitions of _count, _description, _name, _key and
    _value_mnemonic.  They should also override _legal_values if they plan
    to use the default validate() function.  They can also override _max if
    appropriate.  The usage of these variables is as follows:

        _count  Keeps track of the number of instances of the class.
                for comparison to _max.  (read/write)
        _description A string describing the -s name that is implemented by
                the class.
        _legal_values A list of legal values for use with the default
                validate() function.
        _max    Maximum number of instances that are allowed for the
                class.  -1 implies no maximum.  (read-only)
        _name   name part of -s name=value that this class represents.
                (read-only)
        _key    Key for referencing the params dictionary.  Except in the
                case of synonyms it should be the same as _name.
                (read-only).
        _value_mnemonic - Textual representation of the value part of the
                name/value pair for use in printing a representation of the
                command in help messages.

    We also have several class variables to assist in regular expression
    matching of names, e.g. service names or property names.  They are:

        domain_re       - Domain component of a host name.
        comp_domain_re  - RE compiled version of domain_re
        ident_re        - A regular expression for an identifier.
        comp_ident_re   - The RE compiled version of ident_re.
        name_re         - A regular expression for property group, property
                          and instance names.
        comp_name_re    - The RE compiled version of name_re.
        service_re      - A regular expression for service names.
        comp_service_re - The RE compiled version of service_re.
        uri_re          - A regular expression for URIs
        comp_uri_re     - Compiled version of uri_re.
    """

    _count = 0
    _description = None
    _legal_values = None
    _max = 1
    _name = "undefined"
    _key = ""
    _value_mnemonic = None

    domain_re = r"(?!-)[A-Z\d-]{1,63}(?<!-)$"
    comp_domain_re = re.compile(domain_re, re.IGNORECASE)
    ident_re = r"[A-Za-z][_A-Za-z0-9-]*"
    comp_ident_re = re.compile(ident_re)
    name_re = r"([A-Za-z][_A-Za-z0-9.-]*,)?[A-Za-z][_A-Za-z0-9-]*"
    comp_name_re = re.compile(name_re)
    service_re = name_re + r"(/" + name_re + r")*"
    comp_service_re = re.compile(service_re)
    uri_re = r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?"
    comp_uri_re = re.compile(uri_re)

    def __init__(self, value, params):
        """Count this instance and add value to the params dictionary.

        Count the instance and make sure that we have not exceeded the
        maximum for the instance.  If the maximum is not exceeded call
        update_params() to add the value to the params dictionary.
        """
        count = self.__class__._count
        count += 1
        max_inst = self._max
        if (max_inst >= 0) and (count > max_inst):
            raise CLError(
                gettext(
                    "No more than {0} occurrence of \"{1}\" is "
                    "allowed.").format(max_inst, self._name))
        self.update_params(params, self._key, value)
        self.__class__._count = count

    def update_params(self, params, key, value):
        """Validate value and add it to params at key.

        If key already exists in params, throw an exception.
        """
        if key in params:
            raise CLError(
                gettext(
                    "Multiple occurrences of \"{0}\" are prohibited.").format(
                    self._name))
        # Save value for later validation.
        self.value = value
        params[key] = value

    def validate(self, params):
        """Check to see if value is in list of legal values."""
        if self._legal_values is None:
            return
        if self.value in self._legal_values:
            return
        raise CLError(
            gettext("\"{0}\" is not a legal value for {1}").format(
                self.value, self._name))

    def describe_yourself(cls, indent="", addindent="    "):
        """Write a description of name to standard out.

        Parameters:
            indent      - Indentation string for the first line of output.
            addindent   - String to be added to indent for subsequent
                          lines.
        """
        assert cls._description is not None
        assert cls._value_mnemonic is not None

        safe_so(
            textwrap.fill(
                "-s {0}={1}".format(cls._name, cls._value_mnemonic),
                75,
                initial_indent=indent,
                subsequent_indent=indent + addindent))
        indent = indent + addindent
        safe_so(textwrap.fill(
                cls._description,
                75,
                initial_indent=indent,
                subsequent_indent=indent))

    describe_yourself = classmethod(describe_yourself)


class BundleType(NVPair):
    _count = 0
    _legal_values = ["manifest", "profile"]
    _name = NM_BUNDLE_TYPE
    _key = NM_BUNDLE_TYPE
    _value_mnemonic = "type"
    _description = gettext(
        'Specifies the type of bundle to generate.  Legal values are '
        '"manifest" and "profile".  The default is manifest.')

    def update_params(self, params, key, value):
        if value != "manifest":
            SmfNode.is_manifest = False
        NVPair.update_params(self, params, key, value)


class Duration(NVPair):
    _count = 0
    _legal_values = ["contract", "child", "transient", "daemon", "wait"]
    _key = NM_DURATION
    _name = NM_DURATION
    _value_mnemonic = "service_model"
    _description = gettext("{0} is a synonym for {1}.").format(
        NM_DURATION, NM_MODEL)


class Enabled(NVPair):
    _count = 0
    _legal_values = ["true", "false"]
    _name = NM_ENABLED
    _key = NM_ENABLED
    _value_mnemonic = "boolean"
    _description = gettext(
        'Specifies whether or not the service should be enabled.  Legal '
        'values are "true" and "false".')


class InstanceName(NVPair):
    _count = 0
    _name = NM_INST_NAME
    _key = NM_INST_NAME
    _value_mnemonic = "name"
    _description = gettext("Specifies the name of the instance.")

    def validate(self, params):
        if check_name(self.comp_name_re, self.value):
            return
        raise CLError(gettext("\"{0}\" is an invalid {1}").format(
                self.value, self._name))


class Model(Duration):
    """model is a synonym for duration"""
    _legal_values = ["contract", "child", "transient", "daemon", "wait"]
    _name = NM_MODEL
    _value_mnemonic = "service_model"
    _description = gettext(
        'Sets the service model.  This is the value of the '
        'startd/duration property.  Refer to svc.startd(1M).  '
        'Model can be be set to one of "contract", "child" or '
        '"transient".  '
        'Also "daemon" can be use as a synonym for "contract", and '
        '"wait" can be used as a synonym for "child".  The default value '
        'is "transient".')


class Period(NVPair):
    _count = 0
    _name = NM_PERIOD
    _key = NM_PERIOD
    _value_mnemonic = "period"
    _description = gettext(
        "")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        NM_START, self._name)))

        try:
            validate_time(self.value)
        except PropValueError as e:
            raise CLError(
                gettext(
                    "\"{0}\" is not a valid time: {1}".format(
                        self._name, str(e))))


class RcScript(NVPair):
    _count = 0
    _name = NM_RC_SCRIPT
    _key = NM_RC_SCRIPT
    _value_mnemonic = "script_path:run_level"
    _description = gettext(
        "Generates a manifest that facilitates conversion of a legacy rc "
        "script to an SMF service.  script_path is the path to the rc "
        "script and run_level is the  run level of the script (see "
        "init(1M)).  script_path is used to generate the start and stop "
        "method elements in the manifest.  run_level is used to generate "
        "dependencies so that the script runs at the appropriate time.")

    def update_params(self, params, key, value):
        rc_def = value.rpartition(":")
        if (len(rc_def[0]) == 0) or (rc_def[1] != ":") or \
                (len(rc_def[2]) == 0):
            raise CLError(
                gettext(
                    "Value for {0} must contain 2 colon "
                    "separated fields").format(
                    NM_RC_SCRIPT))
        NVPair.update_params(self, params, key, RcDef(rc_def[0], rc_def[2]))

    def validate(self, params):
        """Insure that rc-script not specified with exec methods.

        We could also check run levels at this point, but it is more
        convenient to do it in SmfService where we already have dictionary
        of run levels.
        """
        if NM_START in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specfied with \"{1}\"").format(
                    self._name, NM_START))
        if NM_STOP in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specfied with \"{1}\"").format(
                    self._name, NM_STOP))


class RefreshMethod(NVPair):
    _count = 0
    _name = NM_REFRESH
    _key = NM_REFRESH
    _value_mnemonic = "command"
    _description = gettext(
        "Specifies the command to execute when the service is "
        "refreshed.  White space is allowed in the value.  The method "
        "tokens that are introduced by % as documented in smf_method(5) "
        "are allowed.  :true is the default.").format(NM_REFRESH)


class StartMethod(NVPair):
    _count = 0
    _name = NM_START
    _key = NM_START
    _value_mnemonic = "command"
    _description = gettext(
        "Specifies the command to execute when the service is started.  "
        "White space is allowed in the value.  The method tokens that "
        "are introduced by % as documented in smf_method(5) are allowed.  "
        ":true is allowed.  {0} is required for manifests.").format(
            NM_START)

    def validate(self, params):
        """Don't specify with rc-script."""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specfied with \"{1}\"").format(
                    self._name, NM_RC_SCRIPT))


class StopMethod(NVPair):
    _count = 0
    _name = NM_STOP
    _key = NM_STOP
    _value_mnemonic = "command"
    _description = gettext(
        "Specifies the command to execute when the service is stopped.  "
        "White space is allowed in the value.  The method tokens that "
        "are introduced by % as documented in smf_method(5) are allowed.  "
        "Both :true and :kill are allowed.  :true is the default for "
        "transient services and :kill is the default for contract and "
        "child services.")

    def validate(self, params):
        """Don't specify with rc-script."""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specfied with \"{1}\"").format(
                    self._name, NM_RC_SCRIPT))


class ServiceName(NVPair):
    _count = 0
    _name = NM_SVC_NAME
    _key = NM_SVC_NAME
    _value_mnemonic = "name"
    _description = (
        gettext(
            "Specifies the name of the service.  {0} is required.").format(
            NM_SVC_NAME))

    def validate(self, params):
        """Verify that our value is a valid service name."""
        if check_name(self.comp_service_re, self.value):
            return
        raise CLError(gettext("\"{0}\" is not a valid {1}").format(
                self.value, self._name))


class Property(NVPair):
    """Base class for instance-property and service-property:

    This class is special in two ways.  Because multiple occurrences of
    these names are allowed on the command line, we cannot store a simple
    value in the params dictionary.  Instead a CLProperties object is
    stored in the params dictionary.

    The second difference is that the values associated with these names
    are complex.  The value actually contains property group name, property
    name, property type and value all separated by colons.  We break these
    apart and add them as a PropDef to the CLProperties object.  All
    this is handled by the update_params() method that we provide.
    """
    _value_mnemonic = "pg_name:prop_name:prop_type:value"
    _description = gettext(
        "This option is used to "
        "create a property group named pg_name.  The PG will have a "
        "single property named prop_name with a single value that is of "
        "type prop_type.  Zero or more declarations may be specified.")

    def update_params(self, params, key, value):
        # Need to get the 4 parts of the property definition
        prop_def_components = value.split(":", 3)
        if len(prop_def_components) != 4:
            raise CLError(
                gettext(
                    "Property definition \"{0}\" must have 4 "
                    "components.").format(
                    value))
        if prop_def_components[2] == CLProperties.UNSPECIFIED_PROP_TYPE:
            raise CLError(
                gettext(
                    "\"{0}\" is an invalid property type in {1}.").format(
                    prop_dev_components[2], value))
        if len(prop_def_components[2]) == 0:
            prop_def_components[2] = CLProperties.UNSPECIFIED_PROP_TYPE
        prop_def = PropDef(*prop_def_components)
        if key in params:
            prop_tree = params[key]
        else:
            prop_tree = CLProperties()
            params[key] = prop_tree
        prop_tree.add_propdef(prop_def)
        self.value = value
        self.prop_def = prop_def

    def validate(self, params):
        """Validate the elements of our prop_def."""
        prop_def = self.prop_def
        # Validate PG and property names
        pg_name = prop_def.pg_name
        prop_name = prop_def.prop_name
        if len(pg_name) == 0:
            raise CLError(
                gettext(
                    "Property group name is empty in \"{0}\".").format(
                    self.value))
        if len(prop_name) == 0:
            raise CLError(
                gettext(
                    "Property name is empty in \"{0}\"").format(
                    self.value))
        if not check_name(self.comp_name_re, pg_name):
            raise CLError(
                gettext(
                    "\"{0}\" is an illegal property group name "
                    "in \"{1}\".").format(
                    pg_name, self.value))
        if not check_name(self.comp_name_re, prop_name):
            raise CLError(
                gettext(
                    "\"{0}\" is an illegal property name in \"{1}\".").format(
                    prop_name, self.value))
        prop_type = prop_def.prop_type

        # User is not required to specify the property type.  If we're
        # producing a manifest, we'll use the default property type.  If
        # we're generating a profile, we don't have the information that we
        # need for validation of property type or value.
        if prop_type == CLProperties.UNSPECIFIED_PROP_TYPE:
            if is_manifest():
                prop_type = CLProperties.DEFAULT_PROP_TYPE
            else:
                return

        if not ValueType.valid_type(prop_type):
            raise CLError(
                gettext(
                    "\"{0}\" is an invalid property type in \"{1}\".").format(
                    prop_type, self.value))
        try:
            ValueType.valid_value(prop_type, prop_def.value)
        except PropValueError as err:
            raise CLError("{0}: \"{1}\".".format(
                    err.args[0], self.value))


class InstanceProperty(Property):
    _count = 0
    _max = -1  # No maximum
    _name = NM_INST_PROP
    _key = NM_INST_PROP


class ServiceProperty(Property):
    _count = 0
    _max = -1  # No maximum
    _name = NM_SVC_PROP
    _key = NM_SVC_PROP


class Day(NVPair):
    _count = 0
    _name = NM_DAY
    _key = NM_DAY
    _value_mnemonic = "day"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        # First make sure the value is sane
        val = self.value.title()

        try:
            idx = int(val)
            if idx < -7 or idx > 7 or idx == 0:
                raise CLError(
                    gettext(
                        "\"{0}\" must be between 1 and 7 or -7 and -1".format(
                            self._name)))
        except ValueError:
            # Not an integer, so it might be a name
            if val not in calendar.day_name and val not in calendar.day_abbr:
                raise CLError(
                    gettext(
                        "\"{0}\" must be a valid weekday".format(
                            self._name)))


class DayOfMonth(NVPair):
    _count = 0
    _name = NM_DAY_OF_MONTH
    _key = NM_DAY_OF_MONTH
    _value_mnemonic = "day_of_month"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            idx = int(self.value)

            if idx > 31 or idx < -31 or idx == 0:
                raise CLError(
                    gettext(
                        "\"{0}\" must be between 1 and 31 or -1 and -31".
                        format(self.value)))

        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be between 1 and 31 or -1 and -31".format(
                        self.value)))


class Frequency(NVPair):
    _count = 0
    _name = NM_FREQUENCY
    _key = NM_FREQUENCY
    _value_mnemonic = "frequency"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            val = int(self.value)

            if val < 1:
                raise CLError(
                    gettext(
                        "\"{0}\" must be a positive integer".format(
                            self._name)))
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be a positive integer".format(
                        self._name)))

        # and finally, make sure a reference point exists

        graph = {NM_YEAR: [],
                 NM_MONTH: [NM_YEAR],
                 NM_WEEK_OF_YEAR: [NM_YEAR],
                 NM_WEEKDAY_OF_MONTH: [NM_MONTH],
                 NM_DAY: [NM_WEEK_OF_YEAR, NM_WEEKDAY_OF_MONTH],
                 NM_DAY_OF_MONTH: [NM_MONTH],
                 NM_HOUR: [NM_DAY, NM_DAY_OF_MONTH],
                 NM_MINUTE: [NM_HOUR]}

        entries = {"year": [NM_YEAR],
                   "month": [NM_MONTH],
                   "week": [NM_WEEK_OF_YEAR, NM_DAY, NM_DAY_OF_MONTH],
                   "day": [NM_DAY, NM_DAY_OF_MONTH],
                   "hour": [NM_HOUR],
                   "minute": [NM_MINUTE]}

        nodes = None
        try:
            nodes = entries[params[NM_INTERVAL]]
        except:
            # If the interval does not exist or is
            # invalid, we'll catch that here.  Since
            # other parts of the code enforce the
            # requirement for a valid interval, we
            # can ignore it.  Without a valid interval,
            # we can't tell if a valid reference is
            # defined, anyhow.
            return

        while True:
            found_node = None

            for node in nodes:
                if node in params:
                    found_node = node
                    break

            if found_node is None:
                raise CLError(
                    gettext(
                        "For frequencies other than 1, a full reference" +
                        " point must be defined"))

            nodes = graph[node]

            # We've reached NM_YEAR, the top of the graph
            if len(nodes) == 0:
                break


class Hour(NVPair):
    _count = 0
    _name = NM_HOUR
    _key = NM_HOUR
    _value_mnemonic = "hour"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            val = int(self.value)

            if val > 23 or val < -24:
                raise CLError(
                    gettext(
                        "\"{0}\" must be an integer between -24 and 23".format(
                            self._name, NM_HOUR)))
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be an integer between -24 and 23".format(
                        self._name, NM_HOUR)))


class Interval(NVPair):
    _count = 0
    _name = NM_INTERVAL
    _key = NM_INTERVAL
    _value_mnemonic = "interval"
    _description = gettext("")

    _valid_intervals = ("year",
                        "month",
                        "week",
                        "day",
                        "hour",
                        "minute")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        if self.value not in self._valid_intervals:
            raise CLError(
                gettext(
                    "\"{0}\" must be one of: {1}".format(
                        self._name, ", ".join(self._valid_intervals))))

        # If we've gotten this far, we must be valid
        # We can't use a graph like we did with missing
        # reference points in the Frequency validator
        # because we are checking for continuity and not
        # existence.  Using the graph approach, one undefined
        # constraint means you don't know which edges to
        # traverse.
        entries = {"year": [[NM_MONTH, NM_WEEKDAY_OF_MONTH, NM_DAY, NM_HOUR,
                             NM_MINUTE],
                            [NM_MONTH, NM_DAY_OF_MONTH, NM_DAY, NM_HOUR,
                             NM_MINUTE],
                            [NM_WEEK_OF_YEAR, NM_DAY, NM_HOUR, NM_MINUTE]],
                   "month": [[NM_WEEKDAY_OF_MONTH, NM_DAY, NM_HOUR, NM_MINUTE],
                             [NM_DAY_OF_MONTH, NM_HOUR, NM_MINUTE]],
                   "week": [[NM_DAY, NM_HOUR, NM_MINUTE],
                            [NM_MONTH, NM_DAY_OF_MONTH, NM_HOUR, NM_MINUTE],
                            [NM_MONTH, NM_WEEKDAY_OF_MONTH, NM_DAY, NM_HOUR,
                             NM_MINUTE]],
                   "day": [[NM_HOUR, NM_MINUTE]],
                   "hour": [[NM_MINUTE]],
                   "minute": [[]]}

        paths = entries[self.value]

        last_node = None
        for path in paths:

            contiguous = True
            loop_last = None

            for node in path:
                if node not in params:
                    contiguous = False
                elif not contiguous:
                    loop_last = node
                    break

            # if we found a contiguous path, we're done!
            if contiguous:
                break

        if last_node is not None:
            raise CLError(
                gettext(
                    "Schedule is missing at least one constraint" +
                    " above \"{0}\"".format(last_node)))


class Minute(NVPair):
    _count = 0
    _name = NM_MINUTE
    _key = NM_MINUTE
    _value_mnemonic = "minute"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            val = int(self.value)

            if val > 59 or val < -60:
                raise CLError(
                    gettext(
                        "\"{0}\" must be an integer between -60 and 59".format(
                            self._name, NM_MINUTE)))
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be an integer between -60 and 59".format(
                        self._name, NM_MINUTE)))


class Month(NVPair):
    _count = 0
    _name = NM_MONTH
    _key = NM_MONTH
    _value_mnemonic = "month"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))
        val = self.value.title()
        try:
            idx = int(val)

            if idx > 12 or idx < -12 or idx == 0:
                raise CLError(
                    gettext(
                        "\"{0}\" must be between -12 and -1, 1 and 12, or"
                        " be a valid name or abbreviation of a month".format(
                            self.value)))
        except:
            if val not in calendar.month_name and \
               val not in calendar.month_abbr:
                raise CLError(
                    gettext(
                        "\"{0}\" is not the name of a month".format(
                            self.value)))


class Timezone(NVPair):
    _count = 0
    _name = NM_TIMEZONE
    _key = NM_TIMEZONE
    _value_mnemonic = "timezone"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))


class WeekOfYear(NVPair):
    _count = 0
    _name = NM_WEEK_OF_YEAR
    _key = NM_WEEK_OF_YEAR
    _value_mnemonic = "week_of_year"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            idx = int(self.value)

            if idx > 53 or idx < -53 or idx == 0:
                raise CLError(
                    gettext(
                        "\"{0}\" must be between 1 and 53 or -1 and -53".
                        format(self._name)))
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be between 1 and 53 or -1 and -53".format(
                        self._name)))


class WeekdayOfMonth(NVPair):
    _count = 0
    _name = NM_WEEKDAY_OF_MONTH
    _key = NM_WEEKDAY_OF_MONTH
    _value_mnemonic = "weekday_of_month"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            idx = int(self.value)

            if idx < -5 or idx > 5 or idx == 0:
                raise CLError(
                    gettext(
                        "\"{0}\" must be between 1 and 5 or -1 and -5".format(
                            self._name)))
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be between 1 and 5 or -1 and -5".format(
                        self._name)))


class Year(NVPair):
    _count = 0
    _name = NM_YEAR
    _key = NM_YEAR
    _value_mnemonic = "year"
    _description = gettext("")

    def validate(self, params):
        """Don't specify with rc-script or without start-method"""
        if NM_RC_SCRIPT in params:
            raise CLError(
                gettext(
                    "\"{0}\" should not be specified with \"{1}\"".format(
                        self._name, NM_RC_SCRIPT)))

        if NM_START not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_START)))

        if NM_INTERVAL not in params:
            raise CLError(
                gettext(
                    "\"{0}\" must be specified with \"{1}\"".format(
                        self._name, NM_INTERVAL)))

        try:
            int(self.value)
        except ValueError:
            raise CLError(
                gettext(
                    "\"{0}\" must be an integer".format(self._name)))


def monitor_state(params, parser):
    """Wait for service to move past offline state."""
    # We need to wait for the service to transition out of it's offline
    # state.  Research using svcprop -w shows that there are some nasty
    # race conditions.  First we can start svcprop -w before the manifest
    # import completes.  Second, we can start svcprop -w after the final
    # state is achieved, and svcprop -w will never exit.  We'll resort to
    # the more straight forward polling.
    enabled = params.get(NM_ENABLED, "true")
    if enabled == "true":
        desired_state = "online"
    else:
        desired_state = "disabled"
    service_name = params.get(NM_SVC_NAME)
    instance_name = params.get(NM_INST_NAME, "default")
    fmri = "svc:/{0}:{1}".format(service_name, instance_name)
    safe_so(
        gettext(
            "Waiting for {0} to reach {1} state.\n"
            "It is safe to interrupt.").format(
            service_name, desired_state))
    timeout = int(SmfNode.method_timeout)
    elapsed = 0
    duration = 1
    cmd = ["/usr/bin/svcprop", "-p", "restarter/state", fmri]
    while elapsed < timeout:
        try:
            svcprop = subprocess.Popen(
                cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            result = svcprop.communicate()
        except OSError as e:
            parser.error(
                gettext(
                    "Unable to run svcprop.  {0}.").format(
                    os.strerror(e.errno)))
        if svcprop.returncode != 0:
            if ("doesn't match any entities" not in result[1] and
               "Couldn't find property group" not in results[1]):
                parser.error(
                    gettext(
                        "Problem in running svcprop.  {0}").format(
                        result[1]))
        else:
            if (result[0] != "") and (not result[0].startswith("offline")):
                return
        time.sleep(duration)
        elapsed += duration
        if duration < 4:
            duration += 1
    state = result[0].rstrip(string.whitespace)
    safe_se(
        gettext(
            "{0}: Warning: service, {1}, is {2} after {3} seconds.\n").format(
            SmfNode.pname, fmri, state, elapsed))


def manifest_import(params, parser):
    """Restart manifest-import to import/apply the bundle."""
    cmd = ["/usr/sbin/svcadm", "restart", "manifest-import"]
    try:
        svcadm = subprocess.Popen(
            cmd,  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        result = svcadm.communicate()
    except OSError as e:
        parser.error(
            gettext(
                "Unable to run svcadm to restart manifest-import.  "
                "{0}.").format(os.strerror(e.errno)))
    if svcadm.returncode != 0:
        safe_se(
            gettext(
                "{0}: svcadm is unable to restart manifest-import\n").format(
                SmfNode.pname))
        safe_se(result[1])
        sys.exit(1)
    if not is_manifest():
        # Can't monitor for state transitions with profiles, because there
        # is no useful way for us to determine what to monitor.
        return
    monitor_state(params, parser)


def process_subcommands(bundle, args, name_map):
    """Process the subcommands.

    Parameters:
        bundle - main object for our bundle
        args - list of command line arguments following the options
        name_map - a dictionary mapping command line names to their
        implementing classes.

    Currently, the only subcommand that we support is the help command.  If
    help is used alone, a brief summary of the command is presented.  args
    beyond help are names used in the -s option.  If names are specified,
    the help message focuses on them and explains the legal values.
    """
    parser = bundle.parser
    if len(args) == 0:
        return
    if args[0] != "help":
        raise CLError(
            gettext("{0} is an invalid subcommand.").format(args[0]))
    if len(args) == 1:
        # Just a simple help message.
        name_list = list(name_map.keys())
        name_list.sort()
        names = ", ".join(name_list)
        safe_so(parser.get_usage())
        safe_so(
            gettext(
                "-i is used to install the generated manifest or bundle."))
        safe_so(
            gettext(
                "-o specifies where to write the generated manifest or "
                "bundle.\n"))
        msg = gettext("Legal names for -s are: ") + names + "."
        safe_so(textwrap.fill(msg, 75))
        safe_so(
            gettext(
                "\nFor more information on a name use \"{0} "
                "help name\".").format(bundle.pname))
    else:
        for name in args[1:]:
            cls = name_map.get(name)
            if cls is None:
                raise CLError(
                    gettext(
                        "{0} is an invalid name for -s").format(name))
            cls.describe_yourself()
    sys.exit(0)


def process_command_line(bundle, params):
    """Process command line parameters and add them to params dictionary.

    The created parser will be saved in SmfNode.parser, so that it can be
    used to generate error messages.
    """
    pairs_seen = []

    # Map the command line names to their implementing classes.
    name_map = {
        NM_BUNDLE_TYPE: BundleType,
        NM_DAY: Day,
        NM_DAY_OF_MONTH: DayOfMonth,
        NM_DURATION: Duration,
        NM_ENABLED: Enabled,
        NM_FREQUENCY: Frequency,
        NM_HOUR: Hour,
        NM_INST_NAME: InstanceName,
        NM_INST_PROP: InstanceProperty,
        NM_INTERVAL: Interval,
        NM_MINUTE: Minute,
        NM_MODEL: Model,
        NM_MONTH: Month,
        NM_PERIOD: Period,
        NM_RC_SCRIPT: RcScript,
        NM_REFRESH: RefreshMethod,
        NM_START: StartMethod,
        NM_STOP: StopMethod,
        NM_SVC_NAME: ServiceName,
        NM_SVC_PROP: ServiceProperty,
        NM_TIMEZONE: Timezone,
        NM_WEEK_OF_YEAR: WeekOfYear,
        NM_WEEKDAY_OF_MONTH: WeekdayOfMonth,
        NM_YEAR: Year
    }

    parser = OptionParser(
        usage="%prog [-i | -o output_file] -s name=value ... [help [name]]",
        prog=bundle.pname)
    SmfNode.parser = parser
    if len(sys.argv) <= 1:
        # No arguments on the command line.  Just generate the help command
        # and exit.
        process_subcommands(bundle, ["help"], name_map)
        sys.exit()
    parser.disable_interspersed_args()
    parser.add_option("-s", type="string", dest="nvpairs", action="append")
    parser.add_option(
        "-o", type="string", dest="output_file", action="store", default=None)
    parser.add_option(
        "-i", action="store_true", dest="install", default=None)
    (options, args) = parser.parse_args()
    optlist = options.nvpairs
    if len(args) > 0:
        try:
            process_subcommands(bundle, args, name_map)
        except CLError as e:
            parser.error(e.args[0])
    if optlist is None:
        # No NVPairs were spepecified.
        parser.error(gettext("Use -s to specify bundle parameters."))
    output = options.output_file
    # Make sure that file name ends in .xml
    if output is not None:
        i = output.rfind(".xml")
        if (len(output) - i) != len(".xml"):
            parser.error(
                gettext(
                    'Output file name must end in ".xml" for SMF to '
                    'process it.'))
    params[NM_OUTPUT_FILE] = output
    # See if -i was specified.  It is illegal to specify both -i and -o
    # together.
    install = options.install
    if (install is not None) and (output is not None):
        parser.error(
            gettext("-i and -o are mutually exclusive"))
    if install is not None:
        SmfNode.installing = True

    # From this point on, we don't want to print the usage message for
    # errors.  We just want to have use the standard error message.
    parser.set_usage(SUPPRESS_USAGE)
    for opt in optlist:
        nvpair = opt.partition("=")
        if nvpair[1] != "=":
            parser.error(
                gettext(
                    "\"{0}\" contains no equal sign.  Parameters "
                    "should have the form of name=value.").format(opt))
        value = nvpair[2]
        if value == "":
            parser.error(
                gettext("\"{0}\" contains no value.  Parameters "
                        "should have the form of name=value.").format(opt))
        name = nvpair[0]
        cls = name_map.get(name)
        if cls is None:
            parser.error(gettext("\"{0}\" is an invalid name.").format(name))
        try:
            inst = cls(value, params)
        except CLError as err:
            parser.error(err.args[0])
        pairs_seen.append(inst)

    # Now that we've processed all of the NVPairs, we can determine the
    # output file for use with -i.  The output file is derived from the
    # service name
    if install is not None:
        service_name = params.get(NM_SVC_NAME)
        if service_name is None:
            parser.error(
                gettext(
                    "Specify a service name using -s {0}.").format(
                    NM_SVC_NAME))
        file = os.path.basename(service_name)
        if is_manifest():
            file = "/lib/svc/manifest/site/" + file + ".xml"
        else:
            file = "/etc/svc/profile/site/" + file + ".xml"
        params[NM_OUTPUT_FILE] = file

    # Go through the list of options seen and verify them as needed
    for obj in pairs_seen:
        try:
            obj.validate(params)
        except CLError as err:
            parser.error(err.args[0])


def start_bundle():
    """Initialize the DOM."""
    SmfNode.implementation = getDOMImplementation()
    doc_type = SmfDocumentType()
    doc = SmfDocument("service_bundle", doc_type)
    doc = doc.node
    SmfNode.document = doc
    top = doc.documentElement
    bundle = SmfBundle(doc, top)
    pname = os.path.basename(sys.argv[0])
    SmfNode.pname = pname
    comment = SmfComment(
        doc,
        gettext("Manifest created by ") + pname +
            time.strftime(
            " (%Y-%b-%d %H:%M:%S"
            "%z)"), top)
    return bundle


def validate_and_write(params, parser, channel, filename):
    """Validate the temp file and write to specified location.

    This function is called when the manifest has been written to a
    temporary file.  channel is the open temporary file, and filename is
    the name of the temporary file.  We use params to determine where the
    final manifest should be written, and parser for printing error
    messages.
    """
    # Make sure that data has been written out.
    channel.flush()
    # Use svccfg to validate the manifest
    cmd = ["/usr/sbin/svccfg", "validate"]
    cmd.append(filename)
    try:
        svccfg = subprocess.Popen(
            cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        result = svccfg.communicate()
    except OSError as e:
        parser.error(
            gettext(
                "Problem while using svccfg to validate the manifest.  "
                "{0}.").format(os.strerror(e.errno)))
    if svccfg.returncode != 0:
        safe_se(
            gettext(
                "{0}: svccfg validate found the following errors in the "
                "manifest which is saved at {1}.\n").format(
                SmfNode.pname, filename))
        safe_se(result[1])
        sys.exit(1)
    # Manifest validated successfully.  Write it to the appropriate
    # location.
    outfile = params.get(NM_OUTPUT_FILE)
    if outfile is not None:
        try:
            output = open(outfile, "w")
        except IOError as e:
            parser.error(
                gettext('Unable to open "{0}". {1}').format(
                    outfile, os.strerror(e.errno)))
    else:
        output = sys.stdout
    try:
        channel.seek(0)
        output.writelines(channel.readlines())
    except IOError as e:
        parser.error(
            gettext(
                "I/O error while copying the manifest.  {0}").format(
                os.strerror(e.errno)))
    if outfile is not None:
        output.close()
    # Remove the temporary file.
    try:
        channel.close()
        os.remove(filename)
    except OSError:
        # Don't bother to complain if we can't remove a temp file
        return


def main():
    output = sys.stdout
    params = {}
    bundle = start_bundle()
    process_command_line(bundle, params)
    try:
        bundle.propagate(params)
    except CLError as err:
        bundle.parser.error(err.args[0])
    if is_manifest():
        # Initially write the manifest to a temp file, so that we can run
        # svccfg validate on it.  os.tempnam() gives us a runtime warning
        # saying that we shouldn't use tempname because of symlink
        # vulnerabilities.  Unfortunately, the recommended alternative,
        # tmpfile, won't work, because we need a real file name to pass to
        # svccfg.  Thus, we catch the warning.
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", RuntimeWarning)
            filename = os.tempnam("/tmp", "svcbundle")
    else:
        filename = params.get(NM_OUTPUT_FILE)
    if filename is None:
        output = sys.stdout
    else:
        try:
            output = open(filename, "w+")
        except IOError as e:
            bundle.parser.error(
                gettext('Unable to open "{0}". {1}').format(
                    filename, os.strerror(e.errno)))
    try:
        bundle.document.smf_node.writexml(output, "", "    ", "\n")
    except IOError as e:
        if e.errno != errno.EPIPE:
            bundle.parser.error(
                gettext("Problem in writing output.  {0}").format(
                    os.strerror(e.errno)))
            sys.exit(1)
    if is_manifest():
        validate_and_write(params, bundle.parser, output, filename)
    else:
        if filename is not None:
            output.close()
    if SmfNode.installing:
        manifest_import(params, bundle.parser)
    sys.exit(0)

try:
    main()
except KeyboardInterrupt:
    sys.exit(1)