/* ***************************************************************************** * * $RCSfile: readcfg.c,v $ * $Date: 1999/03/11 19:17:40 $ * $Source: /home/richard/Xml/RCS/readcfg.c,v $ * $Revision: 1.22 $ * $Author: richard $ * ***************************************************************************** * * Copyright 1998, Brown University and Richard Goerwitz * ***************************************************************************** * * This file has one entry point: readcfg(), which does the following: * * 1) Checks argv quickly for the -d option, and if it * finds this option, sets xmlparse_env.libdir to * * 2) Sets the various fields in the global xmlparse_env structure * according to compile-time defaults * * 3) Resets the various fields in xmlparse_env according to command- * line arguments (hence, command-line arguments override compiled * in defaults; note that -d was done in step 1) * * 4) Resets the various fields in xmlparse_env according to the * configuration file, if there is one (hence, configuration-file * directive override both compile-time defaults and arguments) * * Note that this routine assumes that main() has zeroed out the * xmlparse_env structure as follows: * * memset (&xmlparse_env, 0, sizeof (xmlparse_env)); * * Note also that, in addition to the main entry point, readcfg(), * there is also a reconfiguration routine that can be called at * run-time (see read_and_parse_configuration_file() below), just in * case this library is being used in a daemon. There is a signal * library in sigstuff.c that may be used to register this routine * as our SIGHUP handler. * ***************************************************************************** */ #include "general.h" #include "readcfg.h" #include "dtdutil.h" #include "errabort.h" #include "fileutil.h" #include "getmessage.h" #include "lockutil.h" #include "strutil.h" #include #include #ifdef HAVE_LOCALE_H # include "locale.h" #endif #ifndef USHRT_MAX # define USHRT_MAX 65535 #endif static xmlparse_environment *initialize_env (int, char **); static void parse_line (char *, size_t); static int get_int (char *); /* * readcfg: * * Generals: Reads compile-time defaults, command-line args, and * configuration information, and sets xmlparse accordingly. * * If readcfg is called with a nonnull second argument, parses what * command-line args it needs to (e.g., the libdir, name of * configuration file) in order to find the configuration file, then * tries to read in that configuration file and adjust default * settings accordingly. * * NOTE: If you want to reread the configuration file at run-time, * don't use readcfg(). Use read_and_parse_configuration_file() * instead. See the docs above. * * Returns the number of lines read from the config file. */ int readcfg (int argc, char **argv) { char *tmp; int c, old_opterr, old_optind; /* save getopt() state */ old_opterr = opterr; old_optind = optind; /* getopt() shouldn't print err msgs */ opterr = 0; /* while we're initializing, seed the random num generator */ srand (time (NULL)); /* If argv is nonnull, then parse the start-up argument list */ if (argv) { /* start at the beginning */ optind = old_optind; /* Do a quick scan of argv for -d */ xwrap (errdebug (3, "scanning argv for -d option (first pass)\n")); while ((c = getopt (argc, argv, "c:C:d:E:fhl:m:np:su:v")) != EOF) if (c == 'd') { xwrap (errdebug (3, "got -d option\n")); xwrap (errdebug (5, "setting xmlparse_env.libdir to: %s\n", optarg)); /* use optarg as new libdir */ if ((tmp = strdup (optarg)) == NULL) errabort (40, "malloc error in %s\n", "readcfg()"); if (xmlparse_env.libdir) free (xmlparse_env.libdir); xmlparse_env.libdir = tmp; } } /* initialize the global xmlparse_env structure according to * compile-time defaults */ initialize_env (argc, argv); /* now re-initialize the global xmlparse_env structure according to * command-line arguments */ if (argv) { /* start at the beginning, again */ optind = old_optind; /* Now do a full parse of argv */ xwrap (errdebug (3, "parsing command-line arguments (second pass)\n")); /* If you change arg 3 to getopt here, change it in main()! */ while ((c = getopt (argc, argv, "c:C:d:E:fhl:m:np:su:v")) != EOF) { switch (c) { case 'c': xwrap (errdebug (5, "re-setting config file to: %s\n", optarg)); /* use optarg as configuration file name */ if ((tmp = strdup (optarg)) == NULL) errabort (40, "malloc error in %s\n", "readcfg()"); free (xmlparse_env.config_filename); xmlparse_env.config_filename = tmp; break; case 'C': xwrap (errdebug (5, "setting catalog filename to: %s\n", optarg)); /* Set the name of our system SGML catalog file */ if ((tmp = strdup (optarg)) == NULL) errabort (40, "malloc error in %s\n", "readcfg()"); if (xmlparse_env.sgml_catalog_filenames != NULL) free (xmlparse_env.sgml_catalog_filenames); if (xmlparse_env.public_to_system_identifier_hashtable != NULL) rg_free_htable_and_data (xmlparse_env.public_to_system_identifier_hashtable); xmlparse_env.public_to_system_identifier_hashtable = NULL; xmlparse_env.sgml_catalog_filenames = tmp; break; case 'd': break; case 'E': /* Set the maximum errors/warnings to display for each file */ xwrap (errdebug (5, "re-setting max_errors to: %s\n", optarg)); if ((xmlparse_env.max_errors = get_int (optarg)) == -1) errabort (70, "invalid max errors setting (-E %s)\n", optarg); break; case 'f': /* Force attributes/elements in other namespaces to validate OK */ xwrap (errdebug (5, "forcing validate-OK for atts/elements in namespaces\n")); xmlparse_env.force_valid_namespaces = yes; break; case 'h': /* if the user wants help, bail out; let main() handle it */ xwrap (errdebug (5, "bailing out of readcfg()); user wants help\n")); opterr = old_opterr; optind = old_optind; return 0; case 'l': #ifdef XML_NODEBUG /* We weren't compiled with debugging enabled */ errwarn (6, "no debugging facilities\n"); #else /* Set the debug_level (higher = more info) */ xwrap (errdebug (5, "re-setting debug_level to: %s\n", optarg)); if ((xmlparse_env.debug_level = get_int (optarg)) == -1) errabort (70, "invalid debug_level (-l %s)\n", optarg); #endif /* XML_NODEBUG */ break; case 'm': xwrap (errdebug (5, "re-setting message_catalog file to: %s\n", optarg)); /* use optarg as configuration file name */ if (xmlparse_env.message_catalog != NULL) free_message_catalog (xmlparse_env.message_catalog); if (! (xmlparse_env.message_catalog = create_message_catalog (optarg))) errabort (71, "error opening message file, %s\n", optarg); break; case 'n': /* Turn on security; only resolve http: and urn: system ids */ xmlparse_env.no_local_files = yes; break; case 'p': xwrap (errdebug (5, "setting FPI resolution cmd string to: %s\n", optarg)); /* Set the name of our FPI resolution command */ if ((tmp = strdup (optarg)) == NULL) errabort (40, "malloc error in %s\n", "readcfg()"); if (xmlparse_env.fpi_resolution_cmd_string != NULL) free (xmlparse_env.fpi_resolution_cmd_string); xmlparse_env.fpi_resolution_cmd_string = tmp; break; case 's': /* Send errors/warnings to syslog() rather than stderr */ xmlparse_env.use_syslog = yes; break; case 'u': xwrap (errdebug (5, "setting URL resolution cmd string to: %s\n", optarg)); /* Set the name of our URL resolution command */ if ((tmp = strdup (optarg)) == NULL) errabort (40, "malloc error in %s\n", "readcfg()"); if (xmlparse_env.url_resolution_cmd_string != NULL) free (xmlparse_env.url_resolution_cmd_string); xmlparse_env.url_resolution_cmd_string = tmp; break; case 'v': /* if the user wants the version #, bail out; let main() deal */ xwrap (errdebug (5, "bailing out of readcfg()); user wants version #\n")); opterr = old_opterr; optind = old_optind; return 0; } } } /* Finally, reinitialize xmlparse_env according to configuration * file directives. Note that the 0 below (arg 1) indicates that a * signal (e.g., SIGHUP) didn't trigger this call. */ read_and_parse_configuration_file (0); /* restore getopt() state (see the other exit point above) */ opterr = old_opterr; optind = old_optind; /* finished (cf. zero return value, which means -h arg was encountered) */ return 1; } /* * initialize_env: * * Initialize master xmlparse_env structure to some reasonable * values. E.g., set xmlparse_env.libdir to its compile-time * default, DEFAULT_XMLPARSE_LIBDIR; set xmlparse_env.program_name * to the file component of argv[0]. */ static struct xmlparse_environment * initialize_env (int argc, char **argv) { size_t len; char *p, *p2, *ext; char pathbuf[MAXPATHLEN]; #ifndef DEFAULT_XMLPARSE_LIBDIR # error "DEFAULT_XMLPARSE_LIBDIR must be defined in Makefile" #endif #ifndef DEFAULT_CONFIG_FILENAME # error "DEFAULT_CONFIG_FILENAME must be defined in Makefile" #endif #ifndef DEFAULT_URL_RESOLUTION_CMD_STRING # error "DEFAULT_URL_RESOLUTION_CMD_STRING must be defined in Makefile" #endif #ifndef DEFAULT_SGML_CATALOG_FILES # error "DEFAULT_SGML_CATALOG_FILES must be defined in Makefile" #endif if (! xmlparse_env.libdir) xmlparse_env.libdir = strdup (DEFAULT_XMLPARSE_LIBDIR); if (! xmlparse_env.config_filename) xmlparse_env.config_filename = strdup (DEFAULT_CONFIG_FILENAME); if (! xmlparse_env.url_resolution_cmd_string) xmlparse_env.url_resolution_cmd_string = strdup (DEFAULT_URL_RESOLUTION_CMD_STRING); /* if "yes" will only resolve http: and urn: sysids */ xmlparse_env.no_local_files = no; /* don't (by default, at least) send system errors and warnings to syslog */ xmlparse_env.use_syslog = no; /* if 'yes' will assume all attributes/GIs in namespaces are valid */ xmlparse_env.force_valid_namespaces = no; /* should be null; no compile-time default */ if (xmlparse_env.fpi_resolution_cmd_string != NULL) errabort (42, "unexpectedly nonnull FPI resolution string in %s\n", "initialize_env()"); /* colon-separated list of SGML catalog files on the local host */ if (! xmlparse_env.sgml_catalog_filenames) if ((p = getenv ("SGML_CATALOG_FILES"))) xmlparse_env.sgml_catalog_filenames = strdup (p); else xmlparse_env.sgml_catalog_filenames = strdup (DEFAULT_SGML_CATALOG_FILES); /* What's the maximum number of errors/warnings we should display for each file? */ xmlparse_env.max_errors = 200; /* xmlparse_env.program_name is used by errabort.c */ if (! xmlparse_env.program_name) { p2 = NULL; if (argv) { p2 = xmlparse_env.program_name = argv[0]; for (p = argv[0]; *p != '\0'; p++) if (*p == '/') p2 = p + 1; } if (p2 == NULL || *p2 == '\0') p2 = "xmlparse"; if ((p = strdup (p2)) == NULL) errabort (40, "malloc error in %s\n", "initialize_env()"); xmlparse_env.program_name = p; } #ifdef HAVE_SETLOCALE if ((ext = setlocale (LC_ALL, NULL))) if (strlen (DEFAULT_MESSAGE_FILENAME) <= (MAXPATHLEN - strlen (ext) - 2)) { /* concatenate base msg filename w/ ext (mark where ext begins) */ strcpy (pathbuf, DEFAULT_MESSAGE_FILENAME "."); len = strlen (pathbuf); strcat (pathbuf, ext); if ((xmlparse_env.message_catalog = create_message_catalog (pathbuf))) goto success; else if (strcoll (ext, "C") != 0) { /* If locale is en-US, try en-us as well. Note: we marked * where ext begins above. Also: don't try this for "C". */ downcase (&pathbuf[len]); if ((xmlparse_env.message_catalog = create_message_catalog (pathbuf))) goto success; } } #endif /* message catalog stuff (used to support different languages) */ if (! (xmlparse_env.message_catalog = create_message_catalog (DEFAULT_MESSAGE_FILENAME))) errabort (71, "error opening message file, %s\n", DEFAULT_MESSAGE_FILENAME); success: return &xmlparse_env; } /* * read_and_parse_configuration_file: * * Reads and parses the configuration file. See readcfg() above. * Use this function at run-time to re-initialize after altering the * config file. E.g., install it as a SIGHUP handler if you are * using these routines within a daemon. */ RETSIGTYPE read_and_parse_configuration_file (int sig) { FILE *f; char *line; int our_errno, lineno; char pathbuf[MAXPATHLEN]; /* What are we doing? */ xwrap (errdebug (5, "parsing config file, %s\n", xmlparse_env.config_filename)); /* Errno is often set to volatile, and may be reset asynchronously. * So it's never a bad idea to save and restore it in signal * handlers (and this routine MAY be used as a SIGHUP handler). */ our_errno = errno; if (sig > 0) xwrap (errdebug (3, "caught signal %d; reinitializing\n", sig)); /* Keep track of where we were */ xwrap (errdebug (5, "saving current dir\n")); if (getcwd (pathbuf, MAXPATHLEN) == NULL) errabort (80, "can't determine current working directory\n"); /* Try to cd into the default (-d) libdir */ xwrap (errdebug (5, "moving into libdir, %s\n", xmlparse_env.libdir)); if (xmlparse_env.libdir) if (chdir (xmlparse_env.libdir) < 0) { errwarn (72, "can't cd to %s; using cwd\n", xmlparse_env.libdir); free (xmlparse_env.libdir); xmlparse_env.libdir = strdup ("."); } lineno = 0; /* open configuration file */ xwrap (errdebug (5, "opening config file, %s\n", xmlparse_env.config_filename)); if ((f = fopen_and_readlock (xmlparse_env.config_filename)) == NULL) errwarn (74, "can't open cfg file, %s; ignoring\n", xmlparse_env.config_filename); else { /* Parse configuration file line-by-line */ xwrap (errdebug (5, "reading config file\n")); while ((line = getline (f, xmlparse_env.config_filename, &lineno))) parse_line (line, lineno); fclose_and_unlock (f); } /* Leave libdir; return to previous working dir */ xwrap (errdebug (5, "restoring current dir\n")); if (chdir (pathbuf) < 0) errabort (73, "can't cd to %s\n", pathbuf); xwrap (errdebug (3, "%d configuration-file lines read\n", lineno)); errno = our_errno; return; } #define unknown_keyword() { \ errwarn (75, "unknown keyword, line %d of config file; ignoring\n", lineno); \ } /* * parse_line: * * Parse a line of the configuration file into keyword and value, * and set values in xmlparse_env appropriately. E.g., "debug_level * 7" causes xmlparse_env.debug_level to be set to (int)7. */ static void parse_line (char *line, size_t lineno) { int i; char *new_line; char *p, *p2, *str; /* What are we doing? */ xwrap (errdebug (5, "Parsing line %d\n", lineno)); if ((new_line = malloc (strlen (line) + 2)) == NULL) errabort (40, "malloc error in %s\n", "parse_line()"); strcpy (new_line, line); /* take out leading whitespace */ for (p = new_line; isspace (*p); p++) continue; xwrap (errdebug (7, "stripped out leading whitespace: %s\n", p)); /* take out trailing #comments */ trimhash_no_quotes_or_backslash (p); trim (p, " \r\n\t\f"); xwrap (errdebug (7, "stripped out trailing comments: %s\n", p)); switch (*p) { case '\0': xwrap (errdebug (7, "line has nothing of interest; ignoring\n")); break; case 'd': if (strncmp (p, "debug_level", 11) == 0) { #ifdef XML_NODEBUG /* We weren't compiled with debugging enabled */ errwarn (7, "debugging facilities not compiled in\n"); #else xwrap (errdebug (7, "found keyword, %s\n", p)); if ((i = get_int (p + 11)) != -1) xmlparse_env.debug_level = i; else { for (p2 = p + 11; isspace (*p2); p2++); errwarn (76, "bad debug_level, %s, line %d in config file; ignoring\n", p2, lineno); } #endif /* XML_NODEBUG */ } else unknown_keyword(); break; case 'f': if (strncmp (p, "fpi_resolution_cmd_string", 25) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 25)) == NULL) { for (p2 = p + 25; isspace (*p2); p2++); errwarn (76, "bad fpi_resolution_cmd_string, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (xmlparse_env.fpi_resolution_cmd_string != NULL) free (xmlparse_env.fpi_resolution_cmd_string); xmlparse_env.fpi_resolution_cmd_string = str; /* no need to free str here */ } } else unknown_keyword(); break; case 'm': if (strncmp (p, "max_errors", 10) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((i = get_int (p + 10)) != -1) xmlparse_env.max_errors = i; else { for (p2 = p + 10; isspace (*p2); p2++); errwarn (76, "bad max_errors, %s, line %d in config file; ignoring\n", p2, lineno); } } else if (strncmp (p, "message_catalog_filename", 24) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 24)) == NULL) { for (p2 = p + 24; isspace (*p2); p2++); errwarn (76, "bad message_filename value, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (xmlparse_env.message_catalog != NULL) free_message_catalog (xmlparse_env.message_catalog); if (! (xmlparse_env.message_catalog = create_message_catalog (str))) errabort (71, "error opening %s\n", str); free (str); } } else unknown_keyword(); break; case 'n': if (strncmp (p, "no_local_files", 14) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 14)) == NULL) { oops_no_local_files: for (p2 = p + 14; isspace (*p2); p2++); errwarn (76, "bad no_local_files value, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (*str == '\0' || strcoll (str, "yes") == 0) xmlparse_env.no_local_files = yes; else if (strcoll (str, "no") == 0) xmlparse_env.no_local_files = no; else { free (str); goto oops_no_local_files; } free (str); } } case 's': if (strncmp (p, "sgml_catalog_filenames", 21) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 21)) == NULL) { for (p2 = p + 21; isspace (*p2); p2++); errwarn (76, "bad sgml_catalog_filenames, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (xmlparse_env.sgml_catalog_filenames != NULL) free (xmlparse_env.sgml_catalog_filenames); if (xmlparse_env.public_to_system_identifier_hashtable != NULL) rg_free_htable_and_data (xmlparse_env.public_to_system_identifier_hashtable); xmlparse_env.public_to_system_identifier_hashtable = NULL; xmlparse_env.sgml_catalog_filenames = str; /* no need to free str here */ } } else unknown_keyword(); break; case 'u': if (strncmp (p, "use_syslog", 10) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 10)) == NULL) { oops_use_syslog: for (p2 = p + 10; isspace (*p2); p2++); errwarn (76, "bad use_syslog value, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (*str == '\0' || strcoll (str, "yes") == 0) xmlparse_env.use_syslog = yes; else if (strcoll (str, "no") == 0) xmlparse_env.use_syslog = no; else { free (str); goto oops_use_syslog; } free (str); } } else if (strncmp (p, "url_resolution_cmd_string", 25) == 0) { xwrap (errdebug (7, "found keyword, %s\n", p)); if ((str = get_string (p + 25)) == NULL) { for (p2 = p + 25; isspace (*p2); p2++); errwarn (76, "bad url_resolution_cmd_string, %s, line %d in config file; ignoring\n", p2, lineno); } else { if (xmlparse_env.url_resolution_cmd_string != NULL) free (xmlparse_env.url_resolution_cmd_string); xmlparse_env.url_resolution_cmd_string = str; /* no need to free str here */ } } else unknown_keyword(); break; default: unknown_keyword(); break; } free (new_line); /* What did we just do? */ xwrap (errdebug (5, "Parsed line %d in config file\n", lineno)); return; } /* * get_int: * * Convert string pointed to by line into an integer. Return that * integer if the conversion is successful; otherwise, return -1. * Negative numbers are no-go; so are numbers greater than * USHRT_MAX. */ static int get_int (char *line) { long int i; char **endptr; while (isspace (*line)) line++; xwrap (errdebug (7, "looking for a positive integer in: %s\n", line)); /* set debug level (higher = more output) */ errno = 0; endptr = &line; i = strtol (line, endptr, 10); if (errno == 0 && **endptr == '\0') { if (i < 0) errwarn (77, "ignoring negative integer, %d\n", i); else if (i > USHRT_MAX) errwarn (78, "integer too big, %d; ignoring\n", i); else { xwrap (errdebug (5, "found integer, %d\n", (int)i)); return (int )i; } } /* error of some kind */ return -1; }