CedarBackup2.cli

Package CedarBackup2 :: Module cli |
[hide private]
[frames] | no frames]

# Source Code for Module CedarBackup2.cli

       1  # -*- coding: iso-8859-1 -*-
       2  # vim: set ft=python ts=3 sw=3 expandtab:
       3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
       4  #
       5  #              C E D A R
       6  #          S O L U T I O N S       "Software done right."
       7  #           S O F T W A R E
       8  #
       9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      10  #
      11  # Copyright (c) 2004-2007,2010,2015 Kenneth J. Pronovici.
      12  # All rights reserved.
      13  #
      14  # This program is free software; you can redistribute it and/or
      15  # modify it under the terms of the GNU General Public License,
      16  # Version 2, as published by the Free Software Foundation.
      17  #
      18  # This program is distributed in the hope that it will be useful,
      19  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      21  #
      22  # Copies of the GNU General Public License are available from
      23  # the Free Software Foundation website, http://www.gnu.org/.
      24  #
      25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      26  #
      27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org>
      28  # Language : Python 2 (>= 2.7)
      29  # Project  : Cedar Backup, release 2
      30  # Purpose  : Provides command-line interface implementation.
      31  #
      32  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
      33
      34  ########################################################################
      35  # Module documentation
      36  ########################################################################
      37
      38  """
      39  Provides command-line interface implementation for the cback script.
      40
      41  Summary
      42  =======
      43
      44     The functionality in this module encapsulates the command-line interface for
      45     the cback script.  The cback script itself is very short, basically just an
      46     invokation of one function implemented here.  That, in turn, makes it
      47     simpler to validate the command line interface (for instance, it's easier to
      48     run pychecker against a module, and unit tests are easier, too).
      49
      50     The objects and functions implemented in this module are probably not useful
      51     to any code external to Cedar Backup.   Anyone else implementing their own
      52     command-line interface would have to reimplement (or at least enhance) all
      53     of this anyway.
      54
      55  Backwards Compatibility
      56  =======================
      57
      58     The command line interface has changed between Cedar Backup 1.x and Cedar
      59     Backup 2.x.  Some new switches have been added, and the actions have become
      60     simple arguments rather than switches (which is a much more standard command
      61     line format).  Old 1.x command lines are generally no longer valid.
      62
      63  @var DEFAULT_CONFIG: The default configuration file.
      64  @var DEFAULT_LOGFILE: The default log file path.
      65  @var DEFAULT_OWNERSHIP: Default ownership for the logfile.
      66  @var DEFAULT_MODE: Default file permissions mode on the logfile.
      67  @var VALID_ACTIONS: List of valid actions.
      68  @var COMBINE_ACTIONS: List of actions which can be combined with other actions.
      69  @var NONCOMBINE_ACTIONS: List of actions which cannot be combined with other actions.
      70
      71  @sort: cli, Options, DEFAULT_CONFIG, DEFAULT_LOGFILE, DEFAULT_OWNERSHIP,
      72         DEFAULT_MODE, VALID_ACTIONS, COMBINE_ACTIONS, NONCOMBINE_ACTIONS
      73
      74  @author: Kenneth J. Pronovici <pronovic@ieee.org>
      75  """
      76
      77  ########################################################################
      78  # Imported modules
      79  ########################################################################
      80
      81  # System modules
      82  import sys
      83  import os
      84  import logging
      85  import getopt
      86
      87  # Cedar Backup modules
      88  from CedarBackup2.release import AUTHOR, EMAIL, VERSION, DATE, COPYRIGHT
      89  from CedarBackup2.customize import customizeOverrides
      90  from CedarBackup2.util import DirectedGraph, PathResolverSingleton
      91  from CedarBackup2.util import sortDict, splitCommandLine, executeCommand, getFunctionReference
      92  from CedarBackup2.util import getUidGid, encodePath, Diagnostics
      93  from CedarBackup2.config import Config
      94  from CedarBackup2.peer import RemotePeer
      95  from CedarBackup2.actions.collect import executeCollect
      96  from CedarBackup2.actions.stage import executeStage
      97  from CedarBackup2.actions.store import executeStore
      98  from CedarBackup2.actions.purge import executePurge
      99  from CedarBackup2.actions.rebuild import executeRebuild
     100  from CedarBackup2.actions.validate import executeValidate
     101  from CedarBackup2.actions.initialize import executeInitialize
     102
     103
     104  ########################################################################
     105  # Module-wide constants and variables
     106  ########################################################################
     107
     108  logger = logging.getLogger("CedarBackup2.log.cli")
     109
     110  DISK_LOG_FORMAT    = "%(asctime)s --> [%(levelname)-7s] %(message)s"
     111  DISK_OUTPUT_FORMAT = "%(message)s"
     112  SCREEN_LOG_FORMAT  = "%(message)s"
     113  SCREEN_LOG_STREAM  = sys.stdout
     114  DATE_FORMAT        = "%Y-%m-%dT%H:%M:%S %Z"
     115
     116  DEFAULT_CONFIG     = "/etc/cback.conf"
     117  DEFAULT_LOGFILE    = "/var/log/cback.log"
     118  DEFAULT_OWNERSHIP  = [ "root", "adm", ]
     119  DEFAULT_MODE       = 0640
     120
     121  REBUILD_INDEX      = 0        # can't run with anything else, anyway
     122  VALIDATE_INDEX     = 0        # can't run with anything else, anyway
     123  INITIALIZE_INDEX   = 0        # can't run with anything else, anyway
     124  COLLECT_INDEX      = 100
     125  STAGE_INDEX        = 200
     126  STORE_INDEX        = 300
     127  PURGE_INDEX        = 400
     128
     129  VALID_ACTIONS      = [ "collect", "stage", "store", "purge", "rebuild", "validate", "initialize", "all", ]
     130  COMBINE_ACTIONS    = [ "collect", "stage", "store", "purge", ]
     131  NONCOMBINE_ACTIONS = [ "rebuild", "validate", "initialize", "all", ]
     132
     133  SHORT_SWITCHES     = "hVbqc:fMNl:o:m:OdsDu"
     134  LONG_SWITCHES      = [ 'help', 'version', 'verbose', 'quiet',
     135                         'config=', 'full', 'managed', 'managed-only',
     136                         'logfile=', 'owner=', 'mode=',
     137                         'output', 'debug', 'stack', 'diagnostics',
     138                         'unsupported', ]
     139
     140
     141  #######################################################################
     142  # Public functions
     143  #######################################################################
     144
     145  #################
     146  # cli() function
     147  #################
     148
     149 -def cli():
     150     """
     151     Implements the command-line interface for the C{cback} script.
     152
     153     Essentially, this is the "main routine" for the cback script.  It does all
     154     of the argument processing for the script, and then sets about executing the
     155     indicated actions.
     156
     157     As a general rule, only the actions indicated on the command line will be
     158     executed.   We will accept any of the built-in actions and any of the
     159     configured extended actions (which makes action list verification a two-
     160     step process).
     161
     162     The C{'all'} action has a special meaning: it means that the built-in set of
     163     actions (collect, stage, store, purge) will all be executed, in that order.
     164     Extended actions will be ignored as part of the C{'all'} action.
     165
     166     Raised exceptions always result in an immediate return.  Otherwise, we
     167     generally return when all specified actions have been completed.  Actions
     168     are ignored if the help, version or validate flags are set.
     169
     170     A different error code is returned for each type of failure:
     171
     172        - C{1}: The Python interpreter version is < 2.7
     173        - C{2}: Error processing command-line arguments
     174        - C{3}: Error configuring logging
     175        - C{4}: Error parsing indicated configuration file
     176        - C{5}: Backup was interrupted with a CTRL-C or similar
     177        - C{6}: Error executing specified backup actions
     178
     179     @note: This function contains a good amount of logging at the INFO level,
     180     because this is the right place to document high-level flow of control (i.e.
     181     what the command-line options were, what config file was being used, etc.)
     182
     183     @note: We assume that anything that I{must} be seen on the screen is logged
     184     at the ERROR level.  Errors that occur before logging can be configured are
     185     written to C{sys.stderr}.
     186
     187     @return: Error code as described above.
     188     """
     189     try:
     190        if map(int, [sys.version_info[0], sys.version_info[1]]) < [2, 7]:
     191           sys.stderr.write("Python 2 version 2.7 or greater required.\n")
     192           return 1
     193     except:
     194        # sys.version_info isn't available before 2.0
     195        sys.stderr.write("Python 2 version 2.7 or greater required.\n")
     196        return 1
     197
     198     try:
     199        options = Options(argumentList=sys.argv[1:])
     200        logger.info("Specified command-line actions: %s", options.actions)
     201     except Exception, e:
     202        _usage()
     203        sys.stderr.write(" *** Error: %s\n" % e)
     204        return 2
     205
     206     if options.help:
     207        _usage()
     208        return 0
     209     if options.version:
     210        _version()
     211        return 0
     212     if options.diagnostics:
     213        _diagnostics()
     214        return 0
     215
     216     if not options.unsupported:
     217        _unsupported()
     218
     219     if options.stacktrace:
     220        logfile = setupLogging(options)
     221     else:
     222        try:
     223           logfile = setupLogging(options)
     224        except Exception as e:
     225           sys.stderr.write("Error setting up logging: %s\n" % e)
     226           return 3
     227
     228     logger.info("Cedar Backup run started.")
     229     logger.warn("Note: Cedar Backup v2 is unsupported as of 11 Nov 2017!  Please move to Cedar Backup v3.")
     230     logger.info("Options were [%s]", options)
     231     logger.info("Logfile is [%s]", logfile)
     232     Diagnostics().logDiagnostics(method=logger.info)
     233
     234     if options.config is None:
     235        logger.debug("Using default configuration file.")
     236        configPath = DEFAULT_CONFIG
     237     else:
     238        logger.debug("Using user-supplied configuration file.")
     239        configPath = options.config
     240
     241     executeLocal = True
     242     executeManaged = False
     243     if options.managedOnly:
     244        executeLocal = False
     245        executeManaged = True
     246     if options.managed:
     247        executeManaged = True
     248     logger.debug("Execute local actions: %s", executeLocal)
     249     logger.debug("Execute managed actions: %s", executeManaged)
     250
     251     try:
     252        logger.info("Configuration path is [%s]", configPath)
     253        config = Config(xmlPath=configPath)
     254        customizeOverrides(config)
     255        setupPathResolver(config)
     256        actionSet = _ActionSet(options.actions, config.extensions, config.options,
     257                               config.peers, executeManaged, executeLocal)
     258     except Exception, e:
     259        logger.error("Error reading or handling configuration: %s", e)
     260        logger.info("Cedar Backup run completed with status 4.")
     261        return 4
     262
     263     if options.stacktrace:
     264        actionSet.executeActions(configPath, options, config)
     265     else:
     266        try:
     267           actionSet.executeActions(configPath, options, config)
     268        except KeyboardInterrupt:
     269           logger.error("Backup interrupted.")
     270           logger.info("Cedar Backup run completed with status 5.")
     271           return 5
     272        except Exception, e:
     273           logger.error("Error executing backup: %s", e)
     274           logger.info("Cedar Backup run completed with status 6.")
     275           return 6
     276
     277     logger.info("Cedar Backup run completed with status 0.")
     278     return 0
     279
     280
     281  ########################################################################
     282  # Action-related class definition
     283  ########################################################################
     284
     285  ####################
     286  # _ActionItem class
     287  ####################
     288
     289 -class _ActionItem(object):
     290
     291     """
     292     Class representing a single action to be executed.
     293
     294     This class represents a single named action to be executed, and understands
     295     how to execute that action.
     296
     297     The built-in actions will use only the options and config values.  We also
     298     pass in the config path so that extension modules can re-parse configuration
     299     if they want to, to add in extra information.
     300
     301     This class is also where pre-action and post-action hooks are executed.  An
     302     action item is instantiated in terms of optional pre- and post-action hook
     303     objects (config.ActionHook), which are then executed at the appropriate time
     304     (if set).
     305
     306     @note: The comparison operators for this class have been implemented to only
     307     compare based on the index and SORT_ORDER value, and ignore all other
     308     values.  This is so that the action set list can be easily sorted first by
     309     type (_ActionItem before _ManagedActionItem) and then by index within type.
     310
     311     @cvar SORT_ORDER: Defines a sort order to order properly between types.
     312     """
     313
     314     SORT_ORDER = 0
     315
     316 -   def __init__(self, index, name, preHooks, postHooks, function):
     317        """
     318        Default constructor.
     319
     320        It's OK to pass C{None} for C{index}, C{preHooks} or C{postHooks}, but not
     321        for C{name}.
     322
     323        @param index: Index of the item (or C{None}).
     324        @param name: Name of the action that is being executed.
     325        @param preHooks: List of pre-action hooks in terms of an C{ActionHook} object, or C{None}.
     326        @param postHooks: List of post-action hooks in terms of an C{ActionHook} object, or C{None}.
     327        @param function: Reference to function associated with item.
     328        """
     329        self.index = index
     330        self.name = name
     331        self.preHooks = preHooks
     332        self.postHooks = postHooks
     333        self.function = function
     334
     335 -   def __cmp__(self, other):
     336        """
     337        Definition of equals operator for this class.
     338        The only thing we compare is the item's index.
     339        @param other: Other object to compare to.
     340        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     341        """
     342        if other is None:
     343           return 1
     344        if self.index != other.index:
     345           if self.index < other.index:
     346              return -1
     347           else:
     348              return 1
     349        else:
     350           if self.SORT_ORDER != other.SORT_ORDER:
     351              if self.SORT_ORDER < other.SORT_ORDER:
     352                 return -1
     353              else:
     354                 return 1
     355        return 0
     356
     357 -   def executeAction(self, configPath, options, config):
     358        """
     359        Executes the action associated with an item, including hooks.
     360
     361        See class notes for more details on how the action is executed.
     362
     363        @param configPath: Path to configuration file on disk.
     364        @param options: Command-line options to be passed to action.
     365        @param config: Parsed configuration to be passed to action.
     366
     367        @raise Exception: If there is a problem executing the action.
     368        """
     369        logger.debug("Executing [%s] action.", self.name)
     370        if self.preHooks is not None:
     371           for hook in self.preHooks:
     372              self._executeHook("pre-action", hook)
     373        self._executeAction(configPath, options, config)
     374        if self.postHooks is not None:
     375           for hook in self.postHooks:
     376              self._executeHook("post-action", hook)
     377
     378 -   def _executeAction(self, configPath, options, config):
     379        """
     380        Executes the action, specifically the function associated with the action.
     381        @param configPath: Path to configuration file on disk.
     382        @param options: Command-line options to be passed to action.
     383        @param config: Parsed configuration to be passed to action.
     384        """
     385        name = "%s.%s" % (self.function.__module__, self.function.__name__)
     386        logger.debug("Calling action function [%s], execution index [%d]", name, self.index)
     387        self.function(configPath, options, config)
     388
     389 -   def _executeHook(self, type, hook):  # pylint: disable=W0622,R0201
     390        """
     391        Executes a hook command via L{util.executeCommand()}.
     392        @param type: String describing the type of hook, for logging.
     393        @param hook: Hook, in terms of a C{ActionHook} object.
     394        """
     395        fields = splitCommandLine(hook.command)
     396        logger.debug("Executing %s hook for action [%s]: %s", type, hook.action, fields[0:1])
     397        result = executeCommand(command=fields[0:1], args=fields[1:])[0]
     398        if result != 0:
     399           raise IOError("Error (%d) executing %s hook for action [%s]: %s" % (result, type, hook.action, fields[0:1]))
     400
     401
     402  ###########################
     403  # _ManagedActionItem class
     404  ###########################
     405
     406 -class _ManagedActionItem(object):
     407
     408     """
     409     Class representing a single action to be executed on a managed peer.
     410
     411     This class represents a single named action to be executed, and understands
     412     how to execute that action.
     413
     414     Actions to be executed on a managed peer rely on peer configuration and
     415     on the full-backup flag.  All other configuration takes place on the remote
     416     peer itself.
     417
     418     @note: The comparison operators for this class have been implemented to only
     419     compare based on the index and SORT_ORDER value, and ignore all other
     420     values.  This is so that the action set list can be easily sorted first by
     421     type (_ActionItem before _ManagedActionItem) and then by index within type.
     422
     423     @cvar SORT_ORDER: Defines a sort order to order properly between types.
     424     """
     425
     426     SORT_ORDER = 1
     427
     428 -   def __init__(self, index, name, remotePeers):
     429        """
     430        Default constructor.
     431
     432        @param index: Index of the item (or C{None}).
     433        @param name: Name of the action that is being executed.
     434        @param remotePeers: List of remote peers on which to execute the action.
     435        """
     436        self.index = index
     437        self.name = name
     438        self.remotePeers = remotePeers
     439
     440 -   def __cmp__(self, other):
     441        """
     442        Definition of equals operator for this class.
     443        The only thing we compare is the item's index.
     444        @param other: Other object to compare to.
     445        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
     446        """
     447        if other is None:
     448           return 1
     449        if self.index != other.index:
     450           if self.index < other.index:
     451              return -1
     452           else:
     453              return 1
     454        else:
     455           if self.SORT_ORDER != other.SORT_ORDER:
     456              if self.SORT_ORDER < other.SORT_ORDER:
     457                 return -1
     458              else:
     459                 return 1
     460        return 0
     461
     462     # pylint: disable=W0613
     463 -   def executeAction(self, configPath, options, config):
     464        """
     465        Executes the managed action associated with an item.
     466
     467        @note: Only options.full is actually used.  The rest of the arguments
     468        exist to satisfy the ActionItem iterface.
     469
     470        @note: Errors here result in a message logged to ERROR, but no thrown
     471        exception.  The analogy is the stage action where a problem with one host
     472        should not kill the entire backup.  Since we're logging an error, the
     473        administrator will get an email.
     474
     475        @param configPath: Path to configuration file on disk.
     476        @param options: Command-line options to be passed to action.
     477        @param config: Parsed configuration to be passed to action.
     478
     479        @raise Exception: If there is a problem executing the action.
     480        """
     481        for peer in self.remotePeers:
     482           logger.debug("Executing managed action [%s] on peer [%s].", self.name, peer.name)
     483           try:
     484              peer.executeManagedAction(self.name, options.full)
     485           except IOError, e:
     486              logger.error(e)   # log the message and go on, so we don't kill the backup
     487
     488
     489  ###################
     490  # _ActionSet class
     491  ###################
     492
     493 -class _ActionSet(object):
     494
     495     """
     496     Class representing a set of local actions to be executed.
     497
     498     This class does four different things.  First, it ensures that the actions
     499     specified on the command-line are sensible.  The command-line can only list
     500     either built-in actions or extended actions specified in configuration.
     501     Also, certain actions (in L{NONCOMBINE_ACTIONS}) cannot be combined with
     502     other actions.
     503
     504     Second, the class enforces an execution order on the specified actions.  Any
     505     time actions are combined on the command line (either built-in actions or
     506     extended actions), we must make sure they get executed in a sensible order.
     507
     508     Third, the class ensures that any pre-action or post-action hooks are
     509     scheduled and executed appropriately.  Hooks are configured by building a
     510     dictionary mapping between hook action name and command.  Pre-action hooks
     511     are executed immediately before their associated action, and post-action
     512     hooks are executed immediately after their associated action.
     513
     514     Finally, the class properly interleaves local and managed actions so that
     515     the same action gets executed first locally and then on managed peers.
     516
     517     @sort: __init__, executeActions
     518     """
     519
     520 -   def __init__(self, actions, extensions, options, peers, managed, local):
     521        """
     522        Constructor for the C{_ActionSet} class.
     523
     524        This is kind of ugly, because the constructor has to set up a lot of data
     525        before being able to do anything useful.  The following data structures
     526        are initialized based on the input:
     527
     528           - C{extensionNames}: List of extensions available in configuration
     529           - C{preHookMap}: Mapping from action name to list of C{PreActionHook}
     530           - C{postHookMap}: Mapping from action name to list of C{PostActionHook}
     531           - C{functionMap}: Mapping from action name to Python function
     532           - C{indexMap}: Mapping from action name to execution index
     533           - C{peerMap}: Mapping from action name to set of C{RemotePeer}
     534           - C{actionMap}: Mapping from action name to C{_ActionItem}
     535
     536        Once these data structures are set up, the command line is validated to
     537        make sure only valid actions have been requested, and in a sensible
     538        combination.  Then, all of the data is used to build C{self.actionSet},
     539        the set action items to be executed by C{executeActions()}.  This list
     540        might contain either C{_ActionItem} or C{_ManagedActionItem}.
     541
     542        @param actions: Names of actions specified on the command-line.
     543        @param extensions: Extended action configuration (i.e. config.extensions)
     544        @param options: Options configuration (i.e. config.options)
     545        @param peers: Peers configuration (i.e. config.peers)
     546        @param managed: Whether to include managed actions in the set
     547        @param local: Whether to include local actions in the set
     548
     549        @raise ValueError: If one of the specified actions is invalid.
     550        """
     551        extensionNames = _ActionSet._deriveExtensionNames(extensions)
     552        (preHookMap, postHookMap) = _ActionSet._buildHookMaps(options.hooks)
     553        functionMap = _ActionSet._buildFunctionMap(extensions)
     554        indexMap = _ActionSet._buildIndexMap(extensions)
     555        peerMap = _ActionSet._buildPeerMap(options, peers)
     556        actionMap = _ActionSet._buildActionMap(managed, local, extensionNames, functionMap,
     557                                               indexMap, preHookMap, postHookMap, peerMap)
     558        _ActionSet._validateActions(actions, extensionNames)
     559        self.actionSet = _ActionSet._buildActionSet(actions, actionMap)
     560
     561     @staticmethod
     562 -   def _deriveExtensionNames(extensions):
     563        """
     564        Builds a list of extended actions that are available in configuration.
     565        @param extensions: Extended action configuration (i.e. config.extensions)
     566        @return: List of extended action names.
     567        """
     568        extensionNames = []
     569        if extensions is not None and extensions.actions is not None:
     570           for action in extensions.actions:
     571              extensionNames.append(action.name)
     572        return extensionNames
     573
     574     @staticmethod
     575 -   def _buildHookMaps(hooks):
     576        """
     577        Build two mappings from action name to configured C{ActionHook}.
     578        @param hooks: List of pre- and post-action hooks (i.e. config.options.hooks)
     579        @return: Tuple of (pre hook dictionary, post hook dictionary).
     580        """
     581        preHookMap = {}
     582        postHookMap = {}
     583        if hooks is not None:
     584           for hook in hooks:
     585              if hook.before:
     586                 if not hook.action in preHookMap:
     587                    preHookMap[hook.action] = []
     588                 preHookMap[hook.action].append(hook)
     589              elif hook.after:
     590                 if not hook.action in postHookMap:
     591                    postHookMap[hook.action] = []
     592                 postHookMap[hook.action].append(hook)
     593        return (preHookMap, postHookMap)
     594
     595     @staticmethod
     596 -   def _buildFunctionMap(extensions):
     597        """
     598        Builds a mapping from named action to action function.
     599        @param extensions: Extended action configuration (i.e. config.extensions)
     600        @return: Dictionary mapping action to function.
     601        """
     602        functionMap = {}
     603        functionMap['rebuild'] = executeRebuild
     604        functionMap['validate'] = executeValidate
     605        functionMap['initialize'] = executeInitialize
     606        functionMap['collect'] = executeCollect
     607        functionMap['stage'] = executeStage
     608        functionMap['store'] = executeStore
     609        functionMap['purge'] = executePurge
     610        if extensions is not None and extensions.actions is not None:
     611           for action in extensions.actions:
     612              functionMap[action.name] = getFunctionReference(action.module, action.function)
     613        return functionMap
     614
     615     @staticmethod
     616 -   def _buildIndexMap(extensions):
     617        """
     618        Builds a mapping from action name to proper execution index.
     619
     620        If extensions configuration is C{None}, or there are no configured
     621        extended actions, the ordering dictionary will only include the built-in
     622        actions and their standard indices.
     623
     624        Otherwise, if the extensions order mode is C{None} or C{"index"}, actions
     625        will scheduled by explicit index; and if the extensions order mode is
     626        C{"dependency"}, actions will be scheduled using a dependency graph.
     627
     628        @param extensions: Extended action configuration (i.e. config.extensions)
     629
     630        @return: Dictionary mapping action name to integer execution index.
     631        """
     632        indexMap = {}
     633        if extensions is None or extensions.actions is None or extensions.actions == []:
     634           logger.info("Action ordering will use 'index' order mode.")
     635           indexMap['rebuild'] = REBUILD_INDEX
     636           indexMap['validate'] = VALIDATE_INDEX
     637           indexMap['initialize'] = INITIALIZE_INDEX
     638           indexMap['collect'] = COLLECT_INDEX
     639           indexMap['stage'] = STAGE_INDEX
     640           indexMap['store'] = STORE_INDEX
     641           indexMap['purge'] = PURGE_INDEX
     642           logger.debug("Completed filling in action indices for built-in actions.")
     643           logger.info("Action order will be: %s", sortDict(indexMap))
     644        else:
     645           if extensions.orderMode is None or extensions.orderMode == "index":
     646              logger.info("Action ordering will use 'index' order mode.")
     647              indexMap['rebuild'] = REBUILD_INDEX
     648              indexMap['validate'] = VALIDATE_INDEX
     649              indexMap['initialize'] = INITIALIZE_INDEX
     650              indexMap['collect'] = COLLECT_INDEX
     651              indexMap['stage'] = STAGE_INDEX
     652              indexMap['store'] = STORE_INDEX
     653              indexMap['purge'] = PURGE_INDEX
     654              logger.debug("Completed filling in action indices for built-in actions.")
     655              for action in extensions.actions:
     656                 indexMap[action.name] = action.index
     657              logger.debug("Completed filling in action indices for extended actions.")
     658              logger.info("Action order will be: %s", sortDict(indexMap))
     659           else:
     660              logger.info("Action ordering will use 'dependency' order mode.")
     661              graph = DirectedGraph("dependencies")
     662              graph.createVertex("rebuild")
     663              graph.createVertex("validate")
     664              graph.createVertex("initialize")
     665              graph.createVertex("collect")
     666              graph.createVertex("stage")
     667              graph.createVertex("store")
     668              graph.createVertex("purge")
     669              for action in extensions.actions:
     670                 graph.createVertex(action.name)
     671              graph.createEdge("collect", "stage")   # Collect must run before stage, store or purge
     672              graph.createEdge("collect", "store")
     673              graph.createEdge("collect", "purge")
     674              graph.createEdge("stage", "store")     # Stage must run before store or purge
     675              graph.createEdge("stage", "purge")
     676              graph.createEdge("store", "purge")     # Store must run before purge
     677              for action in extensions.actions:
     678                 if action.dependencies.beforeList is not None:
     679                    for vertex in action.dependencies.beforeList:
     680                       try:
     681                          graph.createEdge(action.name, vertex)   # actions that this action must be run before
     682                       except ValueError:
     683                          logger.error("Dependency [%s] on extension [%s] is unknown.", vertex, action.name)
     684                          raise ValueError("Unable to determine proper action order due to invalid dependency.")
     685                 if action.dependencies.afterList is not None:
     686                    for vertex in action.dependencies.afterList:
     687                       try:
     688                          graph.createEdge(vertex, action.name)   # actions that this action must be run after
     689                       except ValueError:
     690                          logger.error("Dependency [%s] on extension [%s] is unknown.", vertex, action.name)
     691                          raise ValueError("Unable to determine proper action order due to invalid dependency.")
     692              try:
     693                 ordering = graph.topologicalSort()
     694                 indexMap = dict([(ordering[i], i+1) for i in range(0, len(ordering))])
     695                 logger.info("Action order will be: %s", ordering)
     696              except ValueError:
     697                 logger.error("Unable to determine proper action order due to dependency recursion.")
     698                 logger.error("Extensions configuration is invalid (check for loops).")
     699                 raise ValueError("Unable to determine proper action order due to dependency recursion.")
     700        return indexMap
     701
     702     @staticmethod
     703 -   def _buildActionMap(managed, local, extensionNames, functionMap, indexMap, preHookMap, postHookMap, peerMap):
     704        """
     705        Builds a mapping from action name to list of action items.
     706
     707        We build either C{_ActionItem} or C{_ManagedActionItem} objects here.
     708
     709        In most cases, the mapping from action name to C{_ActionItem} is 1:1.
     710        The exception is the "all" action, which is a special case.  However, a
     711        list is returned in all cases, just for consistency later.  Each
     712        C{_ActionItem} will be created with a proper function reference and index
     713        value for execution ordering.
     714
     715        The mapping from action name to C{_ManagedActionItem} is always 1:1.
     716        Each managed action item contains a list of peers which the action should
     717        be executed.
     718
     719        @param managed: Whether to include managed actions in the set
     720        @param local: Whether to include local actions in the set
     721        @param extensionNames: List of valid extended action names
     722        @param functionMap: Dictionary mapping action name to Python function
     723        @param indexMap: Dictionary mapping action name to integer execution index
     724        @param preHookMap: Dictionary mapping action name to pre hooks (if any) for the action
     725        @param postHookMap: Dictionary mapping action name to post hooks (if any) for the action
     726        @param peerMap: Dictionary mapping action name to list of remote peers on which to execute the action
     727
     728        @return: Dictionary mapping action name to list of C{_ActionItem} objects.
     729        """
     730        actionMap = {}
     731        for name in extensionNames + VALID_ACTIONS:
     732           if name != 'all': # do this one later
     733              function = functionMap[name]
     734              index = indexMap[name]
     735              actionMap[name] = []
     736              if local:
     737                 (preHooks, postHooks) = _ActionSet._deriveHooks(name, preHookMap, postHookMap)
     738                 actionMap[name].append(_ActionItem(index, name, preHooks, postHooks, function))
     739              if managed:
     740                 if name in peerMap:
     741                    actionMap[name].append(_ManagedActionItem(index, name, peerMap[name]))
     742        actionMap['all'] = actionMap['collect'] + actionMap['stage'] + actionMap['store'] + actionMap['purge']
     743        return actionMap
     744
     745     @staticmethod
     746 -   def _buildPeerMap(options, peers):
     747        """
     748        Build a mapping from action name to list of remote peers.
     749
     750        There will be one entry in the mapping for each managed action.  If there
     751        are no managed peers, the mapping will be empty.  Only managed actions
     752        will be listed in the mapping.
     753
     754        @param options: Option configuration (i.e. config.options)
     755        @param peers: Peers configuration (i.e. config.peers)
     756        """
     757        peerMap = {}
     758        if peers is not None:
     759           if peers.remotePeers is not None:
     760              for peer in peers.remotePeers:
     761                 if peer.managed:
     762                    remoteUser = _ActionSet._getRemoteUser(options, peer)
     763                    rshCommand = _ActionSet._getRshCommand(options, peer)
     764                    cbackCommand = _ActionSet._getCbackCommand(options, peer)
     765                    managedActions = _ActionSet._getManagedActions(options, peer)
     766                    remotePeer = RemotePeer(peer.name, None, options.workingDir, remoteUser, None,
     767                                            options.backupUser, rshCommand, cbackCommand)
     768                    if managedActions is not None:
     769                       for managedAction in managedActions:
     770                          if managedAction in peerMap:
     771                             if remotePeer not in peerMap[managedAction]:
     772                                peerMap[managedAction].append(remotePeer)
     773                          else:
     774                             peerMap[managedAction] = [ remotePeer, ]
     775        return peerMap
     776
     777     @staticmethod
     778 -   def _deriveHooks(action, preHookDict, postHookDict):
     779        """
     780        Derive pre- and post-action hooks, if any, associated with named action.
     781        @param action: Name of action to look up
     782        @param preHookDict: Dictionary mapping pre-action hooks to action name
     783        @param postHookDict: Dictionary mapping post-action hooks to action name
     784        @return Tuple (preHooks, postHooks) per mapping, with None values if there is no hook.
     785        """
     786        preHooks = None
     787        postHooks = None
     788        if preHookDict.has_key(action):
     789           preHooks = preHookDict[action]
     790        if postHookDict.has_key(action):
     791           postHooks = postHookDict[action]
     792        return (preHooks, postHooks)
     793
     794     @staticmethod
     795 -   def _validateActions(actions, extensionNames):
     796        """
     797        Validate that the set of specified actions is sensible.
     798
     799        Any specified action must either be a built-in action or must be among
     800        the extended actions defined in configuration.  The actions from within
     801        L{NONCOMBINE_ACTIONS} may not be combined with other actions.
     802
     803        @param actions: Names of actions specified on the command-line.
     804        @param extensionNames: Names of extensions specified in configuration.
     805
     806        @raise ValueError: If one or more configured actions are not valid.
     807        """
     808        if actions is None or actions == []:
     809           raise ValueError("No actions specified.")
     810        for action in actions:
     811           if action not in VALID_ACTIONS and action not in extensionNames:
     812              raise ValueError("Action [%s] is not a valid action or extended action." % action)
     813        for action in NONCOMBINE_ACTIONS:
     814           if action in actions and actions != [ action, ]:
     815              raise ValueError("Action [%s] may not be combined with other actions." % action)
     816
     817     @staticmethod
     818 -   def _buildActionSet(actions, actionMap):
     819        """
     820        Build set of actions to be executed.
     821
     822        The set of actions is built in the proper order, so C{executeActions} can
     823        spin through the set without thinking about it.  Since we've already validated
     824        that the set of actions is sensible, we don't take any precautions here to
     825        make sure things are combined properly.  If the action is listed, it will
     826        be "scheduled" for execution.
     827
     828        @param actions: Names of actions specified on the command-line.
     829        @param actionMap: Dictionary mapping action name to C{_ActionItem} object.
     830
     831        @return: Set of action items in proper order.
     832        """
     833        actionSet = []
     834        for action in actions:
     835           actionSet.extend(actionMap[action])
     836        actionSet.sort()  # sort the actions in order by index
     837        return actionSet
     838
     839 -   def executeActions(self, configPath, options, config):
     840        """
     841        Executes all actions and extended actions, in the proper order.
     842
     843        Each action (whether built-in or extension) is executed in an identical
     844        manner.  The built-in actions will use only the options and config
     845        values.  We also pass in the config path so that extension modules can
     846        re-parse configuration if they want to, to add in extra information.
     847
     848        @param configPath: Path to configuration file on disk.
     849        @param options: Command-line options to be passed to action functions.
     850        @param config: Parsed configuration to be passed to action functions.
     851
     852        @raise Exception: If there is a problem executing the actions.
     853        """
     854        logger.debug("Executing local actions.")
     855        for actionItem in self.actionSet:
     856           actionItem.executeAction(configPath, options, config)
     857
     858     @staticmethod
     859 -   def _getRemoteUser(options, remotePeer):
     860        """
     861        Gets the remote user associated with a remote peer.
     862        Use peer's if possible, otherwise take from options section.
     863        @param options: OptionsConfig object, as from config.options
     864        @param remotePeer: Configuration-style remote peer object.
     865        @return: Name of remote user associated with remote peer.
     866        """
     867        if remotePeer.remoteUser is None:
     868           return options.backupUser
     869        return remotePeer.remoteUser
     870
     871     @staticmethod
     872 -   def _getRshCommand(options, remotePeer):
     873        """
     874        Gets the RSH command associated with a remote peer.
     875        Use peer's if possible, otherwise take from options section.
     876        @param options: OptionsConfig object, as from config.options
     877        @param remotePeer: Configuration-style remote peer object.
     878        @return: RSH command associated with remote peer.
     879        """
     880        if remotePeer.rshCommand is None:
     881           return options.rshCommand
     882        return remotePeer.rshCommand
     883
     884     @staticmethod
     885 -   def _getCbackCommand(options, remotePeer):
     886        """
     887        Gets the cback command associated with a remote peer.
     888        Use peer's if possible, otherwise take from options section.
     889        @param options: OptionsConfig object, as from config.options
     890        @param remotePeer: Configuration-style remote peer object.
     891        @return: cback command associated with remote peer.
     892        """
     893        if remotePeer.cbackCommand is None:
     894           return options.cbackCommand
     895        return remotePeer.cbackCommand
     896
     897     @staticmethod
     898 -   def _getManagedActions(options, remotePeer):
     899        """
     900        Gets the managed actions list associated with a remote peer.
     901        Use peer's if possible, otherwise take from options section.
     902        @param options: OptionsConfig object, as from config.options
     903        @param remotePeer: Configuration-style remote peer object.
     904        @return: Set of managed actions associated with remote peer.
     905        """
     906        if remotePeer.managedActions is None:
     907           return options.managedActions
     908        return remotePeer.managedActions
     909
     910
     911  #######################################################################
     912  # Utility functions
     913  #######################################################################
     914
     915  ####################
     916  # _usage() function
     917  ####################
     918
     919 -def _usage(fd=sys.stderr):
     920     """
     921     Prints usage information for the cback script.
     922     @param fd: File descriptor used to print information.
     923     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
     924     """
     925     fd.write("\n")
     926     fd.write(" Usage: cback [switches] action(s)\n")
     927     fd.write("\n")
     928     fd.write(" The following switches are accepted:\n")
     929     fd.write("\n")
     930     fd.write("   -h, --help         Display this usage/help listing\n")
     931     fd.write("   -V, --version      Display version information\n")
     932     fd.write("   -b, --verbose      Print verbose output as well as logging to disk\n")
     933     fd.write("   -q, --quiet        Run quietly (display no output to the screen)\n")
     934     fd.write("   -c, --config       Path to config file (default: %s)\n" % DEFAULT_CONFIG)
     935     fd.write("   -f, --full         Perform a full backup, regardless of configuration\n")
     936     fd.write("   -M, --managed      Include managed clients when executing actions\n")
     937     fd.write("   -N, --managed-only Include ONLY managed clients when executing actions\n")
     938     fd.write("   -l, --logfile      Path to logfile (default: %s)\n" % DEFAULT_LOGFILE)
     939     fd.write("   -o, --owner        Logfile ownership, user:group (default: %s:%s)\n" % (DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1]))
     940     fd.write("   -m, --mode         Octal logfile permissions mode (default: %o)\n" % DEFAULT_MODE)
     941     fd.write("   -O, --output       Record some sub-command (i.e. cdrecord) output to the log\n")
     942     fd.write("   -d, --debug        Write debugging information to the log (implies --output)\n")
     943     fd.write("   -s, --stack        Dump a Python stack trace instead of swallowing exceptions\n") # exactly 80 characters in width!
     944     fd.write("   -D, --diagnostics  Print runtime diagnostics to the screen and exit\n")
     945     fd.write("   -u, --unsupported  Acknowledge that you understand Cedar Backup 2 is unsupported\n")
     946     fd.write("\n")
     947     fd.write(" The following actions may be specified:\n")
     948     fd.write("\n")
     949     fd.write("   all                Take all normal actions (collect, stage, store, purge)\n")
     950     fd.write("   collect            Take the collect action\n")
     951     fd.write("   stage              Take the stage action\n")
     952     fd.write("   store              Take the store action\n")
     953     fd.write("   purge              Take the purge action\n")
     954     fd.write("   rebuild            Rebuild \"this week's\" disc if possible\n")
     955     fd.write("   validate           Validate configuration only\n")
     956     fd.write("   initialize         Initialize media for use with Cedar Backup\n")
     957     fd.write("\n")
     958     fd.write(" You may also specify extended actions that have been defined in\n")
     959     fd.write(" configuration.\n")
     960     fd.write("\n")
     961     fd.write(" You must specify at least one action to take.  More than one of\n")
     962     fd.write(" the \"collect\", \"stage\", \"store\" or \"purge\" actions and/or\n")
     963     fd.write(" extended actions may be specified in any arbitrary order; they\n")
     964     fd.write(" will be executed in a sensible order.  The \"all\", \"rebuild\",\n")
     965     fd.write(" \"validate\", and \"initialize\" actions may not be combined with\n")
     966     fd.write(" other actions.\n")
     967     fd.write("\n")
     968
     969
     970  ######################
     971  # _version() function
     972  ######################
     973
     974 -def _version(fd=sys.stdout):
     975     """
     976     Prints version information for the cback script.
     977     @param fd: File descriptor used to print information.
     978     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
     979     """
     980     fd.write("\n")
     981     fd.write(" Cedar Backup version %s, released %s.\n" % (VERSION, DATE))
     982     fd.write("\n")
     983     fd.write(" Copyright (c) %s %s <%s>.\n" % (COPYRIGHT, AUTHOR, EMAIL))
     984     fd.write(" See CREDITS for a list of included code and other contributors.\n")
     985     fd.write(" This is free software; there is NO warranty.  See the\n")
     986     fd.write(" GNU General Public License version 2 for copying conditions.\n")
     987     fd.write("\n")
     988     fd.write(" Use the --help option for usage information.\n")
     989     fd.write("\n")
     990
     991
     992  ##########################
     993  # _diagnostics() function
     994  ##########################
     995
     996 -def _diagnostics(fd=sys.stdout):
     997     """
     998     Prints runtime diagnostics information.
     999     @param fd: File descriptor used to print information.
    1000     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
    1001     """
    1002     fd.write("\n")
    1003     fd.write("Diagnostics:\n")
    1004     fd.write("\n")
    1005     Diagnostics().printDiagnostics(fd=fd, prefix="   ")
    1006     fd.write("\n")
    1007
    1008
    1009  ##########################
    1010  # _unsupported() function
    1011  ##########################
    1012
    1013 -def _unsupported(fd=sys.stdout):
    1014     """
    1015     Prints a message explaining that Cedar Backup2 is unsupported.
    1016     @param fd: File descriptor used to print information.
    1017     @note: The C{fd} is used rather than C{print} to facilitate unit testing.
    1018     """
    1019     fd.write("\n")
    1020     fd.write("*************************** WARNING **************************************\n")
    1021     fd.write("\n")
    1022     fd.write("Warning: Cedar Backup v2 is unsupported!\n")
    1023     fd.write("\n")
    1024     fd.write("There are two releases of Cedar Backup: version 2 and version 3.\n")
    1025     fd.write("This version uses the Python 2 interpreter, and Cedar Backup v3 uses\n")
    1026     fd.write("the Python 3 interpreter. Because Python 2 is approaching its end of\n")
    1027     fd.write("life, and Cedar Backup v3 has been available since July of 2015, Cedar\n")
    1028     fd.write("Backup v2 is unsupported as of 11 Nov 2017. There will be no additional\n")
    1029     fd.write("releases, and users who report problems will be referred to the new\n")
    1030     fd.write("version. Please move to Cedar Backup v3.\n")
    1031     fd.write("\n")
    1032     fd.write("For migration instructions, see the user manual or the notes in the\n")
    1033     fd.write("BitBucket wiki: https://bitbucket.org/cedarsolutions/cedar-backup2/wiki/Home\n")
    1034     fd.write("\n")
    1035     fd.write("To hide this warning, use the -u/--unsupported command-line option.\n")
    1036     fd.write("\n")
    1037     fd.write("*************************** WARNING **************************************\n")
    1038     fd.write("\n")
    1039
    1040
    1041  ##########################
    1042  # setupLogging() function
    1043  ##########################
    1044
    1045 -def setupLogging(options):
    1046     """
    1047     Set up logging based on command-line options.
    1048
    1049     There are two kinds of logging: flow logging and output logging.  Output
    1050     logging contains information about system commands executed by Cedar Backup,
    1051     for instance the calls to C{mkisofs} or C{mount}, etc.  Flow logging
    1052     contains error and informational messages used to understand program flow.
    1053     Flow log messages and output log messages are written to two different
    1054     loggers target (C{CedarBackup2.log} and C{CedarBackup2.output}).  Flow log
    1055     messages are written at the ERROR, INFO and DEBUG log levels, while output
    1056     log messages are generally only written at the INFO log level.
    1057
    1058     By default, output logging is disabled.  When the C{options.output} or
    1059     C{options.debug} flags are set, output logging will be written to the
    1060     configured logfile.  Output logging is never written to the screen.
    1061
    1062     By default, flow logging is enabled at the ERROR level to the screen and at
    1063     the INFO level to the configured logfile.  If the C{options.quiet} flag is
    1064     set, flow logging is enabled at the INFO level to the configured logfile
    1065     only (i.e. no output will be sent to the screen).  If the C{options.verbose}
    1066     flag is set, flow logging is enabled at the INFO level to both the screen
    1067     and the configured logfile.  If the C{options.debug} flag is set, flow
    1068     logging is enabled at the DEBUG level to both the screen and the configured
    1069     logfile.
    1070
    1071     @param options: Command-line options.
    1072     @type options: L{Options} object
    1073
    1074     @return: Path to logfile on disk.
    1075     """
    1076     logfile = _setupLogfile(options)
    1077     _setupFlowLogging(logfile, options)
    1078     _setupOutputLogging(logfile, options)
    1079     return logfile
    1080
    1081 -def _setupLogfile(options):
    1082     """
    1083     Sets up and creates logfile as needed.
    1084
    1085     If the logfile already exists on disk, it will be left as-is, under the
    1086     assumption that it was created with appropriate ownership and permissions.
    1087     If the logfile does not exist on disk, it will be created as an empty file.
    1088     Ownership and permissions will remain at their defaults unless user/group
    1089     and/or mode are set in the options.  We ignore errors setting the indicated
    1090     user and group.
    1091
    1092     @note: This function is vulnerable to a race condition.  If the log file
    1093     does not exist when the function is run, it will attempt to create the file
    1094     as safely as possible (using C{O_CREAT}).  If two processes attempt to
    1095     create the file at the same time, then one of them will fail.  In practice,
    1096     this shouldn't really be a problem, but it might happen occassionally if two
    1097     instances of cback run concurrently or if cback collides with logrotate or
    1098     something.
    1099
    1100     @param options: Command-line options.
    1101
    1102     @return: Path to logfile on disk.
    1103     """
    1104     if options.logfile is None:
    1105        logfile = DEFAULT_LOGFILE
    1106     else:
    1107        logfile = options.logfile
    1108     if not os.path.exists(logfile):
    1109        mode = DEFAULT_MODE if options.mode is None else options.mode
    1110        orig = os.umask(0) # Per os.open(), "When computing mode, the current umask value is first masked out"
    1111        try:
    1112           fd = os.open(logfile, os.O_RDWR|os.O_CREAT|os.O_APPEND, mode)
    1113           with os.fdopen(fd, "a+") as f:
    1114              f.write("")
    1115        finally:
    1116           os.umask(orig)
    1117        try:
    1118           if options.owner is None or len(options.owner) < 2:
    1119              (uid, gid) = getUidGid(DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1])
    1120           else:
    1121              (uid, gid) = getUidGid(options.owner[0], options.owner[1])
    1122           os.chown(logfile, uid, gid)
    1123        except: pass
    1124     return logfile
    1125
    1126 -def _setupFlowLogging(logfile, options):
    1127     """
    1128     Sets up flow logging.
    1129     @param logfile: Path to logfile on disk.
    1130     @param options: Command-line options.
    1131     """
    1132     flowLogger = logging.getLogger("CedarBackup2.log")
    1133     flowLogger.setLevel(logging.DEBUG)    # let the logger see all messages
    1134     _setupDiskFlowLogging(flowLogger, logfile, options)
    1135     _setupScreenFlowLogging(flowLogger, options)
    1136
    1137 -def _setupOutputLogging(logfile, options):
    1138     """
    1139     Sets up command output logging.
    1140     @param logfile: Path to logfile on disk.
    1141     @param options: Command-line options.
    1142     """
    1143     outputLogger = logging.getLogger("CedarBackup2.output")
    1144     outputLogger.setLevel(logging.DEBUG)      # let the logger see all messages
    1145     _setupDiskOutputLogging(outputLogger, logfile, options)
    1146
    1147 -def _setupDiskFlowLogging(flowLogger, logfile, options):
    1148     """
    1149     Sets up on-disk flow logging.
    1150     @param flowLogger: Python flow logger object.
    1151     @param logfile: Path to logfile on disk.
    1152     @param options: Command-line options.
    1153     """
    1154     formatter = logging.Formatter(fmt=DISK_LOG_FORMAT, datefmt=DATE_FORMAT)
    1155     handler = logging.FileHandler(logfile, mode="a")
    1156     handler.setFormatter(formatter)
    1157     if options.debug:
    1158        handler.setLevel(logging.DEBUG)
    1159     else:
    1160        handler.setLevel(logging.INFO)
    1161     flowLogger.addHandler(handler)
    1162
    1163 -def _setupScreenFlowLogging(flowLogger, options):
    1164     """
    1165     Sets up on-screen flow logging.
    1166     @param flowLogger: Python flow logger object.
    1167     @param options: Command-line options.
    1168     """
    1169     formatter = logging.Formatter(fmt=SCREEN_LOG_FORMAT)
    1170     handler = logging.StreamHandler(SCREEN_LOG_STREAM)
    1171     handler.setFormatter(formatter)
    1172     if options.quiet:
    1173        handler.setLevel(logging.CRITICAL)  # effectively turn it off
    1174     elif options.verbose:
    1175        if options.debug:
    1176           handler.setLevel(logging.DEBUG)
    1177        else:
    1178           handler.setLevel(logging.INFO)
    1179     else:
    1180        handler.setLevel(logging.ERROR)
    1181     flowLogger.addHandler(handler)
    1182
    1183 -def _setupDiskOutputLogging(outputLogger, logfile, options):
    1184     """
    1185     Sets up on-disk command output logging.
    1186     @param outputLogger: Python command output logger object.
    1187     @param logfile: Path to logfile on disk.
    1188     @param options: Command-line options.
    1189     """
    1190     formatter = logging.Formatter(fmt=DISK_OUTPUT_FORMAT, datefmt=DATE_FORMAT)
    1191     handler = logging.FileHandler(logfile, mode="a")
    1192     handler.setFormatter(formatter)
    1193     if options.debug or options.output:
    1194        handler.setLevel(logging.DEBUG)
    1195     else:
    1196        handler.setLevel(logging.CRITICAL)  # effectively turn it off
    1197     outputLogger.addHandler(handler)
    1198
    1199
    1200  ###############################
    1201  # setupPathResolver() function
    1202  ###############################
    1203
    1204 -def setupPathResolver(config):
    1205     """
    1206     Set up the path resolver singleton based on configuration.
    1207
    1208     Cedar Backup's path resolver is implemented in terms of a singleton, the
    1209     L{PathResolverSingleton} class.  This function takes options configuration,
    1210     converts it into the dictionary form needed by the singleton, and then
    1211     initializes the singleton.  After that, any function that needs to resolve
    1212     the path of a command can use the singleton.
    1213
    1214     @param config: Configuration
    1215     @type config: L{Config} object
    1216     """
    1217     mapping = {}
    1218     if config.options.overrides is not None:
    1219        for override in config.options.overrides:
    1220           mapping[override.command] = override.absolutePath
    1221     singleton = PathResolverSingleton()
    1222     singleton.fill(mapping)
    1223
    1224
    1225  #########################################################################
    1226  # Options class definition
    1227  ########################################################################
    1228
    1229 -class Options(object):
    1230
    1231     ######################
    1232     # Class documentation
    1233     ######################
    1234
    1235     """
    1236     Class representing command-line options for the cback script.
    1237
    1238     The C{Options} class is a Python object representation of the command-line
    1239     options of the cback script.
    1240
    1241     The object representation is two-way: a command line string or a list of
    1242     command line arguments can be used to create an C{Options} object, and then
    1243     changes to the object can be propogated back to a list of command-line
    1244     arguments or to a command-line string.  An C{Options} object can even be
    1245     created from scratch programmatically (if you have a need for that).
    1246
    1247     There are two main levels of validation in the C{Options} class.  The first
    1248     is field-level validation.  Field-level validation comes into play when a
    1249     given field in an object is assigned to or updated.  We use Python's
    1250     C{property} functionality to enforce specific validations on field values,
    1251     and in some places we even use customized list classes to enforce
    1252     validations on list members.  You should expect to catch a C{ValueError}
    1253     exception when making assignments to fields if you are programmatically
    1254     filling an object.
    1255
    1256     The second level of validation is post-completion validation.  Certain
    1257     validations don't make sense until an object representation of options is
    1258     fully "complete".  We don't want these validations to apply all of the time,
    1259     because it would make building up a valid object from scratch a real pain.
    1260     For instance, we might have to do things in the right order to keep from
    1261     throwing exceptions, etc.
    1262
    1263     All of these post-completion validations are encapsulated in the
    1264     L{Options.validate} method.  This method can be called at any time by a
    1265     client, and will always be called immediately after creating a C{Options}
    1266     object from a command line and before exporting a C{Options} object back to
    1267     a command line.  This way, we get acceptable ease-of-use but we also don't
    1268     accept or emit invalid command lines.
    1269
    1270     @note: Lists within this class are "unordered" for equality comparisons.
    1271
    1272     @sort: __init__, __repr__, __str__, __cmp__
    1273     """
    1274
    1275     ##############
    1276     # Constructor
    1277     ##############
    1278
    1279 -   def __init__(self, argumentList=None, argumentString=None, validate=True):
    1280        """
    1281        Initializes an options object.
    1282
    1283        If you initialize the object without passing either C{argumentList} or
    1284        C{argumentString}, the object will be empty and will be invalid until it
    1285        is filled in properly.
    1286
    1287        No reference to the original arguments is saved off by this class.  Once
    1288        the data has been parsed (successfully or not) this original information
    1289        is discarded.
    1290
    1291        The argument list is assumed to be a list of arguments, not including the
    1292        name of the command, something like C{sys.argv[1:]}.  If you pass
    1293        C{sys.argv} instead, things are not going to work.
    1294
    1295        The argument string will be parsed into an argument list by the
    1296        L{util.splitCommandLine} function (see the documentation for that
    1297        function for some important notes about its limitations).  There is an
    1298        assumption that the resulting list will be equivalent to C{sys.argv[1:]},
    1299        just like C{argumentList}.
    1300
    1301        Unless the C{validate} argument is C{False}, the L{Options.validate}
    1302        method will be called (with its default arguments) after successfully
    1303        parsing any passed-in command line.  This validation ensures that
    1304        appropriate actions, etc. have been specified.  Keep in mind that even if
    1305        C{validate} is C{False}, it might not be possible to parse the passed-in
    1306        command line, so an exception might still be raised.
    1307
    1308        @note: The command line format is specified by the L{_usage} function.
    1309        Call L{_usage} to see a usage statement for the cback script.
    1310
    1311        @note: It is strongly suggested that the C{validate} option always be set
    1312        to C{True} (the default) unless there is a specific need to read in
    1313        invalid command line arguments.
    1314
    1315        @param argumentList: Command line for a program.
    1316        @type argumentList: List of arguments, i.e. C{sys.argv}
    1317
    1318        @param argumentString: Command line for a program.
    1319        @type argumentString: String, i.e. "cback --verbose stage store"
    1320
    1321        @param validate: Validate the command line after parsing it.
    1322        @type validate: Boolean true/false.
    1323
    1324        @raise getopt.GetoptError: If the command-line arguments could not be parsed.
    1325        @raise ValueError: If the command-line arguments are invalid.
    1326        """
    1327        self._help = False
    1328        self._version = False
    1329        self._verbose = False
    1330        self._quiet = False
    1331        self._config = None
    1332        self._full = False
    1333        self._managed = False
    1334        self._managedOnly = False
    1335        self._logfile = None
    1336        self._owner = None
    1337        self._mode = None
    1338        self._output = False
    1339        self._debug = False
    1340        self._stacktrace = False
    1341        self._diagnostics = False
    1342        self._unsupported = False
    1343        self._actions = None
    1344        self.actions = []    # initialize to an empty list; remainder are OK
    1345        if argumentList is not None and argumentString is not None:
    1346           raise ValueError("Use either argumentList or argumentString, but not both.")
    1347        if argumentString is not None:
    1348           argumentList = splitCommandLine(argumentString)
    1349        if argumentList is not None:
    1350           self._parseArgumentList(argumentList)
    1351           if validate:
    1352              self.validate()
    1353
    1354
    1355     #########################
    1356     # String representations
    1357     #########################
    1358
    1359 -   def __repr__(self):
    1360        """
    1361        Official string representation for class instance.
    1362        """
    1363        return self.buildArgumentString(validate=False)
    1364
    1365 -   def __str__(self):
    1366        """
    1367        Informal string representation for class instance.
    1368        """
    1369        return self.__repr__()
    1370
    1371
    1372     #############################
    1373     # Standard comparison method
    1374     #############################
    1375
    1376 -   def __cmp__(self, other):
    1377        """
    1378        Definition of equals operator for this class.
    1379        Lists within this class are "unordered" for equality comparisons.
    1380        @param other: Other object to compare to.
    1381        @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
    1382        """
    1383        if other is None:
    1384           return 1
    1385        if self.help != other.help:
    1386           if self.help < other.help:
    1387              return -1
    1388           else:
    1389              return 1
    1390        if self.version != other.version:
    1391           if self.version < other.version:
    1392              return -1
    1393           else:
    1394              return 1
    1395        if self.verbose != other.verbose:
    1396           if self.verbose < other.verbose:
    1397              return -1
    1398           else:
    1399              return 1
    1400        if self.quiet != other.quiet:
    1401           if self.quiet < other.quiet:
    1402              return -1
    1403           else:
    1404              return 1
    1405        if self.config != other.config:
    1406           if self.config < other.config:
    1407              return -1
    1408           else:
    1409              return 1
    1410        if self.full != other.full:
    1411           if self.full < other.full:
    1412              return -1
    1413           else:
    1414              return 1
    1415        if self.managed != other.managed:
    1416           if self.managed < other.managed:
    1417              return -1
    1418           else:
    1419              return 1
    1420        if self.managedOnly != other.managedOnly:
    1421           if self.managedOnly < other.managedOnly:
    1422              return -1
    1423           else:
    1424              return 1
    1425        if self.logfile != other.logfile:
    1426           if self.logfile < other.logfile:
    1427              return -1
    1428           else:
    1429              return 1
    1430        if self.owner != other.owner:
    1431           if self.owner < other.owner:
    1432              return -1
    1433           else:
    1434              return 1
    1435        if self.mode != other.mode:
    1436           if self.mode < other.mode:
    1437              return -1
    1438           else:
    1439              return 1
    1440        if self.output != other.output:
    1441           if self.output < other.output:
    1442              return -1
    1443           else:
    1444              return 1
    1445        if self.debug != other.debug:
    1446           if self.debug < other.debug:
    1447              return -1
    1448           else:
    1449              return 1
    1450        if self.stacktrace != other.stacktrace:
    1451           if self.stacktrace < other.stacktrace:
    1452              return -1
    1453           else:
    1454              return 1
    1455        if self.diagnostics != other.diagnostics:
    1456           if self.diagnostics < other.diagnostics:
    1457              return -1
    1458           else:
    1459              return 1
    1460        if self.unsupported != other.unsupported:
    1461           if self.unsupported < other.unsupported:
    1462              return -1
    1463           else:
    1464              return 1
    1465        if self.actions != other.actions:
    1466           if self.actions < other.actions:
    1467              return -1
    1468           else:
    1469              return 1
    1470        return 0
    1471
    1472
    1473     #############
    1474     # Properties
    1475     #############
    1476
    1477 -   def _setHelp(self, value):
    1478        """
    1479        Property target used to set the help flag.
    1480        No validations, but we normalize the value to C{True} or C{False}.
    1481        """
    1482        if value:
    1483           self._help = True
    1484        else:
    1485           self._help = False
    1486
    1487 -   def _getHelp(self):
    1488        """
    1489        Property target used to get the help flag.
    1490        """
    1491        return self._help
    1492
    1493 -   def _setVersion(self, value):
    1494        """
    1495        Property target used to set the version flag.
    1496        No validations, but we normalize the value to C{True} or C{False}.
    1497        """
    1498        if value:
    1499           self._version = True
    1500        else:
    1501           self._version = False
    1502
    1503 -   def _getVersion(self):
    1504        """
    1505        Property target used to get the version flag.
    1506        """
    1507        return self._version
    1508
    1509 -   def _setVerbose(self, value):
    1510        """
    1511        Property target used to set the verbose flag.
    1512        No validations, but we normalize the value to C{True} or C{False}.
    1513        """
    1514        if value:
    1515           self._verbose = True
    1516        else:
    1517           self._verbose = False
    1518
    1519 -   def _getVerbose(self):
    1520        """
    1521        Property target used to get the verbose flag.
    1522        """
    1523        return self._verbose
    1524
    1525 -   def _setQuiet(self, value):
    1526        """
    1527        Property target used to set the quiet flag.
    1528        No validations, but we normalize the value to C{True} or C{False}.
    1529        """
    1530        if value:
    1531           self._quiet = True
    1532        else:
    1533           self._quiet = False
    1534
    1535 -   def _getQuiet(self):
    1536        """
    1537        Property target used to get the quiet flag.
    1538        """
    1539        return self._quiet
    1540
    1541 -   def _setConfig(self, value):
    1542        """
    1543        Property target used to set the config parameter.
    1544        """
    1545        if value is not None:
    1546           if len(value) < 1:
    1547              raise ValueError("The config parameter must be a non-empty string.")
    1548        self._config = value
    1549
    1550 -   def _getConfig(self):
    1551        """
    1552        Property target used to get the config parameter.
    1553        """
    1554        return self._config
    1555
    1556 -   def _setFull(self, value):
    1557        """
    1558        Property target used to set the full flag.
    1559        No validations, but we normalize the value to C{True} or C{False}.
    1560        """
    1561        if value:
    1562           self._full = True
    1563        else:
    1564           self._full = False
    1565
    1566 -   def _getFull(self):
    1567        """
    1568        Property target used to get the full flag.
    1569        """
    1570        return self._full
    1571
    1572 -   def _setManaged(self, value):
    1573        """
    1574        Property target used to set the managed flag.
    1575        No validations, but we normalize the value to C{True} or C{False}.
    1576        """
    1577        if value:
    1578           self._managed = True
    1579        else:
    1580           self._managed = False
    1581
    1582 -   def _getManaged(self):
    1583        """
    1584        Property target used to get the managed flag.
    1585        """
    1586        return self._managed
    1587
    1588 -   def _setManagedOnly(self, value):
    1589        """
    1590        Property target used to set the managedOnly flag.
    1591        No validations, but we normalize the value to C{True} or C{False}.
    1592        """
    1593        if value:
    1594           self._managedOnly = True
    1595        else:
    1596           self._managedOnly = False
    1597
    1598 -   def _getManagedOnly(self):
    1599        """
    1600        Property target used to get the managedOnly flag.
    1601        """
    1602        return self._managedOnly
    1603
    1604 -   def _setLogfile(self, value):
    1605        """
    1606        Property target used to set the logfile parameter.
    1607        @raise ValueError: If the value cannot be encoded properly.
    1608        """
    1609        if value is not None:
    1610           if len(value) < 1:
    1611              raise ValueError("The logfile parameter must be a non-empty string.")
    1612        self._logfile = encodePath(value)
    1613
    1614 -   def _getLogfile(self):
    1615        """
    1616        Property target used to get the logfile parameter.
    1617        """
    1618        return self._logfile
    1619
    1620 -   def _setOwner(self, value):
    1621        """
    1622        Property target used to set the owner parameter.
    1623        If not C{None}, the owner must be a C{(user,group)} tuple or list.
    1624        Strings (and inherited children of strings) are explicitly disallowed.
    1625        The value will be normalized to a tuple.
    1626        @raise ValueError: If the value is not valid.
    1627        """
    1628        if value is None:
    1629           self._owner = None
    1630        else:
    1631           if isinstance(value, str):
    1632              raise ValueError("Must specify user and group tuple for owner parameter.")
    1633           if len(value) != 2:
    1634              raise ValueError("Must specify user and group tuple for owner parameter.")
    1635           if len(value[0]) < 1 or len(value[1]) < 1:
    1636              raise ValueError("User and group tuple values must be non-empty strings.")
    1637           self._owner = (value[0], value[1])
    1638
    1639 -   def _getOwner(self):
    1640        """
    1641        Property target used to get the owner parameter.
    1642        The parameter is a tuple of C{(user, group)}.
    1643        """
    1644        return self._owner
    1645
    1646 -   def _setMode(self, value):
    1647        """
    1648        Property target used to set the mode parameter.
    1649        """
    1650        if value is None:
    1651           self._mode = None
    1652        else:
    1653           try:
    1654              if isinstance(value, str):
    1655                 value = int(value, 8)
    1656              else:
    1657                 value = int(value)
    1658           except TypeError:
    1659              raise ValueError("Mode must be an octal integer >= 0, i.e. 644.")
    1660           if value < 0:
    1661              raise ValueError("Mode must be an octal integer >= 0. i.e. 644.")
    1662           self._mode = value
    1663
    1664 -   def _getMode(self):
    1665        """
    1666        Property target used to get the mode parameter.
    1667        """
    1668        return self._mode
    1669
    1670 -   def _setOutput(self, value):
    1671        """
    1672        Property target used to set the output flag.
    1673        No validations, but we normalize the value to C{True} or C{False}.
    1674        """
    1675        if value:
    1676           self._output = True
    1677        else:
    1678           self._output = False
    1679
    1680 -   def _getOutput(self):
    1681        """
    1682        Property target used to get the output flag.
    1683        """
    1684        return self._output
    1685
    1686 -   def _setDebug(self, value):
    1687        """
    1688        Property target used to set the debug flag.
    1689        No validations, but we normalize the value to C{True} or C{False}.
    1690        """
    1691        if value:
    1692           self._debug = True
    1693        else:
    1694           self._debug = False
    1695
    1696 -   def _getDebug(self):
    1697        """
    1698        Property target used to get the debug flag.
    1699        """
    1700        return self._debug
    1701
    1702 -   def _setStacktrace(self, value):
    1703        """
    1704        Property target used to set the stacktrace flag.
    1705        No validations, but we normalize the value to C{True} or C{False}.
    1706        """
    1707        if value:
    1708           self._stacktrace = True
    1709        else:
    1710           self._stacktrace = False
    1711
    1712 -   def _getStacktrace(self):
    1713        """
    1714        Property target used to get the stacktrace flag.
    1715        """
    1716        return self._stacktrace
    1717
    1718 -   def _setDiagnostics(self, value):
    1719        """
    1720        Property target used to set the diagnostics flag.
    1721        No validations, but we normalize the value to C{True} or C{False}.
    1722        """
    1723        if value:
    1724           self._diagnostics = True
    1725        else:
    1726           self._diagnostics = False
    1727
    1728 -   def _getDiagnostics(self):
    1729        """
    1730        Property target used to get the diagnostics flag.
    1731        """
    1732        return self._diagnostics
    1733
    1734 -   def _setUnsupported(self, value):
    1735        """
    1736        Property target used to set the unsupported flag.
    1737        No validations, but we normalize the value to C{True} or C{False}.
    1738        """
    1739        if value:
    1740           self._unsupported = True
    1741        else:
    1742           self._unsupported = False
    1743
    1744 -   def _getUnsupported(self):
    1745        """
    1746        Property target used to get the unsupported flag.
    1747        """
    1748        return self._unsupported
    1749
    1750 -   def _setActions(self, value):
    1751        """
    1752        Property target used to set the actions list.
    1753        We don't restrict the contents of actions.  They're validated somewhere else.
    1754        @raise ValueError: If the value is not valid.
    1755        """
    1756        if value is None:
    1757           self._actions = None
    1758        else:
    1759           try:
    1760              saved = self._actions
    1761              self._actions = []
    1762              self._actions.extend(value)
    1763           except Exception, e:
    1764              self._actions = saved
    1765              raise e
    1766
    1767 -   def _getActions(self):
    1768        """
    1769        Property target used to get the actions list.
    1770        """
    1771        return self._actions
    1772
    1773     help = property(_getHelp, _setHelp, None, "Command-line help (C{-h,--help}) flag.")
    1774     version = property(_getVersion, _setVersion, None, "Command-line version (C{-V,--version}) flag.")
    1775     verbose = property(_getVerbose, _setVerbose, None, "Command-line verbose (C{-b,--verbose}) flag.")
    1776     quiet = property(_getQuiet, _setQuiet, None, "Command-line quiet (C{-q,--quiet}) flag.")
    1777     config = property(_getConfig, _setConfig, None, "Command-line configuration file (C{-c,--config}) parameter.")
    1778     full = property(_getFull, _setFull, None, "Command-line full-backup (C{-f,--full}) flag.")
    1779     managed = property(_getManaged, _setManaged, None, "Command-line managed (C{-M,--managed}) flag.")
    1780     managedOnly = property(_getManagedOnly, _setManagedOnly, None, "Command-line managed-only (C{-N,--managed-only}) flag.")
    1781     logfile = property(_getLogfile, _setLogfile, None, "Command-line logfile (C{-l,--logfile}) parameter.")
    1782     owner = property(_getOwner, _setOwner, None, "Command-line owner (C{-o,--owner}) parameter, as tuple C{(user,group)}.")
    1783     mode = property(_getMode, _setMode, None, "Command-line mode (C{-m,--mode}) parameter.")
    1784     output = property(_getOutput, _setOutput, None, "Command-line output (C{-O,--output}) flag.")
    1785     debug = property(_getDebug, _setDebug, None, "Command-line debug (C{-d,--debug}) flag.")
    1786     stacktrace = property(_getStacktrace, _setStacktrace, None, "Command-line stacktrace (C{-s,--stack}) flag.")
    1787     diagnostics = property(_getDiagnostics, _setDiagnostics, None, "Command-line diagnostics (C{-D,--diagnostics}) flag.")
    1788     unsupported = property(_getUnsupported, _setUnsupported, None, "Command-line unsupported (C{-u,--unsupported}) flag.")
    1789     actions = property(_getActions, _setActions, None, "Command-line actions list.")
    1790
    1791
    1792     ##################
    1793     # Utility methods
    1794     ##################
    1795
    1796 -   def validate(self):
    1797        """
    1798        Validates command-line options represented by the object.
    1799
    1800        Unless C{--help} or C{--version} are supplied, at least one action must
    1801        be specified.  Other validations (as for allowed values for particular
    1802        options) will be taken care of at assignment time by the properties
    1803        functionality.
    1804
    1805        @note: The command line format is specified by the L{_usage} function.
    1806        Call L{_usage} to see a usage statement for the cback script.
    1807
    1808        @raise ValueError: If one of the validations fails.
    1809        """
    1810        if not self.help and not self.version and not self.diagnostics:
    1811           if self.actions is None or len(self.actions) == 0:
    1812              raise ValueError("At least one action must be specified.")
    1813        if self.managed and self.managedOnly:
    1814           raise ValueError("The --managed and --managed-only options may not be combined.")
    1815
    1816 -   def buildArgumentList(self, validate=True):
    1817        """
    1818        Extracts options into a list of command line arguments.
    1819
    1820        The original order of the various arguments (if, indeed, the object was
    1821        initialized with a command-line) is not preserved in this generated
    1822        argument list.   Besides that, the argument list is normalized to use the
    1823        long option names (i.e. --version rather than -V).  The resulting list
    1824        will be suitable for passing back to the constructor in the
    1825        C{argumentList} parameter.  Unlike L{buildArgumentString}, string
    1826        arguments are not quoted here, because there is no need for it.
    1827
    1828        Unless the C{validate} parameter is C{False}, the L{Options.validate}
    1829        method will be called (with its default arguments) against the
    1830        options before extracting the command line.  If the options are not valid,
    1831        then an argument list will not be extracted.
    1832
    1833        @note: It is strongly suggested that the C{validate} option always be set
    1834        to C{True} (the default) unless there is a specific need to extract an
    1835        invalid command line.
    1836
    1837        @param validate: Validate the options before extracting the command line.
    1838        @type validate: Boolean true/false.
    1839
    1840        @return: List representation of command-line arguments.
    1841        @raise ValueError: If options within the object are invalid.
    1842        """
    1843        if validate:
    1844           self.validate()
    1845        argumentList = []
    1846        if self._help:
    1847           argumentList.append("--help")
    1848        if self.version:
    1849           argumentList.append("--version")
    1850        if self.verbose:
    1851           argumentList.append("--verbose")
    1852        if self.quiet:
    1853           argumentList.append("--quiet")
    1854        if self.config is not None:
    1855           argumentList.append("--config")
    1856           argumentList.append(self.config)
    1857        if self.full:
    1858           argumentList.append("--full")
    1859        if self.managed:
    1860           argumentList.append("--managed")
    1861        if self.managedOnly:
    1862           argumentList.append("--managed-only")
    1863        if self.logfile is not None:
    1864           argumentList.append("--logfile")
    1865           argumentList.append(self.logfile)
    1866        if self.owner is not None:
    1867           argumentList.append("--owner")
    1868           argumentList.append("%s:%s" % (self.owner[0], self.owner[1]))
    1869        if self.mode is not None:
    1870           argumentList.append("--mode")
    1871           argumentList.append("%o" % self.mode)
    1872        if self.output:
    1873           argumentList.append("--output")
    1874        if self.debug:
    1875           argumentList.append("--debug")
    1876        if self.stacktrace:
    1877           argumentList.append("--stack")
    1878        if self.diagnostics:
    1879           argumentList.append("--diagnostics")
    1880        if self.unsupported:
    1881           argumentList.append("--unsupported")
    1882        if self.actions is not None:
    1883           for action in self.actions:
    1884              argumentList.append(action)
    1885        return argumentList
    1886
    1887 -   def buildArgumentString(self, validate=True):
    1888        """
    1889        Extracts options into a string of command-line arguments.
    1890
    1891        The original order of the various arguments (if, indeed, the object was
    1892        initialized with a command-line) is not preserved in this generated
    1893        argument string.   Besides that, the argument string is normalized to use
    1894        the long option names (i.e. --version rather than -V) and to quote all
    1895        string arguments with double quotes (C{"}).  The resulting string will be
    1896        suitable for passing back to the constructor in the C{argumentString}
    1897        parameter.
    1898
    1899        Unless the C{validate} parameter is C{False}, the L{Options.validate}
    1900        method will be called (with its default arguments) against the options
    1901        before extracting the command line.  If the options are not valid, then
    1902        an argument string will not be extracted.
    1903
    1904        @note: It is strongly suggested that the C{validate} option always be set
    1905        to C{True} (the default) unless there is a specific need to extract an
    1906        invalid command line.
    1907
    1908        @param validate: Validate the options before extracting the command line.
    1909        @type validate: Boolean true/false.
    1910
    1911        @return: String representation of command-line arguments.
    1912        @raise ValueError: If options within the object are invalid.
    1913        """
    1914        if validate:
    1915           self.validate()
    1916        argumentString = ""
    1917        if self._help:
    1918           argumentString += "--help "
    1919        if self.version:
    1920           argumentString += "--version "
    1921        if self.verbose:
    1922           argumentString += "--verbose "
    1923        if self.quiet:
    1924           argumentString += "--quiet "
    1925        if self.config is not None:
    1926           argumentString += "--config \"%s\" " % self.config
    1927        if self.full:
    1928           argumentString += "--full "
    1929        if self.managed:
    1930           argumentString += "--managed "
    1931        if self.managedOnly:
    1932           argumentString += "--managed-only "
    1933        if self.logfile is not None:
    1934           argumentString += "--logfile \"%s\" " % self.logfile
    1935        if self.owner is not None:
    1936           argumentString += "--owner \"%s:%s\" " % (self.owner[0], self.owner[1])
    1937        if self.mode is not None:
    1938           argumentString += "--mode %o " % self.mode
    1939        if self.output:
    1940           argumentString += "--output "
    1941        if self.debug:
    1942           argumentString += "--debug "
    1943        if self.stacktrace:
    1944           argumentString += "--stack "
    1945        if self.diagnostics:
    1946           argumentString += "--diagnostics "
    1947        if self.unsupported:
    1948           argumentString += "--unsupported "
    1949        if self.actions is not None:
    1950           for action in self.actions:
    1951              argumentString +=  "\"%s\" " % action
    1952        return argumentString
    1953
    1954 -   def _parseArgumentList(self, argumentList):
    1955        """
    1956        Internal method to parse a list of command-line arguments.
    1957
    1958        Most of the validation we do here has to do with whether the arguments
    1959        can be parsed and whether any values which exist are valid.  We don't do
    1960        any validation as to whether required elements exist or whether elements
    1961        exist in the proper combination (instead, that's the job of the
    1962        L{validate} method).
    1963
    1964        For any of the options which supply parameters, if the option is
    1965        duplicated with long and short switches (i.e. C{-l} and a C{--logfile})
    1966        then the long switch is used.  If the same option is duplicated with the
    1967        same switch (long or short), then the last entry on the command line is
    1968        used.
    1969
    1970        @param argumentList: List of arguments to a command.
    1971        @type argumentList: List of arguments to a command, i.e. C{sys.argv[1:]}
    1972
    1973        @raise ValueError: If the argument list cannot be successfully parsed.
    1974        """
    1975        switches = { }
    1976        opts, self.actions = getopt.getopt(argumentList, SHORT_SWITCHES, LONG_SWITCHES)
    1977        for o, a in opts:  # push the switches into a hash
    1978           switches[o] = a
    1979        if switches.has_key("-h") or switches.has_key("--help"):
    1980           self.help = True
    1981        if switches.has_key("-V") or switches.has_key("--version"):
    1982           self.version = True
    1983        if switches.has_key("-b") or switches.has_key("--verbose"):
    1984           self.verbose = True
    1985        if switches.has_key("-q") or switches.has_key("--quiet"):
    1986           self.quiet = True
    1987        if switches.has_key("-c"):
    1988           self.config = switches["-c"]
    1989        if switches.has_key("--config"):
    1990           self.config = switches["--config"]
    1991        if switches.has_key("-f") or switches.has_key("--full"):
    1992           self.full = True
    1993        if switches.has_key("-M") or switches.has_key("--managed"):
    1994           self.managed = True
    1995        if switches.has_key("-N") or switches.has_key("--managed-only"):
    1996           self.managedOnly = True
    1997        if switches.has_key("-l"):
    1998           self.logfile = switches["-l"]
    1999        if switches.has_key("--logfile"):
    2000           self.logfile = switches["--logfile"]
    2001        if switches.has_key("-o"):
    2002           self.owner = switches["-o"].split(":", 1)
    2003        if switches.has_key("--owner"):
    2004           self.owner = switches["--owner"].split(":", 1)
    2005        if switches.has_key("-m"):
    2006           self.mode = switches["-m"]
    2007        if switches.has_key("--mode"):
    2008           self.mode = switches["--mode"]
    2009        if switches.has_key("-O") or switches.has_key("--output"):
    2010           self.output = True
    2011        if switches.has_key("-d") or switches.has_key("--debug"):
    2012           self.debug = True
    2013        if switches.has_key("-s") or switches.has_key("--stack"):
    2014           self.stacktrace = True
    2015        if switches.has_key("-D") or switches.has_key("--diagnostics"):
    2016           self.diagnostics = True
    2017        if switches.has_key("-u") or switches.has_key("--unsupported"):
    2018           self.unsupported = True
    2019
    2020
    2021  #########################################################################
    2022  # Main routine
    2023  ########################################################################
    2024
    2025  if __name__ == "__main__":
    2026     result = cli()
    2027     sys.exit(result)
    2028