CedarBackup2.cli

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

# Source Code for Module CedarBackup2.cli

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