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 cPickle
  27 import errno
  28 import os
  29 import socket
  30 import urllib
  31 import urllib2
  32 import httplib
  33 import shutil
  34 import tempfile
  35 import time
  36 import datetime
  37 import calendar
  38 
  39 import OpenSSL.crypto as osc
  40 
  41 from pkg.misc import msg, emsg
  42 
  43 # import uuid           # XXX interesting 2.5 module
  44 
  45 import pkg.catalog as catalog
  46 import pkg.updatelog as updatelog
  47 import pkg.fmri
  48 import pkg.manifest as manifest
  49 import pkg.misc as misc
  50 import pkg.Uuid25
  51 import pkg.version
  52 import pkg.client.history as history
  53 import pkg.client.imageconfig as imageconfig
  54 import pkg.client.imageplan as imageplan
  55 import pkg.client.pkgplan as pkgplan
  56 import pkg.client.imagestate as imagestate
  57 import pkg.client.retrieve as retrieve
  58 import pkg.client.progress as progress
  59 import pkg.portable as portable
  60 import pkg.client.query_engine as query_e
  61 import pkg.client.indexer as indexer
  62 import pkg.search_errors as search_errors
  63 import pkg.client.api_errors as api_errors
  64 
  65 from pkg.misc import versioned_urlopen
  66 from pkg.misc import TransportException
  67 from pkg.misc import TransferTimedOutException
  68 from pkg.misc import TransportFailures
  69 from pkg.misc import CLIENT_DEFAULT_MEM_USE_KB
  70 from pkg.client import global_settings
  71 from pkg.client.imagetypes import *
  72 
  73 img_user_prefix = ".org.opensolaris,pkg"
  74 img_root_prefix = "var/pkg"
  75 
  76 PKG_STATE_INSTALLED = "installed"
  77 PKG_STATE_KNOWN = "known"
  78 
  79 # Minimum number of days to issue warning before a certificate expires
  80 MIN_WARN_DAYS = datetime.timedelta(days=30)
  81 
  82 class Image(object):
  83         """An Image object is a directory tree containing the laid-down contents
  84         of a self-consistent graph of Packages.
  85 
  86         An Image has a root path.
  87 
  88         An Image of type IMG_ENTIRE does not have a parent Image.  Other Image
  89         types must have a parent Image.  The external state of the parent Image
  90         must be accessible from the Image's context, or duplicated within the
  91         Image (IMG_PARTIAL for zones, for instance).
  92 
  93         The parent of a user Image can be a partial Image.  The parent of a
  94         partial Image must be an entire Image.
  95 
  96         An Image of type IMG_USER stores its external state at self.root +
  97         ".org.opensolaris,pkg".
  98 
  99         An Image of type IMG_ENTIRE or IMG_PARTIAL stores its external state at
 100         self.root + "/var/pkg".
 101 
 102         An Image needs to be able to have a different repository set than the
 103         system's root Image.
 104 
 105         Directory layout
 106 
 107           $IROOT/catalog
 108                Directory containing catalogs for URIs of interest.  Filename is
 109                the escaped URI of the catalog.
 110 
 111           $IROOT/file
 112                Directory containing file hashes of installed packages.
 113 
 114           $IROOT/pkg
 115                Directory containing manifests and states of installed packages.
 116 
 117           $IROOT/index
 118                Directory containing reverse-index databases.
 119 
 120           $IROOT/cfg_cache
 121                File containing image's cached configuration.
 122 
 123           $IROOT/opaque
 124                File containing image's opaque state.
 125 
 126           $IROOT/state/installed
 127                Directory containing files whose names identify the installed
 128                packages.
 129 
 130         All of these directories and files other than state are considered
 131         essential for any image to be complete. To add a new essential file or
 132         subdirectory, the following steps should be done.
 133 
 134         If it's a directory, add it to the image_subdirs list below and it will
 135         be created automatically. The programmer must fill the directory as
 136         needed. If a file is added, the programmer is responsible for creating
 137         that file during image creation at an appropriate place and time.
 138 
 139         If a directory is required to be present in order for an image to be
 140         identifiable as such, it should go into required_subdirs instead.
 141         However, upgrade issues abound; this list should probably not change.
 142 
 143         Once those steps have been carried out, the change should be added
 144         to the test suite for image corruption (t_pkg_install_corrupt_image.py).
 145         This will likely also involve a change to
 146         SingleDepotTestCaseCorruptImage in testutils.py. Each of these files
 147         outline what must be updated.
 148 
 149         XXX Root path probably can't be absolute, so that we can combine or
 150         reuse Image contents.
 151 
 152         XXX Image file format?  Image file manipulation API?"""
 153 
 154         required_subdirs = [ "catalog", "file", "pkg" ]
 155         image_subdirs = required_subdirs + [ "index", "state/installed" ]
 156 
 157         def __init__(self):
 158                 self.cfg_cache = None
 159                 self.type = None
 160                 self.root = None
 161                 self.history = history.History()
 162                 self.imgdir = None
 163                 self.img_prefix = None
 164                 self.index_dir = None
 165                 self.repo_uris = []
 166                 self.filter_tags = {}
 167                 self.catalogs = {}
 168                 self._catalog = {}
 169                 self.pkg_states = None
 170                 self.dl_cache_dir = None
 171                 self.dl_cache_incoming = None
 172                 self.is_user_cache_dir = False
 173                 self.state = imagestate.ImageState()
 174                 self.attrs = {
 175                     "Policy-Require-Optional": False,
 176                     "Policy-Pursue-Latest": True
 177                 }
 178 
 179                 self.imageplan = None # valid after evaluation succeeds
 180 
 181                 # contains a dictionary w/ key = pkgname, value is miminum
 182                 # frmi.XXX  Needs rewrite using graph follower
 183                 self.optional_dependencies = {}
 184 
 185                 # a place to keep info about saved_files; needed by file action
 186                 self.saved_files = {}
 187 
 188                 # A place to keep track of which manifests (based on fmri and
 189                 # operation) have already provided intent information.
 190                 self.__touched_manifests = {}
 191 
 192                 self.__manifest_cache = {}
 193 
 194         def _check_subdirs(self, sub_d, prefix):
 195                 for n in self.required_subdirs:
 196                         if not os.path.isdir(os.path.join(sub_d, prefix, n)):
 197                                 return False
 198                 return True
 199 
 200         def image_type(self, d):
 201                 """Returns the type of image at directory: d; or None"""
 202                 rv = None
 203                 if os.path.isdir(os.path.join(d, img_user_prefix)) and \
 204                         os.path.isfile(os.path.join(d, img_user_prefix,
 205                             "cfg_cache")) and \
 206                             self._check_subdirs(d, img_user_prefix):
 207                         rv = IMG_USER
 208                 elif os.path.isdir(os.path.join(d, img_root_prefix)) \
 209                          and os.path.isfile(os.path.join(d,
 210                              img_root_prefix, "cfg_cache")) and \
 211                              self._check_subdirs(d, img_root_prefix):
 212                         rv = IMG_ENTIRE
 213                 return rv
 214                 
 215         def find_root(self, d, exact_match=False):
 216                 # Ascend from the given directory d to find first
 217                 # encountered image. If exact_match is true, if the
 218                 # image found doesn't match startd, raise an
 219                 # ImageNotFoundException.
 220                 startd = d
 221                 # eliminate problem if relative path such as "." is passed in
 222                 d = os.path.realpath(d)
 223                 while True:
 224                         imgtype = self.image_type(d)
 225                         if imgtype == IMG_USER:
 226                                 # XXX Look at image file to determine filter
 227                                 # tags and repo URIs.
 228                                 if exact_match and \
 229                                     os.path.realpath(startd) != \
 230                                     os.path.realpath(d):
 231                                         raise api_errors.ImageNotFoundException(
 232                                             exact_match, startd, d)
 233                                 self.__set_dirs(imgtype=imgtype, root=d)
 234                                 self.attrs["Build-Release"] = "5.11"
 235                                 return
 236                         elif imgtype == IMG_ENTIRE:
 237                                 # XXX Look at image file to determine filter
 238                                 # tags and repo URIs.
 239                                 # XXX Look at image file to determine if this
 240                                 # image is a partial image.
 241                                 if exact_match and \
 242                                     os.path.realpath(startd) != \
 243                                     os.path.realpath(d):
 244                                         raise api_errors.ImageNotFoundException(
 245                                             exact_match, startd, d)
 246                                 self.__set_dirs(imgtype=imgtype, root=d)
 247                                 self.attrs["Build-Release"] = "5.11"
 248                                 return
 249 
 250                         # XXX follow symlinks or not?
 251                         oldpath = d
 252                         d = os.path.normpath(os.path.join(d, os.path.pardir))
 253 
 254                         # Make sure we are making progress and aren't in an
 255                         # infinite loop.
 256                         #
 257                         # (XXX - Need to deal with symlinks here too)
 258                         if d == oldpath:
 259                                 raise api_errors.ImageNotFoundException(
 260                                     exact_match, startd, d)
 261 
 262         def load_config(self):
 263                 """Load this image's cached configuration from the default
 264                 location."""
 265 
 266                 # XXX Incomplete with respect to doc/image.txt description of
 267                 # configuration.
 268 
 269                 if self.root == None:
 270                         raise RuntimeError, "self.root must be set"
 271 
 272                 ic = imageconfig.ImageConfig()
 273 
 274                 if os.path.isfile("%s/cfg_cache" % self.imgdir):
 275                         ic.read("%s/cfg_cache" % self.imgdir)
 276 
 277                 self.cfg_cache = ic
 278 
 279         def save_config(self):
 280                 self.cfg_cache.write("%s/cfg_cache" % self.imgdir)
 281 
 282         # XXX mkdirs and set_attrs() need to be combined into a create
 283         # operation.
 284         def mkdirs(self):
 285                 for sd in self.image_subdirs:
 286                         if not os.path.isdir(os.path.join(self.imgdir, sd)):
 287                                 os.makedirs(os.path.join(self.imgdir, sd))
 288 
 289         def __set_dirs(self, imgtype, root):
 290                 self.type = imgtype
 291                 self.root = root
 292                 if self.type == IMG_USER:
 293                         self.img_prefix = img_user_prefix
 294                 else:
 295                         self.img_prefix = img_root_prefix
 296                 self.imgdir = os.path.join(self.root, self.img_prefix)
 297                 self.history.root_dir = self.imgdir
 298 
 299                 if "PKG_CACHEDIR" in os.environ:
 300                         self.dl_cache_dir = os.path.normpath( \
 301                             os.environ["PKG_CACHEDIR"])
 302                         self.is_user_cache_dir = True
 303                 else:
 304                         self.dl_cache_dir = os.path.normpath( \
 305                             os.path.join(self.imgdir, "download"))
 306                 self.dl_cache_incoming = os.path.normpath(os.path.join(
 307                     self.dl_cache_dir, "incoming-%d" % os.getpid()))
 308 
 309         def set_attrs(self, type, root, is_zone, auth_name, auth_url,
 310             ssl_key = None, ssl_cert = None):
 311                 self.__set_dirs(imgtype=type, root=root)
 312 
 313                 if not os.path.exists(os.path.join(self.imgdir, "cfg_cache")):
 314                         self.history.operation_name = "image-create"
 315                 else:
 316                         self.history.operation_name = "image-set-attributes"
 317 
 318                 self.mkdirs()
 319 
 320                 self.cfg_cache = imageconfig.ImageConfig()
 321 
 322                 if is_zone:
 323                         self.cfg_cache.filters["opensolaris.zone"] = "nonglobal"
 324 
 325                 self.cfg_cache.authorities[auth_name] = {}
 326                 self.cfg_cache.authorities[auth_name]["prefix"] = auth_name
 327                 self.cfg_cache.authorities[auth_name]["origin"] = \
 328                     misc.url_affix_trailing_slash(auth_url)
 329                 self.cfg_cache.authorities[auth_name]["mirrors"] = []
 330                 self.cfg_cache.authorities[auth_name]["ssl_key"] = ssl_key
 331                 self.cfg_cache.authorities[auth_name]["ssl_cert"] = ssl_cert
 332                 self.cfg_cache.authorities[auth_name]["uuid"] = pkg.Uuid25.uuid1()
 333 
 334                 self.cfg_cache.preferred_authority = auth_name
 335 
 336                 self.save_config()
 337                 self.history.operation_result = history.RESULT_SUCCEEDED
 338 
 339         def is_liveroot(self):
 340                 return self.root == "/"
 341 
 342         def is_zone(self):
 343                 zone = self.cfg_cache.filters.get("opensolaris.zone", "")
 344                 return zone == "nonglobal"
 345 
 346         def get_root(self):
 347                 return self.root
 348 
 349         def gen_authorities(self):
 350                 if not self.cfg_cache:
 351                         raise RuntimeError, "empty ImageConfig"
 352                 if not self.cfg_cache.authorities:
 353                         raise RuntimeError, "no defined authorities"
 354                 for a in self.cfg_cache.authorities:
 355                         yield self.cfg_cache.authorities[a]
 356 
 357         def get_url_by_authority(self, authority = None):
 358                 """Return the URL prefix associated with the given authority.
 359                 For the undefined case, represented by None, return the
 360                 preferred authority."""
 361 
 362                 # XXX This function is a possible location to insert one or more
 363                 # policies regarding use of mirror responses, etc.
 364 
 365                 if authority == None:
 366                         authority = self.cfg_cache.preferred_authority
 367 
 368                 try:
 369                         o = self.cfg_cache.authorities[authority]["origin"]
 370                 except KeyError:
 371                         # If the authority that we're trying to get no longer
 372                         # exists, fall back to preferred authority.
 373                         authority = self.cfg_cache.preferred_authority
 374                         o = self.cfg_cache.authorities[authority]["origin"]
 375 
 376                 return o.rstrip("/")
 377 
 378         def select_mirror(self, auth = None, chosen_set = None):
 379                 """For the given authority, look through the status of
 380                 the mirrors.  Pick the best one.  This method returns
 381                 a DepotStatus object or None.  The chosen_set argument
 382                 contains a set object that lists the mirrors that were
 383                 previously chosen.  This allows us to choose both
 384                 by depot status statistics and ensures we don't
 385                 always pick the same depot."""
 386 
 387                 if auth == None:
 388                         auth = self.cfg_cache.preferred_authority
 389                 try:
 390                         slst = self.cfg_cache.mirror_status[auth]
 391                 except KeyError:
 392                         # If the authority that we're trying to get no longer
 393                         # exists, fall back to preferred authority.
 394                         auth = self.cfg_cache.preferred_authority
 395                         slst = self.cfg_cache.mirror_status[auth]
 396 
 397                 if len(slst) == 0:
 398                         if auth in self.cfg_cache.authority_status:
 399                                 return self.cfg_cache.authority_status[auth]
 400                         else:
 401                                 return None
 402 
 403                 # Choose mirror with fewest errors.
 404                 # If mirrors have same number of errors, choose mirror
 405                 # with smaller number of good transactions.  Assume it's
 406                 # being underused, not high-latency.
 407                 #
 408                 # XXX Will need to revisit the above assumption.
 409                 def cmp_depotstatus(a, b):
 410                         res = cmp(a.errors, b.errors)
 411                         if res == 0:
 412                                 return cmp(a.good_tx, b.good_tx)
 413                         return res
 414 
 415                 slst.sort(cmp = cmp_depotstatus)
 416 
 417                 if chosen_set and len(chosen_set) == len(slst):
 418                         chosen_set.clear()
 419                         chosen_set = None
 420 
 421                 if chosen_set and slst[0] in chosen_set:
 422                         for ds in slst:
 423                                 if ds not in chosen_set:
 424                                         return ds
 425 
 426                 return slst[0]
 427 
 428         def get_ssl_credentials(self, authority = None, origin = None):
 429                 """Return a tuple containing (ssl_key, ssl_cert) for the
 430                 specified authority prefix.  If the authority isn't specified,
 431                 attempt to determine the authority by the given origin.  If
 432                 neither is specified, use the preferred authority.
 433                 """
 434 
 435                 if authority is None:
 436                         if origin is None:
 437                                 authority = self.cfg_cache.preferred_authority
 438                         else:
 439                                 auths = self.cfg_cache.authorities
 440                                 for pfx, auth in auths.iteritems():
 441                                         if auth["origin"] == origin:
 442                                                 authority = pfx
 443                                                 break
 444                                 else:
 445                                         return None
 446 
 447                 try:
 448                         authent = self.cfg_cache.authorities[authority]
 449                 except KeyError:
 450                         authority = self.cfg_cache.preferred_authority
 451                         authent = self.cfg_cache.authorities[authority]
 452 
 453                 return (authent["ssl_key"], authent["ssl_cert"])
 454 
 455         @staticmethod
 456         def build_cert(path):
 457                 """Take the file given in path, open it, and use it to create
 458                 an X509 certificate object."""
 459 
 460                 cf = file(path, "rb")
 461                 certdata = cf.read()
 462                 cf.close()
 463                 cert = osc.load_certificate(osc.FILETYPE_PEM, certdata)
 464 
 465                 return cert
 466 
 467         def check_cert_validity(self):
 468                 """Look through the authorities defined for the image.  Print
 469                 a message and exit with an error if one of the certificates
 470                 has expired.  If certificates are getting close to expiration,
 471                 print a warning instead."""
 472 
 473                 for a in self.gen_authorities():
 474                         pfx, url, ssl_key, ssl_cert, dt, mir = \
 475                             self.split_authority(a)
 476 
 477                         if not ssl_cert:
 478                                 continue
 479 
 480                         try:
 481                                 cert = self.build_cert(ssl_cert)
 482                         except IOError, e:
 483                                 if e.errno == errno.ENOENT:
 484                                         emsg(_("Certificate for authority %s" \
 485                                             " not found") % pfx)
 486                                         emsg(_("File was supposed to exist at" \
 487                                            "  path %s") % ssl_cert)
 488                                         return False
 489                                 else:
 490                                         raise
 491                         # OpenSSL.crypto.Error
 492                         except osc.Error, e:
 493                                 emsg(_("Certificate for authority %s at" \
 494                                     " %s has an invalid format.") % \
 495                                     (pfx, ssl_cert))
 496                                 return False
 497 
 498                         if cert.has_expired():
 499                                 emsg(_("Certificate for authority %s" \
 500                                     " has expired") % pfx)
 501                                 emsg(_("Please install a valid certificate"))
 502                                 return False
 503 
 504                         now = datetime.datetime.utcnow()
 505                         nb = cert.get_notBefore()
 506                         t = time.strptime(nb, "%Y%m%d%H%M%SZ")
 507                         nbdt = datetime.datetime.utcfromtimestamp(
 508                             calendar.timegm(t))
 509 
 510                         # PyOpenSSL's has_expired() doesn't validate the notBefore
 511                         # time on the certificate.  Don't ask me why.
 512 
 513                         if nbdt > now:
 514                                 emsg(_("Certificate for authority %s is" \
 515                                     " invalid") % pfx)
 516                                 emsg(_("Certificate effective date is in" \
 517                                     " the future"))
 518                                 return False
 519 
 520                         na = cert.get_notAfter()
 521                         t = time.strptime(na, "%Y%m%d%H%M%SZ")
 522                         nadt = datetime.datetime.utcfromtimestamp(
 523                             calendar.timegm(t))
 524 
 525                         diff = nadt - now
 526 
 527                         if diff <= MIN_WARN_DAYS:
 528                                 emsg(_("Certificate for authority %s will" \
 529                                     " expire in %d days" % (pfx, diff.days)))
 530 
 531                 return True
 532 
 533         def get_uuid(self, authority):
 534                 """Return the UUID for the specified authority prefix.  If the
 535                 policy for sending the UUID is set to false, return None.
 536                 """
 537                 if not self.cfg_cache.get_policy(imageconfig.SEND_UUID):
 538                         return None
 539 
 540                 try:
 541                         return self.cfg_cache.authorities[authority]["uuid"]
 542                 except KeyError:
 543                         return None
 544                         
 545         def get_default_authority(self):
 546                 return self.cfg_cache.preferred_authority
 547 
 548         def has_authority(self, auth_name):
 549                 return auth_name in self.cfg_cache.authorities
 550 
 551         def delete_authority(self, auth_name):
 552                 self.history.operation_name = "delete-authority"
 553                 if not self.has_authority(auth_name):
 554                         error = "no such authority '%s'" % auth_name
 555                         self.history.operation_errors.append(error)
 556                         self.history.operation_result = \
 557                             history.RESULT_FAILED_UNKNOWN
 558                         raise KeyError, error
 559                 self.cfg_cache.delete_authority(auth_name)
 560                 self.save_config()
 561                 self.destroy_catalog(auth_name)
 562                 self.cache_catalogs()
 563                 self.history.operation_result = history.RESULT_SUCCEEDED
 564 
 565         def get_authority(self, auth_name):
 566                 if not self.has_authority(auth_name):
 567                         raise KeyError, "no such authority '%s'" % auth_name
 568 
 569                 return self.cfg_cache.authorities[auth_name]
 570 
 571         def split_authority(self, auth):
 572                 prefix = auth["prefix"]
 573                 update_dt = None
 574 
 575                 try:
 576                         cat = self.catalogs[prefix]
 577                 except KeyError:
 578                         cat = None
 579 
 580                 if cat:
 581                         update_dt = cat.last_modified()
 582                         if update_dt:
 583                                 update_dt = catalog.ts_to_datetime(update_dt)
 584 
 585                 return (prefix, auth["origin"], auth["ssl_key"],
 586                     auth["ssl_cert"], update_dt, auth["mirrors"])
 587 
 588         def set_preferred_authority(self, auth_name):
 589                 self.history.operation_name = "set-preferred-authority"
 590                 if not self.has_authority(auth_name):
 591                         error = "no such authority '%s'" % auth_name
 592                         self.history.operation_errors.append(error)
 593                         self.history.operation_result = \
 594                             history.RESULT_FAILED_UNKNOWN
 595                         raise KeyError, error
 596                 self.cfg_cache.preferred_authority = auth_name
 597                 self.save_config()
 598                 self.history.operation_result = history.RESULT_SUCCEEDED
 599 
 600         def set_authority(self, auth_name, origin_url = None, ssl_key = None,
 601             ssl_cert = None, refresh_allowed = True, uuid = None):
 602                 self.history.operation_name = "set-authority"
 603                 auths = self.cfg_cache.authorities
 604 
 605                 refresh_needed = False
 606 
 607                 if auth_name in auths:
 608                         # If authority already exists, only update non-NULL
 609                         # values passed to set_authority
 610                         if origin_url:
 611                                 auths[auth_name]["origin"] = \
 612                                     misc.url_affix_trailing_slash(origin_url)
 613                                 refresh_needed = True
 614                         if ssl_key:
 615                                 auths[auth_name]["ssl_key"] = ssl_key
 616                         if ssl_cert:
 617                                 auths[auth_name]["ssl_cert"] = ssl_cert
 618                         if uuid:
 619                                 auths[auth_name]["uuid"] = uuid
 620 
 621                 else:
 622                         auths[auth_name] = {}
 623                         auths[auth_name]["prefix"] = auth_name
 624                         auths[auth_name]["origin"] = \
 625                             misc.url_affix_trailing_slash(origin_url)
 626                         auths[auth_name]["mirrors"] = []
 627                         auths[auth_name]["ssl_key"] = ssl_key
 628                         auths[auth_name]["ssl_cert"] = ssl_cert
 629                         if not uuid:
 630                                 uuid = pkg.Uuid25.uuid1()
 631                         auths[auth_name]["uuid"] = uuid
 632                         refresh_needed = True
 633 
 634                 self.save_config()
 635 
 636                 if refresh_needed and refresh_allowed:
 637                         self.destroy_catalog(auth_name)
 638                         self.destroy_catalog_cache()
 639                         self.retrieve_catalogs(full_refresh=True,
 640                             auths=[auths[auth_name]])
 641 
 642                 self.history.operation_result = history.RESULT_SUCCEEDED
 643 
 644         def set_property(self, prop_name, prop_value):
 645                 self.cfg_cache.properties[prop_name] = prop_value
 646                 self.save_config()
 647 
 648         def get_property(self, prop_name):
 649                 return self.cfg_cache.properties[prop_name]
 650 
 651         def has_property(self, prop_name):
 652                 return prop_name in self.cfg_cache.properties
 653 
 654         def delete_property(self, prop_name):
 655                 del self.cfg_cache.properties[prop_name]
 656                 self.save_config()
 657 
 658         def properties(self):
 659                 for p in self.cfg_cache.properties:
 660                         yield p
 661 
 662         def add_mirror(self, auth_name, mirror):
 663                 """Add the mirror URL contained in mirror to
 664                 auth_name's list of mirrors."""
 665                 self.history.operation_name = "add-mirror"
 666                 auths = self.cfg_cache.authorities
 667                 auths[auth_name]["mirrors"].append(mirror)
 668                 self.save_config()
 669                 self.history.operation_result = history.RESULT_SUCCEEDED
 670 
 671         def has_mirror(self, auth_name, url):
 672                 """Returns true if url is in auth_name's list of mirrors."""
 673 
 674                 return url in self.cfg_cache.authorities[auth_name]["mirrors"]
 675 
 676         def del_mirror(self, auth_name, mirror):
 677                 """Remove the mirror URL contained in mirror from
 678                 auth_name's list of mirrors."""
 679 
 680                 self.history.operation_name = "delete-mirror"
 681                 auths = self.cfg_cache.authorities
 682 
 683                 if mirror in self.cfg_cache.authorities[auth_name]["mirrors"]:
 684                         auths[auth_name]["mirrors"].remove(mirror)
 685                         self.save_config()
 686                 self.history.operation_result = history.RESULT_SUCCEEDED
 687 
 688         def verify(self, fmri, progresstracker, **args):
 689                 """generator that returns any errors in installed pkgs
 690                 as tuple of action, list of errors"""
 691 
 692                 for act in self.get_manifest(fmri, filtered = True).actions:
 693                         errors = act.verify(self, pkg_fmri=fmri, **args)
 694                         progresstracker.verify_add_progress(fmri)
 695                         actname = act.distinguished_name()
 696                         if errors:
 697                                 progresstracker.verify_yield_error(actname,
 698                                     errors)
 699                                 yield (act, errors)
 700 
 701         def repair(self, repairs, progtrack):
 702                 """Repair any actions in the fmri that failed a verify."""
 703                 pps = []
 704                 for fmri, actions in repairs:
 705                         msg("Repairing: %-50s" % fmri.get_pkg_stem())
 706                         m = self.get_manifest(fmri, filtered=True)
 707                         pp = pkgplan.PkgPlan(self, progtrack, False)
 708 
 709                         pp.propose_repair(fmri, m, actions)
 710                         pp.evaluate()
 711                         pps.append(pp)
 712 
 713                 ip = imageplan.ImagePlan(self, progtrack, False)
 714                 ip.pkg_plans = pps
 715                 try:
 716                         ip.evaluate()
 717                         ip.preexecute()
 718                         ip.execute()
 719                 except KeyboardInterrupt:
 720                         raise
 721                 except:
 722                         return False
 723 
 724                 return True
 725 
 726         def get_fmri_manifest_pairs(self):
 727                 """For each installed fmri, finds the path to its manifest file
 728                 and adds the pair of the fmri and the path to a list. Once all
 729                 installed fmris have been processed, the list is returned."""
 730                 return [
 731                     (fmri, self.get_manifest_path(fmri))
 732                     for fmri in self.gen_installed_pkgs()
 733                 ]
 734 
 735         def has_manifest(self, fmri):
 736                 mpath = fmri.get_dir_path()
 737 
 738                 local_mpath = "%s/pkg/%s/manifest" % (self.imgdir, mpath)
 739 
 740                 if (os.path.exists(local_mpath)):
 741                         return True
 742 
 743                 return False
 744 
 745         def __fetch_manifest_with_retries(self, fmri):
 746                 """Wrapper function around __fetch_manifest to handle some
 747                 exceptions and keep track of additional state."""
 748 
 749                 m = None
 750                 retry_count = global_settings.PKG_TIMEOUT_MAX
 751                 failures = TransportFailures()
 752 
 753                 while not m:
 754                         try:
 755                                 m = self.__fetch_manifest(fmri)
 756                         except TransportException, e:
 757                                 retry_count -= 1
 758                                 failures.append(e)
 759 
 760                                 if retry_count <= 0:
 761                                         raise failures
 762 
 763                 return m
 764 
 765         def __get_touched_manifest(self, fmri):
 766                 """Returns whether intent information has been provided for the
 767                 given fmri."""
 768 
 769                 op = self.history.operation_name
 770                 if not op:
 771                         # The client may not have provided the name of the
 772                         # operation it is performing.
 773                         op = "unknown"
 774 
 775                 if op not in self.__touched_manifests:
 776                         # No intent information has been provided for fmris
 777                         # for the current operation.
 778                         return False
 779 
 780                 f = str(fmri)
 781                 if f not in self.__touched_manifests[op]:
 782                         # No intent information has been provided for this
 783                         # fmri for the current operation.
 784                         return False
 785 
 786                 return True
 787 
 788         def __set_touched_manifest(self, fmri):
 789                 """Records that intent information has been provided for the
 790                 given fmri's manifest."""
 791 
 792                 op = self.history.operation_name
 793                 if not op:
 794                         # The client may not have provided the name of the
 795                         # operation it is performing.
 796                         op = "unknown"
 797 
 798                 if op not in self.__touched_manifests:
 799                         # No intent information has yet been provided for fmris
 800                         # for the current operation.
 801                         self.__touched_manifests[op] = {}
 802 
 803                 f = str(fmri)
 804                 if f not in self.__touched_manifests[op]:
 805                         # No intent information has yet been provided for this
 806                         # fmri for the current operation.
 807                         self.__touched_manifests[op][f] = None
 808 
 809         def __touch_manifest(self, fmri):
 810                 """Perform steps necessary to 'touch' a manifest to provide
 811                 intent information.  Ignores most exceptions as this operation
 812                 is only for informational purposes."""
 813 
 814                 if not self.__get_touched_manifest(fmri):
 815                         # If the manifest for this fmri hasn't been "seen"
 816                         # before, determine if intent information needs to be
 817                         # provided.
 818 
 819                         # What is the client currently processing?
 820                         target, intent = self.state.get_target()
 821 
 822                         if target and intent != imagestate.INTENT_EVALUATE:
 823                                 # If the client is currently performing an
 824                                 # image-modifying operation, not just an
 825                                 # an evaluation, then perform further checks.
 826 
 827                                 # Ignore the authority for comparison.
 828                                 na_target = target.get_fmri(anarchy=True)
 829                                 na_fmri = target.get_fmri(anarchy=True)
 830 
 831                                 if na_target == na_fmri:
 832                                         # If the client is currently processing
 833                                         # the given fmri (for an install, etc.)
 834                                         # then intent information is needed.
 835                                         retrieve.touch_manifest(self, fmri)
 836                                         self.__set_touched_manifest(fmri)
 837 
 838         def __fetch_manifest(self, fmri):
 839                 """Perform steps necessary to get manifest from remote host
 840                 and write resulting contents to disk.  Helper routine for
 841                 get_manifest.  Does not filter the results, caller must do
 842                 that.  """
 843 
 844                 m = manifest.Manifest()
 845                 m.set_fmri(self, fmri)
 846 
 847                 fmri_dir_path = os.path.join(self.imgdir, "pkg",
 848                     fmri.get_dir_path())
 849                 mpath = os.path.join(fmri_dir_path, "manifest")
 850 
 851                 # Get manifest as a string from the remote host, then build
 852                 # it up into an in-memory manifest, then write the finished
 853                 # representation to disk.  Note that this may throw a
 854                 # TransportException of some sort; we let upper layers
 855                 # handle that.
 856                 mcontent = retrieve.get_manifest(self, fmri)
 857                 m.set_content(mcontent)
 858 
 859                 # Write the originating authority into the manifest.
 860                 # Manifests prior to this change won't contain this information.
 861                 # In that case, the client attempts to re-download the manifest
 862                 # from the depot.
 863                 if not fmri.has_authority():
 864                         m["authority"] = self.get_default_authority()
 865                 else:
 866                         m["authority"] = fmri.get_authority()
 867 
 868                 try:
 869                         m.store(mpath)
 870                 except EnvironmentError, e:
 871                         if e.errno not in (errno.EROFS, errno.EACCES):
 872                                 raise
 873 
 874                 self.__set_touched_manifest(fmri)
 875 
 876                 return m
 877 
 878         def _valid_manifest(self, fmri, manifest):
 879                 """Check authority attached to manifest.  Make sure
 880                 it matches authority specified in FMRI."""
 881 
 882                 authority = fmri.get_authority()
 883                 if not authority:
 884                         authority = self.get_default_authority()
 885 
 886                 if not "authority" in manifest:
 887                         return False
 888 
 889                 if manifest["authority"] != authority:
 890                         return False
 891 
 892                 return True
 893 
 894         def get_manifest_path(self, fmri):
 895                 """Find on-disk manifest and create in-memory Manifest
 896                 object, applying appropriate filters as needed."""
 897                 mpath = os.path.join(self.imgdir, "pkg",
 898                     fmri.get_dir_path(), "manifest")
 899                 return mpath
 900 
 901         def __get_manifest(self, fmri):
 902                 """Find on-disk manifest and create in-memory Manifest
 903                 object."""
 904 
 905                 m = None
 906                 mpath = os.path.join(self.imgdir, "pkg", fmri.get_dir_path(),
 907                     "manifest")
 908                 if os.path.exists(mpath):
 909                         # If the manifest already exists, load it from storage.
 910                         m = manifest.Manifest()
 911                         mcontent = file(mpath).read()
 912                         m.set_fmri(self, fmri)
 913                         m.set_content(mcontent)
 914 
 915                 try:
 916                         # If the manifest didn't already exist, or isn't from
 917                         # the correct authority, or no authority is attached
 918                         # to the manifest, attempt to download a new one.
 919                         if not m or not self._valid_manifest(fmri, m):
 920                                 m = self.__fetch_manifest_with_retries(fmri)
 921                 except (retrieve.ManifestRetrievalError,
 922                     retrieve.DatastreamRetrievalError):
 923                         # In this case, the client has failed to download a new
 924                         # manifest or re-download an existing one with the same
 925                         # name.
 926                         if not m:
 927                                 # Since an older copy doesn't exist, give up.
 928                                 raise
 929 
 930                         # Since the old manifest exists, keep it, and drive on.
 931 
 932                 return m
 933 
 934         def get_manifest(self, fmri, filtered = False, use_cache = True):
 935                 """Find on-disk manifest and create in-memory Manifest
 936                 object, applying appropriate filters as needed."""
 937 
 938                 if use_cache and fmri in self.__manifest_cache:
 939                         m = self.__manifest_cache[fmri]
 940                 else:
 941                         m = self.__get_manifest(fmri)
 942                         if use_cache:
 943                                 self.__manifest_cache[fmri] = m
 944 
 945                 self.__touch_manifest(fmri)
 946 
 947                 # XXX perhaps all of the below should live in Manifest.filter()?
 948                 if filtered:
 949                         fmri_dir_path = os.path.join(self.imgdir, "pkg",
 950                             fmri.get_dir_path())
 951 
 952                         filters = []
 953                         try:
 954                                 f = file("%s/filters" % fmri_dir_path, "r")
 955                         except IOError, e:
 956                                 if e.errno != errno.ENOENT:
 957                                         raise
 958                         else:
 959                                 filters = [
 960                                     (l.strip(), compile(
 961                                         l.strip(), "<filter string>", "eval"))
 962                                     for l in f.readlines()
 963                                 ]
 964                         m.filter(filters)
 965 
 966                 return m
 967 
 968         def installed_file_authority(self, filepath):
 969                 """Find the pkg's installed file named by filepath.
 970                 Return the authority that installed this package."""
 971 
 972                 read_only = False
 973 
 974                 try:
 975                         f = file(filepath, "r+")
 976                 except IOError, e:
 977                         if e.errno == errno.EACCES or e.errno == errno.EROFS:
 978                                 read_only = True
 979                         else:
 980                                 raise
 981                 if read_only:
 982                         f = file(filepath, "r")
 983 
 984                 flines = f.readlines()
 985                 newauth = None
 986 
 987                 try:
 988                         version, auth = flines
 989                 except ValueError:
 990                         # If we get a ValueError, we've encoutered an
 991                         # installed file of a previous format.  If we want
 992                         # upgrade to work in this situation, it's necessary
 993                         # to assume that the package was installed from
 994                         # the preferred authority.  Here, we set up
 995                         # the authority to record that.
 996                         if flines:
 997                                 auth = flines[0]
 998                                 auth = auth.strip()
 999                                 newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1000                                     auth)
1001                         else:
1002                                 newauth = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1003                                     self.get_default_authority())
1004 
1005                         # Exception handler is only part of this code that
1006                         # sets newauth
1007                         auth = newauth
1008 
1009                 if newauth and not read_only:
1010                         # This is where we actually update the installed
1011                         # file with the new authority.
1012                         f.seek(0)
1013                         f.writelines(["VERSION_1\n", newauth])
1014 
1015                 f.close()
1016 
1017                 assert auth
1018 
1019                 return auth
1020 
1021         def _install_file(self, fmri):
1022                 """Returns the path to the "installed" file for a given fmri."""
1023                 return "%s/pkg/%s/installed" % (self.imgdir, fmri.get_dir_path())
1024 
1025         def install_file_present(self, fmri):
1026                 """Returns true if the package named by the fmri is installed
1027                 on the system.  Otherwise, returns false."""
1028 
1029                 return os.path.exists(self._install_file(fmri))
1030 
1031         def add_install_file(self, fmri):
1032                 """Take an image and fmri. Write a file to disk that
1033                 indicates that the package named by the fmri has been
1034                 installed."""
1035 
1036                 # XXX This can be removed at some point in the future once we
1037                 # think this link is available on all systems
1038                 if not os.path.isdir("%s/state/installed" % self.imgdir):
1039                         self.update_installed_pkgs()
1040 
1041                 f = file(self._install_file(fmri), "w")
1042 
1043                 f.writelines(["VERSION_1\n", fmri.get_authority_str()])
1044                 f.close()
1045 
1046                 fi = file("%s/state/installed/%s" % (self.imgdir,
1047                     fmri.get_link_path()), "w")
1048                 fi.close()
1049                 self.pkg_states[urllib.unquote(fmri.get_link_path())] = \
1050                     (PKG_STATE_INSTALLED, fmri)
1051 
1052         def remove_install_file(self, fmri):
1053                 """Take an image and a fmri.  Remove the file from disk
1054                 that indicates that the package named by the fmri has been
1055                 installed."""
1056 
1057                 # XXX This can be removed at some point in the future once we
1058                 # think this link is available on all systems
1059                 if not os.path.isdir("%s/state/installed" % self.imgdir):
1060                         self.update_installed_pkgs()
1061 
1062                 os.unlink(self._install_file(fmri))
1063                 try:
1064                         os.unlink("%s/state/installed/%s" % (self.imgdir,
1065                             fmri.get_link_path()))
1066                 except EnvironmentError, e:
1067                         if e.errno != errno.ENOENT:
1068                                 raise
1069                 self.pkg_states[urllib.unquote(fmri.get_link_path())] = \
1070                     (PKG_STATE_KNOWN, fmri)
1071 
1072         def update_installed_pkgs(self):
1073                 """Take the image's record of installed packages from the
1074                 prototype layout, with an installed file in each
1075                 $META/pkg/stem/version directory, to the $META/state/installed
1076                 summary directory form."""
1077 
1078                 tmpdir = "%s/state/installed.build" % self.imgdir
1079 
1080                 # Create the link forest in a temporary directory.  We should
1081                 # only execute this method once (if ever) in the lifetime of an
1082                 # image, but if the path already exists and makedirs() blows up,
1083                 # just be quiet if it's already a directory.  If it's not a
1084                 # directory or something else weird happens, re-raise.
1085                 try:
1086                         os.makedirs(tmpdir)
1087                 except OSError, e:
1088                         if e.errno != errno.EEXIST or \
1089                             not os.path.isdir(tmpdir):
1090                                 raise
1091                         return
1092 
1093                 proot = "%s/pkg" % self.imgdir
1094 
1095                 for pd, vd in (
1096                     (p, v)
1097                     for p in sorted(os.listdir(proot))
1098                     for v in sorted(os.listdir("%s/%s" % (proot, p)))
1099                     ):
1100                         path = "%s/%s/%s/installed" % (proot, pd, vd)
1101                         if not os.path.exists(path):
1102                                 continue
1103 
1104                         fmristr = urllib.unquote("%s@%s" % (pd, vd))
1105                         auth = self.installed_file_authority(path)
1106                         f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1107                         fi = file("%s/%s" % (tmpdir, f.get_link_path()), "w")
1108                         fi.close()
1109 
1110                 # Someone may have already created this directory.  Junk the
1111                 # directory we just populated if that's the case.
1112                 try:
1113                         portable.rename(tmpdir, "%s/state/installed" % self.imgdir)
1114                 except EnvironmentError, e:
1115                         if e.errno != errno.EEXIST:
1116                                 raise
1117                         shutil.rmtree(tmpdir)
1118 
1119         def get_version_installed(self, pfmri):
1120                 """Returns an fmri of the installed package matching the
1121                 package stem of the given fmri or None if no match is found."""
1122                 for f in self.gen_installed_pkgs():
1123                         if self.fmri_is_same_pkg(f, pfmri):
1124                                 return f
1125                 return None
1126 
1127         def get_pkg_state_by_fmri(self, pfmri):
1128                 """Given pfmri, determine the local state of the package."""
1129 
1130                 return self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
1131                     (PKG_STATE_KNOWN, None))[0]
1132 
1133         def get_pkg_auth_by_fmri(self, pfmri):
1134                 """Return the authority from which 'pfmri' was installed."""
1135 
1136                 f = self.pkg_states.get(pfmri.get_fmri(anarchy = True)[5:],
1137                     (PKG_STATE_KNOWN, None))[1]
1138                 if f:
1139                         # Return the non-preferred-prefixed name
1140                         return f.get_authority()
1141                 return None
1142 
1143         def fmri_set_default_authority(self, fmri):
1144                 """If the FMRI supplied as an argument does not have
1145                 an authority, set it to the image's preferred authority."""
1146 
1147                 if fmri.has_authority():
1148                         return
1149 
1150                 fmri.set_authority(self.get_default_authority(), True)
1151 
1152         def get_catalog(self, fmri, exception = False):
1153                 """Given a FMRI, look at the authority and return the
1154                 correct catalog for this image."""
1155 
1156                 # If FMRI has no authority, or is default authority,
1157                 # then return the catalog for the preferred authority
1158                 if not fmri.has_authority() or fmri.preferred_authority():
1159                         cat = self.catalogs[self.get_default_authority()]
1160                 else:
1161                         try:
1162                                 cat = self.catalogs[fmri.get_authority()]
1163                         except KeyError:
1164                                 # If the authority that installed this package
1165                                 # has vanished, pick the default authority
1166                                 # instead.
1167                                 if exception:
1168                                         raise
1169                                 else:
1170                                         cat = self.catalogs[\
1171                                             self.get_default_authority()]
1172 
1173                 return cat
1174 
1175         def has_version_installed(self, fmri):
1176                 """Check that the version given in the FMRI or a successor is
1177                 installed in the current image."""
1178 
1179                 v = self.get_version_installed(fmri)
1180 
1181                 if v and not fmri.has_authority():
1182                         fmri.set_authority(v.get_authority_str())
1183                 elif not fmri.has_authority():
1184                         fmri.set_authority(self.get_default_authority(), True)
1185 
1186                 if v and self.fmri_is_successor(v, fmri):
1187                         return True
1188                 else:
1189                         try:
1190                                 cat = self.get_catalog(fmri, exception = True)
1191                         except KeyError:
1192                                 return False
1193 
1194                         # If fmri has been renamed, get the list of newer
1195                         # packages that are equivalent to fmri.
1196                         rpkgs = cat.rename_newer_pkgs(fmri)
1197                         for f in rpkgs:
1198 
1199                                 v = self.get_version_installed(f)
1200 
1201                                 if v and self.fmri_is_successor(v, fmri):
1202                                         return True
1203 
1204                 return False
1205 
1206         def older_version_installed(self, fmri):
1207                 """This method is used by the package plan to determine if an
1208                 older version of the package is installed.  This takes
1209                 the destination fmri and checks if an older package exists.
1210                 This looks first under the existing name, and then sees
1211                 if an older version is installed under another name.  This
1212                 allows upgrade correctly locate the src fmri, if one exists."""
1213 
1214                 v = self.get_version_installed(fmri)
1215 
1216                 assert fmri.has_authority()
1217 
1218                 if v:
1219                         return v
1220                 else:
1221                         cat = self.get_catalog(fmri)
1222 
1223                         rpkgs = cat.rename_older_pkgs(fmri)
1224                         for f in rpkgs:
1225                                 v = self.get_version_installed(f)
1226                                 if v and self.fmri_is_successor(fmri, v):
1227                                         return v
1228 
1229                 return None
1230 
1231         def is_installed(self, fmri):
1232                 """Check that the exact version given in the FMRI is installed
1233                 in the current image."""
1234 
1235                 # All FMRIs passed to is_installed shall have an authority
1236                 assert fmri.has_authority()
1237 
1238                 v = self.get_version_installed(fmri)
1239                 if not v:
1240                         return False
1241 
1242                 return v == fmri
1243 
1244         def __build_dependents(self, progtrack):
1245                 """Build a dictionary mapping packages to the list of packages
1246                 that have required dependencies on them."""
1247                 self.__req_dependents = {}
1248 
1249                 for fmri in self.gen_installed_pkgs():
1250                         progtrack.evaluate_progress(fmri)
1251                         mfst = self.get_manifest(fmri, filtered = True)
1252 
1253                         for dep in mfst.gen_actions_by_type("depend"):
1254                                 if dep.attrs["type"] != "require":
1255                                         continue
1256                                 dfmri = self.strtofmri(dep.attrs["fmri"])
1257                                 if dfmri not in self.__req_dependents:
1258                                         self.__req_dependents[dfmri] = []
1259                                 self.__req_dependents[dfmri].append(fmri)
1260 
1261         def get_dependents(self, pfmri, progtrack):
1262                 """Return a list of the packages directly dependent on the given
1263                 FMRI."""
1264 
1265                 if not hasattr(self, "_Image__req_dependents"):
1266                         self.__build_dependents(progtrack)
1267 
1268                 dependents = []
1269                 # We run through all the keys, in case a package is depended
1270                 # upon under multiple versions.  That is, if pkgA depends on
1271                 # libc@1 and pkgB depends on libc@2, we need to return both pkgA
1272                 # and pkgB.  If we used package names as keys, this would be
1273                 # simpler, but it wouldn't handle package rename.
1274                 for f in self.__req_dependents.iterkeys():
1275                         if self.fmri_is_successor(pfmri, f):
1276                                 dependents.extend(self.__req_dependents[f])
1277                 return dependents
1278 
1279         def retrieve_catalogs(self, full_refresh = False,
1280             auths = None):
1281                 failed = []
1282                 total = 0
1283                 succeeded = 0
1284                 cat = None
1285                 ts = 0
1286 
1287                 if not auths:
1288                         auths = self.gen_authorities()
1289 
1290                 for auth in auths:
1291                         total += 1
1292 
1293                         full_refresh_this_auth = False
1294 
1295                         if auth["prefix"] in self.catalogs:
1296                                 cat = self.catalogs[auth["prefix"]]
1297                                 ts = cat.last_modified()
1298 
1299                                 # Although we may have a catalog with a
1300                                 # timestamp, the user may have changed the
1301                                 # origin URL for the authority.  If this has
1302                                 # occurred, we need to perform a full refresh.
1303                                 if cat.origin() != auth["origin"]:
1304                                         full_refresh_this_auth = True
1305 
1306                         if ts and not full_refresh and \
1307                             not full_refresh_this_auth:
1308                                 hdr = {'If-Modified-Since': ts}
1309                         else:
1310                                 hdr = {}
1311 
1312                         ssl_tuple = self.get_ssl_credentials(auth["prefix"])
1313 
1314                         # XXX Mirror selection and retrieval policy?
1315                         try:
1316                                 c, v = versioned_urlopen(auth["origin"],
1317                                     "catalog", [0], ssl_creds=ssl_tuple,
1318                                     headers=hdr, imgtype=self.type,
1319                                     uuid=self.get_uuid(auth["prefix"]))
1320                         except urllib2.HTTPError, e:
1321                                 # Server returns NOT_MODIFIED if catalog is up
1322                                 # to date
1323                                 if e.code == httplib.NOT_MODIFIED:
1324                                         succeeded += 1
1325                                 else:
1326                                         failed.append((auth, e))
1327                                 continue
1328 
1329                         except urllib2.URLError, e:
1330                                 failed.append((auth, e))
1331                                 continue
1332                         except ValueError, e:
1333                                 failed.append((auth, e))
1334                                 continue
1335 
1336                         # root for this catalog
1337                         croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1338 
1339                         try:
1340                                 updatelog.recv(c, croot, ts, auth)
1341                         except IOError, e:
1342                                 failed.append((auth, e))
1343                         except socket.timeout, e:
1344                                 failed.append((auth, e))
1345                         else:
1346                                 succeeded += 1
1347 
1348                 self.cache_catalogs()
1349 
1350                 if failed:
1351                         raise api_errors.CatalogRefreshException(failed, total,
1352                             succeeded)
1353 
1354         CATALOG_CACHE_VERSION = 1
1355 
1356         def cache_catalogs(self):
1357                 """Read in all the catalogs and cache the data."""
1358                 cache = {}
1359                 for auth in self.gen_authorities():
1360                         croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1361                         # XXX Should I be removing pkg_names.pkl now that we're
1362                         # not using it anymore?
1363                         try:
1364                                 catalog.Catalog.read_catalog(cache,
1365                                     croot, auth = auth["prefix"])
1366                         except EnvironmentError, e:
1367                                 # If a catalog file is just missing, ignore it.
1368                                 # If there's a worse error, make sure the user
1369                                 # knows about it.
1370                                 if e.errno == errno.ENOENT:
1371                                         pass
1372                                 else:
1373                                         raise
1374 
1375                 pickle_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1376 
1377                 try:
1378                         pf = file(pickle_file, "wb")
1379                         # Version the dump file
1380                         cPickle.dump((self.CATALOG_CACHE_VERSION, cache), pf,
1381                             protocol = cPickle.HIGHEST_PROTOCOL)
1382                         pf.close()
1383                 except (cPickle.PickleError, EnvironmentError):
1384                         try:
1385                                 os.remove(pickle_file)
1386                         except EnvironmentError:
1387                                 pass
1388 
1389                 self._catalog = cache
1390 
1391         def load_catalog_cache(self):
1392                 """Read in the cached catalog data."""
1393 
1394                 self.__load_pkg_states()
1395 
1396                 cache_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1397                 try:
1398                         version, self._catalog = \
1399                             cPickle.load(file(cache_file, "rb"))
1400                 except (cPickle.PickleError, EnvironmentError):
1401                         raise RuntimeError
1402 
1403                 # If we don't recognize the version, complain.
1404                 if version != self.CATALOG_CACHE_VERSION:
1405                         raise RuntimeError
1406 
1407                 # Add the packages which are installed, but not in the catalog.
1408                 # XXX Should we have a different state for these, so we can flag
1409                 # them to the user?
1410                 for state, f in self.pkg_states.values():
1411                         if state != PKG_STATE_INSTALLED:
1412                                 continue
1413                         auth, name, vers = f.tuple()
1414 
1415                         if name not in self._catalog or \
1416                             vers not in self._catalog[name]["versions"]:
1417                                 catalog.Catalog.cache_fmri(self._catalog, f,
1418                                     f.get_authority())
1419 
1420         def load_catalogs(self, progresstracker):
1421                 for auth in self.gen_authorities():
1422                         croot = "%s/catalog/%s" % (self.imgdir, auth["prefix"])
1423                         progresstracker.catalog_start(auth["prefix"])
1424                         if auth["prefix"] == self.cfg_cache.preferred_authority:
1425                                 authpfx = "%s_%s" % (pkg.fmri.PREF_AUTH_PFX,
1426                                     auth["prefix"])
1427                                 c = catalog.Catalog(croot,
1428                                     authority=authpfx)
1429                         else:
1430                                 c = catalog.Catalog(croot,
1431                                     authority = auth["prefix"])
1432                         self.catalogs[auth["prefix"]] = c
1433                         progresstracker.catalog_done()
1434 
1435                 # Try to load the catalog cache file.  If that fails, load the
1436                 # data from the canonical text copies of the catalogs from each
1437                 # authority.  Try to save it, to spare the time in the future.
1438                 # XXX Given that this is a read operation, should we be writing?
1439                 try:
1440                         self.load_catalog_cache()
1441                 except RuntimeError:
1442                         self.cache_catalogs()
1443 
1444         def destroy_catalog_cache(self):
1445                 pickle_file = os.path.join(self.imgdir, "catalog/catalog.pkl")
1446                 try:
1447                         portable.remove(pickle_file)
1448                 except OSError, e:
1449                         if e.errno != errno.ENOENT:
1450                                 raise
1451 
1452         def destroy_catalog(self, auth_name):
1453                 try:
1454                         shutil.rmtree("%s/catalog/%s" %
1455                             (self.imgdir, auth_name))
1456                 except OSError, e:
1457                         if e.errno != errno.ENOENT:
1458                                 raise
1459 
1460         def fmri_is_same_pkg(self, cfmri, pfmri):
1461                 """Determine whether fmri and pfmri share the same package
1462                 name, even if they're not equivalent versions.  This
1463                 also checks if two packages with different names are actually
1464                 the same because of a rename operation."""
1465 
1466                 # If the catalog has a rename record that names fmri as a
1467                 # destination, it's possible that pfmri could be the same pkg by
1468                 # rename.
1469                 if cfmri.is_same_pkg(pfmri):
1470                         return True
1471 
1472                 # Get the catalog for the correct authority
1473                 cat = self.get_catalog(cfmri)
1474                 return cat.rename_is_same_pkg(cfmri, pfmri)
1475 
1476 
1477         def fmri_is_successor(self, cfmri, pfmri):
1478                 """Since the catalog keeps track of renames, it's no longer
1479                 sufficient to rely on the FMRI class to determine whether a
1480                 package is a successor.  This routine takes two FMRIs, and
1481                 if they have the same authority, checks if they've been
1482                 renamed.  If a rename has occurred, this runs the is_successor
1483                 routine from the catalog.  Otherwise, this runs the standard
1484                 fmri.is_successor() code."""
1485 
1486                 # Get the catalog for the correct authority
1487                 cat = self.get_catalog(cfmri)
1488 
1489                 # If the catalog has a rename record that names fmri as a
1490                 # destination, it's possible that pfmri could be a successor by
1491                 # rename.
1492                 if cfmri.is_successor(pfmri):
1493                         return True
1494                 else:
1495                         return cat.rename_is_successor(cfmri, pfmri)
1496 
1497         def gen_installed_pkg_names(self):
1498                 """Generate the string representation of all installed
1499                 packages. This is faster than going through gen_installed_pkgs
1500                 when all that will be done is to extract the strings from
1501                 the result.
1502                 """
1503                 if self.pkg_states is not None:
1504                         for i in self.pkg_states.values():
1505                                 yield i[1].get_fmri(anarchy=True)
1506                 else:
1507                         installed_state_dir = "%s/state/installed" % \
1508                             self.imgdir
1509                         if os.path.isdir(installed_state_dir):
1510                                 for pl in os.listdir(installed_state_dir):
1511                                         yield "pkg:/" + urllib.unquote(pl)
1512                         else:
1513                                 proot = "%s/pkg" % self.imgdir
1514                                 for pd in sorted(os.listdir(proot)):
1515                                         for vd in \
1516                                             sorted(os.listdir("%s/%s" %
1517                                             (proot, pd))):
1518                                                 path = "%s/%s/%s/installed" % \
1519                                                     (proot, pd, vd)
1520                                                 if not os.path.exists(path):
1521                                                         continue
1522 
1523                                                 yield urllib.unquote(
1524                                                     "pkg:/%s@%s" % (pd, vd))
1525 
1526         # This could simply call self.inventory() (or be replaced by inventory),
1527         # but it turns out to be about 20% slower.
1528         def gen_installed_pkgs(self):
1529                 """Return an iteration through the installed packages."""
1530                 self.__load_pkg_states()
1531                 return (i[1] for i in self.pkg_states.values())
1532 
1533         def __load_pkg_states(self):
1534                 """Build up the package state dictionary.
1535 
1536                 This dictionary maps the full fmri string to a tuple of the
1537                 state, the prefix of the authority from which it's installed,
1538                 and the fmri object.
1539 
1540                 Note that this dictionary only maps installed packages.  Use
1541                 get_pkg_state_by_fmri() to retrieve the state for arbitrary
1542                 packages.
1543                 """
1544 
1545                 if self.pkg_states is not None:
1546                         return
1547 
1548                 installed_state_dir = "%s/state/installed" % self.imgdir
1549 
1550                 self.pkg_states = {}
1551 
1552                 # If the state directory structure has already been created,
1553                 # loading information from it is fast.  The directory is
1554                 # populated with symlinks, named by their (url-encoded) FMRI,
1555                 # which point to the "installed" file in the corresponding
1556                 # directory under /var/pkg.
1557                 if os.path.isdir(installed_state_dir):
1558                         for pl in sorted(os.listdir(installed_state_dir)):
1559                                 fmristr = urllib.unquote(pl)
1560                                 tmpf = pkg.fmri.PkgFmri(fmristr)
1561                                 path = self._install_file(tmpf)
1562                                 auth = self.installed_file_authority(path)
1563                                 f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1564 
1565                                 self.pkg_states[fmristr] = \
1566                                     (PKG_STATE_INSTALLED, f)
1567 
1568                         return
1569 
1570                 # Otherwise, we must iterate through the earlier installed
1571                 # state.  One day, this can be removed.
1572                 proot = "%s/pkg" % self.imgdir
1573                 for pd in sorted(os.listdir(proot)):
1574                         for vd in sorted(os.listdir("%s/%s" % (proot, pd))):
1575                                 path = "%s/%s/%s/installed" % (proot, pd, vd)
1576                                 if not os.path.exists(path):
1577                                         continue
1578 
1579                                 fmristr = urllib.unquote("%s@%s" % (pd, vd))
1580                                 auth = self.installed_file_authority(path)
1581                                 f = pkg.fmri.PkgFmri(fmristr, authority = auth)
1582 
1583                                 self.pkg_states[fmristr] = \
1584                                     (PKG_STATE_INSTALLED, f)
1585 
1586         def clear_pkg_state(self):
1587                 self.pkg_states = None
1588                 self.__manifest_cache = {}
1589 
1590         def strtofmri(self, myfmri):
1591                 return pkg.fmri.PkgFmri(myfmri, self.attrs["Build-Release"])
1592 
1593         def strtomatchingfmri(self, myfmri):
1594                 return pkg.fmri.MatchingPkgFmri(myfmri, self.attrs["Build-Release"])
1595 
1596         def update_optional_dependency(self, inputfmri):
1597                 """Updates pkgname to min fmri mapping if fmri is newer"""
1598 
1599                 myfmri = inputfmri
1600 
1601                 try:
1602                         name = myfmri.get_name()
1603                 except AttributeError:
1604                         name = pkg.fmri.extract_pkg_name(myfmri)
1605                         myfmri = self.strtomatchingfmri(myfmri)
1606 
1607                 try:
1608                         myfmri = self.inventory([ myfmri ], all_known = True,
1609                             matcher = pkg.fmri.exact_name_match).next()[0]
1610                 except api_errors.InventoryException:
1611                         # If we didn't find the package in the authority it's
1612                         # currently installed from, try again without specifying
1613                         # the authority.  This will get the first available
1614                         # instance of the package preferring the preferred
1615                         # authority.  Make sure to unset the authority on a copy
1616                         # of myfmri, just in case myfmri is the same object as
1617                         # the input fmri.
1618                         myfmri = myfmri.copy()
1619                         myfmri.set_authority(None)
1620                         myfmri = self.inventory([ myfmri ], all_known = True,
1621                             matcher = pkg.fmri.exact_name_match).next()[0]
1622 
1623                 ofmri = self.optional_dependencies.get(name, None)
1624                 if not ofmri or self.fmri_is_successor(myfmri, ofmri):
1625                         self.optional_dependencies[name] = myfmri
1626 
1627         def apply_optional_dependencies(self, myfmri):
1628                 """Updates an fmri if optional dependencies require a newer version.
1629                 Doesn't handle catalog renames... to ease programming for now,
1630                 unversioned fmris are returned upgraded"""
1631 
1632                 try:
1633                         name = myfmri.get_name()
1634                 except AttributeError:
1635                         name = pkg.fmri.extract_pkg_name(myfmri)
1636                         myfmri = self.strtomatchingfmri(myfmri)
1637 
1638                 minfmri = self.optional_dependencies.get(name, None)
1639                 if not minfmri:
1640                         return myfmri
1641 
1642                 if self.fmri_is_successor(minfmri, myfmri):
1643                         return minfmri
1644                 return myfmri
1645 
1646         def load_optional_dependencies(self):
1647                 for fmri in self.gen_installed_pkgs():
1648                         mfst = self.get_manifest(fmri, filtered = True)
1649                         for dep in mfst.gen_actions_by_type("depend"):
1650                                 required, min_fmri, max_fmri = dep.parse(self)
1651                                 if required == False:
1652                                         self.update_optional_dependency(min_fmri)
1653 
1654         def get_user_by_name(self, name):
1655                 return portable.get_user_by_name(name, self.root,
1656                     self.type != IMG_USER)
1657 
1658         def get_name_by_uid(self, uid, returnuid = False):
1659                 # XXX What to do about IMG_PARTIAL?
1660                 try:
1661                         return portable.get_name_by_uid(uid, self.root,
1662                             self.type != IMG_USER)
1663                 except KeyError:
1664                         if returnuid:
1665                                 return uid
1666                         else:
1667                                 raise
1668 
1669         def get_group_by_name(self, name):
1670                 return portable.get_group_by_name(name, self.root,
1671                     self.type != IMG_USER)
1672 
1673         def get_name_by_gid(self, gid, returngid = False):
1674                 try:
1675                         return portable.get_name_by_gid(gid, self.root,
1676                             self.type != IMG_USER)
1677                 except KeyError:
1678                         if returngid:
1679                                 return gid
1680                         else:
1681                                 raise
1682 
1683         @staticmethod
1684         def __multimatch(name, patterns, matcher):
1685                 """Applies a matcher to a name across a list of patterns.
1686                 Returns all tuples of patterns which match the name.  Each tuple
1687                 contains the index into the original list, the pattern itself,
1688                 the package version, the authority, and the raw authority
1689                 string."""
1690                 return [
1691                     (i, pat, pat.tuple()[2],
1692                         pat.get_authority(), pat.get_authority_str())
1693                     for i, pat in enumerate(patterns)
1694                     if matcher(name, pat.tuple()[1])
1695                 ]
1696 
1697         def __inventory(self, patterns = None, all_known = False, matcher = None,
1698             constraint = pkg.version.CONSTRAINT_AUTO):
1699                 """Private method providing the back-end for inventory()."""
1700 
1701                 if not matcher:
1702                         matcher = pkg.fmri.fmri_match
1703 
1704                 if not patterns:
1705                         patterns = []
1706 
1707                 # Store the original patterns before we possibly turn them into
1708                 # PkgFmri objects, so we can give them back to the user in error
1709                 # messages.
1710                 opatterns = patterns[:]
1711 
1712                 illegals = []
1713                 for i, pat in enumerate(patterns):
1714                         if not isinstance(pat, pkg.fmri.PkgFmri):
1715                                 try:
1716                                         if "*" in pat or "?" in pat:
1717                                                 matcher = pkg.fmri.glob_match
1718                                                 patterns[i] = \
1719                                                     pkg.fmri.MatchingPkgFmri(pat,
1720                                                         "5.11")
1721                                         else:
1722                                                 patterns[i] = \
1723                                                     pkg.fmri.PkgFmri(pat,
1724                                                     "5.11")
1725                                 except pkg.fmri.IllegalFmri, e:
1726                                         illegals.append(e)
1727 
1728                 if illegals:
1729                         raise api_errors.InventoryException(illegal=illegals)
1730 
1731                 pauth = self.cfg_cache.preferred_authority
1732 
1733                 # matchingpats is the set of all the patterns which matched a
1734                 # package in the catalog.  This allows us to return partial
1735                 # failure if some patterns match and some don't.
1736                 # XXX It would be nice to keep track of why some patterns failed
1737                 # to match -- based on name, version, or authority.
1738                 matchingpats = set()
1739 
1740                 # XXX Perhaps we shouldn't sort here, but in the caller, to save
1741                 # memory?
1742                 for name in sorted(self._catalog.keys()):
1743                         # Eliminate all patterns not matching "name".  If there
1744                         # are no patterns left, go on to the next name, but only
1745                         # if there were any to start with.
1746                         matches = self.__multimatch(name, patterns, matcher)
1747                         if patterns and not matches:
1748                                 continue
1749 
1750                         newest = self._catalog[name]["versions"][-1]
1751                         for ver in reversed(self._catalog[name]["versions"]):
1752                                 # If a pattern specified a version and that
1753                                 # version isn't succeeded by "ver", then record
1754                                 # the pattern for removal from consideration.
1755                                 nomatch = []
1756                                 for i, match in enumerate(matches):
1757                                         if match[2] and \
1758                                             not ver.is_successor(match[2],
1759                                                 constraint):
1760                                                 nomatch.append(i)
1761 
1762                                 # Eliminate the name matches that didn't match
1763                                 # on versions.  We need to create a new list
1764                                 # because we need to reuse the original
1765                                 # "matches" for each new version.
1766                                 vmatches = [
1767                                     matches[i]
1768                                     for i, match in enumerate(matches)
1769                                     if i not in nomatch
1770                                 ]
1771 
1772                                 # If we deleted all contenders (if we had any to
1773                                 # begin with), go on to the next version.
1774                                 if matches and not vmatches:
1775                                         continue
1776 
1777                                 # Like the version skipping above, do the same
1778                                 # for authorities.
1779                                 authlist = set(self._catalog[name][str(ver)][1])
1780                                 nomatch = []
1781                                 for i, match in enumerate(vmatches):
1782                                         if match[3] and \
1783                                             match[3] not in authlist:
1784                                                 nomatch.append(i)
1785 
1786                                 amatches = [
1787                                     vmatches[i]
1788                                     for i, match in enumerate(vmatches)
1789                                     if i not in nomatch
1790                                 ]
1791 
1792                                 if vmatches and not amatches:
1793                                         continue
1794 
1795                                 # If no patterns were specified or any still-
1796                                 # matching pattern specified no authority, we
1797                                 # use the entire authlist for this version.
1798                                 # Otherwise, we use the intersection of authlist
1799                                 # and the auths in the patterns.
1800                                 aset = set(i[3] for i in amatches)
1801                                 if aset and None not in aset:
1802                                         authlist = set(
1803                                             m[3:5]
1804                                             for m in amatches
1805                                             if m[3] in authlist
1806                                         )
1807                                 else:
1808                                         authlist = zip(authlist, authlist)
1809 
1810                                 pfmri = self._catalog[name][str(ver)][0]
1811 
1812                                 inst_state = self.get_pkg_state_by_fmri(pfmri)
1813                                 inst_auth = self.get_pkg_auth_by_fmri(pfmri)
1814                                 state = {
1815                                     "upgradable": ver != newest,
1816                                     "frozen": False,
1817                                     "incorporated": False,
1818                                     "excludes": False
1819                                 }
1820 
1821                                 # We yield copies of the fmri objects in the
1822                                 # catalog because we add the authorities in, and
1823                                 # don't want to mess up the canonical catalog.
1824                                 # If a pattern had specified an authority as
1825                                 # preferred, be sure to emit an fmri that way,
1826                                 # too.
1827                                 yielded = False
1828                                 if all_known:
1829                                         for auth, rauth in authlist:
1830                                                 nfmri = pfmri.copy()
1831                                                 nfmri.set_authority(rauth,
1832                                                     auth == pauth)
1833                                                 if auth == inst_auth:
1834                                                         state["state"] = \
1835                                                             PKG_STATE_INSTALLED
1836                                                 else:
1837                                                         state["state"] = \
1838                                                             PKG_STATE_KNOWN
1839                                                 yield nfmri, state
1840                                                 yielded = True
1841                                 elif inst_state == PKG_STATE_INSTALLED:
1842                                         nfmri = pfmri.copy()
1843                                         nfmri.set_authority(inst_auth,
1844                                             inst_auth == pauth)
1845                                         state["state"] = inst_state
1846                                         yield nfmri, state
1847                                         yielded = True
1848 
1849                                 if yielded:
1850                                         matchingpats |= set(i[:2] for i in amatches)
1851 
1852                 nonmatchingpats = [
1853                     opatterns[i]
1854                     for i, f in set(enumerate(patterns)) - matchingpats
1855                 ]
1856                 if nonmatchingpats:
1857                         raise api_errors.InventoryException(
1858                             notfound=nonmatchingpats)
1859 
1860         def inventory(self, *args, **kwargs):
1861                 """Enumerate the package FMRIs in the image's catalog.
1862 
1863                 If "patterns" is None (the default) or an empty sequence, all
1864                 package names will match.  Otherwise, it is a list of patterns
1865                 to match against FMRIs in the catalog.
1866 
1867                 If "all_known" is False (the default), only installed packages
1868                 will be enumerated.  If True, all known packages will be
1869                 enumerated.
1870 
1871                 The "matcher" parameter should specify a function taking two
1872                 string arguments: a name and a pattern, returning True if the
1873                 pattern matches the name, and False otherwise.  By default, the
1874                 matcher will be pkg.fmri.fmri_match().
1875 
1876                 The "constraint" parameter defines how a version specified in a
1877                 pattern matches a version in the catalog.  By default, a natural
1878                 "subsetting" constraint is used."""
1879 
1880                 # "preferred" and "first_only" are private arguments that are
1881                 # currently only used in evaluate_fmri(), but could be made more
1882                 # generally useful.  "preferred" ensures that all potential
1883                 # matches from the preferred authority are generated before
1884                 # those from non-preferred authorities.  In the current
1885                 # implementation, this consumes more memory.  "first_only"
1886                 # signals us to return only the first match, which allows us to
1887                 # save all the memory that "preferred" currently eats up.
1888                 preferred = kwargs.pop("preferred", False)
1889                 first_only = kwargs.pop("first_only", False)
1890                 pauth = self.cfg_cache.preferred_authority
1891 
1892                 if not preferred:
1893                         for f in self.__inventory(*args, **kwargs):
1894                                 yield f
1895                 else:
1896                         nplist = []
1897                         firstnp = None
1898                         for f in self.__inventory(*args, **kwargs):
1899                                 if f[0].get_authority() == pauth:
1900                                         yield f
1901                                         if first_only:
1902                                                 return
1903                                 else:
1904                                         if first_only:
1905                                                 if not firstnp:
1906                                                         firstnp = f
1907                                         else:
1908                                                 nplist.append(f)
1909                         if first_only:
1910                                 yield firstnp
1911                                 return
1912 
1913                         for f in nplist:
1914                                 yield f
1915 
1916         def update_index_dir(self, postfix="index"):
1917                 """Since the index directory will not reliably be updated when
1918                 the image root is, this should be called prior to using the
1919                 index directory.
1920                 """
1921                 self.index_dir = os.path.join(self.imgdir, postfix)
1922 
1923         def degraded_local_search(self, args):
1924                 msg("Search capabilities and performance are degraded.\n"
1925                     "To improve, run 'pkg rebuild-index'.")
1926                 res = []
1927 
1928                 for fmri, mfst in self.get_fmri_manifest_pairs():
1929                         m = manifest.Manifest()
1930                         try:
1931                                 mcontent = file(mfst).read()
1932                         except EnvironmentError:
1933                                 # XXX log something?
1934                                 continue
1935                         m.set_content(mcontent)
1936                         new_dict = m.search_dict()
1937 
1938                         tok = args[0]
1939 
1940                         for tok_type in new_dict.keys():
1941                                 if new_dict[tok_type].has_key(tok):
1942                                         ak_list = new_dict[tok_type][tok]
1943                                         for action, keyval in ak_list:
1944                                                 res.append((tok_type, fmri, \
1945                                                     action, keyval))
1946                 return res
1947 
1948         def local_search(self, args, case_sensitive):
1949                 """Search the image for the token in args[0]."""
1950                 assert args[0]
1951                 self.update_index_dir()
1952                 qe = query_e.ClientQueryEngine(self.index_dir)
1953                 query = query_e.Query(args[0], case_sensitive)
1954                 try:
1955                         res = qe.search(query, self.gen_installed_pkg_names())
1956                 except search_errors.NoIndexException:
1957                         res = self.degraded_local_search(args)
1958                 return res
1959 
1960         def remote_search(self, args, servers = None):
1961                 """Search for the token in args[0] on the servers in 'servers'.
1962                 If 'servers' is empty or None, search on all known servers."""
1963                 failed = []
1964 
1965                 if not servers:
1966                         servers = self.gen_authorities()
1967 
1968                 for auth in servers:
1969                         ssl_tuple = self.get_ssl_credentials(
1970                             authority = auth.get("prefix", None),
1971                             origin = auth["origin"])
1972                         try:
1973                                 uuid = self.get_uuid(auth["prefix"])
1974                         except KeyError:
1975                                 uuid = None
1976 
1977                         try:
1978                                 res, v = versioned_urlopen(auth["origin"],
1979                                     "search", [0], urllib.quote(args[0], ""),
1980                                     ssl_creds=ssl_tuple, imgtype=self.type,
1981                                     uuid=uuid)
1982                         except urllib2.HTTPError, e:
1983                                 if e.code != httplib.NOT_FOUND:
1984                                         failed.append((auth, e))
1985                                 continue
1986                         except urllib2.URLError, e:
1987                                 failed.append((auth, e))
1988                                 continue
1989 
1990                         try:
1991                                 for line in res.read().splitlines():
1992                                         fields = line.split()
1993                                         if len(fields) < 4:
1994                                                 yield fields[:2] + [ "", "" ]
1995                                         else:
1996                                                 yield fields[:4]
1997                         except socket.timeout, e:
1998                                 failed.append((auth, e))
1999                                 continue
2000 
2001                 if failed:
2002                         raise RuntimeError, failed
2003 
2004         def incoming_download_dir(self):
2005                 """Return the directory path for incoming downloads
2006                 that have yet to be completed.  Once a file has been
2007                 successfully downloaded, it is moved to the cached download
2008                 directory."""
2009 
2010                 return self.dl_cache_incoming
2011 
2012         def cached_download_dir(self):
2013                 """Return the directory path for cached content.
2014                 Files that have been successfully downloaded live here."""
2015 
2016                 return self.dl_cache_dir
2017 
2018         def cleanup_downloads(self):
2019                 """Clean up any downloads that were in progress but that
2020                 did not successfully finish."""
2021 
2022                 shutil.rmtree(self.dl_cache_incoming, True)
2023 
2024         def cleanup_cached_content(self):
2025                 """Delete the directory that stores all of our cached
2026                 downloaded content.  This may take a while for a large
2027                 directory hierarchy.  Don't clean up caches if the
2028                 user overrode the underlying setting using PKG_CACHEDIR. """
2029 
2030                 if not self.is_user_cache_dir and \
2031                     self.cfg_cache.get_policy(imageconfig.FLUSH_CONTENT_CACHE):
2032                         msg("Deleting content cache")
2033                         shutil.rmtree(self.dl_cache_dir, True)
2034 
2035         def salvagedir(self, path):
2036                 """Called when directory contains something and it's not supposed
2037                 to because it's being deleted. XXX Need to work out a better error
2038                 passback mechanism. Path is rooted in /...."""
2039 
2040                 salvagedir = os.path.normpath(
2041                     os.path.join(self.imgdir, "lost+found",
2042                     path + "-" + time.strftime("%Y%m%dT%H%M%SZ")))
2043 
2044                 parent = os.path.dirname(salvagedir)
2045                 if not os.path.exists(parent):
2046                         os.makedirs(parent)
2047                 shutil.move(os.path.normpath(os.path.join(self.root, path)), salvagedir)
2048                 # XXX need a better way to do this.
2049                 emsg("\nWarning - directory %s not empty - contents preserved "
2050                         "in %s" % (path, salvagedir))
2051 
2052         def temporary_file(self):
2053                 """ create a temp file under image directory for various purposes"""
2054                 tempdir = os.path.normpath(os.path.join(self.imgdir, "tmp"))
2055                 if not os.path.exists(tempdir):
2056                         os.makedirs(tempdir)
2057                 fd, name = tempfile.mkstemp(dir=tempdir)
2058                 os.close(fd)
2059                 return name
2060 
2061         def expanddirs(self, dirs):
2062                 """given a set of directories, return expanded set that includes
2063                 all components"""
2064                 out = set()
2065                 for d in dirs:
2066                         p = d
2067                         while p != "":
2068                                 out.add(p)
2069                                 p = os.path.dirname(p)
2070                 return out
2071 
2072 
2073         def make_install_plan(self, pkg_list, prtrack, check_cancelation,
2074             noexecute, filters = None, verbose=False, use_cache=True):
2075                 """Take a list of packages, specified in pkg_list, and attempt
2076                 to assemble an appropriate image plan.  This is a helper
2077                 routine for some common operations in the client.
2078 
2079                 This method checks all authorities for a package match;
2080                 however, it defaults to choosing the preferred authority
2081                 when an ambiguous package name is specified.  If the user
2082                 wishes to install a package from a non-preferred authority,
2083                 the full FMRI that contains an authority should be used
2084                 to name the package."""
2085 
2086                 if filters is None:
2087                         filters = []
2088                 
2089                 error = 0
2090                 ip = imageplan.ImagePlan(self, prtrack, check_cancelation,
2091                     filters=filters, noexecute=noexecute, use_cache=use_cache)
2092 
2093                 self.load_optional_dependencies()
2094 
2095                 unfound_fmris = []
2096                 multiple_matches = []
2097                 
2098                 for p in pkg_list:
2099                         try:
2100                                 conp = self.apply_optional_dependencies(p)
2101                                 matches = list(self.inventory([ conp ],
2102                                     all_known = True))
2103                         except api_errors.InventoryException:
2104                                 error = 1
2105                                 unfound_fmris.append(p)
2106                                 continue
2107 
2108                         pnames = {}
2109                         pmatch = []
2110                         npnames = {}
2111                         npmatch = []
2112                         for m, state in matches:
2113                                 if m.preferred_authority():
2114                                         pnames[m.get_pkg_stem()] = 1
2115                                         pmatch.append(m)
2116                                 else:
2117                                         npnames[m.get_pkg_stem()] = 1
2118                                         npmatch.append(m)
2119 
2120                         if len(pnames.keys()) > 1:
2121                                 multiple_matches.append((p, pnames.keys()))
2122                                 error = 1
2123                                 continue
2124                         elif len(pnames.keys()) < 1 and len(npnames.keys()) > 1:
2125                                 multiple_matches.append((p, pnames.keys()))
2126                                 error = 1
2127                                 continue
2128 
2129                         # matches is a list reverse sorted by version, so take
2130                         # the first; i.e., the latest.
2131                         if len(pmatch) > 0:
2132                                 ip.propose_fmri(pmatch[0])
2133                         else:
2134                                 ip.propose_fmri(npmatch[0])
2135 
2136                 if error != 0:
2137                         raise api_errors.PlanCreationException(unfound_fmris,
2138                             multiple_matches, None)
2139 
2140                 if verbose:
2141                         msg(_("Before evaluation:"))
2142                         msg(ip)
2143 
2144                 # A plan can be requested without actually performing an
2145                 # operation on the image.
2146                 if self.history.operation_name:
2147                         self.history.operation_start_state = ip.get_plan()
2148 
2149                 ip.evaluate()
2150                 self.imageplan = ip
2151 
2152                 if self.history.operation_name:
2153                         self.history.operation_end_state = \
2154                             ip.get_plan(full=False)
2155 
2156                 if verbose:
2157                         msg(_("After evaluation:"))
2158                         msg(ip.display())
2159 
2160         def make_uninstall_plan(self, fmri_list, recursive_removal,
2161             progresstracker, check_cancelation, noexecute, verbose=False,
2162             use_cache=True):
2163                 ip = imageplan.ImagePlan(self, progresstracker,
2164                     check_cancelation, recursive_removal, 
2165                     noexecute=noexecute, use_cache=use_cache)
2166 
2167                 err = 0
2168 
2169                 unfound_fmris = []
2170                 multiple_matches = []
2171                 missing_matches = []
2172 
2173                 for ppat in fmri_list:
2174                         try:
2175                                 matches = list(self.inventory([ppat]))
2176                         except api_errors.InventoryException:
2177                                 unfound_fmris.append(ppat)
2178                                 err = 1
2179                                 continue
2180 
2181                         if len(matches) > 1:
2182                                 multiple_matches.append((ppat, matches))
2183                                 err = 1
2184                                 continue
2185 
2186                         if len(matches) < 1:
2187                                 missing_matches.append(ppat)
2188                                 err = 1
2189                                 continue
2190 
2191                         # Propose the removal of the first (and only!) match.
2192                         ip.propose_fmri_removal(matches[0][0])
2193 
2194                 if err == 1:
2195                         raise api_errors.PlanCreationException(unfound_fmris,
2196                             multiple_matches, missing_matches)
2197                 if verbose:
2198                         msg(_("Before evaluation:"))
2199                         msg(ip)
2200 
2201                 self.history.operation_start_state = ip.get_plan()
2202                 ip.evaluate()
2203                 self.history.operation_end_state = ip.get_plan(full=False)
2204                 self.imageplan = ip
2205 
2206                 if verbose:
2207                         msg(_("After evaluation:"))
2208                         ip.display()
2209 
2210         def ipkg_is_up_to_date(self, actual_cmd, check_cancelation, noexecute,
2211             refresh_catalogs):
2212                 """ Test whether SUNWipkg is updated to the latest version
2213                     known to be available for this image """
2214                 #
2215                 # This routine makes the distinction between the "target image",
2216                 # which will be altered, and the "running image", which is
2217                 # to say whatever image appears to contain the version of the 
2218                 # pkg command we're running.
2219                 #
2220 
2221                 #
2222                 # There are two relevant cases here:
2223                 #     1) Packaging code and image we're updating are the same
2224                 #        image.  (i.e. 'pkg image-update')
2225                 #
2226                 #     2) Packaging code's image and the image we're updating are
2227                 #        different (i.e. 'pkg image-update -R')
2228                 #
2229                 # In general, we care about getting the user to run the
2230                 # most recent packaging code available for their build.  So,
2231                 # if we're not in the liveroot case, we create a new image
2232                 # which represents "/" on the system.
2233                 #
2234 
2235                 # always quiet for this part of the code.
2236                 progresstracker = progress.QuietProgressTracker()
2237 
2238                 img = self
2239                 
2240                 if not img.is_liveroot():
2241                         newimg = Image()
2242                         cmdpath = os.path.join(os.getcwd(), actual_cmd)
2243                         cmdpath = os.path.realpath(cmdpath)
2244                         cmddir = os.path.dirname(os.path.realpath(cmdpath))
2245                         #
2246                         # Find the path to ourselves, and use that
2247                         # as a way to locate the image we're in.  It's
2248                         # not perfect-- we could be in a developer's
2249                         # workspace, for example.
2250                         #
2251                         newimg.find_root(cmddir)
2252                         newimg.load_config()
2253 
2254                         if refresh_catalogs:
2255                                 # Refresh the catalog, so that we can discover
2256                                 # if a new SUNWipkg is available.
2257                                 try:
2258                                         newimg.retrieve_catalogs()
2259                                 except api_errors.CatalogRefreshException, cre:
2260                                         cre.message = \
2261                                             _("SUNWipkg update check failed.")
2262                                         raise
2263 
2264                         # Load catalog.
2265                         newimg.load_catalogs(progresstracker)
2266                         img = newimg
2267 
2268                 # XXX call to progress tracker that SUNWipkg is being checked
2269 
2270                 img.make_install_plan(["SUNWipkg"], progresstracker,
2271                     check_cancelation, noexecute, filters = [])
2272 
2273                 return img.imageplan.nothingtodo()
2274 
2275         def installed_fmris_from_args(self, args):
2276                 """Helper function to translate client command line arguments
2277                 into a list of installed fmris.  Used by info, contents,
2278                 verify.
2279                 """
2280                 found = []
2281                 notfound = []
2282                 illegals = []
2283                 try:
2284                         for m in self.inventory(args):
2285                                 found.append(m[0])
2286                 except api_errors.InventoryException, e:
2287                         illegals = e.illegal
2288                         notfound = e.notfound
2289 
2290                 return found, notfound, illegals
2291 
2292         def rebuild_search_index(self, progtracker):
2293                 """Rebuilds the search indexes.  Removes all
2294                 existing indexes and replaces them from scratch rather than
2295                 performing the incremental update which is usually used."""
2296                 self.update_index_dir()
2297                 if not os.path.isdir(self.index_dir):
2298                         self.mkdirs()
2299                 ind = indexer.Indexer(self.index_dir,
2300                     CLIENT_DEFAULT_MEM_USE_KB, progtracker)
2301                 ind.rebuild_index_from_scratch(self.get_fmri_manifest_pairs())