18827 pkg(1) should provide interfaces to control backup BE creation
authorShawn Walker <shawn.walker@oracle.com>
Fri, 19 Aug 2011 18:33:58 -0700
changeset 2517 8f26a1d7100c
parent 2516 bcea906ea68d
child 2518 8ac25b43cff1
18827 pkg(1) should provide interfaces to control backup BE creation 17608 image property needed to control BE creation 18826 operations performed on the live BE should create backup BEs as needed
doc/client_api_versions.txt
src/client.py
src/gui/modules/misc_non_gui.py
src/man/pkg.1
src/modules/client/actuator.py
src/modules/client/api.py
src/modules/client/bootenv.py
src/modules/client/imageconfig.py
src/modules/client/imageplan.py
src/modules/client/pkgdefs.py
src/modules/lint/engine.py
src/pkgdep.py
src/sysrepo.py
src/tests/pkg5unittest.py
src/util/distro-import/importer.py
--- a/doc/client_api_versions.txt	Fri Aug 19 17:59:28 2011 -0700
+++ b/doc/client_api_versions.txt	Fri Aug 19 18:33:58 2011 -0700
@@ -1,3 +1,23 @@
+Version 67:
+Compatible with clients using version 66.
+
+    pkg.client.api.ImageInterface has changed as follows:
+
+        * planning functions such as gen_plan_install that allow
+          specifying a be_name now also support specifying a
+          backup_be_name, and the ability to create a backup BE
+          by setting backup_be=True.
+
+    pkg.client.api.PlanDescription has changed as follows:
+
+        * New properties named 'backup_be' and 'backup_be_name' have been
+          added to allow callers to determine whether a backup BE will be
+          created and what name was specified for it.
+
+        * The new property 'is_active_root_be' has been added to allow
+          consumers to determine if the image being modified is for the
+          live, active root BE.
+
 Version 66:
 Incompatible with clients using versions 0-63.
 
--- a/src/client.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/client.py	Fri Aug 19 18:33:58 2011 -0700
@@ -90,7 +90,7 @@
         import sys
         sys.exit(1)
 
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 PKG_CLIENT_NAME = "pkg"
 
 JUST_UNKNOWN = 0
@@ -149,18 +149,22 @@
             "version"]
 
         basic_usage["install"] = _(
-            "[-nvq] [-g path_or_uri ...] [--accept] [--licenses]\n"
-            "            [--no-be-activate] [--no-index] [--no-refresh] [--deny-new-be |\n"
-            "            --require-new-be] [--be-name name] [--reject pkg_fmri_pattern ... ]\n"
-            "            pkg_fmri_pattern ...")
+            "[-nvq] [-g path_or_uri ...] [--accept]\n"
+            "            [--licenses] [--no-be-activate] [--no-index] [--no-refresh]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            [--reject pkg_fmri_pattern ... ] pkg_fmri_pattern ...")
         basic_usage["uninstall"] = _(
-            "[-nvq] [--no-be-activate] [--no-index] [--deny-new-be |\n"
-            "            --require-new-be] [--be-name name] pkg_fmri_pattern ...")
+            "[-nvq] [--no-be-activate] [--no-index]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            pkg_fmri_pattern ...")
         basic_usage["update"] = _(
-            "[-fnvq] [-g path_or_uri ...] [--accept] [--licenses]\n"
-            "            [--no-be-activate] [--no-index] [--no-refresh] [--deny-new-be |\n"
-            "            --require-new-be] [--be-name name] [--reject pkg_fmri_pattern ...]\n"
-            "            [pkg_fmri_pattern ...]")
+            "[-fnvq] [-g path_or_uri ...] [--accept]\n"
+            "            [--licenses] [--no-be-activate] [--no-index] [--no-refresh]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            [--reject pkg_fmri_pattern ...] [pkg_fmri_pattern ...]")
         basic_usage["list"] = _(
             "[-Hafnsuv] [-g path_or_uri ...] [--no-refresh]\n"
             "            [pkg_fmri_pattern ...]")
@@ -229,8 +233,10 @@
         adv_usage["verify"] = _("[-Hqv] [pkg_fmri_pattern ...]")
         adv_usage["fix"] = _("[--accept] [--licenses] [pkg_fmri_pattern ...]")
         adv_usage["revert"] = _(
-            "[-nv] [--no-be-activate] [--be-name name] [--deny-new-be |\n"
-            "            --require-new-be] (--tagged tag-name ... | path-to-file ...)")
+            "[-nv] [--no-be-activate]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            (--tagged tag-name ... | path-to-file ...)")
 
         adv_usage["image-create"] = _(
             "[-FPUfz] [--force] [--full|--partial|--user] [--zone]\n"
@@ -240,21 +246,28 @@
             "            [--facet <facet_spec>=(True|False) ...]\n"
             "            [(-p|--publisher) [<name>=]<repo_uri>] dir")
         adv_usage["change-variant"] = _(
-            "[-nvq] [-g path_or_uri ...] [--accept] [--licenses]\n"
-            "            [--no-be-activate] [--deny-new-be | --require-new-be]\n"
-            "            [--be-name name] <variant_spec>=<instance> ...")
+            "[-nvq] [-g path_or_uri ...]\n"
+            "            [--accept] [--licenses] [--no-be-activate]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            <variant_spec>=<instance> ...")
 
         adv_usage["change-facet"] = _(
-            "[-nvq] [-g path_or_uri ...] [--accept] [--licenses]\n"
-            "            [--no-be-activate] [--deny-new-be | --require-new-be]\n"
-            "            [--be-name name] <facet_spec>=[True|False|None] ...")
+            "[-nvq] [-g path_or_uri ...]\n"
+            "            [--accept] [--licenses] [--no-be-activate]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            <facet_spec>=[True|False|None] ...")
 
         adv_usage["mediator"] = _("[-aH] [-F format] [<mediator> ...]")
         adv_usage["set-mediator"] = _(
-            "[-nv] [-I <implementation>] [-V <version>]\n"
-            "            [--no-be-activate] [--deny-new-be | --require-new-be]\n"
-            "            [--be-name name] <mediator> ...")
+            "[-nv] [-I <implementation>]\n"
+            "            [-V <version>] [--no-be-activate]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name name]\n"
+            "            [--deny-new-be | --require-new-be] [--be-name name]\n"
+            "            <mediator> ...")
         adv_usage["unset-mediator"] = _("[-nvIV] [--no-be-activate]\n"
+            "            [--no-backup-be | --require-backup-be] [--backup-be-name]\n"
             "            [--deny-new-be | --require-new-be] [--be-name name]\n"
             "            <mediator> ...")
 
@@ -901,6 +914,19 @@
 
         plan = api_inst.describe()
 
+        if not api_inst.is_zone and not plan.is_active_root_be:
+                # Warn the user since this isn't likely what they wanted.
+                if plan.new_be:
+                        logger.warning(_("""\
+WARNING: The boot environment being modified is not the active one.  Changes
+made in the active BE will not be reflected on the next boot.
+"""))
+                else:
+                        logger.warning(_("""\
+WARNING: The boot environment being modified is not the active one.  Changes
+made will not be reflected on the next boot.
+"""))
+
         a, r, i, c = [], [], [], []
         for src, dest in plan.get_changes():
                 if dest is None:
@@ -955,6 +981,9 @@
                         status.append((_("Activate boot environment:"),
                             bool_str(plan.activate_be)))
 
+                status.append((_("Create backup boot environment:"),
+                    bool_str(plan.backup_be)))
+
                 if not plan.new_be:
                         cond_show(_("Services to change:"), "%d",
                             len(plan.get_services()))
@@ -1063,7 +1092,9 @@
         removed_fmris = []
         changed_fmris = []
         affected_fmris = []
+        backup_be_created = False
         new_be_created = False
+        backup_be_name = None
         be_name = None
         boot_archive_rebuilt = False
         be_activated = True
@@ -1094,7 +1125,9 @@
                         else:
                                 added_fmris.append(str(add))
                 variants_changed, facets_changed = plan.get_parsable_varcets()
+                backup_be_created = plan.backup_be
                 new_be_created = plan.new_be
+                backup_be_name = plan.backup_be_name
                 be_name = plan.be_name
                 boot_archive_rebuilt = plan.update_boot_archive
                 be_activated = plan.activate_be
@@ -1119,7 +1152,9 @@
                         api_inst.set_plan_license_status(dfmri, dest_li.license,
                             displayed=True)
         ret = {
+            "create-backup-be": backup_be_created,
             "create-new-be": new_be_created,
+            "backup-be-name": backup_be_name,
             "be-name": be_name,
             "boot-archive-rebuild": boot_archive_rebuilt,
             "activate-be": be_activated,
@@ -1485,8 +1520,9 @@
 
         # All the api interface functions that we inovke have some
         # common arguments.  Set those up now.
-        kwargs["accept"] = _accept
-        kwargs["li_ignore"] = _li_ignore
+        if _op != PKG_OP_REVERT:
+                kwargs["accept"] = _accept
+                kwargs["li_ignore"] = _li_ignore
         kwargs["noexecute"] = _noexecute
         if _origins != None:
                 kwargs["repos"] = _origins
@@ -1506,6 +1542,8 @@
                 api_plan_func = _api_inst.gen_plan_detach
         elif _op == PKG_OP_INSTALL:
                 api_plan_func = _api_inst.gen_plan_install
+        elif _op == PKG_OP_REVERT:
+                api_plan_func = _api_inst.gen_plan_revert
         elif _op == PKG_OP_SYNC:
                 api_plan_func = _api_inst.gen_plan_sync
         elif _op == PKG_OP_UNINSTALL:
@@ -1597,15 +1635,34 @@
         del opts_new["deny_new_be"]
         opts_new["new_be"] = None
 
-        if opts["require_new_be"] and opts["deny_new_be"]:
+        if (opts["be_name"] or opts["require_new_be"]) and opts["deny_new_be"]:
                 opts_err_incompat("--require-new-be", "--deny-new-be", op)
 
-        # create a new key called "new_be" in the options array
-        if opts["require_new_be"]:
+        # create a new key called "backup_be" in the options array
+        if opts["require_new_be"] or opts["be_name"]:
                 opts_new["new_be"] = True
         if opts["deny_new_be"]:
                 opts_new["new_be"] = False
 
+        # synthesize require_backup_be and no_backup_be into backup_be
+        del opts_new["require_backup_be"]
+        del opts_new["no_backup_be"]
+        opts_new["backup_be"] = None
+
+        if (opts["require_backup_be"] or opts["backup_be_name"]) and \
+            opts["no_backup_be"]:
+                opts_err_incompat("--require-backup-be", "--no-backup-be", op)
+
+        if (opts["require_backup_be"] or opts["backup_be_name"]) and \
+            (opts["require_new_be"] or opts["be_name"]):
+                opts_err_incompat("--require-backup-be", "--require-new-be", op)
+
+        # create a new key called "backup_be" in the options array
+        if opts["require_backup_be"] or opts["backup_be_name"]:
+                opts_new["backup_be"] = True
+        if opts["no_backup_be"]:
+                opts_new["backup_be"] = False
+
 def opts_table_cb_li_ignore(op, api_inst, opts, opts_new):
 
         # synthesize li_ignore_all and li_ignore_list into li_ignore
@@ -1847,10 +1904,13 @@
 #
 opts_table_beopts = [
     opts_table_cb_beopts,
-    ("",  "be-name=",        "be_name",              None),
-    ("",  "deny-new-be",     "deny_new_be",          False),
-    ("",  "no-be-activate",  "be_activate",          True),
-    ("",  "require-new-be",  "require_new_be",       False),
+    ("",  "backup-be-name=",   "backup_be_name",     None),
+    ("",  "be-name=",          "be_name",            None),
+    ("",  "deny-new-be",       "deny_new_be",        False),
+    ("",  "no-backup-be",      "no_backup_be",       False),
+    ("",  "no-be-activate",    "be_activate",        True),
+    ("",  "require-backup-be", "require_backup_be",  False),
+    ("",  "require-new-be",    "require_new_be",     False),
 ]
 
 opts_table_force = [
@@ -1925,7 +1985,7 @@
 
 opts_table_parsable = [
     opts_table_cb_parsable,
-    ("", "parsable=",        "parsable_version",    None),
+    ("", "parsable=",        "parsable_version",     None),
 ]
 
 opts_table_nqv = \
@@ -1991,6 +2051,14 @@
     ("F", "output-format",   "output_format",       None)
 ]
 
+opts_revert= \
+    opts_table_beopts + \
+    opts_table_nqv + \
+    opts_table_parsable + \
+    [
+    ("",  "tagged",          "tagged",               False),
+]
+
 opts_set_mediator = \
     opts_table_beopts + \
     opts_table_no_index + \
@@ -2075,9 +2143,9 @@
 ]
 
 def change_variant(op, api_inst, pargs,
-    accept, be_activate, be_name, li_ignore, li_parent_sync, new_be,
-    noexecute, origins, parsable_version, quiet, refresh_catalogs, reject_pats,
-    show_licenses, update_index, verbose):
+    accept, backup_be, backup_be_name, be_activate, be_name, li_ignore,
+    li_parent_sync, new_be, noexecute, origins, parsable_version, quiet,
+    refresh_catalogs, reject_pats, show_licenses, update_index, verbose):
         """Attempt to change a variant associated with an image, updating
         the image contents as necessary."""
 
@@ -2113,15 +2181,16 @@
             _noexecute=noexecute, _origins=origins,
             _parsable_version=parsable_version, _quiet=quiet,
             _show_licenses=show_licenses, _verbose=verbose,
+            backup_be=backup_be, backup_be_name=backup_be_name,
             be_activate=be_activate, be_name=be_name,
             li_parent_sync=li_parent_sync, new_be=new_be,
             refresh_catalogs=refresh_catalogs, reject_list=reject_pats,
             update_index=update_index, variants=variants)
 
 def change_facet(op, api_inst, pargs,
-    accept, be_activate, be_name, li_ignore, li_parent_sync, new_be,
-    noexecute, origins, parsable_version, quiet, refresh_catalogs, reject_pats,
-    show_licenses, update_index, verbose):
+    accept, backup_be, backup_be_name, be_activate, be_name, li_ignore,
+    li_parent_sync, new_be, noexecute, origins, parsable_version, quiet,
+    refresh_catalogs, reject_pats, show_licenses, update_index, verbose):
         """Attempt to change the facets as specified, updating
         image as necessary"""
 
@@ -2170,15 +2239,16 @@
             _noexecute=noexecute, _origins=origins,
             _parsable_version=parsable_version, _quiet=quiet,
             _show_licenses=show_licenses, _verbose=verbose,
+            backup_be=backup_be, backup_be_name=backup_be_name,
             be_activate=be_activate, be_name=be_name,
             li_parent_sync=li_parent_sync, new_be=new_be, facets=facets,
             refresh_catalogs=refresh_catalogs, reject_list=reject_pats,
             update_index=update_index)
 
 def install(op, api_inst, pargs,
-    accept, be_activate, be_name, li_ignore, li_parent_sync, new_be,
-    noexecute, origins, parsable_version, quiet, refresh_catalogs, reject_pats,
-    show_licenses, update_index, verbose):
+    accept, backup_be, backup_be_name, be_activate, be_name, li_ignore,
+    li_parent_sync, new_be, noexecute, origins, parsable_version, quiet,
+    refresh_catalogs, reject_pats, show_licenses, update_index, verbose):
 
         """Attempt to take package specified to INSTALLED state.  The operands
         are interpreted as glob patterns."""
@@ -2200,6 +2270,7 @@
         return __api_op(op, api_inst, _accept=accept, _li_ignore=li_ignore,
             _noexecute=noexecute, _origins=origins, _quiet=quiet,
             _show_licenses=show_licenses, _verbose=verbose,
+            backup_be=backup_be, backup_be_name=backup_be_name,
             be_activate=be_activate, be_name=be_name,
             li_parent_sync=li_parent_sync, new_be=new_be,
             _parsable_version=parsable_version, pkgs_inst=pargs,
@@ -2207,8 +2278,8 @@
             update_index=update_index)
 
 def uninstall(op, api_inst, pargs,
-    be_activate, be_name, new_be, li_ignore, update_index, noexecute,
-    parsable_version, quiet, verbose, stage):
+    be_activate, backup_be, backup_be_name, be_name, new_be, li_ignore,
+    update_index, noexecute, parsable_version, quiet, verbose, stage):
         """Attempt to take package specified to DELETED state."""
 
         api_inst.progresstracker = get_tracker(
@@ -2226,14 +2297,15 @@
 
         return __api_op(op, api_inst, _li_ignore=li_ignore,
             _noexecute=noexecute, _quiet=quiet, _stage=stage,
-            _verbose=verbose, be_activate=be_activate, be_name=be_name,
-            new_be=new_be, _parsable_version=parsable_version,
+            _verbose=verbose, backup_be=backup_be,
+            backup_be_name=backup_be_name, be_activate=be_activate,
+            be_name=be_name, new_be=new_be, _parsable_version=parsable_version,
             pkgs_to_uninstall=pargs, update_index=update_index)
 
 def update(op, api_inst, pargs,
-    accept, be_activate, be_name, force, li_ignore, li_parent_sync,
-    new_be, noexecute, origins, parsable_version, quiet, refresh_catalogs,
-    reject_pats, show_licenses, stage, update_index, verbose):
+    accept, backup_be, backup_be_name, be_activate, be_name, force, li_ignore,
+    li_parent_sync, new_be, noexecute, origins, parsable_version, quiet,
+    refresh_catalogs, reject_pats, show_licenses, stage, update_index, verbose):
         """Attempt to take all installed packages specified to latest
         version."""
 
@@ -2268,111 +2340,28 @@
             _parsable_version=parsable_version, _quiet=quiet,
             _review_release_notes=review_release_notes,
             _show_licenses=show_licenses, _stage=stage, _verbose=verbose,
+            backup_be=backup_be, backup_be_name=backup_be_name,
             be_activate=be_activate, be_name=be_name, force=force,
             li_parent_sync=li_parent_sync, new_be=new_be,
             pkgs_update=pkgs_update, refresh_catalogs=refresh_catalogs,
             reject_list=reject_pats, update_index=update_index)
 
-def revert(api_inst, args):
+def revert(op, api_inst, pargs,
+    backup_be, backup_be_name, be_activate, be_name, new_be, noexecute,
+    parsable_version, quiet, tagged, verbose):
         """Attempt to revert files to their original state, either
         via explicit path names or via tagged contents."""
 
-        op = "revert"
-        opts, pargs = getopt.getopt(args, "nvq", ["tagged", "deny-new-be",
-            "no-be-activate", "parsable=", "require-new-be", "be-name="])
-
-        quiet = tagged = noexecute = False
-        verbose = 0
-        new_be = None
-        be_activate = True
-        be_name = None
-        parsable_version = None
-
-        for opt, arg in opts:
-                if opt == "-n":
-                        noexecute = True
-                elif opt == "-v":
-                        verbose = verbose + 1
-                elif opt == "-q":
-                        quiet = True
-                elif opt == "--deny-new-be":
-                        new_be = False
-                elif opt == "--no-be-activate":
-                        be_activate = False
-                elif opt == "--require-new-be":
-                        new_be = True
-                elif opt == "--be-name":
-                        be_name = arg
-                elif opt == "--tagged":
-                        tagged = True
-                elif opt == "--parsable":
-                        try:
-                                parsable_version = int(arg)
-                        except ValueError:
-                                usage(_("--parsable takes an integer "
-                                    "indicating the version of parsable output "
-                                    "to display."), cmd=op)
-                        quiet = True
-
         if not pargs:
                 usage(_("at least one file path or tag name required"), cmd=op)
-        if verbose > 2:
-                DebugValues.set_value("plan", "True")
-        if verbose and (parsable_version is not None):
-                usage(_("verbose and parsable are incompatible options."),
-                    cmd=op)
 
         api_inst.progresstracker = get_tracker(
             parsable_version=parsable_version, quiet=quiet, verbose=verbose)
 
-        stuff_to_do = None
-        try:
-                for pd in api_inst.gen_plan_revert(pargs, tagged=tagged,
-                    noexecute=noexecute, be_name=be_name, new_be=new_be,
-                    be_activate=be_activate):
-                        continue
-                stuff_to_do = not api_inst.planned_nothingtodo()
-        except:
-                ret_code = __api_plan_exception(op, noexecute, verbose,
-                    api_inst)
-                if ret_code != EXIT_OK:
-                        return ret_code
-
-        if not stuff_to_do:
-                if verbose:
-                        __display_plan(api_inst, verbose)
-                if parsable_version is not None:
-                        try:
-                                __display_parsable_plan(api_inst,
-                                    parsable_version)
-                        except api_errors.ApiException, e:
-                                error(e, cmd=op)
-                                return EXIT_OOPS
-                else:
-                        msg(_("No files need to be reverted."))
-                return EXIT_NOP
-
-        if not quiet:
-                __display_plan(api_inst, verbose)
-        if parsable_version is not None:
-                try:
-                        __display_parsable_plan(api_inst, parsable_version)
-                except api_errors.ApiException, e:
-                        error(e, cmd=op)
-                        return EXIT_OOPS
-
-        if noexecute:
-                return EXIT_OK
-
-        # Exceptions which happen here are printed in the above level, with
-        # or without some extra decoration done here.
-        ret_code = __api_prepare(op, api_inst, accept=False)
-        if ret_code != EXIT_OK:
-                return ret_code
-
-        ret_code = __api_execute_plan(op, api_inst)
-
-        return ret_code
+        return __api_op(op, api_inst, _noexecute=noexecute, _quiet=quiet,
+            _verbose=verbose, be_activate=be_activate, be_name=be_name,
+            new_be=new_be, _parsable_version=parsable_version, args=pargs,
+            tagged=tagged)
 
 def list_mediators(op, api_inst, pargs, omit_headers, output_format,
     list_available):
@@ -2474,13 +2463,13 @@
                 return EXIT_OOPS
         return EXIT_OK
 
-def set_mediator(op, api_inst, pargs, be_activate, be_name, med_implementation,
+def set_mediator(op, api_inst, pargs,
+    backup_be, backup_be_name, be_activate, be_name, med_implementation,
     med_version, new_be, noexecute, parsable_version, quiet, update_index,
     verbose):
         """Set the version and/or implementation for the specified
         mediator(s)."""
 
-        op = "set-mediator"
         if not pargs:
                 usage(_("at least one mediator must be specified"),
                     cmd=op)
@@ -2520,8 +2509,9 @@
         stuff_to_do = None
         try:
                 for pd in api_inst.gen_plan_set_mediators(mediators,
-                    noexecute=noexecute, be_name=be_name, new_be=new_be,
-                    be_activate=be_activate):
+                    noexecute=noexecute, backup_be=backup_be,
+                    backup_be_name=backup_be_name, be_name=be_name,
+                    new_be=new_be, be_activate=be_activate):
                         continue
                 stuff_to_do = not api_inst.planned_nothingtodo()
         except:
@@ -2564,13 +2554,13 @@
 
         return ret_code
 
-def unset_mediator(op, api_inst, pargs, be_activate, be_name,
-    med_implementation, med_version, new_be, noexecute, parsable_version,quiet,
-    update_index, verbose):
+def unset_mediator(op, api_inst, pargs,
+    backup_be, backup_be_name, be_activate, be_name, med_implementation,
+    med_version, new_be, noexecute, parsable_version, quiet, update_index,
+    verbose):
         """Unset the version and/or implementation for the specified
         mediator(s)."""
 
-        op = "unset-mediator"
         if not pargs:
                 usage(_("at least one mediator must be specified"),
                     cmd=op)
@@ -2597,8 +2587,9 @@
         stuff_to_do = None
         try:
                 for pd in api_inst.gen_plan_set_mediators(mediators,
-                    noexecute=noexecute, be_name=be_name, new_be=new_be,
-                    be_activate=be_activate):
+                    noexecute=noexecute, backup_be=backup_be,
+                    backup_be_name=backup_be_name, be_name=be_name,
+                    new_be=new_be, be_activate=be_activate):
                         continue
                 stuff_to_do = not api_inst.planned_nothingtodo()
         except:
@@ -5033,10 +5024,10 @@
         return EXIT_OK
 
 def set_property_linked(op, api_inst, pargs,
-    accept, be_activate, be_name, li_ignore, li_md_only, li_name,
-    li_parent_sync, li_pkg_updates, new_be, noexecute, origins,
-    parsable_version, quiet, refresh_catalogs, reject_pats, show_licenses,
-    update_index, verbose):
+    accept, backup_be, backup_be_name, be_activate, be_name, li_ignore,
+    li_md_only, li_name, li_parent_sync, li_pkg_updates, new_be, noexecute,
+    origins, parsable_version, quiet, refresh_catalogs, reject_pats,
+    show_licenses, update_index, verbose):
         """pkg set-property-linked
             [-nvq] [--accept] [--licenses] [--no-index] [--no-refresh]
             [--no-parent-sync] [--no-pkg-updates]
@@ -5058,7 +5049,7 @@
         if not xrval:
                 return EXIT_OOPS
 
-        LIXXX
+        return EXIT_OK
 
 def audit_linked(op, api_inst, pargs,
     li_parent_sync, li_target_all, li_target_list, omit_headers, quiet):
@@ -5101,10 +5092,10 @@
         return rv
 
 def sync_linked(op, api_inst, pargs,
-    accept, be_activate, be_name, li_ignore, li_parent_sync, new_be,
-    noexecute, origins, parsable_version, quiet, refresh_catalogs, reject_pats,
-    show_licenses, update_index, verbose, li_md_only, li_pkg_updates,
-    li_target_all, li_target_list, stage):
+    accept, backup_be, backup_be_name, be_activate, be_name, li_ignore,
+    li_parent_sync, new_be, noexecute, origins, parsable_version, quiet,
+    refresh_catalogs, reject_pats, show_licenses, update_index, verbose,
+    li_md_only, li_pkg_updates, li_target_all, li_target_list, stage):
 
         """pkg audit-linked [-a|-l <li-name>]
             [-nvq] [--accept] [--licenses] [--no-index] [--no-refresh]
@@ -5130,7 +5121,8 @@
                     _li_ignore=li_ignore, _noexecute=noexecute,
                     _origins=origins, _parsable_version=parsable_version,
                     _quiet=quiet, _show_licenses=show_licenses, _stage=stage,
-                    _verbose=verbose, be_activate=be_activate,
+                    _verbose=verbose, backup_be=backup_be,
+                    backup_be_name=backup_be_name, be_activate=be_activate,
                     be_name=be_name, li_md_only=li_md_only,
                     li_parent_sync=li_parent_sync,
                     li_pkg_updates=li_pkg_updates, new_be=new_be,
@@ -5158,9 +5150,10 @@
 
 def attach_linked(op, api_inst, pargs,
     accept, allow_relink, attach_child, attach_parent, be_activate,
-    be_name, force, li_ignore, li_md_only, li_parent_sync, li_pkg_updates,
-    li_props, new_be, noexecute, origins, parsable_version, quiet,
-    refresh_catalogs, reject_pats, show_licenses, update_index, verbose):
+    backup_be, backup_be_name, be_name, force, li_ignore, li_md_only,
+    li_parent_sync, li_pkg_updates, li_props, new_be, noexecute, origins,
+    parsable_version, quiet, refresh_catalogs, reject_pats, show_licenses,
+    update_index, verbose):
         """pkg attach-linked
             [-fnvq] [--accept] [--licenses] [--no-index] [--no-refresh]
             [--no-pkg-updates] [--linked-md-only]
@@ -5202,6 +5195,7 @@
                     _origins=origins, _parsable_version=parsable_version,
                     _quiet=quiet, _show_licenses=show_licenses,
                     _verbose=verbose, allow_relink=allow_relink,
+                    backup_be=backup_be, backup_be_name=backup_be_name,
                     be_activate=be_activate, be_name=be_name, force=force,
                     li_md_only=li_md_only, li_path=li_path,
                     li_pkg_updates=li_pkg_updates, li_props=li_props,
@@ -5946,7 +5940,7 @@
             "rebuild-index"         : (rebuild_index, None),
             "refresh"               : (publisher_refresh, None),
             "remove-property-value" : (property_remove_value, None),
-            "revert"                : (revert, None),
+            "revert"                : (revert, opts_revert, -1),
             "search"                : (search, None),
             "set-authority"         : (publisher_set, None),
             "set-mediator"          : (set_mediator, opts_set_mediator, -1),
--- a/src/gui/modules/misc_non_gui.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/gui/modules/misc_non_gui.py	Fri Aug 19 18:33:58 2011 -0700
@@ -41,7 +41,7 @@
 
 # The current version of the Client API the PM, UM and
 # WebInstall GUIs have been tested against and are known to work with.
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 LOG_DIR = "/var/tmp"
 LOG_ERROR_EXT = "_error.log"
 LOG_INFO_EXT = "_info.log"
--- a/src/man/pkg.1	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/man/pkg.1	Fri Aug 19 18:33:58 2011 -0700
@@ -18,26 +18,27 @@
 .LP
 .nf
 /usr/bin/pkg install [-nvq] [-g \fIpath_or_uri\fR ...] [--accept]
-    [--licenses] [--no-be-activate] [--no-index]
-    [--no-refresh] [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] [--reject \fIpkg_fmri_pattern\fR ...]
+    [--licenses] [--no-be-activate] [--no-index] [--no-refresh]
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
+    [--reject \fIpkg_fmri_pattern\fR ...] \fIpkg_fmri_pattern\fR ...
+.fi
+
+.LP
+.nf
+/usr/bin/pkg uninstall [-nvq] [--no-be-activate] [--no-index]
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
     \fIpkg_fmri_pattern\fR ...
 .fi
 
 .LP
 .nf
-/usr/bin/pkg uninstall [-nvq] [--no-be-activate]
-    [--no-index] [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] \fIpkg_fmri_pattern\fR ...
-.fi
-
-.LP
-.nf
 /usr/bin/pkg update [-fnvq] [-g \fIpath_or_uri\fR ...] [--accept]
-    [--licenses] [--no-be-activate] [--no-index]
-    [--no-refresh] [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] [--reject \fIpkg_fmri_pattern\fR ...]
-    [\fIpkg_fmri_pattern\fR ...]
+    [--licenses] [--no-be-activate] [--no-index] [--no-refresh]
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
+    [--reject \fIpkg_fmri_pattern\fR ...] [\fIpkg_fmri_pattern\fR ...]
 .fi
 
 .LP
@@ -78,7 +79,8 @@
 .LP
 .nf
 /usr/bin/pkg revert [-nv] [--no-be-activate]
-    [--be-name \fIname\fR] [--deny-new-be | --require-new-be]
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
     (--tagged \fItag-name\fR ... | \fIpath-to-file\fR ...)
 .fi
 
@@ -91,13 +93,15 @@
 .nf
 usr/bin/pkg set-mediator [-nv] [-I \fIimplementation\fR]
     [-V \fIversion\fR] [--no-be-activate]
-    [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] \fImediator\fR ...
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
+    \fImediator\fR ...
 .fi
 
 .LP
 .nf
 /usr/bin/pkg unset-mediator [-nvIV] [--no-be-activate]
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
     [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
     \fImediator\fR ...
 .fi
@@ -111,8 +115,9 @@
 .nf
 /usr/bin/pkg change-variant [-nvq] [-g \fIpath_or_uri\fR ...]
     [--accept] [--licenses] [--no-be-activate]
-    [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] \fIvariant_spec\fR=\fIinstance\fR ...
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
+    \fIvariant_spec\fR=\fIinstance\fR ...
 .fi
 
 .LP
@@ -124,8 +129,9 @@
 .nf
 /usr/bin/pkg change-facet [-nvq] [-g \fIpath_or_uri\fR ...]
     [--accept] [--licenses] [--no-be-activate]
-    [--deny-new-be | --require-new-be]
-    [--be-name \fIname\fR] \fIfacet_spec\fR=[True|False|None] ...
+    [--no-backup-be | --require-backup-be] [--backup-be-name \fIname\fR]
+    [--deny-new-be | --require-new-be] [--be-name \fIname\fR]
+    \fIfacet_spec\fR=[True|False|None] ...
 .fi
 
 .LP
@@ -310,7 +316,7 @@
 .ne 2
 .mk
 .na
-\fBinstall\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--no-refresh\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] [\fB--reject\fR \fIpkg_fmri_pattern\fR ...] \fIpkg_fmri_pattern\fR ...\fR
+\fBinstall\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--no-refresh\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] [\fB--reject\fR \fIpkg_fmri_pattern\fR ...] \fIpkg_fmri_pattern\fR ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -332,17 +338,23 @@
 .sp
 With \fB--licenses\fR, display all of the licenses for the packages that are installed or updated as part of this operation.
 .sp
+With \fB--no-backup-be\fR, do not create a backup boot environment.
+.sp
 With \fB--no-be-activate\fR, if a boot environment is created, do not set it as the active BE on the next boot. See \fBbeadm\fR(1M) for more information.
 .sp
 With \fB--no-index\fR, do not update the search indices after the operation has completed successfully.
 .sp
 With \fB--no-refresh\fR, do not attempt to contact the repositories for the image's publishers to retrieve the newest list of available packages and other metadata.
 .sp
-With \fB--be-name\fR, rename the newly created boot environment to be the argument given. This option is only valid if a new boot environment is created during the operation. See also \fBbeadm\fR(1M).
+With \fB--backup-be-name\fR, name the created backup boot environment using the given argument. Use of \fB--backup-be-name\fR implies \fB--require-backup-be\fR. See also \fBbeadm\fR(1M).
+.sp
+With \fB--be-name\fR, rename the newly created boot environment to be the argument given. Use of \fB--be-name\fR implies \fB--require-new-be\fR. See also \fBbeadm\fR(1M).
 .sp
-With \fB--require-new-be\fR, always create a new boot environment. Without this option, a boot environment is created automatically if needed.
+With \fB--require-backup-be\fR, always create a backup boot environment if a new boot environment will not be created. Without this option, a backup boot environment is created based on image policy. See \fBbe-policy\fR in "Image Properties" below for an explanation of when backup boot environments are created automatically.
 .sp
-With \fB--deny-new-be\fR, do not create a new boot environment. This operation is not performed if a new boot environment is required.
+With \fB--require-new-be\fR, always create a new boot environment. Without this option, a boot environment is created based on image policy. See \fBbe-policy\fR in "Image Properties" below for an explanation of when boot environments are created automatically. This option cannot be combined with \fB--require-backup-be\R.
+.sp
+With \fB--deny-new-be\fR, do not create a new boot environment. The operation will not be performed if a new boot environment is required.
 .sp
 With \fB--reject\fR, prevent packages with names matching the given pattern from being installed. If matching packages are already installed, they are removed as part of this operation. Rejected packages that are the target of group dependencies are placed on the avoid list.
 .RE
@@ -351,7 +363,7 @@
 .ne 2
 .mk
 .na
-\fBuninstall\fR [\fB-nvq\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIpkg_fmri_pattern\fR ...\fR
+\fBuninstall\fR [\fB-nvq\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIpkg_fmri_pattern\fR ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -366,7 +378,7 @@
 .ne 2
 .mk
 .na
-\fBupdate\fR [\fB-fnvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--no-refresh\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] [\fB--reject\fR \fIpkg_fmri_pattern\fR ...] [\fIpkg_fmri_pattern\fR ...]\fR
+\fBupdate\fR [\fB-fnvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-index\fR] [\fB--no-refresh\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] [\fB--reject\fR \fIpkg_fmri_pattern\fR ...] [\fIpkg_fmri_pattern\fR ...]\fR
 .ad
 .sp .6
 .RS 4n
@@ -671,7 +683,7 @@
 .ne 2
 .mk
 .na
-\fBrevert\fR [\fB-nv\fR] [\fB--no-be-activate\fR] [\fB--be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] (\fB--tagged\fR \fItag-name\fR ... | \fIpath-to-file\fR ...)\fR
+\fBrevert\fR [\fB-nv\fR] [\fB--no-be-activate\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] (\fB--tagged\fR \fItag-name\fR ... | \fIpath-to-file\fR ...)\fR
 .ad
 .sp .6
 .RS 4n
@@ -706,7 +718,7 @@
 .ne 2
 .mk
 .na
-\fBset-mediator\fR [\fB-nv\fR] [\fB-I\fR \fIimplementation\fR] [\fB-V\fR \fIversion\fR] [\fB--no-be-activate\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fImediator\fR ...\fR
+\fBset-mediator\fR [\fB-nv\fR] [\fB-I\fR \fIimplementation\fR] [\fB-V\fR \fIversion\fR] [\fB--no-be-activate\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fImediator\fR ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -725,7 +737,7 @@
 .ne 2
 .mk
 .na
-\fBunset-mediator\fR [\fB-nvIV\fR] [\fB--no-be-activate\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fImediator\fR ...\fR
+\fBunset-mediator\fR [\fB-nvIV\fR] [\fB--no-be-activate\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fImediator\fR ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -755,7 +767,7 @@
 .ne 2
 .mk
 .na
-\fBchange-variant\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIvariant_spec\fR=\fIinstance\fR ...\fR
+\fBchange-variant\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIvariant_spec\fR=\fIinstance\fR ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -781,7 +793,7 @@
 .ne 2
 .mk
 .na
-\fBchange-facet\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIfacet_spec\fR=[True|False|None] ...\fR
+\fBchange-facet\fR [\fB-nvq\fR] [\fB-g\fR \fIpath_or_uri\fR ...] [\fB--accept\fR] [\fB--licenses\fR] [\fB--no-be-activate\fR] [\fB--no-backup-be\fR | \fB--require-backup-be\fR] [\fB--backup-be-name\fR \fIname\fR] [\fB--deny-new-be\fR | \fB--require-new-be\fR] [\fB--be-name\fR \fIname\fR] \fIfacet_spec\fR=[True|False|None] ...\fR
 .ad
 .sp .6
 .RS 4n
@@ -1269,6 +1281,65 @@
 .ne 2
 .mk
 .na
+\fBbe-policy\fR
+.ad
+.sp .6
+.RS 4n
+(string) Specify when a boot environment is created during packaging operations. The following values are allowed:
+.sp
+.ne 2
+.mk
+.na
+\fBdefault\fR
+.ad
+.RS 22n
+.rt  
+Apply the default BE creation policy, currently 'create-backup'.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fBalways-new\fR
+.ad
+.RS 22n
+.rt  
+Requires a reboot for all package operations by performing them in a new BE set as active on the next boot. A backup BE will never be created unless explicitly requested.
+.sp
+This policy is the safest, but is more strict than most sites need, as no packages can be added without a reboot.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fBcreate-backup\fR
+.ad
+.RS 22n
+.rt  
+For package operations that require a reboot, a new BE will be created and set as active on the next boot. If packages are modified or content that could affect the kernel are installed and the operation affects the live BE, a backup BE will be created but will not be set as active. A backup BE may also be explicitly requested.
+.sp
+This policy is potentially risky only if newly installed software causes system instability, which is possible, but relatively rare.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fBwhen-required\fR
+.ad
+.RS 22n
+.rt  
+For package operations that require a reboot, a new BE will be created and set as active on the next boot. A backup BE will never be created unless explicitly requested.
+.sp
+This policy carries the greatest risk since if a packaging change to the live BE makes further changes impossible, there may be no recent BE to which one can fallback.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
 \fBca-path\fR
 .ad
 .sp .6
--- a/src/modules/client/actuator.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/actuator.py	Fri Aug 19 18:33:58 2011 -0700
@@ -141,7 +141,16 @@
                 return "\n".join("  %16s: %s" % (fmri, smf)
                     for fmri, smf in self.get_list())
 
+        def reboot_advised(self):
+                """Returns True if action install execution may require a
+                reboot."""
+
+                return bool("true" in self.install.get("reboot-needed", []))
+
         def reboot_needed(self):
+                """Returns True if action execution requires a new boot
+                environment."""
+
                 return bool("true" in self.update.get("reboot-needed", [])) or \
                     bool("true" in self.removal.get("reboot-needed", []))
 
--- a/src/modules/client/api.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/api.py	Fri Aug 19 18:33:58 2011 -0700
@@ -51,6 +51,7 @@
 import pkg.client.bootenv as bootenv
 import pkg.client.history as history
 import pkg.client.image as image
+import pkg.client.imageconfig as imgcfg
 import pkg.client.imageplan as ip
 import pkg.client.imagetypes as imgtypes
 import pkg.client.indexer as indexer
@@ -73,7 +74,8 @@
 from pkg.client.pkgdefs import *
 from pkg.smf import NonzeroExitException
 
-CURRENT_API_VERSION = 66
+CURRENT_API_VERSION = 67
+COMPATIBLE_API_VERSIONS = frozenset([66, CURRENT_API_VERSION])
 CURRENT_P5I_VERSION = 1
 
 # Image type constants.
@@ -282,9 +284,7 @@
                 by 'img_path'.
                 """
 
-                compatible_versions = set([CURRENT_API_VERSION])
-
-                if version_id not in compatible_versions:
+                if version_id not in COMPATIBLE_API_VERSIONS:
                         raise apx.VersionException(CURRENT_API_VERSION,
                             version_id)
 
@@ -370,6 +370,7 @@
                 self.__prepared = False
                 self.__executed = False
                 self.__be_activate = True
+                self.__backup_be_name = None
                 self.__be_name = None
                 self.__can_be_canceled = False
                 self.__canceling = False
@@ -378,6 +379,7 @@
                 self._img.blocking_locks = self.__blocking_locks
                 self.__cancel_lock = pkg.nrlock.NRLock()
                 self.__cancel_cv = threading.Condition(self.__cancel_lock)
+                self.__backup_be = None # create if needed
                 self.__new_be = None # create if needed
                 self.__alt_sources = {}
 
@@ -574,8 +576,8 @@
                 if not rc:
                         raise apx.ImageLockedError()
 
-        def __plan_common_start(self, operation, noexecute, new_be, be_name,
-            be_activate):
+        def __plan_common_start(self, operation, noexecute, backup_be,
+            backup_be_name, new_be, be_name, be_activate):
                 """Start planning an operation:
                     Acquire locks.
                     Log the start of the operation.
@@ -595,13 +597,16 @@
 
                 assert self._activity_lock._is_owned()
                 self.log_operation_start(operation)
+                self.__backup_be = backup_be
+                self.__backup_be_name = backup_be_name
                 self.__new_be = new_be
                 self.__be_activate = be_activate
                 self.__be_name = be_name
-                if self.__be_name is not None:
-                        self.check_be_name(be_name)
-                        if not self._img.is_liveroot():
-                                raise apx.BENameGivenOnDeadBE(self.__be_name)
+                for val in (self.__be_name, self.__backup_be_name):
+                        if val is not None:
+                                self.check_be_name(val)
+                                if not self._img.is_liveroot():
+                                        raise apx.BENameGivenOnDeadBE(val)
 
         def __plan_common_finish(self):
                 """Finish planning an operation."""
@@ -619,20 +624,48 @@
 
                 self._activity_lock.release()
 
-        def __set_new_be(self):
-                """Figure out whether or not we'd create a new boot environment
-                given inputs and plan.  Toss cookies if we need a new be and
-                can't have one."""
-                # decide whether or not to create new BE.
-
-                if self._img.is_liveroot():
-                        if self.__new_be is None:
-                                self.__new_be = self._img.imageplan.reboot_needed()
-                        elif self.__new_be is False and \
-                            self._img.imageplan.reboot_needed():
-                                raise apx.ImageUpdateOnLiveImageException()
-                else:
+        def __set_be_creation(self):
+                """Figure out whether or not we'd create a new or backup boot
+                environment given inputs and plan.  Toss cookies if we need a
+                new be and can't have one."""
+
+                if not self._img.is_liveroot():
+                        self.__backup_be = False
                         self.__new_be = False
+                        return
+
+                if self.__new_be is None:
+                        # If image policy requires a new BE or the plan requires
+                        # it, then create a new BE.
+                        self.__new_be = (self._img.cfg.get_policy_str(
+                            imgcfg.BE_POLICY) == "always-new" or
+                            self._img.imageplan.reboot_needed())
+                elif self.__new_be is False and \
+                    self._img.imageplan.reboot_needed():
+                        raise apx.ImageUpdateOnLiveImageException()
+
+                if not self.__new_be and self.__backup_be is None:
+                        # Create a backup be if allowed by policy (note that the
+                        # 'default' policy is currently an alias for
+                        # 'create-backup') ...
+                        allow_backup = self._img.cfg.get_policy_str(
+                            imgcfg.BE_POLICY) in ("default",
+                                "create-backup")
+
+                        self.__backup_be = False
+                        if allow_backup:
+                                # ...when packages are being
+                                # updated...
+                                for src, dest in self._img.imageplan.plan_desc:
+                                        if src and dest:
+                                                self.__backup_be = True
+                                                break
+                        if allow_backup and not self.__backup_be:
+                                # ...or if new packages that have
+                                # reboot-needed=true are being
+                                # installed.
+                                self.__backup_be = \
+                                    self._img.imageplan.reboot_advised()
 
         def avoid_pkgs(self, fmri_strings, unavoid=False):
                 """Avoid/Unavoid one or more packages.  It is an error to
@@ -797,7 +830,8 @@
                 # information in the plan.  We have to save it here and restore
                 # it later because __reset_unlock() torches it.
                 if exc_type == apx.ConflictingActionErrors:
-                        plan_desc = PlanDescription(self._img, self.__new_be,
+                        plan_desc = PlanDescription(self._img, self.__backup_be,
+                            self.__backup_be_name, self.__new_be,
                             self.__be_activate, self.__be_name)
 
                 self.__reset_unlock()
@@ -857,10 +891,11 @@
                 raise apx.IpkgOutOfDateException()
 
         def __plan_op(self, _op, _accept=False, _ad_kwargs=None,
-            _be_activate=True, _be_name=None, _ipkg_require_latest=False,
-            _li_ignore=None, _li_md_only=False, _li_parent_sync=True,
-            _new_be=False, _noexecute=False,
-            _refresh_catalogs=True, _repos=None, _update_index=True, **kwargs):
+            _backup_be=None, _backup_be_name=None, _be_activate=True,
+            _be_name=None, _ipkg_require_latest=False, _li_ignore=None,
+            _li_md_only=False, _li_parent_sync=True, _new_be=False,
+            _noexecute=False, _refresh_catalogs=True, _repos=None,
+            _update_index=True, **kwargs):
                 """Contructs a plan to change the package or linked image
                 state of an image.
 
@@ -934,8 +969,8 @@
                 kwargs.update(args_common)
 
                 # Lock the current image.
-                self.__plan_common_start(_op, _noexecute, _new_be, _be_name,
-                    _be_activate)
+                self.__plan_common_start(_op, _noexecute, _backup_be,
+                    _backup_be_name, _new_be, _be_name, _be_activate)
 
                 try:
                         # reset any child recursion state we might have
@@ -1009,8 +1044,9 @@
                                 _update_index = False
 
                         self._disable_cancel()
-                        self.__set_new_be()
+                        self.__set_be_creation()
                         self.__plan_desc = PlanDescription(self._img,
+                            self.__backup_be, self.__backup_be_name,
                             self.__new_be, self.__be_activate, self.__be_name)
 
                         # Yield to our caller so they can display our plan
@@ -1111,10 +1147,10 @@
                 return (not self.planned_nothingtodo(), self.solaris_image())
 
         def gen_plan_update(self, pkgs_update=None, accept=False,
-            be_activate=True, be_name=None, force=False, li_ignore=None,
-            li_parent_sync=True, new_be=True, noexecute=False,
-            refresh_catalogs=True, reject_list=misc.EmptyI, repos=None,
-            update_index=True):
+            backup_be=None, backup_be_name=None, be_activate=True,
+            be_name=None, force=False, li_ignore=None, li_parent_sync=True,
+            new_be=True, noexecute=False, refresh_catalogs=True,
+            reject_list=misc.EmptyI, repos=None, update_index=True):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1147,6 +1183,7 @@
 
                 op = API_OP_UPDATE
                 return self.__plan_op(op, _accept=accept,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _ipkg_require_latest=ipkg_require_latest,
                     _li_ignore=li_ignore, _li_parent_sync=li_parent_sync,
@@ -1168,10 +1205,11 @@
                         continue
                 return not self.planned_nothingtodo()
 
-        def gen_plan_install(self, pkgs_inst, accept=False, be_activate=True,
-            be_name=None, li_ignore=None, li_parent_sync=True, new_be=False,
-            noexecute=False, refresh_catalogs=True,
-            reject_list=misc.EmptyI, repos=None, update_index=True):
+        def gen_plan_install(self, pkgs_inst, accept=False, backup_be=None,
+            backup_be_name=None, be_activate=True, be_name=None, li_ignore=None,
+            li_parent_sync=True, new_be=False, noexecute=False,
+            refresh_catalogs=True, reject_list=misc.EmptyI, repos=None,
+            update_index=True):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1187,6 +1225,17 @@
 
                 'pkgs_inst' is a list of packages to install.
 
+                'backup_be' indicates whether a backup boot environment should
+                be created before the operation is executed.  If True, a backup
+                boot environment will be created.  If False, a backup boot
+                environment will not be created. If None and a new boot
+                environment is not created, and packages are being updated or
+                are being installed and tagged with reboot-needed, a backup
+                boot environment will be created.
+
+                'backup_be_name' is a string to use as the name of any backup 
+                boot environment created during the operation.
+
                 'be_name' is a string to use as the name of any new boot
                 environment created during the operation.
 
@@ -1238,6 +1287,7 @@
 
                 op = API_OP_INSTALL
                 return self.__plan_op(op, _accept=accept,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _li_parent_sync=li_parent_sync,
                     _new_be=new_be, _noexecute=noexecute,
@@ -1245,7 +1295,8 @@
                     _update_index=update_index, pkgs_inst=pkgs_inst,
                     reject_list=reject_list)
 
-        def gen_plan_sync(self, accept=False, be_activate=True, be_name=None,
+        def gen_plan_sync(self, accept=False, backup_be=None,
+            backup_be_name=None, be_activate=True, be_name=None,
             li_ignore=None, li_md_only=False, li_parent_sync=True,
             li_pkg_updates=True, new_be=False, noexecute=False,
             refresh_catalogs=True,
@@ -1283,6 +1334,7 @@
 
                 op = API_OP_SYNC
                 return self.__plan_op(op, _accept=accept,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _li_md_only=li_md_only,
                     _li_parent_sync=li_parent_sync, _new_be=new_be,
@@ -1291,9 +1343,9 @@
                     li_pkg_updates=li_pkg_updates, reject_list=reject_list)
 
         def gen_plan_attach(self, lin, li_path, accept=False,
-            allow_relink=False, be_activate=True, be_name=None,
-            force=False, li_ignore=None, li_md_only=False,
-            li_pkg_updates=True, li_props=None, new_be=False,
+            allow_relink=False, backup_be=None, backup_be_name=None,
+            be_activate=True, be_name=None, force=False, li_ignore=None,
+            li_md_only=False, li_pkg_updates=True, li_props=None, new_be=False,
             noexecute=False, refresh_catalogs=True,
             reject_list=misc.EmptyI, repos=None, update_index=True):
                 """This is a generator function that yields a PlanDescription
@@ -1337,6 +1389,7 @@
                     "props": li_props,
                 }
                 return self.__plan_op(op, _accept=accept,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _li_md_only=li_md_only,
                     _new_be=new_be, _noexecute=noexecute,
@@ -1344,9 +1397,9 @@
                     _update_index=update_index, _ad_kwargs=ad_kwargs,
                     li_pkg_updates=li_pkg_updates, reject_list=reject_list)
 
-        def gen_plan_detach(self, accept=False, be_activate=True,
-            be_name=None, force=False, li_ignore=None, new_be=False,
-            noexecute=False):
+        def gen_plan_detach(self, accept=False, backup_be=None,
+            backup_be_name=None, be_activate=True, be_name=None, force=False,
+            li_ignore=None, new_be=False, noexecute=False):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1369,6 +1422,7 @@
                     "force": force
                 }
                 return self.__plan_op(op, _accept=accept, _ad_kwargs=ad_kwargs,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _new_be=new_be,
                     _noexecute=noexecute, _refresh_catalogs=False,
@@ -1384,8 +1438,9 @@
                 return not self.planned_nothingtodo()
 
         def gen_plan_uninstall(self, pkgs_to_uninstall, accept=False,
-            be_activate=True, be_name=None, li_ignore=None, new_be=False,
-            noexecute=False, update_index=True):
+            backup_be=None, backup_be_name=None, be_activate=True,
+            be_name=None, li_ignore=None, new_be=False, noexecute=False,
+            update_index=True):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1409,15 +1464,17 @@
 
                 op = API_OP_UNINSTALL
                 return self.__plan_op(op, _accept=accept,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _li_parent_sync=False,
                     _new_be=new_be, _noexecute=noexecute,
                     _refresh_catalogs=False, _update_index=update_index,
                     pkgs_to_uninstall=pkgs_to_uninstall)
 
-        def gen_plan_set_mediators(self, mediators, be_activate=True,
-            be_name=None, li_ignore=None, li_parent_sync=True, new_be=None,
-            noexecute=False, update_index=True):
+        def gen_plan_set_mediators(self, mediators, backup_be=None,
+            backup_be_name=None, be_activate=True, be_name=None, li_ignore=None,
+            li_parent_sync=True, new_be=None, noexecute=False,
+            update_index=True):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1457,6 +1514,7 @@
 
                 assert mediators
                 return self.__plan_op(API_OP_SET_MEDIATOR, _accept=True,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_activate=be_activate, _be_name=be_name,
                     _li_ignore=li_ignore, _li_parent_sync=li_parent_sync,
                     mediators=mediators, _new_be=new_be, _noexecute=noexecute,
@@ -1474,9 +1532,9 @@
                 return not self.planned_nothingtodo()
 
         def gen_plan_change_varcets(self, facets=None, variants=None,
-            accept=False, be_activate=True, be_name=None, li_ignore=None,
-            li_parent_sync=True, new_be=None, noexecute=False,
-            refresh_catalogs=True,
+            accept=False, backup_be=None, backup_be_name=None,
+            be_activate=True, be_name=None, li_ignore=None, li_parent_sync=True,
+            new_be=None, noexecute=False, refresh_catalogs=True,
             reject_list=misc.EmptyI, repos=None, update_index=True):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
@@ -1508,12 +1566,12 @@
                 else:
                         op = API_OP_CHANGE_FACET
 
-                return self.__plan_op(op, _accept=accept,
-                    _be_activate=be_activate, _be_name=be_name,
-                    _li_ignore=li_ignore, _li_parent_sync=li_parent_sync,
-                    _new_be=new_be, _noexecute=noexecute,
-                    _refresh_catalogs=refresh_catalogs, _repos=repos,
-                    _update_index=update_index, variants=variants,
+                return self.__plan_op(op, _accept=accept, _backup_be=backup_be,
+                    _backup_be_name=backup_be_name, _be_activate=be_activate,
+                    _be_name=be_name, _li_ignore=li_ignore,
+                    _li_parent_sync=li_parent_sync, _new_be=new_be,
+                    _noexecute=noexecute, _refresh_catalogs=refresh_catalogs,
+                    _repos=repos, _update_index=update_index, variants=variants,
                     facets=facets, reject_list=reject_list)
 
         def plan_revert(self, args, tagged=False, noexecute=True, be_name=None,
@@ -1525,8 +1583,9 @@
                         continue
                 return not self.planned_nothingtodo()
 
-        def gen_plan_revert(self, args, tagged=False, noexecute=True,
-            be_activate=True, be_name=None, new_be=None):
+        def gen_plan_revert(self, args, backup_be=None, backup_be_name=None,
+            be_activate=True, be_name=None, new_be=None, noexecute=True,
+            tagged=False):
                 """This is a generator function that yields a PlanDescription
                 object.  If parsable_version is set, it also yields dictionaries
                 containing plan information for child images.
@@ -1544,6 +1603,7 @@
 
                 op = API_OP_REVERT
                 return self.__plan_op(op, _be_activate=be_activate,
+                    _backup_be=backup_be, _backup_be_name=backup_be_name,
                     _be_name=be_name, _li_ignore=[], _new_be=new_be,
                     _noexecute=noexecute, _refresh_catalogs=False,
                     _update_index=False, args=args, tagged=tagged)
@@ -1885,6 +1945,25 @@
                                 self.log_operation_end(error=e)
                                 raise e
 
+                        # Before proceeding, create a backup boot environment if
+                        # requested.
+                        if self.__backup_be == True:
+                                try:
+                                        be.create_backup_be(
+                                            be_name=self.__backup_be_name)
+                                except Exception, e:
+                                        self.log_operation_end(error=e)
+                                        raise
+                                except:
+                                        # Handle exceptions that are not
+                                        # subclasses of Exception.
+                                        exc_type, exc_value, exc_traceback = \
+                                            sys.exc_info()
+                                        self.log_operation_end(error=exc_type)
+                                        raise
+
+                        # After (possibly) creating backup be, determine if
+                        # operation should execute on a clone of current BE.
                         if self.__new_be == True:
                                 try:
                                         be.init_image_recovery(self._img,
@@ -4512,9 +4591,12 @@
 class PlanDescription(object):
         """A class which describes the changes the plan will make."""
 
-        def __init__(self, img, new_be, be_activate, be_name):
+        def __init__(self, img, backup_be, backup_be_name, new_be, be_activate,
+            be_name):
                 self.__plan = img.imageplan
                 self._img = img
+                self.__backup_be = backup_be
+                self.__backup_be_name = backup_be_name
                 self.__new_be = new_be
                 self.__be_activate = be_activate
                 self.__be_name = be_name
@@ -4653,12 +4735,43 @@
                 return self.__be_activate
 
         @property
+        def backup_be(self):
+                """A boolean value indicating that execution of the plan will
+                result in a backup clone of the current live environment."""
+                return self.__backup_be
+
+        @property
+        def backup_be_name(self):
+                """A value containing either the name of the backup boot
+                environment to create or None."""
+                return self.__backup_be_name
+ 
+        @property
         def be_name(self):
                 """A value containing either the name of the boot environment to
-                create, or None."""
+                create or None."""
                 return self.__be_name
 
         @property
+        def is_active_root_be(self):
+                """A boolean indicating whether the image to be modified is the
+                active BE for the system's root image."""
+
+                if not self._img.is_liveroot() or self._img.is_zone():
+                        return False
+
+                try:
+                        be_name, be_uuid = bootenv.BootEnv.get_be_name(
+                            self._img.root)
+                        return be_name == \
+                            bootenv.BootEnv.get_activated_be_name()
+                except apx.BEException:
+                        # If boot environment logic isn't supported, return
+                        # False.  This is necessary for user images and for
+                        # the test suite.
+                        return False
+
+        @property
         def reboot_needed(self):
                 """A boolean value indicating that execution of the plan will
                 require a restart of the system to take effect if the target
--- a/src/modules/client/bootenv.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/bootenv.py	Fri Aug 19 18:33:58 2011 -0700
@@ -144,6 +144,55 @@
                         # if were are on UFS.
                         raise RuntimeError, "recoveryDisabled"
 
+        def __get_new_be_name(self, suffix=None):
+                """Create a new boot environment name."""
+
+                new_bename = self.be_name
+                if suffix:
+                        new_bename += suffix
+                base, sep, rev = new_bename.rpartition("-")
+                if sep and rev.isdigit():
+                        # The source BE has already been auto-named, so we need
+                        # to bump the revision.  List all BEs, cycle through the
+                        # names and find the one with the same basename as
+                        # new_bename, and has the highest revision.  Then add
+                        # one to it.  This means that gaps in the numbering will
+                        # not be filled.
+                        rev = int(rev)
+                        maxrev = rev
+
+                        for d in self.beList:
+                                oben = d.get("orig_be_name", None)
+                                if not oben:
+                                        continue
+                                nbase, sep, nrev = oben.rpartition("-")
+                                if (not sep or nbase != base or
+                                    not nrev.isdigit()):
+                                        continue
+                                maxrev = max(int(nrev), rev)
+                else:
+                        # If we didn't find the separator, or if the rightmost
+                        # part wasn't an integer, then we just start with the
+                        # original name.
+                        base = new_bename
+                        maxrev = 0
+
+                good = False
+                num = maxrev
+                while not good:
+                        new_bename = "-".join((base, str(num)))
+                        for d in self.beList:
+                                oben = d.get("orig_be_name", None)
+                                if not oben:
+                                        continue
+                                if oben == new_bename:
+                                        break
+                        else:
+                                good = True
+
+                        num += 1
+                return new_bename
+
         def __store_image_state(self):
                 """Internal function used to preserve current image information
                 and history state to be restored later with __reset_image_state
@@ -333,6 +382,32 @@
                 except AttributeError:
                         raise api_errors.BENamingNotSupported(be_name)
 
+        def create_backup_be(self, be_name=None):
+                """Create a backup BE if the BE being modified is the live one.
+
+                'be_name' is an optional string indicating the name to use
+                for the new backup BE."""
+
+                self.check_be_name(be_name)
+
+                if self.is_live_BE:
+                        # Create a clone of the live BE, but do not mount or
+                        # activate it.  Do nothing with the returned snapshot
+                        # name that is taken of the clone during beCopy.
+                        ret, be_name_clone, not_used = be.beCopy()
+                        if ret != 0:
+                                raise api_errors.UnableToCopyBE()
+
+                        if not be_name:
+                                be_name = self.__get_new_be_name(
+                                    suffix="-backup-1")
+                        ret = be.beRename(be_name_clone, be_name)
+                        if ret != 0:
+                                raise api_errors.UnableToRenameBE(
+                                    be_name_clone, be_name)
+                elif be_name is not None:
+                        raise api_errors.BENameGivenOnDeadBE(be_name)
+
         def init_image_recovery(self, img, be_name=None):
 
                 """Initialize for an update.
@@ -666,6 +741,11 @@
                 pass
 
         @staticmethod
+        def create_backup_be(be_name=None):
+                if be_name is not None:
+                        raise api_errors.BENameGivenOnDeadBE(be_name)
+
+        @staticmethod
         def init_image_recovery(img, be_name=None):
                 if be_name is not None:
                         raise api_errors.BENameGivenOnDeadBE(be_name)
--- a/src/modules/client/imageconfig.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/imageconfig.py	Fri Aug 19 18:33:58 2011 -0700
@@ -52,6 +52,7 @@
 # pkg(5) and their default values. Calls to the ImageConfig.get_policy method
 # should use the constants defined here.
 
+BE_POLICY = "be-policy"
 FLUSH_CONTENT_CACHE = "flush-content-cache-on-success"
 MIRROR_DISCOVERY = "mirror-discovery"
 SEND_UUID = "send-uuid"
@@ -59,6 +60,7 @@
 CHECK_CERTIFICATE_REVOCATION = "check-certificate-revocation"
 
 default_policies = {
+    BE_POLICY: "default",
     CHECK_CERTIFICATE_REVOCATION: False,
     FLUSH_CONTENT_CACHE: False,
     MIRROR_DISCOVERY: False,
@@ -155,6 +157,9 @@
                 cfg.PropertySection("property", properties=[
                     cfg.PropPublisher("preferred-authority"),
                     cfg.PropList("publisher-search-order"),
+                    cfg.PropDefined(BE_POLICY, allowed=["default",
+                        "always-new", "create-backup", "when-required"],
+                        default=default_policies[BE_POLICY]),
                     cfg.PropBool(FLUSH_CONTENT_CACHE,
                         default=default_policies[FLUSH_CONTENT_CACHE]),
                     cfg.PropBool(MIRROR_DISCOVERY,
--- a/src/modules/client/imageplan.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/imageplan.py	Fri Aug 19 18:33:58 2011 -0700
@@ -982,6 +982,11 @@
                     for k in set(olddict.keys() + newdict.keys())
                 ]
 
+        def reboot_advised(self):
+                """Check if evaluated imageplan suggests a reboot"""
+                assert self.state >= MERGED_OK
+                return self.__actuators.reboot_advised()
+
         def reboot_needed(self):
                 """Check if evaluated imageplan requires a reboot"""
                 assert self.state >= MERGED_OK
--- a/src/modules/client/pkgdefs.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/client/pkgdefs.py	Fri Aug 19 18:33:58 2011 -0700
@@ -52,6 +52,7 @@
 PKG_OP_LIST            = "list"
 PKG_OP_LIST_LINKED     = "list-linked"
 PKG_OP_PROP_LINKED     = "property-linked"
+PKG_OP_REVERT          = "revert"
 PKG_OP_SET_MEDIATOR    = "set-mediator"
 PKG_OP_SET_PROP_LINKED = "set-property-linked"
 PKG_OP_SYNC            = "sync-linked"
@@ -67,6 +68,7 @@
     PKG_OP_LIST,
     PKG_OP_LIST_LINKED,
     PKG_OP_PROP_LINKED,
+    PKG_OP_REVERT,
     PKG_OP_SET_MEDIATOR,
     PKG_OP_SET_PROP_LINKED,
     PKG_OP_SYNC,
--- a/src/modules/lint/engine.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/modules/lint/engine.py	Fri Aug 19 18:33:58 2011 -0700
@@ -39,7 +39,7 @@
 import sys
 
 PKG_CLIENT_NAME = "pkglint"
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 pkg.client.global_settings.client_name = PKG_CLIENT_NAME
 
 class LintEngineException(Exception):
--- a/src/pkgdep.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/pkgdep.py	Fri Aug 19 18:33:58 2011 -0700
@@ -42,7 +42,7 @@
 import pkg.publish.dependencies as dependencies
 from pkg.misc import msg, emsg, PipeError
 
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 PKG_CLIENT_NAME = "pkgdepend"
 
 DEFAULT_SUFFIX = ".res"
--- a/src/sysrepo.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/sysrepo.py	Fri Aug 19 18:33:58 2011 -0700
@@ -53,7 +53,7 @@
 orig_cwd = None
 
 PKG_CLIENT_NAME = "pkg.sysrepo"
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 pkg.client.global_settings.client_name = PKG_CLIENT_NAME
 
 # exit codes
--- a/src/tests/pkg5unittest.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/tests/pkg5unittest.py	Fri Aug 19 18:33:58 2011 -0700
@@ -126,7 +126,7 @@
 
 # Version test suite is known to work with.
 PKG_CLIENT_NAME = "pkg"
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 
 ELIDABLE_ERRORS = [ TestSkippedException, depotcontroller.DepotStateException ]
 
@@ -770,9 +770,10 @@
 
         def assertEqualParsable(self, output, activate_be=True,
             add_packages=EmptyI, affect_packages=EmptyI, affect_services=EmptyI,
-            be_name=None, boot_archive_rebuild=False, change_facets=EmptyI,
-            change_packages=EmptyI, change_mediators=EmptyI,
-            change_variants=EmptyI, child_images=EmptyI, create_new_be=False,
+            backup_be_name=None, be_name=None, boot_archive_rebuild=False,
+            change_facets=EmptyI, change_packages=EmptyI,
+            change_mediators=EmptyI, change_variants=EmptyI,
+            child_images=EmptyI, create_backup_be=False, create_new_be=False,
             image_name=None, licenses=EmptyI, remove_packages=EmptyI,
             version=0):
                 """Check that the parsable output in 'output' is what is
--- a/src/util/distro-import/importer.py	Fri Aug 19 17:59:28 2011 -0700
+++ b/src/util/distro-import/importer.py	Fri Aug 19 18:33:58 2011 -0700
@@ -56,7 +56,7 @@
 from pkg.misc import emsg
 from pkg.portable import PD_LOCAL_PATH, PD_PROTO_DIR, PD_PROTO_DIR_LIST
 
-CLIENT_API_VERSION = 66
+CLIENT_API_VERSION = 67
 PKG_CLIENT_NAME = "importer.py"
 pkg.client.global_settings.client_name = PKG_CLIENT_NAME