CedarBackup3.cli

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

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