/** \file configfile.c * Define routines to read INI-file like files. */ /* This file is part of LCDd, the lcdproc server. * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. * * Copyright(c) 2001, Joris Robijn * (c) 2003, Rene Wagner * (c) 2006,2007 Peter Marschall * */ #include "config.h" #include #include #include #include #include "shared/report.h" typedef struct key { char *name; char *value; struct key *next_key; } key; typedef struct section { char *name; key *first_key; struct section *next_section; } section; static section *first_section = NULL; /* Yes there is a static. It's C after all :)*/ static section *find_section(const char *sectionname); static section *add_section(const char *sectionname); static key *find_key(section *s, const char *keyname, int skip); static key *add_key(section *s, const char *keyname, const char *value); static char get_next_char_f(FILE *f); #if defined(LCDPROC_CONFIG_READ_STRING) static int process_config(section **current_section, char(*get_next_char)(), const char *source_descr, FILE *f); #else static int process_config(section **current_section, const char *source_descr, FILE *f); #endif /**** PUBLIC FUNCTIONS ****/ /** Parse configuration from INI-file style config file into memory. * \param filename Name of the config file. * \retval 0 config successfully parsed * \retval <0 error occurred */ int config_read_file(const char *filename) { FILE *f; section *curr_section = NULL; int result = 0; report(RPT_NOTICE, "Using Configuration File: %s", filename); f = fopen(filename, "r"); if (f == NULL) { return -1; } #if defined(LCDPROC_CONFIG_READ_STRING) result = process_config(&curr_section, get_next_char_f, filename, f); #else result = process_config(&curr_section, filename, f); #endif fclose(f); return result; } #if defined(LCDPROC_CONFIG_READ_STRING) int config_read_string(const char *sectionname, const char *str) /* All the config parameters are placed in the given section in memory.*/ { int pos = 0; section *s; /* We use a nested fuction to transfer the characters from buffer to parser*/ char get_next_char() { return str[pos++]; } if ((s = find_section(sectionname)) == NULL) s = add_section(sectionname); return process_config(&s, get_next_char, "command line", NULL); } #endif /** Get string from configuration in memory. * * The strings returned are always NUL-terminated. * They should never be modified, and used only short-term. * In successive calls this function can re-use the data space ! * * You can do some things with the returned string: * \li Scan or parse it: * \code * s = config_get_string(...); * sscanf(s, "%dx%d", &w, &h); // scan format like: 20x4 * \endcode * ...and check the w and h values... * \li Copy it to a preallocated buffer like \c device[256]: * \code * s = config_get_string(...); * strncpy(device, s, sizeof(device)); * device[sizeof(device)-1] = '\0'; // make sure it is terminated * \endcode * \li Copy it to a newly allocated space in \c char \c *device: * \code * s = config_get_string(...); * device = malloc(strlen(s)+1); * if (device == NULL) return -5; // or whatever < 0 * strcpy( device, s ); * \endcode * * \param sectionname Name of the section where the key is sought. * \param keyname Name of the key to look for. * \param skip Number of values to skip/ignore before returning the value. * This is used to iterate through the values of a multi-valued key: * \c 0 for the first value, \c 1 for the 2nd, ... and \c -1 for the last. * \param default_value Default value if section/key is not found * or \c skip exceeds the number of values of the key. * \return Value found / \c default_value */ const char *config_get_string(const char *sectionname, const char *keyname, int skip, const char *default_value) { key *k = find_key(find_section(sectionname), keyname, skip); if (k == NULL) return default_value; return k->value; /* This is the safer way:*/ /* Reallocate memory space for the return value*/ /* string_storage = realloc(string_storage,(strlen(k->value) / 256 + 1) * 256); strcpy(string_storage, k->value); But then you also need a global static string_storage = NULL; */ } /** Get boolean value from configuration in memory. * * Legal boolean values are: * \li \c 0 , \c false , \c off , \c no or \c n for FALSE. * \li \c 1 , \c true , \c on , \c yes or \c y for TRUE * * \param sectionname Name of the section where the key is sought. * \param keyname Name of the key to look for. * \param skip Number of values to skip/ignore before returning the value. * This is used to iterate through the values of a multi-valued key: * \c 0 for the first value, \c 1 for the 2nd, ... and \c -1 for the last. * \param default_value Default value if section/key is not found, value is no legal boolean, * or \c skip exceeds the number of values of the key. * \return Value found / \c default_value */ short config_get_bool(const char *sectionname, const char *keyname, int skip, short default_value) { key *k = find_key(find_section(sectionname), keyname, skip); if (k == NULL) return default_value; if (strcasecmp(k->value, "0") == 0 || strcasecmp(k->value, "false") == 0 || strcasecmp(k->value, "n") == 0 || strcasecmp(k->value, "no") == 0 || strcasecmp(k->value, "off") == 0) { return 0; } if (strcasecmp(k->value, "1") == 0 || strcasecmp(k->value, "true") == 0 || strcasecmp(k->value, "y") == 0 || strcasecmp(k->value, "yes") == 0 || strcasecmp(k->value, "on") == 0) { return 1; } return default_value; } /** Get integer from configuration in memory. * \param sectionname Name of the section where the key is sought. * \param keyname Name of the key to look for. * \param skip Number of values to skip/ignore before returning the value. * This is used to iterate through the values of a multi-valued key: * \c 0 for the first value, \c 1 for the 2nd, ... and \c -1 for the last. * \param default_value Default value if section/key is not found, value is no integer, * or \c skip exceeds the number of values of the key. * \return Value found / \c default_value */ long int config_get_int(const char *sectionname, const char *keyname, int skip, long int default_value) { key *k = find_key(find_section(sectionname), keyname, skip); if (k != NULL) { char *end; long int v = strtol(k->value, &end, 0); if ((end != NULL) && (end != k->value) && (*end == '\0')) /* Conversion succesful */ return v; } return default_value; } /** Get floating point number from configuration in memory. * \param sectionname Name of the section where the key is sought. * \param keyname Name of the key to look for. * \param skip Number of values to skip/ignore before returning the value. * This is used to iterate through the values of a multi-valued key: * \c 0 for the first value, \c 1 for the 2nd, ... and \c -1 for the last. * \param default_value Default value if section/key is not found, value is no floating point number * or \c skip exceeds the number of values of the key. * \return Value found / \c default_value */ double config_get_float(const char *sectionname, const char *keyname, int skip, double default_value) { key *k = find_key(find_section(sectionname), keyname, skip); if (k != NULL) { char *end; double v = strtod(k->value, &end); if ((end != NULL) && (end != k->value) && (*end == '\0')) /* Conversion succesful*/ return v; } return default_value; } /** Test whether the configuration containis a specific section. * \param sectionname Name of the section to look for. * \retval 0 section not in config * \retval 1 section in config */ int config_has_section(const char *sectionname) { return (find_section(sectionname) != NULL) ? 1 : 0; } /** Test whether the configuration contains a specific key in a specfic section. * \param sectionname Name of the section where the key is sought. * \param keyname Name of the key to look for. * \retval 0 key or section not found * \retval n key found with \c n values (\c n > 0) */ int config_has_key(const char *sectionname, const char *keyname) { section *s = find_section(sectionname); int count = 0; if (s != NULL) { key *k; for (k = s->first_key; k != NULL; k = k->next_key) { /* Did we find the right key ?*/ if (strcasecmp(k->name, keyname) == 0) count++; } } return count; } /** Clear configuration. */ void config_clear(void) { section *s; section *next_s; for (s = first_section; s != NULL; s = next_s) { key *k; key *next_k; for (k = s->first_key; k != NULL; k = next_k) { /* Advance before we destroy the current key */ next_k = k->next_key; free(k->name); free(k->value); free(k); } /* Advance before we destroy the current section */ next_s = s->next_section; /* And destroy it */ free(s->name); free(s); } /* Finally make everything inaccessable */ first_section = NULL; } /**** INTERNAL FUNCTIONS ****/ static section *find_section(const char *sectionname) { section *s; for (s = first_section; s != NULL; s = s->next_section) { if (strcasecmp(s->name, sectionname) == 0) { return s; } } return NULL; /* not found */ } static section *add_section(const char *sectionname) { section *s; section **place = &first_section; for (s = first_section; s != NULL; s = s->next_section) place = &(s->next_section); *place = (section*) malloc(sizeof(section)); if (*place != NULL) { (*place)->name = strdup(sectionname); (*place)->first_key = NULL; (*place)->next_section = NULL; } return(*place); } static key *find_key(section *s, const char *keyname, int skip) { key *k; int count = 0; key *last_key = NULL; /* Check for NULL section*/ if (s == NULL) return NULL; for (k = s->first_key; k != NULL; k = k->next_key) { /* Did we find the right key ?*/ if (strcasecmp(k->name, keyname) == 0) { if (count == skip) return k; count++; last_key = k; } } if (skip == -1) return last_key; return NULL; /* not found*/ } static key *add_key(section *s, const char *keyname, const char *value) { if (s != NULL) { key *k; key **place = &(s->first_key); for (k = s->first_key; k != NULL; k = k->next_key) place = &(k->next_key); *place = (key *) malloc(sizeof(key)); if (*place != NULL) { (*place)->name = strdup(keyname); (*place)->value = strdup(value); (*place)->next_key = NULL; } return(*place); } return NULL; } #if defined(LCDPROC_CONFIG_READ_STRING) static char get_next_char_f(FILE *f) { int c = fgetc(f); return((c == EOF) ? '\0' : c); } #endif /* Parser states */ #define ST_INITIAL 0 #define ST_COMMENT 257 #define ST_SECTIONLABEL 258 #define ST_KEYNAME 259 #define ST_ASSIGNMENT 260 #define ST_VALUE 261 #define ST_QUOTEDVALUE 262 #define ST_SECTIONLABEL_DONE 263 #define ST_VALUE_DONE 264 #define ST_INVALID_SECTIONLABEL 265 #define ST_INVALID_KEYNAME 266 #define ST_INVALID_ASSIGNMENT 267 #define ST_INVALID_VALUE 268 #define ST_END 999 /* Limits */ #define MAXSECTIONLABELLENGTH 40 #define MAXKEYNAMELENGTH 40 #define MAXVALUELENGTH 200 #if defined(LCDPROC_CONFIG_READ_STRING) static int process_config(section **current_section, char(*get_next_char)(), const char *source_descr, FILE *f) #else static int process_config(section **current_section, const char *source_descr, FILE *f) #endif { int state = ST_INITIAL; int ch; char sectionname[MAXSECTIONLABELLENGTH+1]; int sectionname_pos = 0; char keyname[MAXKEYNAMELENGTH+1]; int keyname_pos = 0; char value[MAXVALUELENGTH+1]; int value_pos = 0; int escape = 0; key *k; int line_nr = 1; int error = 0; #if !defined(LCDPROC_CONFIG_READ_STRING) if (f == NULL) return(0); #endif while (state != ST_END) { #if defined(LCDPROC_CONFIG_READ_STRING) ch = (f != NULL) ? get_next_char(f) : get_next_char(); #else ch = fgetc(f); if (ch == EOF) ch = '\0'; #endif /* Secretly keep count of the line numbers */ if (ch == '\n') line_nr++; switch (state) { case ST_INITIAL: switch (ch) { case '#': case ';': /* comment start */ state = ST_COMMENT; /* fall through */ case '\0': case '\n': case '\r': case '\t': case ' ': /* ignore spaces */ break; case '[': /* section name */ state = ST_SECTIONLABEL; sectionname_pos = 0; sectionname[sectionname_pos] = '\0'; break; default: /* key word */ state = ST_KEYNAME; keyname_pos = 0; keyname[keyname_pos++] = ch; keyname[keyname_pos] = '\0'; } break; case ST_SECTIONLABEL: /* section label: "["{non-space chars}+"]" */ switch (ch) { case '\0': case '\n': /* premature end of label */ report(RPT_WARNING, "Unterminated section label on line %d of %s: %s", line_nr, source_descr, sectionname); error = 1; state = ST_INITIAL; /* alrady at the end, no resync required */ break; case ']': /* label terminated: find/create section */ if (!(*current_section = find_section(sectionname))) { *current_section = add_section(sectionname); } state = ST_SECTIONLABEL_DONE; break; // case '\r': // case '\t': // case ' ': // /* no spaces allowed in section labels WHY? */ // report(RPT_WARNING, "Invalid character in section label on line %d of %s: %s", // line_nr, source_descr, sectionname); // error = 1; // state = ST_INVALID_SECTIONLABEL; /* resync required */ // break; default: /* append char to section label */ if (sectionname_pos < MAXSECTIONLABELLENGTH) { sectionname[sectionname_pos++] = ch; sectionname[sectionname_pos] = '\0'; break; } report(RPT_WARNING, "Section name too long on line %d of %s: %s", line_nr, source_descr, sectionname); error = 1; state = ST_INVALID_SECTIONLABEL; /* resync required */ } break; case ST_KEYNAME: /* key name: {non-space chars}+ */ switch (ch) { case '\r': case '\t': case ' ': /* ignore trailing spaces */ if (keyname_pos != 0) state = ST_ASSIGNMENT; break; case '\0': case '\n': /* premature end of line */ report(RPT_WARNING, "Loose word found on line %d of %s: %s", line_nr, source_descr, keyname); error = 1; state = ST_INITIAL; /* already at the end; no resync required */ break; case '=': /* end of key reached, "=" found, now we need a value */ state = ST_VALUE; value[0] = '\0'; value_pos = 0; break; // case '"': // case '[': // case ']': // /* character invalid in key names WHY ? */ // report(RPT_WARNING, "Invalid character in key name on line %d of %s: %s", // line_nr, source_descr, keyname); // error = 1; // state = ST_INVALID_KEYNAME; /* resync required */ // break; default: /* append char to key name */ if (keyname_pos < MAXKEYNAMELENGTH) { keyname[keyname_pos++] = ch; keyname[keyname_pos] = '\0'; break; } report(RPT_WARNING, "Key name too long on line %d of %s: %s", line_nr, source_descr, keyname); error = 1; state = ST_INVALID_KEYNAME; /* resync required */ } break; case ST_ASSIGNMENT: /* assignement: "=" */ switch (ch) { case '\t': case ' ': /* ignore leading spaces */ break; case '=': /* "=" found, now we need a value */ state = ST_VALUE; value[0] = '\0'; value_pos = 0; break; default: report(RPT_WARNING, "Assigment expected on line %d of %s: %s", line_nr, source_descr, keyname); error = 1; state = ST_INVALID_ASSIGNMENT; } break; case ST_VALUE: /* value: {non-space char}+ | "\""{any potentially-quoted char}+"\"" */ switch (ch) { case '#': case ';': /* allow comment if we already had a value */ /* WHY ONLY THEN ? 'xx=' can be seen as equivalent to 'xx=""' */ if (value_pos > 0) { state = ST_COMMENT; break; } /* fall through */ case '[': case ']': case '=': /* illegal characters WHY? */ report(RPT_WARNING, "Invalid character '%c' in value on line %d of %s, at key: %s", ch, line_nr, source_descr, keyname); error = 1; state = ST_INVALID_VALUE; break; case '\t': case ' ': /* ignore leading spaces */ if (value_pos == 0) break; /* fall through */ case '\0': case '\n': case '\r': /* value complete */ if (!*current_section) { report(RPT_WARNING, "Data outside sections on line %d of %s with key: %s", line_nr, source_descr, keyname); error = 1; } else { /* Store the value*/ k = add_key(*current_section, keyname, value); } /* And be ready for next thing...*/ state = ((ch == ' ') || (ch == '\t')) ? ST_VALUE_DONE : ST_INITIAL; break; case '"': /* quoted string */ state = ST_QUOTEDVALUE; break; default: /* append char to value */ if (value_pos < MAXVALUELENGTH) { value[value_pos++] = ch; value[value_pos] = '\0'; break; } report(RPT_WARNING, "Value too long on line %d of %s, at key: %s", line_nr, source_descr, keyname); error = 1; state = ST_INVALID_VALUE; } break; case ST_QUOTEDVALUE: /* a quoted part of a string */ switch (ch) { case '\0': case '\n': report(RPT_WARNING, "Premature end of quoted string on line %d of %s: %s", line_nr, source_descr, keyname); error = 1; state = ST_INITIAL; break; case '\\': if (!escape) { escape = 1; break; } /* fall though */ case '"': if (!escape) { state = ST_VALUE; break; } /* fall though */ default: if (escape) { switch (ch) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; /* default: literal char (i.e. ignore '\') */ } escape = 0; } value[value_pos++] = ch; value[value_pos] = '\0'; } break; case ST_SECTIONLABEL_DONE: case ST_VALUE_DONE: switch (ch) { case ';': case '#': state = ST_COMMENT; break; case '\0': case '\n': state = ST_INITIAL; break; case '\t': case ' ': break; default: /* illegal characters */ report(RPT_WARNING, "Invalid character '%c' on line %d of %s", ch, line_nr, source_descr); error = 1; state = ST_INVALID_VALUE; } case ST_INVALID_SECTIONLABEL: /* invalid section label: resync up to end of label/next line */ if (ch == ']') state = ST_INITIAL; /* fall through */ case ST_INVALID_ASSIGNMENT: case ST_INVALID_KEYNAME: case ST_INVALID_VALUE: case ST_COMMENT: /* comment or error: ignore anything up to the next line */ if (ch == '\n') state = ST_INITIAL; } if (ch == '\0') { if ((!error) && (state != ST_INITIAL) && (state != ST_COMMENT) && (state != ST_SECTIONLABEL_DONE) && (state != ST_VALUE_DONE)) { report(RPT_WARNING, "Premature end of configuration on line %d of %s: %d", line_nr, source_descr, state); error = 1; } state = ST_END; } } return (error) ? -1 : 0; } #if CONFIGFILE_DEBUGTEST void config_dump(void) { section *s; for (s = first_section; s != NULL; s = s->next_section) { key *k; fprintf(stderr, "[%s]\n", s->name); for (k = s->first_key; k != NULL; k = k->next_key) fprintf(stderr, "%s = \"%s\"\n", k->name, k->value); fprintf(stderr, "\n"); } } int main(int argc, char *argv[]) { if (argc > 0) config_read_file(argv[1]); config_dump(); } #endif