Print this page
305 http_proxy value needs more checking for valid url syntax
2341 Client should be more conservative about closing sockets
4235 Misleading "node name" unknown messages when http_proxy set incorrectly
4495 Want ability to cancel individual file downloads
8902 Need a transport that downloads files by GET
9508 Captive portal test only run during catalog refresh
9613 Implicit refresh should raise InvalidDepotResponseException
9615 EOL clientside search v0
9629 EOL clientside support for filelist
9630 Hostile depot should live with other depot code
9631 HTTPS transport should be more rigorous in verification
9670 More specific error exceptions requested from search
9686 network operations should use accept-encoding when appropriate
9715 The info() operation should use the activity_lock


  58 import urlparse
  59 
  60 import pkg
  61 import pkg.actions as actions
  62 import pkg.client.api as api
  63 import pkg.client.api_errors as api_errors
  64 import pkg.client.bootenv as bootenv
  65 import pkg.client.history as history
  66 import pkg.client.image as image
  67 import pkg.client.imagetypes as imgtypes
  68 import pkg.client.progress as progress
  69 import pkg.client.publisher as publisher
  70 import pkg.fmri as fmri
  71 import pkg.misc as misc
  72 
  73 from pkg.client import global_settings
  74 from pkg.client.debugvalues import DebugValues
  75 from pkg.client.history import (RESULT_CANCELED, RESULT_FAILED_BAD_REQUEST,
  76     RESULT_FAILED_CONFIGURATION, RESULT_FAILED_TRANSPORT, RESULT_FAILED_UNKNOWN,
  77     RESULT_FAILED_OUTOFMEMORY)
  78 from pkg.client.filelist import FileListRetrievalError
  79 from pkg.client.retrieve import (CatalogRetrievalError,
  80     DatastreamRetrievalError, ManifestRetrievalError)
  81 from pkg.misc import EmptyI, msg, emsg, PipeError
  82 
  83 CLIENT_API_VERSION = 15
  84 PKG_CLIENT_NAME = "pkg"
  85 
  86 def error(text, cmd=None):
  87         """Emit an error message prefixed by the command name """
  88 
  89         if cmd:
  90                 text = "%s: %s" % (cmd, text)
  91         else:
  92                 # If we get passed something like an Exception, we can convert
  93                 # it down to a string.
  94                 text = str(text)
  95 
  96         # If the message starts with whitespace, assume that it should come
  97         # *before* the command-name prefix.
  98         text_nows = text.lstrip()
  99         ws = text[:len(text) - len(text_nows)]
 100 


 557                         raise RuntimeError("Catalog refresh failed during"
 558                             " image-update.")
 559                 if not stuff_to_do:
 560                         msg(_("No updates available for this image."))
 561                         return 0
 562         except api_errors.InventoryException, e:
 563                 error(_("image-update failed (inventory exception):\n%s") % e)
 564                 return 1
 565         except api_errors.CatalogRefreshException, e:
 566                 if display_catalog_failures(e) == 0:
 567                         if not noexecute:
 568                                 return 1
 569                 else:
 570                         raise RuntimeError("Catalog refresh failed during"
 571                             " image-update.")
 572         except api_errors.BEException, e:
 573                 error(_(e))
 574                 return 1
 575         except (api_errors.CertificateError,
 576             api_errors.PlanCreationException,
 577             api_errors.NetworkUnavailableException,
 578             api_errors.PermissionsException), e:
 579                 # Prepend a newline because otherwise the exception will
 580                 # be printed on the same line as the spinner.
 581                 error("\n" + str(e))
 582                 return 1
 583         except api_errors.IpkgOutOfDateException:
 584                 msg(_("WARNING: pkg(5) appears to be out of date, and should " \
 585                     "be updated before\nrunning image-update.\n"))
 586                 msg(_("Please update pkg(5) using 'pfexec pkg install " \
 587                     "SUNWipkg' and then retry\nthe image-update."))
 588                 return 1
 589         except api_errors.ImageNotFoundException, e:
 590                 error(_("No image rooted at '%s'") % e.user_dir)
 591                 return 1
 592         if noexecute:
 593                 return 0
 594 
 595         ret_code = 0
 596 
 597         # Exceptions which happen here are printed in the above level, with
 598         # or without some extra decoration done here.
 599         # XXX would be nice to kick the progress tracker.
 600         try:
 601                 api_inst.prepare()
 602         except misc.TransportException:
 603                 # move past the progress tracker line.
 604                 msg("\n")
 605                 raise


 606         except KeyboardInterrupt:
 607                 raise
 608         except api_errors.PermissionsException, e:
 609                 # Prepend a newline because otherwise the exception will
 610                 # be printed on the same line as the spinner.
 611                 error("\n" + str(e))
 612                 return 1
 613         except:
 614                 error(_("\nAn unexpected error happened while preparing for " \
 615                     "image-update:"))
 616                 raise
 617 
 618         try:
 619                 api_inst.execute_plan()
 620         except RuntimeError, e:
 621                 error(_("image-update failed: %s") % e)
 622                 ret_code = 1
 623         except api_errors.ImageUpdateOnLiveImageException:
 624                 error(_("image-update cannot be done on live image"))
 625                 ret_code = 1


 719                 # caught while planning.
 720                 stuff_to_do, cre = api_inst.plan_install(pkg_list, filters,
 721                     refresh_catalogs, noexecute, verbose=verbose,
 722                     update_index=update_index)
 723                 if cre and not display_catalog_failures(cre):
 724                         raise RuntimeError("Catalog refresh failed during"
 725                             " install.")
 726                 if not stuff_to_do:
 727                         msg(_("No updates available for this image."))
 728                         return 0
 729         except api_errors.CatalogRefreshException, e:
 730                 if display_catalog_failures(e) == 0:
 731                         if not noexecute:
 732                                 return 1
 733                 else:
 734                         error(_("Catalog refresh failed during install."),
 735                             cmd="install")
 736                         return 1
 737         except (api_errors.CertificateError,
 738             api_errors.PlanCreationException,
 739             api_errors.NetworkUnavailableException,
 740             api_errors.PermissionsException), e:
 741                 # Prepend a newline because otherwise the exception will
 742                 # be printed on the same line as the spinner.
 743                 error("\n" + str(e), cmd="install")
 744                 return 1
 745         except api_errors.InventoryException, e:
 746                 error(_("install failed (inventory exception):\n%s") % e,
 747                     cmd="install")
 748                 return 1
 749         except fmri.IllegalFmri, e:
 750                 error(e, cmd="install")
 751                 return 1
 752 
 753         if noexecute:
 754                 return 0
 755 
 756         # Exceptions which happen here are printed in the above level, with
 757         # or without some extra decoration done here.
 758         # XXX would be nice to kick the progress tracker.
 759         try:
 760                 api_inst.prepare()
 761         except misc.TransportException:
 762                 # move past the progress tracker line.
 763                 msg("\n")
 764                 raise


 765         except KeyboardInterrupt:
 766                 raise
 767         except api_errors.PermissionsException, e:
 768                 # Prepend a newline because otherwise the exception will
 769                 # be printed on the same line as the spinner.
 770                 error("\n" + str(e))
 771                 return 1
 772         except:
 773                 error(_("\nAn unexpected error happened while preparing for " \
 774                     "install:"))
 775                 raise
 776 
 777         ret_code = 0
 778 
 779         try:
 780                 api_inst.execute_plan()
 781         except RuntimeError, e:
 782                 error(_("installation failed: %s") % e)
 783                 ret_code = 1
 784         except api_errors.CorruptedIndexException, e:


 862                 error("""Cannot remove '%s' due to
 863 the following packages that depend on it:""" % e[0])
 864                 for d in e[1]:
 865                         emsg("  %s" % d)
 866                 return 1
 867         except (api_errors.PlanCreationException,
 868             api_errors.PermissionsException), e:
 869                 # Prepend a newline because otherwise the exception will
 870                 # be printed on the same line as the spinner.
 871                 error("\n" + str(e))
 872                 return 1
 873 
 874         if noexecute:
 875                 return 0
 876 
 877         # Exceptions which happen here are printed in the above level, with
 878         # or without some extra decoration done here.
 879         # XXX would be nice to kick the progress tracker.
 880         try:
 881                 api_inst.prepare()
 882         except misc.TransportException:
 883                 # move past the progress tracker line.
 884                 msg("\n")
 885                 raise


 886         except api_errors.FileInUseException, e:
 887                 error("\n" + str(e))
 888                 return 1
 889         except KeyboardInterrupt:
 890                 raise
 891         except:
 892                 error(_("\nAn unexpected error happened while preparing for " \
 893                     "install:"))
 894                 raise
 895 
 896         ret_code = 0
 897 
 898         try:
 899                 api_inst.execute_plan()
 900         except RuntimeError, e:
 901                 error(_("uninstallation failed: %s") % e)
 902                 ret_code = 1
 903         except api_errors.CorruptedIndexException, e:
 904                 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
 905                 ret_code = 1


 916                 ret_code = 1
 917         except KeyboardInterrupt:
 918                 raise
 919         except Exception, e:
 920                 error(_("An unexpected error happened during " \
 921                     "uninstallation: %s") % e)
 922                 raise
 923 
 924         return ret_code
 925 
 926 def freeze(img, args):
 927         """Attempt to take package specified to FROZEN state, with given
 928         restrictions.  Package must have been in the INSTALLED state."""
 929         return 0
 930 
 931 def unfreeze(img, args):
 932         """Attempt to return package specified to INSTALLED state from FROZEN
 933         state."""
 934         return 0
 935 
 936 def process_v_0_search(tup, first):
 937         """Transforms the tuples returned by search v1 into the four column
 938         output format.
 939 
 940         The "tup" parameter is a four tuple with the each entry corresponding
 941         to a column of the output.
 942         
 943         The "first" parameter is a boolean stating whether this is the first
 944         time this function has been called.  This controls the printing of the
 945         header information."""
 946 
 947         try:
 948                 index, mfmri, action, value = tup
 949         except ValueError:
 950                 error(_("The server returned a malformed result.\n"
 951                     "The problematic structure: %r") % (tup,))
 952                 return False
 953         if first:
 954                 msg("%-10s %-9s %-25s %s" %
 955                     ("INDEX", "ACTION", "VALUE", "PACKAGE"))
 956         msg("%-10s %-9s %-25s %s" % (index, action, value,
 957             fmri.PkgFmri(str(mfmri)).get_short_fmri()))
 958         return True
 959 
 960 def __convert_output(a_str, match):
 961         """Converts a string to a three tuple with the information to fill
 962         the INDEX, ACTION, and VALUE columns.
 963 
 964         The "a_str" parameter is the string representation of an action.
 965         
 966         The "match" parameter is a string whose precise interpretation is given
 967         below.
 968 
 969         For most action types, match defines which attribute the query matched
 970         with.  For example, it states whether the basename or path attribute of
 971         a file action matched the query.  Attribute (set) actions are treated
 972         differently because they only have one attribute, and many values
 973         associated with that attribute.  For those actions, the match parameter
 974         states which value matched the query."""
 975         
 976         a = actions.fromstr(a_str.rstrip())
 977         if isinstance(a, actions.attribute.AttributeAction):
 978                 return a.attrs.get(a.key_attr), a.name, match
 979         return match, a.name, a.attrs.get(a.key_attr)


1003                             "The problematic structure:%r") % (tup,))
1004                         return False
1005                 if first:
1006                         msg("%-10s %-9s %-25s %s" %
1007                             ("INDEX", "ACTION", "VALUE", "PACKAGE"))
1008                 try:
1009                         out1, out2, out3 = __convert_output(action, match)
1010                 except (actions.UnknownActionError,
1011                     actions.MalformedActionError), e:
1012                         error(_("The server returned a malformed action.\n%s") %
1013                             e)
1014                         return False
1015                 msg("%-10s %-9s %-25s %s" %
1016                     (out1, out2, out3,
1017                     fmri.PkgFmri(str(pfmri)).get_short_fmri()))
1018         else:
1019                 pfmri = tup
1020                 if first:
1021                         msg("%s" % ("PACKAGE"))
1022                 pub_name = ''
1023                 if pub is not None and "prefix" in pub:



1024                         pub_name = " (%s)" % pub.prefix


1025                 msg("%s%s" %
1026                     (fmri.PkgFmri(str(pfmri)).get_short_fmri(), pub_name))
1027         return True
1028 
1029 def search(img_dir, args):
1030         """Search for the given query."""
1031 
1032         opts, pargs = getopt.getopt(args, "alprs:I")
1033 
1034         local = remote = case_sensitive = False
1035         servers = []
1036         return_actions = True
1037         for opt, arg in opts:
1038                 if opt == "-a":
1039                         return_actions = True
1040                 elif opt == "-l":
1041                         local = True
1042                 elif opt == "-p":
1043                         return_actions = False
1044                 elif opt == "-r":


1083         
1084         try:
1085                 if local:
1086                         searches.append(api_inst.local_search(query))
1087                 if remote:
1088                         searches.append(api_inst.remote_search(query,
1089                             servers=servers))
1090 
1091                 # By default assume we don't find anything.
1092                 retcode = 1
1093 
1094                 for raw_value in itertools.chain(*searches):
1095                         try:
1096                                 query_num, pub, (v, return_type, tmp) = \
1097                                     raw_value
1098                         except ValueError, e:
1099                                 error(_("The server returned a malformed "
1100                                     "result:%r") % (raw_value,))
1101                                 bad_res = True
1102                                 continue
1103                         if v == 0:
1104                                 ret = process_v_0_search(tmp, first)
1105                         else:
1106                                 ret = process_v_1_search(tmp, first,
1107                                     return_type, pub)
1108                         good_res |= ret
1109                         bad_res |= not ret
1110                         first = False
1111         except (api_errors.IncorrectIndexFileHash,
1112             api_errors.InconsistentIndexException):
1113                 error(_("The search index appears corrupted.  Please "
1114                     "rebuild the index with 'pkg rebuild-index'."))
1115                 return 1
1116         except api_errors.ProblematicSearchServers, e:
1117                 error(e)
1118                 bad_res = True
1119         except api_errors.SlowSearchUsed, e:
1120                 error(e)
1121         except (api_errors.IncorrectIndexFileHash,
1122             api_errors.InconsistentIndexException):
1123                 error(_("The search index appears corrupted.  Please "
1124                     "rebuild the index with 'pkg rebuild-index'."))
1125                 return 1


1596         else:
1597                 msg(txt)
1598 
1599         for pub, err in cre.failed:
1600                 if isinstance(err, urllib2.HTTPError):
1601                         emsg("   %s: %s - %s" % \
1602                             (err.filename, err.code, err.msg))
1603                 elif isinstance(err, urllib2.URLError):
1604                         if err.args[0][0] == 8:
1605                                 emsg("    %s: %s" % \
1606                                     (urlparse.urlsplit(
1607                                         pub["origin"])[1].split(":")[0],
1608                                     err.args[0][1]))
1609                         else:
1610                                 if isinstance(err.args[0], socket.timeout):
1611                                         emsg("    %s: %s" % \
1612                                             (pub["origin"], "timeout"))
1613                                 else:
1614                                         emsg("    %s: %s" % \
1615                                             (pub["origin"], err.args[0][1]))
1616                 elif isinstance(err, CatalogRetrievalError) and \
1617                     isinstance(err.exc, EnvironmentError) and \
1618                     err.exc.errno == errno.EACCES:
1619                         if err.prefix:
1620                                 emsg("   ", _("Could not update catalog "
1621                                      "for '%s' due to insufficient "
1622                                      "permissions.") % err.prefix)
1623                         else:
1624                                 emsg("   ", _("Could not update a catalog "
1625                                      "due to insufficient permissions."))
1626 
1627                         emsg("   ", _("Please try the command again "
1628                         "using pfexec, or otherwise increase \n    your "
1629                         "permissions."))
1630                 else:
1631                         emsg("   ", err)
1632 
1633         if cre.message:
1634                 emsg(cre.message)
1635 
1636         return succeeded
1637 
1638 def publisher_refresh(img_dir, args):
1639         """Update metadata for the image's publishers."""
1640 
1641         # XXX will need to show available content series for each package
1642         full_refresh = False
1643         opts, pargs = getopt.getopt(args, "", ["full"])
1644         for opt, arg in opts:
1645                 if opt == "--full":
1646                         full_refresh = True
1647 
1648         try:
1649                 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1650                     get_tracker(), None, PKG_CLIENT_NAME)
1651         except api_errors.ImageNotFoundException, e:
1652                 error(_("'%s' is not an install image") % e.user_dir)
1653                 return 1
1654 
1655         try:
1656                 # The user explicitly requested this refresh, so set the
1657                 # refresh to occur immediately.
1658                 api_inst.refresh(full_refresh=full_refresh, immediate=True,
1659                     pubs=pargs)
1660         except api_errors.PublisherError, e:
1661                 error(e)
1662                 error(_("'pkg publisher' will show a list of publishers."))
1663                 return 1
1664         except (api_errors.PermissionsException,
1665             api_errors.NetworkUnavailableException), e:
1666                 # Prepend a newline because otherwise the exception will
1667                 # be printed on the same line as the spinner.
1668                 error("\n" + str(e))
1669                 return 1
1670         except api_errors.CatalogRefreshException, e:
1671                 if display_catalog_failures(e) == 0:
1672                         return 1
1673                 else:
1674                         return 3
1675         else:
1676                 return 0
1677 
1678 def publisher_set(img, img_dir, args):
1679         """pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
1680             [-O origin_url] [-m mirror to add] [-M mirror to remove]
1681             [--enable] [--disable] [--no-refresh] publisher"""
1682 
1683         preferred = False
1684         ssl_key = None
1685         ssl_cert = None


2272         except OSError, e:
2273                 # Ensure messages are displayed after the spinner.
2274                 emsg("\n")
2275                 error(_("cannot create image at %(image_dir)s: %(reason)s") %
2276                     { "image_dir": image_dir, "reason": e.args[1] })
2277                 return 1
2278         except api_errors.PermissionsException, e:
2279                 # Ensure messages are displayed after the spinner.
2280                 emsg("")
2281                 error(e, cmd="image-create")
2282                 return 1
2283         except api_errors.InvalidDepotResponseException, e:
2284                 # Ensure messages are displayed after the spinner.
2285                 emsg("\n")
2286                 error(_("The URI '%(pub_url)s' does not appear to point to a "
2287                     "valid pkg server.\nPlease check the server's "
2288                     "address and client's network configuration."
2289                     "\nAdditional details:\n\n%(error)s") %
2290                     { "pub_url": pub_url, "error": e },
2291                     cmd="image-create")

2292                 return 1
2293         except api_errors.CatalogRefreshException, cre:
2294                 # Ensure messages are displayed after the spinner.
2295                 error("", cmd="image-create")
2296                 if display_catalog_failures(cre) == 0:
2297                         return 1
2298                 else:
2299                         return 3
2300         return 0
2301 
2302 
2303 def rebuild_index(img_dir, pargs):
2304         """pkg rebuild-index
2305 
2306         Forcibly rebuild the search indexes. Will remove existing indexes
2307         and build new ones from scratch."""
2308         quiet = False
2309 
2310         if pargs:
2311                 usage(_("rebuild-index: command does not take operands " \


2411 
2412                         state = he.operation_end_state
2413                         if state:
2414                                 data.append(("End State", "\n" + state))
2415 
2416                         errors = "\n".join(he.operation_errors)
2417                         if errors:
2418                                 data.append(("Errors", "\n" + errors))
2419 
2420                         for field, value in data:
2421                                 msg("%15s: %s" % (_(field), value))
2422 
2423                         # Separate log entries with a blank line.
2424                         msg("")
2425                 else:
2426                         msg("%-19s %-25s %-15s %s" % (start_time,
2427                             he.operation_name, he.client_name, outcome))
2428 
2429         return 0
2430 



















2431 # To allow exception handler access to the image.
2432 __img = None
2433 
2434 def main_func():
2435         global_settings.client_name = PKG_CLIENT_NAME
2436 
2437         global __img
2438         __img = img = image.Image()
2439 
2440         misc.setlocale(locale.LC_ALL, "", error)
2441         gettext.install("pkg", "/usr/share/locale")
2442 
2443         try:
2444                 opts, pargs = getopt.getopt(sys.argv[1:], "R:D:?",
2445                     ["debug=", "help"])
2446         except getopt.GetoptError, e:
2447                 usage(_("illegal global option -- %s") % e.opt)
2448 
2449         show_usage = False
2450         for opt, arg in opts:


2455                                 usage(_("%(opt)s takes argument of form "
2456                                     "name=value, not %(arg)s") % { "opt":  opt,
2457                                     "arg": arg })
2458                         DebugValues.set_value(key, value)
2459                 elif opt == "-R":
2460                         mydir = arg
2461                 elif opt in ("--help", "-?"):
2462                         show_usage = True
2463 
2464         subcommand = None
2465         if pargs:
2466                 subcommand = pargs.pop(0)
2467                 if subcommand == "help":
2468                         show_usage = True
2469 
2470         if show_usage:
2471                 usage(retcode=0)
2472         elif not subcommand:
2473                 usage()
2474 
2475         socket.setdefaulttimeout(
2476             int(os.environ.get("PKG_CLIENT_TIMEOUT", "30"))) # in seconds
2477 
2478         # Override default PKG_TIMEOUT_MAX if a value has been specified
2479         # in the environment.
2480         global_settings.PKG_TIMEOUT_MAX = int(os.environ.get("PKG_TIMEOUT_MAX",
2481             global_settings.PKG_TIMEOUT_MAX))
2482 







2483         if subcommand == "image-create":
2484                 if "mydir" in locals():
2485                         usage(_("-R not allowed for %s subcommand") %
2486                               subcommand)
2487                 try:
2488                         ret = image_create(img, pargs)
2489                 except getopt.GetoptError, e:
2490                         usage(_("illegal %s option -- %s") % \
2491                             (subcommand, e.opt))
2492                 return ret
2493         elif subcommand == "version":
2494                 if "mydir" in locals():
2495                         usage(_("-R not allowed for %s subcommand") %
2496                               subcommand)
2497                 if pargs:
2498                         usage(_("version: command does not take operands " \
2499                             "('%s')") % " ".join(pargs))
2500                 msg(pkg.VERSION)
2501                 return 0
2502 


2620         except SystemExit, __e:
2621                 if __img:
2622                         __img.history.abort(RESULT_FAILED_UNKNOWN)
2623                 raise __e
2624         except (PipeError, KeyboardInterrupt):
2625                 if __img:
2626                         __img.history.abort(RESULT_CANCELED)
2627                 # We don't want to display any messages here to prevent
2628                 # possible further broken pipe (EPIPE) errors.
2629                 __ret = 1
2630         except api_errors.CertificateError, __e:
2631                 if __img:
2632                         __img.history.abort(RESULT_FAILED_CONFIGURATION)
2633                 error(__e)
2634                 __ret = 1
2635         except api_errors.PublisherError, __e:
2636                 if __img:
2637                         __img.history.abort(RESULT_FAILED_BAD_REQUEST)
2638                 error(__e)
2639                 __ret = 1
2640         except misc.TransportException, __e:
2641                 if __img:
2642                         __img.history.abort(RESULT_FAILED_TRANSPORT)
2643                 error(_("\nMaximum number of network retries exceeded during "
2644                     "download. Details follow:\n%s") % __e)


2645                 __ret = 1
2646         except (ManifestRetrievalError,
2647             DatastreamRetrievalError, FileListRetrievalError), __e:
2648                 if __img:
2649                         __img.history.abort(RESULT_FAILED_TRANSPORT)
2650                 error(_("An error was encountered while attempting to retrieve"
2651                     " package or file data for the requested operation."))
2652                 error(__e)
2653                 __ret = 1
2654         except api_errors.InvalidDepotResponseException, __e:
2655                 if __img:
2656                         __img.history.abort(RESULT_FAILED_TRANSPORT)
2657                 error(_("\nUnable to contact a valid package depot. "
2658                     "This may be due to a problem with the server, "
2659                     "network misconfiguration, or an incorrect pkg client "
2660                     "configuration.  Please check your network settings and "
2661                     "attempt to contact the server using a web browser."))
2662                 error(_("\nAdditional details:\n\n%s") % __e)

2663                 __ret = 1 
2664         except history.HistoryLoadException, __e:
2665                 # Since a history related error occurred, discard all
2666                 # information about the current operation(s) in progress.
2667                 if __img:
2668                         __img.history.clear()
2669                 error(_("An error was encountered while attempting to load "
2670                     "history information\nabout past client operations."))
2671                 error(__e)
2672                 __ret = 1
2673         except history.HistoryStoreException, __e:
2674                 # Since a history related error occurred, discard all
2675                 # information about the current operation(s) in progress.
2676                 if __img:
2677                         __img.history.clear()
2678                 error(_("An error was encountered while attempting to store "
2679                     "information about the\ncurrent operation in client "
2680                     "history."))
2681                 error(__e)
2682                 __ret = 1




  58 import urlparse
  59 
  60 import pkg
  61 import pkg.actions as actions
  62 import pkg.client.api as api
  63 import pkg.client.api_errors as api_errors
  64 import pkg.client.bootenv as bootenv
  65 import pkg.client.history as history
  66 import pkg.client.image as image
  67 import pkg.client.imagetypes as imgtypes
  68 import pkg.client.progress as progress
  69 import pkg.client.publisher as publisher
  70 import pkg.fmri as fmri
  71 import pkg.misc as misc
  72 
  73 from pkg.client import global_settings
  74 from pkg.client.debugvalues import DebugValues
  75 from pkg.client.history import (RESULT_CANCELED, RESULT_FAILED_BAD_REQUEST,
  76     RESULT_FAILED_CONFIGURATION, RESULT_FAILED_TRANSPORT, RESULT_FAILED_UNKNOWN,
  77     RESULT_FAILED_OUTOFMEMORY)



  78 from pkg.misc import EmptyI, msg, emsg, PipeError
  79 
  80 CLIENT_API_VERSION = 15
  81 PKG_CLIENT_NAME = "pkg"
  82 
  83 def error(text, cmd=None):
  84         """Emit an error message prefixed by the command name """
  85 
  86         if cmd:
  87                 text = "%s: %s" % (cmd, text)
  88         else:
  89                 # If we get passed something like an Exception, we can convert
  90                 # it down to a string.
  91                 text = str(text)
  92 
  93         # If the message starts with whitespace, assume that it should come
  94         # *before* the command-name prefix.
  95         text_nows = text.lstrip()
  96         ws = text[:len(text) - len(text_nows)]
  97 


 554                         raise RuntimeError("Catalog refresh failed during"
 555                             " image-update.")
 556                 if not stuff_to_do:
 557                         msg(_("No updates available for this image."))
 558                         return 0
 559         except api_errors.InventoryException, e:
 560                 error(_("image-update failed (inventory exception):\n%s") % e)
 561                 return 1
 562         except api_errors.CatalogRefreshException, e:
 563                 if display_catalog_failures(e) == 0:
 564                         if not noexecute:
 565                                 return 1
 566                 else:
 567                         raise RuntimeError("Catalog refresh failed during"
 568                             " image-update.")
 569         except api_errors.BEException, e:
 570                 error(_(e))
 571                 return 1
 572         except (api_errors.CertificateError,
 573             api_errors.PlanCreationException,

 574             api_errors.PermissionsException), e:
 575                 # Prepend a newline because otherwise the exception will
 576                 # be printed on the same line as the spinner.
 577                 error("\n" + str(e))
 578                 return 1
 579         except api_errors.IpkgOutOfDateException:
 580                 msg(_("WARNING: pkg(5) appears to be out of date, and should " \
 581                     "be updated before\nrunning image-update.\n"))
 582                 msg(_("Please update pkg(5) using 'pfexec pkg install " \
 583                     "SUNWipkg' and then retry\nthe image-update."))
 584                 return 1
 585         except api_errors.ImageNotFoundException, e:
 586                 error(_("No image rooted at '%s'") % e.user_dir)
 587                 return 1
 588         if noexecute:
 589                 return 0
 590 
 591         ret_code = 0
 592 
 593         # Exceptions which happen here are printed in the above level, with
 594         # or without some extra decoration done here.
 595         # XXX would be nice to kick the progress tracker.
 596         try:
 597                 api_inst.prepare()
 598         except api_errors.TransportError, e:
 599                 # move past the progress tracker line.
 600                 msg("\n")
 601                 if verbose:
 602                         e.verbose = True
 603                 raise e
 604         except KeyboardInterrupt:
 605                 raise
 606         except api_errors.PermissionsException, e:
 607                 # Prepend a newline because otherwise the exception will
 608                 # be printed on the same line as the spinner.
 609                 error("\n" + str(e))
 610                 return 1
 611         except:
 612                 error(_("\nAn unexpected error happened while preparing for " \
 613                     "image-update:"))
 614                 raise
 615 
 616         try:
 617                 api_inst.execute_plan()
 618         except RuntimeError, e:
 619                 error(_("image-update failed: %s") % e)
 620                 ret_code = 1
 621         except api_errors.ImageUpdateOnLiveImageException:
 622                 error(_("image-update cannot be done on live image"))
 623                 ret_code = 1


 717                 # caught while planning.
 718                 stuff_to_do, cre = api_inst.plan_install(pkg_list, filters,
 719                     refresh_catalogs, noexecute, verbose=verbose,
 720                     update_index=update_index)
 721                 if cre and not display_catalog_failures(cre):
 722                         raise RuntimeError("Catalog refresh failed during"
 723                             " install.")
 724                 if not stuff_to_do:
 725                         msg(_("No updates available for this image."))
 726                         return 0
 727         except api_errors.CatalogRefreshException, e:
 728                 if display_catalog_failures(e) == 0:
 729                         if not noexecute:
 730                                 return 1
 731                 else:
 732                         error(_("Catalog refresh failed during install."),
 733                             cmd="install")
 734                         return 1
 735         except (api_errors.CertificateError,
 736             api_errors.PlanCreationException,

 737             api_errors.PermissionsException), e:
 738                 # Prepend a newline because otherwise the exception will
 739                 # be printed on the same line as the spinner.
 740                 error("\n" + str(e), cmd="install")
 741                 return 1
 742         except api_errors.InventoryException, e:
 743                 error(_("install failed (inventory exception):\n%s") % e,
 744                     cmd="install")
 745                 return 1
 746         except fmri.IllegalFmri, e:
 747                 error(e, cmd="install")
 748                 return 1
 749 
 750         if noexecute:
 751                 return 0
 752 
 753         # Exceptions which happen here are printed in the above level, with
 754         # or without some extra decoration done here.
 755         # XXX would be nice to kick the progress tracker.
 756         try:
 757                 api_inst.prepare()
 758         except api_errors.TransportError, e:
 759                 # move past the progress tracker line.
 760                 msg("\n")
 761                 if verbose:
 762                         e.verbose = True
 763                 raise e
 764         except KeyboardInterrupt:
 765                 raise
 766         except api_errors.PermissionsException, e:
 767                 # Prepend a newline because otherwise the exception will
 768                 # be printed on the same line as the spinner.
 769                 error("\n" + str(e))
 770                 return 1
 771         except:
 772                 error(_("\nAn unexpected error happened while preparing for " \
 773                     "install:"))
 774                 raise
 775 
 776         ret_code = 0
 777 
 778         try:
 779                 api_inst.execute_plan()
 780         except RuntimeError, e:
 781                 error(_("installation failed: %s") % e)
 782                 ret_code = 1
 783         except api_errors.CorruptedIndexException, e:


 861                 error("""Cannot remove '%s' due to
 862 the following packages that depend on it:""" % e[0])
 863                 for d in e[1]:
 864                         emsg("  %s" % d)
 865                 return 1
 866         except (api_errors.PlanCreationException,
 867             api_errors.PermissionsException), e:
 868                 # Prepend a newline because otherwise the exception will
 869                 # be printed on the same line as the spinner.
 870                 error("\n" + str(e))
 871                 return 1
 872 
 873         if noexecute:
 874                 return 0
 875 
 876         # Exceptions which happen here are printed in the above level, with
 877         # or without some extra decoration done here.
 878         # XXX would be nice to kick the progress tracker.
 879         try:
 880                 api_inst.prepare()
 881         except api_errors.TransportError, e:
 882                 # move past the progress tracker line.
 883                 msg("\n")
 884                 if verbose:
 885                         e.verbose = True
 886                 raise e
 887         except api_errors.FileInUseException, e:
 888                 error("\n" + str(e))
 889                 return 1
 890         except KeyboardInterrupt:
 891                 raise
 892         except:
 893                 error(_("\nAn unexpected error happened while preparing for " \
 894                     "install:"))
 895                 raise
 896 
 897         ret_code = 0
 898 
 899         try:
 900                 api_inst.execute_plan()
 901         except RuntimeError, e:
 902                 error(_("uninstallation failed: %s") % e)
 903                 ret_code = 1
 904         except api_errors.CorruptedIndexException, e:
 905                 error(INCONSISTENT_INDEX_ERROR_MESSAGE)
 906                 ret_code = 1


 917                 ret_code = 1
 918         except KeyboardInterrupt:
 919                 raise
 920         except Exception, e:
 921                 error(_("An unexpected error happened during " \
 922                     "uninstallation: %s") % e)
 923                 raise
 924 
 925         return ret_code
 926 
 927 def freeze(img, args):
 928         """Attempt to take package specified to FROZEN state, with given
 929         restrictions.  Package must have been in the INSTALLED state."""
 930         return 0
 931 
 932 def unfreeze(img, args):
 933         """Attempt to return package specified to INSTALLED state from FROZEN
 934         state."""
 935         return 0
 936 
























 937 def __convert_output(a_str, match):
 938         """Converts a string to a three tuple with the information to fill
 939         the INDEX, ACTION, and VALUE columns.
 940 
 941         The "a_str" parameter is the string representation of an action.
 942         
 943         The "match" parameter is a string whose precise interpretation is given
 944         below.
 945 
 946         For most action types, match defines which attribute the query matched
 947         with.  For example, it states whether the basename or path attribute of
 948         a file action matched the query.  Attribute (set) actions are treated
 949         differently because they only have one attribute, and many values
 950         associated with that attribute.  For those actions, the match parameter
 951         states which value matched the query."""
 952         
 953         a = actions.fromstr(a_str.rstrip())
 954         if isinstance(a, actions.attribute.AttributeAction):
 955                 return a.attrs.get(a.key_attr), a.name, match
 956         return match, a.name, a.attrs.get(a.key_attr)


 980                             "The problematic structure:%r") % (tup,))
 981                         return False
 982                 if first:
 983                         msg("%-10s %-9s %-25s %s" %
 984                             ("INDEX", "ACTION", "VALUE", "PACKAGE"))
 985                 try:
 986                         out1, out2, out3 = __convert_output(action, match)
 987                 except (actions.UnknownActionError,
 988                     actions.MalformedActionError), e:
 989                         error(_("The server returned a malformed action.\n%s") %
 990                             e)
 991                         return False
 992                 msg("%-10s %-9s %-25s %s" %
 993                     (out1, out2, out3,
 994                     fmri.PkgFmri(str(pfmri)).get_short_fmri()))
 995         else:
 996                 pfmri = tup
 997                 if first:
 998                         msg("%s" % ("PACKAGE"))
 999                 pub_name = ''
1000                 # If pub is not None, it's either a RepositoryURI or a Publisher
1001                 # object.  If it's a Publisher, it has a prefix.  Otherwise,
1002                 # use the uri.
1003                 if pub is not None and hasattr(pub, "prefix"):
1004                         pub_name = " (%s)" % pub.prefix
1005                 elif pub is not None and hasattr(pub, "uri"):
1006                         pub_name = " (%s)" % pub.uri
1007                 msg("%s%s" %
1008                     (fmri.PkgFmri(str(pfmri)).get_short_fmri(), pub_name))
1009         return True
1010 
1011 def search(img_dir, args):
1012         """Search for the given query."""
1013 
1014         opts, pargs = getopt.getopt(args, "alprs:I")
1015 
1016         local = remote = case_sensitive = False
1017         servers = []
1018         return_actions = True
1019         for opt, arg in opts:
1020                 if opt == "-a":
1021                         return_actions = True
1022                 elif opt == "-l":
1023                         local = True
1024                 elif opt == "-p":
1025                         return_actions = False
1026                 elif opt == "-r":


1065         
1066         try:
1067                 if local:
1068                         searches.append(api_inst.local_search(query))
1069                 if remote:
1070                         searches.append(api_inst.remote_search(query,
1071                             servers=servers))
1072 
1073                 # By default assume we don't find anything.
1074                 retcode = 1
1075 
1076                 for raw_value in itertools.chain(*searches):
1077                         try:
1078                                 query_num, pub, (v, return_type, tmp) = \
1079                                     raw_value
1080                         except ValueError, e:
1081                                 error(_("The server returned a malformed "
1082                                     "result:%r") % (raw_value,))
1083                                 bad_res = True
1084                                 continue



1085                         ret = process_v_1_search(tmp, first,
1086                             return_type, pub)
1087                         good_res |= ret
1088                         bad_res |= not ret
1089                         first = False
1090         except (api_errors.IncorrectIndexFileHash,
1091             api_errors.InconsistentIndexException):
1092                 error(_("The search index appears corrupted.  Please "
1093                     "rebuild the index with 'pkg rebuild-index'."))
1094                 return 1
1095         except api_errors.ProblematicSearchServers, e:
1096                 error(e)
1097                 bad_res = True
1098         except api_errors.SlowSearchUsed, e:
1099                 error(e)
1100         except (api_errors.IncorrectIndexFileHash,
1101             api_errors.InconsistentIndexException):
1102                 error(_("The search index appears corrupted.  Please "
1103                     "rebuild the index with 'pkg rebuild-index'."))
1104                 return 1


1575         else:
1576                 msg(txt)
1577 
1578         for pub, err in cre.failed:
1579                 if isinstance(err, urllib2.HTTPError):
1580                         emsg("   %s: %s - %s" % \
1581                             (err.filename, err.code, err.msg))
1582                 elif isinstance(err, urllib2.URLError):
1583                         if err.args[0][0] == 8:
1584                                 emsg("    %s: %s" % \
1585                                     (urlparse.urlsplit(
1586                                         pub["origin"])[1].split(":")[0],
1587                                     err.args[0][1]))
1588                         else:
1589                                 if isinstance(err.args[0], socket.timeout):
1590                                         emsg("    %s: %s" % \
1591                                             (pub["origin"], "timeout"))
1592                                 else:
1593                                         emsg("    %s: %s" % \
1594                                             (pub["origin"], err.args[0][1]))







1595                 else:







1596                         emsg("   ", err)
1597 
1598         if cre.message:
1599                 emsg(cre.message)
1600 
1601         return succeeded
1602 
1603 def publisher_refresh(img_dir, args):
1604         """Update metadata for the image's publishers."""
1605 
1606         # XXX will need to show available content series for each package
1607         full_refresh = False
1608         opts, pargs = getopt.getopt(args, "", ["full"])
1609         for opt, arg in opts:
1610                 if opt == "--full":
1611                         full_refresh = True
1612 
1613         try:
1614                 api_inst = api.ImageInterface(img_dir, CLIENT_API_VERSION,
1615                     get_tracker(), None, PKG_CLIENT_NAME)
1616         except api_errors.ImageNotFoundException, e:
1617                 error(_("'%s' is not an install image") % e.user_dir)
1618                 return 1
1619 
1620         try:
1621                 # The user explicitly requested this refresh, so set the
1622                 # refresh to occur immediately.
1623                 api_inst.refresh(full_refresh=full_refresh, immediate=True,
1624                     pubs=pargs)
1625         except api_errors.PublisherError, e:
1626                 error(e)
1627                 error(_("'pkg publisher' will show a list of publishers."))
1628                 return 1
1629         except (api_errors.PermissionsException), e:

1630                 # Prepend a newline because otherwise the exception will
1631                 # be printed on the same line as the spinner.
1632                 error("\n" + str(e))
1633                 return 1
1634         except api_errors.CatalogRefreshException, e:
1635                 if display_catalog_failures(e) == 0:
1636                         return 1
1637                 else:
1638                         return 3
1639         else:
1640                 return 0
1641 
1642 def publisher_set(img, img_dir, args):
1643         """pkg set-publisher [-Ped] [-k ssl_key] [-c ssl_cert] [--reset-uuid]
1644             [-O origin_url] [-m mirror to add] [-M mirror to remove]
1645             [--enable] [--disable] [--no-refresh] publisher"""
1646 
1647         preferred = False
1648         ssl_key = None
1649         ssl_cert = None


2236         except OSError, e:
2237                 # Ensure messages are displayed after the spinner.
2238                 emsg("\n")
2239                 error(_("cannot create image at %(image_dir)s: %(reason)s") %
2240                     { "image_dir": image_dir, "reason": e.args[1] })
2241                 return 1
2242         except api_errors.PermissionsException, e:
2243                 # Ensure messages are displayed after the spinner.
2244                 emsg("")
2245                 error(e, cmd="image-create")
2246                 return 1
2247         except api_errors.InvalidDepotResponseException, e:
2248                 # Ensure messages are displayed after the spinner.
2249                 emsg("\n")
2250                 error(_("The URI '%(pub_url)s' does not appear to point to a "
2251                     "valid pkg server.\nPlease check the server's "
2252                     "address and client's network configuration."
2253                     "\nAdditional details:\n\n%(error)s") %
2254                     { "pub_url": pub_url, "error": e },
2255                     cmd="image-create")
2256                 print_proxy_config()
2257                 return 1
2258         except api_errors.CatalogRefreshException, cre:
2259                 # Ensure messages are displayed after the spinner.
2260                 error("", cmd="image-create")
2261                 if display_catalog_failures(cre) == 0:
2262                         return 1
2263                 else:
2264                         return 3
2265         return 0
2266 
2267 
2268 def rebuild_index(img_dir, pargs):
2269         """pkg rebuild-index
2270 
2271         Forcibly rebuild the search indexes. Will remove existing indexes
2272         and build new ones from scratch."""
2273         quiet = False
2274 
2275         if pargs:
2276                 usage(_("rebuild-index: command does not take operands " \


2376 
2377                         state = he.operation_end_state
2378                         if state:
2379                                 data.append(("End State", "\n" + state))
2380 
2381                         errors = "\n".join(he.operation_errors)
2382                         if errors:
2383                                 data.append(("Errors", "\n" + errors))
2384 
2385                         for field, value in data:
2386                                 msg("%15s: %s" % (_(field), value))
2387 
2388                         # Separate log entries with a blank line.
2389                         msg("")
2390                 else:
2391                         msg("%-19s %-25s %-15s %s" % (start_time,
2392                             he.operation_name, he.client_name, outcome))
2393 
2394         return 0
2395 
2396 def print_proxy_config():
2397         """If the user has configured http_proxy or https_proxy in the
2398         environment, print out the values.  Some transport errors are
2399         not debuggable without this information handy."""
2400 
2401         http_proxy = os.environ.get("http_proxy", None)
2402         https_proxy = os.environ.get("https_proxy", None)
2403 
2404         if not http_proxy and not https_proxy:
2405                 return
2406 
2407         emsg(_("\nThe following proxy configuration is set in the"
2408             " environment:\n"))
2409         if http_proxy:
2410                 emsg(_("http_proxy: %s\n") % http_proxy)
2411         if https_proxy:
2412                 emsg(_("https_proxy: %s\n") % https_proxy)
2413 
2414 
2415 # To allow exception handler access to the image.
2416 __img = None
2417 
2418 def main_func():
2419         global_settings.client_name = PKG_CLIENT_NAME
2420 
2421         global __img
2422         __img = img = image.Image()
2423 
2424         misc.setlocale(locale.LC_ALL, "", error)
2425         gettext.install("pkg", "/usr/share/locale")
2426 
2427         try:
2428                 opts, pargs = getopt.getopt(sys.argv[1:], "R:D:?",
2429                     ["debug=", "help"])
2430         except getopt.GetoptError, e:
2431                 usage(_("illegal global option -- %s") % e.opt)
2432 
2433         show_usage = False
2434         for opt, arg in opts:


2439                                 usage(_("%(opt)s takes argument of form "
2440                                     "name=value, not %(arg)s") % { "opt":  opt,
2441                                     "arg": arg })
2442                         DebugValues.set_value(key, value)
2443                 elif opt == "-R":
2444                         mydir = arg
2445                 elif opt in ("--help", "-?"):
2446                         show_usage = True
2447 
2448         subcommand = None
2449         if pargs:
2450                 subcommand = pargs.pop(0)
2451                 if subcommand == "help":
2452                         show_usage = True
2453 
2454         if show_usage:
2455                 usage(retcode=0)
2456         elif not subcommand:
2457                 usage()
2458 
2459         # Override default PKG_TIMEOUT_MAX and PKG_CLIENT_TIMEOUT 
2460         # if a value has been specified in the environment.



2461         global_settings.PKG_TIMEOUT_MAX = int(os.environ.get("PKG_TIMEOUT_MAX",
2462             global_settings.PKG_TIMEOUT_MAX))
2463 
2464         global_settings.PKG_CLIENT_TIMEOUT = int(os.environ.get(
2465             "PKG_CLIENT_TIMEOUT", global_settings.PKG_CLIENT_TIMEOUT))
2466 
2467         # This call only affects sockets created by Python.  The transport
2468         # framework must set the timeout value internally.
2469         socket.setdefaulttimeout(global_settings.PKG_TIMEOUT_MAX) # in seconds
2470 
2471         if subcommand == "image-create":
2472                 if "mydir" in locals():
2473                         usage(_("-R not allowed for %s subcommand") %
2474                               subcommand)
2475                 try:
2476                         ret = image_create(img, pargs)
2477                 except getopt.GetoptError, e:
2478                         usage(_("illegal %s option -- %s") % \
2479                             (subcommand, e.opt))
2480                 return ret
2481         elif subcommand == "version":
2482                 if "mydir" in locals():
2483                         usage(_("-R not allowed for %s subcommand") %
2484                               subcommand)
2485                 if pargs:
2486                         usage(_("version: command does not take operands " \
2487                             "('%s')") % " ".join(pargs))
2488                 msg(pkg.VERSION)
2489                 return 0
2490 


2608         except SystemExit, __e:
2609                 if __img:
2610                         __img.history.abort(RESULT_FAILED_UNKNOWN)
2611                 raise __e
2612         except (PipeError, KeyboardInterrupt):
2613                 if __img:
2614                         __img.history.abort(RESULT_CANCELED)
2615                 # We don't want to display any messages here to prevent
2616                 # possible further broken pipe (EPIPE) errors.
2617                 __ret = 1
2618         except api_errors.CertificateError, __e:
2619                 if __img:
2620                         __img.history.abort(RESULT_FAILED_CONFIGURATION)
2621                 error(__e)
2622                 __ret = 1
2623         except api_errors.PublisherError, __e:
2624                 if __img:
2625                         __img.history.abort(RESULT_FAILED_BAD_REQUEST)
2626                 error(__e)
2627                 __ret = 1
2628         except api_errors.TransportError, __e:
2629                 if __img:
2630                         __img.history.abort(RESULT_FAILED_TRANSPORT)
2631                 emsg(_("\nErrors were encountered while attempting to retrieve"
2632                     " package or file data for\nthe requested operation."))
2633                 emsg(_("Details follow:\n\n%s") % __e)
2634                 print_proxy_config()
2635                 __ret = 1








2636         except api_errors.InvalidDepotResponseException, __e:
2637                 if __img:
2638                         __img.history.abort(RESULT_FAILED_TRANSPORT)
2639                 emsg(_("\nUnable to contact a valid package depot. "
2640                     "This may be due to a problem with the server, "
2641                     "network misconfiguration, or an incorrect pkg client "
2642                     "configuration.  Please check your network settings and "
2643                     "attempt to contact the server using a web browser."))
2644                 emsg(_("\nAdditional details:\n\n%s") % __e)
2645                 print_proxy_config()
2646                 __ret = 1 
2647         except history.HistoryLoadException, __e:
2648                 # Since a history related error occurred, discard all
2649                 # information about the current operation(s) in progress.
2650                 if __img:
2651                         __img.history.clear()
2652                 error(_("An error was encountered while attempting to load "
2653                     "history information\nabout past client operations."))
2654                 error(__e)
2655                 __ret = 1
2656         except history.HistoryStoreException, __e:
2657                 # Since a history related error occurred, discard all
2658                 # information about the current operation(s) in progress.
2659                 if __img:
2660                         __img.history.clear()
2661                 error(_("An error was encountered while attempting to store "
2662                     "information about the\ncurrent operation in client "
2663                     "history."))
2664                 error(__e)
2665                 __ret = 1