1 #!/usr/bin/python2.4
   2 #
   3 # CDDL HEADER START
   4 #
   5 # The contents of this file are subject to the terms of the
   6 # Common Development and Distribution License (the "License").
   7 # You may not use this file except in compliance with the License.
   8 #
   9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10 # or http://www.opensolaris.org/os/licensing.
  11 # See the License for the specific language governing permissions
  12 # and limitations under the License.
  13 #
  14 # When distributing Covered Code, include this CDDL HEADER in each
  15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16 # If applicable, add the following below this CDDL HEADER, with the
  17 # fields enclosed by brackets "[]" replaced with your own identifying
  18 # information: Portions Copyright [yyyy] [name of copyright owner]
  19 #
  20 # CDDL HEADER END
  21 #
  22 
  23 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  24 # Use is subject to license terms.
  25 
  26 import errno
  27 import itertools
  28 import os
  29 
  30 import pkg.manifest as manifest
  31 import pkg.client.filelist as filelist
  32 import pkg.actions.directory as directory
  33 from pkg.misc import msg
  34 from pkg.misc import get_pkg_otw_size
  35 
  36 class PkgPlan(object):
  37         """A package plan takes two package FMRIs and an Image, and produces the
  38         set of actions required to take the Image from the origin FMRI to the
  39         destination FMRI.
  40 
  41         If the destination FMRI is None, the package is removed.
  42         """
  43 
  44         def __init__(self, image, progtrack):
  45                 self.origin_fmri = None
  46                 self.destination_fmri = None
  47                 self.actions = []
  48 
  49                 self.__origin_mfst = manifest.null
  50                 self.__destination_mfst = manifest.null
  51                 self.__legacy_info = {}
  52 
  53                 self.image = image
  54                 self.__progtrack = progtrack
  55 
  56                 self.__xfersize = -1
  57                 self.__xferfiles = -1
  58 
  59                 self.__destination_filters = []
  60 
  61         def __str__(self):
  62                 s = "%s -> %s\n" % (self.origin_fmri, self.destination_fmri)
  63 
  64                 for src, dest in itertools.chain(*self.actions):
  65                         s += "  %s -> %s\n" % (src, dest)
  66 
  67                 return s
  68 
  69         def propose_destination(self, fmri, mfst):
  70                 self.destination_fmri = fmri
  71                 self.__destination_mfst = mfst
  72                 self.__legacy_info["version"] = self.destination_fmri.version
  73 
  74                 if self.image.install_file_present(fmri):
  75                         raise RuntimeError, "already installed"
  76 
  77         def propose_removal(self, fmri, mfst):
  78                 self.origin_fmri = fmri
  79                 self.__origin_mfst = mfst
  80 
  81                 if not self.image.install_file_present(fmri):
  82                         raise RuntimeError, "not installed"
  83 
  84         def get_actions(self):
  85                 raise NotImplementedError()
  86 
  87         def get_nactions(self):
  88                 return len(self.actions[0]) + len(self.actions[1]) + \
  89                     len(self.actions[2])
  90 
  91         def update_pkg_set(self, fmri_set):
  92                 """ updates a set of installed fmris to reflect
  93                 proposed new state"""
  94 
  95                 if self.origin_fmri:
  96                         fmri_set.discard(self.origin_fmri)
  97 
  98                 if self.destination_fmri:
  99                         fmri_set.add(self.destination_fmri)
 100                         
 101         def evaluate(self, filters = []):
 102                 """Determine the actions required to transition the package."""
 103                 # if origin unset, determine if we're dealing with an previously
 104                 # installed version or if we're dealing with the null package
 105                 #
 106                 # XXX Perhaps make the pkgplan creator make this explicit, so we
 107                 # don't have to check?
 108                 f = None
 109                 if not self.origin_fmri:
 110                         f = self.image.older_version_installed(
 111                             self.destination_fmri)
 112                         if f:
 113                                 self.origin_fmri = f
 114                                 self.__origin_mfst = \
 115                                     self.image.get_manifest(f)
 116 
 117                 self.__destination_filters = filters
 118 
 119                 # Try to load the filter used for the last install of the
 120                 # package.
 121                 origin_filters = []
 122                 if self.origin_fmri:
 123                         try:
 124                                 f = file("%s/pkg/%s/filters" % \
 125                                     (self.image.imgdir,
 126                                     self.origin_fmri.get_dir_path()), "r")
 127                         except EnvironmentError, e:
 128                                 if e.errno != errno.ENOENT:
 129                                         raise
 130                         else:
 131                                 origin_filters = [
 132                                     (l.strip(), compile(
 133                                         l.strip(), "<filter string>", "eval"))
 134                                     for l in f.readlines()
 135                                 ]
 136 
 137                 self.__destination_mfst.filter(self.__destination_filters)
 138                 self.__origin_mfst.filter(origin_filters)
 139 
 140                 # Assume that origin actions are unique, but make sure that
 141                 # destination ones are.
 142                 ddups = self.__destination_mfst.duplicates()
 143                 if ddups:
 144                         raise RuntimeError, ["Duplicate actions", ddups]
 145 
 146                 self.actions = self.__destination_mfst.difference(
 147                     self.__origin_mfst)
 148 
 149                 # figure out how many implicit directories disappear in this
 150                 # transition and add directory remove actions.  These won't
 151                 # do anything unless no pkgs reference that directory in
 152                 # new state....
 153 
 154                 tmpset = set()
 155 
 156                 for a in self.__origin_mfst.actions:
 157                         tmpset.update(a.directory_references())
 158 
 159                 absent_dirs = self.image.expanddirs(tmpset)
 160 
 161                 tmpset = set()
 162 
 163                 for a in self.__destination_mfst.actions:
 164                         tmpset.update(a.directory_references())
 165 
 166                 absent_dirs.difference_update(self.image.expanddirs(tmpset))
 167 
 168                 for a in absent_dirs:
 169                         self.actions[2].append(
 170                             [directory.DirectoryAction(path=a), None])
 171 
 172                 # Stash information needed by legacy actions.
 173                 self.__legacy_info["description"] = \
 174                     self.__destination_mfst.get("description", "none provided")
 175 
 176                 #
 177                 # We cross a point of no return here, and throw away the origin
 178                 # and destination manifests.  This is really important for
 179                 # memory footprint.
 180                 #
 181                 self.__origin_mfst = None
 182                 self.__destination_mfst = None
 183 
 184         def get_legacy_info(self):
 185                 """ Returns information needed by the legacy action to
 186                     populate the SVR4 packaging info. """
 187                 return self.__legacy_info
 188 
 189         def get_xferstats(self):
 190                 if self.__xfersize != -1:
 191                         return (self.__xferfiles, self.__xfersize)
 192 
 193                 self.__xfersize = 0
 194                 self.__xferfiles = 0
 195                 for src, dest in itertools.chain(*self.actions):
 196                         if dest and dest.needsdata(src):
 197                                 self.__xfersize += get_pkg_otw_size(dest)
 198                                 self.__xferfiles += 1
 199 
 200                 return (self.__xferfiles, self.__xfersize)
 201 
 202         def will_xfer(self):
 203                 nf, nb = self.get_xferstats()
 204                 if nf > 0:
 205                         return True
 206                 else:
 207                         return False
 208 
 209         def get_xfername(self):
 210                 if self.destination_fmri:
 211                         return self.destination_fmri.get_name()
 212                 if self.origin_fmri:
 213                         return self.origin_fmri.get_name()
 214                 return None
 215 
 216         def preexecute(self):
 217                 """Perform actions required prior to installation or removal of
 218                 a package.
 219 
 220                 This method executes each action's preremove() or preinstall()
 221                 methods, as well as any package-wide steps that need to be taken
 222                 at such a time.
 223                 """
 224 
 225                 for src, dest in itertools.chain(*self.actions):
 226                         if dest:
 227                                 dest.preinstall(self, src)
 228                         else:
 229                                 src.preremove(self)
 230 
 231         def download(self):
 232                 """Download data for any actions that need it."""
 233                 flist = filelist.FileList(self.image, self.destination_fmri,
 234                     self.__progtrack)
 235                 self.__progtrack.download_start_pkg(self.get_xfername())
 236                 for src, dest in itertools.chain(*self.actions):
 237                         if dest:
 238                                 if dest.needsdata(src):
 239                                         flist.add_action(dest)
 240 
 241                 flist.flush()
 242                 self.__progtrack.download_end_pkg()
 243 
 244         def gen_install_actions(self):
 245                 for src, dest in self.actions[0]:
 246                         yield src, dest
 247 
 248         def gen_removal_actions(self):
 249                 for src, dest in self.actions[2]:
 250                         yield src, dest
 251 
 252         def gen_update_actions(self):
 253                 for src, dest in self.actions[1]:
 254                         yield src, dest
 255 
 256         def execute_install(self, src, dest):
 257                 """ perform action for installation of package"""
 258                 try:
 259                         dest.install(self, src)
 260                 except Exception, e:
 261                         msg("Action install failed for '%s' (%s):\n  %s: %s" % \
 262                             (dest.attrs.get(dest.key_attr, id(dest)),
 263                              self.destination_fmri.get_pkg_stem(),
 264                              e.__class__.__name__, e))
 265                         raise
 266 
 267         def execute_update(self, src, dest):
 268                 """ handle action updates"""
 269                 try:
 270                         dest.install(self, src)
 271                 except Exception, e:
 272                         msg("Action upgrade failed for '%s' (%s):\n %s: %s" % \
 273                              (dest.attrs.get(dest.key_attr, id(dest)),
 274                              self.destination_fmri.get_pkg_stem(),
 275                              e.__class__.__name__, e))
 276                         raise
 277 
 278         def execute_removal(self, src, dest):
 279                 """ handle action removals"""
 280                 try:
 281                         src.remove(self)
 282                 except Exception, e:
 283                         msg("Action removal failed for '%s' (%s):\n  %s: %s" % \
 284                             (src.attrs.get(src.key_attr, id(src)),
 285                              self.origin_fmri.get_pkg_stem(),
 286                              e.__class__.__name__, e))
 287                         raise
 288 
 289         def postexecute(self):
 290                 """Perform actions required after install or remove of a pkg.
 291 
 292                 This method executes each action's postremove() or postinstall()
 293                 methods, as well as any package-wide steps that need to be taken
 294                 at such a time.
 295                 """
 296                 # record that package states are consistent
 297                 for src, dest in itertools.chain(*self.actions):
 298                         if dest:
 299                                 dest.postinstall(self, src)
 300                         else:
 301                                 src.postremove(self)
 302 
 303                 # For an uninstall or an upgrade, remove the installation 
 304                 # turds from the origin's directory.
 305                 # XXX should this just go in preexecute?
 306                 if self.destination_fmri == None or self.origin_fmri != None:
 307                         self.image.remove_install_file(self.origin_fmri)
 308 
 309                         try:
 310                                 os.unlink("%s/pkg/%s/filters" % (
 311                                     self.image.imgdir,
 312                                     self.origin_fmri.get_dir_path()))
 313                         except EnvironmentError, e:
 314                                 if e.errno != errno.ENOENT:
 315                                         raise
 316 
 317                 if self.destination_fmri != None:
 318                         self.image.add_install_file(self.destination_fmri)
 319 
 320                         # Save the filters we used to install the package, so
 321                         # they can be referenced later.
 322                         if self.__destination_filters:
 323                                 f = file("%s/pkg/%s/filters" % \
 324                                     (self.image.imgdir,
 325                                     self.destination_fmri.get_dir_path()), "w")
 326 
 327                                 f.writelines([
 328                                     myfilter + "\n"
 329                                     for myfilter, code in \
 330                                         self.__destination_filters
 331                                 ])
 332                                 f.close()
 333