Home | History | Annotate | Download | only in client
      1 #!/usr/bin/python
      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 #
     24 # Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
     25 # Use is subject to license terms.
     26 #
     27 
     28 """This module provides the supported, documented interface for clients to
     29 interface with the pkg(5) system.
     30 
     31 Refer to pkg.api_common for additional core class documentation."""
     32 
     33 import copy
     34 import errno
     35 import fnmatch
     36 import os
     37 import shutil
     38 import sys
     39 import threading
     40 import urllib
     41 
     42 import pkg.client.actuator as actuator
     43 import pkg.client.api_errors as api_errors
     44 import pkg.client.bootenv as bootenv
     45 import pkg.client.history as history
     46 import pkg.client.image as image
     47 import pkg.client.imagetypes as imgtypes
     48 import pkg.client.indexer as indexer
     49 import pkg.client.publisher as publisher
     50 import pkg.client.query_parser as query_p
     51 import pkg.fmri as fmri
     52 import pkg.misc as misc
     53 import pkg.nrlock
     54 import pkg.p5i as p5i
     55 import pkg.search_errors as search_errors
     56 import pkg.version
     57 
     58 from pkg.api_common import (PackageInfo, LicenseInfo, PackageCategory,
     59     _get_pkg_cat_data)
     60 from pkg.client.imageplan import EXECUTED_OK
     61 from pkg.client import global_settings
     62 
     63 CURRENT_API_VERSION = 32
     64 CURRENT_P5I_VERSION = 1
     65 
     66 # Image type constants.
     67 IMG_TYPE_NONE = imgtypes.IMG_NONE # No image.
     68 IMG_TYPE_ENTIRE = imgtypes.IMG_ENTIRE # Full image ('/').
     69 IMG_TYPE_PARTIAL = imgtypes.IMG_PARTIAL  # Not yet implemented.
     70 IMG_TYPE_USER = imgtypes.IMG_USER # Not '/'; some other location.
     71 
     72 logger = global_settings.logger
     73 
     74 class ImageInterface(object):
     75         """This class presents an interface to images that clients may use.
     76         There is a specific order of methods which must be used to install
     77         or uninstall packages, or update an image. First, plan_install,
     78         plan_uninstall, plan_update_all or plan_change_variant must be
     79         called.  After that method completes successfully, describe may be
     80         called, and prepare must be called. Finally, execute_plan may be
     81         called to implement the previous created plan. The other methods
     82         do not have an ordering imposed upon them, and may be used as
     83         needed. Cancel may only be invoked while a cancelable method is
     84         running."""
     85 
     86         # Constants used to reference specific values that info can return.
     87         INFO_FOUND = 0
     88         INFO_MISSING = 1
     89         INFO_MULTI_MATCH = 2
     90         INFO_ILLEGALS = 3
     91 
     92         LIST_ALL = 0
     93         LIST_INSTALLED = 1
     94         LIST_INSTALLED_NEWEST = 2
     95         LIST_NEWEST = 3
     96         LIST_UPGRADABLE = 4
     97 
     98         # Private constants used for tracking which type of plan was made.
     99         __INSTALL = 1
    100         __UNINSTALL = 2
    101         __IMAGE_UPDATE = 3
    102 
    103         def __init__(self, img_path, version_id, progresstracker,
    104             cancel_state_callable, pkg_client_name):
    105                 """Constructs an ImageInterface object.
    106 
    107                 'img_path' is the absolute path to an existing image.
    108 
    109                 'version_id' indicates the version of the api the client is
    110                 expecting to use.
    111 
    112                 'progresstracker' is the ProgressTracker object the client wants
    113                 the api to use for UI progress callbacks.
    114 
    115                 'cancel_state_callable' is an optional function reference that
    116                 will be called if the cancellable status of an operation
    117                 changes.
    118 
    119                 'pkg_client_name' is a string containing the name of the client,
    120                 such as "pkg" or "packagemanager".
    121 
    122                 This function can raise VersionException and
    123                 ImageNotFoundException."""
    124 
    125                 compatible_versions = set([CURRENT_API_VERSION])
    126 
    127                 if version_id not in compatible_versions:
    128                         raise api_errors.VersionException(CURRENT_API_VERSION,
    129                             version_id)
    130 
    131                 # The image's History object will use client_name from
    132                 # global_settings, but if the program forgot to set it,
    133                 # we'll go ahead and do so here.
    134                 if global_settings.client_name is None:
    135                         global_settings.client_name = pkg_client_name
    136 
    137                 if isinstance(img_path, basestring):
    138                         # Store this for reset().
    139                         self.__img_path = img_path
    140                         self.__img = image.Image(img_path,
    141                             progtrack=progresstracker)
    142                 elif isinstance(img_path, image.Image):
    143                         # This is a temporary, special case for client.py
    144                         # until the image api is complete.
    145                         self.__img = img_path
    146                         self.__img_path = img_path.get_root()
    147                 else:
    148                         # API consumer passed an unknown type for img_path.
    149                         raise TypeError(_("Unknown img_path type."))
    150 
    151                 self.__progresstracker = progresstracker
    152                 self.__cancel_state_callable = cancel_state_callable
    153                 self.__plan_type = None
    154                 self.__plan_desc = None
    155                 self.__prepared = False
    156                 self.__executed = False
    157                 self.__be_name = None
    158                 self.__can_be_canceled = False
    159                 self.__canceling = False
    160                 self.__activity_lock = pkg.nrlock.NRLock()
    161                 self.__blocking_locks = False
    162                 self.__img.blocking_locks = self.__blocking_locks
    163                 self.__cancel_lock = pkg.nrlock.NRLock()
    164                 self.__cancel_cv = threading.Condition(self.__cancel_lock)
    165 
    166         def __set_blocking_locks(self, value):
    167                 self.__activity_lock.acquire()
    168                 self.__blocking_locks = value
    169                 self.__img.blocking_locks = value
    170                 self.__activity_lock.release()
    171 
    172         blocking_locks = property(lambda self: self.__blocking_locks,
    173             __set_blocking_locks, doc="A boolean value indicating whether "
    174             "the API should wait until the image interface can be locked if "
    175             "it is in use by another thread or process.  Clients should be "
    176             "aware that there is no timeout mechanism in place if blocking is "
    177             "enabled, and so should take steps to remain responsive to user "
    178             "input or provide a way for users to cancel operations.")
    179 
    180         @property
    181         def img(self):
    182                 """Private; public access to this property will be removed at
    183                 a later date.  Do not use."""
    184                 return self.__img
    185 
    186         @property
    187         def img_type(self):
    188                 """Returns the IMG_TYPE constant for the image's type."""
    189                 if not self.__img:
    190                         return None
    191                 return self.__img.image_type(self.__img.root)
    192 
    193         @property
    194         def is_zone(self):
    195                 """A boolean value indicating whether the image is a zone."""
    196                 return self.__img.is_zone()
    197 
    198         @property
    199         def root(self):
    200                 """The absolute pathname of the filesystem root of the image.
    201                 This property is read-only."""
    202                 if not self.__img:
    203                         return None
    204                 return self.__img.root
    205 
    206         @staticmethod
    207         def check_be_name(be_name):
    208                 bootenv.BootEnv.check_be_name(be_name)
    209                 return True
    210 
    211         def __cert_verify(self, log_op_end=None):
    212                 """Verify validity of certificates.  Any
    213                 api_errors.ExpiringCertificate exceptions are caught
    214                 here, a message is displayed, and execution continues.
    215                 All other exceptions will be passed to the calling
    216                 context.  The caller can also set log_op_end to a list
    217                 of exceptions that should result in a call to
    218                 self.log_operation_end() before the exception is passed
    219                 on."""
    220 
    221                 if log_op_end == None:
    222                         log_op_end = []
    223 
    224                 # we always explicitly handle api_errors.ExpiringCertificate
    225                 assert api_errors.ExpiringCertificate not in log_op_end
    226 
    227                 try:
    228                         self.__img.check_cert_validity()
    229                 except api_errors.ExpiringCertificate, e:
    230                         logger.error(e)
    231                 except:
    232                         exc_type, exc_value, exc_traceback = sys.exc_info()
    233                         if exc_type in log_op_end:
    234                                 self.log_operation_end(error=exc_value)
    235                         raise
    236 
    237         def __refresh_publishers(self):
    238                 """Refresh publisher metadata."""
    239 
    240                 #
    241                 # Verify validity of certificates before possibly
    242                 # attempting network operations.
    243                 #
    244                 self.__cert_verify()
    245                 self.__img.refresh_publishers(immediate=True,
    246                     progtrack=self.__progresstracker)
    247 
    248         def __acquire_activity_lock(self):
    249                 """Private helper method to aqcuire activity lock."""
    250 
    251                 rc = self.__activity_lock.acquire(
    252                     blocking=self.__blocking_locks)
    253                 if not rc:
    254                         raise api_errors.ImageLockedError()
    255 
    256         def __plan_common_start(self, operation, noexecute):
    257                 """Start planning an operation.  Acquire locks and log
    258                 the start of the operation."""
    259 
    260                 self.__acquire_activity_lock()
    261                 try:
    262                         self.__enable_cancel()
    263                         if self.__plan_type is not None:
    264                                 raise api_errors.PlanExistsException(
    265                                     self.__plan_type)
    266                         self.__img.lock(allow_unprivileged=noexecute)
    267                 except:
    268                         self.__activity_lock.release()
    269                         raise
    270 
    271                 assert self.__activity_lock._is_owned()
    272                 self.log_operation_start(operation)
    273 
    274         def __plan_common_finish(self):
    275                 """Finish planning an operation."""
    276 
    277                 assert self.__activity_lock._is_owned()
    278                 self.__img.cleanup_downloads()
    279                 self.__img.unlock()
    280                 self.__activity_lock.release()
    281 
    282         def __plan_common_exception(self, log_op_end=None):
    283                 """Deal with exceptions that can occur while planning an
    284                 operation.  Any exceptions generated here are passed
    285                 onto the calling context.  By default all exceptions
    286                 will result in a call to self.log_operation_end() before
    287                 they are passed onto the calling context.  Optionally,
    288                 the caller can specify the exceptions that should result
    289                 in a call to self.log_operation_end() by setting
    290                 log_op_end."""
    291 
    292                 if log_op_end == None:
    293                         log_op_end = []
    294 
    295                 # we always explicity handle api_errors.PlanCreationException
    296                 assert api_errors.PlanCreationException not in log_op_end
    297 
    298                 exc_type, exc_value, exc_traceback = sys.exc_info()
    299 
    300                 if exc_type == api_errors.PlanCreationException:
    301                         self.__set_history_PlanCreationException(exc_value)
    302                 elif exc_type == api_errors.CanceledException:
    303                         self.__cancel_done()
    304                 elif not log_op_end or exc_type in log_op_end:
    305                         self.log_operation_end(error=exc_value)
    306 
    307                 if exc_type != api_errors.ImageLockedError:
    308                         # Must be called before reset_unlock, and only if
    309                         # the exception was not a locked error.
    310                         self.__img.unlock()
    311 
    312                 self.__reset_unlock()
    313                 self.__activity_lock.release()
    314                 raise
    315 
    316         def plan_install(self, pkg_list, refresh_catalogs=True,
    317             noexecute=False, verbose=False, update_index=True):
    318                 """Contructs a plan to install the packages provided in
    319                 pkg_list.  pkg_list is a list of packages to install.
    320                 refresh_catalogs controls whether the catalogs will
    321                 automatically be refreshed. noexecute determines whether the
    322                 history will be recorded after planning is finished.  verbose
    323                 controls whether verbose debugging output will be printed to the
    324                 terminal.  Its existence is temporary.  It returns a boolean
    325                 which tells the client whether there is anything to do.  It can
    326                 raise PlanCreationException, PermissionsException and
    327                 InventoryException. The noexecute argument is included for
    328                 compatibility with operational history.  The hope is it can be
    329                 removed in the future."""
    330 
    331                 self.__plan_common_start("install", noexecute)
    332                 try:
    333                         if refresh_catalogs:
    334                                 self.__refresh_publishers()
    335 
    336                         self.__img.make_install_plan(pkg_list,
    337                             self.__progresstracker,
    338                             self.__check_cancelation, noexecute,
    339                             verbose=verbose)
    340 
    341                         assert self.__img.imageplan
    342 
    343                         self.__disable_cancel()
    344 
    345                         if not noexecute:
    346                                 self.__plan_type = self.__INSTALL
    347 
    348                         self.__plan_desc = PlanDescription(self.__img)
    349                         if self.__img.imageplan.nothingtodo() or noexecute:
    350                                 self.log_operation_end(
    351                                     result=history.RESULT_NOTHING_TO_DO)
    352 
    353                         self.__img.imageplan.update_index = update_index
    354                 except:
    355                         self.__plan_common_exception(log_op_end=[
    356                             api_errors.CanceledException, fmri.IllegalFmri,
    357                             Exception])
    358                         # NOTREACHED
    359 
    360                 self.__plan_common_finish()
    361                 res = not self.__img.imageplan.nothingtodo()
    362                 return res
    363 
    364         def plan_uninstall(self, pkg_list, recursive_removal, noexecute=False,
    365             verbose=False, update_index=True):
    366                 """Contructs a plan to uninstall the packages provided in
    367                 pkg_list. pkg_list is a list of packages to install.
    368                 recursive_removal controls whether recursive removal is
    369                 allowed. noexecute determines whether the history will be
    370                 recorded after planning is finished. verbose controls whether
    371                 verbose debugging output will be printed to the terminal. Its
    372                 existence is temporary. If there are things to do to complete
    373                 the uninstall, it returns True, otherwise it returns False. It
    374                 can raise NonLeafPackageException and PlanCreationException."""
    375 
    376                 self.__plan_common_start("uninstall", noexecute)
    377 
    378                 try:
    379                         self.__img.make_uninstall_plan(pkg_list,
    380                             recursive_removal, self.__progresstracker,
    381                             self.__check_cancelation, noexecute,
    382                             verbose=verbose)
    383 
    384                         assert self.__img.imageplan
    385 
    386                         self.__disable_cancel()
    387 
    388                         if not noexecute:
    389                                 self.__plan_type = self.__UNINSTALL
    390 
    391                         self.__plan_desc = PlanDescription(self.__img)
    392                         if noexecute:
    393                                 self.log_operation_end(
    394                                     result=history.RESULT_NOTHING_TO_DO)
    395                         self.__img.imageplan.update_index = update_index
    396                 except:
    397                         self.__plan_common_exception()
    398                         # NOTREACHED
    399 
    400                 self.__plan_common_finish()
    401                 res = not self.__img.imageplan.nothingtodo()
    402                 return res
    403 
    404         def plan_update_all(self, actual_cmd, refresh_catalogs=True,
    405             noexecute=False, force=False, verbose=False, update_index=True,
    406             be_name=None):
    407                 """Creates a plan to update all packages on the system to the
    408                 latest known versions.  actual_cmd is the command used to start
    409                 the client.  It is used to determine the image to check whether
    410                 SUNWipkg is up to date.  refresh_catalogs controls whether the
    411                 catalogs will automatically be refreshed.  noexecute determines
    412                 whether the history will be recorded after planning is finished.
    413                 force controls whether update should proceed even if ipkg is not
    414                 up to date.  verbose controls whether verbose debugging output
    415                 will be printed to the terminal.  Its existence is temporary. It
    416                 returns a tuple of two things.  The first is a boolean which
    417                 tells the client whether there is anything to do.  The second
    418                 tells whether the image is an opensolaris image.  It can raise
    419                 CatalogRefreshException, IpkgOutOfDateException,
    420                 PlanCreationException and PermissionsException."""
    421 
    422                 self.__plan_common_start("image-update", noexecute)
    423                 try:
    424                         self.check_be_name(be_name)
    425                         self.__be_name = be_name
    426 
    427                         if refresh_catalogs:
    428                                 self.__refresh_publishers()
    429 
    430                         # If we can find SUNWipkg and SUNWcs in the
    431                         # target image, then we assume this is a valid
    432                         # opensolaris image, and activate some
    433                         # special case behaviors.
    434                         opensolaris_image = True
    435                         fmris, notfound, illegals = \
    436                             self.__img.installed_fmris_from_args(
    437                                 ["SUNWipkg", "SUNWcs"])
    438                         assert(len(illegals) == 0)
    439                         if notfound:
    440                                 opensolaris_image = False
    441 
    442                         if opensolaris_image and not force:
    443                                 try:
    444                                         if not self.__img.ipkg_is_up_to_date(
    445                                             actual_cmd,
    446                                             self.__check_cancelation,
    447                                             noexecute,
    448                                             refresh_allowed=refresh_catalogs,
    449                                             progtrack=self.__progresstracker):
    450                                                 raise api_errors.IpkgOutOfDateException()
    451                                 except api_errors.ImageNotFoundException:
    452                                         # Can't do anything in this
    453                                         # case; so proceed.
    454                                         pass
    455 
    456                         self.__img.make_update_plan(self.__progresstracker,
    457                             self.__check_cancelation, noexecute,
    458                             verbose=verbose)
    459 
    460                         assert self.__img.imageplan
    461 
    462                         self.__disable_cancel()
    463 
    464                         if not noexecute:
    465                                 self.__plan_type = self.__IMAGE_UPDATE
    466 
    467                         self.__plan_desc = PlanDescription(self.__img)
    468 
    469                         if self.__img.imageplan.nothingtodo() or noexecute:
    470                                 self.log_operation_end(
    471                                     result=history.RESULT_NOTHING_TO_DO)
    472                         self.__img.imageplan.update_index = update_index
    473 
    474                 except:
    475                         self.__plan_common_exception(
    476                             log_op_end=[api_errors.IpkgOutOfDateException])
    477                         # NOTREACHED
    478 
    479                 self.__plan_common_finish()
    480                 res = not self.__img.imageplan.nothingtodo()
    481                 return res, opensolaris_image
    482 
    483         def plan_change_varcets(self, variants=None, facets=None,
    484             noexecute=False, verbose=False, be_name=None):
    485                 """Creates a plan to change the specified variants/facets on an image.
    486                 verbose controls
    487                 whether verbose debugging output will be printed to the
    488                 terminal.  This function has two return values.  The first is
    489                 a boolean which tells the client whether there is anything to
    490                 do.  The third is either None, or an exception which indicates
    491                 partial success.  This is currently used to indicate a failure
    492                 in refreshing catalogs. It can raise CatalogRefreshException,
    493                 IpkgOutOfDateException, NetworkUnavailableException,
    494                 PlanCreationException and PermissionsException."""
    495 
    496                 self.__plan_common_start("change-variant", noexecute)
    497                 if not variants and not facets:
    498                         raise ValueError, "Nothing to do"
    499                 try:
    500                         self.check_be_name(be_name)
    501                         self.be_name = be_name
    502 
    503                         self.__refresh_publishers()
    504 
    505                         self.__img.image_change_varcets(variants,
    506                             facets,
    507                             self.__progresstracker,
    508                             self.__check_cancelation,
    509                             noexecute, verbose=verbose)
    510 
    511                         assert self.__img.imageplan
    512 
    513                         self.__disable_cancel()
    514 
    515                         if not noexecute:
    516                                 self.__plan_type = self.__IMAGE_UPDATE
    517 
    518                         self.__plan_desc = PlanDescription(self.__img)
    519 
    520                         if self.__img.imageplan.nothingtodo() or noexecute:
    521                                 self.log_operation_end(
    522                                     result=history.RESULT_NOTHING_TO_DO)
    523 
    524                         #
    525                         # We always rebuild the search index after a
    526                         # variant change
    527                         #
    528                         self.__img.imageplan.update_index = True
    529 
    530                 except:
    531                         self.__plan_common_exception()
    532                         # NOTREACHED
    533 
    534                 self.__plan_common_finish()
    535                 res = not self.__img.imageplan.nothingtodo()
    536                 return res
    537 
    538         def describe(self):
    539                 """Returns None if no plan is ready yet, otherwise returns
    540                 a PlanDescription."""
    541 
    542                 return self.__plan_desc
    543 
    544         def prepare(self):
    545                 """Takes care of things which must be done before the plan
    546                 can be executed. This includes downloading the packages to
    547                 disk and preparing the indexes to be updated during
    548                 execution. It can raise ProblematicPermissionsIndexException,
    549                 and PlanMissingException. Should only be called once a
    550                 plan_X method has been called."""
    551 
    552                 self.__acquire_activity_lock()
    553                 try:
    554                         self.__img.lock()
    555                 except:
    556                         self.__activity_lock.release()
    557                         raise
    558 
    559                 try:
    560                         if not self.__img.imageplan:
    561                                 raise api_errors.PlanMissingException()
    562 
    563                         if self.__prepared:
    564                                 raise api_errors.AlreadyPreparedException()
    565                         assert self.__plan_type == self.__INSTALL or \
    566                             self.__plan_type == self.__UNINSTALL or \
    567                             self.__plan_type == self.__IMAGE_UPDATE
    568 
    569                         self.__enable_cancel()
    570 
    571                         try:
    572                                 self.__img.imageplan.preexecute()
    573                         except search_errors.ProblematicPermissionsIndexException, e:
    574                                 raise api_errors.ProblematicPermissionsIndexException(e)
    575                         except:
    576                                 raise
    577 
    578                         self.__disable_cancel()
    579                         self.__prepared = True
    580                 except api_errors.CanceledException, e:
    581                         self.__cancel_done()
    582                         if self.__img.history.operation_name:
    583                                 # If an operation is in progress, log
    584                                 # the error and mark its end.
    585                                 self.log_operation_end(error=e)
    586                         raise
    587                 except Exception, e:
    588                         self.__cancel_cleanup_exception()
    589                         if self.__img.history.operation_name:
    590                                 # If an operation is in progress, log
    591                                 # the error and mark its end.
    592                                 self.log_operation_end(error=e)
    593                         raise
    594                 except:
    595                         # Handle exceptions that are not subclasses of
    596                         # Exception.
    597                         self.__cancel_cleanup_exception()
    598                         if self.__img.history.operation_name:
    599                                 # If an operation is in progress, log
    600                                 # the error and mark its end.
    601                                 exc_type, exc_value, exc_traceback = \
    602                                     sys.exc_info()
    603                                 self.log_operation_end(error=exc_type)
    604                         raise
    605                 finally:
    606                         self.__img.cleanup_downloads()
    607                         self.__img.unlock()
    608                         self.__activity_lock.release()
    609 
    610         def execute_plan(self):
    611                 """Executes the plan. This is uncancelable one it begins. It
    612                 can raise CorruptedIndexException, ImageLockedError,
    613                 ProblematicPermissionsIndexException, ImageplanStateException,
    614                 ImageUpdateOnLiveImageException, and PlanMissingException.
    615                 Should only be called after the prepare method has been
    616                 called."""
    617 
    618                 self.__acquire_activity_lock()
    619                 try:
    620                         self.__disable_cancel()
    621                         self.__img.lock()
    622                 except:
    623                         self.__activity_lock.release()
    624                         raise
    625 
    626                 try:
    627                         if not self.__img.imageplan:
    628                                 raise api_errors.PlanMissingException()
    629 
    630                         if not self.__prepared:
    631                                 raise api_errors.PrematureExecutionException()
    632 
    633                         if self.__executed:
    634                                 raise api_errors.AlreadyExecutedException()
    635 
    636                         assert self.__plan_type == self.__INSTALL or \
    637                             self.__plan_type == self.__UNINSTALL or \
    638                             self.__plan_type == self.__IMAGE_UPDATE
    639 
    640                         try:
    641                                 be = bootenv.BootEnv(self.__img.get_root())
    642                         except RuntimeError:
    643                                 be = bootenv.BootEnvNull(self.__img.get_root())
    644                         self.__img.bootenv = be
    645 
    646                         if self.__plan_type is self.__IMAGE_UPDATE:
    647                                 try:
    648                                         be.init_image_recovery(self.__img,
    649                                             self.__be_name)
    650                                 except Exception, e:
    651                                         self.log_operation_end(error=e)
    652                                         raise
    653                                 except:
    654                                         # Handle exceptions that are not
    655                                         # subclasses of Exception.
    656                                         exc_type, exc_value, exc_traceback = \
    657                                             sys.exc_info()
    658                                         self.log_operation_end(error=exc_type)
    659                                         raise
    660 
    661                                 if self.__img.is_liveroot():
    662                                         e = api_errors.ImageUpdateOnLiveImageException()
    663                                         self.log_operation_end(error=e)
    664                                         raise e
    665                         else:
    666                                 if self.__img.imageplan.reboot_needed() and \
    667                                     self.__img.is_liveroot():
    668                                         e = api_errors.RebootNeededOnLiveImageException()
    669                                         self.log_operation_end(error=e)
    670                                         raise e
    671 
    672                         try:
    673                                 self.__img.imageplan.execute()
    674                         except RuntimeError, e:
    675                                 if self.__plan_type is self.__IMAGE_UPDATE:
    676                                         be.restore_image()
    677                                 else:
    678                                         be.restore_install_uninstall()
    679                                 # Must be done after bootenv restore.
    680                                 self.log_operation_end(error=e)
    681                                 raise
    682                         except search_errors.ProblematicPermissionsIndexException, e:
    683                                 error = api_errors.ProblematicPermissionsIndexException(e)
    684                                 self.log_operation_end(error=error)
    685                                 raise error
    686                         except (search_errors.InconsistentIndexException,
    687                                 search_errors.PartialIndexingException), e:
    688                                 error = api_errors.CorruptedIndexException(e)
    689                                 self.log_operation_end(error=error)
    690                                 raise error
    691                         except search_errors.MainDictParsingException, e:
    692                                 error = api_errors.MainDictParsingException(e)
    693                                 self.log_operation_end(error=error)
    694                                 raise error
    695                         except actuator.NonzeroExitException, e:
    696                                 # Won't happen during image-update
    697                                 be.restore_install_uninstall()
    698                                 error = api_errors.ActuatorException(e)
    699                                 self.log_operation_end(error=error)
    700                                 raise error
    701                         except api_errors.WrapIndexingException, e:
    702                                 self.__finished_execution(be)
    703                                 raise
    704                         except Exception, e:
    705                                 if self.__plan_type is self.__IMAGE_UPDATE:
    706                                         be.restore_image()
    707                                 else:
    708                                         be.restore_install_uninstall()
    709                                 # Must be done after bootenv restore.
    710                                 self.log_operation_end(error=e)
    711                                 raise
    712                         except:
    713                                 # Handle exceptions that are not subclasses of
    714                                 # Exception.
    715                                 exc_type, exc_value, exc_traceback = \
    716                                     sys.exc_info()
    717 
    718                                 if self.__plan_type is self.__IMAGE_UPDATE:
    719                                         be.restore_image()
    720                                 else:
    721                                         be.restore_install_uninstall()
    722                                 # Must be done after bootenv restore.
    723                                 self.log_operation_end(error=exc_type)
    724                                 raise
    725 
    726                         self.__finished_execution(be)
    727                 finally:
    728                         self.__img.cleanup_downloads()
    729                         self.__img.unlock()
    730                         self.__activity_lock.release()
    731 
    732         def __finished_execution(self, be):
    733                 if self.__img.imageplan.state != EXECUTED_OK:
    734                         if self.__plan_type is self.__IMAGE_UPDATE:
    735                                 be.restore_image()
    736                         else:
    737                                 be.restore_install_uninstall()
    738 
    739                         error = api_errors.ImageplanStateException(
    740                             self.__img.imageplan.state)
    741                         # Must be done after bootenv restore.
    742                         self.log_operation_end(error=error)
    743                         raise error
    744                 if self.__plan_type is self.__IMAGE_UPDATE:
    745                         be.activate_image()
    746                 else:
    747                         be.activate_install_uninstall()
    748                 self.__img.cleanup_cached_content()
    749                 # If the end of the operation wasn't already logged
    750                 # by one of the previous operations, then log it as
    751                 # ending now.
    752                 if self.__img.history.operation_name:
    753                         self.log_operation_end()
    754                 self.__executed = True
    755                 try:
    756                         if int(os.environ.get("PKG_DUMP_STATS", 0)) > 0:
    757                                 self.__img.transport.stats.dump()
    758                 except ValueError:
    759                         # Don't generate stats if an invalid value
    760                         # is supplied.
    761                         pass
    762 
    763         def set_plan_license_status(self, pfmri, plicense, accepted=None,
    764             displayed=None):
    765                 """Sets the license status for the given package FMRI and
    766                 license entry.
    767 
    768                 'accepted' is an optional parameter that can be one of three
    769                 values:
    770                         None    leaves accepted status unchanged
    771                         False   sets accepted status to False
    772                         True    sets accepted status to True
    773 
    774                 'displayed' is an optional parameter that can be one of three
    775                 values:
    776                         None    leaves displayed status unchanged
    777                         False   sets displayed status to False
    778                         True    sets displayed status to True"""
    779 
    780                 self.__acquire_activity_lock()
    781                 try:
    782                         try:
    783                                 self.__disable_cancel()
    784                         except api_errors.CanceledException:
    785                                 self.__cancel_done()
    786                                 raise
    787 
    788                         if not self.__img.imageplan:
    789                                 raise api_errors.PlanMissingException()
    790 
    791                         for pp in self.__img.imageplan.pkg_plans:
    792                                 if pp.destination_fmri == pfmri:
    793                                         pp.set_license_status(plicense,
    794                                             accepted=accepted,
    795                                             displayed=displayed)
    796                                         break
    797                 finally:
    798                         self.__activity_lock.release()
    799 
    800         def refresh(self, full_refresh=False, pubs=None, immediate=False):
    801                 """Refreshes the metadata (e.g. catalog) for one or more
    802                 publishers.
    803 
    804                 'full_refresh' is an optional boolean value indicating whether
    805                 a full retrieval of publisher metadata (e.g. catalogs) or only
    806                 an update to the existing metadata should be performed.  When
    807                 True, 'immediate' is also set to True.
    808 
    809                 'pubs' is a list of publisher prefixes or publisher objects
    810                 to refresh.  Passing an empty list or using the default value
    811                 implies all publishers.
    812 
    813                 'immediate' is an optional boolean value indicating whether
    814                 a refresh should occur now.  If False, a publisher's selected
    815                 repository will only be checked for updates if the update
    816                 interval period recorded in the image configuration has been
    817                 exceeded.
    818 
    819                 Currently returns an image object, allowing existing code to
    820                 work while the rest of the API is put into place."""
    821 
    822                 self.__acquire_activity_lock()
    823                 try:
    824                         self.__disable_cancel()
    825                         self.__img.lock()
    826                         try:
    827                                 self.__refresh(full_refresh=full_refresh,
    828                                     pubs=pubs, immediate=immediate)
    829                                 return self.__img
    830                         finally:
    831                                 self.__img.unlock()
    832                                 self.__img.cleanup_downloads()
    833                 except api_errors.CanceledException:
    834                         self.__cancel_done()
    835                         raise
    836                 finally:
    837                         self.__activity_lock.release()
    838 
    839         def __refresh(self, full_refresh=False, pubs=None, immediate=False):
    840                 """Private refresh method; caller responsible for locking and
    841                 cleanup."""
    842 
    843                 self.__img.refresh_publishers(full_refresh=full_refresh,
    844                     immediate=immediate, pubs=pubs,
    845                     progtrack=self.__progresstracker)
    846 
    847         def __licenses(self, pfmri, mfst):
    848                 """Private function. Returns the license info from the
    849                 manifest mfst."""
    850                 license_lst = []
    851                 for lic in mfst.gen_actions_by_type("license"):
    852                         license_lst.append(LicenseInfo(pfmri, lic,
    853                             img=self.__img))
    854                 return license_lst
    855 
    856         def get_pkg_categories(self, installed=False, pubs=misc.EmptyI):
    857                 """Returns an order list of tuples of the form (scheme,
    858                 category) containing the names of all categories in use by
    859                 the last version of each unique package in the catalog on a
    860                 per-publisher basis.
    861 
    862                 'installed' is an optional boolean value indicating whether
    863                 only the categories used by currently installed packages
    864                 should be returned.  If False, the categories used by the
    865                 latest vesion of every known package will be returned
    866                 instead.
    867 
    868                 'pubs' is an optional list of publisher prefixes to restrict
    869                 the results to."""
    870 
    871                 if installed:
    872                         img_cat = self.__img.get_catalog(
    873                             self.__img.IMG_CATALOG_INSTALLED)
    874                         excludes = misc.EmptyI
    875                 else:
    876                         img_cat = self.__img.get_catalog(
    877                             self.__img.IMG_CATALOG_KNOWN)
    878                         excludes = self.__img.list_excludes()
    879                 return sorted(img_cat.categories(excludes=excludes, pubs=pubs))
    880 
    881         def get_pkg_list(self, pkg_list, cats=None, patterns=misc.EmptyI,
    882             pubs=misc.EmptyI, variants=False):
    883                 """A generator function that produces tuples of the form ((pub,
    884                 stem, version), summary, categories, states).  Where 'pub' is
    885                 the publisher of the package, 'stem' is the name of the package,
    886                 'version' is a string for the package version, 'summary' is the
    887                 package summary, 'categories' is a list of tuples of the form
    888                 (scheme, category), and 'states' is a list of PackageInfo states
    889                 for the package.  Results are always sorted by stem, publisher,
    890                 and then in descending version order.
    891 
    892                 'pkg_list' is one of the following constant values indicating
    893                 what base set of package data should be used for results:
    894 
    895                         LIST_ALL
    896                                 All known packages.
    897 
    898                         LIST_INSTALLED
    899                                 Installed packages.
    900 
    901                         LIST_INSTALLED_NEWEST
    902                                 Installed packages and the newest
    903                                 versions of packages not installed.
    904                                 Renamed packages that are listed in
    905                                 an installed incorporation will be
    906                                 excluded unless they are installed.
    907 
    908                         LIST_NEWEST
    909                                 The newest versions of all known packages.
    910 
    911                         LIST_UPGRADABLE
    912                                 Packages that are installed and upgradable.
    913 
    914                 'cats' is an optional list of package category tuples of the
    915                 form (scheme, cat) to restrict the results to.  If a package
    916                 is assigned to any of the given categories, it will be
    917                 returned.  A value of [] will return packages not assigned
    918                 to any package category.  A value of None indicates that no
    919                 package category filtering should be applied.
    920 
    921                 'patterns' is an optional list of FMRI wildcard strings to
    922                 filter results by.
    923 
    924                 'pubs' is an optional list of publisher prefixes to restrict
    925                 the results to.
    926 
    927                 'variants' is an optional boolean value that indicates that
    928                 packages that are for arch or zone variants not applicable to
    929                 this image should be returned.
    930 
    931                 Please note that this function may invoke network operations
    932                 to retrieve the requested package information."""
    933 
    934                 all = installed = inst_newest = newest = upgradable = False
    935                 if pkg_list == self.LIST_ALL:
    936                         all = True
    937                 elif pkg_list == self.LIST_INSTALLED:
    938                         installed = True
    939                 elif pkg_list == self.LIST_INSTALLED_NEWEST:
    940                         inst_newest = True
    941                 elif pkg_list == self.LIST_NEWEST:
    942                         newest = True
    943                 elif pkg_list == self.LIST_UPGRADABLE:
    944                         upgradable = True
    945 
    946                 inc_vers = {}
    947                 brelease = self.__img.attrs["Build-Release"]
    948 
    949                 # Each pattern in patterns can be a partial or full FMRI, so
    950                 # extract the individual components for use in filtering.
    951                 illegals = []
    952                 pat_tuples = {}
    953                 MATCH_EXACT = 0
    954                 MATCH_FMRI = 1
    955                 MATCH_GLOB = 2
    956                 for pat in patterns:
    957                         try:
    958                                 if "*" in pat or "?" in pat:
    959                                         matcher = MATCH_GLOB
    960 
    961                                         # XXX By default, matching FMRIs
    962                                         # currently do not also use
    963                                         # MatchingVersion.  If that changes,
    964                                         # this should change too.
    965                                         parts = pat.split("@", 1)
    966                                         if len(parts) == 1:
    967                                                 npat = pkg.fmri.MatchingPkgFmri(
    968                                                     pat, brelease)
    969                                         else:
    970                                                 npat = pkg.fmri.MatchingPkgFmri(
    971                                                     parts[0], brelease)
    972                                                 npat.version = \
    973                                                     pkg.version.MatchingVersion(
    974                                                     str(parts[1]), brelease)
    975                                 elif pat.startswith("pkg:/"):
    976                                         matcher = MATCH_EXACT
    977                                         npat = pkg.fmri.PkgFmri(pat,
    978                                             brelease)
    979                                 else:
    980                                         matcher = MATCH_FMRI
    981                                         npat = pkg.fmri.PkgFmri(pat,
    982                                             brelease)
    983                                 pat_tuples[pat] = (npat.tuple(), matcher)
    984                         except (pkg.fmri.FmriError, pkg.version.VersionError):
    985                                 illegals.append(pat)
    986 
    987                 if illegals:
    988                         raise api_errors.InventoryException(illegal=illegals)
    989 
    990                 # For LIST_INSTALLED_NEWEST, installed packages need to be
    991                 # determined and incorporation and publisher relationships
    992                 # mapped.
    993                 inst_stems = {}
    994                 ren_inst_stems = {}
    995                 ren_stems = {}
    996 
    997                 pub_ranks = {}
    998                 inc_stems = {}
    999                 if inst_newest:
   1000                         img_cat = self.__img.get_catalog(
   1001                             self.__img.IMG_CATALOG_INSTALLED)
   1002                         cat_info = frozenset([img_cat.DEPENDENCY])
   1003 
   1004                         pub_ranks = self.__img.get_publisher_ranks()
   1005 
   1006                         # The incorporation list should include all installed,
   1007                         # incorporated packages from all publishers.
   1008                         for t in img_cat.entry_actions(cat_info):
   1009                                 (pub, stem, ver), entry, actions = t
   1010 
   1011                                 inst_stems[stem] = ver
   1012                                 pkgr = False
   1013                                 targets = set()
   1014                                 for a in actions:
   1015                                         if a.name == "set" and \
   1016                                             a.attrs["name"] == "pkg.renamed":
   1017                                                 pkgr = True
   1018                                                 continue
   1019                                         elif a.name != "depend":
   1020                                                 continue
   1021 
   1022                                         if a.attrs["type"] == "require":
   1023                                                 # Because the actions are not
   1024                                                 # returned in a guaranteed
   1025                                                 # order, the dependencies will
   1026                                                 # have to be recorded for
   1027                                                 # evaluation later.
   1028                                                 targets.add(a.attrs["fmri"])
   1029                                         elif a.attrs["type"] == "incorporate":
   1030                                                 # Record incorporated packages.
   1031                                                 tgt = fmri.PkgFmri(
   1032                                                     a.attrs["fmri"], brelease)
   1033                                                 tver = tgt.version
   1034                                                 over = inc_vers.get(
   1035                                                     tgt.pkg_name, None)
   1036 
   1037                                                 # In case this package has been
   1038                                                 # incorporated more than once,
   1039                                                 # use the newest version.
   1040                                                 if over is not None and \
   1041                                                     over > tver:
   1042                                                         continue
   1043                                                 inc_vers[tgt.pkg_name] = tver
   1044 
   1045                                 if pkgr:
   1046                                         for f in targets:
   1047                                                 tgt = fmri.PkgFmri(f, brelease)
   1048                                                 ren_stems[tgt.pkg_name] = stem
   1049                                                 ren_inst_stems.setdefault(stem,
   1050                                                     set())
   1051                                                 ren_inst_stems[stem].add(
   1052                                                     tgt.pkg_name)
   1053 
   1054                         def check_stem(t, entry):
   1055                                 pub, stem, ver = t
   1056                                 if stem in inst_stems:
   1057                                         iver = inst_stems[stem]
   1058                                         if stem in ren_inst_stems or \
   1059                                             ver == iver:
   1060                                                 # The package has been renamed
   1061                                                 # or the entry is for the same
   1062                                                 # version as that which is
   1063                                                 # installed, so doesn't need
   1064                                                 # to be checked.
   1065                                                 return False
   1066                                         # The package may have been renamed in
   1067                                         # a newer version, so must be checked.
   1068                                         return True
   1069                                 elif stem in inc_vers:
   1070                                         # Package is incorporated, but not
   1071                                         # installed, so should be checked.
   1072                                         return True
   1073 
   1074                                 tgt = ren_stems.get(stem, None)
   1075                                 while tgt is not None:
   1076                                         # This seems counter-intuitive, but
   1077                                         # for performance and other reasons,
   1078                                         # this stem should only be checked
   1079                                         # for a rename if it is incorporated
   1080                                         # or installed using a previous name.
   1081                                         if tgt in inst_stems or \
   1082                                             tgt in inc_vers:
   1083                                                 return True
   1084                                         tgt = ren_stems.get(tgt, None)
   1085 
   1086                                 # Package should not be checked.
   1087                                 return False
   1088 
   1089                         img_cat = self.__img.get_catalog(
   1090                             self.__img.IMG_CATALOG_KNOWN)
   1091 
   1092                         # Find terminal rename entry for all known packages not
   1093                         # rejected by check_stem().
   1094                         for t, entry, actions in img_cat.entry_actions(cat_info,
   1095                             cb=check_stem, last=True):
   1096                                 pkgr = False
   1097                                 targets = set()
   1098                                 for a in actions:
   1099                                         if a.name == "set" and \
   1100                                             a.attrs["name"] == "pkg.renamed":
   1101                                                 pkgr = True
   1102                                                 continue
   1103 
   1104                                         if a.name != "depend":
   1105                                                 continue
   1106 
   1107                                         if a.attrs["type"] != "require":
   1108                                                 continue
   1109 
   1110                                         # Because the actions are not
   1111                                         # returned in a guaranteed
   1112                                         # order, the dependencies will
   1113                                         # have to be recorded for
   1114                                         # evaluation later.
   1115                                         targets.add(a.attrs["fmri"])
   1116 
   1117                                 if pkgr:
   1118                                         pub, stem, ver = t
   1119                                         for f in targets:
   1120                                                 tgt = fmri.PkgFmri(f, brelease)
   1121                                                 ren_stems[tgt.pkg_name] = stem
   1122 
   1123                         # Determine highest ranked publisher for package stems
   1124                         # listed in installed incorporations.
   1125                         def pub_order(a, b):
   1126                                 return cmp(pub_ranks[a][0], pub_ranks[b][0])
   1127 
   1128                         for p in sorted(pub_ranks, cmp=pub_order):
   1129                                 if pubs and p not in pubs:
   1130                                         continue
   1131                                 for stem in img_cat.names(pubs=[p]):
   1132                                         if stem in inc_vers:
   1133                                                 inc_stems.setdefault(stem, p)
   1134 
   1135                 if installed or upgradable:
   1136                         img_cat = self.__img.get_catalog(
   1137                             self.__img.IMG_CATALOG_INSTALLED)
   1138 
   1139                         # Don't need to perform variant filtering if only
   1140                         # listing installed packages.
   1141                         variants = True
   1142                 else:
   1143                         img_cat = self.__img.get_catalog(
   1144                             self.__img.IMG_CATALOG_KNOWN)
   1145 
   1146                 cat_info = frozenset([img_cat.DEPENDENCY, img_cat.SUMMARY])
   1147                 api_info = frozenset([PackageInfo.SUMMARY,
   1148                     PackageInfo.CATEGORIES])
   1149 
   1150                 # Keep track of when the newest version has been found for
   1151                 # each incorporated stem.
   1152                 slist = set()
   1153 
   1154                 # Keep track of listed stems for all other packages on a
   1155                 # per-publisher basis.
   1156                 nlist = set()
   1157 
   1158                 def check_state(t, entry):
   1159                         states = entry["metadata"]["states"]
   1160                         pkgi = self.__img.PKG_STATE_INSTALLED in states
   1161                         pkgu = self.__img.PKG_STATE_UPGRADABLE in states
   1162                         pub, stem, ver = t
   1163 
   1164                         if upgradable:
   1165                                 # If package is marked upgradable, return it.
   1166                                 return pkgu
   1167                         elif pkgi:
   1168                                 # Nothing more to do here.
   1169                                 return True
   1170                         elif stem in inst_stems:
   1171                                 # Some other version of this package is
   1172                                 # installed, so this one should not be
   1173                                 # returned.
   1174                                 return False
   1175 
   1176                         # Attempt to determine if this package is installed
   1177                         # under a different name or constrained under a
   1178                         # different name.
   1179                         tgt = ren_stems.get(stem, None)
   1180                         while tgt is not None:
   1181                                 if tgt in inc_vers:
   1182                                         # Package is incorporated under a
   1183                                         # different name, so allow this
   1184                                         # to fallthrough to the incoporation
   1185                                         # evaluation.
   1186                                         break
   1187                                 elif tgt in inst_stems:
   1188                                         # Package is installed under a
   1189                                         # different name, so skip it.
   1190                                         return False
   1191                                 tgt = ren_stems.get(tgt, None)
   1192 
   1193                         # Attempt to find a suitable version to return.
   1194                         if stem in inc_vers:
   1195                                 # For package stems that are incorporated, only
   1196                                 # return the newest successor version  based on
   1197                                 # publisher rank.
   1198                                 if stem in slist:
   1199                                         # Newest version already returned.
   1200                                         return False
   1201 
   1202                                 if stem in inc_stems and \
   1203                                     pub != inc_stems[stem]:
   1204                                         # This entry is for a lower-ranked
   1205                                         # publisher.
   1206                                         return False
   1207 
   1208                                 # XXX version should not require build release.
   1209                                 ever = pkg.version.Version(ver, brelease)
   1210 
   1211                                 # If the entry's version is a successor to
   1212                                 # the incorporated version, then this is the
   1213                                 # 'newest' version of this package since
   1214                                 # entries are processed in descending version
   1215                                 # order.
   1216                                 iver = inc_vers[stem]
   1217                                 if ever.is_successor(iver,
   1218                                     pkg.version.CONSTRAINT_AUTO):
   1219                                         slist.add(stem)
   1220                                         return True
   1221                                 return False
   1222 
   1223                         pkg_stem = pub + "!" + stem
   1224                         if pkg_stem in nlist:
   1225                                 # A newer version has already been listed for
   1226                                 # this stem and publisher.
   1227                                 return False
   1228                         return True
   1229 
   1230                 filter_cb = None
   1231                 if inst_newest or upgradable:
   1232                         # Filtering needs to be applied.
   1233                         filter_cb = check_state
   1234 
   1235                 arch = self.__img.get_arch()
   1236                 excludes = self.__img.list_excludes()
   1237                 is_zone = self.__img.is_zone()
   1238 
   1239                 for t, entry, actions in img_cat.entry_actions(cat_info,
   1240                     cb=filter_cb, excludes=excludes, last=newest,
   1241                     ordered=True, pubs=pubs):
   1242                         pub, stem, ver = t
   1243 
   1244                         # Perform image arch and zone variant filtering so
   1245                         # that only packages appropriate for this image are
   1246                         # returned, but only do this for packages that are
   1247                         # not installed.
   1248                         pcats = []
   1249                         pkgr = False
   1250                         omit_package = False
   1251                         summ = None
   1252                         targets = set()
   1253 
   1254                         states = entry["metadata"]["states"]
   1255                         pkgi = self.__img.PKG_STATE_INSTALLED in states
   1256                         for a in actions:
   1257                                 if a.name == "depend" and \
   1258                                     a.attrs["type"] == "require":
   1259                                         targets.add(a.attrs["fmri"])
   1260                                         continue
   1261                                 if a.name != "set":
   1262                                         continue
   1263 
   1264                                 atname = a.attrs["name"]
   1265                                 atvalue = a.attrs["value"]
   1266                                 if atname == "pkg.summary":
   1267                                         summ = atvalue
   1268                                         continue
   1269 
   1270                                 if atname == "description":
   1271                                         if summ is None:
   1272                                                 # Historical summary field.
   1273                                                 summ = atvalue
   1274                                         continue
   1275 
   1276                                 if atname == "info.classification":
   1277                                         pcats.extend(
   1278                                             a.parse_category_info())
   1279 
   1280                                 if pkgi:
   1281                                         # No filtering for installed packages.
   1282                                         continue
   1283 
   1284                                 # Rename filtering should only be performed for
   1285                                 # incorporated packages at this point.
   1286                                 if atname == "pkg.renamed":
   1287                                         if stem in inc_vers:
   1288                                                 pkgr = True
   1289                                         continue
   1290 
   1291                                 if variants:
   1292                                         # No variant filtering.
   1293                                         continue
   1294 
   1295                                 is_list = type(atvalue) == list
   1296                                 if atname == "variant.arch":
   1297                                         if (is_list and arch not in atvalue) or \
   1298                                            (not is_list and arch != atvalue):
   1299                                                 # Package is not for the
   1300                                                 # image's architecture.
   1301                                                 omit_package = True
   1302                                                 continue
   1303 
   1304                                 if atname == "variant.opensolaris.zone":
   1305                                         if (is_zone and is_list and
   1306                                             "nonglobal" not in atvalue) or \
   1307                                            (is_zone and not is_list and
   1308                                             atvalue != "nonglobal"):
   1309                                                 # Package is for zones only.
   1310                                                 omit_package = True
   1311 
   1312                         if filter_cb is not None:
   1313                                 pkg_stem = pub + "!" + stem
   1314                                 nlist.add(pkg_stem)
   1315 
   1316                         if not pkgi and pkgr and stem in inc_vers:
   1317                                 # If the package is not installed, but this is
   1318                                 # the terminal version entry for the stem and
   1319                                 # it is an incorporated package, then omit the
   1320                                 # package if it has been installed or is
   1321                                 # incorporated using one of the new names.
   1322                                 for e in targets:
   1323                                         tgt = e
   1324                                         while tgt is not None:
   1325                                                 if tgt in ren_inst_stems or \
   1326                                                     tgt in inc_vers:
   1327                                                         omit_package = True
   1328                                                         break
   1329                                                 tgt = ren_stems.get(tgt, None)
   1330 
   1331                         # Pattern filtering has to be applied last so that
   1332                         # renames, incorporations, and everything else is
   1333                         # handled correctly.
   1334                         if not omit_package:
   1335                                 for pat in patterns:
   1336                                         (pat_pub, pat_stem, pat_ver), matcher = \
   1337                                             pat_tuples[pat]
   1338 
   1339                                         if pat_pub is not None and \
   1340                                             pub != pat_pub:
   1341                                                 # Publisher doesn't match.
   1342                                                 omit_package = True
   1343                                                 continue
   1344 
   1345                                         if matcher == MATCH_EXACT:
   1346                                                 if pat_stem != stem:
   1347                                                         # Stem doesn't match.
   1348                                                         omit_package = True
   1349                                                         continue
   1350                                         elif matcher == MATCH_FMRI:
   1351                                                 if not ("/" + stem).endswith(
   1352                                                     "/" + pat_stem):
   1353                                                         # Stem doesn't match.
   1354                                                         omit_package = True
   1355                                                         continue
   1356                                         elif matcher == MATCH_GLOB:
   1357                                                 if not fnmatch.fnmatchcase(stem,
   1358                                                     pat_stem):
   1359                                                         # Stem doesn't match.
   1360                                                         omit_package = True
   1361                                                         continue
   1362 
   1363                                         if pat_ver is not None:
   1364                                                 ever = pkg.version.Version(ver,
   1365                                                     brelease)
   1366                                                 if not ever.is_successor(pat_ver,
   1367                                                     pkg.version.CONSTRAINT_AUTO):
   1368                                                         omit_package = True
   1369                                                         continue
   1370 
   1371                                         # If this entry matched at least one
   1372                                         # pattern, then ensure it is returned.
   1373                                         omit_package = False
   1374                                         break
   1375 
   1376                         if omit_package:
   1377                                 continue
   1378 
   1379                         if cats is not None:
   1380                                 if not cats:
   1381                                         if pcats:
   1382                                                 # Only want packages with no
   1383                                                 # categories.
   1384                                                 continue
   1385                                 elif not [sc for sc in cats if sc in pcats]:
   1386                                         # Package doesn't match specified
   1387                                         # category criteria.
   1388                                         continue
   1389 
   1390                         # Return the requested package data.
   1391                         yield (t, summ, pcats,
   1392                             frozenset(entry["metadata"]["states"]))
   1393 
   1394         def info(self, fmri_strings, local, info_needed):
   1395                 """Gathers information about fmris.  fmri_strings is a list
   1396                 of fmri_names for which information is desired.  local
   1397                 determines whether to retrieve the information locally
   1398                 (if possible).  It returns a dictionary of lists.  The keys
   1399                 for the dictionary are the constants specified in the class
   1400                 definition.  The values are lists of PackageInfo objects or
   1401                 strings."""
   1402 
   1403                 # Currently, this is mostly a wapper for activity locking.
   1404                 self.__acquire_activity_lock()
   1405                 try:
   1406                         i = self._info_op(fmri_strings, local, info_needed)
   1407                 finally:
   1408                         self.__img.cleanup_downloads()
   1409                         self.__activity_lock.release()
   1410 
   1411                 return i
   1412 
   1413         def _info_op(self, fmri_strings, local, info_needed):
   1414                 """Performs the actual info operation.  The external
   1415                 interface to the API's consumers is defined in info()."""
   1416 
   1417                 bad_opts = info_needed - PackageInfo.ALL_OPTIONS
   1418                 if bad_opts:
   1419                         raise api_errors.UnrecognizedOptionsToInfo(bad_opts)
   1420 
   1421                 self.log_operation_start("info")
   1422 
   1423                 fmris = []
   1424                 notfound = []
   1425                 multiple_matches = []
   1426                 illegals = []
   1427 
   1428                 ppub = self.__img.get_preferred_publisher()
   1429                 if local:
   1430                         fmris, notfound, illegals = \
   1431                             self.__img.installed_fmris_from_args(fmri_strings)
   1432                         if not fmris and not notfound and not illegals:
   1433                                 self.log_operation_end(
   1434                                     result=history.RESULT_NOTHING_TO_DO)
   1435                                 raise api_errors.NoPackagesInstalledException()
   1436                 else:
   1437                         # Verify validity of certificates before attempting
   1438                         # network operations.
   1439                         self.__cert_verify(
   1440                             log_op_end=[api_errors.CertificateError])
   1441 
   1442                         # XXX This loop really needs not to be copied from
   1443                         # Image.make_install_plan()!
   1444                         for p in fmri_strings:
   1445                                 try:
   1446                                         matches = list(self.__img.inventory([p],
   1447                                             all_known=True, ordered=False))
   1448                                 except api_errors.InventoryException, e:
   1449                                         assert(len(e.notfound) == 1 or \
   1450                                             len(e.illegal) == 1)
   1451                                         if e.notfound:
   1452                                                 notfound.append(e.notfound[0])
   1453                                         else:
   1454                                                 illegals.append(e.illegal[0])
   1455                                         err = 1
   1456                                         continue
   1457 
   1458                                 pnames = {}
   1459                                 pmatch = []
   1460                                 npnames = {}
   1461                                 npmatch = []
   1462                                 for m, state in matches:
   1463                                         if m.get_publisher() == ppub:
   1464                                                 pnames[m.get_pkg_stem()] = 1
   1465                                                 pmatch.append((m, state))
   1466                                         else:
   1467                                                 npnames[m.get_pkg_stem()] = 1
   1468                                                 npmatch.append((m, state))
   1469 
   1470                                 if len(pnames.keys()) > 1:
   1471                                         multiple_matches.append(
   1472                                             (p, pnames.keys()))
   1473                                         error = 1
   1474                                         continue
   1475                                 elif len(pnames.keys()) < 1 and \
   1476                                     len(npnames.keys()) > 1:
   1477                                         multiple_matches.append(
   1478                                             (p, pnames.keys()))
   1479                                         error = 1
   1480                                         continue
   1481 
   1482                                 # matches is a list reverse sorted by version,
   1483                                 # so take the first; i.e., the latest.
   1484                                 if len(pmatch) > 0:
   1485                                         fmris.append(pmatch[0])
   1486                                 else:
   1487                                         fmris.append(npmatch[0])
   1488 
   1489                 if local:
   1490                         img_cat = self.__img.get_catalog(
   1491                             self.__img.IMG_CATALOG_INSTALLED)
   1492                 else:
   1493                         img_cat = self.__img.get_catalog(
   1494                             self.__img.IMG_CATALOG_KNOWN)
   1495                 excludes = self.__img.list_excludes()
   1496 
   1497                 # Set of options that can use catalog data.
   1498                 cat_opts = frozenset([PackageInfo.SUMMARY,
   1499                     PackageInfo.CATEGORIES, PackageInfo.DESCRIPTION,
   1500                     PackageInfo.DEPENDENCIES])
   1501 
   1502                 # Set of options that require manifest retrieval.
   1503                 act_opts = PackageInfo.ACTION_OPTIONS - \
   1504                     frozenset([PackageInfo.DEPENDENCIES])
   1505 
   1506                 pis = []
   1507                 for f, fstate in fmris:
   1508                         pub = name = version = release = None
   1509                         build_release = branch = packaging_date = None
   1510                         if PackageInfo.IDENTITY in info_needed:
   1511                                 pub, name, version = f.tuple()
   1512                                 pub = fmri.strip_pub_pfx(pub)
   1513                                 release = version.release
   1514                                 build_release = version.build_release
   1515                                 branch = version.branch
   1516                                 packaging_date = \
   1517                                     version.get_timestamp().strftime("%c")
   1518 
   1519                         pref_pub = None
   1520                         if PackageInfo.PREF_PUBLISHER in info_needed:
   1521                                 pref_pub = (f.get_publisher() == ppub)
   1522 
   1523                         # XXX gross; info needs to switch to using get_pkg_list
   1524                         # routines at a later date once matching is figured
   1525                         # out.
   1526                         states = set()
   1527                         if PackageInfo.STATE in info_needed:
   1528                                 if fstate["state"] == \
   1529                                     self.__img.PKG_STATE_INSTALLED:
   1530                                         states.add(PackageInfo.INSTALLED)
   1531                                 if fstate["in_catalog"]:
   1532                                         states.add(PackageInfo.KNOWN)
   1533                                 if fstate["upgradable"]:
   1534                                         states.add(PackageInfo.UPGRADABLE)
   1535                                 if fstate["obsolete"]:
   1536                                         states.add(PackageInfo.OBSOLETE)
   1537                                 if fstate["renamed"]:
   1538                                         states.add(PackageInfo.RENAMED)
   1539 
   1540                         links = hardlinks = files = dirs = dependencies = None
   1541                         summary = size = licenses = cat_info = description = \
   1542                             None
   1543 
   1544                         if cat_opts & info_needed:
   1545                                 summary, description, cat_info, dependencies = \
   1546                                     _get_pkg_cat_data(img_cat, info_needed,
   1547                                         excludes=excludes, pfmri=f)
   1548                                 if cat_info is not None:
   1549                                         cat_info = [
   1550                                             PackageCategory(scheme, cat)
   1551                                             for scheme, cat in cat_info
   1552                                         ]
   1553 
   1554                         if (frozenset([PackageInfo.SIZE,
   1555                             PackageInfo.LICENSES]) | act_opts) & info_needed:
   1556                                 mfst = self.__img.get_manifest(f)
   1557                                 if PackageInfo.LICENSES in info_needed:
   1558                                         licenses = self.__licenses(f, mfst)
   1559 
   1560                                 if PackageInfo.SIZE in info_needed:
   1561                                         size = mfst.get_size(excludes=excludes)
   1562 
   1563                                 if act_opts & info_needed:
   1564                                         if PackageInfo.LINKS in info_needed:
   1565                                                 links = list(
   1566                                                     mfst.gen_key_attribute_value_by_type(
   1567                                                     "link", excludes))
   1568                                         if PackageInfo.HARDLINKS in info_needed:
   1569                                                 hardlinks = list(
   1570                                                     mfst.gen_key_attribute_value_by_type(
   1571                                                     "hardlink", excludes))
   1572                                         if PackageInfo.FILES in info_needed:
   1573                                                 files = list(
   1574                                                     mfst.gen_key_attribute_value_by_type(
   1575                                                     "file", excludes))
   1576                                         if PackageInfo.DIRS in info_needed:
   1577                                                 dirs = list(
   1578                                                     mfst.gen_key_attribute_value_by_type(
   1579                                                     "dir", excludes))
   1580 
   1581                         pis.append(PackageInfo(pkg_stem=name, summary=summary,
   1582                             category_info_list=cat_info, states=states,
   1583                             publisher=pub, preferred_publisher=pref_pub,
   1584                             version=release, build_release=build_release,
   1585                             branch=branch, packaging_date=packaging_date,
   1586                             size=size, pfmri=str(f), licenses=licenses,
   1587                             links=links, hardlinks=hardlinks, files=files,
   1588                             dirs=dirs, dependencies=dependencies,
   1589                             description=description))
   1590                 if pis:
   1591                         self.log_operation_end()
   1592                 elif illegals or multiple_matches:
   1593                         self.log_operation_end(
   1594                             result=history.RESULT_FAILED_BAD_REQUEST)
   1595                 else:
   1596                         self.log_operation_end(
   1597                             result=history.RESULT_NOTHING_TO_DO)
   1598                 return {
   1599                     self.INFO_FOUND: pis,
   1600                     self.INFO_MISSING: notfound,
   1601                     self.INFO_MULTI_MATCH: multiple_matches,
   1602                     self.INFO_ILLEGALS: illegals
   1603                 }
   1604 
   1605         def can_be_canceled(self):
   1606                 """Returns true if the API is in a cancelable state."""
   1607                 return self.__can_be_canceled
   1608 
   1609         def __disable_cancel(self):
   1610                 """Sets_can_be_canceled to False in a way that prevents missed
   1611                 wakeups.  This may raise CanceledException, if a
   1612                 cancellation is pending."""
   1613 
   1614                 self.__cancel_lock.acquire()
   1615                 if self.__canceling:
   1616                         self.__cancel_lock.release()
   1617                         self.__img.transport.reset()
   1618                         raise api_errors.CanceledException()
   1619                 else:
   1620                         self.__set_can_be_canceled(False)
   1621                 self.__cancel_lock.release()
   1622 
   1623         def __enable_cancel(self):
   1624                 """Sets can_be_canceled to True while grabbing the cancel
   1625                 locks.  The caller must still hold the activity lock while
   1626                 calling this function."""
   1627 
   1628                 self.__cancel_lock.acquire()
   1629                 self.__set_can_be_canceled(True)
   1630                 self.__cancel_lock.release()
   1631 
   1632         def __set_can_be_canceled(self, status):
   1633                 """Private method. Handles the details of changing the
   1634                 cancelable state."""
   1635                 assert self.__cancel_lock._is_owned()
   1636 
   1637                 # If caller requests a change to current state there is
   1638                 # nothing to do.
   1639                 if self.__can_be_canceled == status:
   1640                         return
   1641 
   1642                 if status == True:
   1643                         # Callers must hold activity lock for operations
   1644                         # that they will make cancelable.
   1645                         assert self.__activity_lock._is_owned()
   1646                         # In any situation where the caller holds the activity
   1647                         # lock and wants to set cancelable to true, a cancel
   1648                         # should not already be in progress.  This is because
   1649                         # it should not be possible to invoke cancel until
   1650                         # this routine has finished.  Assert that we're not
   1651                         # canceling.
   1652                         assert not self.__canceling
   1653 
   1654                 self.__can_be_canceled = status
   1655                 if self.__cancel_state_callable:
   1656                         self.__cancel_state_callable(self.__can_be_canceled)
   1657 
   1658         def reset(self):
   1659                 """Resets the API back the the initial state. Note:
   1660                 this does not necessarily return the disk to its initial state
   1661                 since the indexes or download cache may have been changed by
   1662                 the prepare method."""
   1663                 self.__acquire_activity_lock()
   1664                 self.__reset_unlock()
   1665                 self.__activity_lock.release()
   1666 
   1667         def __reset_unlock(self):
   1668                 """Private method. Provides a way to reset without taking the
   1669                 activity lock. Should only be called by a thread which already
   1670                 holds the activity lock."""
   1671 
   1672                 assert self.__activity_lock._is_owned()
   1673 
   1674                 # This needs to be done first so that find_root can use it.
   1675                 self.__progresstracker.reset()
   1676 
   1677                 self.__img.cleanup_downloads()
   1678                 self.__img.transport.shutdown()
   1679                 # Recreate the image object using the path the api
   1680                 # object was created with instead of the current path.
   1681                 self.__img = image.Image(self.__img_path,
   1682                     progtrack=self.__progresstracker)
   1683                 self.__img.blocking_locks = self.__blocking_locks
   1684 
   1685                 self.__plan_desc = None
   1686                 self.__plan_type = None
   1687                 self.__prepared = False
   1688                 self.__executed = False
   1689                 self.__be_name = None
   1690 
   1691                 self.__cancel_cleanup_exception()
   1692 
   1693         def __check_cancelation(self):
   1694                 """Private method. Provides a callback method for internal
   1695                 code to use to determine whether the current action has been
   1696                 canceled."""
   1697                 return self.__canceling
   1698 
   1699         def __cancel_cleanup_exception(self):
   1700                 """A private method that is called from exception handlers.
   1701                 This is not needed if the method calls reset unlock,
   1702                 which will call this method too.  This catches the case
   1703                 where a caller might have called cancel and gone to sleep,
   1704                 but the requested operation failed with an exception before
   1705                 it could raise a CanceledException."""
   1706 
   1707                 self.__cancel_lock.acquire()
   1708                 self.__set_can_be_canceled(False)
   1709                 self.__canceling = False
   1710                 # Wake up any threads that are waiting on this aborted
   1711                 # operation.
   1712                 self.__cancel_cv.notify_all()
   1713                 self.__cancel_lock.release()
   1714 
   1715         def __cancel_done(self):
   1716                 """A private method that wakes any threads that have been
   1717                 sleeping, waiting for a cancellation to finish."""
   1718 
   1719                 self.__cancel_lock.acquire()
   1720                 if self.__canceling:
   1721                         self.__canceling = False
   1722                         self.__cancel_cv.notify_all()
   1723                 self.__cancel_lock.release()
   1724 
   1725         def cancel(self):
   1726                 """Used for asynchronous cancelation. It returns the API
   1727                 to the state it was in prior to the current method being
   1728                 invoked.  Canceling during a plan phase returns the API to
   1729                 its initial state. Canceling during prepare puts the API
   1730                 into the state it was in just after planning had completed.
   1731                 Plan execution cannot be canceled. A call to this method blocks
   1732                 until the cancellation has happened. Note: this does not
   1733                 necessarily return the disk to its initial state since the
   1734                 indexes or download cache may have been changed by the
   1735                 prepare method."""
   1736 
   1737                 self.__cancel_lock.acquire()
   1738 
   1739                 if not self.__can_be_canceled:
   1740                         self.__cancel_lock.release()
   1741                         return False
   1742 
   1743                 self.__set_can_be_canceled(False)
   1744                 self.__canceling = True
   1745                 # Wait until the cancelled operation wakes us up.
   1746                 self.__cancel_cv.wait()
   1747                 self.__canceling = False
   1748                 self.__cancel_lock.release()
   1749                 return True
   1750 
   1751         def __set_history_PlanCreationException(self, e):
   1752                 if e.unmatched_fmris or e.multiple_matches or \
   1753                     e.missing_matches or e.illegal:
   1754                         self.log_operation_end(error=e,
   1755                             result=history.RESULT_FAILED_BAD_REQUEST)
   1756                 else:
   1757                         self.log_operation_end(error=e)
   1758 
   1759         def local_search(self, query_lst):
   1760                 """local_search takes a list of Query objects and performs
   1761                 each query against the installed packages of the image."""
   1762 
   1763                 l = query_p.QueryLexer()
   1764                 l.build()
   1765                 qp = query_p.QueryParser(l)
   1766                 ssu = None
   1767                 for i, q in enumerate(query_lst):
   1768                         try:
   1769                                 query = qp.parse(q.encoded_text())
   1770                                 query_rr = qp.parse(q.encoded_text())
   1771                                 if query_rr.remove_root(self.__img.root):
   1772                                         query.add_or(query_rr)
   1773                         except query_p.BooleanQueryException, e:
   1774                                 raise api_errors.BooleanQueryException(e)
   1775                         except query_p.ParseError, e:
   1776                                 raise api_errors.ParseError(e)
   1777                         self.__img.update_index_dir()
   1778                         assert self.__img.index_dir
   1779                         try:
   1780                                 query.set_info(num_to_return=q.num_to_return,
   1781                                     start_point=q.start_point,
   1782                                     index_dir=self.__img.index_dir,
   1783                                     get_manifest_path=\
   1784                                         self.__img.get_manifest_path,
   1785                                     gen_installed_pkg_names=\
   1786                                         self.__img.gen_installed_pkg_names,
   1787                                     case_sensitive=q.case_sensitive)
   1788                                 res = query.search(
   1789                                     self.__img.gen_installed_pkgs,
   1790                                     self.__img.get_manifest_path,
   1791                                     self.__img.list_excludes())
   1792                         except search_errors.InconsistentIndexException, e:
   1793                                 raise api_errors.InconsistentIndexException(e)
   1794                         # i is being inserted to track which query the results
   1795                         # are for.  None is being inserted since there is no
   1796                         # publisher being searched against.
   1797                         try:
   1798                                 for r in res:
   1799                                         yield i, None, r
   1800                         except api_errors.SlowSearchUsed, e:
   1801                                 ssu = e
   1802                 if ssu:
   1803                         raise ssu
   1804 
   1805         @staticmethod
   1806         def __parse_v_0(line, pub, v):
   1807                 """This function parses the string returned by a version 0
   1808                 search server and puts it into the expected format of
   1809                 (query_number, publisher, (version, return_type, (results))).
   1810 
   1811                 "query_number" in the return value is fixed at 0 since search
   1812                 v0 servers cannot accept multiple queries in a single HTTP
   1813                 request."""
   1814 
   1815                 line = line.strip()
   1816                 fields = line.split(None, 3)
   1817                 return (0, pub, (v, Query.RETURN_ACTIONS, (fields[:4])))
   1818 
   1819         @staticmethod
   1820         def __parse_v_1(line, pub, v):
   1821                 """This function parses the string returned by a version 1
   1822                 search server and puts it into the expected format of
   1823                 (query_number, publisher, (version, return_type, (results)))
   1824                 If it receives a line it can't parse, it raises a
   1825                 ServerReturnError."""
   1826 
   1827                 fields = line.split(None, 2)
   1828                 if len(fields) != 3:
   1829                         raise api_errors.ServerReturnError(line)
   1830                 try:
   1831                         return_type = int(fields[1])
   1832                         query_num = int(fields[0])
   1833                 except ValueError:
   1834                         raise api_errors.ServerReturnError(line)
   1835                 if return_type == Query.RETURN_ACTIONS:
   1836                         subfields = fields[2].split(None, 2)
   1837                         return (query_num, pub, (v, return_type,
   1838                             (subfields[0], urllib.unquote(subfields[1]),
   1839                             subfields[2])))
   1840                 elif return_type == Query.RETURN_PACKAGES:
   1841                         return (query_num, pub, (v, return_type, fields[2]))
   1842                 else:
   1843                         raise api_errors.ServerReturnError(line)
   1844 
   1845         def remote_search(self, query_str_and_args_lst, servers=None):
   1846                 """This function takes a list of Query objects, and optionally
   1847                 a list of servers to search against.  It performs each query
   1848                 against each server and yields the results in turn.  If no
   1849                 servers are provided, the search is conducted against all
   1850                 active servers known by the image.
   1851 
   1852                 The servers argument is a list of servers in two possible
   1853                 forms: the old deprecated form of a publisher, in a
   1854                 dictionary, or a Publisher object.
   1855 
   1856                 A call to this function returns a generator that holds
   1857                 API locks.  Callers must either iterate through all of the
   1858                 results, or call close() on the resulting object.  Otherwise
   1859                 it is possible to get deadlocks or NRLock reentrance
   1860                 exceptions."""
   1861 
   1862                 clean_exit = True
   1863                 canceled = False
   1864 
   1865                 self.__acquire_activity_lock()
   1866                 self.__enable_cancel()
   1867                 try:
   1868                         for r in self._remote_search(query_str_and_args_lst,
   1869                             servers):
   1870                                 yield r
   1871                 except GeneratorExit:
   1872                         return
   1873                 except api_errors.CanceledException:
   1874                         canceled = True
   1875                         raise
   1876                 except Exception:
   1877                         clean_exit = False
   1878                         raise
   1879                 finally:
   1880                         if canceled:
   1881                                 self.__cancel_done()
   1882                         elif clean_exit:
   1883                                 try:
   1884                                         self.__disable_cancel()
   1885                                 except api_errors.CanceledException:
   1886                                         self.__cancel_done()
   1887                                         self.__activity_lock.release()
   1888                                         raise
   1889                         else:
   1890                                 self.__cancel_cleanup_exception()
   1891                         self.__activity_lock.release()
   1892 
   1893         def _remote_search(self, query_str_and_args_lst, servers=None):
   1894                 """This is the implementation of remote_search.  The other
   1895                 function is a wrapper that handles locking and exception
   1896                 handling.  This is a generator function."""
   1897 
   1898                 failed = []
   1899                 invalid = []
   1900                 unsupported = []
   1901 
   1902                 if not servers:
   1903                         servers = self.__img.gen_publishers()
   1904 
   1905                 new_qs = []
   1906                 l = query_p.QueryLexer()
   1907                 l.build()
   1908                 qp = query_p.QueryParser(l)
   1909                 for q in query_str_and_args_lst:
   1910                         try:
   1911                                 query = qp.parse(q.encoded_text())
   1912                                 query_rr = qp.parse(q.encoded_text())
   1913                                 if query_rr.remove_root(self.__img.root):
   1914                                         query.add_or(query_rr)
   1915                                         new_qs.append(query_p.Query(str(query),
   1916                                             q.case_sensitive, q.return_type,
   1917                                             q.num_to_return, q.start_point))
   1918                                 else:
   1919                                         new_qs.append(q)
   1920                         except query_p.BooleanQueryException, e:
   1921                                 raise api_errors.BooleanQueryException(e)
   1922                         except query_p.ParseError, e:
   1923                                 raise api_errors.ParseError(e)
   1924 
   1925                 query_str_and_args_lst = new_qs
   1926 
   1927                 for pub in servers:
   1928                         descriptive_name = None
   1929 
   1930                         if self.__canceling:
   1931                                 raise api_errors.CanceledException()
   1932 
   1933                         if isinstance(pub, dict):
   1934                                 origin = pub["origin"]
   1935                                 try:
   1936                                         pub = self.__img.get_publisher(
   1937                                             origin=origin)
   1938                                 except api_errors.UnknownPublisher:
   1939                                         pub = publisher.RepositoryURI(origin)
   1940                                         descriptive_name = origin
   1941 
   1942                         if not descriptive_name:
   1943                                 descriptive_name = pub.prefix
   1944 
   1945                         try:
   1946                                 res = self.__img.transport.do_search(pub,
   1947                                     query_str_and_args_lst,
   1948                                     ccancel=self.__check_cancelation)
   1949                         except api_errors.CanceledException:
   1950                                 raise
   1951                         except api_errors.NegativeSearchResult:
   1952                                 continue
   1953                         except api_errors.TransportError, e:
   1954                                 failed.append((descriptive_name, e))
   1955                                 continue
   1956                         except api_errors.UnsupportedSearchError, e:
   1957                                 unsupported.append((descriptive_name, e))
   1958                                 continue
   1959                         except api_errors.MalformedSearchRequest, e:
   1960                                 ex = self._validate_search(
   1961                                     query_str_and_args_lst)
   1962                                 if ex:
   1963                                         raise ex
   1964                                 failed.append((descriptive_name, e))
   1965                                 continue
   1966 
   1967                         try:
   1968                                 if not self.validate_response(res, 1):
   1969                                         invalid.append(descriptive_name)
   1970                                         continue
   1971                                 for line in res:
   1972                                         yield self.__parse_v_1(line, pub, 1)
   1973                         except api_errors.CanceledException:
   1974                                 raise
   1975                         except api_errors.TransportError, e:
   1976                                 failed.append((descriptive_name, e))
   1977                                 continue
   1978 
   1979                 if failed or invalid or unsupported:
   1980                         raise api_errors.ProblematicSearchServers(failed,
   1981                             invalid, unsupported)
   1982 
   1983         @staticmethod
   1984         def __unconvert_return_type(v):
   1985                 return v == query_p.Query.RETURN_ACTIONS
   1986 
   1987         def _validate_search(self, query_str_lst):
   1988                 """Called by remote search if server responds that the
   1989                 request was invalid.  In this case, parse the query on
   1990                 the client-side and determine what went wrong."""
   1991 
   1992                 for q in query_str_lst:
   1993                         l = query_p.QueryLexer()
   1994                         l.build()
   1995                         qp = query_p.QueryParser(l)
   1996                         try:
   1997                                 query = qp.parse(q.encoded_text())
   1998                         except query_p.BooleanQueryException, e:
   1999                                 return api_errors.BooleanQueryException(e)
   2000                         except query_p.ParseError, e:
   2001                                 return api_errors.ParseError(e)
   2002 
   2003                 return None
   2004 
   2005         def rebuild_search_index(self):
   2006                 """Rebuilds the search indexes.  Removes all
   2007                 existing indexes and replaces them from scratch rather than
   2008                 performing the incremental update which is usually used.
   2009                 This is useful for times when the index for the client has
   2010                 been corrupted."""
   2011                 self.__img.update_index_dir()
   2012                 self.log_operation_start("rebuild-index")
   2013                 if not os.path.isdir(self.__img.index_dir):
   2014                         self.__img.mkdirs()
   2015                 try:
   2016                         ind = indexer.Indexer(self.__img, self.__img.get_manifest,
   2017                             self.__img.get_manifest_path,
   2018                             self.__progresstracker, self.__img.list_excludes())
   2019                         ind.rebuild_index_from_scratch(
   2020                             self.__img.gen_installed_pkgs())
   2021                 except search_errors.ProblematicPermissionsIndexException, e:
   2022                         error = api_errors.ProblematicPermissionsIndexException(e)
   2023                         self.log_operation_end(error=error)
   2024                         raise error
   2025                 except search_errors.MainDictParsingException, e:
   2026                         error = api_errors.MainDictParsingException(e)
   2027                         self.log_operation_end(error=error)
   2028                         self.__img.cleanup_downloads()
   2029                         raise error
   2030                 else:
   2031                         self.log_operation_end()
   2032 
   2033         def get_manifest(self, pfmri, all_variants=True, intent=None):
   2034                 return self.__img.get_manifest(pfmri)
   2035 
   2036         @staticmethod
   2037         def validate_response(res, v):
   2038                 """This function is used to determine whether the first
   2039                 line returned from a server is expected.  This ensures that
   2040                 search is really communicating with a search-enabled server."""
   2041 
   2042                 try:
   2043                         s = res.next()
   2044                         return s == Query.VALIDATION_STRING[v]
   2045                 except StopIteration:
   2046                         return False
   2047 
   2048         def add_publisher(self, pub, refresh_allowed=True):
   2049                 """Add the provided publisher object to the image
   2050                 configuration."""
   2051                 try:
   2052                         self.__img.add_publisher(pub,
   2053                             refresh_allowed=refresh_allowed,
   2054                             progtrack=self.__progresstracker)
   2055                 finally:
   2056                         self.__img.cleanup_downloads()
   2057 
   2058         def get_pub_search_order(self):
   2059                 """Return current search order of publishers; includes
   2060                 disabled publishers"""
   2061                 return self.__img.cfg_cache.publisher_search_order
   2062 
   2063         def set_pub_search_after(self, being_moved_prefix, staying_put_prefix):
   2064                 """Change the publisher search order so that being_moved is
   2065                 searched after staying_put"""
   2066                 self.__img.pub_search_after(being_moved_prefix, staying_put_prefix)
   2067 
   2068         def set_pub_search_before(self, being_moved_prefix, staying_put_prefix):
   2069                 """Change the publisher search order so that being_moved is
   2070                 searched before staying_put"""
   2071                 self.__img.pub_search_before(being_moved_prefix, staying_put_prefix)
   2072 
   2073         def get_preferred_publisher(self):
   2074                 """Returns the preferred publisher object for the image."""
   2075                 return self.get_publisher(
   2076                     prefix=self.__img.get_preferred_publisher())
   2077 
   2078         def get_publisher(self, prefix=None, alias=None, duplicate=False):
   2079                 """Retrieves a publisher object matching the provided prefix
   2080                 (name) or alias.
   2081 
   2082                 'duplicate' is an optional boolean value indicating whether
   2083                 a copy of the publisher object should be returned instead
   2084                 of the original.
   2085                 """
   2086                 pub = self.__img.get_publisher(prefix=prefix, alias=alias)
   2087                 if duplicate:
   2088                         # Never return the original so that changes to the
   2089                         # retrieved object are not reflected until
   2090                         # update_publisher is called.
   2091                         return copy.copy(pub)
   2092                 return pub
   2093 
   2094         def get_publisherdata(self, pub=None, repo=None):
   2095                 """Attempts to retrieve publisher configuration information from
   2096                 the specified publisher's repository or the provided repository.
   2097                 If successful, it will either return an empty list (in the case
   2098                 that the repository supports the operation, but doesn't offer
   2099                 configuration information) or a list of Publisher objects.
   2100                 If this operation is not supported by the publisher or the
   2101                 specified repository, an UnsupportedRepositoryOperation
   2102                 exception will be raised.
   2103 
   2104                 'pub' is an optional Publisher object.
   2105 
   2106                 'repo' is an optional RepositoryURI object.
   2107 
   2108                 Either 'pub' or 'repo' must be provided."""
   2109 
   2110                 assert (pub or repo) and not (pub and repo)
   2111 
   2112                 # Transport accepts either type of object, but a distinction is
   2113                 # made in the client API for clarity.
   2114                 pub = max(pub, repo)
   2115 
   2116                 self.__activity_lock.acquire()
   2117                 try:
   2118                         self.__enable_cancel()
   2119                         data = self.__img.transport.get_publisherdata(pub,
   2120                             ccancel=self.__check_cancelation)
   2121                         self.__disable_cancel()
   2122                         return data
   2123                 except api_errors.CanceledException:
   2124                         self.__cancel_done()
   2125                         raise
   2126                 except:
   2127                         self.__cancel_cleanup_exception()
   2128                         raise
   2129                 finally:
   2130                         self.__img.cleanup_downloads()
   2131                         self.__activity_lock.release()
   2132 
   2133         def get_publishers(self, duplicate=False):
   2134                 """Returns a list of the publisher objects for the current
   2135                 image.
   2136 
   2137                 'duplicate' is an optional boolean value indicating whether
   2138                 copies of the publisher objects should be returned instead
   2139                 of the originals.
   2140                 """
   2141                 if duplicate:
   2142                         # Return a copy so that changes to the retrieved objects
   2143                         # are not reflected until update_publisher is called.
   2144                         pubs = [
   2145                             copy.copy(p)
   2146                             for p in self.__img.get_publishers().values()
   2147                         ]
   2148                 else:
   2149                         pubs = self.__img.get_publishers().values()
   2150                 return misc.get_sorted_publishers(pubs,
   2151                     preferred=self.__img.get_preferred_publisher())
   2152 
   2153         def get_publisher_last_update_time(self, prefix=None, alias=None):
   2154                 """Returns a datetime object representing the last time the
   2155                 catalog for a publisher was modified or None."""
   2156 
   2157                 if alias:
   2158                         pub = self.get_publisher(alias=alias)
   2159                 else:
   2160                         pub = self.get_publisher(prefix=prefix)
   2161 
   2162                 if pub.disabled:
   2163                         return None
   2164 
   2165                 dt = None
   2166                 self.__acquire_activity_lock()
   2167                 try:
   2168                         self.__enable_cancel()
   2169                         try:
   2170                                 dt = pub.catalog.last_modified
   2171                         except:
   2172                                 self.__reset_unlock()
   2173                                 raise
   2174                         try:
   2175                                 self.__disable_cancel()
   2176                         except api_errors.CanceledException:
   2177                                 self.__cancel_done()
   2178                                 raise
   2179                 finally:
   2180                         self.__activity_lock.release()
   2181                 return dt
   2182 
   2183         def has_publisher(self, prefix=None, alias=None):
   2184                 """Returns a boolean value indicating whether a publisher using
   2185                 the given prefix or alias exists."""
   2186                 return self.__img.has_publisher(prefix=prefix, alias=alias)
   2187 
   2188         def remove_publisher(self, prefix=None, alias=None):
   2189                 """Removes a publisher object matching the provided prefix
   2190                 (name) or alias."""
   2191                 self.__img.remove_publisher(prefix=prefix, alias=alias,
   2192                     progtrack=self.__progresstracker)
   2193 
   2194         def set_preferred_publisher(self, prefix=None, alias=None):
   2195                 """Sets the preferred publisher for the image."""
   2196                 self.__img.set_preferred_publisher(prefix=prefix, alias=alias)
   2197 
   2198         def update_publisher(self, pub, refresh_allowed=True):
   2199                 """Replaces an existing publisher object with the provided one
   2200                 using the _source_object_id identifier set during copy.
   2201 
   2202                 'refresh_allowed' is an optional boolean value indicating
   2203                 whether a refresh of publisher metadata (such as its catalog)
   2204                 should be performed if transport information is changed for a
   2205                 repository, mirror, or origin.  If False, no attempt will be
   2206                 made to retrieve publisher metadata."""
   2207 
   2208                 self.__acquire_activity_lock()
   2209                 try:
   2210                         self.__disable_cancel()
   2211                         with self.__img.locked_op("update-publisher"):
   2212                                 return self.__update_publisher(pub,
   2213                                     refresh_allowed=refresh_allowed)
   2214                 except api_errors.CanceledException, e:
   2215                         self.__cancel_done()
   2216                         raise
   2217                 finally:
   2218                         self.__img.cleanup_downloads()
   2219                         self.__activity_lock.release()
   2220 
   2221         def __update_publisher(self, pub, refresh_allowed=True):
   2222                 """Private publisher update method; caller responsible for
   2223                 locking."""
   2224 
   2225                 if pub.disabled and \
   2226                     pub.prefix == self.__img.get_preferred_publisher():
   2227                         raise api_errors.SetPreferredPublisherDisabled(
   2228                             pub.prefix)
   2229 
   2230                 def origins_changed(oldr, newr):
   2231                         old_origins = set([
   2232                             (o.uri, o.ssl_cert,
   2233                                 o.ssl_key)
   2234                             for o in oldr.origins
   2235                         ])
   2236                         new_origins = set([
   2237                             (o.uri, o.ssl_cert,
   2238                                 o.ssl_key)
   2239                             for o in newr.origins
   2240                         ])
   2241                         return new_origins - old_origins
   2242 
   2243                 def need_refresh(oldo, newo):
   2244                         if newo.disabled:
   2245                                 # The publisher is disabled, so no refresh
   2246                                 # should be performed.
   2247                                 return False
   2248 
   2249                         if oldo.disabled and not newo.disabled:
   2250                                 # The publisher has been re-enabled, so
   2251                                 # retrieve the catalog.
   2252                                 return True
   2253 
   2254                         if len(newo.repositories) != len(oldo.repositories):
   2255                                 # If there are an unequal number of repositories
   2256                                 # then some have been added or removed.
   2257                                 return True
   2258 
   2259                         oldr = oldo.selected_repository
   2260                         newr = newo.selected_repository
   2261                         if newr._source_object_id != id(oldr):
   2262                                 # Selected repository has changed.
   2263                                 return True
   2264                         return len(origins_changed(oldr, newr)) != 0
   2265 
   2266                 refresh_catalog = False
   2267                 disable = False
   2268                 orig_pub = None
   2269                 publishers = self.__img.get_publishers()
   2270 
   2271                 # First, attempt to match the updated publisher object to an
   2272                 # existing one using the object id that was stored during
   2273                 # copy().
   2274                 for key, old in publishers.iteritems():
   2275                         if pub._source_object_id == id(old):
   2276                                 # Store the new publisher's id and the old
   2277                                 # publisher object so it can be restored if the
   2278                                 # update operation fails.
   2279                                 orig_pub = (id(pub), old)
   2280                                 break
   2281 
   2282                 if not orig_pub:
   2283                         # If a matching publisher couldn't be found and
   2284                         # replaced, something is wrong (client api usage
   2285                         # error).
   2286                         raise api_errors.UnknownPublisher(pub)
   2287 
   2288                 # Next, be certain that the publisher's prefix and alias
   2289                 # are not already in use by another publisher.
   2290                 for key, old in publishers.iteritems():
   2291                         if pub._source_object_id == id(old):
   2292                                 # Don't check the object we're replacing.
   2293                                 continue
   2294 
   2295                         if pub.prefix == old.prefix or \
   2296                             pub.prefix == old.alias or \
   2297                             pub.alias and (pub.alias == old.alias or
   2298                             pub.alias == old.prefix):
   2299                                 raise api_errors.DuplicatePublisher(pub)
   2300 
   2301                 # Next, determine what needs updating and add the updated
   2302                 # publisher.
   2303                 for key, old in publishers.iteritems():
   2304                         if pub._source_object_id == id(old):
   2305                                 old = orig_pub[-1]
   2306                                 if need_refresh(old, pub):
   2307                                         refresh_catalog = True
   2308                                 if not old.disabled and pub.disabled:
   2309                                         disable = True
   2310 
   2311                                 # Now remove the old publisher object using the
   2312                                 # iterator key if the prefix has changed.
   2313                                 if key != pub.prefix:
   2314                                         del publishers[key]
   2315 
   2316                                 # Prepare the new publisher object.
   2317                                 pub.meta_root = \
   2318                                     self.__img._get_publisher_meta_root(
   2319                                     pub.prefix)
   2320                                 pub.transport = self.__img.transport
   2321 
   2322                                 # Finally, add the new publisher object.
   2323                                 publishers[pub.prefix] = pub
   2324                                 break
   2325 
   2326                 def cleanup():
   2327                         new_id, old_pub = orig_pub
   2328                         for new_pfx, new_pub in publishers.iteritems():
   2329                                 if id(new_pub) == new_id:
   2330                                         del publishers[new_pfx]
   2331                                         publishers[old_pub.prefix] = old_pub
   2332                                         break
   2333 
   2334                 repo = pub.selected_repository
   2335                 if not repo.origins:
   2336                         raise api_errors.PublisherOriginRequired(pub.prefix)
   2337 
   2338                 validate = origins_changed(orig_pub[-1].selected_repository,
   2339                     pub.selected_repository)
   2340 
   2341                 try:
   2342                         if disable:
   2343                                 # Remove the publisher's metadata (such as
   2344                                 # catalogs, etc.).  This only needs to be done
   2345                                 # in the event that a publisher is disabled; in
   2346                                 # any other case (the origin changing, etc.),
   2347                                 # refresh() will do the right thing.
   2348                                 self.__img.remove_publisher_metadata(pub)
   2349                         elif not pub.disabled and not refresh_catalog:
   2350                                 refresh_catalog = pub.needs_refresh
   2351 
   2352                         if refresh_catalog and refresh_allowed:
   2353                                 # One of the publisher's repository origins may
   2354                                 # have changed, so the publisher needs to be
   2355                                 # revalidated.
   2356 
   2357                                 if validate:
   2358                                         self.__img.transport.valid_publisher_test(
   2359                                             pub)
   2360 
   2361                                 # Validate all new origins against publisher
   2362                                 # configuration.
   2363                                 for uri, ssl_cert, ssl_key in validate:
   2364                                         repo = publisher.RepositoryURI(uri,
   2365                                             ssl_cert=ssl_cert, ssl_key=ssl_key)
   2366                                         pub.validate_config(repo)
   2367 
   2368                                 self.__refresh(pubs=[pub], immediate=True)
   2369                         elif refresh_catalog:
   2370                                 # Something has changed (such as a repository
   2371                                 # origin) for the publisher, so a refresh should
   2372                                 # occur, but isn't currently allowed.  As such,
   2373                                 # clear the last_refreshed time so that the next
   2374                                 # time the client checks to see if a refresh is
   2375                                 # needed and is allowed, one will be performed.
   2376                                 pub.last_refreshed = None
   2377                 except Exception, e:
   2378                         # If any of the above fails, the original publisher
   2379                         # information needs to be restored so that state is
   2380                         # consistent.
   2381                         cleanup()
   2382                         raise
   2383                 except:
   2384                         # If any of the above fails, the original publisher
   2385                         # information needs to be restored so that state is
   2386                         # consistent.
   2387                         cleanup()
   2388                         raise
   2389 
   2390                 # Successful; so save configuration.
   2391                 self.__img.save_config()
   2392 
   2393         def log_operation_end(self, error=None, result=None):
   2394                 """Marks the end of an operation to be recorded in image
   2395                 history.
   2396 
   2397                 'result' should be a pkg.client.history constant value
   2398                 representing the outcome of an operation.  If not provided,
   2399                 and 'error' is provided, the final result of the operation will
   2400                 be based on the class of 'error' and 'error' will be recorded
   2401                 for the current operation.  If 'result' and 'error' is not
   2402                 provided, success is assumed."""
   2403                 self.__img.history.log_operation_end(error=error, result=result)
   2404 
   2405         def log_operation_error(self, error):
   2406                 """Adds an error to the list of errors to be recorded in image
   2407                 history for the current opreation."""
   2408                 self.__img.history.log_operation_error(error)
   2409 
   2410         def log_operation_start(self, name):
   2411                 """Marks the start of an operation to be recorded in image
   2412                 history."""
   2413                 self.__img.history.log_operation_start(name)
   2414 
   2415         def parse_p5i(self, data=None, fileobj=None, location=None):
   2416                 """Reads the pkg(5) publisher JSON formatted data at 'location'
   2417                 or from the provided file-like object 'fileobj' and returns a
   2418                 list of tuples of the format (publisher object, pkg_names).
   2419                 pkg_names is a list of strings representing package names or
   2420                 FMRIs.  If any pkg_names not specific to a publisher were
   2421                 provided, the last tuple returned will be of the format (None,
   2422                 pkg_names).
   2423 
   2424                 'data' is an optional string containing the p5i data.
   2425 
   2426                 'fileobj' is an optional file-like object that must support a
   2427                 'read' method for retrieving data.
   2428 
   2429                 'location' is an optional string value that should either start
   2430                 with a leading slash and be pathname of a file or a URI string.
   2431                 If it is a URI string, supported protocol schemes are 'file',
   2432                 'ftp', 'http', and 'https'.
   2433 
   2434                 'data' or 'fileobj' or 'location' must be provided."""
   2435 
   2436                 return p5i.parse(data=data, fileobj=fileobj, location=location)
   2437 
   2438         def write_p5i(self, fileobj, pkg_names=None, pubs=None):
   2439                 """Writes the publisher, repository, and provided package names
   2440                 to the provided file-like object 'fileobj' in JSON p5i format.
   2441 
   2442                 'fileobj' is only required to have a 'write' method that accepts
   2443                 data to be written as a parameter.
   2444 
   2445                 'pkg_names' is a dict of lists, tuples, or sets indexed by
   2446                 publisher prefix that contain package names, FMRI strings, or
   2447                 package info objects.  A prefix of "" can be used for packages
   2448                 that are not specific to a publisher.
   2449 
   2450                 'pubs' is an optional list of publisher prefixes or Publisher
   2451                 objects.  If not provided, the information for all publishers
   2452                 (excluding those disabled) will be output."""
   2453 
   2454                 if not pubs:
   2455                         plist = [
   2456                             p for p in self.get_publishers()
   2457                             if not p.disabled
   2458                         ]
   2459                 else:
   2460                         plist = []
   2461                         for p in pubs:
   2462                                 if not isinstance(p, publisher.Publisher):
   2463                                         plist.append(self.__img.get_publisher(
   2464                                             prefix=p, alias=p))
   2465                                 else:
   2466                                         plist.append(p)
   2467 
   2468                 # Transform PackageInfo object entries into PkgFmri entries
   2469                 # before passing them to the p5i module.
   2470                 new_pkg_names = {}
   2471                 for pub in pkg_names:
   2472                         pkglist = []
   2473                         for p in pkg_names[pub]:
   2474                                 if isinstance(p, PackageInfo):
   2475                                         pkglist.append(p.fmri)
   2476                                 else:
   2477                                         pkglist.append(p)
   2478                         new_pkg_names[pub] = pkglist
   2479                 p5i.write(fileobj, plist, pkg_names=new_pkg_names)
   2480 
   2481 
   2482 class Query(query_p.Query):
   2483         """This class is the object used to pass queries into the api functions.
   2484         It encapsulates the possible options available for a query as well as
   2485         the text of the query itself."""
   2486 
   2487         def __init__(self, text, case_sensitive, return_actions=True,
   2488             num_to_return=None, start_point=None):
   2489                 if return_actions:
   2490                         return_type = query_p.Query.RETURN_ACTIONS
   2491                 else:
   2492                         return_type = query_p.Query.RETURN_PACKAGES
   2493                 query_p.Query.__init__(self, text, case_sensitive, return_type,
   2494                     num_to_return, start_point)
   2495 
   2496 
   2497 class PlanDescription(object):
   2498         """A class which describes the changes the plan will make."""
   2499 
   2500         def __init__(self, img):
   2501                 self.__plan = img.imageplan
   2502                 self.__img = img
   2503 
   2504         def get_changes(self):
   2505                 """A generation function that yields tuples of PackageInfo
   2506                 objects of the form (src_pi, dest_pi).
   2507 
   2508                 If 'src_pi' is None, then 'dest_pi' is the package being
   2509                 installed.
   2510 
   2511                 If 'src_pi' is not None, and 'dest_pi' is None, 'src_pi'
   2512                 is the package being removed.
   2513 
   2514                 If 'src_pi' is not None, and 'dest_pi' is not None,
   2515                 then 'src_pi' is the original version of the package,
   2516                 and 'dest_pi' is the new version of the package it is
   2517                 being upgraded to."""
   2518 
   2519                 for pp in self.__plan.pkg_plans:
   2520                         yield (PackageInfo.build_from_fmri(pp.origin_fmri),
   2521                             PackageInfo.build_from_fmri(pp.destination_fmri))
   2522 
   2523         def get_licenses(self, pfmri=None):
   2524                 """A generator function that yields information about the
   2525                 licenses related to the current plan in tuples of the form
   2526                 (dest_fmri, src, dest, accepted, displayed) for the given
   2527                 package FMRI or all packages in the plan.  This is only
   2528                 available for licenses that are being installed or updated.
   2529 
   2530                 'dest_fmri' is the FMRI of the package being installed.
   2531 
   2532                 'src' is a LicenseInfo object if the license of the related
   2533                 package is being updated; otherwise it is None.
   2534 
   2535                 'dest' is the LicenseInfo object for the license that is being
   2536                 installed.
   2537 
   2538                 'accepted' is a boolean value indicating that the license has
   2539                 been marked as accepted for the current plan.
   2540 
   2541                 'displayed' is a boolean value indicating that the license has
   2542                 been marked as displayed for the current plan."""
   2543 
   2544                 for pp in self.__plan.pkg_plans:
   2545                         dfmri = pp.destination_fmri
   2546                         if pfmri and dfmri != pfmri:
   2547                                 continue
   2548 
   2549                         for lid, entry in pp.get_licenses():
   2550                                 src = entry["src"]
   2551                                 src_li = None
   2552                                 if src:
   2553                                         src_li = LicenseInfo(pp.origin_fmri,
   2554                                             src, img=self.__img)
   2555 
   2556                                 dest = entry["dest"]
   2557                                 dest_li = None
   2558                                 if dest:
   2559                                         dest_li = LicenseInfo(
   2560                                             pp.destination_fmri, dest,
   2561                                             img=self.__img)
   2562 
   2563                                 yield (pp.destination_fmri, src_li, dest_li,
   2564                                     entry["accepted"], entry["displayed"])
   2565 
   2566                         if pfmri:
   2567                                 break
   2568 
   2569 def image_create(pkg_client_name, version_id, root, imgtype, is_zone,
   2570     cancel_state_callable=None, facets=misc.EmptyDict, force=False,
   2571     mirrors=misc.EmptyI, origins=misc.EmptyI, prefix=None, refresh_allowed=True,
   2572     repo_uri=None, ssl_cert=None, ssl_key=None, user_provided_dir=False,
   2573     progtrack=None, variants=misc.EmptyDict):
   2574         """Creates an image at the specified location.
   2575 
   2576         'pkg_client_name' is a string containing the name of the client,
   2577         such as "pkg" or "packagemanager".
   2578 
   2579         'version_id' indicates the version of the api the client is
   2580         expecting to use.
   2581 
   2582         'root' is the absolute path of the directory where the image will
   2583         be created.  If it does not exist, it will be created.
   2584 
   2585         'imgtype' is an IMG_TYPE constant representing the type of image
   2586         to create.
   2587 
   2588         'is_zone' is a boolean value indicating whether the image being
   2589         created is for a zone.
   2590 
   2591         'cancel_state_callable' is an optional function reference that will
   2592         be called if the cancellable status of an operation changes.
   2593 
   2594         'facets' is a dictionary of facet names and values to set during
   2595         the image creation process.
   2596 
   2597         'force' is an optional boolean value indicating that if an image
   2598         already exists at the specified 'root' that it should be overwritten.
   2599 
   2600         'mirrors' is an optional list of URI strings that should be added to
   2601         all publishers configured during image creation as mirrors.
   2602 
   2603         'origins' is an optional list of URI strings that should be added to
   2604         all publishers configured during image creation as origins.
   2605 
   2606         'prefix' is an optional publisher prefix to configure as a publisher
   2607         for the new image if origins is provided, or to restrict which publisher
   2608         will be configured if 'repo_uri' is provided.  If this prefix does not
   2609         match the publisher configuration retrieved from the repository, an
   2610         UnknownRepositoryPublishers exception will be raised.  If not provided,
   2611         'refresh_allowed' cannot be False.
   2612 
   2613         'refresh_allowed' is an optional boolean value indicating whether
   2614         publisher configuration data and metadata can be retrieved during
   2615         image creation.  If False, 'repo_uri' cannot be specified and
   2616         a 'prefix' must be provided.
   2617 
   2618         'repo_uri' is an optional URI string of a package repository to
   2619         retrieve publisher configuration information from.  If the target
   2620         repository supports this, all publishers found will be added to the
   2621         image and any origins or mirrors will be added to all of those
   2622         publishers.  If the target repository does not support this, and a
   2623         prefix was not specified, an UnsupportedRepositoryOperation exception
   2624         will be raised.  If the target repository supports the operation, but
   2625         does not provide complete configuration information, a
   2626         RepoPubConfigUnavailable exception will be raised.
   2627 
   2628         'ssl_cert' is an optional pathname of an SSL Certificate file to
   2629         configure all publishers with and to use when retrieving publisher
   2630         configuration information.  If provided, 'ssl_key' must also be
   2631         provided.  The certificate file must be pem-encoded.
   2632 
   2633         'ssl_key' is an optional pathname of an SSL Key file to configure all
   2634         publishers with and to use when retrieving publisher configuration
   2635         information.  If provided, 'ssl_cert' must also be provided.  The
   2636         key file must be pem-encoded.
   2637 
   2638         'user_provided_dir' is an optional boolean value indicating that the
   2639         provided 'root' was user-supplied and that additional error handling
   2640         should be enforced.  This primarily affects cases where a relative
   2641         root has been provided or the root was based on the current working
   2642         directory.
   2643 
   2644         'progtrack' is an optional ProgressTracker object.
   2645 
   2646         'variants' is a dictionary of variant names and values to set during
   2647         the image creation process.
   2648 
   2649         Callers must provide one of the following when calling this function:
   2650          * a 'prefix' and 'repo_uri' (origins and mirrors are optional)
   2651          * no 'prefix' and a 'repo_uri'  (origins and mirrors are optional)
   2652          * a 'prefix' and 'origins'
   2653         """
   2654 
   2655         # Caller must provide a prefix and repository, or no prefix and a
   2656         # repository, or a prefix and origins.
   2657         assert (prefix and repo_uri) or (not prefix and repo_uri) or (prefix and
   2658             origins)
   2659 
   2660         # If prefix isn't provided, and refresh isn't allowed, then auto-config
   2661         # cannot be done.
   2662         assert not repo_uri or (repo_uri and refresh_allowed)
   2663 
   2664         destroy_root = False
   2665         try:
   2666                 destroy_root = not os.path.exists(root)
   2667         except EnvironmentError, e:
   2668                 if e.errno == errno.EACCES:
   2669                         raise api_errors.PermissionsException(
   2670                             e.filename)
   2671                 raise
   2672 
   2673         # The image object must be created first since transport may be
   2674         # needed to retrieve publisher configuration information.
   2675         img = image.Image(root, force=force, imgtype=imgtype,
   2676             progtrack=progtrack, should_exist=False,
   2677             user_provided_dir=user_provided_dir)
   2678 
   2679         api_inst = ImageInterface(img, version_id,
   2680             progtrack, cancel_state_callable, pkg_client_name)
   2681 
   2682         try:
   2683                 if repo_uri:
   2684                         # Assume auto configuration.
   2685                         repo = publisher.RepositoryURI(repo_uri,
   2686                             ssl_cert=ssl_cert, ssl_key=ssl_key)
   2687 
   2688                         pubs = None
   2689                         try:
   2690                                 pubs = api_inst.get_publisherdata(repo=repo)
   2691                         except api_errors.UnsupportedRepositoryOperation:
   2692                                 if not prefix:
   2693                                         raise api_errors.RepoPubConfigUnavailable(
   2694                                             location=repo_uri)
   2695                                 # For a v0 repo where a prefix was specified,
   2696                                 # fallback to manual configuration.
   2697                                 if not origins:
   2698                                         origins = [repo_uri]
   2699                                 repo_uri = None
   2700 
   2701                         if not prefix and not pubs:
   2702                                 # Empty repository configuration.
   2703                                 raise api_errors.RepoPubConfigUnavailable(
   2704                                     location=repo_uri)
   2705 
   2706                         if repo_uri:
   2707                                 for p in pubs:
   2708                                         if p.selected_repository:
   2709                                                 continue
   2710                                         # Incomplete repository configuration.
   2711                                         raise api_errors.RepoPubConfigUnavailable(
   2712                                             location=repo_uri)
   2713 
   2714                 if prefix and not repo_uri:
   2715                         # Auto-configuration not possible or not requested.
   2716                         repo = publisher.Repository()
   2717                         for o in origins:
   2718                                 repo.add_origin(o)
   2719                         for m in mirrors:
   2720                                 repo.add_mirror(m)
   2721                         pub = publisher.Publisher(prefix,
   2722                             repositories=[repo])
   2723                         pubs = [pub]
   2724 
   2725                 if prefix and prefix not in pubs:
   2726                         # If publisher prefix requested isn't found in the list
   2727                         # of publishers at this point, then configuration isn't
   2728                         # possible.
   2729                         known = [p.prefix for p in pubs]
   2730                         raise api_errors.UnknownRepositoryPublishers(
   2731                             known=known, unknown=[prefix], location=repo_uri)
   2732                 elif prefix:
   2733                         # Filter out any publishers that weren't requested.
   2734                         pubs = [
   2735                             p for p in pubs
   2736                             if p.prefix == prefix
   2737                         ]
   2738 
   2739                 # Add additional origins and mirrors that weren't found in the
   2740                 # publisher configuration if provided.
   2741                 for p in pubs:
   2742                         pr = p.selected_repository
   2743                         for o in origins:
   2744                                 if not pr.has_origin(o):
   2745                                         pr.add_origin(o)
   2746                         for m in mirrors:
   2747                                 if not pr.has_mirror(m):
   2748                                         pr.add_mirror(m)
   2749 
   2750                 # Set provided SSL Cert/Key for all configured publishers.
   2751                 for p in pubs:
   2752                         repo = p.selected_repository
   2753                         for o in repo.origins:
   2754                                 o.ssl_cert = ssl_cert
   2755                                 o.ssl_key = ssl_key
   2756                         for m in repo.mirrors:
   2757                                 m.ssl_cert = ssl_cert
   2758                                 m.ssl_key = ssl_key
   2759 
   2760                 img.create(pubs, facets=facets, is_zone=is_zone,
   2761                     progtrack=progtrack, refresh_allowed=refresh_allowed,
   2762                     variants=variants)
   2763         except EnvironmentError, e:
   2764                 if e.errno == errno.EACCES:
   2765                         raise api_errors.PermissionsException(
   2766                             e.filename)
   2767                 if e.errno == errno.EROFS:
   2768                         raise api_errors.ReadOnlyFileSystemException(
   2769                             e.filename)
   2770                 raise
   2771         except:
   2772                 # Ensure a defunct image isn't left behind.
   2773                 img.destroy()
   2774                 if destroy_root and \
   2775                     os.path.abspath(root) != "/" and \
   2776                     os.path.exists(root):
   2777                         # Root didn't exist before create and isn't '/',
   2778                         # so remove it.
   2779                         shutil.rmtree(root, True)
   2780                 raise
   2781 
   2782         img.cleanup_downloads()
   2783 
   2784         return api_inst
   2785 
   2786