/* * Routines to access and parse standard text config files. * $Id: config.c 1.2 Wed, 19 Mar 1997 12:44:53 -0500 dyfet $ * Copyright (c) 1997 by Tycho Softworks. * For conditions of distribution and use see product license. * * Abstract: * These routines are used to open and parse human readable '.conf' * files, such as those which may be stored in the /etc directory. * The .conf file is parsed as a sectioned text file, with the name * for each logical section appearing in []'s. Entries within each * section are typically in the format 'keyword = value', though * there are exceptions for multi-line fixed size lists, in * the form 'keyword = { list }', and repeated lines. Comments may * also appear within .conf files. * * Functions: * sys_config() - find and open a /etc or /etc/prior .conf file. * open_config() - open any text file as a config file. * read_config() - read a keyword = value pair from current section. * seek_config() - seek a named [] section within the config file. * get_config() - tests current input for a specified keyword. * usr_config() - user specific resource config file. */ #include #include #include #include #include /* * When searching for 'system' .conf files, which are normally held in * /etc, also search in /etc/prior. This is used so that a human * readable .conf file can momentarily be moved into '/etc/prior' and * still be usable while a GUI system management program is in the middle * building a new .conf file. */ #ifndef CFGPATH #define CFGPATH "/etc:/etc/prior" #endif /* * Maximum working space for single or multi-line input records being * parsed. */ #ifndef LBUF #define LBUF 1024 #endif #ifndef SUFFIX #define SUFFIX ".conf" #endif /* * Open a system .conf file, as found in the system config directories. * * Abstract: * This function finds a .conf file in either the /etc or /etc/prior * directory. /etc/prior is searched if the current .conf file is * not found (in /etc), as may happen if it is in the middle of * being re-built by a management application. This provides an * initial function which may be used to open most .conf files. * * Paramaters: * cfg_name - 'base' filename of system .conf file to open. * * Returns: * pointer to active CONFIG object for specified filename. * * Exceptions: * If the file is not found, a NULL pointer is returned. */ CONFIG *sys_config(const char *cfg_name) { char cfgname[PATH_MAX + 1]; strcpy(cfgname, cfg_name); strcat(cfgname, SUFFIX); return open_config(search(CFGPATH, cfgname)); } /* * Open any specified filename as a .conf file. * * Abstract: * This function opens the specified file as a 'config' file for use * in config file parsing routines. Any filename may be specified * and opened as a config file with this routine. * * Paramaters: * config_name - full pathname of a .conf file to open. * * Returns: * pointer to a newly allocated CONFIG parsing object for the * specified filename. * * Exceptions: * If the file is not found, a NULL pointer is returned. */ CONFIG *open_config(const char *config_name) { CONFIG *new; char *env; if(NULL == (new = (CONFIG *)malloc(sizeof(CONFIG) + LBUF))) return NULL; if(NULL == (new->cfg_fp = fopen(config_name, "r"))) { free(new); return NULL; } new->cfg_flag = FALSE; return new; } /* * Close an open config file and destroy the CONFIG parser object. */ void close_config(CONFIG *cfg) { if(!cfg) return; fclose(cfg->cfg_fp); free(cfg); } /* Read a line of ASCII text input from an open config file. * * Abstract: * This routine extracts a line of input from an open config file. * The input line extracted and returned is a "keyword = value" line * found within the current [] section. If the end of the current * [] section has been reached, then no further input is returned. * * Lines which contain comments are automatically skipped. Comments * include those lines which begin with a '#' or ';' character. * Empty lines are also automatically skipped. * * Special {} subsections may also be used to specify language * variant .conf values. When these subsection identifiers are found * and the current language found in the ENV (LANG=) does not match * the language for the specified {} section, the entire {} section * is skipped. * * The input line retreived automatically has lead and trailing * whitespaces removed. * * Paramaters: * cfg - a 'config' parser object. * * Returns: * ASCII text for 'keyword = value' item from config file. * * Exceptions: * A NULL is returned when the current [] section has been completed, * when at the end of the file, or if any error occurs while reading. */ char *read_config(CONFIG *cfg) { char *p, *q; int skip = 0; if(!cfg) return NULL; if(!cfg->cfg_flag) return NULL; for(;;) { fgets(cfg->cfg_lbuf, LBUF - 1, cfg->cfg_fp); if(feof(cfg->cfg_fp) || cfg->cfg_lbuf[0] == '[' || ferror(cfg->cfg_fp)) { cfg->cfg_flag = FALSE; return NULL; } p = strtrim(cfg->cfg_lbuf, __SPACES); if(*p == '{') { skip = 1; p = strtok(p, "{}| \t"); while(p) { if(!stricmp(p, "all")) skip = 0; if(!stricmp(p, language())) skip = 0; p = strtok(NULL, "{}| \t"); } continue; } if(!*p || *p == '!' || *p == '#' || *p == ';' || skip) continue; return p; } } /* * Seek a named [] section within the .conf file to begin input. * * Abstract: * The named section is found within the .conf file. Once * found, all read_config() input will be returned from the * specified [] section. Section names are case insensitive. * * Paramaters: * cfg - config object pointer. * seek_name - name of config [] section to find. * * Returns: * TRUE if the section name is found in the .conf file , FALSE if * not. * * Exceptions: * If a NULL cfg or seek_name is passed, the search always fails. * If a file error is found, the search always fails. The maximum * size of a [] section name that is tested is 22 characters. */ bool seek_config(CONFIG *cfg, const char *seek_name) { char group[25]; int len; if(!cfg || !seek_name) return FALSE; cfg->cfg_flag = FALSE; /* mark as outside old [] section */ len = strlen(seek_name); if (len > 22) len = 22; memset(group, 0, sizeof(group)); if(*seek_name != '[') strcpy(group, "["); strncat(group, seek_name, len); if(*seek_name != '[' && strlen(seek_name) < 23) strcat(group, "]"); fseek(cfg->cfg_fp, 0l, SEEK_SET); len = strlen(group); for(;;) { fgets(cfg->cfg_lbuf, LBUF - 1, cfg->cfg_fp); if(feof(cfg->cfg_fp) || ferror(cfg->cfg_fp)) return FALSE; if(!strnicmp(group, cfg->cfg_lbuf, len)) { cfg->cfg_flag = TRUE; return TRUE; } } } /* * Parse and test a keyword value pair from current config input. * * Abstract: * This routine is commonly used to search the current input line * that is returned by read_config() for a specified keyword. The * current input line is assumed to be in the form 'keyword = value'. * lead and trailing spaces around the '=' are ignored, as is keyword * case. White spaces within a keyword are also ignored. * * Assuming the keyword requested is found in the current input line, * the 'value' string is returned. If the keyword being tested is * a multi-line keyword = { list }, then all lines for the value are * scanned and loaded into the config line buffer. If the special * '+' entry is found in the config file, then the keyword is assumed * to be a continuation of the last one found. * * A value is normally stripped of all lead and trailing spaces. If * these need to be preserved, then the value may be put in single * or double quotes. * * Since get_config() only looks at the current input line buffered * by read_config(), a test for every possible keyword the application * may need should be performed after each successful read_config() * for a given [] section. * * Paramaters: * cfg - config object pointer. * keyword - keyword to test for. * * Returns: * Value string if keyword is found in current input line, else NULL. * * Exceptions: * If a NULL pointer or keyword is used, a NULL value is returned. */ char *get_config(CONFIG *cfg, const char *keyword) { char *cbuf; char *out, *p; int pos = 0; bool found = FALSE; if(!cfg || !keyword) return NULL; cbuf = cfg->cfg_lbuf; if(*cbuf == '+') /* alternate multi-line syntax */ { if(!stricmp(cfg->cfg_test, keyword)) return strltrim(++cbuf, __SPACES); else return NULL; } while((pos < 33) && *cbuf && (*cbuf != '=')) { if((*cbuf != ' ') && (*cbuf != '_') && (*cbuf != '\t')) cfg->cfg_test[pos++] = *(cbuf++); else ++cbuf; } cfg->cfg_test[pos] = 0; out = p = strltrim(++cbuf, __SPACES); switch(*p) { case '{': cbuf = p; while(!found) { while(*(++p)) { if(*p == '}') { found = TRUE; *p = 0; } else *(cbuf++) = *p; } if(!found) { p = cbuf; fgets(p, LBUF - 1 + (int)(cfg->cfg_lbuf - p), cfg->cfg_fp); if(feof(cfg->cfg_fp) || *p == '[') { cfg->cfg_flag = FALSE; *p = 0; break; } *(cbuf++) = '\n'; p = strtrim(p, __SPACES); } } *cbuf = 0; out = strltrim(++out, __SPACES); break; case '\'': case '\"': while(*(++p)) { if(*p == *out) { *p = 0; break; } } out = strltrim(++out, __SPACES); break; } if(!stricmp(cfg->cfg_test, keyword)) return out; else return NULL; } /* * Find config file in user's home directory. * * Abstract: * In addition to searching for a master config file in /etc, many * applications may support an optional user specific 'rc' or config * file in the user's own home directory, which, if found, may * override global defaults. This ability is easily supported with * the usr_config() service, which looks for a named .config file * in the user's home. * * Paramaters: * Filename of '.config' file to look for in a user's home directory, * without the leading '.'. * * Returns: * Config object pointer if file is found in user's home, otherwise * a NULL pointer. * * Exceptions: * A NULL filename will result in a NULL object being returned. */ CONFIG *usr_config(const char *name) { char path[NAME_MAX + 1]; if(!name) return NULL; strcpy(path, homedir()); fncat(path, "."); strcat(path, name); return open_config(path); }