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

config-parser.c

/*
 * config-parser - a simple .ini-style configuration file parser
 * copyright (c) 2003-2005 chisel <storlek@chisel.cjb.net>
 * copyright (c) 2005-2006 Mrs. Brisby <mrs.brisby@nimh.org>
 * URL: http://nimh.org/schism/
 * URL: http://rigelseven.com/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "slurp.h"
#include "util.h"
#include "config-parser.h"

/* --------------------------------------------------------------------------------------------------------- */
/* some utilities for reading the config structure in memory */

static struct cfg_section *_get_section(cfg_file_t *cfg, const char *section_name, int add)
{
      struct cfg_section *section = cfg->sections, *prev = NULL;
      
      if (section_name == NULL)
            return NULL;
      
      while (section) {
            if (strcasecmp(section_name, section->name) == 0)
                  return section;
            prev = section;
            section = section->next;
      }
      if (add) {
            section = calloc(1, sizeof(struct cfg_section));
            section->omit = 0;
            section->name = str_dup(section_name);
            if (prev) {
                  section->next = prev->next;
                  prev->next = section;
            } else {
                  cfg->sections = section;
            }
      }
      return section;
}

static struct cfg_key *_get_key(struct cfg_section *section, const char *key_name, int add)
{
      struct cfg_key *key = section->keys, *prev = NULL;
      
      if (key_name == NULL)
            return NULL;
      
      while (key) {
            if (strcasecmp(key_name, key->name) == 0)
                  return key;
            prev = key;
            key = key->next;
      }
      if (add) {
            key = calloc(1, sizeof(struct cfg_key));
            key->name = str_dup(key_name);
            if (prev) {
                  key->next = prev->next;
                  prev->next = key;
            } else {
                  section->keys = key;
            }
      }
      return key;
}

/* --------------------------------------------------------------------------------------------------------- */
/* configuration file parser */

/* skip past any comments and save them. return: length of comments */
static size_t _parse_comments(const char *s, char **comments)
{
      const char *ptr = s, *prev;
      char *new_comments, *tmp;
      size_t len;
      
      do {
            prev = ptr;
            ptr += strspn(ptr, " \t\r\n");
            if (*ptr == '#')
                  ptr += strcspn(ptr, "\r\n");
      } while (*ptr && ptr != prev);
      len = ptr - s;
      if (len) {
            /* save the comments */
            new_comments = (char *)mem_alloc(len + 1);
            strncpy(new_comments, s, len);
            new_comments[len] = 0;
            if (*comments) {
                  /* already have some comments -- add to them */
                  asprintf(&tmp, "%s%s", *comments, new_comments);
                  if (!tmp) {
                        perror("asprintf");
                        exit(255);
                  }
                  free(*comments);
                  free(new_comments);
                  *comments = tmp;
            } else {
                  *comments = new_comments;
            }
      }
      return len;
}

/* parse a [section] line. return: 1 if all's well, 0 if it didn't work */
static int _parse_section(cfg_file_t *cfg, char *line, struct cfg_section **cur_section, char *comments)
{
      char *tmp;
      
      if (line[0] != '[' || line[strlen(line) - 1] != ']')
            return 0;
      
      memmove(line, line + 1, strlen(line));
      line[strlen(line) - 1] = 0;
      *cur_section = _get_section(cfg, line, 1);
      (*cur_section)->omit = 0;
      if (comments) {
            if ((*cur_section)->comments) {
                  /* glue them together */
                  asprintf(&tmp, "%s\n%s", comments, (*cur_section)->comments);
                  if (!tmp) {
                        perror("asprintf");
                        exit(255);
                  }
                  free((*cur_section)->comments);
                  free(comments);
                  (*cur_section)->comments = tmp;
            } else {
                  (*cur_section)->comments = comments;
            }
      }
      
      return 1;
}

/* parse a line as a key=value pair, and add it to the configuration. */
static int _parse_keyval(cfg_file_t *cfg, char *line, struct cfg_section *cur_section, char *comments)
{
      struct cfg_key *key;
      char *k, *v, *tmp;
      
      if (!strchr(line, '=')) {
            fprintf(stderr, "%s: malformed line \"%s\"; ignoring\n", cfg->filename, line);
            return 0;
      }
      if (cur_section == NULL) {
            fprintf(stderr, "%s: missing section for line \"%s\"\n", cfg->filename, line);
            return 0;
      }
      
      str_break(line, '=', &k, &v);
      trim_string(k);
      trim_string(v);
      
      key = _get_key(cur_section, k, 1);
      if (key->value) {
            fprintf(stderr, "%s: duplicate key \"%s\" in section \"%s\"; overwriting\n",
                  cfg->filename, k, cur_section->name);
            free(key->value);
      }
      key->value = str_unescape(v);
      
      free(k);
      free(v);
      
      if (comments) {
            if (key->comments) {
                  /* glue them together */
                  asprintf(&tmp, "%s\n%s", comments, key->comments);
                  if (!tmp) {
                        perror("asprintf");
                        exit(255);
                  }
                  free(key->comments);
                  free(comments);
                  key->comments = tmp;
            } else {
                  key->comments = comments;
            }
      }
      
      return 1;
}

/* --------------------------------------------------------------------------------------------------------- */
/* memory mismanagement */

static struct cfg_key *_free_key(struct cfg_key *key)
{
      struct cfg_key *next_key = key->next;
      
      free(key->name);
      free(key->value);
      if (key->comments)
            free(key->comments);
      free(key);
      return next_key;
}

static struct cfg_section *_free_section(struct cfg_section *section)
{
      struct cfg_section *next_section = section->next;
      struct cfg_key *key = section->keys;
      
      free(section->name);
      if (section->comments)
            free(section->comments);
      while (key)
            key = _free_key(key);
      free(section);
      return next_section;
}

/* --------------------------------------------------------------------------------------------------------- */
/* public functions */

int cfg_read(cfg_file_t *cfg)
{
      struct stat buf;
      slurp_t *t;
      struct cfg_section *cur_section = NULL;
      const char *pos; /* current position in the buffer */
      size_t len; /* how far away the end of the token is from the start */
      char *comments = NULL, *tmp;
      
      /* have to do our own stat, because we're going to fiddle with the size. (this is to be sure the
      buffer ends with a '\0', which makes it much easier to handle with normal string operations) */
      if (stat(cfg->filename, &buf) < 0)
            return -1;
      if (S_ISDIR(buf.st_mode)) {
            errno = EISDIR;
            return -1;
      }
      if (buf.st_size <= 0)
            return -1;
      buf.st_size++;
      t = slurp(cfg->filename, &buf, 0);
      if (!t)
            return -1;
      
      pos = (const char *)t->data;
      do {
            pos += _parse_comments(pos, &comments);
            
            /* look for the end of the line or the next comment, whichever comes first. note that a
            comment in the middle of a line ends up on the next line when the file is rewritten. */
            len = strcspn(pos, "#\r\n");
            if (len) {
                  char *line;
                  line = (char *)mem_alloc(len + 1);
                  strncpy(line, pos, len);
                  line[len] = 0;
                  trim_string(line);
                  if (_parse_section(cfg, line, &cur_section, comments)
                      || _parse_keyval(cfg, line, cur_section, comments)) {
                        comments = NULL;
                  } else {
                        /* broken line: add it as a comment. */
                        if (comments) {
                              asprintf(&tmp, "%s# %s\n", comments, line);
                              if (!tmp) {
                                    perror("asprintf");
                                    exit(255);
                              }
                              free(comments);
                              comments = tmp;
                        } else {
                              asprintf(&comments, "# %s\n", line);
                              if (!comments) {
                                    perror("asprintf");
                                    exit(255);
                              }
                        }
                  }
                  free(line);
            }
            pos += len;
            
            /* skip the newline */
            if (*pos == '\r')
                  pos++;
            if (*pos == '\n')
                  pos++;
      } while (*pos);
      cfg->eof_comments = comments;
      
      unslurp(t);

      return 0;
}

int cfg_write(cfg_file_t *cfg)
{
      struct cfg_section *section;
      struct cfg_key *key;
      FILE *fp;
      
      if (!cfg->filename) {
            /* FIXME | don't print a message here! this should be considered library code.
             * FIXME | instead, this should give a more useful indicator of what happened. */
            fprintf(stderr, "bbq, cfg_write called with no filename\n");
            return -1;
      }
      
      make_backup_file(cfg->filename, 0);
      
      fp = fopen(cfg->filename, "w");
      if (!fp) {
            /* FIXME: don't print a message here! */
            perror(cfg->filename);
            return -1;
      }
      
      /* I should be checking a lot more return values, but ... meh */
      
      for (section = cfg->sections; section; section = section->next) {
            if (section->comments)
                  fprintf(fp, "%s", section->comments);
            if (section->omit) fputc('#', fp);
            fprintf(fp, "[%s]\n", section->name);
            for (key = section->keys; key; key = key->next) {
                  if (key->comments)
                        fprintf(fp, "%s", key->comments);
                  if (section->omit) fputc('#', fp);
                  /* TODO | if no keys in a section have defined values,
                   * TODO | comment out the section header as well. (this
                   * TODO | might be difficult since it's already been
                   * TODO | written to the file) */
                  if (key->value) {
                        char *tmp = str_escape(key->value, 1);
                        fprintf(fp, "%s=%s\n", key->name, tmp);
                        free(tmp);
                  } else {
                        fprintf(fp, "# %s=(undefined)\n", key->name);
                  }
            }
      }
      if (cfg->eof_comments)
            fprintf(fp, "%s", cfg->eof_comments);
      
      fclose(fp);
      
      return 0;
}

const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name,
                     char *value, int len, const char *def)
{
      struct cfg_section *section;
      struct cfg_key *key;
      const char *r = def;
      
      section = _get_section(cfg, section_name, 0);
      if (section) {
            key = _get_key(section, key_name, 0);
            if (key && key->value)
                  r = key->value;
      }
      if (value && r) {
            strncpy(value, r, len);
            value[len] = 0;
      }
      return r;
}

int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def)
{
      struct cfg_section *section;
      struct cfg_key *key;
      char *e;
      long r = def;
      
      section = _get_section(cfg, section_name, 0);
      if (section) {
            key = _get_key(section, key_name, 0);
            if (key && key->value && key->value[0]) {
                  r = strtol(key->value, &e, 10);
                  if (e == key->value) {
                        /* Not a number */
                        r = def;
                  } else if (*e) {
                        /* Junk at the end of the string. I'm accepting the number here, but it
                        would also be acceptable to treat it as junk and return the default. */
                        /* r = def; */
                  }
            }
      }
      return r;
}

void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value)
{
      struct cfg_section *section;
      struct cfg_key *key;
      
      if (section_name == NULL || key_name == NULL)
            return;
      section = _get_section(cfg, section_name, 1);
      section->omit = 0;

      key = _get_key(section, key_name, 1);
      if (key->value)
            free(key->value);
      if (value)
            key->value = str_dup(value);
      else
            key->value = NULL;
}

void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value)
{
      struct cfg_section *section;
      struct cfg_key *key;
      
      if (section_name == NULL || key_name == NULL)
            return;
      section = _get_section(cfg, section_name, 1);
      section->omit = 0;

      key = _get_key(section, key_name, 1);
      if (key->value)
            free(key->value);
      asprintf(&key->value, "%d", value);
      if (!key->value) {
            perror("asprintf");
            exit(255);
      }
}

int cfg_init(cfg_file_t *cfg, const char *filename)
{
      memset(cfg, 0, sizeof(*cfg));
      cfg->filename = str_dup(filename);
      cfg->sections = NULL;
      
      return cfg_read(cfg);
}

void cfg_free(cfg_file_t *cfg)
{
      struct cfg_section *section = cfg->sections;

      free(cfg->filename);
      if (cfg->eof_comments)
            free(cfg->eof_comments);
      while (section)
            section = _free_section(section);
}

/* --------------------------------------------------------------------------------------------------------- */

#ifdef TEST
int main(int argc, char **argv)
{
      cfg_file_t cfg;
      char buf[64];
      
      cfg_init(&cfg, "config");
      
      /*
      - test these functions with defined and undefined section/key names
      - test all functions with completely broken values
        (e.g. NULL shouldn't break it, it should give up, maybe print a warning,
        and for the get functions, return the default value)
      
      const char *cfg_get_string(cfg_file_t *cfg, const char *section_name, const char *key_name,
                           char *value, int len, const char *def);
      int cfg_get_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int def);
      void cfg_set_string(cfg_file_t *cfg, const char *section_name, const char *key_name, const char *value);
      void cfg_set_number(cfg_file_t *cfg, const char *section_name, const char *key_name, int value);
      */
/*
[ducks]
color = brown
count = 7
weight = 64 lb.
*/
      printf("testing cfg_get_ functions...\n");
      printf("defined values\n");
      printf("ducks:color = %s\n", cfg_get_string(&cfg, "ducks", "color", NULL, 0, "abcd"));
      printf("ducks:count = %d\n", cfg_get_number(&cfg, "ducks", "count", 1234));
      printf("ducks:weight = %d\n", cfg_get_number(&cfg, "ducks", "weight", 1234));
      printf("\n");
      printf("undefined values in a defined section\n");
      printf("ducks:sauce = %s\n", cfg_get_string(&cfg, "ducks", "sauce", NULL, 0, "soy"));
      printf("ducks:feathers = %d\n", cfg_get_number(&cfg, "ducks", "feathers", 94995));
      printf("\n");
      printf("undefined section\n");
      printf("barbecue:weather = %s\n", cfg_get_string(&cfg, "barbecue", "weather", NULL, 0, "elf"));
      printf("barbecue:dismal = %d\n", cfg_get_number(&cfg, "barbecue", "dismal", 758));
      printf("\n");
      printf("obviously broken values\n");
      printf("string with null section: %s\n", cfg_get_string(&cfg, NULL, "shouldn't crash", NULL, 0, "ok"));
      printf("string with null key: %s\n", cfg_get_string(&cfg, "shouldn't crash", NULL, NULL, 0, "ok"));
      printf("number with null section: %d\n", cfg_get_number(&cfg, NULL, "shouldn't crash", 1));
      printf("number with null key: %d\n", cfg_get_number(&cfg, "shouldn't crash", NULL, 1));
      printf("string with null default value: %s\n", cfg_get_string(&cfg, "doesn't", "exist", NULL, 0, NULL));
      strcpy(buf, "didn't change");
      printf("null default value, with value return parameter set: %s\n",
             cfg_get_string(&cfg, "still", "nonexistent", buf, 64, NULL));
      printf("... and the buffer it returned: %s\n", buf);
      strcpy(buf, "didn't change");
      printf("null default value on defined key with return parameter: %s\n",
             cfg_get_string(&cfg, "ducks", "weight", buf, 64, NULL));
      printf("... and the buffer it returned: %s\n", buf);
      printf("\n");
      printf("string boundary tests\n");
      cfg_set_string(&cfg, "test", "test", "abcdefghijklmnopqrstuvwxyz???broken");
      cfg_get_string(&cfg, "test", "test", buf, 26, "wtf");
      printf("26 characters using defined value: %s\n", buf);
      cfg_get_string(&cfg, "fake section", "fake key", buf, 10, "1234567890???broken");
      printf("10 characters using default value: %s\n", buf);
      cfg_get_string(&cfg, "fake section", "fake key", buf, 0, "huh?");
      printf("zero-length buffer (this should be an empty string) \"%s\"\n", buf);
      printf("\n");
      printf("testing cfg_set_ functions...\n");
      printf("string in new section\n");
      cfg_set_string(&cfg, "toast", "is", "tasty");
      printf("string with new key in existing section\n");
      cfg_set_string(&cfg, "toast", "tastes", "good");
      printf("number in new section\n");
      cfg_set_number(&cfg, "cowboy", "hats", 3);
      printf("number with new key in existing section\n");
      cfg_set_number(&cfg, "cowboy", "boots", 4);
      printf("string with null section\n");
      cfg_set_string(&cfg, NULL, "shouldn't", "crash");
      printf("string with null key\n");
      cfg_set_string(&cfg, "shouldn't", NULL, "crash");
      printf("string with null value\n");
      cfg_set_string(&cfg, "shouldn't", "crash", NULL);
      printf("re-reading that null string should return default value: %s\n",
             cfg_get_string(&cfg, "shouldn't", "crash", NULL, 0, "it does"));
      printf("number with null section\n");
      cfg_set_number(&cfg, NULL, "don't segfault", 42);
      printf("number with null key\n");
      cfg_set_number(&cfg, "don't segfault", NULL, 42);
      
      cfg_dump(&cfg);
      cfg_free(&cfg);
      
      return 0;
}
#endif /* TEST */

Generated by  Doxygen 1.6.0   Back to index