#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#
"""The pkg.config module provides a set of classes for managing both 'flat'
(single-level) and 'structured' (n-level deep) configuration data that may
be stored in memory, on disk, or using an smf(7) service instance.
The basic structure of the classes found here is roughly as follows:
Configuration Class (e.g. Config)
Provides storage and retrieval of property sections and properties via
its child property sections.
Section Class (e.g. PropertySection)
Provides storage and retrieval of property data via its child properties.
Property Class
Provides storage and retrieval of property data.
Generally, consumers should only need to consume the interfaces provided by the
Config class or its subclasses. However, any public method or property of the
property sections or property objects can be used as well if advanced access or
manipulation of configuration data is needed.
"""
import ast
import codecs
import copy
import errno
import os
import re
import shlex
import six
import stat
import subprocess
import tempfile
import uuid
"""Base exception class for property errors."""
"""Base exception class for property errors."""
"""Exception class used to indicate an invalid property name."""
assert prop is not None
return _("Property name '{0}' is not valid. Section names "
"may not contain: tabs, newlines, carriage returns, "
"form feeds, vertical tabs, slashes, backslashes, or "
"""Exception class used to indicate an invalid property template name.
"""
assert prop is not None
return _("Property template name '{0}' is not valid.").format(
"""Exception class used to indicate an invalid property value."""
value=None):
return _("'{value}' is less than the minimum "
"of '{minimum}' permitted for property "
"'{prop}' in section '{section}'.").format(
return _("'{value}' is greater than the maximum "
"of '{maximum}' permitted for property "
"'{prop}' in section '{section}'.").format(
return _("Invalid value '{value}' for property "
"'{prop}' in section '{section}'.").format(
return _("Invalid value '{value}' for {prop}.").format(
"""Exception class used to indicate the property in question doesn't
allow multiple values."""
return _("Property '{prop}' in section '{section}' "
"doesn't allow multiple values.").format(
return _("Property {0} doesn't allow multiple values.").format(
"""Exception class used to indicate that the value specified
could not be found in the property's list of values."""
return _("Value '{value}' not found in the list of "
"values for property '{prop}' in section "
return _("Value '{value}' not found in the list of values "
"""Exception class used to indicate an invalid section name."""
assert section is not None
return _("Section name '{0}' is not valid. Section names "
"may not contain: tabs, newlines, carriage returns, "
"form feeds, vertical tabs, slashes, backslashes, or "
"""Exception class used to indicate an invalid section template name."""
assert section is not None
return _("Section template name '{0}' is not valid.").format(
"""Exception class used to indicate an invalid property."""
return _("Unknown property '{prop}' in section "
"""Exception class used to indicate an invalid section."""
return _("Unknown property section: {0}.").format(
"""Base class for properties."""
# Whitespace (except single space), '/', and '\' are never allowed.
_value = None
try:
except ValueError:
# Name contains non-ASCII characters.
# Last, set the property's initial value.
return True
return False
return False
return False
return True
return True
# Assume that value can be represented in utf-8.
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Only string values are allowed.
# Transform encoded UTF-8 data into unicode objects if needed.
# Automatically transform encoded UTF-8 data into
# unicode objects if needed.
try:
except ValueError:
try:
except ValueError:
# Assume sequence of arbitrary
# 8-bit data.
pass
return value
"""The name of the property."""
"""The value of the property."""
"""Sets the property's value."""
if value is None:
value = ""
else:
"""A class representing a template for a property. These templates are
used when loading existing configuration data or when adding new
properties to an existing configuration object if the property name
found matches the pattern name given for the template.
"""
assert prop_type
try:
except Exception:
# Unfortunately, python doesn't have a public exception
# class to catch re parse issues; but this only happens
# for misbehaved programs anyway.
"""Returns a new PropertySection object based on the template
using the given name.
"""
pargs = {}
if self.__value_map is not None:
"""Returns a boolean indicating whether the given name matches
the pattern for this template.
"""
"""The name (pattern string) of the property template."""
# Must return a string.
"""Class representing properties with a boolean value."""
return
return
return
return
"""Class representing a property with an integer value."""
return prop
"""Minimum value permitted for this property or None."""
"""Maximum value permitted for this property or None."""
value = 0
try:
except Exception:
return
# Only string values are allowed.
"""Class representing properties with that can only have one of a set
of pre-defined values."""
return prop
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
return
if value == a:
break
if a == "<exec:pathname>" and \
# Don't try to determine if path is valid;
# just that the value starts with 'exec:'.
break
# Don't try to determine if FMRI is valid;
# just that the value starts with 'svc:'.
break
break
# Don't try to determine if path is valid;
# just that the length is greater than 1.
break
else:
"""A list of allowed values for this property."""
"""Class representing properties with a list of string values that may
contain arbitrary character data.
"""
"""Parse the provided python string literal and return the
resulting data structure."""
try:
except (SyntaxError, ValueError):
# ast raises ValueError if input isn't safe or
# valid.
return value
# the value can be arbitrary 8-bit data, so we allow bytes here
value = []
# Only accept lists for literal string form.
else:
try:
except TypeError:
nvalue = []
for v in value:
if v is None:
v = ""
v = str(v)
# Only string values are allowed.
self._is_allowed(v)
"""Class representing properties with a value specified as a list of
a string key, with None as a value.
"""
value = []
# Only accept lists for literal string form.
else:
try:
except TypeError:
nvalue = []
for v in value:
if v is None:
v = {}
elif not isinstance(v, dict):
# Only dict values are allowed.
for item in v:
# we allow None values, but always store them
# as an empty string to prevent them getting
# serialised as "None"
if not v[item]:
v[item] = ""
# if we don't allow an empty list, raise an error
# ensure that we only have dictionary values
return
# ensure that each dictionary in the value is allowed
if not val:
continue
"""Class representing a property with a list of string values that are
simple in nature. Output is in a comma-separated format that may not
be suitable for some datasets such as those containing arbitrary data,
newlines, commas or that may contain zero-length strings. This class
exists for compatibility with older configuration files that stored
lists of data in this format and should not be used for new consumers.
"""
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
try:
except ValueError:
# Arbitrary 8-bit data not supported for simple
# lists.
"""Parse the provided list string and return it as a list."""
# Automatically transform encoded UTF-8 data into Unicode
# objects if needed. This results in ASCII data being
# stored using str() objects, and UTF-8 data using
# unicode() objects. In Python 3, we just want UTF-8 data
# using str(unicode) objects.
result = []
else:
for v in value:
try:
v = v.encode("ascii")
else:
except ValueError:
try:
v = v.decode("utf-8")
except ValueError:
# Arbitrary 8-bit data not
# supported for simple lists.
raise InvalidPropertyValueError(
return result
# Performing the join using a unicode string results in
# a single unicode string object.
return u""
"""Class representing publisher URI properties."""
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
if value == "":
return
if not valid:
"""Class representing a property for a list of publisher URIs. Output
is in a basic comma-separated format that may not be suitable for some
datasets. This class exists for compatibility with older configuration
files that stored lists of data in this format and should not be used
for new consumers.
"""
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
if not valid:
"""Class representing a property for a list of publisher URIs."""
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
if not valid:
"""Class representing a list of values associated with a given publisher
URI.
A PropPubURIDictionaryList contains a series of dictionaries, where
each dictionary must have a "uri" key with a valid URI as a value.
eg.
[ {'uri':'http://foo',
'proxy': 'http://foo-proxy'},
{'uri': 'http://bar',
'proxy': http://bar-proxy'}
... ]
"""
"""Raises an InvalidPropertyValueError if 'value' is not allowed
for this property.
"""
# Enforce base class rules.
if 'uri' not in dic:
"""Class representing a Universally Unique Identifier property."""
if value == "":
return
try:
except Exception:
# Not a valid UUID.
"""Class representing a property with a non-negative integer dotsequence
value."""
value = "0"
else:
try:
except Exception:
"""A class representing a section of the configuration that also
provides an interface for adding and managing properties and sections
for the section."""
# Whitespace (except single space), '/', and '\' are never allowed
# although consumers can place additional restrictions by providing
# a name re. In addition, the name "CONFIGURATION" is reserved for
# use by the configuration serialization classes.
name == "CONFIGURATION":
raise InvalidSectionNameError(name)
try:
except ValueError:
# Name contains non-ASCII characters.
raise InvalidSectionNameError(name)
# Should be set last.
# Dict is in arbitrary order, sort it first to ensure the
# order is same in Python 2 and 3.
return True
return False
return False
for p in self.get_properties():
return propsec
"""Adds the specified property object to the section. The
property must not already exist."""
return prop
"""Returns a dictionary of property values indexed by property
name."""
return dict(
if hasattr(p, "value")
)
"""Returns the property object with the specified name. If
not found, an UnknownPropertyError will be raised."""
try:
except KeyError:
"""Returns a generator that yields the list of property objects.
"""
"""Removes any matching property object from the section."""
try:
except KeyError:
"""The name of the section."""
"""A class representing a template for a section of the configuration.
These templates are used when loading existing configuration data
or when adding new sections to an existing configuration object if
the section name found matches the pattern name given for the template.
"""
try:
except Exception:
# Unfortunately, python doesn't have a public exception
# class to catch re parse issues; but this only happens
# for misbehaved programs anyway.
"""Returns a new PropertySection object based on the template
using the given name.
"""
# A *copy* of the properties must be used to construct the new
# section; otherwise all sections created by this template will
# share the same property *objects* (which is bad).
])
"""Returns a boolean indicating whether the given name matches
the pattern for this template.
"""
"""The name (pattern text) of the property section template."""
# Must return a string.
"""The Config class provides basic in-memory management of configuration
data."""
_target = None
version=None):
"""Initializes a Config object.
'definitions' is a dictionary of PropertySection objects indexed
by configuration version defining the initial set of property
sections, properties, and values for a Config object.
'overrides' is an optional dictionary of property values indexed
by section name and property name. If provided, it will be used
to override any default values initially assigned during
initialization.
'version' is an integer value that will be used to determine
which configuration definition to use. If not provided, the
newest version found in 'definitions' will be used.
"""
if version is None:
if definitions:
else:
version = 0
"""Returns a unicode object representation of the configuration
object.
"""
out = u""
for p in props:
out += u"\n"
return out
"""Returns the Property object matching the given name for
the given PropertySection object, or adds a new one (if it
does not already exist) based on class definitions.
'default_type' is an optional parameter specifying the type of
property to create if a class definition does not exist for the
given property.
"""
try:
except UnknownSectionError:
# Get a copy of the definition for this section.
# Elide property templates.
elide = [
if not isinstance(p, Property)
]
# force map() to process elements
try:
except UnknownPropertyError:
# See if there is an existing definition for this
# property; if there is, duplicate it, and add it
# to the section.
return propobj
# Subclasses can redefine these to impose additional restrictions on
# section and property names. These methods should return if the name
# is valid, or raise an exception if it is not. These methods are only
# used during __init__, add_section, reset, set_property, and write.
"""Raises an exception if property name is not valid for this
class.
"""
pass
"""Raises an exception if section name is not valid for this
class.
"""
pass
"""Returns a new Property object for the given name based on
class definitions (if available).
"""
try:
except UnknownPropertyError:
# No specific definition found for this section,
# see if there is a suitable template for creating
# one.
for p in secdef.get_properties():
if not isinstance(p, PropertyTemplate):
continue
# Not a known property; create a new one using
# the default type.
return default_type(name)
"""Returns a new PropertySection object for the given name based
on class definitions (if available).
"""
# See if there is an existing definition for this
# section; if there is, return a copy.
if not isinstance(s, PropertySection):
# Ignore section templates.
continue
else:
# No specific definition found for this section,
# see if there is a suitable template for creating
# one.
if not isinstance(s,
continue
return PropertySection(name)
"""Returns the configuration object to its default state."""
if not isinstance(s, PropertySection):
# Templates should be skipped during reset.
continue
# Elide property templates.
elide = [
if not isinstance(p, Property)
]
"""Adds the value to the property object matching the given
section and name. If the section or property does not already
exist, it will be added. Raises InvalidPropertyValueError if
the value is not valid for the given property or if the target
property isn't a list."""
# If a value was just appended directly, the property class
# set method wouldn't be executed and the value added wouldn't
# get verified, so append to a copy of the property's value and
# then set the property to the new value. This allows the new
# property.
try:
except PropertyConfigError as e:
raise
"""Adds the specified property section object. The section must
not already exist.
"""
"""Returns a dictionary of dictionaries indexed by section name
and then property name for all properties."""
return dict(
for s in self.get_sections()
)
"""Returns the value of the property object matching the given
section and name. Raises UnknownPropertyError if it does not
exist.
Be aware that references to the original value are returned;
if the return value is not an immutable object (such as a list),
changes to the object will affect the property. If the return
value needs to be modified, consumers are advised to create a
copy first, and then call set_property() to update the value.
Calling set_property() with the updated value is the only way
to ensure that changes to a property's value are persistent.
"""
try:
except UnknownSectionError:
# To aid in debugging, re-raise as a property error
# so that both the unknown section and property are
# in the error message.
"""Returns a generator that yields a list of tuples of the form
(section object, property generator). The property generator
yields the list of property objects for the section.
"""
return (
(s, s.get_properties())
for s in self.get_sections()
)
"""Returns the PropertySection object with the given name.
Raises UnknownSectionError if it does not exist.
"""
try:
except KeyError:
"""Returns a generator that yields the list of property section
objects."""
"""Remove the property object matching the given section and
name. Raises UnknownPropertyError if it does not exist.
"""
try:
except UnknownSectionError:
# To aid in debugging, re-raise as a property error
# so that both the unknown section and property are
# in the error message.
"""Removes the value from the list of values for the property
object matching the given section and name. Raises
UnknownPropertyError if the property or section does not
exist. Raises InvalidPropertyValueError if the value is not
valid for the given property or if the target property isn't a
list."""
try:
except UnknownSectionError:
# To aid in debugging, re-raise as a property error
# so that both the unknown section and property are
# in the error message.
# Remove the value from a copy of the actual property object
# value so that the property's set verification can happen.
try:
except ValueError:
else:
try:
except PropertyConfigError as e:
raise
"""Remove the object matching the given section name. Raises
UnknownSectionError if it does not exist.
"""
try:
except KeyError:
"""Discards current configuration data and returns the
configuration object to its initial state.
'overrides' is an optional dictionary of property values
indexed by section name and property name. If provided,
it will be used to override any default values initially
assigned during reset.
"""
# Initialize to default state.
"""Sets the value of the property object matching the given
section and name. If the section or property does not already
exist, it will be added. Raises InvalidPropertyValueError if
the value is not valid for the given property."""
try:
except PropertyConfigError as e:
raise
"""Sets the values of the property objects matching those found
in the provided dictionary. If any section or property does not
already exist, it will be added. An InvalidPropertyValueError
will be raised if the value is not valid for the given
properties.
'properties' should be a dictionary of dictionaries indexed by
section and then by property name. As an example:
{
'section': {
'property': value
}
}
"""
# Dict is in arbitrary order, sort it first to ensure the
# order is same in Python 2 and 3.
"""Returns the target used for storage and retrieval of
configuration data. This can be None, a pathname, or
an SMF FMRI.
"""
"""Returns an integer value used to indicate what set of
configuration data is in use."""
"""Saves the current configuration object to the target
provided at initialization.
"""
pass
"""The FileConfig class provides file-based retrieval and storage of
non-structured (one-level deep) configuration data. This particular
class uses Python's ConfigParser module for configuration storage and
management.
ConfigParser uses a simple text format that consists of sections, lead
by a "[section]" header, and followed by "name = value" entries, with
continuations, etc. in the style of RFC 822. Values can be split over
multiple lines by beginning continuation lines with whitespace. A
sample configuration file might look like this:
[pkg]
port = 80
[pub_example_com]
feed_description = example.com's software
update log
"""
"""Initializes the object.
'pathname' is the name of the file to read existing
configuration data from or to write new configuration
data to. If the file does not already exist, defaults
are set based on the version provided and the file will
be created when the configuration is written.
'definitions' is a dictionary of PropertySection objects indexed
by configuration version defining the initial set of property
sections, properties, and values for a Config object.
'overrides' is an optional dictionary of property values indexed
by section name and property name. If provided, it will be used
to override any default values initially assigned during
initialization.
'version' is an integer value that will be used to determine
which configuration definition to use. If not provided, the
version will be based on the contents of the configuration
file or the newest version found in 'definitions'.
"""
# Must be set first.
"""Reads the specified pathname and populates the configuration
object based on the data contained within. The file is
expected to be in a ConfigParser-compatible format.
"""
# First, attempt to read the target.
# Disabled ConfigParser's inane option transformation to ensure
# option case is preserved.
cp.optionxform = lambda x: x
try:
encoding="utf-8")
except EnvironmentError as e:
# Assume default configuration.
pass
raise api_errors.PermissionsException(
e.filename)
else:
raise
else:
try:
# readfp() will be removed in futher Python
# versions, use read_file() instead.
else:
except (configparser.ParsingError,
raise api_errors.InvalidConfigFile(
# Attempt to determine version from contents.
try:
except (configparser.NoSectionError,
# Assume current version.
pass
# Reset to initial state to ensure the default set of properties
# and values exists so that any values not specified by the
# saved configuration or overrides will be correct. This must
# be done after the version is determined above so that the
# saved configuration data can be merged with the correct
# configuration definition.
if section == "CONFIGURATION":
# Reserved for configuration file management.
continue
continue
prop)
# Try to convert unicode object to str object
# to ensure comparisons works as expected for
# consumers.
try:
except UnicodeEncodeError:
# Value contains unicode.
pass
try:
except PropertyConfigError as e:
if hasattr(e, "section") and \
not e.section:
raise
"""Discards current configuration state and returns the
configuration object to its initial state.
'overrides' is an optional dictionary of property values
indexed by section name and property name. If provided,
it will be used to override any default values initially
assigned during reset.
"""
# Reload the configuration.
if not overrides:
# Unless there were overrides, ignore any initial
# values for the purpose of determining whether a
# write should occur. This isn't strictly correct,
# but is the desired behaviour in most cases. This
# also matches the historical behaviour of the
# configuration classes used in pkg(7).
"""Saves the configuration data using the pathname provided at
initialization.
"""
return
# Disabled ConfigParser's inane option transformation to ensure
# option case is preserved.
cp.optionxform = lambda x: x
for p in props:
assert isinstance(p, Property)
# Used to track configuration management information.
fn = None
try:
st = None
try:
except OSError as e:
raise
if st:
try:
except OSError as e:
raise
else:
else:
# it becomes easier to open the file
except EnvironmentError as e:
raise api_errors.PermissionsException(
e.filename)
e.filename)
raise
finally:
# For SMF properties and property groups, this defines the naming restrictions.
# Although, additional restrictions may be imposed by the property and section
# classes in this module.
"""Exception class used to indicate an invalid SMF property name."""
assert prop is not None
return _("Property name '{name}' is not valid. Property "
"names may not contain: tabs, newlines, carriage returns, "
"form feeds, vertical tabs, slashes, or backslashes and "
"must also match the regular expression: {exp}").format(
"""Exception class used to indicate an invalid SMF section name."""
assert section is not None
return _("Section name '{name}' is not valid. Section names "
"may not contain: tabs, newlines, carriage returns, form "
"feeds, vertical tabs, slashes, or backslashes and must "
"also match the regular expression: {exp}").format(
"""Exception classes used to indicate that an error was encountered
while attempting to read configuration data from SMF."""
return _("Unable to read configuration data for SMF FMRI "
"""Exception classes used to indicate that an error was encountered
while attempting to write configuration data to SMF."""
return _("Unable to write configuration data for SMF FMRI "
"""The SMFConfig class provides SMF-based retrieval of non-structured
(one-level deep) configuration data. Property groups should be named
after property sections. Properties with list-based values should be
stored using SMF list properties."""
"network", "startd", "manifestfiles", "start", "stop",
"tm_common_name")
"""Initializes the object.
'svc_fmri' is the FMRI of the SMF service to use for property
data storage and retrieval.
'definitions' is a dictionary of PropertySection objects indexed
by configuration version defining the initial set of property
sections, properties, and values for a Config object.
'doorpath' is an optional pathname indicating the location of
a door file to be used to communicate with SMF. This is
intended for use with an alternative svc.configd daemon.
'overrides' is an optional dictionary of property values indexed
by section name and property name. If provided, it will be used
to override any default values initially assigned during
initialization.
'version' is an integer value that will be used to determine
which configuration definition to use. If not provided, the
version will be based on the newest version found in
'definitions'.
"""
# Must be set first.
"""Raises an exception if property name is not valid for this
class.
"""
raise SMFInvalidPropertyNameError(name)
"""Raises an exception if section name is not valid for this
class.
"""
raise SMFInvalidSectionNameError(name)
"""Reads the configuration from the SMF FMRI specified at init
time.
"""
doorpath = ""
if self.__doorpath:
if status:
cfgdata = {}
prop = None
if prop is None:
else:
# Output from svcprop can be spread over multiple lines
# if a property value has embedded newlines. As such,
# look for the escape sequence at the end of the string
# to determine if output should be accumulated.
prop += "\n"
continue
continue
# SMF-specific groups ignored.
prop = None
continue
if (t == "astring" or t == "ustring") and v == '""':
v = ''
prop = None
# Reset to initial state to ensure the default set of properties
# and values exists so that any values not specified by the
# saved configuration or overrides will be correct. This must
# be done after the version is determined above so that the
# saved configuration data can be merged with the correct
# configuration definition.
# shlex.split() automatically does escaping for a list of values
# so no need to do it here.
if section == "CONFIGURATION":
# Reserved for configuration file management.
continue
continue
prop)
nvalue = []
try:
v = v.encode(
"ascii")
else:
v, "ascii")
except ValueError:
try:
v = v.decode(
"utf-8")
except ValueError:
# Permit opaque
# data. It's
# up to each
# class whether
# to allow it.
pass
else:
# Allow shlex to unescape the value,
# but rejoin all components as one.
# Finally, set the property value.
try:
except PropertyConfigError as e:
if hasattr(e, "section") and \
not e.section:
raise
"""Discards current configuration state and returns the
configuration object to its initial state.
'overrides' is an optional dictionary of property values
indexed by section name and property name. If provided,
it will be used to override any default values initially
assigned during reset.
"""
# Reload the configuration.
if not overrides:
# Unless there were overrides, ignore any initial
# values for the purpose of determining whether a
# write should occur. This isn't strictly correct,
# but is the desired behaviour in most cases. This
# also matches the historical behaviour of the
# configuration classes used in pkg(7).
"""Saves the current configuration object to the target
provided at initialization.
"""
"data to SMF is not supported at this time."))