manifest.py revision 1500
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
import os
import errno
import tempfile
import sha
"""A Manifest is the representation of the actions composing a specific
package version on both the client and the repository. Both purposes
utilize the same storage format.
The serialized structure of a manifest is an unordered list of actions.
The special action, "set", represents a package attribute.
The reserved attribute, "fmri", represents the package and version
described by this manifest. It is available as a string via the
attributes dictionary, and as an FMRI object from the fmri member.
The list of manifest-wide reserved attributes is
base_directory Default base directory, for non-user images.
fmri Package FMRI.
isa Package is intended for a list of ISAs.
platform Package is intended for a list of platforms.
relocatable Suitable for User Image.
All non-prefixed attributes are reserved to the framework. Third
parties may prefix their attributes with a reversed domain name, domain
name, or stock symbol. An example might be
com.example,supported
as an indicator that a specific package version is supported by the
vendor, example.com.
manifest.null is provided as the null manifest. Differences against the
null manifest result in the complete set of attributes and actions of
the non-null manifest, meaning that all operations can be viewed as
tranitions between the manifest being installed and the manifest already
present in the image (which may be the null manifest).
"""
self.actions_bytype = {}
r = ""
r += "%s\n" % act
return r
"""A generator function that returns the unsorted manifest
contents as lines of text."""
yield "%s\n" % act
def tostr_unsorted(self):
"""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."""
# XXX Do we need to find some way to assert that the keys are
# all unique?
# No origin was provided, so nothing has been changed or
# removed; only added. In addition, this doesn't need
# to be sorted since the caller likely already does
return (
[], [])
)
)
# XXX for now, we force license actions to always be
# different to insure that existing license files for
# new versions are always installed
changed = [
]
# XXX Do changed actions need to be sorted at all? This is
# likely to be the largest list, so we might save significant
# time by not sorting. Should we sort above? Insert into a
# sorted list?
# singlesort = lambda x: x[0] or x[1]
addsort = lambda x: x[1]
remsort = lambda x: x[0]
"""Like the unix utility comm, except that this function
takes an arbitrary number of manifests and compares them,
returning a tuple consisting of each manifest's actions
that are not the same for all manifests, followed by a
list of actions that are the same in each manifest."""
# construct list of dictionaries of actions in each
# manifest, indexed by unique keys
m_dicts = [
dict(
for a in m.actions)
for m in compare_m
]
# construct list of key sets in each dict
#
m_sets = [
for m in m_dicts
]
# determine which common_keys have common actions
for k in common_keys.copy():
m_dicts[i + 1][k]):
break
return tuple(
[
]
+
[
]
)
"""Where difference() returns three lists, combined_difference()
returns a single list of the concatenation of the three."""
"""Output expects that self is newer than other. Use of sets
requires that we convert the action objects into some marshalled
form, otherwise set member identities are derived from the
object pointers, rather than the contents."""
out = ""
if not src:
elif not dest:
else:
return out
"""Generate actions in manifest through ordered callable list"""
for c in excludes:
if not c(a):
break
else:
yield a
"""Generate actions in the manifest of type "type"
through ordered callable list"""
for c in excludes:
if not c(a):
break
else:
yield a
"""Generate the value of the key atrribute for each action
of type "type" in the manifest."""
return (
)
"""Find actions in the manifest which are duplicates (i.e.,
represent the same object) but which are not identical (i.e.,
have all the same attributes)."""
def fun(a):
"""Return a key on which actions can be sorted."""
alldups = []
if dups:
return alldups
accumulate = ""
for l in content.splitlines():
l = l.lstrip()
continue
elif accumulate:
l = accumulate + l
accumulate = ""
if not l or l[0] == "#": # ignore blank lines & comments
continue
try:
except actions.ActionError, e:
# Add the FMRI to the exception and re-raise
raise
"""content is the text representation of the manifest"""
self.actions_bytype = {}
self.attributes = {}
# sdict and odict in difference() above, and have that be our
# main datastore, rather than the simple list we have now. If
# we do that here, we can even assert that the "same" action
# can't be in a manifest twice. (The problem of having the same
# action more than once in packages that can be installed
# together has to be solved somewhere else, though.)
if signatures:
# Generate manifest signature based upon input
# content, but only if signatures were
# requested.
self.signatures = {
}
return
"""Performs any needed transformations on the action then adds
it to the manifest.
The "action" parameter is the action object that should be
added to the manifest.
The "excludes" parameter is the variants to exclude from the
manifest."""
# XXX handle legacy transition issues; not needed after
# 2009.06 release & republication are complete.
# Translate old action to new.
return
# add any set actions to attributes
# append any variants and facets to manifest dict
if v not in d:
else:
return
"""Fill attribute array w/ set action contents."""
try:
if keyvalue == "fmri":
keyvalue = "pkg.fmri"
except KeyError: # ignore broken set actions
pass
log=None):
"""Produces the search dictionary for a specific manifest.
A dictionary is constructed which maps a tuple of token,
action type, key, and the value that matched the token to
the byte offset into the manifest file. file_path is the
path to the manifest file. excludes is the variants which
should be allowed in this image. return_line is a debugging
flag which makes the function map the information to the
string of the line, rather than the byte offset to allow
easier debugging."""
if log is None:
log = lambda x: None
cur_pos = 0
action_dict = {}
"""Translates what actions.generate_indices produces
into a dictionary mapping token, action_name, key, and
the value that should be displayed for matching that
token to byte offsets into the manifest file.
The "lst" parameter is the data to be converted.
The "cp" parameter is the byte offset into the file
for the action which produced lst."""
if action_name == "set":
if full_value is None:
else:
if full_value is None:
if full_value is None:
(action_name, subtype, t,
for t in tok
], cp)
else:
full_value) in action_dict:
else:
while line:
if l and l[0] != "#":
try:
except actions.ActionError, e:
log((_("%(fp)s:\n%(e)s") %
else:
try:
except KeyError, k:
log(_("%(fp)s contains "
"an action which is"
" missing the "
"expected attribute"
": %(at)s.\nThe "
"action is:"
"%(act)s") %
{
"fp": file_path,
"act":l
})
else:
if return_line:
arg = l
return action_dict
def hash_create(mfstcontent):
"""This method takes a string representing the on-disk
manifest content, and returns a hash value."""
"""Verifies whether the signatures for the contents of
the manifest match the specified signature data. Raises
the 'BadManifestSignatures' exception on failure."""
"""Store the manifest contents to disk."""
#
# 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.
#
return None
return variants
return [variants]
def get_all_variants(self):
"""Return a dictionary mapping variant tags to their values."""
)))
try:
except KeyError:
return default
"""Returns the boolean of the value of the attribute 'key'."""
if ret == "true":
return True
elif ret == "false":
return False
else:
raise ValueError(_("Attribute value '%s' not 'true' or "
"'false'" % ret))
"""Returns an integer representing the total size, in bytes, of
the Manifest's data payload.
'excludes' is a list of variants which should be allowed when
calculating the total.
"""
size = 0
return size
"""Return the value for the package attribute 'key'."""
"""Set the value for the package attribute 'key' to 'value'."""
return
class CachedManifest(Manifest):
"""This class handles a cache of manifests for the client;
it partitions the manifest into multiple files (one per
action type) and also builds an on-disk cache of the
directories explictly and implicitly referenced by the
def __file_dir(self):
contents=None):
"""Raises KeyError exception if cached manifest
is not present and contents are None; delays
reading of manifest until required if cache file
is present"""
# Do we have a cached copy?
if not contents:
# we have no cached copy; save one
# don't specify excludes so on-disk copy has
# all variants
if self.__storeback():
elif excludes:
return
# we have a cached copy of the manifest
# have we computed the dircache?
if self.__storeback():
elif excludes:
"""Load all manifest contents from on-disk copy of manifest"""
f.close()
"""Unload manifest; used to reduce peak memory comsumption
when downloading new manifests"""
self.actions_bytype = {}
self.attributes = {}
def __finiload(self):
"""Finish loading.... this part of initialization is common
to multiple code paths"""
def __storeback(self):
""" store the current action set; also create per-type
caches. Return True if data was saved, False if not"""
try:
return True
except EnvironmentError, e:
# this allows us to try to cache new manifests
# when non-root w/o failures
raise
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"""
# create per-action type cache; use rename to avoid
# corrupt files if ^C'd in the middle
t_prefix = "manifest.%s." % n
for a in self.actions_bytype[n]:
f.write("%s\n" % a)
f.close()
# create dircache
prefix="manifest.dircache.")
f.write(s)
f.close()
def __gen_dirs_to_str(dirs):
""" from a dictionary of paths, generate contents of dircache
file"""
for d in dirs:
for v in dirs[d]:
yield "dir path=%s %s\n" % \
for t in v.iteritems()))
def __actions_to_dirs(self):
""" create dictionary of all directories referenced
by actions explicitly or implicitly from self.actions...
include variants as values; collapse variants where possible"""
dirs = {}
# build a dictionary containing all directories tagged w/
# variants
v, f = a.get_varcet_keys()
for d in expanddirs(a.directory_references()):
if d not in dirs:
# remove any tags if any entries are always installed (NULL)
for d in dirs:
if {} in dirs[d]:
dirs[d] = [{}]
continue
# could collapse dirs where all variants are present
return dirs
""" return a list of directories implicitly or
explicitly referenced by this object"""
# no cached copy
# need to load from disk
# generate actions that contain directories
alist = [
for s in self.__gen_dirs_to_str(
]
else:
# we have cached copy on disk; use it
f.close()
s = set([
a.attrs["path"]
for a in alist
if a.include_this(excludes)
])
return list(s)
""" generate actions of the specified type;
use already in-memory stuff if already loaded,
otherwise use per-action types files"""
# invoke subclass method to generate action by action
excludes):
yield a
return
# no cached copy :-(
# get manifest from disk
# invoke subclass method to generate action by action
excludes):
yield a
else:
# we have a cached copy - use it
return # no such action in this manifest
for l in f:
if a.include_this(excludes):
yield a
f.close()
def __load_attributes(self):
"""Load attributes dictionary from cached set actions;
this speeds up pkg info a lot"""
return False
for l in f:
f.close()
return True
"""No assignments to cached manifests allowed."""
assert "CachedManifests are not dicts"
try:
except KeyError:
return default
def get_all_variants(self):
class EmptyCachedManifest(Manifest):
"""Special class for pkgplan's need for a empty manifest;
the regular null manifest doesn't support get_directories
and making the cached manifest code handle this case is
too ugly..."""
"""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 ([], [],
def get_directories(excludes):
return []