manifest.py revision 3312
1516N/A#!/usr/bin/python
39N/A#
39N/A# CDDL HEADER START
39N/A#
39N/A# The contents of this file are subject to the terms of the
39N/A# Common Development and Distribution License (the "License").
39N/A# You may not use this file except in compliance with the License.
39N/A#
39N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
39N/A# or http://www.opensolaris.org/os/licensing.
39N/A# See the License for the specific language governing permissions
39N/A# and limitations under the License.
39N/A#
39N/A# When distributing Covered Code, include this CDDL HEADER in each
39N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
39N/A# If applicable, add the following below this CDDL HEADER, with the
39N/A# fields enclosed by brackets "[]" replaced with your own identifying
39N/A# information: Portions Copyright [yyyy] [name of copyright owner]
39N/A#
39N/A# CDDL HEADER END
39N/A#
926N/A
926N/A#
2608N/A# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
926N/A#
39N/A
2453N/Afrom collections import namedtuple, defaultdict
342N/Afrom functools import reduce
1516N/A
1636N/Aimport errno
1386N/Aimport fnmatch
838N/Aimport hashlib
39N/Aimport os
51N/Aimport re
2073N/Aimport six
2144N/Aimport tempfile
1066N/Afrom itertools import groupby, chain, product, repeat
1231N/Afrom operator import itemgetter
2453N/Afrom six.moves import zip
1352N/A
1890N/Aimport pkg.actions as actions
296N/Aimport pkg.client.api_errors as apx
39N/Aimport pkg.facet as facet
1713N/Aimport pkg.fmri as fmri
1713N/Aimport pkg.misc as misc
39N/Aimport pkg.portable as portable
39N/Aimport pkg.variant as variant
39N/Aimport pkg.version as version
39N/A
39N/Afrom pkg.misc import EmptyDict, EmptyI, expanddirs, PKG_FILE_MODE, PKG_DIR_MODE
205N/Afrom pkg.actions.attribute import AttributeAction
39N/Afrom pkg.actions.directory import DirectoryAction
205N/A
39N/Adef _compile_fnpats(fn_pats):
39N/A """Private helper function that returns a compiled version of a
39N/A dictionary of fnmatch patterns."""
39N/A
39N/A return dict(
39N/A (key, [
39N/A re.compile(fnmatch.translate(pat), re.IGNORECASE).match
39N/A for pat in pats
39N/A ])
39N/A for (key, pats) in six.iteritems(fn_pats)
39N/A )
39N/A
39N/A
39N/Adef _attr_matches(action, attr_match):
39N/A """Private helper function: given an action, return True if any of its
39N/A attributes' values matches the pattern for the same attribute in the
39N/A attr_match dictionary, and False otherwise. Note that the patterns must
39N/A be pre-comiled using re.compile() or _compile_fnpats."""
39N/A
39N/A if not attr_match:
39N/A return True
39N/A
48N/A for (attr, matches) in six.iteritems(attr_match):
48N/A if attr in action.attrs:
48N/A for match in matches:
48N/A for attrval in action.attrlist(attr):
48N/A if match(attrval):
59N/A return True
48N/A return False
2073N/A
2073N/A
39N/Aclass ManifestDifference(
39N/A namedtuple("ManifestDifference", "added changed removed")):
567N/A
838N/A __slots__ = []
838N/A
838N/A __state__desc = tuple([
1890N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
2453N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
2608N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
39N/A ])
39N/A
39N/A __state__commonize = frozenset([
1431N/A actions.generic.NSG,
1431N/A ])
39N/A
227N/A @staticmethod
315N/A def getstate(obj, je_state=None):
315N/A """Returns the serialized state of this object in a format
39N/A that that can be easily stored using JSON, pickle, etc."""
1352N/A return misc.json_encode(ManifestDifference.__name__,
1352N/A tuple(obj),
1352N/A ManifestDifference.__state__desc,
1352N/A commonize=ManifestDifference.__state__commonize,
1431N/A je_state=je_state)
1431N/A
429N/A @staticmethod
315N/A def fromstate(state, jd_state=None):
1352N/A """Allocate a new object using previously serialized state
429N/A obtained via getstate()."""
1352N/A
1352N/A # decode serialized state into python objects
39N/A state = misc.json_decode(ManifestDifference.__name__,
926N/A state,
926N/A ManifestDifference.__state__desc,
203N/A commonize=ManifestDifference.__state__commonize,
203N/A jd_state=jd_state)
203N/A
203N/A return ManifestDifference(*state)
1045N/A
1045N/Aclass Manifest(object):
72N/A """A Manifest is the representation of the actions composing a specific
72N/A package version on both the client and the repository. Both purposes
59N/A utilize the same storage format.
2054N/A
1045N/A The serialized structure of a manifest is an unordered list of actions.
1045N/A
1045N/A The special action, "set", represents a package attribute.
1045N/A
1713N/A The reserved attribute, "fmri", represents the package and version
1045N/A described by this manifest. It is available as a string via the
1045N/A attributes dictionary, and as an FMRI object from the fmri member.
1045N/A
2084N/A The list of manifest-wide reserved attributes is
2084N/A
2084N/A base_directory Default base directory, for non-user images.
2084N/A fmri Package FMRI.
2084N/A isa Package is intended for a list of ISAs.
2084N/A platform Package is intended for a list of platforms.
2084N/A relocatable Suitable for User Image.
72N/A
2084N/A All non-prefixed attributes are reserved to the framework. Third
838N/A parties may prefix their attributes with a reversed domain name, domain
72N/A name, or stock symbol. An example might be
72N/A
2084N/A com.example,supported
838N/A
72N/A as an indicator that a specific package version is supported by the
59N/A vendor, example.com.
72N/A
72N/A manifest.null is provided as the null manifest. Differences against the
59N/A null manifest result in the complete set of attributes and actions of
72N/A the non-null manifest, meaning that all operations can be viewed as
72N/A tranitions between the manifest being installed and the manifest already
72N/A present in the image (which may be the null manifest).
72N/A """
72N/A
2144N/A def __init__(self, pfmri=None):
72N/A self.fmri = pfmri
59N/A
72N/A self._cache = {}
72N/A self._absent_cache = []
72N/A self.actions = []
72N/A self.actions_bytype = {}
59N/A self.attributes = {} # package-wide attributes
72N/A self.signatures = EmptyDict
72N/A self.excludes = EmptyI
72N/A if pfmri is not None:
202N/A if not isinstance(pfmri, fmri.PkgFmri):
72N/A pfmri = fmri.PkgFmri(pfmri)
72N/A self.publisher = pfmri.publisher
59N/A else:
1713N/A self.publisher = None
59N/A
838N/A def __str__(self):
2240N/A r = ""
838N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
838N/A r += "set name=pkg.fmri value={0}\n".format(self.fmri)
838N/A
926N/A for act in sorted(self.actions):
838N/A r += "{0}\n".format(act)
838N/A return r
2240N/A
2240N/A def as_lines(self):
2284N/A """A generator function that returns the unsorted manifest
2240N/A contents as lines of text."""
838N/A
2240N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
2240N/A yield "set name=pkg.fmri value={0}\n".format(self.fmri)
2240N/A
2240N/A for act in self.actions:
2240N/A yield "{0}\n".format(act)
2240N/A
2240N/A def tostr_unsorted(self):
2240N/A return "".join((l for l in self.as_lines()))
2240N/A
2317N/A def difference(self, origin, origin_exclude=EmptyI,
2240N/A self_exclude=EmptyI):
2240N/A """Return three lists of action pairs representing origin and
2240N/A destination actions. The first list contains the pairs
2240N/A representing additions, the second list contains the pairs
2240N/A representing updates, and the third list contains the pairs
2240N/A representing removals. All three lists are in the order in
2240N/A which they should be executed."""
2240N/A # XXX Do we need to find some way to assert that the keys are
2240N/A # all unique?
2240N/A
2240N/A if isinstance(origin, EmptyFactoredManifest):
2284N/A # No origin was provided, so nothing has been changed or
2284N/A # removed; only added. In addition, this doesn't need
2284N/A # to be sorted since the caller likely already does
2284N/A # (such as pkgplan/imageplan).
2240N/A return ManifestDifference(
2240N/A [(None, a) for a in self.gen_actions(
2284N/A excludes=self_exclude)], [], [])
2284N/A
2284N/A def hashify(v):
838N/A """handle key values that may be lists"""
838N/A if type(v) is not list:
838N/A return v
838N/A return tuple(v)
838N/A
838N/A def dictify(mf, excludes):
838N/A # Transform list of actions into a dictionary keyed by
926N/A # action key attribute, key attribute and mediator, or
838N/A # id if there is no key attribute.
838N/A for a in mf.gen_actions(excludes=excludes):
838N/A if (a.name == "link" or
838N/A a.name == "hardlink") and \
838N/A a.attrs.get("mediator"):
838N/A akey = (a.name, tuple([
838N/A a.attrs[a.key_attr],
845N/A a.attrs.get("mediator-version"),
845N/A a.attrs.get("mediator-implementation")
926N/A ]))
838N/A else:
845N/A akey = (a.name, hashify(a.attrs.get(
845N/A a.key_attr, id(a))))
845N/A yield (akey, a)
838N/A
845N/A sdict = dict(dictify(self, self_exclude))
845N/A odict = dict(dictify(origin, origin_exclude))
838N/A
926N/A sset = set(six.iterkeys(sdict))
203N/A oset = set(six.iterkeys(odict))
315N/A
838N/A added = [(None, sdict[i]) for i in sset - oset]
203N/A removed = [(odict[i], None) for i in oset - sset]
838N/A changed = [
48N/A (odict[i], sdict[i])
48N/A for i in oset & sset
48N/A if odict[i].different(sdict[i])
48N/A ]
48N/A
838N/A # XXX Do changed actions need to be sorted at all? This is
203N/A # likely to be the largest list, so we might save significant
48N/A # time by not sorting. Should we sort above? Insert into a
203N/A # sorted list?
72N/A
181N/A # singlesort = lambda x: x[0] or x[1]
72N/A addsort = itemgetter(1)
181N/A remsort = itemgetter(0)
46N/A removed.sort(key=remsort, reverse=True)
181N/A added.sort(key=addsort)
237N/A changed.sort(key=addsort)
46N/A
2453N/A return ManifestDifference(added, changed, removed)
2453N/A
2453N/A @staticmethod
2453N/A def comm(compare_m):
2339N/A """Like the unix utility comm, except that this function
2453N/A takes an arbitrary number of manifests and compares them,
2453N/A returning a tuple consisting of each manifest's actions
2453N/A that are not the same for all manifests, followed by a
2453N/A list of actions that are the same in each manifest."""
2453N/A
2339N/A # Must specify at least one manifest.
2339N/A assert compare_m
2339N/A dups = []
2339N/A
2339N/A # construct list of dictionaries of actions in each
2339N/A # manifest, indexed by unique key and variant combination
2453N/A m_dicts = []
2453N/A for m in compare_m:
2453N/A m_dict = {}
2453N/A for a in m.gen_actions():
2453N/A # The unique key for each action is based on its
2453N/A # type, key attribute, and unique variants set
2453N/A # on the action.
2453N/A try:
2453N/A key = set(a.attrlist(a.key_attr))
2453N/A if (a.name == "link" or
2453N/A a.name == "hardlink") and \
2453N/A a.attrs.get("mediator"):
2339N/A for v in ("mediator-version",
2453N/A "mediator-implementation"):
2453N/A key.update([
2453N/A "{0}={1}".format(v,
2453N/A a.attrs.get(v))])
2453N/A key.update(
2453N/A "{0}={1}".format(v, a.attrs[v])
2453N/A for v in a.get_varcet_keys()[0]
2453N/A )
2453N/A
2453N/A key = tuple(key)
2453N/A except KeyError:
2453N/A # If there is no key attribute for the
2453N/A # action, then fallback to the object
2453N/A # id for the action as its identifier.
2453N/A key = (id(a),)
2453N/A
2453N/A # catch duplicate actions here...
2453N/A if m_dict.setdefault((a.name, key), a) != a:
2453N/A dups.append((m_dict[(a.name, key)], a))
2453N/A
2453N/A m_dicts.append(m_dict)
2453N/A
2453N/A if dups:
2453N/A raise ManifestError(duplicates=dups)
2453N/A
2453N/A # construct list of key sets in each dict
2453N/A m_sets = [
2453N/A set(m.keys())
2339N/A for m in m_dicts
2339N/A ]
2339N/A
2339N/A common_keys = reduce(lambda a, b: a & b, m_sets)
2339N/A
2453N/A # determine which common_keys have common actions
2453N/A for k in common_keys.copy():
2453N/A for i in range(len(m_dicts) - 1):
2453N/A if m_dicts[i][k].different(
2453N/A m_dicts[i + 1][k]):
2339N/A common_keys.remove(k)
2453N/A break
2453N/A return tuple(
2453N/A [
2453N/A [m_dicts[i][k] for k in m_sets[i] - common_keys]
2339N/A for i in range(len(m_dicts))
2453N/A ]
2453N/A +
2453N/A [
2453N/A [ m_dicts[0][k] for k in common_keys ]
2453N/A ]
2339N/A )
2339N/A
2339N/A def combined_difference(self, origin, ov=EmptyI, sv=EmptyI):
2339N/A """Where difference() returns three lists, combined_difference()
2339N/A returns a single list of the concatenation of the three."""
2608N/A return list(chain(*self.difference(origin, ov, sv)))
2608N/A
2608N/A def humanized_differences(self, other, ov=EmptyI, sv=EmptyI):
2339N/A """Output expects that self is newer than other. Use of sets
2453N/A requires that we convert the action objects into some marshalled
2453N/A form, otherwise set member identities are derived from the
2339N/A object pointers, rather than the contents."""
2453N/A
2339N/A l = self.difference(other, ov, sv)
2540N/A out = ""
2339N/A
2339N/A for src, dest in chain(*l):
2339N/A if not src:
2339N/A out += "+ {0}\n".format(str(dest))
2339N/A elif not dest:
2339N/A out += "- {0}\n" + str(src)
2339N/A else:
2339N/A out += "{0} -> {1}\n".format(src, dest)
2339N/A return out
2339N/A
2453N/A def _gen_dirs_to_str(self):
2453N/A """Generate contents of dircache file containing all dirctories
2453N/A referenced explicitly or implicitly from self.actions. Include
2453N/A variants as values; collapse variants where possible."""
2453N/A
2453N/A def gen_references(a):
2453N/A for d in expanddirs(a.directory_references()):
2453N/A yield d
2608N/A
2608N/A dirs = self._actions_to_dict(gen_references)
2608N/A for d in dirs:
2453N/A for v in dirs[d]:
2453N/A a = DirectoryAction(path=d, **v)
2453N/A yield str(a) + "\n"
2453N/A
2453N/A def _gen_mediators_to_str(self):
2453N/A """Generate contents of mediatorcache file containing all
2540N/A mediators referenced explicitly or implicitly from self.actions.
2453N/A Include variants as values; collapse variants where possible."""
2453N/A
2453N/A def gen_references(a):
2453N/A if (a.name == "link" or a.name == "hardlink") and \
2453N/A "mediator" in a.attrs:
2453N/A yield (a.attrs.get("mediator"),
2453N/A a.attrs.get("mediator-priority"),
2453N/A a.attrs.get("mediator-version"),
2453N/A a.attrs.get("mediator-implementation"))
2453N/A
2453N/A mediators = self._actions_to_dict(gen_references)
2453N/A for mediation, mvariants in six.iteritems(mediators):
2453N/A values = {
2453N/A "mediator-priority": mediation[1],
2453N/A "mediator-version": mediation[2],
2453N/A "mediator-implementation": mediation[3],
2453N/A }
2453N/A for mvariant in mvariants:
2453N/A a = "set name=pkg.mediator " \
2453N/A "value={0} {1} {2}\n".format(mediation[0],
2453N/A " ".join((
2453N/A "=".join(t)
2453N/A for t in six.iteritems(values)
2453N/A if t[1]
2453N/A )),
2453N/A " ".join((
2453N/A "=".join(t)
838N/A for t in six.iteritems(mvariant)
838N/A ))
2608N/A )
2608N/A yield a
2608N/A
2608N/A def _gen_attrs_to_str(self):
838N/A """Generate set action supplemental data containing all facets
838N/A and variants from self.actions and size information. Each
838N/A returned line must be newline-terminated."""
838N/A
838N/A emit_variants = "pkg.variant" not in self
838N/A emit_facets = "pkg.facet" not in self
342N/A emit_sizes = "pkg.size" not in self and "pkg.csize" not in self
926N/A
838N/A if not any((emit_variants, emit_facets, emit_sizes)):
838N/A # Package already has these attributes.
2608N/A return
2608N/A
2608N/A # List of possible variants and possible values for them.
2608N/A variants = defaultdict(set)
926N/A
838N/A # Seed with declared set of variants as actions may be common to
838N/A # both and so will not be tagged with variant.
838N/A for name in self.attributes:
838N/A if name[:8] == "variant.":
838N/A variants[name] = set(self.attributes[name])
926N/A
2453N/A # List of possible facets and under what variant combinations
2453N/A # they were seen.
2453N/A facets = defaultdict(set)
2608N/A
2453N/A # Unique (facet, value) (variant, value) combinations.
2453N/A refs = defaultdict(lambda: defaultdict(int))
2453N/A
2453N/A for a in self.gen_actions():
2453N/A name = a.name
926N/A attrs = a.attrs
615N/A if name == "set":
615N/A if attrs["name"][:12] == "pkg.variant":
615N/A emit_variants = False
615N/A elif attrs["name"][:9] == "pkg.facet":
615N/A emit_facets = False
926N/A
615N/A afacets = []
615N/A avariants = []
838N/A for attr, val in six.iteritems(attrs):
111N/A if attr[:8] == "variant.":
111N/A variants[attr].add(val)
111N/A avariants.append((attr, val))
111N/A elif attr[:6] == "facet.":
111N/A afacets.append((attr, val))
111N/A
111N/A for name, val in afacets:
111N/A # Facet applicable to this particular variant
113N/A # combination.
926N/A varkey = tuple(sorted(avariants))
838N/A facets[varkey].add(name)
926N/A
113N/A # This *must* be sorted to ensure reproducible set
113N/A # action generation for sizes and to ensure each
113N/A # combination is actually unique.
113N/A varcetkeys = tuple(sorted(chain(afacets, avariants)))
113N/A refs[varcetkeys]["csize"] += misc.get_pkg_otw_size(a)
113N/A if name == "signature":
113N/A refs[varcetkeys]["csize"] += \
113N/A a.get_action_chain_csize()
113N/A refs[varcetkeys]["size"] += a.get_size()
111N/A
1500N/A # Prevent scope leak.
1432N/A afacets = avariants = attrs = varcetkeys = None
1542N/A
1970N/A if emit_variants:
2073N/A # Unnecessary if we can guarantee all variants will be
2073N/A # declared at package level. Omit the "variant." prefix
2073N/A # from attribute values since that's implicit and can be
2073N/A # added back when the action is parsed.
2073N/A yield "{0}\n".format(AttributeAction(None,
2073N/A name="pkg.variant",
1542N/A value=sorted(v[8:] for v in variants)))
146N/A
1432N/A # Emit a set action for every variant used with possible values
1432N/A # if one does not already exist.
50N/A for name in variants:
1432N/A # merge_facets needs the variant values sorted and this
1432N/A # is desirable when generating the variant attr anyway.
1432N/A variants[name] = sorted(variants[name])
51N/A if name not in self.attributes:
1636N/A yield "{0}\n".format(AttributeAction(None,
1432N/A name=name, value=variants[name]))
1507N/A
51N/A if emit_facets:
1500N/A # Get unvarianted facet set.
591N/A cfacets = facets.pop((), set())
1970N/A
1970N/A # For each variant combination, remove unvarianted
1970N/A # facets since they are common to all variants.
591N/A for varkey, fnames in facets.items():
1542N/A fnames.difference_update(cfacets)
1970N/A if not fnames:
1970N/A # No facets unique to this combo;
1970N/A # discard.
2073N/A del facets[varkey]
1500N/A
2073N/A # If all possible variant combinations supported by the
2073N/A # package have at least one facet, then the intersection
1713N/A # of facets for all variants can be merged with the
1713N/A # common set.
2073N/A merge_facets = len(facets) > 0
2073N/A if merge_facets:
2073N/A # Determine unique set of variant combinations
1713N/A # seen for faceted actions.
2608N/A vcombos = set((
2608N/A tuple(
2613N/A vpair[0]
2284N/A for vpair in varkey
2073N/A )
2073N/A for varkey in facets
1713N/A ))
2073N/A
2073N/A # For each unique variant combination, determine
2073N/A # if the cartesian product of all variant values
1713N/A # supported by the package for the combination
1713N/A # has been seen. In other words, if the
2073N/A # combination is ((variant.arch,)) and the
2073N/A # package supports (i386, sparc), then both
2073N/A # (variant.arch, i386) and (variant.arch, sparc)
1500N/A # must exist. This code assumes variant values
1500N/A # for each variant are already sorted.
1500N/A for pair in chain.from_iterable(
1500N/A product(*(
1500N/A tuple((name, val)
2613N/A for val in variants[name])
51N/A for name in vcombo)
1500N/A )
1500N/A for vcombo in vcombos
1500N/A ):
1500N/A if pair not in facets:
1500N/A # If any combination the package
1500N/A # supports has not been seen for
1500N/A # one or more facets, then some
2073N/A # facets are unique to one or
2073N/A # more combinations.
2073N/A merge_facets = False
2073N/A break
2073N/A
2073N/A if merge_facets:
1890N/A # Merge the facets common to all variants if safe;
1500N/A # if we always merged them, then facets only
2073N/A # used by a single variant (think i386-only or
2073N/A # sparc-only content) would be seen unvarianted
2073N/A # (that's bad).
1500N/A vfacets = list(facets.values())
1500N/A vcfacets = vfacets[0].intersection(*vfacets[1:])
1500N/A
1500N/A if vcfacets:
926N/A # At least one facet is shared between
1500N/A # all variant combinations; move the
2026N/A # common ones to the unvarianted set.
2608N/A cfacets.update(vcfacets)
2608N/A
2608N/A # Remove facets common to all combos.
2608N/A for varkey, fnames in facets.items():
1500N/A fnames.difference_update(vcfacets)
2026N/A if not fnames:
2026N/A # No facets unique to
2026N/A # this combo; discard.
2026N/A del facets[varkey]
2073N/A
2026N/A # Omit the "facet." prefix from attribute values since
2026N/A # that's implicit and can be added back when the action
1500N/A # is parsed.
1500N/A val = sorted(f[6:] for f in cfacets)
1500N/A if not val:
1500N/A # If we don't do this, action stringify will
1500N/A # emit this as "set name=pkg.facet" which is
123N/A # then transformed to "set name=name
1500N/A # value=pkg.facet". Not what we wanted, but is
1500N/A # expected for historical reasons.
877N/A val = ""
1500N/A
1500N/A # Always emit an action enumerating the list of facets
1500N/A # common to all variants, even if there aren't any.
1500N/A # That way if there are also no variant-specific facets,
1500N/A # package operations will know that no facets are used
1500N/A # by the package instead of having to scan the whole
1500N/A # manifest.
1500N/A yield "{0}\n".format(AttributeAction(None,
1500N/A name="pkg.facet.common", value=val))
1500N/A
1500N/A # Now emit a pkg.facet action for each variant
1500N/A # combination containing the list of facets unique to
1500N/A # that combination.
1500N/A for varkey, fnames in six.iteritems(facets):
39N/A # A unique key for each combination is needed,
1500N/A # and using a hash obfuscates that interface
1500N/A # while giving us a reliable way to generate
1500N/A # a reproducible, unique identifier. The key
1500N/A # string below looks like this before hashing:
1500N/A # variant.archi386variant.debug.osnetTrue...
838N/A key = hashlib.sha1(
1500N/A "".join("{0}{1}".format(*v) for v in varkey)
1500N/A ).hexdigest()
1500N/A
1500N/A # Omit the "facet." prefix from attribute values
1500N/A # since that's implicit and can be added back
1500N/A # when the action is parsed.
2238N/A act = AttributeAction(None,
2238N/A name="pkg.facet.{0}".format(key),
2238N/A value=sorted(f[6:] for f in fnames))
2238N/A attrs = act.attrs
1500N/A # Tag action with variants.
1500N/A for v in varkey:
2238N/A attrs[v[0]] = v[1]
2238N/A yield "{0}\n".format(act)
2238N/A
2238N/A # Emit pkg.[c]size attribute for [compressed] size of package
2238N/A # for each facet/variant combination.
2238N/A csize = 0
39N/A size = 0
956N/A for varcetkeys in refs:
956N/A rcsize = refs[varcetkeys]["csize"]
956N/A rsize = refs[varcetkeys]["size"]
956N/A
1431N/A if not varcetkeys:
1431N/A # For unfaceted/unvarianted actions, keep a
956N/A # running total so a single [c]size action can
956N/A # be generated.
956N/A csize += rcsize
956N/A size += rsize
956N/A continue
956N/A
941N/A if emit_sizes and (rcsize > 0 or rsize > 0):
1007N/A # Only emit if > 0; actions may be
1007N/A # faceted/variant without payload.
1007N/A
1007N/A # A unique key for each combination is needed,
1007N/A # and using a hash obfuscates that interface
1007N/A # while giving us a reliable way to generate
1007N/A # a reproducible, unique identifier. The key
1007N/A # string below looks like this before hashing:
1007N/A # facet.docTruevariant.archi386...
1007N/A key = hashlib.sha1(
1007N/A "".join("{0}{1}".format(*v) for v in varcetkeys)
1007N/A ).hexdigest()
1007N/A
1007N/A # The sizes are abbreviated in the name of byte
1100N/A # conservation.
1407N/A act = AttributeAction(None,
941N/A name="pkg.sizes.{0}".format(key),
941N/A value=["csz={0}".format(rcsize),
144N/A "sz={0}".format(rsize)])
941N/A attrs = act.attrs
1100N/A for v in varcetkeys:
1100N/A attrs[v[0]] = v[1]
1100N/A yield "{0}\n".format(act)
1100N/A
1100N/A if emit_sizes:
1100N/A act = AttributeAction(None, name="pkg.sizes.common",
1100N/A value=["csz={0}".format(csize),
1100N/A "sz={0}".format(size)])
1100N/A yield "{0}\n".format(act)
1100N/A
941N/A def _actions_to_dict(self, references):
941N/A """create dictionary of all actions referenced explicitly or
941N/A implicitly from self.actions... include variants as values;
941N/A collapse variants where possible"""
941N/A
941N/A refs = {}
941N/A # build a dictionary containing all actions tagged w/
941N/A # variants
941N/A for a in self.actions:
941N/A v, f = a.get_varcet_keys()
941N/A variants = dict((name, a.attrs[name]) for name in v + f)
941N/A for ref in references(a):
941N/A if ref not in refs:
941N/A refs[ref] = [variants]
941N/A elif variants not in refs[ref]:
429N/A refs[ref].append(variants)
941N/A
941N/A # remove any tags if any entries are always delivered (NULL)
941N/A for ref in refs:
941N/A if {} in refs[ref]:
941N/A refs[ref] = [{}]
429N/A continue
941N/A # could collapse refs where all variants are present
941N/A # (the current logic only collapses them if at least
941N/A # one reference is delivered without a facet or
941N/A # variant)
941N/A return refs
1007N/A
1007N/A def get_directories(self, excludes):
1234N/A """ return a list of directories implicitly or
1007N/A explicitly referenced by this object"""
1007N/A
1007N/A if self.excludes == excludes:
1007N/A excludes = EmptyI
1007N/A assert excludes == EmptyI or self.excludes == EmptyI
1007N/A try:
1007N/A alist = self._cache["manifest.dircache"]
1007N/A except KeyError:
1007N/A # generate actions that contain directories
1007N/A alist = self._cache["manifest.dircache"] = [
1007N/A actions.fromstr(s.rstrip())
1007N/A for s in self._gen_dirs_to_str()
1007N/A ]
1007N/A
1007N/A s = set([
1007N/A a.attrs["path"]
1007N/A for a in alist
1007N/A if not excludes or a.include_this(excludes,
1007N/A publisher=self.publisher)
1007N/A ])
1007N/A
1007N/A return list(s)
1007N/A
1007N/A def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
1007N/A """A generator function that returns the supported facet
1007N/A attributes (strings) for this package based on the specified (or
1007N/A current) excludes that also match at least one of the patterns
1007N/A provided. Facets must be true or false so a list of possible
941N/A facet values is not returned."""
941N/A
941N/A if self.excludes == excludes:
144N/A excludes = EmptyI
144N/A assert excludes == EmptyI or self.excludes == EmptyI
1472N/A
1472N/A try:
1472N/A facets = self["pkg.facet"]
1472N/A except KeyError:
1352N/A facets = None
1516N/A
1890N/A if facets is not None and excludes == EmptyI:
1890N/A # No excludes? Then use the pre-determined set of
1890N/A # facets.
1890N/A for f in misc.yield_matching("facet.", facets, patterns):
1890N/A yield f
1352N/A return
1472N/A
1352N/A # If different excludes were specified, then look for pkg.facet
1352N/A # actions containing the list of facets.
1352N/A found = False
1352N/A seen = set()
1352N/A for a in self.gen_actions_by_type("set", excludes=excludes):
1352N/A if a.attrs["name"][:10] == "pkg.facet.":
1352N/A # Either a pkg.facet.common action or a
2073N/A # pkg.facet.X variant-specific action.
1352N/A found = True
429N/A val = a.attrlist("value")
429N/A if len(val) == 1 and val[0] == "":
144N/A # No facets.
1386N/A continue
1386N/A
1386N/A for f in misc.yield_matching("facet.", (
1636N/A "facet.{0}".format(n)
1636N/A for n in val
1636N/A ), patterns):
1636N/A if f in seen:
2073N/A # Prevent duplicates; it's
1636N/A # possible a given facet may be
2073N/A # valid for more than one unique
1636N/A # variant combination that's
1636N/A # allowed by current excludes.
1636N/A continue
1386N/A
1636N/A seen.add(f)
1636N/A yield f
1636N/A
1636N/A if not found:
2073N/A # Fallback to sifting actions to yield possible.
1636N/A facets = self._get_varcets(excludes=excludes)[1]
2284N/A for f in misc.yield_matching("facet.", facets, patterns):
1636N/A yield f
1636N/A
1636N/A def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
1386N/A """A generator function that yields a list of tuples of the form
315N/A (variant, [values]). Where 'variant' is the variant attribute
315N/A name (e.g. 'variant.arch') and '[values]' is a list of the
315N/A variant values supported by this package. Variants returned are
315N/A those allowed by the specified (or current) excludes that also
315N/A match at least one of the patterns provided."""
315N/A
315N/A if self.excludes == excludes:
315N/A excludes = EmptyI
1636N/A assert excludes == EmptyI or self.excludes == EmptyI
1636N/A
1636N/A try:
1636N/A variants = self["pkg.variant"]
1636N/A except KeyError:
1636N/A variants = None
2073N/A
1636N/A if variants is not None and excludes == EmptyI:
2073N/A # No excludes? Then use the pre-determined set of
1636N/A # variants.
1636N/A for v in misc.yield_matching("variant.", variants,
144N/A patterns):
838N/A yield v, self.attributes.get(v, [])
838N/A return
838N/A
926N/A # If different excludes were specified, then look for
926N/A # pkg.variant action containing the list of variants.
926N/A found = False
926N/A variants = defaultdict(set)
838N/A for a in self.gen_actions_by_type("set", excludes=excludes):
1231N/A aname = a.attrs["name"]
1231N/A if aname == "pkg.variant":
2091N/A val = a.attrlist("value")
1231N/A if len(val) == 1 and val[0] == "":
1231N/A # No variants.
1231N/A return
1231N/A for v in val:
1231N/A found = True
205N/A # Ensure variant entries exist (debug
257N/A # variants may not) via defaultdict.
257N/A variants["variant.{0}".format(v)]
257N/A elif aname[:8] == "variant.":
205N/A for v in a.attrlist("value"):
205N/A found = True
1461N/A variants[aname].add(v)
1461N/A
1461N/A if not found:
1461N/A # Fallback to sifting actions to get possible.
1461N/A variants = self._get_varcets(excludes=excludes)[0]
1461N/A
1461N/A for v in misc.yield_matching("variant.", variants, patterns):
1461N/A yield v, variants[v]
1461N/A
1461N/A def gen_mediators(self, excludes=EmptyI):
1461N/A """A generator function that yields tuples of the form (mediator,
1461N/A mediations) expressing the set of possible mediations for this
1044N/A package, where 'mediations' is a set() of possible mediations for
1044N/A the mediator. Each mediation is a tuple of the form (priority,
1044N/A version, implementation).
1044N/A """
1044N/A
1044N/A if self.excludes == excludes:
1044N/A excludes = EmptyI
1044N/A assert excludes == EmptyI or self.excludes == EmptyI
1044N/A try:
1044N/A alist = self._cache["manifest.mediatorcache"]
2286N/A except KeyError:
1044N/A # generate actions that contain mediators
1044N/A alist = self._cache["manifest.mediatorcache"] = [
205N/A actions.fromstr(s.rstrip())
838N/A for s in self._gen_mediators_to_str()
838N/A ]
205N/A
296N/A ret = defaultdict(set)
296N/A for attrs in (
838N/A act.attrs
296N/A for act in alist
296N/A if not excludes or act.include_this(excludes)):
296N/A med_ver = attrs.get("mediator-version")
296N/A if med_ver:
296N/A try:
296N/A med_ver = version.Version(med_ver)
296N/A except version.VersionError:
1461N/A # Consider this mediation unavailable
296N/A # if it can't be parsed for whatever
205N/A # reason.
838N/A continue
205N/A
48N/A ret[attrs["value"]].add((
956N/A attrs.get("mediator-priority"),
2054N/A med_ver,
2054N/A attrs.get("mediator-implementation"),
2054N/A ))
2054N/A
2054N/A for m in ret:
2054N/A yield m, ret[m]
2054N/A
956N/A def gen_actions(self, attr_match=None, excludes=EmptyI):
2073N/A """Generate actions in manifest through ordered callable list"""
2073N/A
2073N/A if self.excludes == excludes:
2073N/A excludes = EmptyI
2073N/A assert excludes == EmptyI or self.excludes == EmptyI
1386N/A
2073N/A if attr_match:
2073N/A attr_match = _compile_fnpats(attr_match)
2073N/A
2073N/A pub = self.publisher
2073N/A for a in self.actions:
2073N/A for c in excludes:
2073N/A if not c(a, publisher=pub):
2073N/A break
956N/A else:
2608N/A # These conditions are split by performance.
2608N/A if not attr_match:
2608N/A yield a
956N/A elif _attr_matches(a, attr_match):
2073N/A yield a
2073N/A
2073N/A def gen_actions_by_type(self, atype, attr_match=None, excludes=EmptyI):
2144N/A """Generate actions in the manifest of type "type"
2144N/A through ordered callable list"""
2144N/A
2073N/A if self.excludes == excludes:
2073N/A excludes = EmptyI
2073N/A assert excludes == EmptyI or self.excludes == EmptyI
2073N/A
2073N/A if attr_match:
2608N/A attr_match = _compile_fnpats(attr_match)
2608N/A
2631N/A pub = self.publisher
2073N/A for a in self.actions_bytype.get(atype, []):
956N/A for c in excludes:
956N/A if not c(a, publisher=pub):
2073N/A break
956N/A else:
956N/A # These conditions are split by performance.
956N/A if not attr_match:
956N/A yield a
956N/A elif _attr_matches(a, attr_match):
2073N/A yield a
956N/A
967N/A def gen_actions_by_types(self, atypes, attr_match=None, excludes=EmptyI):
967N/A """Generate actions in the manifest of types "atypes"
2631N/A through ordered callable list."""
2073N/A
956N/A for atype in atypes:
956N/A for a in self.gen_actions_by_type(atype,
956N/A attr_match=attr_match, excludes=excludes):
2073N/A yield a
1507N/A
956N/A def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
956N/A """Generate the value of the key attribute for each action
956N/A of type "type" in the manifest."""
956N/A
967N/A return (
967N/A a.attrs.get(a.key_attr)
2631N/A for a in self.gen_actions_by_type(atype, excludes=excludes)
956N/A )
956N/A
2631N/A def duplicates(self, excludes=EmptyI):
2631N/A """Find actions in the manifest which are duplicates (i.e.,
956N/A represent the same object) but which are not identical (i.e.,
2073N/A have all the same attributes)."""
2073N/A
2073N/A def fun(a):
956N/A """Return a key on which actions can be sorted."""
956N/A return a.name, a.attrs.get(a.key_attr, id(a))
2073N/A
956N/A alldups = []
956N/A acts = [a for a in self.gen_actions(excludes=excludes)]
967N/A
967N/A for k, g in groupby(sorted(acts, key=fun), fun):
967N/A glist = list(g)
967N/A dups = set()
967N/A for i in range(len(glist) - 1):
1507N/A if glist[i].different(glist[i + 1]):
1507N/A dups.add(glist[i])
1507N/A dups.add(glist[i + 1])
967N/A if dups:
1507N/A alldups.append((k, dups))
956N/A return alldups
1507N/A
956N/A def __content_to_actions(self, content):
956N/A """Parse manifest content, stripping line-continuation
956N/A characters from the input as it is read; this results in actions
956N/A with values across multiple lines being passed to the
956N/A action parsing code whitespace-separated instead.
967N/A
956N/A For example:
956N/A
2073N/A set name=pkg.summary \
956N/A value="foo"
967N/A set name=pkg.description value="foo " \
2073N/A "bar baz"
956N/A
1672N/A ...will each be passed to action parsing as:
967N/A
956N/A set name=pkg.summary value="foo"
956N/A set name=pkg.description value="foo " "bar baz"
956N/A """
1507N/A
956N/A accumulate = ""
956N/A lineno = 0
956N/A errors = []
956N/A
2073N/A if isinstance(content, six.string_types):
1386N/A # Get an iterable for the string.
2144N/A content = content.splitlines()
2144N/A
2144N/A for l in content:
956N/A lineno += 1
1507N/A l = l.lstrip()
956N/A if l.endswith("\\"): # allow continuation chars
1386N/A accumulate += l[0:-1] # elide backslash
1386N/A continue
1386N/A elif accumulate:
1386N/A l = accumulate + l
1386N/A accumulate = ""
956N/A
956N/A if not l or l[0] == "#": # ignore blank lines & comments
956N/A continue
1507N/A
2073N/A try:
1386N/A yield actions.fromstr(l)
2453N/A except actions.ActionError as e:
2453N/A # Accumulate errors and continue so that as
2453N/A # much of the action data as possible can be
2453N/A # parsed.
2453N/A e.fmri = self.fmri
2453N/A e.lineno = lineno
2453N/A errors.append(e)
2453N/A
2453N/A if errors:
2453N/A raise apx.InvalidPackageErrors(errors)
956N/A
2453N/A def set_content(self, content=None, excludes=EmptyI, pathname=None,
2453N/A signatures=False):
2453N/A """Populate the manifest with actions.
956N/A
1352N/A 'content' is an optional value containing either the text
2073N/A representation of the manifest or an iterable of
2073N/A action objects.
2073N/A
2073N/A 'excludes' is optional. If provided it must be a length two
2035N/A list with the variants to be excluded as the first element and
2035N/A the facets to be excluded as the second element.
2073N/A
2035N/A 'pathname' is an optional filename containing the location of
2035N/A the manifest content.
2035N/A
2035N/A 'signatures' is an optional boolean value that indicates whether
2073N/A a manifest signature should be generated. This is only possible
2035N/A when 'content' is a string or 'pathname' is provided.
2035N/A """
2035N/A
2035N/A assert content is not None or pathname is not None
2475N/A assert not (content and pathname)
2475N/A
2475N/A self.actions = []
2475N/A self.actions_bytype = {}
2035N/A self.attributes = {}
2453N/A self._cache = {}
2453N/A self._absent_cache = []
2453N/A
2453N/A # So we could build up here the type/key_attr dictionaries like
1507N/A # sdict and odict in difference() above, and have that be our
2453N/A # main datastore, rather than the simple list we have now. If
956N/A # we do that here, we can even assert that the "same" action
956N/A # can't be in a manifest twice. (The problem of having the same
956N/A # action more than once in packages that can be installed
956N/A # together has to be solved somewhere else, though.)
956N/A if pathname:
2339N/A try:
2453N/A with open(pathname, "rb") as mfile:
2339N/A content = mfile.read()
2453N/A except EnvironmentError as e:
2453N/A raise apx._convert_error(e)
2453N/A if isinstance(content, six.string_types):
2453N/A if signatures:
2613N/A # Generate manifest signature based upon
2613N/A # input content, but only if signatures
2613N/A # were requested. In order to interoperate with
2613N/A # older clients, we must use sha-1 here.
2613N/A self.signatures = {
2613N/A "sha-1": self.hash_create(content)
2453N/A }
2453N/A content = self.__content_to_actions(content)
2453N/A
2453N/A for action in content:
2453N/A self.add_action(action, excludes)
2453N/A self.excludes = excludes
2453N/A # Make sure that either no excludes were provided or that both
2453N/A # variants and facet excludes were or that variant, facet and
2453N/A # hydrate excludes were.
2339N/A assert len(self.excludes) != 1
956N/A
956N/A def exclude_content(self, excludes):
956N/A """Remove any actions from the manifest which should be
956N/A excluded."""
956N/A
956N/A self.set_content(content=self.actions, excludes=excludes)
956N/A
956N/A def add_action(self, action, excludes):
1507N/A """Performs any needed transformations on the action then adds
956N/A it to the manifest.
956N/A
956N/A The "action" parameter is the action object that should be
956N/A added to the manifest.
2613N/A
2205N/A The "excludes" parameter is the variants to exclude from the
2205N/A manifest."""
2073N/A
956N/A attrs = action.attrs
956N/A aname = action.name
956N/A
956N/A # XXX handle legacy transition issues; not needed once support
956N/A # for upgrading images from older releases (< build 151) has
956N/A # been removed.
956N/A if "opensolaris.zone" in attrs and \
1507N/A "variant.opensolaris.zone" not in attrs:
956N/A attrs["variant.opensolaris.zone"] = \
956N/A attrs["opensolaris.zone"]
956N/A
2613N/A if aname == "set" and attrs["name"] == "authority":
2613N/A # Translate old action to new.
2613N/A attrs["name"] = "publisher"
2613N/A
956N/A if excludes and not action.include_this(excludes,
2073N/A publisher=self.publisher):
956N/A return
956N/A
956N/A self.actions.append(action)
956N/A try:
956N/A self.actions_bytype[aname].append(action)
956N/A except KeyError:
956N/A self.actions_bytype.setdefault(aname, []).append(action)
956N/A
956N/A # add any set actions to attributes
956N/A if aname == "set":
956N/A self.fill_attributes(action)
2453N/A
2453N/A def fill_attributes(self, action):
2453N/A """Fill attribute array w/ set action contents."""
2453N/A try:
2453N/A keyvalue = action.attrs["name"]
2453N/A if keyvalue[:10] == "pkg.sizes.":
2453N/A # To reduce manifest bloat, size and csize
956N/A # are set on a single action so need splitting
956N/A # into separate attributes.
956N/A attrval = action.attrlist("value")
956N/A for entry in attrval:
2073N/A szname, szval = entry.split("=", 1)
956N/A if szname == "sz":
956N/A szname = "pkg.size"
956N/A elif szname == "csz":
956N/A szname = "pkg.csize"
956N/A else:
956N/A # Skip unknowns.
956N/A continue
956N/A
1507N/A self.attributes.setdefault(szname, 0)
1507N/A self.attributes[szname] += int(szval)
956N/A return
956N/A except (KeyError, TypeError, ValueError):
956N/A # ignore broken set actions
956N/A pass
1507N/A
956N/A # Ensure facet and variant attributes are always lists.
2054N/A if keyvalue[:10] == "pkg.facet.":
2054N/A # Possible facets list is spread over multiple actions.
956N/A val = action.attrlist("value")
956N/A if len(val) == 1 and val[0] == "":
956N/A # No facets.
956N/A val = []
956N/A
956N/A seen = self.attributes.setdefault("pkg.facet", [])
956N/A for f in val:
956N/A entry = "facet.{0}".format(f)
956N/A if entry not in seen:
956N/A # Prevent duplicates; it's possible a
956N/A # given facet may be valid for more than
956N/A # one unique variant combination that's
956N/A # allowed by current excludes.
956N/A seen.append(f)
956N/A return
956N/A elif keyvalue == "pkg.variant":
956N/A val = action.attrlist("value")
1376N/A if len(val) == 1 and val[0] == "":
1376N/A # No variants.
1376N/A val = []
1376N/A
1376N/A self.attributes[keyvalue] = [
956N/A "variant.{0}".format(v)
2073N/A for v in val
2073N/A ]
956N/A return
956N/A elif keyvalue[:8] == "variant.":
956N/A self.attributes[keyvalue] = action.attrlist("value")
956N/A return
956N/A
956N/A if keyvalue == "fmri":
956N/A # Ancient manifest compatibility.
2026N/A keyvalue = "pkg.fmri"
2026N/A self.attributes[keyvalue] = action.attrs["value"]
2026N/A
2026N/A @staticmethod
2026N/A def search_dict(file_path, excludes, return_line=False,
956N/A log=None):
956N/A """Produces the search dictionary for a specific manifest.
956N/A A dictionary is constructed which maps a tuple of token,
956N/A action type, key, and the value that matched the token to
956N/A the byte offset into the manifest file. file_path is the
1352N/A path to the manifest file. excludes is the variants which
1352N/A should be allowed in this image. return_line is a debugging
956N/A flag which makes the function map the information to the
956N/A string of the line, rather than the byte offset to allow
1507N/A easier debugging."""
1507N/A
956N/A if log is None:
956N/A log = lambda x: None
2073N/A
2073N/A try:
2073N/A file_handle = open(file_path, "rb")
2073N/A except EnvironmentError as e:
2073N/A if e.errno != errno.ENOENT:
2073N/A raise
2073N/A log((_("{fp}:\n{e}").format(
2073N/A fp=file_path, e=e)))
1507N/A return {}
2054N/A cur_pos = 0
964N/A line = file_handle.readline()
964N/A action_dict = {}
2054N/A def __handle_list(lst, cp):
964N/A """Translates what actions.generate_indices produces
2073N/A into a dictionary mapping token, action_name, key, and
964N/A the value that should be displayed for matching that
964N/A token to byte offsets into the manifest file.
964N/A
1045N/A The "lst" parameter is the data to be converted.
1045N/A
1045N/A The "cp" parameter is the byte offset into the file
1045N/A for the action which produced lst."""
1045N/A
1045N/A for action_name, subtype, tok, full_value in lst:
1045N/A if action_name == "set":
1045N/A if full_value is None:
1045N/A full_value = tok
1045N/A else:
1045N/A if full_value is None:
1045N/A full_value = subtype
1713N/A if full_value is None:
1505N/A full_value = action_name
1045N/A if isinstance(tok, list):
1352N/A __handle_list([
1352N/A (action_name, subtype, t,
964N/A full_value)
964N/A for t in tok
2608N/A ], cp)
2608N/A else:
2608N/A if (tok, action_name, subtype,
2608N/A full_value) in action_dict:
2608N/A action_dict[(tok, action_name,
2608N/A subtype, full_value)
2608N/A ].append(cp)
2608N/A else:
2608N/A action_dict[(tok, action_name,
2054N/A subtype, full_value)] = [cp]
2284N/A while line:
2284N/A l = line.strip()
2284N/A if l and l[0] != "#":
2284N/A try:
2284N/A action = actions.fromstr(l)
2453N/A except actions.ActionError as e:
2284N/A log((_("{fp}:\n{e}").format(
2284N/A fp=file_path, e=e)))
2284N/A else:
2284N/A if not excludes or \
2284N/A action.include_this(excludes):
2284N/A if "path" in action.attrs:
2284N/A np = action.attrs["path"].lstrip(os.path.sep)
2284N/A action.attrs["path"] = \
2284N/A np
try:
inds = action.generate_indices()
except KeyError as k:
log(_("{fp} contains "
"an action which is"
" missing the "
"expected attribute"
": {at}.\nThe "
"action is:"
"{act}").format(
fp=file_path,
at=k.args[0],
act=l
))
else:
arg = cur_pos
if return_line:
arg = l
__handle_list(inds, arg)
cur_pos = file_handle.tell()
line = file_handle.readline()
file_handle.close()
return action_dict
@staticmethod
def hash_create(mfstcontent):
"""This method takes a string representing the on-disk
manifest content, and returns a hash value."""
# This must be an SHA-1 hash in order to interoperate with
# older clients.
sha_1 = hashlib.sha1()
if isinstance(mfstcontent, six.text_type):
# Byte stream expected, so pass encoded.
sha_1.update(mfstcontent.encode("utf-8"))
else:
sha_1.update(mfstcontent)
return sha_1.hexdigest()
def validate(self, signatures):
"""Verifies whether the signatures for the contents of
the manifest match the specified signature data. Raises
the 'BadManifestSignatures' exception on failure."""
if signatures != self.signatures:
raise apx.BadManifestSignatures(self.fmri)
def store(self, mfst_path):
"""Store the manifest contents to disk."""
t_dir = os.path.dirname(mfst_path)
t_prefix = os.path.basename(mfst_path) + "."
try:
os.makedirs(t_dir, mode=PKG_DIR_MODE)
except EnvironmentError as e:
if e.errno == errno.EACCES:
raise apx.PermissionsException(e.filename)
if e.errno == errno.EROFS:
raise apx.ReadOnlyFileSystemException(
e.filename)
if e.errno != errno.EEXIST:
raise
try:
fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
except EnvironmentError as e:
if e.errno == errno.EACCES:
raise apx.PermissionsException(e.filename)
if e.errno == errno.EROFS:
raise apx.ReadOnlyFileSystemException(
e.filename)
raise
mfile = os.fdopen(fd, "wb")
#
# We specifically avoid sorting manifests before writing
# them to disk-- there's really no point in doing so, since
# we'll sort actions globally during packaging operations.
#
mfile.write(self.tostr_unsorted())
mfile.close()
try:
os.chmod(fn, PKG_FILE_MODE)
portable.rename(fn, mfst_path)
except EnvironmentError as e:
if e.errno == errno.EACCES:
raise apx.PermissionsException(e.filename)
if e.errno == errno.EROFS:
raise apx.ReadOnlyFileSystemException(
e.filename)
raise
def get_variants(self, name):
if name not in self.attributes:
return None
variants = self.attributes[name]
if not isinstance(variants, str):
return variants
return [variants]
def get_all_variants(self):
"""Return a dictionary mapping variant tags to their values."""
return variant.VariantCombinationTemplate(dict((
(name, self.attributes[name])
for name in self.attributes
if name.startswith("variant.")
)))
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def getbool(self, key, default):
"""Returns the boolean of the value of the attribute 'key'."""
ret = self.get(key, default).lower()
if ret == "true":
return True
elif ret == "false":
return False
else:
raise ValueError(_("Attribute value '{0}' not 'true' or "
"'false'".format(ret)))
def get_size(self, excludes=EmptyI):
"""Returns an integer tuple of the form (size, csize), where
'size' represents the total uncompressed size, in bytes, of the
Manifest's data payload, and 'csize' represents the compressed
version of that.
'excludes' is a list of a list of variants and facets which
should be allowed when calculating the total."""
if self.excludes == excludes:
excludes = EmptyI
assert excludes == EmptyI or self.excludes == EmptyI
csize = 0
size = 0
attrs = self.attributes
if ("pkg.size" in attrs and "pkg.csize" in attrs) and \
(excludes == EmptyI or self.excludes == excludes):
# If specified excludes match loaded excludes, then use
# cached attributes; this is safe as manifest attributes
# are reset or updated every time exclude_content,
# set_content, or add_action is called.
return (attrs["pkg.size"], attrs["pkg.csize"])
for a in self.gen_actions(excludes=excludes):
size += a.get_size()
csize += misc.get_pkg_otw_size(a)
if excludes == EmptyI:
# Cache for future calls.
attrs["pkg.size"] = size
attrs["pkg.csize"] = csize
return (size, csize)
def _get_varcets(self, excludes=EmptyI):
"""Private helper function to get list of facets/variants."""
variants = defaultdict(set)
facets = defaultdict(set)
nexcludes = excludes
if nexcludes:
# Facet filtering should never be applied when excluding
# actions; only variant filtering. This is ugly, but
# our current variant/facet filtering system doesn't
# allow you to be selective and various bits in
# pkg.manifest assume you always filter on both so we
# have to fake up a filter for facets.
nexcludes = [
x for x in excludes
if x.__func__ != facet._allow_facet
]
# Excludes list must always have zero or 2+ items; so
# fake second entry.
nexcludes.append(lambda x, publisher: True)
assert len(nexcludes) > 1
for action in self.gen_actions():
# append any variants and facets to manifest dict
attrs = action.attrs
v_list, f_list = action.get_varcet_keys()
if not (v_list or f_list):
continue
try:
for v, d in zip(v_list, repeat(variants)):
d[v].add(attrs[v])
if not excludes or action.include_this(
nexcludes, publisher=self.publisher):
# While variants are package level (you
# can't install a package without
# setting the variant first), facets
# from the current action should only be
# included if the action is not
# excluded.
for v, d in zip(f_list, repeat(facets)):
d[v].add(attrs[v])
except TypeError:
# Lists can't be set elements.
raise actions.InvalidActionError(action,
_("{forv} '{v}' specified multiple times").format(
forv=v.split(".", 1)[0], v=v))
return (variants, facets)
def __getitem__(self, key):
"""Return the value for the package attribute 'key'."""
return self.attributes[key]
def __setitem__(self, key, value):
"""Set the value for the package attribute 'key' to 'value'."""
self.attributes[key] = value
for a in self.actions:
if a.name == "set" and a.attrs["name"] == key:
a.attrs["value"] = value
return
new_attr = AttributeAction(None, name=key, value=value)
self.actions.append(new_attr)
self.actions_bytype.setdefault("set", []).append(new_attr)
def __contains__(self, key):
return key in self.attributes
null = Manifest()
class FactoredManifest(Manifest):
"""This class serves as a wrapper for the Manifest class for callers
that need efficient access to package data on a per-action type basis.
It achieves this by partitioning the manifest into multiple files (one
per action type) and then storing an on-disk cache of the directories
explictly and implicitly referenced by the manifest each tagged with
the appropriate variants/facets."""
def __init__(self, fmri, cache_root, contents=None, excludes=EmptyI,
pathname=None):
"""Raises KeyError exception if factored manifest is not present
and contents are None; delays reading of manifest until required
if cache file is present.
'fmri' is a PkgFmri object representing the identity of the
package.
'cache_root' is the pathname of the directory where the manifest
and cache files should be stored or loaded from.
'contents' is an optional string to use as the contents of the
manifest if a cached copy does not already exist.
'excludes' is optional. If provided it must be a length two
list with the variants to be excluded as the first element and
the facets to be exclduded as the second element.
'pathname' is an optional string containing the pathname of a
manifest. If not provided, it is assumed that the manifest is
stored in a file named 'manifest' in the directory indicated by
'cache_root'. If provided, and contents is also provided, then
'contents' will be stored in 'pathname' if it does not already
exist.
"""
Manifest.__init__(self, fmri)
self.__cache_root = cache_root
self.__pathname = pathname
# Make sure that either no excludes were provided or 2+ excludes
# were.
assert len(self.excludes) != 1
self.loaded = False
# Do we have a cached copy?
if not os.path.exists(self.pathname):
if contents is None:
raise KeyError(fmri)
# we have no cached copy; save one
# don't specify excludes so on-disk copy has
# all variants
self.set_content(content=contents)
self.__finiload()
if self.__storeback():
self.__unload()
if excludes:
self.exclude_content(excludes)
return
# we have a cached copy of the manifest
mdpath = self.__cache_path("manifest.dircache")
# have we computed the dircache?
if not os.path.exists(mdpath): # we're adding cache
self.excludes = EmptyI # to existing manifest
self.__load()
if self.__storeback():
self.__unload()
if excludes:
self.excludes = excludes
self.__load()
return
self.exclude_content(excludes)
def __cache_path(self, name):
return os.path.join(self.__cache_root, name)
def __load(self):
"""Load all manifest contents from on-disk copy of manifest"""
self.set_content(excludes=self.excludes, pathname=self.pathname)
self.__finiload()
def __unload(self):
"""Unload manifest; used to reduce peak memory comsumption
when downloading new manifests"""
self.actions = []
self.actions_bytype = {}
self.attributes = {}
self.loaded = False
def __finiload(self):
"""Finish loading.... this part of initialization is common
to multiple code paths"""
self.loaded = True
def __storeback(self):
""" store the current action set; also create per-type
caches. Return True if data was saved, False if not"""
assert self.loaded
try:
self.store(self.pathname)
self.__storebytype()
return True
except apx.PermissionsException:
# this allows us to try to cache new manifests
# when non-root w/o failures.
return False
def __storebytype(self):
""" create manifest.<typename> files to accelerate partial
parsing of manifests. Separate from __storeback code to
allow upgrade to reuse existing on disk manifests"""
assert self.loaded
t_dir = self.__cache_root
# Ensure target cache directory and intermediates exist.
misc.makedirs(t_dir)
# create per-action type cache; use rename to avoid corrupt
# files if ^C'd in the middle. All action types are considered
# so that empty cache files are created if no action of that
# type exists for the package (avoids full manifest loads
# later).
for n, acts in six.iteritems(self.actions_bytype):
t_prefix = "manifest.{0}.".format(n)
try:
fd, fn = tempfile.mkstemp(dir=t_dir,
prefix=t_prefix)
except EnvironmentError as e:
raise apx._convert_error(e)
f = os.fdopen(fd, "wb")
try:
for a in acts:
f.write("{0}\n".format(a))
if n == "set":
# Add supplemental action data; yes this
# does mean the cache is not the same as
# retrieved manifest, but that's ok.
# Signature verification is done using
# the raw manifest.
f.writelines(self._gen_attrs_to_str())
except EnvironmentError as e:
raise apx._convert_error(e)
finally:
f.close()
try:
os.chmod(fn, PKG_FILE_MODE)
portable.rename(fn,
self.__cache_path("manifest.{0}".format(n)))
except EnvironmentError as e:
raise apx._convert_error(e)
def create_cache(name, refs):
try:
fd, fn = tempfile.mkstemp(dir=t_dir,
prefix=name + ".")
with os.fdopen(fd, "wb") as f:
f.writelines(refs())
os.chmod(fn, PKG_FILE_MODE)
portable.rename(fn, self.__cache_path(name))
except EnvironmentError as e:
raise apx._convert_error(e)
create_cache("manifest.dircache", self._gen_dirs_to_str)
create_cache("manifest.mediatorcache",
self._gen_mediators_to_str)
@staticmethod
def clear_cache(cache_root):
"""Remove all manifest cache files found in the given directory
(excluding the manifest itself) and the cache_root if it is
empty afterwards.
"""
try:
for cname in os.listdir(cache_root):
if not cname.startswith("manifest."):
continue
try:
portable.remove(os.path.join(
cache_root, cname))
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
# Ensure cache dir is removed if the last cache file is
# removed; we don't care if it fails.
try:
os.rmdir(cache_root)
except:
pass
except EnvironmentError as e:
if e.errno != errno.ENOENT:
# Only raise error if failure wasn't due to
# cache directory not existing.
raise apx._convert_error(e)
def __load_cached_data(self, name):
"""Private helper function for loading arbitrary cached manifest
data.
"""
mpath = self.__cache_path(name)
if os.path.exists(mpath):
# we have cached copy on disk; use it
try:
with open(mpath, "rb") as f:
self._cache[name] = [
a for a in
(
actions.fromstr(s.rstrip())
for s in f
)
if not self.excludes or
a.include_this(self.excludes,
publisher=self.publisher)
]
return
except EnvironmentError as e:
raise apx._convert_error(e)
except actions.ActionError as e:
# Cache file is malformed; hopefully due to bugs
# that have been resolved (as opposed to actual
# corruption). Assume we should just ignore the
# cache and load action data.
try:
self.clear_cache(self.__cache_root)
except Exception as e:
# Ignore errors encountered during cache
# dump for this specific case.
pass
# no cached copy
if not self.loaded:
# need to load from disk
self.__load()
assert self.loaded
def get_directories(self, excludes):
""" return a list of directories implicitly or explicitly
referenced by this object
"""
self.__load_cached_data("manifest.dircache")
return Manifest.get_directories(self, excludes)
def gen_actions_by_type(self, atype, attr_match=None, excludes=EmptyI):
""" generate actions of the specified type;
use already in-memory stuff if already loaded,
otherwise use per-action types files"""
if self.loaded: #if already loaded, use in-memory cached version
# invoke subclass method to generate action by action
for a in Manifest.gen_actions_by_type(self, atype,
attr_match=attr_match, excludes=excludes):
yield a
return
# This checks if we've already written out the factored
# manifest files. If so, we'll use it, and if not, then
# we'll load the full manifest.
mpath = self.__cache_path("manifest.dircache")
if not os.path.exists(mpath):
# no cached copy :-(
if not self.loaded:
# get manifest from disk
self.__load()
# invoke subclass method to generate action by action
for a in Manifest.gen_actions_by_type(self, atype,
attr_match=attr_match, excludes=excludes):
yield a
return
if excludes == EmptyI:
excludes = self.excludes
assert excludes == self.excludes or self.excludes == EmptyI
if atype in self._absent_cache:
# No such action in the manifest; must be done *after*
# asserting excludes are correct to avoid hiding
# failures.
return
# Assume a cached copy exists; if not, tag the action type to
# avoid pointless I/O later.
mpath = self.__cache_path("manifest.{0}".format(atype))
if attr_match:
attr_match = _compile_fnpats(attr_match)
try:
with open(mpath, "rb") as f:
for l in f:
a = actions.fromstr(l.rstrip())
if (excludes and
not a.include_this(excludes,
publisher=self.publisher)):
continue
# These conditions are split by
# performance.
if not attr_match:
yield a
elif _attr_matches(a, attr_match):
yield a
except EnvironmentError as e:
if e.errno == errno.ENOENT:
self._absent_cache.append(atype)
return # no such action in this manifest
raise apx._convert_error(e)
def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
"""A generator function that returns the supported facet
attributes (strings) for this package based on the specified (or
current) excludes that also match at least one of the patterns
provided. Facets must be true or false so a list of possible
facet values is not returned."""
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.gen_facets(self, excludes=excludes,
patterns=patterns)
def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
"""A generator function that yields a list of tuples of the form
(variant, [values]). Where 'variant' is the variant attribute
name (e.g. 'variant.arch') and '[values]' is a list of the
variant values supported by this package. Variants returned are
those allowed by the specified (or current) excludes that also
match at least one of the patterns provided."""
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.gen_variants(self, excludes=excludes,
patterns=patterns)
def gen_mediators(self, excludes=EmptyI):
"""A generator function that yields set actions expressing the
set of possible mediations for this package.
"""
self.__load_cached_data("manifest.mediatorcache")
return Manifest.gen_mediators(self, excludes=excludes)
def __load_attributes(self):
"""Load attributes dictionary from cached set actions;
this speeds up pkg info a lot"""
mpath = self.__cache_path("manifest.set")
if not os.path.exists(mpath):
return False
with open(mpath, "rb") as f:
for l in f:
a = actions.fromstr(l.rstrip())
if not self.excludes or \
a.include_this(self.excludes,
publisher=self.publisher):
self.fill_attributes(a)
return True
def get_size(self, excludes=EmptyI):
"""Returns an integer tuple of the form (size, csize), where
'size' represents the total uncompressed size, in bytes, of the
Manifest's data payload, and 'csize' represents the compressed
version of that.
'excludes' is a list of a list of variants and facets which
should be allowed when calculating the total."""
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.get_size(self, excludes=excludes)
def __getitem__(self, key):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.__getitem__(self, key)
def __setitem__(self, key, value):
"""No assignments to factored manifests allowed."""
assert "FactoredManifests are not dicts"
def __contains__(self, key):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.__contains__(self, key)
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def get_variants(self, name):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.get_variants(self, name)
def get_all_variants(self):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.get_all_variants(self)
@staticmethod
def search_dict(cache_path, excludes, return_line=False):
return Manifest.search_dict(cache_path, excludes,
return_line=return_line)
def gen_actions(self, attr_match=None, excludes=EmptyI):
if not self.loaded:
self.__load()
return Manifest.gen_actions(self, attr_match=attr_match,
excludes=excludes)
def __str__(self, excludes=EmptyI):
if not self.loaded:
self.__load()
return Manifest.__str__(self)
def duplicates(self, excludes=EmptyI):
if not self.loaded:
self.__load()
return Manifest.duplicates(self, excludes=excludes)
def difference(self, origin, origin_exclude=EmptyI,
self_exclude=EmptyI):
if not self.loaded:
self.__load()
return Manifest.difference(self, origin,
origin_exclude=origin_exclude,
self_exclude=self_exclude)
def store(self, mfst_path):
"""Store the manifest contents to disk."""
if not self.loaded:
self.__load()
super(FactoredManifest, self).store(mfst_path)
@property
def pathname(self):
"""The absolute pathname of the file containing the manifest."""
if self.__pathname:
return self.__pathname
return os.path.join(self.__cache_root, "manifest")
class EmptyFactoredManifest(Manifest):
"""Special class for pkgplan's need for a empty manifest;
the regular null manifest doesn't support get_directories
and making the factored manifest code handle this case is
too ugly..."""
def __init__(self):
Manifest.__init__(self)
def difference(self, origin, origin_exclude=EmptyI,
self_exclude=EmptyI):
"""Return three lists of action pairs representing origin and
destination actions. The first list contains the pairs
representing additions, the second list contains the pairs
representing updates, and the third list contains the pairs
representing removals. All three lists are in the order in
which they should be executed."""
# The difference for this case is simply everything in the
# origin has been removed. This is an optimization for
# uninstall.
return ManifestDifference([], [],
[(a, None) for a in origin.gen_actions(excludes=
origin_exclude)])
@staticmethod
def get_directories(excludes):
return []
def exclude_content(self, *args, **kwargs):
# This method is overridden so that self.excludes is never set
# on the singleton NullFactoredManifest.
return
def set_content(self, *args, **kwargs):
raise RuntimeError("Cannot call set_content on an "
"EmptyFactoredManifest")
NullFactoredManifest = EmptyFactoredManifest()
class ManifestError(Exception):
"""Simple Exception class to handle manifest specific errors"""
def __init__(self, duplicates=EmptyI):
self.__duplicates = duplicates
def __str__(self):
ret = []
for d in self.__duplicates:
ret.append("{0}\n{1}\n\n".format(*d))
return "\n".join(ret)