1516N/A#!/usr/bin/python
50N/A#
50N/A# CDDL HEADER START
50N/A#
50N/A# The contents of this file are subject to the terms of the
50N/A# Common Development and Distribution License (the "License").
50N/A# You may not use this file except in compliance with the License.
50N/A#
50N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
50N/A# or http://www.opensolaris.org/os/licensing.
50N/A# See the License for the specific language governing permissions
50N/A# and limitations under the License.
50N/A#
50N/A# When distributing Covered Code, include this CDDL HEADER in each
50N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
50N/A# If applicable, add the following below this CDDL HEADER, with the
50N/A# fields enclosed by brackets "[]" replaced with your own identifying
50N/A# information: Portions Copyright [yyyy] [name of copyright owner]
50N/A#
50N/A# CDDL HEADER END
50N/A#
50N/A
2026N/A#
3402N/A# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
2026N/A#
50N/A
2627N/Aimport copy
3159N/Aimport grp
202N/Aimport itertools
2690N/Aimport os
3159N/Aimport pwd
3234N/Aimport six
3159N/Aimport stat
1431N/A
1755N/Aimport pkg.actions
1352N/Aimport pkg.actions.directory as directory
1618N/Aimport pkg.client.api_errors as apx
2690N/Aimport pkg.fmri
72N/Aimport pkg.manifest as manifest
2690N/Aimport pkg.misc
2690N/A
3194N/Afrom functools import reduce
3194N/A
2690N/Afrom pkg.client import global_settings
1431N/Afrom pkg.misc import expanddirs, get_pkg_otw_size, EmptyI
50N/A
2690N/Alogger = global_settings.logger
1618N/A
50N/Aclass PkgPlan(object):
50N/A """A package plan takes two package FMRIs and an Image, and produces the
50N/A set of actions required to take the Image from the origin FMRI to the
66N/A destination FMRI.
135N/A
66N/A If the destination FMRI is None, the package is removed.
66N/A """
50N/A
2205N/A __slots__ = [
2627N/A "__destination_mfst",
2690N/A "_executed",
2690N/A "_license_status",
2627N/A "__origin_mfst",
2627N/A "__repair_actions",
3415N/A "__salvage_actions",
2627N/A "__xferfiles",
2627N/A "__xfersize",
2627N/A "_autofix_pkgs",
2627N/A "_hash",
2627N/A "actions",
2627N/A "destination_fmri",
2627N/A "image",
2627N/A "origin_fmri",
2627N/A "pkg_summary",
2205N/A ]
1846N/A
2690N/A #
2690N/A # we don't serialize __xferfiles or __xfersize since those should be
2690N/A # recalculated after after a plan is loaded (since the contents of the
2690N/A # download cache may have changed).
2690N/A #
2690N/A # we don't serialize __origin_mfst, __destination_mfst, or
2690N/A # __repair_actions since we only support serializing pkgplans which
2690N/A # have had their actions evaluated and merged, and when action
2690N/A # evaluation is complete these fields are cleared.
2690N/A #
2690N/A # we don't serialize our image object pointer. that has to be reset
2690N/A # after this object is reloaded.
2690N/A #
2690N/A __state__noserialize = frozenset([
2690N/A "__destination_mfst",
2690N/A "__origin_mfst",
2690N/A "__repair_actions",
3415N/A "__salvage_actions",
2690N/A "__xferfiles",
2690N/A "__xfersize",
2690N/A "image",
2690N/A ])
2690N/A
2690N/A # make sure all __state__noserialize values are valid
2690N/A assert (__state__noserialize - set(__slots__)) == set()
2690N/A
2690N/A # figure out which state we are saving.
2690N/A __state__serialize = set(__slots__) - __state__noserialize
2690N/A
2690N/A # describe our state and the types of all objects
2690N/A __state__desc = {
2690N/A "_autofix_pkgs": [ pkg.fmri.PkgFmri ],
2690N/A "_license_status": {
3234N/A six.string_types[0]: {
2690N/A "src": pkg.actions.generic.NSG,
2690N/A "dest": pkg.actions.generic.NSG,
2690N/A },
2690N/A },
2690N/A "actions": pkg.manifest.ManifestDifference,
2690N/A "destination_fmri": pkg.fmri.PkgFmri,
2690N/A "origin_fmri": pkg.fmri.PkgFmri,
2690N/A }
2690N/A
2690N/A __state__commonize = frozenset([
2690N/A pkg.fmri.PkgFmri,
2690N/A ])
2690N/A
2627N/A def __init__(self, image=None):
50N/A self.destination_fmri = None
2054N/A self.__destination_mfst = manifest.NullFactoredManifest
50N/A
1846N/A self.origin_fmri = None
2054N/A self.__origin_mfst = manifest.NullFactoredManifest
1846N/A
1846N/A self.actions = manifest.ManifestDifference([], [], [])
50N/A self.image = image
1846N/A self.pkg_summary = None
235N/A
2690N/A self._executed = False
2690N/A self._license_status = {}
2453N/A self.__repair_actions = {}
3415N/A self.__salvage_actions = {}
1846N/A self.__xferfiles = -1
1846N/A self.__xfersize = -1
2205N/A self._autofix_pkgs = []
2690N/A self._hash = None
2690N/A
2690N/A @staticmethod
2690N/A def getstate(obj, je_state=None):
2690N/A """Returns the serialized state of this object in a format
2690N/A that that can be easily stored using JSON, pickle, etc."""
2690N/A
2690N/A # validate unserialized state
2690N/A # (see comments above __state__noserialize)
2690N/A assert obj.__origin_mfst == manifest.NullFactoredManifest
2690N/A assert obj.__destination_mfst == manifest.NullFactoredManifest
2690N/A assert obj.__repair_actions == {}
2690N/A
2690N/A # we use __slots__, so create a state dictionary
2690N/A state = {}
2690N/A for k in obj.__state__serialize:
2690N/A state[k] = getattr(obj, k)
2690N/A
2690N/A return pkg.misc.json_encode(PkgPlan.__name__, state,
2690N/A PkgPlan.__state__desc,
2690N/A commonize=PkgPlan.__state__commonize, je_state=je_state)
2690N/A
2690N/A @staticmethod
2690N/A def setstate(obj, state, jd_state=None):
2690N/A """Update the state of this object using previously serialized
2690N/A state obtained via getstate()."""
2690N/A
2690N/A # get the name of the object we're dealing with
2690N/A name = type(obj).__name__
2690N/A
2690N/A # decode serialized state into python objects
2690N/A state = pkg.misc.json_decode(name, state,
2690N/A PkgPlan.__state__desc,
2690N/A commonize=PkgPlan.__state__commonize,
2690N/A jd_state=jd_state)
2690N/A
2690N/A # we use __slots__, so directly update attributes
2690N/A for k in state:
2690N/A setattr(obj, k, state[k])
2690N/A
2690N/A # update unserialized state
2690N/A # (see comments above __state__noserialize)
2690N/A obj.__origin_mfst = manifest.NullFactoredManifest
2690N/A obj.__destination_mfst = manifest.NullFactoredManifest
2690N/A obj.__repair_actions = {}
2690N/A obj.__xferfiles = -1
2690N/A obj.__xfersize = -1
2690N/A obj.image = None
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Allocate a new object using previously serialized state
2690N/A obtained via getstate()."""
2690N/A rv = PkgPlan()
2690N/A PkgPlan.setstate(rv, state, jd_state)
2690N/A return rv
580N/A
63N/A def __str__(self):
3158N/A s = "{0} -> {1}\n".format(self.origin_fmri,
3158N/A self.destination_fmri)
202N/A for src, dest in itertools.chain(*self.actions):
3158N/A s += " {0} -> {1}\n".format(src, dest)
72N/A return s
63N/A
1618N/A def __add_license(self, src, dest):
1618N/A """Adds a license status entry for the given src and dest
1618N/A license actions.
1618N/A
1618N/A 'src' should be None or the source action for a license.
1618N/A
1618N/A 'dest' must be the destination action for a license."""
1618N/A
2690N/A self._license_status[dest.attrs["license"]] = {
1618N/A "src": src,
1618N/A "dest": dest,
1618N/A "accepted": False,
1618N/A "displayed": False,
1618N/A }
1618N/A
1505N/A def propose(self, of, om, df, dm):
1505N/A """Propose origin and dest fmri, manifest"""
1505N/A self.origin_fmri = of
1505N/A self.__origin_mfst = om
1505N/A self.destination_fmri = df
1505N/A self.__destination_mfst = dm
1271N/A
3159N/A def __get_orig_act(self, dest):
3159N/A """Generate the on-disk state (attributes) of the action
3159N/A that fail verification."""
3159N/A
3159N/A if not dest.has_payload or "path" not in dest.attrs:
3159N/A return
3159N/A
3159N/A path = os.path.join(self.image.root, dest.attrs["path"])
3159N/A try:
3159N/A pstat = os.lstat(path)
3159N/A except Exception:
3159N/A # If file to repair isn't on-disk, treat as install
3159N/A return
3159N/A
3159N/A act = pkg.actions.fromstr(str(dest))
3159N/A act.attrs["mode"] = oct(stat.S_IMODE(pstat.st_mode))
3159N/A try:
3159N/A owner = pwd.getpwuid(pstat.st_uid).pw_name
3159N/A group = grp.getgrgid(pstat.st_gid).gr_name
3159N/A except KeyError:
3159N/A # If associated user / group can't be determined, treat
3159N/A # as install. This is not optimal for repairs, but
3159N/A # ensures proper ownership of file is set.
3159N/A return
3159N/A act.attrs["owner"] = owner
3159N/A act.attrs["group"] = group
3159N/A
3159N/A # No need to generate hash of on-disk content as verify
3159N/A # short-circuits hash comparison by setting replace_required
3159N/A # flag on action. The same is true for preserved files which
3159N/A # will automatically handle content replacement if needed based
3159N/A # on the result of _check_preserve.
3159N/A return act
3159N/A
2453N/A def propose_repair(self, fmri, mfst, install, remove, autofix=False):
1505N/A self.propose(fmri, mfst, fmri, mfst)
1505N/A # self.origin_fmri = None
1505N/A # I'd like a cleaner solution than this; we need to actually
2453N/A # construct a list of actions as things currently are rather
2453N/A # than just re-applying the current set of actions.
1505N/A #
583N/A # Create a list of (src, dst) pairs for the actions to send to
2453N/A # execute_repair.
1271N/A
2205N/A if autofix:
3159N/A # If an uninstall causes a fixup to happen, we can't
3159N/A # generate an on-disk state action because the result
3159N/A # of needsdata is different between propose and execute.
3159N/A # Therefore, we explicitly assign None to src for actions
3159N/A # to be installed.
3159N/A self.__repair_actions = {
3159N/A # src is none for repairs
3159N/A "install": [(None, x) for x in install],
3159N/A # dest is none for removals.
3159N/A "remove": [(x, None) for x in remove],
3159N/A }
2205N/A self._autofix_pkgs.append(fmri)
3159N/A else:
3159N/A self.__repair_actions = {
3159N/A # src can be None or an action representing on-disk state
3159N/A "install": [(self.__get_orig_act(x), x) for x in install],
3159N/A "remove": [(x, None) for x in remove],
3159N/A }
2205N/A
50N/A def get_actions(self):
235N/A raise NotImplementedError()
235N/A
235N/A def get_nactions(self):
1713N/A return len(self.actions.added) + len(self.actions.changed) + \
1713N/A len(self.actions.removed)
50N/A
307N/A def update_pkg_set(self, fmri_set):
307N/A """ updates a set of installed fmris to reflect
307N/A proposed new state"""
307N/A
307N/A if self.origin_fmri:
307N/A fmri_set.discard(self.origin_fmri)
307N/A
307N/A if self.destination_fmri:
307N/A fmri_set.add(self.destination_fmri)
1271N/A
2608N/A def evaluate(self, old_excludes=EmptyI, new_excludes=EmptyI,
2608N/A can_exclude=False):
66N/A """Determine the actions required to transition the package."""
50N/A
2026N/A # If new actions are being installed, check the destination
2026N/A # manifest for signatures.
2026N/A if self.destination_fmri is not None:
2026N/A try:
2261N/A dest_pub = self.image.get_publisher(
2261N/A prefix=self.destination_fmri.publisher)
2261N/A except apx.UnknownPublisher:
2261N/A # Since user removed publisher, assume this is
2261N/A # the same as if they had set signature-policy
2261N/A # ignore for the publisher.
2261N/A sig_pol = None
2261N/A else:
2261N/A sig_pol = self.image.signature_policy.combine(
2261N/A dest_pub.signature_policy)
2261N/A
2797N/A if self.destination_fmri in self._autofix_pkgs:
2797N/A # Repaired packages use a manifest synthesized
2797N/A # from the installed one; so retrieve the
2797N/A # installed one for our signature checks.
2797N/A sigman = self.image.get_manifest(
2797N/A self.destination_fmri,
2797N/A ignore_excludes=True)
2797N/A else:
2797N/A sigman = self.__destination_mfst
2797N/A
2797N/A sigs = list(sigman.gen_actions_by_type("signature",
3041N/A excludes=new_excludes))
2261N/A if sig_pol and (sigs or sig_pol.name != "ignore"):
2261N/A # Only perform signature verification logic if
2261N/A # there are signatures or if signature-policy
2261N/A # is not 'ignore'.
2261N/A
2261N/A try:
2261N/A sig_pol.process_signatures(sigs,
2797N/A sigman.gen_actions(),
2458N/A dest_pub, self.image.trust_anchors,
2458N/A self.image.cfg.get_policy(
2458N/A "check-certificate-revocation"))
3171N/A except apx.SigningException as e:
2261N/A e.pfmri = self.destination_fmri
2610N/A if isinstance(e, apx.BrokenChain):
2610N/A e.ext_exs.extend(
2610N/A self.image.bad_trust_anchors
2610N/A )
2261N/A raise
2608N/A if can_exclude:
2608N/A if self.__destination_mfst is not None:
2608N/A self.__destination_mfst.exclude_content(
2608N/A new_excludes)
2608N/A if self.__origin_mfst is not None and \
2608N/A self.__destination_mfst != self.__origin_mfst:
2608N/A self.__origin_mfst.exclude_content(old_excludes)
2608N/A old_excludes = EmptyI
2608N/A new_excludes = EmptyI
2026N/A
1271N/A self.actions = self.__destination_mfst.difference(
3402N/A self.__origin_mfst, old_excludes, new_excludes,
3402N/A pkgplan=self)
50N/A
307N/A # figure out how many implicit directories disappear in this
307N/A # transition and add directory remove actions. These won't
307N/A # do anything unless no pkgs reference that directory in
307N/A # new state....
307N/A
1045N/A # Retrieving origin_dirs first and then checking it for any
1045N/A # entries allows avoiding an unnecessary expanddirs for the
1045N/A # destination manifest when it isn't needed.
1045N/A origin_dirs = expanddirs(self.__origin_mfst.get_directories(
1045N/A old_excludes))
307N/A
2277N/A # Manifest.get_directories() returns implicit directories, which
2277N/A # means that this computation ends up re-adding all the explicit
2277N/A # directories getting removed to the removed list. This is
2277N/A # ugly, but safe.
1045N/A if origin_dirs:
1045N/A absent_dirs = origin_dirs - \
1045N/A expanddirs(self.__destination_mfst.get_directories(
1045N/A new_excludes))
1045N/A
1045N/A for a in absent_dirs:
1713N/A self.actions.removed.append(
2987N/A (directory.DirectoryAction(path=a,
2987N/A implicit="True"), None))
307N/A
400N/A # Stash information needed by legacy actions.
1846N/A self.pkg_summary = \
1174N/A self.__destination_mfst.get("pkg.summary",
1174N/A self.__destination_mfst.get("description", "none provided"))
400N/A
2453N/A # Add any install repair actions to the update list
2453N/A self.actions.changed.extend(self.__repair_actions.get("install",
2453N/A EmptyI))
2453N/A self.actions.removed.extend(self.__repair_actions.get("remove",
2453N/A EmptyI))
2453N/A
2453N/A # No longer needed.
2690N/A self.__repair_actions = {}
583N/A
1618N/A for src, dest in itertools.chain(self.gen_update_actions(),
1618N/A self.gen_install_actions()):
1618N/A if dest.name == "license":
1618N/A self.__add_license(src, dest)
2453N/A if not src:
2453N/A # Initial installs require acceptance.
2117N/A continue
2117N/A src_ma = src.attrs.get("must-accept", False)
2117N/A dest_ma = dest.attrs.get("must-accept", False)
2117N/A if (dest_ma and src_ma) and \
2117N/A src.hash == dest.hash:
2117N/A # If src action required acceptance,
2117N/A # then license was already accepted
2117N/A # before, and if the hashes are the
2117N/A # same for the license payload, then
2117N/A # it doesn't need to be accepted again.
2117N/A self.set_license_status(
2117N/A dest.attrs["license"],
2117N/A accepted=True)
1618N/A
3415N/A # Keep a cache of dir actions with salvage-from attrs.
3415N/A # Since directories are installed in top-down order,
3415N/A # we need this list to make sure we salvage contents
3415N/A # as accurately as possible. For example, where:
3415N/A #
3415N/A # /var/user gets salvaged to /var/.migrate/user
3415N/A # and
3415N/A # /var/user/myuser/.ssh to /var/.migrate/user/myuser/.ssh
3415N/A #
3415N/A # We must ensure that we don't try to salvage
3415N/A # var/user/myuser/.ssh when installing
3415N/A # /var/.migrate/user,
3415N/A # but instead wait till /var/.migrate/user/myuser/.ssh
3415N/A # is being installed, otherwise that content will
3415N/A # the salvaged to the wrong place.
3415N/A if (dest.name == "dir" and
3415N/A "salvage-from" in dest.attrs):
3415N/A for p in dest.attrlist("salvage-from"):
3415N/A self.__salvage_actions[p] = dest
3415N/A
1618N/A def get_licenses(self):
1618N/A """A generator function that yields tuples of the form (license,
1618N/A entry). Where 'entry' is a dict containing the license status
1618N/A information."""
1618N/A
3234N/A for lic, entry in six.iteritems(self._license_status):
1618N/A yield lic, entry
1618N/A
1618N/A def set_license_status(self, plicense, accepted=None, displayed=None):
1618N/A """Sets the license status for the given license entry.
1618N/A
1618N/A 'plicense' should be the value of the license attribute for the
1618N/A destination license action.
1618N/A
1618N/A 'accepted' is an optional parameter that can be one of three
1618N/A values:
1618N/A None leaves accepted status unchanged
1618N/A False sets accepted status to False
1618N/A True sets accepted status to True
1618N/A
1618N/A 'displayed' is an optional parameter that can be one of three
1618N/A values:
1618N/A None leaves displayed status unchanged
1618N/A False sets displayed status to False
1618N/A True sets displayed status to True"""
1618N/A
2690N/A entry = self._license_status[plicense]
1618N/A if accepted is not None:
1618N/A entry["accepted"] = accepted
1618N/A if displayed is not None:
1618N/A entry["displayed"] = displayed
1618N/A
235N/A def get_xferstats(self):
400N/A if self.__xfersize != -1:
400N/A return (self.__xferfiles, self.__xfersize)
235N/A
400N/A self.__xfersize = 0
400N/A self.__xferfiles = 0
235N/A for src, dest in itertools.chain(*self.actions):
1767N/A if dest and dest.needsdata(src, self):
487N/A self.__xfersize += get_pkg_otw_size(dest)
400N/A self.__xferfiles += 1
2286N/A if dest.name == "signature":
2286N/A self.__xfersize += \
2286N/A dest.get_action_chain_csize()
2286N/A self.__xferfiles += \
2286N/A len(dest.attrs.get("chain",
2286N/A "").split())
235N/A
400N/A return (self.__xferfiles, self.__xfersize)
235N/A
2407N/A def get_bytes_added(self):
2407N/A """Return tuple of compressed bytes possibly downloaded
2407N/A and number of bytes laid down; ignore removals
2407N/A because they're usually pinned by snapshots"""
2407N/A def sum_dest_size(a, b):
2407N/A if b[1]:
2446N/A return (a[0] + int(b[1].attrs.get("pkg.csize" ,0)),
2446N/A a[1] + int(b[1].attrs.get("pkg.size", 0)))
2446N/A return (a[0], a[1])
2407N/A
2446N/A return reduce(sum_dest_size, itertools.chain(*self.actions),
2446N/A (0, 0))
2446N/A
2693N/A def get_xferfmri(self):
235N/A if self.destination_fmri:
2693N/A return self.destination_fmri
235N/A if self.origin_fmri:
2693N/A return self.origin_fmri
235N/A return None
289N/A
50N/A def preexecute(self):
388N/A """Perform actions required prior to installation or removal of
388N/A a package.
135N/A
66N/A This method executes each action's preremove() or preinstall()
66N/A methods, as well as any package-wide steps that need to be taken
66N/A at such a time.
66N/A """
111N/A
1618N/A # Determine if license acceptance requirements have been met as
1618N/A # early as possible.
1618N/A errors = []
1618N/A for lic, entry in self.get_licenses():
1618N/A dest = entry["dest"]
1618N/A if (dest.must_accept and not entry["accepted"]) or \
1618N/A (dest.must_display and not entry["displayed"]):
1618N/A errors.append(apx.LicenseAcceptanceError(
1618N/A self.destination_fmri, **entry))
1618N/A
1618N/A if errors:
1618N/A raise apx.PkgLicenseErrors(errors)
1618N/A
202N/A for src, dest in itertools.chain(*self.actions):
72N/A if dest:
136N/A dest.preinstall(self, src)
66N/A else:
136N/A src.preremove(self)
66N/A
2627N/A def download(self, progtrack, check_cancel):
481N/A """Download data for any actions that need it."""
2693N/A progtrack.download_start_pkg(self.get_xferfmri())
1191N/A mfile = self.image.transport.multi_file(self.destination_fmri,
2627N/A progtrack, check_cancel)
1191N/A
1448N/A if mfile is None:
2693N/A progtrack.download_end_pkg(self.get_xferfmri())
1191N/A return
1191N/A
481N/A for src, dest in itertools.chain(*self.actions):
1767N/A if dest and dest.needsdata(src, self):
1191N/A mfile.add_action(dest)
481N/A
1618N/A mfile.wait_files()
2693N/A progtrack.download_end_pkg(self.get_xferfmri())
235N/A
2690N/A def cacheload(self):
2690N/A """Load previously downloaded data for actions that need it."""
2690N/A
2690N/A fmri = self.destination_fmri
2690N/A for src, dest in itertools.chain(*self.actions):
2690N/A if not dest or not dest.needsdata(src, self):
2690N/A continue
2690N/A dest.data = self.image.transport.action_cached(fmri,
2690N/A dest)
2690N/A
237N/A def gen_install_actions(self):
1713N/A for src, dest in self.actions.added:
237N/A yield src, dest
289N/A
237N/A def gen_removal_actions(self):
1713N/A for src, dest in self.actions.removed:
237N/A yield src, dest
289N/A
237N/A def gen_update_actions(self):
1713N/A for src, dest in self.actions.changed:
237N/A yield src, dest
237N/A
237N/A def execute_install(self, src, dest):
237N/A """ perform action for installation of package"""
2690N/A self._executed = True
237N/A try:
237N/A dest.install(self, src)
2293N/A except (pkg.actions.ActionError, EnvironmentError):
1755N/A # Don't log these as they're expected, and should be
1755N/A # handled by the caller.
1755N/A raise
3171N/A except Exception as e:
3158N/A logger.error("Action install failed for '{0}' ({1}):\n "
3158N/A "{2}: {3}".format(dest.attrs.get(dest.key_attr,
3158N/A id(dest)), self.destination_fmri.get_pkg_stem(),
3158N/A e.__class__.__name__, e))
237N/A raise
202N/A
237N/A def execute_update(self, src, dest):
1618N/A """ handle action updates"""
2690N/A self._executed = True
237N/A try:
237N/A dest.install(self, src)
2293N/A except (pkg.actions.ActionError, EnvironmentError):
1755N/A # Don't log these as they're expected, and should be
1755N/A # handled by the caller.
1755N/A raise
3171N/A except Exception as e:
3158N/A logger.error("Action upgrade failed for '{0}' ({1}):\n "
3158N/A "{2}: {3}".format(dest.attrs.get(dest.key_attr,
3158N/A id(dest)), self.destination_fmri.get_pkg_stem(),
3158N/A e.__class__.__name__, e))
237N/A raise
59N/A
237N/A def execute_removal(self, src, dest):
237N/A """ handle action removals"""
2690N/A self._executed = True
237N/A try:
237N/A src.remove(self)
2293N/A except (pkg.actions.ActionError, EnvironmentError):
1755N/A # Don't log these as they're expected, and should be
1755N/A # handled by the caller.
1755N/A raise
3171N/A except Exception as e:
3158N/A logger.error("Action removal failed for '{0}' ({1}):\n "
3158N/A "{2}: {3}".format(src.attrs.get(src.key_attr,
3158N/A id(src)), self.origin_fmri.get_pkg_stem(),
3158N/A e.__class__.__name__, e))
237N/A raise
289N/A
3255N/A def execute_retry(self, src, dest):
3255N/A """handle a retry operation"""
3255N/A dest.retry(self, dest)
3255N/A
66N/A def postexecute(self):
400N/A """Perform actions required after install or remove of a pkg.
135N/A
66N/A This method executes each action's postremove() or postinstall()
66N/A methods, as well as any package-wide steps that need to be taken
66N/A at such a time.
66N/A """
66N/A # record that package states are consistent
202N/A for src, dest in itertools.chain(*self.actions):
72N/A if dest:
136N/A dest.postinstall(self, src)
66N/A else:
136N/A src.postremove(self)
2293N/A
2293N/A def salvage(self, path):
2293N/A """Used to save unexpected files or directories found during
2293N/A plan execution. Salvaged items are tracked in the imageplan.
2293N/A """
2293N/A
2690N/A assert self._executed
2470N/A spath = self.image.salvage(path)
3121N/A # get just the file path that was salvaged
3121N/A fpath = path.replace(
3121N/A os.path.normpath(self.image.get_root()), "", 1)
3121N/A if fpath.startswith(os.path.sep):
3121N/A fpath = fpath[1:]
2690N/A self.image.imageplan.pd._salvaged.append((fpath, spath))
2470N/A
2470N/A def salvage_from(self, local_path, full_destination):
2470N/A """move unpackaged contents to specified destination"""
2470N/A # remove leading / if present
2470N/A if local_path.startswith(os.path.sep):
2470N/A local_path = local_path[1:]
2470N/A
3415N/A # The salvaged locations known to us are a list of tuples of
3415N/A # the form (old dir, lost+found salvage dir) and stored in
3415N/A # self.image.imageplan.pd._salvaged[:]
3415N/A
3415N/A #
3415N/A # Check if this salvage-from is also the best match for other
3415N/A # possibly previously packaged subdirs of this directory.
3415N/A # E.g. if we stop delivering /var/user/evsuser/.ssh, then the
3415N/A # action that specifies 'salvage-from=var/user' ought to deal
3415N/A # with its files.
3415N/A #
3415N/A # On the other hand, if we package another directory, with
3415N/A # 'salvage-from=/var/user/evsuser', then that should be used
3415N/A # to salvage the .ssh content, not the action that salvages
3415N/A # from /var/user.
3415N/A #
2690N/A for fpath, spath in self.image.imageplan.pd._salvaged[:]:
2470N/A if fpath.startswith(local_path):
3415N/A for other_salvage in self.__salvage_actions:
3415N/A if fpath.startswith(other_salvage) and \
3415N/A len(other_salvage) > len(local_path):
3415N/A continue
2470N/A
3415N/A self.image.imageplan.pd._salvaged.remove(
3415N/A (fpath, spath))
3415N/A self.image.recover(
3415N/A spath, full_destination,
3415N/A local_path, fpath)
2608N/A
2608N/A @property
2608N/A def destination_manifest(self):
2608N/A return self.__destination_mfst
2608N/A
2608N/A def clear_dest_manifest(self):
2690N/A self.__destination_mfst = manifest.NullFactoredManifest
2608N/A
2608N/A @property
2608N/A def origin_manifest(self):
2608N/A return self.__origin_mfst
2608N/A
2608N/A def clear_origin_manifest(self):
2690N/A self.__origin_mfst = manifest.NullFactoredManifest