Hallo, dies ist ein Test.
PWD: /www/data-lst1/unixsoft/unixsoft/kaempfer/.public_html
Running in File Mode
Relative path: ././../../../../../../bin/./svcbundle
Real path: /usr/bin/svcbundle
Zurück
#!/usr/bin/python3.7 -Es import solaris.no_site_packages # # Copyright (c) 2012, 2020, Oracle and/or its affiliates. # """ svcbundle is a program to generate SMF manifests that accepts multiple occurrences of "-s name=value" where 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(8)). day_of_month The day_of_month parameter of a scheduled service (see svc.periodicd(8)). 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(8)). hour The hour parameter of a scheduled service (see svc.periodicd(8)). interval The interval parameter of a scheduled service (see svc.periodicd(8)). 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(8)). model Sets the service model. This is the value of the startd/duration property. Refer to svc.startd(8). 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(8)). 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(8)). 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(8)) 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(7) 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(8)). weekday_of_month The weekday_of_month parameter of a scheduled service (see svc.periodicd(8)). week_of_year The week_of_year parameter of a scheduled service (see svc.periodicd(8)). year The year parameter of a scheduled service (see svc.periodicd(8)). 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 import gettext import locale from optparse import OptionParser, SUPPRESS_USAGE import os import re import socket import string import subprocess import sys import textwrap import time import urllib.parse import warnings from xml.dom.minidom import getDOMImplementation from xml.dom.minidom import Node from xml.sax.saxutils import quoteattr import tempfile except KeyboardInterrupt: # Avoid obnoxious Python traceback on interrupt. import sys sys.exit(1) _ = gettext.translation("svcbundle", "/usr/lib/locale", fallback=True).gettext # 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_DELAY = "delay" 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_JITTER = "jitter" NM_MINUTE = "minute" NM_MODEL = "model" NM_MONTH = "month" NM_PERIOD = "period" NM_RC_SCRIPT = "rc-script" NM_REFRESH = "refresh-method" NM_TIMEOUT_REFRESH = "refresh-timeout" NM_START = "start-method" NM_TIMEOUT_START = "start-timeout" NM_STOP = "stop-method" NM_TIMEOUT_STOP = "stop-timeout" NM_SVC_NAME = "service-name" NM_SVC_PROP = "service-property" NM_TIMEOUT = "timeout" NM_TIMEZONE = "timezone" NM_WEEK_OF_YEAR = "week_of_year" NM_WEEKDAY_OF_MONTH = "weekday_of_month" NM_YEAR = "year" NM_METHOD_USER = "user" NM_METHOD_GROUP = "group" NM_METHOD_PRIVILEGES = "privileges" # 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(_("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( _("\"{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(_("Invalid network type")) comp = addr.split("/", 1) if (len(comp) > 1) and (len(comp[1]) > 0): # We have a net mask designator. try: bits = int(comp[1]) except ValueError: raise PropValueError(_("Invalid net mask specifier")) if addr_type == socket.AF_INET: maxbits = 32 else: maxbits = 128 if (bits < 0) or (bits > maxbits): raise PropValueError(_("Invalid net mask specifier")) if len(comp[0]) == 0: raise PropValueError(_("Network address has 0 length")) try: socket.inet_pton(addr_type, comp[0]) except socket.error: raise PropValueError(_("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(_("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 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 is 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_or_prop = None if len(fmri) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH: raise PropValueError(_("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) parts = [] # If fmri is not empty, it now starts with the PG name. if len(fmri) > 0: parts = fmri.split(ValueType.SCF_FMRI_PROPERTY_PREFIX) # Now do the validations: if (scope is not None) and (len(scope) > 0): if not check_name(NVPair.comp_ident_re, scope): raise PropValueError(_("FMRI contains invalid scope")) if (svc is None) or (len(svc) == 0): raise PropValueError(_("Service name is empty in FMRI")) else: if not check_name(NVPair.comp_service_re, svc): raise PropValueError(_("Illegal service name in FMRI")) if inst is not None: if len(inst) == 0: raise PropValueError(_("Instance name is empty in FMRI")) if not check_name(NVPair.comp_inst_re, inst): raise PropValueError(_("Illegal instance name in FMRI")) for name in parts: if len(name) == 0: raise PropValueError( _("Property group or property name is empty in FMRI")) _name = urllib.parse.unquote(name) if not check_name(NVPair.comp_pg_prop_name_re, _name): raise PropValueError( _("Illegal property group or 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(_("FMRI contains invalid scope")) else: # Path must be absolute if not fmri.startswith("/"): raise PropValueError(_("File FMRI paths must be absolute")) # The following validation functions are modeled on valid_encoded_value() # in scf_type.c. They are used to validate property values. def validate_astring(value): """Validate SMF ascii string (astring). Since Python 3.x is UTF-8 by default we have to do an encode to ascii and check for encoding errors """ if len(value) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH: raise PropValueError(_("Value is too long")) try: value.encode('ascii') except UnicodeEncodeError: raise PropValueError(_("astring is not valid ASCII")) def validate_boolean(value): if value != "true" and value != "false": raise PropValueError(_("Value must be true or false")) def validate_count(value): try: i = int(value) except ValueError: raise PropValueError(_("Value is not a number")) if i < 0: raise PropValueError(_("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 (PropValueError, SvcbundleError): exceptions += 1 if exceptions >= 2: raise PropValueError( _("Value is neither a valid host name nor IP address")) def validate_hostname(host): check_hostname(host) def validate_integer(value): try: i = int(value) except ValueError: raise PropValueError(_("Value is not a number")) def validate_opaque(value): """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(value) if strlen % 2 != 0: raise PropValueError( _("Opaque string must contain an even number of characters")) if strlen >= 2 * ValueType.SCF_LIMIT_MAX_VALUE_LENGTH: raise PropValueError(_("Opaque string is too long.")) for c in value: if c not in string.hexdigits: raise PropValueError( _("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(_("Time value is empty")) comp = time.split(".") if len(comp) >= 1: if len(comp[0]) <= 0: raise PropValueError(_("No seconds specified")) try: sec = int(comp[0]) except ValueError: raise PropValueError(_("Time contains non-numeric values")) if len(comp) == 2: if len(comp[1]) > 9: raise PropValueError(_("Fractional part of time must contain " "no more than 9 digits")) try: nanosec = int(comp[1]) except ValueError: raise PropValueError(_("Time contains non-numeric values")) if len(comp) > 2: raise PropValueError(_("Time is not of the form seconds.fraction")) def validate_uri(value): r"""Verify that value 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(value) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH: raise PropValueError(_("URI is too long")) if len(value) == 0: raise PropValueError(_("URI has zero length")) mo = NVPair.comp_uri_re.match(value) if mo is None: raise PropValueError(_("URI is invalid")) if mo.group() != value: # Didn't match the whole string. raise PropValueError(_("URI is invalid")) if len(mo.group(5)) == 0: raise PropValueError(_("URI does not contain a path component")) def validate_ustring(value): """Validate UTF-8 strings. Since Python 3.x defaults to UTF-8 all we need to do is check the length. Compare with validate_astring() above. """ if len(value) > ValueType.SCF_LIMIT_MAX_VALUE_LENGTH: raise PropValueError(_("ustring is too long")) """ 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, kind): self.value = value self.kind = kind 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, kind): for branch in self.branches: if branch.value == value and branch.kind == kind: return branch branch = Tree(value, kind) 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, 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): frag = pd.pg_name pg_names = frag.split(ValueType.SCF_FMRI_NESTED_PROPERTYGRP_PREFIX) pg = self.root for name in pg_names: pg_name = urllib.parse.unquote(name) if not check_name(NVPair.comp_pg_prop_name_re, pg_name): safe_se(_("{0}: error: \"{1}\" is an illegal property " "group name in \"{2}\"\n") .format(SmfNode.pname, pg_name, frag)) sys.exit(1) child = pg.find_or_add(pg_name, "property_group") pg = child prop_name = urllib.parse.unquote(pd.prop_name) if not check_name(NVPair.comp_pg_prop_name_re, pg_name): safe_se(_("{0}: error: \"{1}\" is an " "illegal property name\n").format( SmfNode.pname, prop_name)) sys.exit(1) prop = pg.find_or_add(prop_name, "property") prop_type = prop.find_or_add(pd.prop_type, "property_type") prop_type.find_or_add(pd.value, "property_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 overridden 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 writexml(self, writer, indent="", addindent="", newl=""): """Write the XML for this node and its descandents. Parameters: writer - Output channel indent - String preceding 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 # pylint complains that node does not not have an attributes # member. This is not true. Section 19.8.2.2 of the Python 2.7 # standard library documentation says DOM elements have an # attribute member that is a NamedNodeMap. Above, we assert that # this is an element node. # pylint: disable=E1101 attributes = node.attributes # pylint: enable=E1101 if attributes is not None: for i in range(attributes.length): att = attributes.item(i) # Convert special XML characters to character entities # and Do The Right Thing with nested quotes node_value = quoteattr(att.nodeValue) 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 preceding 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): dtd = "/usr/share/lib/xml/dtd/service_bundle.dtd.1" doc_type = self.implementation.createDocumentType( "service_bundle", "", dtd) 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(_("No {0} specified.").format(NM_SVC_NAME)) # If there is a leading "/" then remove it. name = re.sub(r'^/', '', 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(_( "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") timeout_s = params.get(NM_TIMEOUT) em.setAttribute("timeout_seconds", timeout_s if timeout_s is not None else 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 delay_s = params.get(NM_DELAY) jitter_s = params.get(NM_JITTER) em.setAttribute("delay", delay_s if delay_s is not None else "0") em.setAttribute("jitter", jitter_s if jitter_s is not None else "0") em.setAttribute("recover", "false") em.setAttribute("persistent", "false") timeout_s = params.get(NM_TIMEOUT) em.setAttribute("timeout_seconds", timeout_s if timeout_s is not None else "0") start = params.get(NM_START) if (start is None): raise CLError(_("{0} is required").format(NM_START)) em.setAttribute("exec", start) period = params.get(NM_PERIOD) if (period is None): raise CLError(_("{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(_("{0} is required").format(NM_START)) em.setAttribute("exec", start) interval = params.get(NM_INTERVAL) if (interval is None): raise CLError(_("{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(_("{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) timeout_s = params.get(NM_TIMEOUT) em.setAttribute("timeout_seconds", timeout_s if timeout_s is not None else "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( _( "The exec attribute below can be changed to a " "command that SMF should execute when the service is " "refreshed. Use svcbundle -s refresh-method to set " "the attribute.")) em.setAttribute("exec", ":true") refreshTimeout = params.get(NM_TIMEOUT_REFRESH) if (refreshTimeout is not None): em.setAttribute("timeout_seconds", refreshTimeout) 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(_("{0} is required").format(NM_START)) em.setAttribute("exec", start) startTimeout = params.get(NM_TIMEOUT_START) if (startTimeout is not None): em.setAttribute("timeout_seconds", startTimeout) 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( _( "The exec attribute below can be changed to a " "command that SMF should execute to stop the " "service. Use svcbundle -s stop-method to set " "the attribute.")) duration = params.get(NM_DURATION, "transient") if duration == "transient": # Transient services cannot be :killed. stop = ":true" else: stop = ":kill" em.setAttribute("exec", stop) stopTimeout = params.get(NM_TIMEOUT_STOP) if (stopTimeout is not None): em.setAttribute("timeout_seconds", stopTimeout) class SmfText(SmfNode): """This class encapsulates a DOM Text node""" def __init__(self, parent, content): txt = self.document.createTextNode(content) txt.smf_node = self self.node = txt parent.appendChild(txt) def writexml(self, writer, indent="", addindent="", newl=""): """Write the XML for this node. Merely write node data as text. Parameters: writer - Output channel indent - String preceding the output of this node addindent - Additional indentation for descendant nodes newl - String to be output at the end of a line """ node = self.node assert node.nodeType == Node.TEXT_NODE our_indent = indent + addindent self.wrapper.initial_indent = our_indent self.wrapper.subsequent_indent = our_indent writer.write(self.wrapper.fill(node.data)) writer.write(newl) class SmfLoctext(SmfNode): """Implements the loctext element of the DTD.""" def __init__(self, parent): SmfNode.__init__(self, parent, "loctext") def propagate(self, comment, localtext): """comment if not None will be presented as an introductory comment to this loctext. localtext is the localized text. """ if comment is not None: self.intro_comment(comment) node = self.node node.setAttribute("xml:lang", "C") txt = SmfText(node, localtext) 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( _("Unable to run svcs. {0}.").format( os.strerror(e.errno))) if inst_check.returncode != 0: SmfNode.parser.error( _("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: 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 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 for prop in tree: if prop.kind != "property": continue prop_name = prop.value # The branches of prop_tree contain property types. types = prop.branches for t in types: prop_type = t.value # The branches of the type tree contain the values. values = t.branches if len(values) == 1: # Only 1 value. Create an propval element. prop_element = SmfPropVal(pg) prop_element.propagate( PropDef(pg_name, prop_name, prop_type, values[0].value)) else: # For multiple values create a property element. prop_element = SmfProperty(pg) prop_element.propagate( PropDef(pg_name, prop_name, prop_type, values)) # Create the nested property groups for children in tree: if children.kind != "property_group": continue pg_name = children.value npg = SmfPropertyGroup(pg) npg.propagate(children) 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, _("A duration property group is not needed.")) 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") # 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_NESTED_PROPERTYGRP_PREFIX = "/" 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. & must be first in the list to avoid converting the # ampersand in other entities. out_map = [ ("&", "&"), ('"', """), ("<", "<"), (">", ">")] _value_type_map = { "astring": Implementors( SmfAstringList, validate_astring), "boolean": Implementors(SmfBooleanList, validate_boolean), "count": Implementors(SmfCountList, validate_count), "fmri": Implementors(SmfFmriList, validate_fmri), "host": Implementors(SmfHostList, validate_host), "hostname": Implementors( SmfHostnameList, validate_hostname), "integer": Implementors(SmfIntegerList, validate_integer), "net_address": Implementors( SmfNetAddressList, validate_ipaddr), "net_address_v4": Implementors( SmfNetAddressV4List, validate_v4addr), "net_address_v6": Implementors( SmfNetAddressV6List, validate_v6addr), "opaque": Implementors( SmfOpaqueList, validate_opaque), "time": Implementors(SmfTimeList, validate_time), "uri": Implementors( SmfUriList, validate_uri), "ustring": Implementors( SmfUstringList, validate_ustring) } @classmethod def valid_type(cls, prop_type): """Return True if prop_type is a valid property type.""" return prop_type in cls._value_type_map @classmethod 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 @classmethod 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( _("\"{0}\" is not a valid property type.").format(prop_type)) func = implementor.validator if func is None: return True return func(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( _("\"{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) # Classes for generating the templates section: class SmfMethodContext(SmfNode): """Add method_context to allow specifying user/group etc.""" def __init__(self, parent): SmfNode.__init__(self, parent, "method_context") def propagate(self, params): """Generate method_credential. Only emit a method_credential if at least one of user/group/privileges are available in params.""" context = self.node user = params.get(NM_METHOD_USER) group = params.get(NM_METHOD_GROUP) privileges = params.get(NM_METHOD_PRIVILEGES) """If there are no method_credentials attributes remove our node""" if user is None and group is None and privileges is None: self.parent.removeChild(context) else: cred = SmfMethodCred(context) cred.propagate(params) class SmfMethodCred(SmfNode): """Add method_context to allow specifying user/group etc.""" def __init__(self, parent): SmfNode.__init__(self, parent, "method_credential") def propagate(self, params): """Generate method_credential attributes: user/group etc.""" cred = self.node user = params.get(NM_METHOD_USER) if user is not None: cred.setAttribute("user", user) group = params.get(NM_METHOD_GROUP) if group is not None: cred.setAttribute("group", group) privileges = params.get(NM_METHOD_PRIVILEGES) if privileges is not None: cred.setAttribute("privileges", privileges) 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(_("Replace loctext content " "with a short name for the service."), params.get(NM_SVC_NAME, _("No service name"))) 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( _("Replace loctext content with a brief description of the " "service"), _("The {0} service.").format( params.get(NM_SVC_NAME, _("Nameless")))) 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(_("No {0} specified.").format(NM_SVC_NAME)) # If there is a leading "/" then remove it. name = re.sub(r'^/', '', 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), SmfMethodContext(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( _("\"{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 overridden 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 ident_re - identifiers inst_re - instance names service_re - service names uri_re - URIs pg_prop_name_re - property group and property names comp_* - the compiled version of the named regular expression """ _count = 0 _description = None _legal_values = [] _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) inst_re = r"([A-Za-z0-9][_A-Za-z0-9.-]*,)?[A-Za-z0-9][_A-Za-z0-9.-]*" comp_inst_re = re.compile(inst_re) service_re = r"/?" + inst_re + r"(/" + inst_re + r")*" comp_service_re = re.compile(service_re) uri_re = r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?" comp_uri_re = re.compile(uri_re) user_group_re = r"\w{1,32}$" comp_user_group_re = re.compile(user_group_re) privileges_re = r"(^{([a-z].+[a-z_])}:.+$)|(^\!{0,1}([a-z][a-z_]+)$)" comp_privileges_re = re.compile(privileges_re) unreserved = r"A-Za-z0-9-._~," reserved = r":/?#\[\]@!$&'()*+;= %" pg_prop_name_re = r"[" + unreserved + reserved + "]+" comp_pg_prop_name_re = re.compile(pg_prop_name_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( _("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( _("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 len(self._legal_values) == 0 or self.value in self._legal_values: return raise CLError(_("\"{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 = _( '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 = _("{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 = _( '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 = _("Specifies the name of the instance.") def validate(self, params): if check_name(self.comp_inst_re, self.value): return raise CLError(_("\"{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 = _( 'Sets the service model. This is the value of the ' 'startd/duration property. Refer to svc.startd(8). ' '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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( NM_START, self._name)) try: validate_time(self.value) except PropValueError as e: raise CLError( _( "\"{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 = _( "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(8)). 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( _( "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( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_START)) if NM_STOP in params: raise CLError( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_STOP)) class RefreshMethod(NVPair): _count = 0 _name = NM_REFRESH _key = NM_REFRESH _value_mnemonic = "command" _description = _( "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(7) " "are allowed. :true is the default.").format(NM_REFRESH) class StartMethod(NVPair): _count = 0 _name = NM_START _key = NM_START _value_mnemonic = "command" _description = _( "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(7) 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( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) class StopMethod(NVPair): _count = 0 _name = NM_STOP _key = NM_STOP _value_mnemonic = "command" _description = _( "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(7) 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( _( "\"{0}\" should not be specified 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 = ( _( "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(_("\"{0}\" is not a valid {1}").format( self.value, self._name)) class CredUser(NVPair): _count = 0 _name = NM_METHOD_USER _key = NM_METHOD_USER _value_mnemonic = "user" _decription = (_("Specifies the user name to run the method as")) def validate(self, params): """Verify the username is valid.""" if check_name(self.comp_user_group_re, self.value): return raise CLError(_("\"{0}\" is not a valid {1}").format( self.value, self._name)) class CredGroup(NVPair): _count = 0 _name = NM_METHOD_GROUP _key = NM_METHOD_GROUP _value_mnemonic = "group" _decription = (_("Specifies the group name to run the method as")) def validate(self, params): """Verify the groupname is valid.""" if check_name(self.comp_user_group_re, self.value): return raise CLError(_("\"{0}\" is not a valid {1}").format( self.value, self._name)) class CredPrivileges(NVPair): _count = 0 _name = NM_METHOD_PRIVILEGES _key = NM_METHOD_PRIVILEGES _value_mnemonic = "privileges" _decription = (_("Specifies the privilege set the method will run with")) def validate(self, params): """Verify the privilegesname is valid.""" for priv in self.value.split(','): if not check_name(self.comp_privileges_re, priv): raise CLError(_("\"{0}\" is not a valid {1} expression: " "{2} invalid").format(self.value, self._name, priv)) 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 = _( "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( _( "Property definition \"{0}\" must have 4 " "components.").format( value)) if prop_def_components[2] == CLProperties.UNSPECIFIED_PROP_TYPE: raise CLError( _( "\"{0}\" is an invalid property type in {1}.").format( prop_def_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( _( "Property group name is empty in \"{0}\".").format( self.value)) if len(prop_name) == 0: raise CLError( _( "Property name is empty in \"{0}\"").format( self.value)) if not check_name(self.comp_pg_prop_name_re, pg_name): raise CLError( _( "\"{0}\" is an illegal property group name " "in \"{1}\".").format( pg_name, self.value)) if not check_name(self.comp_pg_prop_name_re, prop_name): raise CLError( _( "\"{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( _( "\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _( "\"{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( _( "\"{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( _( "\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{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( _("\"{0}\" must be between 1 and 31 or -1 and -31").format( self.value)) except ValueError: raise CLError( _("\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_INTERVAL)) try: val = int(self.value) if val < 1: raise CLError( _("\"{0}\" must be a positive integer").format( self._name)) except ValueError: raise CLError( _("\"{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 KeyError: # 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( _( "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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" must not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_INTERVAL)) try: val = int(self.value) if val > 23 or val < -24: raise CLError(_("\"{0}\" must be an integer between " + "-24 and 23").format(self._name, NM_HOUR)) except ValueError: raise CLError( _("\"{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 = "" _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( _( "\"{0}\" must not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_INTERVAL)) if self.value not in self._valid_intervals: raise CLError( _( "\"{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( _( "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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_INTERVAL)) try: val = int(self.value) if val > 59 or val < -60: raise CLError( _("\"{0}\" must be an integer between -60 and 59").format( self._name, NM_MINUTE)) except ValueError: raise CLError( _("\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" must not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{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( _("\"{0}\" must be between -12 and -1, 1 and 12, or" " be a valid name or abbreviation of a month").format( self.value)) except ValueError: if val not in calendar.month_name and \ val not in calendar.month_abbr: raise CLError(_("\"{0}\" is not the name of a month").format( self.value)) class SmfTimeout(NVPair): _count = 0 _name = NM_TIMEOUT _key = NM_TIMEOUT _value_mnemonic = "timeout" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) class SmfTimeoutStart(SmfTimeout): _count = 0 _name = NM_TIMEOUT_START _key = NM_TIMEOUT_START _value_mnemonic = "start-timeout" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) period = params.get(NM_PERIOD) if (period is not None): raise CLError( _("\"{0}\" cannot be specified with periodic services." ).format(self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) class SmfTimeoutStop(SmfTimeout): _count = 0 _name = NM_TIMEOUT_STOP _key = NM_TIMEOUT_STOP _value_mnemonic = "stop-timeout" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) period = params.get(NM_PERIOD) if (period is not None): raise CLError( _("\"{0}\" cannot be specified with periodic services." ).format(self._name)) interval = params.get(NM_INTERVAL) if (interval is not None): raise CLError( _("\"{0}\" cannot be specified with scheduled services." ).format(self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) class SmfTimeoutRefresh(SmfTimeout): _count = 0 _name = NM_TIMEOUT_REFRESH _key = NM_TIMEOUT_REFRESH _value_mnemonic = "refresh-timeout" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) period = params.get(NM_PERIOD) if (period is not None): raise CLError( _("\"{0}\" cannot be specified with periodic services." ).format(self._name)) interval = params.get(NM_INTERVAL) if (interval is not None): raise CLError( _("\"{0}\" cannot be specified with scheduled services." ).format(self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) class Jitter(NVPair): _count = 0 _name = NM_JITTER _key = NM_JITTER _value_mnemonic = "jitter" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) period_s = params.get(NM_PERIOD) if period_s is None: raise CLError( _("\"{0}\" applies only to periodic services. Please " "provide the period attribute to create a periodic " "service.").format(self._name)) class Delay(NVPair): _count = 0 _name = NM_DELAY _key = NM_DELAY _value_mnemonic = "delay" _description = "" def validate(self, params): """Values entered need to be integers and in seconds""" try: t_sec = int(self.value) if t_sec < 0: raise CLError( _("\"{0}\" must be greater than or equal to 0.").format( self._name)) except ValueError: raise CLError( _("\"{0}\" must be an integer value greater than or equal" " to 0.").format(self._name)) period_s = params.get(NM_PERIOD) if period_s is None: raise CLError( _("\"{0}\" applies only to periodic services. Please " "provide the period attribute to create a periodic " "service.").format(self._name)) class Timezone(NVPair): _count = 0 _name = NM_TIMEZONE _key = NM_TIMEZONE _value_mnemonic = "timezone" _description = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" must not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _("\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _("\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _("\"{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( _("\"{0}\" must be between 1 and 53 or -1 and -53").format( self._name)) except ValueError: raise CLError( _("\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _( "\"{0}\" should not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _( "\"{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( _( "\"{0}\" must be between 1 and 5 or -1 and -5").format( self._name)) except ValueError: raise CLError( _( "\"{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 = "" def validate(self, params): """Don't specify with rc-script or without start-method""" if NM_RC_SCRIPT in params: raise CLError( _( "\"{0}\" must not be specified with \"{1}\"").format( self._name, NM_RC_SCRIPT)) if NM_START not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_START)) if NM_INTERVAL not in params: raise CLError( _( "\"{0}\" must be specified with \"{1}\"").format( self._name, NM_INTERVAL)) try: int(self.value) except ValueError: raise CLError( _( "\"{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( _( "Waiting for {0} to reach {1} state.\n" "It is safe to interrupt.").format( service_name, desired_state)) timeout = params.get(NM_TIMEOUT) if (timeout is None): 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, text=True) result = svcprop.communicate() except OSError as e: parser.error(_("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" not in result[1]): parser.error(_("Problem in running svcprop. {0}").format( result[1])) else: if ((result[0] != "") and (not result[0].startswith("uninitialized")) 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( _( "{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( _( "Unable to run svcadm to restart manifest-import. " "{0}.").format(os.strerror(e.errno))) if svcadm.returncode != 0: safe_se( _( "{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( _("{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( _( "-i is used to install the generated manifest or bundle.")) safe_so( _( "-o specifies where to write the generated manifest or " "bundle.\n")) msg = _("Legal names for -s are: ") + names + "." safe_so(textwrap.fill(msg, 75)) safe_so( _( "\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( _( "{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_DELAY: Delay, NM_DURATION: Duration, NM_ENABLED: Enabled, NM_FREQUENCY: Frequency, NM_HOUR: Hour, NM_INST_NAME: InstanceName, NM_INST_PROP: InstanceProperty, NM_INTERVAL: Interval, NM_JITTER: Jitter, NM_MINUTE: Minute, NM_MODEL: Model, NM_MONTH: Month, NM_PERIOD: Period, NM_RC_SCRIPT: RcScript, NM_REFRESH: RefreshMethod, NM_TIMEOUT_REFRESH: SmfTimeoutRefresh, NM_START: StartMethod, NM_TIMEOUT_START: SmfTimeoutStart, NM_STOP: StopMethod, NM_TIMEOUT_STOP: SmfTimeoutStop, NM_SVC_NAME: ServiceName, NM_SVC_PROP: ServiceProperty, NM_TIMEOUT: SmfTimeout, NM_TIMEZONE: Timezone, NM_WEEK_OF_YEAR: WeekOfYear, NM_WEEKDAY_OF_MONTH: WeekdayOfMonth, NM_YEAR: Year, NM_METHOD_USER: CredUser, NM_METHOD_GROUP: CredGroup, NM_METHOD_PRIVILEGES: CredPrivileges } options = "[-i | -o output_file] -s name=value ... [help [name]]" usage = "%s %s\n%s" % (bundle.pname, options, _(__doc__)) parser = OptionParser(usage=usage, 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(_("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( _( '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( _("-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(_("\"{0}\" contains no equal sign. Parameters " "must have the form of name=value.").format(opt)) value = nvpair[2] if value == "": parser.error(_("\"{0}\" contains no value. Parameters " "must have the form of name=value.").format(opt)) name = nvpair[0] cls = name_map.get(name) if cls is None: parser.error(_("\"{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( _( "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, _("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 = ["svccfg", "validate"] cmd.append(filename) env = {} env["PATH"] = "/usr/sbin" if "SVCCFG_DTD" in os.environ: env = os.environ.copy() try: svccfg = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) result = svccfg.communicate() except OSError as e: parser.error(_("Problem while using svccfg to validate the manifest. " "{0}.").format(os.strerror(e.errno))) if svccfg.returncode != 0: safe_se(_("{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( _('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(_("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 = {} try: locale.setlocale(locale.LC_ALL, "") except locale.Error: pass 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. fd, filename = tempfile.mkstemp(suffix=".xml", dir="/tmp", prefix="svcbundle-") else: filename = params.get(NM_OUTPUT_FILE) if filename is None: output = sys.stdout else: try: output = open(filename, "w+", encoding="utf-8", errors="surrogateescape") except IOError as e: bundle.parser.error( _('Unable to open "{0}". {1}').format( filename, os.strerror(e.errno))) try: # pylint complains that document has no smf_node member. That is # not true. For every minidom object that we create, we add an # smf_node attribute to hold the associated SmfNode object. See # the comments for the SmfNode class. # pylint: disable=E1103 bundle.document.smf_node.writexml(output, "", " ", "\n") # pylint: enable=E1103 except IOError as e: if e.errno != errno.EPIPE: bundle.parser.error( _("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) if __name__ == "__main__": try: main() except KeyboardInterrupt: sys.exit(1)