Logo Search packages:      
Sourcecode: condor version File versions  Download package

condor_config.cpp

/***************************************************************
 *
 * Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
 * University of Wisconsin-Madison, WI.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You may
 * obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************/


/*

  This file implements config(), the function all daemons call to
  configure themselves.  It takes an optional argument which
  determines if config should be quiet or verbose on errors.  It
  defaults to verbose error reporting.

  There's also an entry point, config_host() where you pass in a
  string that should be filled in for HOSTNAME.  This is only used by
  special arguments to condor_config_val used by condor_init to
  bootstrap the installation process.

  When looking for the global config source, config() checks the
  "CONDOR_CONFIG" environment variable to find its location.  If that
  doesn't exist, we look in the following locations:

      1) /etc/condor/
      2) /usr/local/etc/
      3) ~condor/
      4) ${GLOBUS_LOCATION}/etc/

  If none of the above locations contain a config source, config()
  prints an error message and exits.

  In each "global" config source, a list of "local" config files, or a single
  cmd whose output is to be piped in as the configuration settings can be
  specified.  If a cmd is specified, it is processed and its output used
  as the configuration data.  If a file or list of files is specified, each
  file given in the list is read and processed in order.  These lists can
  be used to specify both platform-specific config files and machine-specific
  config files, in addition to a single, pool-wide, platform-independent
  config file.

*/

#include "condor_common.h"
#include "condor_debug.h"
#include "condor_syscall_mode.h"
#include "condor_config.h"
#include "condor_string.h"
#include "string_list.h"
#include "condor_attributes.h"
#include "my_hostname.h"
#include "condor_version.h"
#include "util_lib_proto.h"
#include "my_username.h"
#ifdef WIN32
#     include "ntsysinfo.h"         // for WinNT getppid
#     include <locale.h>
#endif
#include "directory.h"              // for StatInfo
#include "condor_scanner.h"         // for MAXVARNAME, etc
#include "condor_distribution.h"
#include "condor_environ.h"
#include "setenv.h"
#include "HashTable.h"
#include "extra_param_info.h"
#include "condor_uid.h"
#include "condor_mkstemp.h"
#include "basename.h"
#include "condor_random_num.h"
#include "extArray.h"
#include "subsystem_info.h"

#if HAVE_EXT_GCB
#include "GCB.h"
#endif

extern "C" {
      
// Function prototypes
void real_config(char* host, int wantsQuiet, bool wantExtraInfo);
int Read_config(const char*, BUCKET**, int, int, bool,
                        ExtraParamTable* = NULL);
bool is_piped_command(const char* filename);
bool is_valid_command(const char* cmdToExecute);
int SetSyscalls(int);
char* find_global();
char* find_file(const char*, const char*);
void init_tilde();
void fill_attributes();
void check_domain_attributes();
void clear_config();
void reinsert_specials(char*);
void process_config_source(char*, char*, char*, int);
void process_locals( char*, char*);
void process_directory( char*, char*);
static int  process_dynamic_configs();
void check_params();

// External variables
extern int  ConfigLineNo;
}  /* End extern "C" */

// Global variables
BUCKET      *ConfigTab[TABLESIZE];
static ExtraParamTable *extra_info = NULL;
static char* tilde = NULL;
extern DLL_IMPORT_MAGIC char **environ;
static bool have_config_source = true;

MyString global_config_source;
StringList local_config_sources;

static int ParamValueNameAscendingSort(const void *l, const void *r);


// Function implementations

void
config_fill_ad( ClassAd* ad, const char *prefix )
{
      char        *tmp;
      char        *expr;
      StringList  reqdExprs;
      MyString    buffer;

      if( !ad ) return;

      if ( ( NULL == prefix ) && get_mySubSystem()->hasLocalName() ) {
            prefix = get_mySubSystem()->getLocalName();
      }

      buffer.sprintf( "%s_EXPRS", get_mySubSystem()->getName() );
      tmp = param( buffer.Value() );
      if( tmp ) {
            reqdExprs.initializeFromString (tmp);     
            free (tmp);
      }

      buffer.sprintf( "%s_ATTRS", get_mySubSystem()->getName() );
      tmp = param( buffer.Value() );
      if( tmp ) {
            reqdExprs.initializeFromString (tmp);     
            free (tmp);
      }

      if(prefix) {
            buffer.sprintf( "%s_%s_EXPRS", prefix, get_mySubSystem()->getName() );
            tmp = param( buffer.Value() );
            if( tmp ) {
                  reqdExprs.initializeFromString (tmp);     
                  free (tmp);
            }

            buffer.sprintf( "%s_%s_ATTRS", prefix, get_mySubSystem()->getName() );
            tmp = param( buffer.Value() );
            if( tmp ) {
                  reqdExprs.initializeFromString (tmp);     
                  free (tmp);
            }

      }

      if( !reqdExprs.isEmpty() ) {
            reqdExprs.rewind();
            while ((tmp = reqdExprs.next())) {
                  expr = NULL;
                  if(prefix) {
                        buffer.sprintf("%s_%s", prefix, tmp);     
                        expr = param(buffer.Value());
                  }
                  if(!expr) {
                        expr = param(tmp);
                  }
                  if(expr == NULL) continue;
                  buffer.sprintf( "%s = %s", tmp, expr );

                  if( !ad->Insert( buffer.Value() ) ) {
                        dprintf(D_ALWAYS,
                                    "CONFIGURATION PROBLEM: Failed to insert ClassAd attribute %s.  The most common reason for this is that you forgot to quote a string value in the list of attributes being added to the %s ad.\n",
                                    buffer.Value(), get_mySubSystem()->getName() );
                  }

                  free( expr );
            }     
      }
      
      /* Insert the version into the ClassAd */
      buffer.sprintf( "%s=\"%s\"", ATTR_VERSION, CondorVersion() );
      ad->Insert( buffer.Value() );

      buffer.sprintf( "%s=\"%s\"", ATTR_PLATFORM, CondorPlatform() );
      ad->Insert( buffer.Value() );
}


/*
Walks all found configuration entries looking for the
"forbidden string".  If said string is found, EXCEPT.

Output is via a giant EXCEPT string because the dprintf
system probably isn't working yet.
*/
const char * FORBIDDEN_CONFIG_VAL = "YOU_MUST_CHANGE_THIS_INVALID_CONDOR_CONFIGURATION_VALUE";
static void
validate_entries( bool ignore_invalid_entry ) {
      HASHITER it = hash_iter_begin( ConfigTab, TABLESIZE );
      unsigned int invalid_entries = 0;
      MyString tmp;
      MyString output = "The following configuration macros appear to contain default values that must be changed before Condor will run.  These macros are:\n";
      while( ! hash_iter_done(it) ) {
            char * val = hash_iter_value(it);
            if( strstr(val, FORBIDDEN_CONFIG_VAL) ) {
                  char * name = hash_iter_key(it);
                  MyString filename;
                  int line_number;
                  param_get_location(name, filename, line_number);
                  tmp.sprintf("   %s (found on line %d of %s)\n", name, line_number, filename.Value());
                  output += tmp;
                  invalid_entries++;
            }
            hash_iter_next(it);
      }
      hash_iter_delete(&it);
      if(invalid_entries > 0) {
            if(ignore_invalid_entry) {
                  dprintf(D_ALWAYS, "%s", output.Value());
            } else {
                  EXCEPT(output.Value());
            }
      }
}

// return a list sorted by macro name of all of the macro values found in the
// locally defined files.
ExtArray<ParamValue>*
param_all(void)
{
      ExtArray<ParamValue> *pvs = NULL;
      MyString filename;
      int line_number;
      MyString str;
      HASHITER it = hash_iter_begin( ConfigTab, TABLESIZE );
      char *name = NULL;
      char *value = NULL;
      int i;
      ParamValue *sort_array = NULL;

      pvs = new ExtArray<ParamValue>;
      ASSERT(pvs);

      // walk the config table and insert everything I found into the list.
      i = 0;
      while( ! hash_iter_done(it) ) {
            name = hash_iter_key(it);
            value = hash_iter_value(it);
            param_get_location(name, filename, line_number);

            (*pvs)[i].name = name;
            (*pvs)[i].value = value;
            (*pvs)[i].filename = filename;
            (*pvs)[i].lnum = line_number;
            (*pvs)[i].source = "Local Config File";

            i++;

            hash_iter_next(it);
      }
      hash_iter_delete(&it);

      // Sort the list based upon name
      // qsort and extArray don't play nice together...

      // copy the data to a new POD array

      sort_array = new ParamValue[pvs->getlast() + 1];
      ASSERT(sort_array);

      for (i = 0; i < pvs->getlast() + 1; i++) {
            sort_array[i] = (*pvs)[i];
      }

      // sort it
      qsort(sort_array, pvs->getlast() + 1, sizeof(ParamValue),
            ParamValueNameAscendingSort);

      // copy it back into the ExtArray
      for (i = 0; i < pvs->getlast() + 1; i++) {
            (*pvs)[i] = sort_array[i];
      }
      
      delete [] sort_array;

      return pvs;
}

static int ParamValueNameAscendingSort(const void *l, const void *r)
{
      const ParamValue *left = (const ParamValue*)l;
      const ParamValue *right = (const ParamValue*)r;

      if (left->name < right->name) {
            return -1;
      }

      if (left->name > right->name) {
            return 1;
      }

      return 0;
}


void
config( int wantsQuiet, bool ignore_invalid_entry, bool wantsExtraInfo )
{
#ifdef WIN32
      setlocale( LC_ALL, "English" );
#endif
      real_config( NULL, wantsQuiet, wantsExtraInfo );
      validate_entries( ignore_invalid_entry );
}


void
config_host( char* host )
{
      real_config( host, 0, true );
}

/* This function initialize GSI (maybe other) authentication related
   stuff Daemons that should use the condor daemon credentials should
   set the argument is_daemon=true.  This function is automatically
   called at config init time with is_daemon=false, so that all
   processes get the basic auth config.  The order of calls to this
   function do not matter, as the results are only additive.
   Therefore, calling with is_daemon=false and then with
   is_daemon=true or vice versa are equivalent.
*/
void
condor_auth_config(int is_daemon)
{
#if !defined(SKIP_AUTHENTICATION) && defined(HAVE_EXT_GLOBUS)

            // First, if there is X509_USER_PROXY, we clear it
            // (if we're a daemon).
      if ( is_daemon ) {
            UnsetEnv( "X509_USER_PROXY" );
      }

            // Next, we param the configuration file for GSI related stuff and
            // set the corresponding environment variables for it

      char *pbuf = 0;
      char *proxy_buf = 0;
      char *cert_buf = 0;
      char *key_buf = 0;
      char *trustedca_buf = 0;
      char *mapfile_buf = 0;

      MyString buffer;


            // Here's how it works. If you define any of
            // GSI_DAEMON_CERT, GSI_DAEMON_KEY, GSI_DAEMON_PROXY, or
            // GSI_DAEMON_TRUSTED_CA_DIR, those will get stuffed into the
            // environment.
            //
            // Everything else depends on GSI_DAEMON_DIRECTORY. If
            // GSI_DAEMON_DIRECTORY is not defined, then only settings that are
            // defined above will be placed in the environment, so if you
            // want the cert and host in a non-standard location, but want to use
            // /etc/grid-security/certifcates as the trusted ca dir, only
            // define GSI_DAEMON_CERT and GSI_DAEMON_KEY, and not
            // GSI_DAEMON_DIRECTORY and GSI_DAEMON_TRUSTED_CA_DIR
            //
            // If GSI_DAEMON_DIRECTORY is defined, condor builds a "reasonable"
            // default out of what's already been defined and what it can
            // construct from GSI_DAEMON_DIRECTORY  - ie  the trusted CA dir ends
            // up as in $(GSI_DAEMON_DIRECTORY)/certificates, and so on
            // The proxy is not included in the "reasonable defaults" section

            // First, let's get everything we might want
      pbuf = param( STR_GSI_DAEMON_DIRECTORY );
      trustedca_buf = param( STR_GSI_DAEMON_TRUSTED_CA_DIR );
      mapfile_buf = param( STR_GSI_MAPFILE );
      if( is_daemon ) {
            proxy_buf = param( STR_GSI_DAEMON_PROXY );
            cert_buf = param( STR_GSI_DAEMON_CERT );
            key_buf = param( STR_GSI_DAEMON_KEY );
      }

      if (pbuf) {

            if( !trustedca_buf) {
                  buffer.sprintf( "%s%ccertificates", pbuf, DIR_DELIM_CHAR);
                  SetEnv( STR_GSI_CERT_DIR, buffer.Value() );
            }

            if (!mapfile_buf ) {
                  buffer.sprintf( "%s%cgrid-mapfile", pbuf, DIR_DELIM_CHAR);
                  SetEnv( STR_GSI_MAPFILE, buffer.Value() );
            }

            if( is_daemon ) {
                  if( !cert_buf ) {
                        buffer.sprintf( "%s%chostcert.pem", pbuf, DIR_DELIM_CHAR);
                        SetEnv( STR_GSI_USER_CERT, buffer.Value() );
                  }
      
                  if (!key_buf ) {
                        buffer.sprintf( "%s%chostkey.pem", pbuf, DIR_DELIM_CHAR);
                        SetEnv( STR_GSI_USER_KEY, buffer.Value() );
                  }
            }

            free( pbuf );
      }

      if(trustedca_buf) {
            SetEnv( STR_GSI_CERT_DIR, trustedca_buf );
            free(trustedca_buf);
      }

      if (mapfile_buf) {
            SetEnv( STR_GSI_MAPFILE, mapfile_buf );
            free(mapfile_buf);
      }

      if( is_daemon ) {
            if(proxy_buf) {
                  SetEnv( STR_GSI_USER_PROXY, proxy_buf );
                  free(proxy_buf);
            }

            if(cert_buf) {
                  SetEnv( STR_GSI_USER_CERT, cert_buf );
                  free(cert_buf);
            }

            if(key_buf) {
                  SetEnv( STR_GSI_USER_KEY, key_buf );
                  free(key_buf);
            }
      }

#endif
}

void
condor_net_remap_config( bool force_param )
{
    char *str = NULL;
      if( ! force_param && getenv("NET_REMAP_ENABLE") ) {
                  /*
                    this stuff is already set.  unless the caller is forcing
                    us to call param() again (e.g. the master is trying to
                    re-bind() if the GCB broker is down and it's got a list
                    to try) we should return immediately and leave our
                    environment alone.  this way, the master can choose what
                    GCB broker to use for itself and all its children, even
                    if there's a list and we're using $RANDOM_CHOICE().
                  */
            return;
      }
            
            /*
              this method is only called if we're enabling a network remap
              service.  if we do, we always need to force condor to bind()
              to all interfaces (INADDR_ANY).  since we don't want to rely
              on users to set this themselves to get GCB working, we'll
              set it automatically.  the only harm of setting this is that
              we need Condor to automatically handle hostallow stuff for
              "localhost", or users need to add localhost to their
              hostallow settings as appropriate.  we can't rely on the
              later, and the former only works on some platforms.
              luckily, the automatic localhost stuff works on all
              platforms where GCB works (linux, and we hope, solaris), so
              it's safe to turn this on whenever we're using GCB
            */
      insert( "BIND_ALL_INTERFACES", "TRUE", ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("BIND_ALL_INTERFACES");

    // Env: the type of service
    SetEnv( "NET_REMAP_ENABLE", "true");
    str = param("NET_REMAP_SERVICE");
    if (str) {
        if (!strcasecmp(str, "GCB")) {
            SetEnv( "GCB_ENABLE", "true" );
            free(str);
            str = NULL;
            // Env: InAgent
            if( (str = param("NET_REMAP_INAGENT")) ) {
                              // NET_REMAP_INAGENT is a list of GCB brokers.
                        const char *next_broker;
                        StringList all_brokers( str );
                        StringList working_brokers;

                              // Pick a random working GCB broker.
                        all_brokers.rewind();
                        while ( (next_broker = all_brokers.next()) ) {
                              int rc = 0;
                              int num_slots = 0;

#if HAVE_EXT_GCB
                              rc = GCB_broker_query( next_broker,
                                                               GCB_DATA_QUERY_FREE_SOCKS,
                                                               &num_slots );
#endif
                              if ( rc == 0 ) {
                                    working_brokers.append( next_broker );
                              }
                        }

                        if ( working_brokers.number() > 0 ) {
                              int rand_entry = (get_random_int() % working_brokers.number()) + 1;
                              int i = 0;
                              working_brokers.rewind();
                              while ( (i < rand_entry) &&
                                          (next_broker=working_brokers.next()) ) {
                                    i++;
                              }

                              dprintf( D_FULLDEBUG,"Using GCB broker %s\n",next_broker );
                              SetEnv( "GCB_INAGENT", next_broker );
                        } else {
                                    // TODO How should we indicate that we tried and
                                    //   failed to find a working broker? For now, we
                                    //   set GCB_INAGENT to a valid, but non-existent
                                    //   IP address. That should cause a failure when
                                    //   we try to make a socket. The address we use
                                    //   is defined in our header file, so callers
                                    //   can check GCB_INAGENT to see if we failed to
                                    //   find a broker.
                              dprintf( D_ALWAYS,"No usable GCB brokers were found. "
                                           "Setting GCB_INAGENT=%s\n",
                                           CONDOR_GCB_INVALID_BROKER );
                              SetEnv( "GCB_INAGENT", CONDOR_GCB_INVALID_BROKER );
                        }
                        free( str );
                str = NULL;
            }
            // Env: Routing table
            if( (str = param("NET_REMAP_ROUTE")) ) {
                SetEnv( "GCB_ROUTE", str );
                        free( str );
                str = NULL;
            }
        } else if (!strcasecmp(str, "DPF")) {
            SetEnv( "DPF_ENABLE", "true" );
            free(str);
            str = NULL;
            // Env: InAgent
            if( (str = param("NET_REMAP_INAGENT")) ) {
                SetEnv( "DPF_INAGENT", str );
                        free(str);
                        str = NULL;
            }
            // Env: Routing table
            if( (str = param("NET_REMAP_ROUTE")) ) {
                SetEnv( "DPF_ROUTE", str );
                        free(str);
                        str = NULL;
            }
        }
    }
}


void
real_config(char* host, int wantsQuiet, bool wantExtraInfo)
{
      char* config_source = NULL;
      char* tmp = NULL;
      int scm;

      static bool first_time = true;
      if( first_time ) {
            first_time = false;
            init_config(wantExtraInfo);
      } else {
                  // Clear out everything in our config hash table so we can
                  // rebuild it from scratch.
            clear_config();
            if (wantExtraInfo) {
                  extra_info = new ExtraParamTable();
            } else {
                  extra_info = new DummyExtraParamTable();
            }
      }

      dprintf( D_CONFIG, "config: using subsystem '%s', local '%s'\n",
                   get_mySubSystem()->getName(), get_mySubSystem()->getLocalName("") );

            /*
              N.B. if we are using the yellow pages, system calls which are
              not supported by either remote system calls or file descriptor
              mapping will occur.  Thus we must be in LOCAL/UNRECORDED mode here.
            */
      scm = SetSyscalls( SYS_LOCAL | SYS_UNRECORDED );

            // Try to find user "condor" in the passwd file.
      init_tilde();

            // Insert an entry for "tilde", (~condor)
      if( tilde ) {
            insert( "tilde", tilde, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("tilde");

      } else {
                  // What about tilde if there's no ~condor?
      }

            // Insert some default values for attributes we want even if
            // they're not defined in the config sources: ARCH and OPSYS.
            // We also want to insert the special "SUBSYSTEM" macro here.
            // We do this now since if they are defined in the config
            // files, these values will get overridden.  However, we want
            // them defined to begin with so that people can use them in
            // the global config source to specify the location of
            // platform-specific config sources, etc.  -Derek Wright 6/8/98
            // Moved all the domain-specific stuff to a separate function
            // since we might not know our full hostname yet. -Derek 10/20/98
      fill_attributes();

            // Try to find the global config source

      char* env = getenv( EnvGetName(ENV_CONFIG) );
      if( env && stricmp(env, "ONLY_ENV") == MATCH ) {
                  // special case, no config source desired
            have_config_source = false;
      }

      if( have_config_source && ! (config_source = find_global()) ) {
            if( wantsQuiet ) {
                  fprintf( stderr, "%s error: can't find config source.\n",
                               myDistro->GetCap() );
                  exit( 1 );
            }
            fprintf(stderr,"\nNeither the environment variable %s_CONFIG,\n",
                        myDistro->GetUc() );
#       if defined UNIX
            fprintf(stderr,"/etc/%s/, nor ~%s/ contain a %s_config source.\n",
                        myDistro->Get(), myDistro->Get(), myDistro->Get() );
#       elif defined WIN32
            fprintf(stderr,"nor the registry contains a %s_config source.\n", myDistro->Get() );
#       else
#           error "Unknown O/S"
#       endif
            fprintf( stderr,"Either set %s_CONFIG to point to a valid config "
                        "source,\n", myDistro->GetUc() );
#       if defined UNIX
            fprintf( stderr,"or put a \"%s_config\" file in /etc/%s or ~%s/\n",
                         myDistro->Get(), myDistro->Get(), myDistro->Get() );
#       elif defined WIN32
            fprintf( stderr,"or put a \"%s_config\" source in the registry at:\n"
                         " HKEY_LOCAL_MACHINE\\Software\\%s\\%s_CONFIG",
                         myDistro->Get(), myDistro->Get(), myDistro->GetUc() );
#       else
#           error "Unknown O/S"
#       endif
            fprintf( stderr, "Exiting.\n\n" );
            exit( 1 );
      }

            // Read in the global file
      if( have_config_source ) {
            process_config_source( config_source, "global config source", NULL, true );
            global_config_source = config_source;
            free( config_source );
            config_source = NULL;
      }

            // Insert entries for "hostname" and "full_hostname".  We do
            // this here b/c we need these macros defined so that we can
            // find the local config source if that's defined in terms of
            // hostname or something.  However, we do this after reading
            // the global config source so people can put the
            // DEFAULT_DOMAIN_NAME parameter somewhere if they need it.
            // -Derek Wright <wright@cs.wisc.edu> 5/11/98
      if( host ) {
            insert( "hostname", host, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("hostname");
      } else {
            insert( "hostname", my_hostname(), ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("hostname");
      }
      insert( "full_hostname", my_full_hostname(), ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("full_hostname");

            // Also insert tilde since we don't want that over-written.
      if( tilde ) {
            insert( "tilde", tilde, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("tilde");
      }

            // Read in the LOCAL_CONFIG_FILE as a string list and process
            // all the files in the order they are listed.
      char *dirlist = param("LOCAL_CONFIG_DIR");
      if(dirlist) {
            process_directory(dirlist, host);
      }
      process_locals( "LOCAL_CONFIG_FILE", host );

      char* newdirlist = param("LOCAL_CONFIG_DIR");
      if(newdirlist && dirlist) {
            if(strcmp(dirlist, newdirlist) ) {
                  process_directory(newdirlist, host);
            }
      }

      if(dirlist) { free(dirlist); dirlist = NULL; }
      if(newdirlist) { free(newdirlist); newdirlist = NULL; }

            // Daemons should additionally call condor_auth_config()
            // explicitly with the argument is_daemon=true.  Here, we just
            // call with is_daemon=false, since that is fine for both daemons
            // and non-daemons to do.
      condor_auth_config( false );

      // The following lines should be placed very carefully. Must be after
      // global and local config sources being processed but before any
      // call that may be interposed by GCB
    if ( param_boolean("NET_REMAP_ENABLE", false) ) {
        condor_net_remap_config();
    }
                  
            // Re-insert the special macros.  We don't want the user to
            // override them, since it's not going to work.
      reinsert_specials( host );

            // Now, insert any macros defined in the environment.
      for( int i = 0; environ[i]; i++ ) {
            char magic_prefix[MAX_DISTRIBUTION_NAME + 3];   // case-insensitive
            strcpy( magic_prefix, "_" );
            strcat( magic_prefix, myDistro->Get() );
            strcat( magic_prefix, "_" );
            int prefix_len = strlen( magic_prefix );

            // proceed only if we see the magic prefix
            if( strncasecmp( environ[i], magic_prefix, prefix_len ) != 0 ) {
                  continue;
            }

            char *varname = strdup( environ[i] );
            if( !varname ) {
                  EXCEPT( "Out of memory in %s:%d\n", __FILE__, __LINE__ );
            }

            // isolate variable name by finding & nulling the '='
            int equals_offset = strchr( varname, '=' ) - varname;
            varname[equals_offset] = '\0';
            // isolate value by pointing to everything after the '='
            char *varvalue = varname + equals_offset + 1;
//          assert( !strcmp( varvalue, getenv( varname ) ) );
            // isolate Condor macro_name by skipping magic prefix
            char *macro_name = varname + prefix_len;

            // special macro START_owner needs to be expanded (for the
            // glide-in code) [which should probably be fixed to use
            // the general mechanism and set START itself --pfc]
            if( !strcmp( macro_name, "START_owner" ) ) {
                  char *pretext = "Owner == \"";
                  char *posttext = "\"";
                  char *ownerstr = (char *)malloc( strlen( pretext ) + strlen( varvalue )
                                                            + strlen( posttext ) );
                  sprintf( ownerstr, "%s%s%s", pretext, varvalue, posttext );
                  insert( "START", ownerstr, ConfigTab, TABLESIZE );
                  extra_info->AddEnvironmentParam("START");
                  free( ownerstr );
            }
            // ignore "_CONDOR_" without any macro name attached
            else if( macro_name[0] != '\0' ) {
                  insert( macro_name, varvalue, ConfigTab, TABLESIZE );
                  extra_info->AddEnvironmentParam(macro_name);
            }

            free( varname );
      }

            // Re-insert the special macros.  We don't want the user to
            // override them, since it's not going to work.
      reinsert_specials( host );

      process_dynamic_configs();

      if (config_source) {
            free( config_source );
      }

            // Now that we're done reading files, if DEFAULT_DOMAIN_NAME
            // is set, we need to re-initialize my_full_hostname().
      if( (tmp = param("DEFAULT_DOMAIN_NAME")) ) {
            free( tmp );
            init_full_hostname();
      }

            // Also, we should be safe to process the NETWORK_INTERFACE
            // parameter at this point, if it's set.
      init_ipaddr( TRUE );

            // Re-insert the special macros.  We don't want the user to
            // override them, since it's not going to work.
      reinsert_specials( host );

            // Make sure our FILESYSTEM_DOMAIN and UID_DOMAIN settings are
            // correct.
      check_domain_attributes();

            // We have to do some platform-specific checking to make sure
            // all the parameters we think are defined really are.
      check_params();

      condor_except_should_dump_core( param_boolean("ABORT_ON_EXCEPTION", false) );

      (void)SetSyscalls( scm );
}


void
process_config_source( char* file, char* name, char* host, int required )
{
      int rval;
      if( access( file, R_OK ) != 0 && !is_piped_command(file)) {
            if( !required) { return; }

            if( !host ) {
                  fprintf( stderr, "ERROR: Can't read %s %s\n",
                               name, file );
                  exit( 1 );
            }
      } else {
            rval = Read_config( file, ConfigTab, TABLESIZE, EXPAND_LAZY,
                                          false, extra_info );
            if( rval < 0 ) {
                  fprintf( stderr,
                               "Configuration Error Line %d while reading %s %s\n",
                               ConfigLineNo, name, file );
                  exit( 1 );
            }
      }
}


// Param for given name, read it in as a string list, and process each
// config source listed there.  If the value is actually a cmd whose
// output should be piped, then do *not* treat it as a file list.
void
process_locals( char* param_name, char* host )
{
      StringList sources_to_process, sources_done;
      char *source, *sources_value;
      char *tmp;
      int local_required;
      
      local_required = true;  
    tmp = param( "REQUIRE_LOCAL_CONFIG_FILE" );
    if( tmp ) {
            if( tmp[0] == 'f' || tmp[0] == 'F' ) {
                  local_required = false;
            }
            free( tmp );
    }

      sources_value = param( param_name );
      if( sources_value ) {
            if ( is_piped_command( sources_value ) ) {
                  sources_to_process.insert( sources_value );
            } else {
                  sources_to_process.initializeFromString( sources_value );
            }
            sources_to_process.rewind();
            while( (source = sources_to_process.next()) ) {
                  process_config_source( source, "config source", host,
                                                   local_required );
                  local_config_sources.append( source );

                  sources_done.append(source);

                  char* new_sources_value = param(param_name);
                  if(new_sources_value) {
                        if(strcmp(sources_value, new_sources_value) ) {
                        // the file we just processed altered the list of sources to
                        // process
                              sources_to_process.clearAll();
                              if ( is_piped_command( new_sources_value ) ) {
                                    sources_to_process.insert( new_sources_value );
                              } else {
                                    sources_to_process.initializeFromString(new_sources_value);
                              }

                              // remove all the ones we've finished from the old list
                  sources_done.rewind();
                  while( (source = sources_done.next()) ) {
                                    sources_to_process.remove(source);
                              }
                              sources_to_process.rewind();
                              free(sources_value);
                              sources_value = new_sources_value;
                        } else {
                              free(new_sources_value);
                        }
                  }
            }
            free(sources_value);
      }
}

int compareFiles(const void *a, const void *b) {
       return strcmp(*(char *const*)a, *(char *const*)b);
}

// examine each file in a directory and treat it as a config file
void
process_directory( char* dirlist, char* host )
{
      StringList locals;
      Directory *files;
      const char *file, *dirpath;
      char **paths;
      char *tmp;
      int local_required;
      
      local_required = true;  
      tmp = param( "REQUIRE_LOCAL_CONFIG_FILE" );
      if( tmp ) {
            if( tmp[0] == 'f' || tmp[0] == 'F' ) {
                  local_required = false;
            }
            free( tmp );
      }

      if(!dirlist) { return; }
      locals.initializeFromString( dirlist );
      locals.rewind();
      while( (dirpath = locals.next()) ) {

            paths = (char **)calloc(65536, sizeof(char *));
            files = new Directory(dirpath);
            int i = 0;
            if(files == NULL) {
                  fprintf(stderr, "Cannot open %s\n", dirpath);
            } else {
                  while( (file = files->Next()) && i < 65536) {
                        // don't consider directories
                        // maybe we should squash symlinks here...
                        if(! files->IsDirectory() ) {
                              paths[i] = strdup(files->GetFullPath());
                              i++;
                        }
                  }
                  delete files;
            }
            qsort(paths, i, sizeof(char *), compareFiles);
            char **pathCopy = paths;
            while(*pathCopy) {
                  process_config_source( *pathCopy, "config source", host,
                                                   local_required );

                  local_config_sources.append(*pathCopy);

                  free(*pathCopy);
                  pathCopy++;
            }
            free(paths);
      }
}

// Try to find the "condor" user's home directory
void
init_tilde()
{
      if( tilde ) {
            free( tilde );
            tilde = NULL;
      }
# if defined UNIX
      struct passwd *pw;
      if( (pw=getpwnam( myDistro->Get() )) ) {
            tilde = strdup( pw->pw_dir );
      }
# else
      // On Windows, we'll just look in the registry for TILDE.
      HKEY  handle;
      char regKey[1024];

      sprintf( regKey, "Software\\%s", myDistro->GetCap() );

      if ( RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKey,
            0, KEY_READ, &handle) == ERROR_SUCCESS ) {

            // got the reg key open; now we just need to see if
            // we can open the TILDE string value.

            char the_path[MAX_PATH];
            DWORD valType;
            DWORD valSize = MAX_PATH - 2;

            the_path[0] = '\0';

            if ( RegQueryValueEx(handle, "TILDE", 0,
                  &valType, (unsigned char *)the_path, &valSize) == ERROR_SUCCESS ) {

                  if ( valType == REG_SZ && the_path[0] ) {
                        // got it!
                        tilde = strdup(the_path);
                  }
            }
            RegCloseKey(handle);
      }
      
# endif
}


char*
get_tilde()
{
      init_tilde();
      return tilde;
}


char*
find_global()
{
      MyString    file;
      file.sprintf( "%s_config", myDistro->Get() );
      return find_file( EnvGetName( ENV_CONFIG), file.Value() );
}


// Find location of specified file
char*
find_file(const char *env_name, const char *file_name)
{
      char* config_source = NULL;
      char* env = NULL;
      int fd = 0;

            // If we were given an environment variable name, try that first.
      if( env_name && (env = getenv( env_name )) ) {
            config_source = strdup( env );
            StatInfo si( config_source );
            switch( si.Error() ) {
            case SIGood:
                  if( si.IsDirectory() ) {
                        fprintf( stderr, "File specified in %s environment "
                                     "variable:\n\"%s\" is a directory.  "
                                     "Please specify a file.\n", env_name,
                                     config_source );
                        free( config_source );
                        config_source = NULL;
                        exit( 1 );
                  }
                        // Otherwise, we're happy
                  return config_source;
                  break;
            case SINoFile:
                  // Check to see if it is a pipe command, in which case we're fine.
                  if (!is_piped_command(config_source) ||
                        !is_valid_command(config_source)) {

                        fprintf( stderr, "File specified in %s environment "
                                     "variable:\n\"%s\" does not exist.\n",
                                     env_name, config_source );
                        free( config_source );
                        exit( 1 );
                        break;
                  }
                  // Otherwise, we're happy
                  return config_source;

            case SIFailure:
                  fprintf( stderr, "Cannot stat file specified in %s "
                               "environment variable:\n\"%s\", errno: %d\n",
                               env_name, config_source, si.Errno() );
                  free( config_source );
                  exit( 1 );
                  break;
            }
      }

# ifdef UNIX

      if (!config_source) {
                  // List of condor_config file locations we'll try to open.
                  // As soon as we find one, we'll stop looking.
            int locations_length = 4;
            MyString locations[locations_length];
                  // 1) /etc/condor/condor_config
            locations[0].sprintf( "/etc/%s/%s", myDistro->Get(), file_name );
                  // 2) /usr/local/etc/condor_config (FreeBSD)
            locations[1].sprintf( "/usr/local/etc/%s", file_name );
            if (tilde) {
                        // 3) ~condor/condor_config
                  locations[2].sprintf( "%s/%s", tilde, file_name );
            }
                  // 4) ${GLOBUS_LOCATION}/etc/condor_config
            char *globus_location;
            if ((globus_location = getenv("GLOBUS_LOCATION"))) {
                  locations[3].sprintf( "%s/etc/%s", globus_location, file_name );
            }

            int ctr;    
            for (ctr = 0 ; ctr < locations_length; ctr++) {
                        // Only use this file if the path isn't empty and
                        // if we can read it properly.
                  if (!locations[ctr].IsEmpty()) {
                        config_source = strdup(locations[ctr].Value());
                        if ((fd = safe_open_wrapper(config_source, O_RDONLY)) < 0) {
                              free(config_source);
                              config_source = NULL;
                        } else {
                              close(fd);
                              dprintf(D_FULLDEBUG, "Reading condor configuration "
                                          "from '%s'\n", config_source);
                              break;
                        }
                  }
            } // FOR
      } // IF

# elif defined WIN32    // ifdef UNIX
      // Only look in the registry on WinNT.
      HKEY  handle;
      char  regKey[256];

      sprintf( regKey, "Software\\%s", myDistro->GetCap() );
      if ( !config_source && RegOpenKeyEx(HKEY_LOCAL_MACHINE, regKey,
            0, KEY_READ, &handle) == ERROR_SUCCESS ) {
            // We have found a registry key for Condor, which
            // means this user has a pulse and has actually run the
            // installation program before trying to run Condor.
            // This user deserves a tax credit.

            // So now that we found the key, read it.
            char the_path[MAX_PATH];
            DWORD valType;
            DWORD valSize = MAX_PATH - 2;

            the_path[0] = '\0';
            if ( RegQueryValueEx(handle, env_name, 0,
                  &valType, (unsigned char *)the_path, &valSize) == ERROR_SUCCESS ) {

                  // confirm it is a string value with something there
                  if ( valType == REG_SZ && the_path[0] ) {
                        // got it!  whoohooo!
                        config_source = strdup(the_path);

                        if ( strncmp(config_source, "\\\\", 2 ) == 0 ) {
                              // UNC Path, so run a 'net use' on it first.
                              NETRESOURCE nr;
                              nr.dwType = RESOURCETYPE_DISK;
                              nr.lpLocalName = NULL;
                              nr.lpRemoteName = condor_dirname(config_source);
                              nr.lpProvider = NULL;
                              
                              if ( NO_ERROR != WNetAddConnection2(
                                                            &nr,   /* NetResource */
                                                            NULL,  /* password (default) */
                                                            NULL,  /* username (default) */
                                                            0      /* flags (none) */
                                    ) ) {

                                    if ( GetLastError() == ERROR_INVALID_PASSWORD ) {
                                          // try again with an empty password
                                          WNetAddConnection2(
                                                            &nr,   /* NetResource */
                                                            "",    /* password (none) */
                                                            NULL,  /* username (default) */
                                                            0      /* flags (none) */
                                          );
                                    }

                                    // whether it worked or not, we're gonna
                                    // continue.  The goal of running the
                                    // WNetAddConnection2() is to make a mapping
                                    // to the UNC path. For reasons I don't fully
                                    // understand, some sites need the mapping,
                                    // and some don't. If it works, great; if not,
                                    // try the safe_open_wrapper() anyways, and at
                                    // worst we'll fail fast and the user can fix
                                    // their file server.
                              }

                              if (nr.lpRemoteName) {
                                    free(nr.lpRemoteName);
                              }
                        }

                        if( !(is_piped_command(config_source) &&
                                is_valid_command(config_source)) &&
                              (fd = safe_open_wrapper( config_source, O_RDONLY)) < 0 ) {

                              free( config_source );
                              config_source = NULL;
                        } else {
                              if (fd != 0) {
                                    close( fd );
                              }
                        }
                  }
            }

            RegCloseKey(handle);
      }
# else
#     error "Unknown O/S"
# endif           /* ifdef UNIX / Win32 */

      return config_source;
}


void
fill_attributes()
{
            /* There are a few attributes that specify what platform we're
               on that we want to insert values for even if they're not
               defined in the config sources.  These are ARCH and OPSYS,
               which we compute with the sysapi_condor_arch() and sysapi_opsys()
               functions.  We also insert the subsystem here.  Moved all
               the domain stuff to check_domain_attributes() on
               10/20.  Also, since this is called before we read in any
               config sources, there's no reason to check to see if any of
               these are already defined.  -Derek Wright
               Amended -Pete Keller 06/01/99 */

      const char *tmp;

      if( (tmp = sysapi_condor_arch()) != NULL ) {
            insert( "ARCH", tmp, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("ARCH");
      }

      if( (tmp = sysapi_uname_arch()) != NULL ) {
            insert( "UNAME_ARCH", tmp, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("UNAME_ARCH");
      }

      if( (tmp = sysapi_opsys()) != NULL ) {
            insert( "OPSYS", tmp, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("OPSYS");
      }

      if( (tmp = sysapi_uname_opsys()) != NULL ) {
            insert( "UNAME_OPSYS", tmp, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("UNAME_OPSYS");
      }

      insert( "subsystem", get_mySubSystem()->getName(), ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("subsystem");
}


void
check_domain_attributes()
{
            /* Make sure the FILESYSTEM_DOMAIN and UID_DOMAIN attributes
               are set to something reasonable.  If they're not already
               defined, we default to our own full hostname.  Moved this
               to its own function so we're sure we have our full hostname
               by the time we call this. -Derek Wright 10/20/98 */

      char *uid_domain, *filesys_domain;

      filesys_domain = param("FILESYSTEM_DOMAIN");
      if( !filesys_domain ) {
            filesys_domain = my_full_hostname();
            insert( "FILESYSTEM_DOMAIN", filesys_domain, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("FILESYSTEM_DOMAIN");
      } else {
            free( filesys_domain );
      }

      uid_domain = param("UID_DOMAIN");
      if( !uid_domain ) {
            uid_domain = my_full_hostname();
            insert( "UID_DOMAIN", uid_domain, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("UID_DOMAIN");
      } else {
            free( uid_domain );
      }
}

void
init_config(bool wantExtraInfo  /* = true */)
{
      memset( (char *)ConfigTab, 0, (TABLESIZE * sizeof(BUCKET*)) );
      if (wantExtraInfo) {
            extra_info = new ExtraParamTable();
      } else {
            extra_info = new DummyExtraParamTable();
      }

      return;
}

void
clear_config()
{
      register    int   i;
      register    BUCKET      *ptr = NULL;
      register    BUCKET      *tmp = NULL;

      for( i=0; i<TABLESIZE; i++ ) {
            ptr = ConfigTab[i];
            while( ptr ) {
                  tmp = ptr->next;
                  FREE( ptr->value );
                  ptr->value = NULL;
                  FREE( ptr->name );
                  ptr->name = NULL;
                  FREE( ptr );
                  ptr = tmp;
            }
            ConfigTab[i] = NULL;
      }
      if (extra_info != NULL) {
            delete extra_info;
            extra_info = NULL;
      }
      global_config_source       = "";
      local_config_sources.clearAll();
      return;
}


/*
** Return the value associated with the named parameter.  Return NULL
** if the given parameter is not defined.
*/
char *
param( const char *name )
{
      char        *val = NULL;
      MyString     param_name;
      MyString     prefix;

      // Try in order to find the parameter
      // As we walk through, any value (including empty string) will
      // cause a 'match' since presumably it was set to empty
      // specifically to clear this parameter for this specific
      // subsystem / local.

      // 1. "subsys.local.name"
      const char  *local = get_mySubSystem()->getLocalName();
      if (  (NULL == val) && local ) {
            prefix = get_mySubSystem()->getName();
            prefix += ".";
            prefix += local;
            prefix += ".";
            param_name = prefix + name;
            val = lookup_macro( param_name.GetCStr(), ConfigTab, TABLESIZE );
      }
      // 2. "local.name"
      if (  (NULL == val) && local ) {
            prefix = local;
            prefix += ".";
            param_name = prefix + name;
            val = lookup_macro( param_name.GetCStr(), ConfigTab, TABLESIZE );
      }
      // 3. "subsys.name"
      if ( NULL == val ) {
            prefix = get_mySubSystem()->getName();
            prefix += ".";
            param_name = prefix + name;
            val = lookup_macro( param_name.GetCStr(), ConfigTab, TABLESIZE );
      }
      // 4. "name"
      if ( NULL == val ) {
            prefix = "";
            param_name = name;
            val = lookup_macro( name, ConfigTab, TABLESIZE );
      }

      // Still nothing (or empty)?  Give up.
      if ( (NULL == val) || (*val=='\0') ) {
            return NULL;
      }

      if ( prefix != "" ) {
            dprintf( D_CONFIG, "Config '%s': using prefix '%s' ==> '%s'\n",
                         name, prefix.GetCStr(), val );
      }
      else {
            dprintf( D_CONFIG, "Config '%s': no prefix ==> '%s'\n", name, val );
      }

      // Ok, now expand it out...
      val = expand_macro( val, ConfigTab, TABLESIZE );

      // If it returned an empty string, free it before returning NULL
      if( val == NULL ) {
            return NULL;
      } else if ( val[0] == '\0' ) {
            free( val );
            return( NULL );
      } else {
            return val;
      }
}

/*
** Return the integer value associated with the named paramter.
** This version returns true if a the parameter was found, or false
** otherwise.
** If the value is not defined or not a valid integer, then
** return the default_value argument .  The min_value and max_value
** arguments are optional and default to MININT and MAXINT.
** These range checks are disabled if check_ranges is false.
*/

bool
param_integer( const char *name, int &value,
                     bool use_default, int default_value,
                     bool check_ranges, int min_value, int max_value )
{
      int result;
      long long_result;
      char *string;
      char *endptr = NULL;

      ASSERT( name );
      string = param( name );
      if( ! string ) {
            dprintf( D_CONFIG, "%s is undefined, using default value of %d\n",
                         name, default_value );
            if ( use_default ) {
                  value = default_value;
            }
            return false;
      }

      long_result = strtol(string,&endptr,10);
      result = long_result;

      ASSERT(endptr);
      if( endptr != string ) {
            while( isspace(*endptr) ) {
                  endptr++;
            }
      }
      bool valid = (endptr != string && *endptr == '\0');

      if( !valid ) {
            EXCEPT( "%s in the condor configuration is not an integer (%s)."
                    "  Please set it to an integer in the range %d to %d"
                    " (default %d).",
                    name, string, min_value, max_value, default_value );
      }
      else if( (long)result != long_result ) {
            EXCEPT( "%s in the condor configuration is out of bounds for"
                        " an integer (%s)."
                        "  Please set it to an integer in the range %d to %d"
                        " (default %d).",
                        name, string, min_value, max_value, default_value );
      }
      else if ( check_ranges  &&  ( result < min_value )  ) {
            EXCEPT( "%s in the condor configuration is too low (%s)."
                        "  Please set it to an integer in the range %d to %d"
                        " (default %d).",
                        name, string, min_value, max_value, default_value );
      }
      else if ( check_ranges  && ( result > max_value )  ) {
            EXCEPT( "%s in the condor configuration is too high (%s)."
                        "  Please set it to an integer in the range %d to %d"
                        " (default %d).",
                        name, string, min_value, max_value, default_value );
      }
      free( string );

      value = result;
      return true;
}


/*
** Return the integer value associated with the named paramter.
** If the value is not defined or not a valid integer, then
** return the default_value argument.  The min_value and max_value
** arguments are optional and default to MININT and MAXINT.
*/

int
param_integer( const char *name, int default_value,
                     int min_value, int max_value )
{
      int result;

      param_integer( name, result, true, default_value,
                           true, min_value, max_value );
      return result;
}

int param_integer_c( const char *name, int default_value,
                                 int min_value, int max_value)
{
      return param_integer( name, default_value, min_value, max_value );
}

// require that the attribute I'm looking for is defined in the config file.
char* param_or_except(const char *attr)
{
      char *tmp = NULL;

      tmp = param(attr);
      if (tmp == NULL || strlen(tmp) <= 0) {
            EXCEPT("Please define config file entry to non-null value: %s", attr);
      }

      return tmp;
}


/*
 * Return the [single precision] floating point value associated with the named
 * parameter.  If the value is not defined or not a valid float, then return
 * the default_value argument.  The min_value and max_value arguments are
 * optional and default to DBL_MIN and DBL_MAX.
 */

double
param_double( const char *name, double default_value,
                     double min_value, double max_value )
{
      double result;
      char *string;
      char *endptr = NULL;

      ASSERT( name );
      string = param( name );
      if( ! string ) {
            dprintf( D_CONFIG, "%s is undefined, using default value of %f\n",
                         name, default_value );
            return default_value;
      }

      result = strtod(string,&endptr);

      ASSERT(endptr);
      if( endptr != string ) {
            while( isspace(*endptr) ) {
                  endptr++;
            }
      }
      bool valid = (endptr != string && *endptr == '\0');

      if( !valid ) {
            EXCEPT( "%s in the condor configuration is not a valid floating point number (%s)."
                    "  Please set it to a number in the range %lg to %lg"
                    " (default %lg).",
                    name, string, min_value, max_value, default_value );
      }
      else if( result < min_value ) {
            EXCEPT( "%s in the condor configuration is too low (%s)."
                    "  Please set it to a number in the range %lg to %lg"
                    " (default %lg).",
                    name, string, min_value, max_value, default_value );
      }
      else if( result > max_value ) {
            EXCEPT( "%s in the condor configuration is too high (%s)."
                    "  Please set it to a number in the range %lg to %lg"
                    " (default %lg).",
                    name, string, min_value, max_value, default_value );
      }
      free( string );
      return result;
}

/*
** Return the boolean value associated with the named paramter.
** The parameter value is expected to be set to the string
** "TRUE" or "FALSE" (no quotes, case insensitive).
** If the value is not defined or not a valid, then
** return the default_value argument.
*/

bool
param_boolean( const char *name, const bool default_value, bool do_log )
{
      bool result;
      char *string;
      char *endptr;
      bool valid = true;

      ASSERT( name );
      string = param( name );
      if (!string) {
            if (do_log) {
                  dprintf( D_CONFIG, "%s is undefined, using default value of %s\n",
                               name, default_value ? "True" : "False" );
            }
            return default_value;
      }

      endptr = string;
      if( strncasecmp(endptr,"true",4) == 0 ) {
            endptr+=4;
            result = true;
      }
      else if( strncasecmp(endptr,"1",1) == 0 ) {
            endptr+=1;
            result = true;
      }
      else if( strncasecmp(endptr,"false",5) == 0 ) {
            endptr+=5;
            result = false;
      }
      else if( strncasecmp(endptr,"0",1) == 0 ) {
            endptr+=1;
            result = false;
      }
      else {
            valid = false;
      }

      while( isspace(*endptr) ) {
            endptr++;
      }
      if( *endptr != '\0' ) {
            valid = false;
      }

      if( !valid ) {
            EXCEPT( "%s in the condor configuration  is not a valid boolean (\"%s\")."
                    "  Please set it to True or False (default is %s)",
                    name, string, default_value ? "True" : "False" );
      }

      free( string );
      
      return result;
}

bool
param_boolean_expr( const char *name, bool default_value, ClassAd const *me, ClassAd const *target )
{
      char *expr;
      bool value = default_value;

      ASSERT( name );
      expr = param( name );
      if( ! expr ) {
            dprintf( D_CONFIG, "%s is undefined, using default value of %s\n",
                         name, default_value ? "True" : "False" );
            return default_value;
      }

      if( *expr ) {
            ClassAd rhs;
            if( me ) {
                  rhs = *me;
            }

            if( !rhs.AssignExpr( name, expr ) ) {
                  EXCEPT("Invalid expression for %s (%s) in config file.",
                         name, expr);
            }

            int int_value = value;
            if( !rhs.EvalBool(name,target,int_value) ) {
                  EXCEPT("Invalid result (not a boolean) for %s (%s) "
                         "in condor configuration.",
                         name, expr );
            }
            value = (int_value != 0);
      }
      free( expr );

      return value;
}

char *
macro_expand( const char *str )
{
      return( expand_macro(str, ConfigTab, TABLESIZE) );
}

/*
** Same as param_boolean but for C -- returns 0 or 1
** The parameter value is expected to be set to the string
** "TRUE" or "FALSE" (no quotes, case insensitive).
** If the value is not defined or not a valid, then
** return the default_value argument.
*/
extern "C" int
param_boolean_int( const char *name, int default_value )
{
    bool default_bool;

    default_bool = default_value == 0 ? false : true;
    return param_boolean(name, default_bool) ? 1 : 0;
}

// Note that the line_number can be -1 if the filename isn't a real
// filename, but something like <Internal> or <Environment>
bool param_get_location(
      const char *parameter,
      MyString  &filename,
      int       &line_number)
{
      bool found_it;

      if (parameter != NULL && extra_info != NULL) {
            found_it = extra_info->GetParam(parameter, filename, line_number);
      } else {
            found_it = false;
      }
      return found_it;
}

void
reinsert_specials( char* host )
{
      static unsigned int reinsert_pid = 0;
      static unsigned int reinsert_ppid = 0;
      static bool warned_no_user = false;
      char buf[40];

      if( tilde ) {
            insert( "tilde", tilde, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("tilde");
      }
      if( host ) {
            insert( "hostname", host, ConfigTab, TABLESIZE );
      } else {
            insert( "hostname", my_hostname(), ConfigTab, TABLESIZE );
      }
      insert( "full_hostname", my_full_hostname(), ConfigTab, TABLESIZE );
      insert( "subsystem", get_mySubSystem()->getName(), ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("hostname");
      extra_info->AddInternalParam("full_hostname");
      extra_info->AddInternalParam("subsystem");

      // Insert login-name for our real uid as "username".  At the time
      // we're reading in the config source, the priv state code is not
      // initialized, so our euid will always be the same as our ruid.
      char *myusernm = my_username();
      if( myusernm ) {
            insert( "username", myusernm, ConfigTab, TABLESIZE );
            free(myusernm);
            myusernm = NULL;
            extra_info->AddInternalParam("username");
      } else {
            if( ! warned_no_user ) {
                  dprintf( D_ALWAYS, "ERROR: can't find username of current user! "
                               "BEWARE: $(USERNAME) will be undefined\n" );
                  warned_no_user = true;
            }
      }

      // Insert real-uid and real-gid as "real_uid" and "real_gid".
      // Now these values are meaningless on Win32, but leaving
      // them undefined can be undesireable, and setting them
      // to "0" could be dangerous (that is root uid on unix),
      // so we set them to something....
      {
            uid_t myruid;
            gid_t myrgid;
#ifdef WIN32
                  // Hmmm...
            myruid = 666;
            myrgid = 666;
#else
            myruid = getuid();
            myrgid = getgid();
#endif
            sprintf(buf,"%u",myruid);
            insert( "real_uid", buf, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("real_uid");
            sprintf(buf,"%u",myrgid);
            insert( "real_gid", buf, ConfigTab, TABLESIZE );
            extra_info->AddInternalParam("real_gid");
      }
            
      // Insert values for "pid" and "ppid".  Use static values since
      // this is expensive to re-compute on Windows.
      // Note: we have to resort to ifdef WIN32 junk even though
      // DaemonCore can nicely give us this information.  We do this
      // because the config code is used by the tools as well as daemons.
      if (!reinsert_pid) {
#ifdef WIN32
            reinsert_pid = ::GetCurrentProcessId();
#else
            reinsert_pid = getpid();
#endif
      }
      sprintf(buf,"%u",reinsert_pid);
      insert( "pid", buf, ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("pid");
      if ( !reinsert_ppid ) {
#ifdef WIN32
            CSysinfo system_hackery;
            reinsert_ppid = system_hackery.GetParentPID(reinsert_pid);
#else
            reinsert_ppid = getppid();
#endif
      }
      sprintf(buf,"%u",reinsert_ppid);
      insert( "ppid", buf, ConfigTab, TABLESIZE );
      insert( "ip_address", my_ip_string(), ConfigTab, TABLESIZE );
      extra_info->AddInternalParam("ppid");
      extra_info->AddInternalParam("ip_address");
}


void
config_insert( const char* attrName, const char* attrValue )
{
      if( ! (attrName && attrValue) ) {
            return;
      }
      insert( attrName, attrValue, ConfigTab, TABLESIZE );
}


void
check_params()
{
#if defined( HPUX )
            // Only on HPUX does this check matter...
      char* tmp;
      if( !(tmp = param("ARCH")) ) {
                  // Arch isn't defined.  That means the user didn't define
                  // it _and_ the special file we use that maps workstation
                  // models to CPU types doesn't exist either.  Print a
                  // verbose message and exit.  -Derek Wright 8/14/98
            fprintf( stderr, "ERROR: %s must know if you are running "
                         "on an HPPA1 or an HPPA2 CPU.\n",
                         myDistro->Get() );
            fprintf( stderr, "Normally, we look in %s for your model.\n",
                         "/opt/langtools/lib/sched.models" );
            fprintf( stderr, "This file lists all HP models and the "
                         "corresponding CPU type.  However,\n" );
            fprintf( stderr, "this file does not exist on your machine "
                         "or your model (%s)\n", sysapi_uname_arch() );
            fprintf( stderr, "was not listed.  You should either explicitly "
                         "set the ARCH parameter\n" );
            fprintf( stderr, "in your config source, or install the "
                         "sched.models file.\n" );
            exit( 1 );
      } else {
            free( tmp );
      }
#endif
}

/* Begin code for runtime support for modifying a daemon's config source.
   See condor_daemon_core.V6/README.config for more details. */

static StringList PersistAdminList;

class RuntimeConfigItem {
public:
      RuntimeConfigItem() : admin(NULL), config(NULL) { }
      ~RuntimeConfigItem() { if (admin) free(admin); if (config) free(config); }
      void initialize() { admin = config = NULL; }
      char *admin;
      char *config;
};

#include "extArray.h"

static ExtArray<RuntimeConfigItem> rArray;

static MyString toplevel_persistent_config;

/*
  we want these two bools to be global, and only initialized on
  startup, so that folks can't play tricks and change these
  dynamically.  for example, if a site enables runtime but not
  persistent configs, we can't allow someone to set
  "ENABLE_PERSISTENT_CONFIG" with a condor_config_val -rset.
  therefore, we only read these once, before we look at any of the
  dynamic config source, to make sure we're happy.  this means it
  requires a restart to change any of these, but i think that's a
  reasonable burden on admins, considering the potential security
  implications.  -derek 2006-03-17
*/
static bool enable_runtime;
static bool enable_persistent;

static void
init_dynamic_config()
{
      static bool initialized = false;

      if( initialized ) {
                  // already have a value, we're done
            return;
      }

      enable_runtime = param_boolean( "ENABLE_RUNTIME_CONFIG", false );
      enable_persistent = param_boolean( "ENABLE_PERSISTENT_CONFIG", false );
      initialized = true;

      if( !enable_persistent ) {
                  // we don't want persistent configs, leave the toplevel blank
            return;
      }

      char* tmp;

            // if we're using runtime config, try a subsys-specific config
            // knob for the root location
      MyString filename_parameter;
      filename_parameter.sprintf( "%s_CONFIG", get_mySubSystem()->getName() );
      tmp = param( filename_parameter.Value() );
      if( tmp ) {
            toplevel_persistent_config = tmp;
            free( tmp );
            return;
      }

      tmp = param( "PERSISTENT_CONFIG_DIR" );

      if( !tmp ) {
            if ( get_mySubSystem()->isClient( ) || !have_config_source ) {
                        /*
                           we are just a tool, not a daemon.
                           or, we were explicitly told we don't have
                           the usual config sources.
                           thus it is not imperative that we find what we
                           were looking for...
                        */
                  return;
            } else {
                        // we are a daemon.  if we fail, we must exit.
                  fprintf( stderr, "%s error: ENABLE_PERSISTENT_CONFIG is TRUE, "
                               "but neither %s nor PERSISTENT_CONFIG_DIR is "
                               "specified in the configuration file\n",
                               myDistro->GetCap(), filename_parameter.Value() );
                  exit( 1 );
            }
      }
      toplevel_persistent_config.sprintf( "%s%c.config.%s", tmp,
                                                            DIR_DELIM_CHAR,
                                                            get_mySubSystem()->getName() );
      free(tmp);
}


/*
** Caller is responsible for allocating admin and config with malloc.
** Caller should not free admin and config after the call.
*/

#define ABORT \
      if(admin) { free(admin); } \
      if(config) { free(config); } \
      set_priv(priv); \
      return -1

int
set_persistent_config(char *admin, char *config)
{
      int fd, rval;
      char *tmp;
      MyString filename;
      MyString tmp_filename;
      priv_state priv;

      if (!admin || !admin[0] || !enable_persistent) {
            if (admin)  { free(admin);  }
            if (config) { free(config); }
            return -1;
      }

      // make sure top level config source is set
      init_dynamic_config();
      if( ! toplevel_persistent_config.Length() ) {
            EXCEPT( "Impossible: programmer error: toplevel_persistent_config "
                        "is 0-length, but we already initialized, enable_persistent "
                        "is TRUE, and set_persistent_config() has been called" );
      }

      priv = set_root_priv();
      if (config && config[0]) {    // (re-)set config
                  // write new config to temporary file
            filename.sprintf( "%s.%s", toplevel_persistent_config.Value(), admin );
            tmp_filename.sprintf( "%s.tmp", filename.Value() );
            do {
                  unlink( tmp_filename.Value() );
                  fd = safe_open_wrapper( tmp_filename.Value(), O_WRONLY|O_CREAT|O_EXCL, 0644 );
            } while (fd == -1 && errno == EEXIST);
            if( fd < 0 ) {
                  dprintf( D_ALWAYS, "safe_open_wrapper(%s) returned %d '%s' (errno %d) in "
                               "set_persistent_config()\n", tmp_filename.Value(),
                               fd, strerror(errno), errno );
                  ABORT;
            }
            if (write(fd, config, strlen(config)) != (ssize_t)strlen(config)) {
                  dprintf( D_ALWAYS, "write() failed with '%s' (errno %d) in "
                               "set_persistent_config()\n", strerror(errno), errno );
                  ABORT;
            }
            if (close(fd) < 0) {
                  dprintf( D_ALWAYS, "close() failed with '%s' (errno %d) in "
                               "set_persistent_config()\n", strerror(errno), errno );
                  ABORT;
            }
            
                  // commit config changes
            if (rotate_file(tmp_filename.Value(), filename.Value()) < 0) {
                  dprintf( D_ALWAYS, "rotate_file(%s,%s) failed with '%s' "
                               "(errno %d) in set_persistent_config()\n",
                               tmp_filename.Value(), filename.Value(),
                               strerror(errno), errno );
                  ABORT;
            }
      
            // update admin list in memory
            if (!PersistAdminList.contains(admin)) {
                  PersistAdminList.append(admin);
            } else {
                  free(admin);
                  free(config);
                  set_priv(priv);
                  return 0;         // if no update is required, then we are done
            }

      } else {                            // clear config

            // update admin list in memory
            PersistAdminList.remove(admin);
            if (config) {
                  free(config);
                  config = NULL;
            }
      }           

      // update admin list on disk
      tmp_filename.sprintf( "%s.tmp", toplevel_persistent_config.Value() );
      do {
            unlink( tmp_filename.Value() );
            fd = safe_open_wrapper( tmp_filename.Value(), O_WRONLY|O_CREAT|O_EXCL, 0644 );
      } while (fd == -1 && errno == EEXIST);
      if( fd < 0 ) {
            dprintf( D_ALWAYS, "safe_open_wrapper(%s) returned %d '%s' (errno %d) in "
                         "set_persistent_config()\n", tmp_filename.Value(),
                         fd, strerror(errno), errno );
            ABORT;
      }
      const char param[] = "RUNTIME_CONFIG_ADMIN = ";
      if (write(fd, param, strlen(param)) != (ssize_t)strlen(param)) {
            dprintf( D_ALWAYS, "write() failed with '%s' (errno %d) in "
                         "set_persistent_config()\n", strerror(errno), errno );
            ABORT;
      }
      PersistAdminList.rewind();
      bool first_time = true;
      while( (tmp = PersistAdminList.next()) ) {
            if (!first_time) {
                  if (write(fd, ", ", 2) != 2) {
                        dprintf( D_ALWAYS, "write() failed with '%s' (errno %d) in "
                                     "set_persistent_config()\n", strerror(errno), errno );
                        ABORT;
                  }
            } else {
                  first_time = false;
            }
            if (write(fd, tmp, strlen(tmp)) != (ssize_t)strlen(tmp)) {
                  dprintf( D_ALWAYS, "write() failed with '%s' (errno %d) in "
                               "set_persistent_config()\n", strerror(errno), errno );
                  ABORT;
            }
      }
      if (write(fd, "\n", 1) != 1) {
            dprintf( D_ALWAYS, "write() failed with '%s' (errno %d) in "
                         "set_persistent_config()\n", strerror(errno), errno );
            ABORT;
      }
      if (close(fd) < 0) {
            dprintf( D_ALWAYS, "close() failed with '%s' (errno %d) in "
                         "set_persistent_config()\n", strerror(errno), errno );
            ABORT;
      }
      
      rval = rotate_file( tmp_filename.Value(),
                                    toplevel_persistent_config.Value() );
      if (rval < 0) {
            dprintf( D_ALWAYS, "rotate_file(%s,%s) failed with '%s' (errno %d) "
                         "in set_persistent_config()\n", tmp_filename.Value(),
                         filename.Value(), strerror(errno), errno );
            ABORT;
      }

      // if we removed a config, then we should clean up by removing the file(s)
      if (!config || !config[0]) {
            filename.sprintf( "%s.%s", toplevel_persistent_config.Value(), admin );
            unlink( filename.Value() );
            if (PersistAdminList.number() == 0) {
                  unlink( toplevel_persistent_config.Value() );
            }
      }

      set_priv( priv );
      free( admin );
      if (config) { free( config ); }
      return 0;
}


int
set_runtime_config(char *admin, char *config)
{
      int i;

      if (!admin || !admin[0] || !enable_runtime) {
            if (admin)  { free(admin);  }
            if (config) { free(config); }
            return -1;
      }

      if (config && config[0]) {
            for (i=0; i <= rArray.getlast(); i++) {
                  if (strcmp(rArray[i].admin, admin) == MATCH) {
                        free(admin);
                        free(rArray[i].config);
                        rArray[i].config = config;
                        return 0;
                  }
            }
            rArray[i].admin = admin;
            rArray[i].config = config;
      } else {
            for (i=0; i <= rArray.getlast(); i++) {
                  if (strcmp(rArray[i].admin, admin) == MATCH) {
                        free(admin);
                        if (config) free(config);
                        free(rArray[i].admin);
                        free(rArray[i].config);
                        rArray[i] = rArray[rArray.getlast()];
                        rArray[rArray.getlast()].initialize();
                        rArray.truncate(rArray.getlast()-1);
                        return 0;
                  }
            }
      }

      return 0;
}


extern "C" {

static int
process_persistent_configs()
{
      char *tmp = NULL;
      int rval;
      bool processed = false;

      if( access( toplevel_persistent_config.Value(), R_OK ) == 0 &&
            PersistAdminList.number() == 0 )
      {
            processed = true;

            rval = Read_config( toplevel_persistent_config.Value(), ConfigTab,
                                          TABLESIZE, EXPAND_LAZY, true, extra_info );
            if (rval < 0) {
                  dprintf( D_ALWAYS, "Configuration Error Line %d while reading "
                               "top-level persistent config source: %s\n",
                               ConfigLineNo, toplevel_persistent_config.Value() );
                  exit(1);
            }

            tmp = param ("RUNTIME_CONFIG_ADMIN");
            if (tmp) {
                  PersistAdminList.initializeFromString(tmp);
                  free(tmp);
            }
      }

      PersistAdminList.rewind();
      while ((tmp = PersistAdminList.next())) {
            processed = true;
            MyString config_source;
            config_source.sprintf( "%s.%s", toplevel_persistent_config.Value(),
                                             tmp );
            rval = Read_config( config_source.Value(), ConfigTab, TABLESIZE,
                                           EXPAND_LAZY, true, extra_info );
            if (rval < 0) {
                  dprintf( D_ALWAYS, "Configuration Error Line %d "
                               "while reading persistent config source: %s\n",
                               ConfigLineNo, config_source.Value() );
                  exit(1);
            }
      }
      return (int)processed;
}


static int
process_runtime_configs()
{
      int i, rval, fd;
      bool processed = false;

      for (i=0; i <= rArray.getlast(); i++) {
            processed = true;

            char* tmp_dir = temp_dir_path();
            ASSERT(tmp_dir);
            MyString tmp_file_tmpl = tmp_dir;
            free(tmp_dir);
            tmp_file_tmpl += "/cndrtmpXXXXXX";

            char* tmp_file = strdup(tmp_file_tmpl.Value());
            fd = condor_mkstemp( tmp_file );
            if (fd < 0) {
                  dprintf( D_ALWAYS, "condor_mkstemp(%s) returned %d, '%s' (errno %d) in "
                         "process_dynamic_configs()\n", tmp_file, fd,
                         strerror(errno), errno );
                  exit(1);
            }

            if (write(fd, rArray[i].config, strlen(rArray[i].config))
                  != (ssize_t)strlen(rArray[i].config)) {
                  dprintf( D_ALWAYS, "write failed with errno %d in "
                               "process_dynamic_configs\n", errno );
                  exit(1);
            }
            if (close(fd) < 0) {
                  dprintf( D_ALWAYS, "close failed with errno %d in "
                               "process_dynamic_configs\n", errno );
                  exit(1);
            }
            rval = Read_config( tmp_file, ConfigTab, TABLESIZE,
                                          EXPAND_LAZY, false, extra_info );
            if (rval < 0) {
                  dprintf( D_ALWAYS, "Configuration Error Line %d "
                               "while reading %s, runtime config: %s\n",
                               ConfigLineNo, tmp_file, rArray[i].admin );
                  exit(1);
            }
            unlink(tmp_file);
            free(tmp_file);
      }

      return (int)processed;
}


/*
** returns 1 if dynamic (runtime or persistent) configs were
** processed; 0 if no dynamic configs were defined, and -1 on error.
*/
static int
process_dynamic_configs()
{
      int per_rval = 0;
      int run_rval = 0;

      init_dynamic_config();

      if( enable_persistent ) {
            per_rval = process_persistent_configs();
      }

      if( enable_runtime ) {
            run_rval = process_runtime_configs();
      }

      if( per_rval < 0 || run_rval < 0 ) {
            return -1;
      }
      if( per_rval || run_rval ) {
            return 1;
      }
      return 0;
}

} // end of extern "C"

/* End code for runtime support for modifying a daemon's config source. */

Generated by  Doxygen 1.6.0   Back to index