#!/usr/bin/env python # Michael Cohen # David Collett # # ****************************************************** # Version: FLAG $Version: 0.87-pre1 Date: Thu Jun 12 00:48:38 EST 2008$ # ****************************************************** # # * This program is free software; you can redistribute it and/or # * modify it under the terms of the GNU General Public License # * as published by the Free Software Foundation; either version 2 # * of the License, or (at your option) any later version. # * # * This program is distributed in the hope that it will be useful, # * but WITHOUT ANY WARRANTY; without even the implied warranty of # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # * GNU General Public License for more details. # * # * You should have received a copy of the GNU General Public License # * along with this program; if not, write to the Free Software # * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ****************************************************** """ Configuration modules for pyflag. PyFlag is a complex package and requires a flexible configuration system. The following are the requirements of the configuration system: 1) Configuration must be available from a number of sources: - Autoconf must be able to set things like the python path (in case pyflag is installed to a different prefix) - Users must be able to configure the installed system for their specific requirements. - Unconfigured parameters must be resolved at run time through the GUI and saved. 2) Configuration must be able to apply to cases specifically. 3) Because pyflag is modular, configuration variables might be required for each module. This means that definitions and declarations of configuration variables must be distributed in each plugin. These goals are achieved by the use of multiple sources of configuration information: - The system wide configuration file is this file: conf.py. It is generated from the build system from conf.py.in by substituting autoconfigured variables into it. It contains the most basic settings related to the installation, e.g. which python interpreted is used, where the python modules are installed etc. In particular it refers to the location of the system configuration file (usually found in /usr/local/etc/pyflagrc, or in /etc/pyflagrc). - The sysconfig file contains things like where the upload directory is, where to store temporary files etc. These are mainly installation wide settings which are expected to be modified by the administrator. Note that if you want the GUI to manipulate this file it needs to be writable by the user running the GUI. - Finally a conf table in each case is used to provide a per case configuration """ import ConfigParser, optparse, os, sys class PyFlagOptionParser(optparse.OptionParser): final = False def _process_long_opt(self, rargs, values): arg = rargs.pop(0) # Value explicitly attached to arg? Pretend it's the next # argument. if "=" in arg: (opt, next_arg) = arg.split("=", 1) rargs.insert(0, next_arg) had_explicit_value = True else: opt = arg had_explicit_value = False try: opt = self._match_long_opt(opt) except optparse.BadOptionError: ## we are here because we dont recognise this option. Its ## possible that it has not been defined yet so unless ## this is our final run we just ignore it. if self.final: raise return None option = self._long_opt[opt] if option.takes_value(): nargs = option.nargs if len(rargs) < nargs: if nargs == 1: self.error(_("%s option requires an argument") % opt) else: self.error(_("%s option requires %d arguments") % (opt, nargs)) elif nargs == 1: value = rargs.pop(0) else: value = tuple(rargs[0:nargs]) del rargs[0:nargs] elif had_explicit_value: self.error(_("%s option does not take a value") % opt) else: value = None option.process(opt, value, values, self) def error(self, msg): ## We cant emit errors about missing parameters until we are ## sure that all modules have registered all their parameters if self.final: return optparse.OptionParser.error(self,msg) class ConfObject(object): """ This is a singleton class to manage the configuration. This means it can be instantiated many times, but each instance refers to the global configuration (which is set in class variables). """ optparser = PyFlagOptionParser(add_help_option=False, version=False, ) initialised = False ## This is the globals dictionary which will be used for ## evaluating the configuration directives. g_dict = dict(__builtins__ = None) ## These are the options derived by reading any config files cnf_opts = {} ## Command line opts opts = {} args = None default_opts = {} docstrings = {} ## These are the actual options returned by the optparser: optparse_opts = None ## Filename where the configuration file is: filename = None filenames = [] ## These parameters can not be updated by the GUI (but will be ## propagated into new configuration files) readonly = {} ## Absolute parameters can only be set by the code or command ## lines, they can not be over ridden in the configuration ## file. This ensures that only configuration files dont mask new ## options (e.g. schema version) _absolute = {} ## A list of option names: options = [] def __init__(self): """ This is a singleton object kept in the class """ if not ConfObject.initialised: self.optparser.add_option("-h", "--help", action="store_true", default=False, help="list all available options and their default values. Default values may be set in the configuration file @sysconfdir@/pyflagrc") ConfObject.initialised = True def set_usage(self, usage=None, version=None): if usage: self.optparser.set_usage(usage) if version: self.optparser.version = version def add_file(self, filename, type='init'): """ Adds a new file to parse """ self.filenames.append(filename) self.cnf_opts.clear() for f in self.filenames: try: conf_parser = ConfigParser.ConfigParser() conf_parser.read(f) for k,v in conf_parser.items('DEFAULT'): ## Absolute parameters are protected from ## configuration files: if k in self._absolute.keys(): continue try: v = eval(v, self.g_dict) except Exception,e: pass ## update the configured options self.cnf_opts[k] = v except IOError: print "Unable to open %s" % filename ConfObject.filename = filename def parse_options(self, final=True): """ Parses the options from command line and any conf files currently added. The final parameter should be only called from main programs at the point where they are prepared for us to call exit if required; (For example when we detect the -h parameter). """ self.optparser.final = final ## Parse the command line options: try: (opts, args) = self.optparser.parse_args() except UnboundLocalError: raise RuntimeError("Unknown option - use -h to see help") self.opts.clear() ## Update our cmdline dict: for k in dir(opts): v = getattr(opts,k) if k in self.options and not v==None: self.opts[k] = v self.optparse_opts = opts self.args = args if final: ## Reparse the config file again: self.add_file(self.filename) try: ## Help can only be set on the command line if getattr(self.optparse_opts, "help"): ## Populate the metavars with the default values: for opt in self.optparser.option_list: try: opt.metavar = "%s" % getattr(self, opt.dest) except Exception,e: pass self.optparser.print_help() sys.exit(0) except AttributeError: pass def add_option(self, option, short_option=None, **args): """ Adds options both to the config file parser and the command line parser """ option = option.lower() if option in self.options: return self.options.append(option) ## If this is read only we store it in a special dict try: if args['readonly']: self.readonly[option] = args['default'] del args['readonly'] except KeyError: pass ## If there is a default specified, we update our defaults dict: try: default = args['default'] try: default = eval(default, self.g_dict) except: pass self.default_opts[option] = default del args['default'] except KeyError: pass try: self._absolute[option] = args['absolute'] del args['absolute'] except KeyError: pass self.docstrings[option] = args.get('help',None) if short_option: self.optparser.add_option("-%s" % short_option, "--%s" % option, **args) else: self.optparser.add_option("--%s" % option, **args) ## update the command line parser ## We have to do the try-catch for python 2.4 support of short ## arguments. It can be removed when python 2.5 is a requirement try: self.parse_options(False) except AttributeError: pass def update(self, key,value): """ This can be used by scripts to force a value of an option """ self.readonly[key.lower()] = value def __getattr__(self, attr): ## If someone is looking for a configuration parameter but ## we have not parsed anything yet - do so now. if self.opts == None: self.parse_options(False) ## Maybe its a class method? try: return super(ConfObject, self).__getattribute__(attr) except AttributeError: pass ## Is it a ready only parameter (i.e. can not be overridden by ## the config file) try: return self.readonly[attr.lower()] except KeyError: pass ## Try to find the attribute in the command line options: try: return self.opts[attr.lower()] except KeyError: pass ## Was it given in the environment? try: return os.environ["PYFLAG_" + attr.upper()] except KeyError: pass ## No - try the configuration file: try: return self.cnf_opts[attr.lower()] except KeyError: pass ## No - is there a default for it? try: return self.default_opts[attr.lower()] except KeyError: pass ## Maybe its just a command line option: try: if not attr.startswith("_") and self.optparse_opts: return getattr(self.optparse_opts, attr.lower()) except AttributeError: pass raise AttributeError("Parameter %s is not configured - try setting it on the command line (-h for help)" % attr) config = ConfObject() config_file = "@sysconfdir@/pyflagrc" if os.access(config_file, os.R_OK): config.add_file(config_file) else: config.add_file("pyflagrc") ## The following are global parameters we need - administrators will ## probably need to set those to something more sensible: config.add_option("UPLOADDIR", default="/tmp/", help = "Directory under which the web GUI will allow loading files") config.add_option("DATADIR", default="@DATADIR@", readonly=False, help = "Directory where pyflag installation files go") config.add_option("FLAG_BIN", default="@bin@", help = "Directory where pyflag binaries are called from") config.add_option("RESULTDIR", default="/tmp/", help = "Directory to store temporary analysed data.") ## This calculates the version: import re version = "$Version: 0.87-pre1 Date: Thu Jun 12 00:48:38 EST 2008$" m = re.match("\$Version: ([^ ]+)", version) if m: version = m.group(1) else: version = "@VERSION@" config.add_option("VERSION", default=version, readonly=True, help = "The current pyflag version") config.add_option("MAGICFILE", default = "@datadir@/magic:@magic@", help="Location of magic files") try: config.add_option("CONF_FILE", default=os.environ['HOME']+'/.pyflagrc', help = "User based configuration file") config.add_file(config.CONF_FILE) except KeyError: pass