manifest.py revision 2238
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#
2238N/A# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
926N/A#
39N/A
1713N/Afrom collections import namedtuple
2026N/Aimport copy
342N/Aimport errno
1516N/Aimport hashlib
1636N/Aimport os
1386N/Aimport tempfile
838N/Afrom itertools import groupby, chain, repeat
39N/A
51N/Aimport pkg.actions as actions
2073N/Aimport pkg.client.api_errors as apx
2144N/Aimport pkg.misc as misc
1066N/Aimport pkg.portable as portable
1231N/Aimport pkg.variant as variant
1352N/A
1890N/Afrom pkg.misc import EmptyDict, EmptyI, expanddirs, PKG_FILE_MODE, PKG_DIR_MODE
296N/Afrom pkg.actions.attribute import AttributeAction
39N/A
1713N/AManifestDifference = namedtuple("ManifestDifference", "added changed removed")
1713N/A
39N/Aclass Manifest(object):
39N/A """A Manifest is the representation of the actions composing a specific
39N/A package version on both the client and the repository. Both purposes
39N/A utilize the same storage format.
39N/A
205N/A The serialized structure of a manifest is an unordered list of actions.
39N/A
205N/A The special action, "set", represents a package attribute.
39N/A
39N/A The reserved attribute, "fmri", represents the package and version
39N/A described by this manifest. It is available as a string via the
39N/A attributes dictionary, and as an FMRI object from the fmri member.
39N/A
39N/A The list of manifest-wide reserved attributes is
39N/A
39N/A base_directory Default base directory, for non-user images.
39N/A fmri Package FMRI.
39N/A isa Package is intended for a list of ISAs.
39N/A platform Package is intended for a list of platforms.
39N/A relocatable Suitable for User Image.
39N/A
39N/A All non-prefixed attributes are reserved to the framework. Third
39N/A parties may prefix their attributes with a reversed domain name, domain
39N/A name, or stock symbol. An example might be
39N/A
39N/A com.example,supported
39N/A
39N/A as an indicator that a specific package version is supported by the
39N/A vendor, example.com.
39N/A
48N/A manifest.null is provided as the null manifest. Differences against the
48N/A null manifest result in the complete set of attributes and actions of
48N/A the non-null manifest, meaning that all operations can be viewed as
48N/A tranitions between the manifest being installed and the manifest already
48N/A present in the image (which may be the null manifest).
59N/A """
48N/A
2073N/A def __init__(self, pfmri=None):
2073N/A self.fmri = pfmri
39N/A
39N/A self.actions = []
567N/A self.actions_bytype = {}
838N/A self.variants = {} # variants seen in package
838N/A self.facets = {} # facets seen in package
838N/A self.attributes = {} # package-wide attributes
1890N/A self.signatures = EmptyDict
39N/A
39N/A def __str__(self):
39N/A r = ""
1431N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
1431N/A r += "set name=pkg.fmri value=%s\n" % self.fmri
39N/A
227N/A for act in sorted(self.actions):
315N/A r += "%s\n" % act
315N/A return r
39N/A
1352N/A def as_lines(self):
1352N/A """A generator function that returns the unsorted manifest
1352N/A contents as lines of text."""
1352N/A
1431N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
1431N/A yield "set name=pkg.fmri value=%s\n" % self.fmri
429N/A
315N/A for act in self.actions:
1352N/A yield "%s\n" % act
429N/A
1352N/A def tostr_unsorted(self):
1352N/A return "".join((l for l in self.as_lines()))
39N/A
926N/A def difference(self, origin, origin_exclude=EmptyI,
926N/A self_exclude=EmptyI):
203N/A """Return three lists of action pairs representing origin and
203N/A destination actions. The first list contains the pairs
203N/A representing additions, the second list contains the pairs
203N/A representing updates, and the third list contains the pairs
1045N/A representing removals. All three lists are in the order in
1045N/A which they should be executed."""
72N/A # XXX Do we need to find some way to assert that the keys are
72N/A # all unique?
59N/A
2054N/A if isinstance(origin, EmptyFactoredManifest):
1045N/A # No origin was provided, so nothing has been changed or
1045N/A # removed; only added. In addition, this doesn't need
1045N/A # to be sorted since the caller likely already does
1045N/A # (such as pkgplan/imageplan).
1713N/A return ManifestDifference(
1045N/A [(None, a) for a in self.gen_actions(self_exclude)],
1045N/A [], [])
1045N/A
2084N/A def hashify(v):
2084N/A """handle key values that may be lists"""
2084N/A if isinstance(v, list):
2084N/A return frozenset(v)
2084N/A else:
2084N/A return v
2084N/A
72N/A sdict = dict(
2084N/A ((a.name, hashify(a.attrs.get(a.key_attr, id(a)))), a)
838N/A for a in self.gen_actions(self_exclude)
72N/A )
72N/A odict = dict(
2084N/A ((a.name, hashify(a.attrs.get(a.key_attr, id(a)))), a)
838N/A for a in origin.gen_actions(origin_exclude)
72N/A )
59N/A
72N/A sset = set(sdict.keys())
72N/A oset = set(odict.keys())
59N/A
72N/A added = [(None, sdict[i]) for i in sset - oset]
72N/A removed = [(odict[i], None) for i in oset - sset]
72N/A changed = [
72N/A (odict[i], sdict[i])
72N/A for i in oset & sset
2144N/A if odict[i].different(sdict[i])
72N/A ]
59N/A
72N/A # XXX Do changed actions need to be sorted at all? This is
72N/A # likely to be the largest list, so we might save significant
72N/A # time by not sorting. Should we sort above? Insert into a
72N/A # sorted list?
59N/A
72N/A # singlesort = lambda x: x[0] or x[1]
72N/A addsort = lambda x: x[1]
72N/A remsort = lambda x: x[0]
202N/A removed.sort(key = remsort, reverse = True)
72N/A added.sort(key = addsort)
72N/A changed.sort(key = addsort)
59N/A
1713N/A return ManifestDifference(added, changed, removed)
59N/A
838N/A @staticmethod
838N/A def comm(*compare_m):
838N/A """Like the unix utility comm, except that this function
838N/A takes an arbitrary number of manifests and compares them,
838N/A returning a tuple consisting of each manifest's actions
926N/A that are not the same for all manifests, followed by a
838N/A list of actions that are the same in each manifest."""
838N/A
838N/A # construct list of dictionaries of actions in each
838N/A # manifest, indexed by unique keys
926N/A m_dicts = [
838N/A dict(
926N/A ((a.name, a.attrs.get(a.key_attr, id(a))), a)
926N/A for a in m.actions)
926N/A for m in compare_m
838N/A ]
838N/A # construct list of key sets in each dict
838N/A #
838N/A m_sets = [
838N/A set(m.keys())
838N/A for m in m_dicts
838N/A ]
838N/A
838N/A common_keys = reduce(lambda a, b: a & b, m_sets)
926N/A
838N/A # determine which common_keys have common actions
838N/A for k in common_keys.copy():
838N/A for i in range(len(m_dicts) - 1):
838N/A if m_dicts[i][k].different(
838N/A m_dicts[i + 1][k]):
838N/A common_keys.remove(k)
838N/A break
845N/A return tuple(
845N/A [
926N/A [m_dicts[i][k] for k in m_sets[i] - common_keys]
838N/A for i in range(len(m_dicts))
845N/A ]
845N/A +
845N/A [
838N/A [ m_dicts[0][k] for k in common_keys ]
845N/A ]
845N/A )
838N/A
838N/A
926N/A def combined_difference(self, origin, ov=EmptyI, sv=EmptyI):
203N/A """Where difference() returns three lists, combined_difference()
315N/A returns a single list of the concatenation of the three."""
838N/A return list(chain(*self.difference(origin, ov, sv)))
203N/A
838N/A def humanized_differences(self, other, ov=EmptyI, sv=EmptyI):
48N/A """Output expects that self is newer than other. Use of sets
48N/A requires that we convert the action objects into some marshalled
48N/A form, otherwise set member identities are derived from the
48N/A object pointers, rather than the contents."""
48N/A
838N/A l = self.difference(other, ov, sv)
203N/A out = ""
48N/A
203N/A for src, dest in chain(*l):
72N/A if not src:
181N/A out += "+ %s\n" % str(dest)
72N/A elif not dest:
181N/A out += "- %s\n" + str(src)
46N/A else:
181N/A out += "%s -> %s\n" % (src, dest)
237N/A return out
46N/A
838N/A def gen_actions(self, excludes=EmptyI):
838N/A """Generate actions in manifest through ordered callable list"""
838N/A for a in self.actions:
838N/A for c in excludes:
838N/A if not c(a):
838N/A break
838N/A else:
838N/A yield a
342N/A
926N/A def gen_actions_by_type(self, atype, excludes=EmptyI):
838N/A """Generate actions in the manifest of type "type"
838N/A through ordered callable list"""
926N/A for a in self.actions_bytype.get(atype, []):
838N/A for c in excludes:
838N/A if not c(a):
838N/A break
838N/A else:
838N/A yield a
926N/A
926N/A def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
615N/A """Generate the value of the key atrribute for each action
615N/A of type "type" in the manifest."""
615N/A
615N/A return (
615N/A a.attrs.get(a.key_attr)
926N/A for a in self.gen_actions_by_type(atype, excludes)
615N/A )
615N/A
838N/A def duplicates(self, excludes=EmptyI):
111N/A """Find actions in the manifest which are duplicates (i.e.,
111N/A represent the same object) but which are not identical (i.e.,
111N/A have all the same attributes)."""
111N/A
111N/A def fun(a):
111N/A """Return a key on which actions can be sorted."""
111N/A return a.name, a.attrs.get(a.key_attr, id(a))
111N/A
113N/A alldups = []
926N/A acts = [a for a in self.gen_actions(excludes)]
838N/A
926N/A for k, g in groupby(sorted(acts, key=fun), fun):
113N/A glist = list(g)
113N/A dups = set()
113N/A for i in range(len(glist) - 1):
113N/A if glist[i].different(glist[i + 1]):
113N/A dups.add(glist[i])
113N/A dups.add(glist[i + 1])
113N/A if dups:
113N/A alldups.append((k, dups))
113N/A return alldups
111N/A
1500N/A def __content_to_actions(self, content):
1432N/A accumulate = ""
1542N/A lineno = 0
1970N/A errors = []
2073N/A
2073N/A if isinstance(content, basestring):
2073N/A # Get an iterable for the string.
2073N/A content = content.splitlines()
2073N/A
2073N/A for l in content:
1542N/A lineno += 1
146N/A l = l.lstrip()
1432N/A if l.endswith("\\"): # allow continuation chars
1432N/A accumulate += l[0:-1] # elide backslash
50N/A continue
1432N/A elif accumulate:
1432N/A l = accumulate + l
1432N/A accumulate = ""
51N/A
1636N/A if not l or l[0] == "#": # ignore blank lines & comments
1432N/A continue
1507N/A
51N/A try:
1500N/A yield actions.fromstr(l)
591N/A except actions.ActionError, e:
1970N/A # Accumulate errors and continue so that as
1970N/A # much of the action data as possible can be
1970N/A # parsed.
591N/A e.fmri = self.fmri
1542N/A e.lineno = lineno
1970N/A errors.append(e)
1970N/A
1970N/A if errors:
2073N/A raise apx.InvalidPackageErrors(errors)
1500N/A
2073N/A def set_content(self, content=None, excludes=EmptyI, pathname=None,
2073N/A signatures=False):
1713N/A """Populate the manifest with actions.
1713N/A
2073N/A 'content' is an optional value containing either the text
2073N/A representation of the manifest or an iterable of
2073N/A action objects.
1713N/A
2073N/A 'excludes' is an optional list of variants to exclude from the
1713N/A manifest.
2073N/A
2073N/A 'pathname' is an optional filename containing the location of
2073N/A the manifest content.
1713N/A
2073N/A 'signatures' is an optional boolean value that indicates whether
2073N/A a manifest signature should be generated. This is only possible
2073N/A when 'content' is a string or 'pathname' is provided.
1713N/A """
1713N/A
2073N/A assert content is not None or pathname is not None
2073N/A assert not (content and pathname)
2073N/A
1500N/A self.actions = []
1500N/A self.actions_bytype = {}
1500N/A self.variants = {}
1500N/A self.facets = {}
1500N/A self.attributes = {}
51N/A
1500N/A # So we could build up here the type/key_attr dictionaries like
1500N/A # sdict and odict in difference() above, and have that be our
1500N/A # main datastore, rather than the simple list we have now. If
1500N/A # we do that here, we can even assert that the "same" action
1500N/A # can't be in a manifest twice. (The problem of having the same
1500N/A # action more than once in packages that can be installed
1500N/A # together has to be solved somewhere else, though.)
2073N/A if pathname:
2073N/A try:
2073N/A with open(pathname, "rb") as mfile:
2073N/A content = mfile.read()
2073N/A except EnvironmentError, e:
2073N/A raise apx._convert_error(e)
1890N/A if isinstance(content, basestring):
1500N/A if signatures:
2073N/A # Generate manifest signature based upon
2073N/A # input content, but only if signatures
2073N/A # were requested.
1500N/A self.signatures = {
1500N/A "sha-1": self.hash_create(content)
1500N/A }
1500N/A content = self.__content_to_actions(content)
926N/A
1500N/A for action in content:
2026N/A self.add_action(action, excludes)
1500N/A
2026N/A def exclude_content(self, excludes):
2026N/A """Remove any actions from the manifest which should be
2026N/A excluded."""
2026N/A
2073N/A self.set_content(content=self.actions, excludes=excludes)
2026N/A
2026N/A def add_action(self, action, excludes):
1500N/A """Performs any needed transformations on the action then adds
1500N/A it to the manifest.
1500N/A
1500N/A The "action" parameter is the action object that should be
1500N/A added to the manifest.
123N/A
1500N/A The "excludes" parameter is the variants to exclude from the
1500N/A manifest."""
877N/A
1500N/A # XXX handle legacy transition issues; not needed after
1500N/A # 2009.06 release & republication are complete.
1500N/A if "opensolaris.zone" in action.attrs and \
1500N/A "variant.opensolaris.zone" not in action.attrs:
1500N/A action.attrs["variant.opensolaris.zone"] = \
1500N/A action.attrs["opensolaris.zone"]
1500N/A
1500N/A if action.name == "set" and action.attrs["name"] == "authority":
1500N/A # Translate old action to new.
1500N/A action.attrs["name"] = "publisher"
1500N/A
1500N/A if action.attrs.has_key("path"):
1500N/A np = action.attrs["path"].lstrip(os.path.sep)
1500N/A action.attrs["path"] = np
39N/A
1500N/A if not action.include_this(excludes):
1500N/A return
1500N/A
1500N/A self.actions.append(action)
1500N/A self.actions_bytype.setdefault(action.name, []).append(action)
838N/A
1500N/A # add any set actions to attributes
1500N/A if action.name == "set":
1500N/A self.fill_attributes(action)
1500N/A # append any variants and facets to manifest dict
1500N/A v_list, f_list = action.get_varcet_keys()
1500N/A
2238N/A if not (v_list or f_list):
2238N/A return
2238N/A
2238N/A try:
1500N/A for v, d in zip(v_list, repeat(self.variants)) \
1500N/A + zip(f_list, repeat(self.facets)):
2238N/A d.setdefault(v, set()).add(action.attrs[v])
2238N/A except TypeError:
2238N/A # Lists can't be set elements.
2238N/A raise actions.InvalidActionError(action,
2238N/A _("%(forv)s '%(v)s' specified multiple times") %
2238N/A {"forv": v.split(".", 1)[0], "v": v})
39N/A
956N/A def fill_attributes(self, action):
956N/A """Fill attribute array w/ set action contents."""
956N/A try:
956N/A keyvalue = action.attrs["name"]
1431N/A if keyvalue == "fmri":
1431N/A keyvalue = "pkg.fmri"
956N/A if keyvalue not in self.attributes:
956N/A self.attributes[keyvalue] = \
956N/A action.attrs["value"]
956N/A except KeyError: # ignore broken set actions
956N/A pass
956N/A
941N/A @staticmethod
1007N/A def search_dict(file_path, excludes, return_line=False,
1007N/A log=None):
1007N/A """Produces the search dictionary for a specific manifest.
1007N/A A dictionary is constructed which maps a tuple of token,
1007N/A action type, key, and the value that matched the token to
1007N/A the byte offset into the manifest file. file_path is the
1007N/A path to the manifest file. excludes is the variants which
1007N/A should be allowed in this image. return_line is a debugging
1007N/A flag which makes the function map the information to the
1007N/A string of the line, rather than the byte offset to allow
1007N/A easier debugging."""
1007N/A
1007N/A if log is None:
1007N/A log = lambda x: None
1100N/A
1407N/A file_handle = file(file_path, "rb")
941N/A cur_pos = 0
941N/A line = file_handle.readline()
144N/A action_dict = {}
941N/A def __handle_list(lst, cp):
1100N/A """Translates what actions.generate_indices produces
1100N/A into a dictionary mapping token, action_name, key, and
1100N/A the value that should be displayed for matching that
1100N/A token to byte offsets into the manifest file.
1100N/A
1100N/A The "lst" parameter is the data to be converted.
1100N/A
1100N/A The "cp" parameter is the byte offset into the file
1100N/A for the action which produced lst."""
1100N/A
941N/A for action_name, subtype, tok, full_value in lst:
941N/A if action_name == "set":
941N/A if full_value is None:
941N/A full_value = tok
941N/A else:
941N/A if full_value is None:
941N/A full_value = subtype
941N/A if full_value is None:
941N/A full_value = action_name
941N/A if isinstance(tok, list):
941N/A __handle_list([
941N/A (action_name, subtype, t,
941N/A full_value)
941N/A for t in tok
941N/A ], cp)
429N/A else:
941N/A if (tok, action_name, subtype,
941N/A full_value) in action_dict:
941N/A action_dict[(tok, action_name,
941N/A subtype, full_value)
941N/A ].append(cp)
429N/A else:
941N/A action_dict[(tok, action_name,
941N/A subtype, full_value)] = [cp]
941N/A while line:
941N/A l = line.strip()
941N/A if l and l[0] != "#":
1007N/A try:
1007N/A action = actions.fromstr(l)
1234N/A except actions.ActionError, e:
1007N/A log((_("%(fp)s:\n%(e)s") %
1007N/A { "fp": file_path, "e": e }))
1007N/A else:
1007N/A if action.include_this(excludes):
1007N/A if action.attrs.has_key("path"):
1007N/A np = action.attrs["path"].lstrip(os.path.sep)
1007N/A action.attrs["path"] = \
1007N/A np
1007N/A try:
1007N/A inds = action.generate_indices()
1007N/A except KeyError, k:
1007N/A log(_("%(fp)s contains "
1007N/A "an action which is"
1007N/A " missing the "
1007N/A "expected attribute"
1007N/A ": %(at)s.\nThe "
1007N/A "action is:"
1007N/A "%(act)s") %
1007N/A {
1007N/A "fp": file_path,
1007N/A "at": k.args[0],
1007N/A "act":l
1007N/A })
1007N/A else:
1007N/A arg = cur_pos
1007N/A if return_line:
1007N/A arg = l
1007N/A __handle_list(inds, arg)
941N/A cur_pos = file_handle.tell()
941N/A line = file_handle.readline()
941N/A file_handle.close()
144N/A return action_dict
144N/A
1472N/A @staticmethod
1472N/A def hash_create(mfstcontent):
1472N/A """This method takes a string representing the on-disk
1472N/A manifest content, and returns a hash value."""
1352N/A
1516N/A sha_1 = hashlib.sha1()
1890N/A if isinstance(mfstcontent, unicode):
1890N/A # Byte stream expected, so pass encoded.
1890N/A sha_1.update(mfstcontent.encode("utf-8"))
1890N/A else:
1890N/A sha_1.update(mfstcontent)
1352N/A
1472N/A return sha_1.hexdigest()
1352N/A
1352N/A def validate(self, signatures):
1352N/A """Verifies whether the signatures for the contents of
1352N/A the manifest match the specified signature data. Raises
1352N/A the 'BadManifestSignatures' exception on failure."""
1352N/A
1352N/A if signatures != self.signatures:
2073N/A raise apx.BadManifestSignatures(self.fmri)
1352N/A
429N/A def store(self, mfst_path):
429N/A """Store the manifest contents to disk."""
144N/A
1386N/A t_dir = os.path.dirname(mfst_path)
1386N/A t_prefix = os.path.basename(mfst_path) + "."
1386N/A
1636N/A try:
1636N/A os.makedirs(t_dir, mode=PKG_DIR_MODE)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2073N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A if e.errno != errno.EEXIST:
1636N/A raise
1386N/A
1636N/A try:
1636N/A fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2073N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A raise
1636N/A
1386N/A mfile = os.fdopen(fd, "wb")
315N/A
315N/A #
315N/A # We specifically avoid sorting manifests before writing
315N/A # them to disk-- there's really no point in doing so, since
315N/A # we'll sort actions globally during packaging operations.
315N/A #
315N/A mfile.write(self.tostr_unsorted())
315N/A mfile.close()
1636N/A
1636N/A try:
1636N/A os.chmod(fn, PKG_FILE_MODE)
1636N/A portable.rename(fn, mfst_path)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2073N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A raise
144N/A
838N/A def get_variants(self, name):
838N/A if name not in self.attributes:
838N/A return None
926N/A variants = self.attributes[name]
926N/A if not isinstance(variants, str):
926N/A return variants
926N/A return [variants]
838N/A
1231N/A def get_all_variants(self):
1231N/A """Return a dictionary mapping variant tags to their values."""
2091N/A return variant.VariantCombinationTemplate(dict((
1231N/A (name, self.attributes[name])
1231N/A for name in self.attributes
1231N/A if name.startswith("variant.")
1231N/A )))
1231N/A
205N/A def get(self, key, default):
257N/A try:
257N/A return self[key]
257N/A except KeyError:
205N/A return default
205N/A
1461N/A def getbool(self, key, default):
1461N/A """Returns the boolean of the value of the attribute 'key'."""
1461N/A
1461N/A ret = self.get(key, default).lower()
1461N/A if ret == "true":
1461N/A return True
1461N/A elif ret == "false":
1461N/A return False
1461N/A else:
1461N/A raise ValueError(_("Attribute value '%s' not 'true' or "
1461N/A "'false'" % ret))
1461N/A
1044N/A def get_size(self, excludes=EmptyI):
1044N/A """Returns an integer representing the total size, in bytes, of
1044N/A the Manifest's data payload.
1044N/A
1044N/A 'excludes' is a list of variants which should be allowed when
1044N/A calculating the total.
1044N/A """
1044N/A
1044N/A size = 0
1044N/A for a in self.gen_actions(excludes):
1044N/A size += int(a.attrs.get("pkg.size", "0"))
1044N/A
1044N/A return size
1044N/A
205N/A def __getitem__(self, key):
838N/A """Return the value for the package attribute 'key'."""
838N/A return self.attributes[key]
205N/A
296N/A def __setitem__(self, key, value):
296N/A """Set the value for the package attribute 'key' to 'value'."""
838N/A self.attributes[key] = value
296N/A for a in self.actions:
296N/A if a.name == "set" and a.attrs["name"] == key:
296N/A a.attrs["value"] = value
296N/A return
296N/A
296N/A new_attr = AttributeAction(None, name=key, value=value)
296N/A self.actions.append(new_attr)
1461N/A self.actions_bytype.setdefault("set", []).append(new_attr)
296N/A
205N/A def __contains__(self, key):
838N/A return key in self.attributes
205N/A
48N/Anull = Manifest()
956N/A
2054N/Aclass FactoredManifest(Manifest):
2054N/A """This class serves as a wrapper for the Manifest class for callers
2054N/A that need efficient access to package data on a per-action type basis.
2054N/A It achieves this by partitioning the manifest into multiple files (one
2054N/A per action type) and then storing an on-disk cache of the directories
2054N/A explictly and implicitly referenced by the manifest each tagged with
2054N/A the appropriate variants/facets."""
956N/A
2073N/A def __init__(self, fmri, cache_root, contents=None, excludes=EmptyI,
2073N/A pathname=None):
2073N/A """Raises KeyError exception if factored manifest is not present
2073N/A and contents are None; delays reading of manifest until required
2073N/A if cache file is present.
1386N/A
2073N/A 'fmri' is a PkgFmri object representing the identity of the
2073N/A package.
2073N/A
2073N/A 'cache_root' is the pathname of the directory where the manifest
2073N/A and cache files should be stored or loaded from.
2073N/A
2073N/A 'contents' is an optional string to use as the contents of the
2073N/A manifest if a cached copy does not already exist.
956N/A
2073N/A 'excludes' is an optional list of excludes to apply to the
2073N/A manifest after loading.
956N/A
2073N/A 'pathname' is an optional string containing the pathname of a
2073N/A manifest. If not provided, it is assumed that the manifest is
2073N/A stored in a file named 'manifest' in the directory indicated by
2144N/A 'cache_root'. If provided, and contents is also provided, then
2144N/A 'contents' will be stored in 'pathname' if it does not already
2144N/A exist.
2073N/A """
2073N/A
2073N/A Manifest.__init__(self, fmri)
2073N/A self.__cache_root = cache_root
2073N/A self.__pathname = pathname
956N/A self.excludes = excludes
2073N/A self.loaded = False
956N/A
956N/A # Do we have a cached copy?
2073N/A if not os.path.exists(self.pathname):
956N/A if not contents:
956N/A raise KeyError, fmri
956N/A # we have no cached copy; save one
956N/A # don't specify excludes so on-disk copy has
956N/A # all variants
2073N/A self.set_content(content=contents)
956N/A self.__finiload()
967N/A if self.__storeback():
967N/A self.__unload()
967N/A elif excludes:
2073N/A self.exclude_content(excludes)
956N/A return
956N/A
956N/A # we have a cached copy of the manifest
2073N/A mdpath = self.__cache_path("manifest.dircache")
1507N/A
956N/A # have we computed the dircache?
956N/A if not os.path.exists(mdpath): # we're adding cache
956N/A self.excludes = EmptyI # to existing manifest
956N/A self.__load()
967N/A if self.__storeback():
967N/A self.__unload()
967N/A elif excludes:
956N/A self.excludes = excludes
956N/A self.__load()
956N/A
2073N/A def __cache_path(self, name):
2073N/A return os.path.join(self.__cache_root, name)
2073N/A
956N/A def __load(self):
956N/A """Load all manifest contents from on-disk copy of manifest"""
2073N/A self.set_content(excludes=self.excludes, pathname=self.pathname)
956N/A self.__finiload()
956N/A
967N/A def __unload(self):
967N/A """Unload manifest; used to reduce peak memory comsumption
967N/A when downloading new manifests"""
967N/A self.actions = []
967N/A self.actions_bytype = {}
1507N/A self.variants = {}
1507N/A self.facets = {}
1507N/A self.attributes = {}
967N/A self.loaded = False
1507N/A
956N/A def __finiload(self):
1507N/A """Finish loading.... this part of initialization is common
956N/A to multiple code paths"""
956N/A self.loaded = True
956N/A
956N/A def __storeback(self):
956N/A """ store the current action set; also create per-type
967N/A caches. Return True if data was saved, False if not"""
956N/A assert self.loaded
956N/A try:
2073N/A self.store(self.pathname)
956N/A self.__storebytype()
967N/A return True
2073N/A except apx.PermissionsException:
956N/A # this allows us to try to cache new manifests
1672N/A # when non-root w/o failures.
967N/A return False
956N/A
956N/A def __storebytype(self):
956N/A """ create manifest.<typename> files to accelerate partial
1507N/A parsing of manifests. Separate from __storeback code to
956N/A allow upgrade to reuse existing on disk manifests"""
956N/A
956N/A assert self.loaded
956N/A
2073N/A t_dir = self.__cache_root
1386N/A
2144N/A # Ensure target cache directory and intermediates exist.
2144N/A misc.makedirs(t_dir)
2144N/A
956N/A # create per-action type cache; use rename to avoid
1507N/A # corrupt files if ^C'd in the middle
956N/A for n in self.actions_bytype.keys():
1386N/A t_prefix = "manifest.%s." % n
1386N/A
1386N/A fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
1386N/A f = os.fdopen(fd, "wb")
1386N/A
956N/A for a in self.actions_bytype[n]:
956N/A f.write("%s\n" % a)
956N/A f.close()
1507N/A os.chmod(fn, PKG_FILE_MODE)
2073N/A portable.rename(fn, self.__cache_path("manifest.%s" % n))
1386N/A
956N/A # create dircache
1386N/A fd, fn = tempfile.mkstemp(dir=t_dir,
1386N/A prefix="manifest.dircache.")
1386N/A f = os.fdopen(fd, "wb")
956N/A dirs = self.__actions_to_dirs()
956N/A
956N/A for s in self.__gen_dirs_to_str(dirs):
956N/A f.write(s)
956N/A
956N/A f.close()
1507N/A os.chmod(fn, PKG_FILE_MODE)
2073N/A portable.rename(fn, self.__cache_path("manifest.dircache"))
956N/A
1352N/A @staticmethod
1352N/A def __gen_dirs_to_str(dirs):
1507N/A """ from a dictionary of paths, generate contents of dircache
956N/A file"""
1507N/A for d in dirs:
956N/A for v in dirs[d]:
956N/A yield "dir path=%s %s\n" % \
956N/A (d, " ".join("%s=%s" % t \
956N/A for t in v.iteritems()))
1507N/A
956N/A def __actions_to_dirs(self):
1507N/A """ create dictionary of all directories referenced
1507N/A by actions explicitly or implicitly from self.actions...
956N/A include variants as values; collapse variants where possible"""
956N/A assert self.loaded
1507N/A
956N/A dirs = {}
1507N/A # build a dictionary containing all directories tagged w/
956N/A # variants
956N/A for a in self.actions:
956N/A v, f = a.get_varcet_keys()
956N/A variants = dict((name, a.attrs[name]) for name in v + f)
1507N/A for d in expanddirs(a.directory_references()):
956N/A if d not in dirs:
956N/A dirs[d] = [variants]
956N/A elif variants not in dirs[d]:
956N/A dirs[d].append(variants)
956N/A
956N/A # remove any tags if any entries are always installed (NULL)
956N/A for d in dirs:
956N/A if {} in dirs[d]:
956N/A dirs[d] = [{}]
956N/A continue
956N/A # could collapse dirs where all variants are present
956N/A return dirs
1507N/A
2035N/A @staticmethod
2073N/A def clear_cache(cache_root):
2073N/A """Remove all manifest cache files found in the given directory
2073N/A (excluding the manifest itself).
2073N/A """
2035N/A
2035N/A try:
2073N/A for cname in os.listdir(cache_root):
2035N/A if not cname.startswith("manifest."):
2035N/A continue
2035N/A try:
2035N/A portable.remove(os.path.join(
2073N/A cache_root, cname))
2035N/A except EnvironmentError, e:
2035N/A if e.errno != errno.ENOENT:
2035N/A raise
2035N/A except EnvironmentError, e:
2149N/A raise apx._convert_error(e)
2035N/A
956N/A def get_directories(self, excludes):
956N/A """ return a list of directories implicitly or
956N/A explicitly referenced by this object"""
1507N/A
2073N/A mpath = self.__cache_path("manifest.dircache")
956N/A
956N/A if not os.path.exists(mpath):
956N/A # no cached copy
956N/A if not self.loaded:
956N/A # need to load from disk
956N/A self.__load()
956N/A # generate actions that contain directories
956N/A alist = [
2205N/A actions.fromstr(s.strip())
2205N/A for s in self.__gen_dirs_to_str(
2205N/A self.__actions_to_dirs())
2205N/A ]
1507N/A else:
956N/A # we have cached copy on disk; use it
956N/A f = file(mpath)
956N/A alist = [actions.fromstr(s.strip()) for s in f]
956N/A f.close()
956N/A s = set([
2205N/A a.attrs["path"]
2205N/A for a in alist
2205N/A if a.include_this(excludes)
2205N/A ])
956N/A return list(s)
956N/A
956N/A def gen_actions_by_type(self, atype, excludes=EmptyI):
956N/A """ generate actions of the specified type;
956N/A use already in-memory stuff if already loaded,
956N/A otherwise use per-action types files"""
956N/A
956N/A if self.loaded: #if already loaded, use in-memory cached version
956N/A # invoke subclass method to generate action by action
1507N/A for a in Manifest.gen_actions_by_type(self, atype,
956N/A excludes):
956N/A yield a
956N/A return
956N/A
2205N/A # This checks if we've already written out the factorerd
2205N/A # manifest files. If so, we'll use it, and if not, then
2205N/A # we'll load the full manifest.
2073N/A mpath = self.__cache_path("manifest.dircache")
956N/A
956N/A if not os.path.exists(mpath):
956N/A # no cached copy :-(
956N/A if not self.loaded:
956N/A # get manifest from disk
956N/A self.__load()
956N/A # invoke subclass method to generate action by action
1507N/A for a in Manifest.gen_actions_by_type(self, atype,
956N/A excludes):
956N/A yield a
956N/A else:
956N/A # we have a cached copy - use it
2073N/A mpath = self.__cache_path("manifest.%s" % atype)
956N/A
956N/A if not os.path.exists(mpath):
956N/A return # no such action in this manifest
956N/A
956N/A f = file(mpath)
956N/A for l in f:
956N/A a = actions.fromstr(l.strip())
956N/A if a.include_this(excludes):
956N/A yield a
956N/A f.close()
956N/A
956N/A def __load_attributes(self):
956N/A """Load attributes dictionary from cached set actions;
956N/A this speeds up pkg info a lot"""
956N/A
2073N/A mpath = self.__cache_path("manifest.set")
956N/A if not os.path.exists(mpath):
956N/A return False
956N/A f = file(mpath)
956N/A for l in f:
956N/A a = actions.fromstr(l.strip())
956N/A if a.include_this(self.excludes):
956N/A self.fill_attributes(a)
956N/A f.close()
1507N/A return True
1507N/A
956N/A def __getitem__(self, key):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.__getitem__(self, key)
1507N/A
956N/A def __setitem__(self, key, value):
2054N/A """No assignments to factored manifests allowed."""
2054N/A assert "FactoredManifests are not dicts"
956N/A
956N/A def __contains__(self, key):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.__contains__(self, key)
956N/A
956N/A def get(self, key, default):
956N/A try:
956N/A return self[key]
956N/A except KeyError:
956N/A return default
956N/A
956N/A def get_variants(self, name):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.get_variants(self, name)
956N/A
1376N/A def get_all_variants(self):
1376N/A if not self.loaded and not self.__load_attributes():
1376N/A self.__load()
1376N/A return Manifest.get_all_variants(self)
1376N/A
956N/A @staticmethod
2073N/A def search_dict(cache_path, excludes, return_line=False):
2073N/A return Manifest.search_dict(cache_path, excludes,
956N/A return_line=return_line)
956N/A
956N/A def gen_actions(self, excludes=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
956N/A return Manifest.gen_actions(self, excludes=excludes)
956N/A
2026N/A def __str__(self, excludes=EmptyI):
2026N/A if not self.loaded:
2026N/A self.__load()
2026N/A return Manifest.__str__(self)
2026N/A
956N/A def duplicates(self, excludes=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
956N/A return Manifest.duplicates(self, excludes=excludes)
956N/A
1352N/A def difference(self, origin, origin_exclude=EmptyI,
1352N/A self_exclude=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
1507N/A return Manifest.difference(self, origin,
1507N/A origin_exclude=origin_exclude,
956N/A self_exclude=self_exclude)
956N/A
2073N/A @property
2073N/A def pathname(self):
2073N/A """The absolute pathname of the file containing the manifest."""
2073N/A
2073N/A if self.__pathname:
2073N/A return self.__pathname
2073N/A return os.path.join(self.__cache_root, "manifest")
2073N/A
1507N/A
2054N/Aclass EmptyFactoredManifest(Manifest):
964N/A """Special class for pkgplan's need for a empty manifest;
964N/A the regular null manifest doesn't support get_directories
2054N/A and making the factored manifest code handle this case is
964N/A too ugly..."""
2073N/A
964N/A def __init__(self):
964N/A Manifest.__init__(self)
964N/A
1045N/A def difference(self, origin, origin_exclude=EmptyI,
1045N/A self_exclude=EmptyI):
1045N/A """Return three lists of action pairs representing origin and
1045N/A destination actions. The first list contains the pairs
1045N/A representing additions, the second list contains the pairs
1045N/A representing updates, and the third list contains the pairs
1045N/A representing removals. All three lists are in the order in
1045N/A which they should be executed."""
1045N/A
1045N/A # The difference for this case is simply everything in the
1045N/A # origin has been removed. This is an optimization for
1045N/A # uninstall.
1713N/A return ManifestDifference([], [],
1505N/A [(a, None) for a in origin.gen_actions(origin_exclude)])
1045N/A
1352N/A @staticmethod
1352N/A def get_directories(excludes):
964N/A return []
964N/A
2054N/ANullFactoredManifest = EmptyFactoredManifest()