Files
archived-lcd4linux/cfg.c
michux baa22cf17a add sample cfg for vnc driver, indent
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1006 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
2009-03-26 13:07:17 +00:00

715 lines
15 KiB
C

/* $Id$
* $URL$
* $URL$
*
* config file stuff
*
* Copyright (C) 1999, 2000 Michael Reinelt <michael@reinelt.co.at>
* Copyright (C) 2004, 2009 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
*
* This file is part of LCD4Linux.
*
* LCD4Linux 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, or (at your option)
* any later version.
*
* LCD4Linux 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
/*
* exported functions:
*
* cfg_init (source)
* read configuration from source
* returns 0 if successful
* returns -1 in case of an error
*
* cfg_source (void)
* returns the file the configuration was read from
*
* cfg_cmd (arg)
* allows us to overwrite entries in the
* config-file from the command line.
* arg is 'key=value'
* cfg_cmd can be called _before_ cfg_read()
* returns 0 if ok, -1 if arg cannot be parsed
*
* cfg_list (section)
* returns a list of all keys in the specified section
* This list was allocated be cfg_list() and must be
* freed by the caller!
*
* cfg_rename (section, old, new)
* changes the key of a existing entry
*
* cfg_get_raw (section, key, defval)
* return the a value for a given key in a given section
* or <defval> if key does not exist. Does NOT evaluate
* the expression. Therefore used to get the expression
* itself!
*
* cfg_get (section, key, defval)
* return the a value for a given key in a given section
* or <defval> if key does not exist. The specified
* value in the config is treated as a expression and
* is evaluated!
*
* cfg_number (section, key, defval, min, int max, *value)
* return the a value for a given key in a given section
* convert it into a number with syntax checking
* check if its in a given range. As it uses cfg_get()
* internally, the evaluator is used here, too.
*
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include "debug.h"
#include "evaluator.h"
#include "cfg.h"
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif
typedef struct {
char *key;
char *val;
int lock;
} ENTRY;
static char *Config_File = NULL;
static ENTRY *Config = NULL;
static int nConfig = 0;
/* bsearch compare function for config entries */
static int c_lookup(const void *a, const void *b)
{
char *key = (char *) a;
ENTRY *entry = (ENTRY *) b;
return strcasecmp(key, entry->key);
}
/* qsort compare function for variables */
static int c_sort(const void *a, const void *b)
{
ENTRY *ea = (ENTRY *) a;
ENTRY *eb = (ENTRY *) b;
return strcasecmp(ea->key, eb->key);
}
/* remove leading and trailing whitespace */
static char *strip(char *s, const int strip_comments)
{
char *p;
while (isblank(*s))
s++;
for (p = s; *p; p++) {
if (*p == '"')
do
p++;
while (*p && *p != '\n' && *p != '\r' && *p != '"');
if (*p == '\'')
do
p++;
while (*p && *p != '\n' && *p != '\r' && *p != '\'');
if (*p == '\n' || (strip_comments && *p == '#' && (p == s || *(p - 1) != '\\'))) {
*p = '\0';
break;
}
if (*p == '\r' && *(p + 1) == '\n') {
/* replace <CR> from DOS <CR><LF> with blank */
*p = ' ';
}
}
for (p--; p > s && isblank(*p); p--)
*p = '\0';
return s;
}
/* which if a string contains only valid chars */
/* i.e. start with a char and contains chars and nums */
static int validchars(const char *string, const int numstart)
{
const char *c;
for (c = string; *c; c++) {
/* first and following chars */
if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c == '_'))
continue;
/* number as first or following char */
if ((numstart || c > string) && *c >= '0' && *c <= '9')
continue;
/* only following chars */
if ((c > string) && ((*c == '.') || (*c == '-')))
continue;
return 0;
}
return 1;
}
static void cfg_add(const char *section, const char *key, const char *val, const int lock)
{
char *buffer;
ENTRY *entry;
/* allocate buffer */
buffer = malloc(strlen(section) + strlen(key) + 2);
*buffer = '\0';
/* prepare section.key */
if (section != NULL && *section != '\0') {
strcpy(buffer, section);
strcat(buffer, ".");
}
strcat(buffer, key);
/* does the key already exist? */
entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
if (entry != NULL) {
if (entry->lock > lock)
return;
debug("Warning: key <%s>: value <%s> overwritten with <%s>", buffer, entry->val, val);
free(buffer);
if (entry->val)
free(entry->val);
entry->val = strdup(val);
return;
}
nConfig++;
Config = realloc(Config, nConfig * sizeof(ENTRY));
Config[nConfig - 1].key = buffer;
Config[nConfig - 1].val = strdup(val);
Config[nConfig - 1].lock = lock;
qsort(Config, nConfig, sizeof(ENTRY), c_sort);
}
int cfg_cmd(const char *arg)
{
char *key, *val;
char *buffer;
buffer = strdup(arg);
key = strip(buffer, 0);
for (val = key; *val; val++) {
if (*val == '=') {
*val++ = '\0';
break;
}
}
if (*key == '\0' || *val == '\0') {
free(buffer);
return -1;
}
if (!validchars(key, 0)) {
free(buffer);
return -1;
}
cfg_add("", key, val, 1);
free(buffer);
return 0;
}
char *cfg_list(const char *section)
{
int i, len;
char *key, *list;
/* calculate key length */
len = strlen(section) + 1;
/* prepare search key */
key = malloc(len + 1);
strcpy(key, section);
strcat(key, ".");
/* start with empty string */
list = malloc(1);
*list = '\0';
/* search matching entries */
for (i = 0; i < nConfig; i++) {
if (strncasecmp(Config[i].key, key, len) == 0) {
list = realloc(list, strlen(list) + strlen(Config[i].key) - len + 2);
if (*list != '\0')
strcat(list, "|");
strcat(list, Config[i].key + len);
}
}
free(key);
return list;
}
int cfg_rename(const char *section, const char *old, const char *new)
{
char *buffer;
ENTRY *old_entry, *new_entry;
/* prepare old section.key */
buffer = malloc(strlen(section) + strlen(old) + 2);
*buffer = '\0';
if (section != NULL && *section != '\0') {
strcpy(buffer, section);
strcat(buffer, ".");
}
strcat(buffer, old);
/* lookup old entry */
old_entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
free(buffer);
if (old_entry == NULL) {
error("internal error: cfg_rename(%s, %s, %s) failed: entry not found!", section, old, new);
return -1;
}
/* prepare new section.key */
buffer = malloc(strlen(section) + strlen(new) + 2);
*buffer = '\0';
if (section != NULL && *section != '\0') {
strcpy(buffer, section);
strcat(buffer, ".");
}
strcat(buffer, new);
/* lookup new entry */
new_entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
if (new_entry != NULL) {
info("cfg_rename(%s, %s, %s) failed: entry already exists!", section, old, new);
free(buffer);
return -1;
}
/* replace key */
free(old_entry->key);
old_entry->key = buffer;
/* sort table again */
qsort(Config, nConfig, sizeof(ENTRY), c_sort);
return 0;
}
static char *cfg_lookup(const char *section, const char *key)
{
int len;
char *buffer;
ENTRY *entry;
/* calculate key length */
len = strlen(key) + 1;
if (section != NULL)
len += strlen(section) + 1;
/* allocate buffer */
buffer = malloc(len);
*buffer = '\0';
/* prepare section:key */
if (section != NULL && *section != '\0') {
strcpy(buffer, section);
strcat(buffer, ".");
}
strcat(buffer, key);
/* search entry */
entry = bsearch(buffer, Config, nConfig, sizeof(ENTRY), c_lookup);
/* free buffer again */
free(buffer);
if (entry != NULL)
return entry->val;
return NULL;
}
char *cfg_get_raw(const char *section, const char *key, const char *defval)
{
char *val = cfg_lookup(section, key);
if (val != NULL)
return val;
return (char *) defval;
}
char *cfg_get(const char *section, const char *key, const char *defval)
{
char *expression;
char *retval;
void *tree = NULL;
RESULT result = { 0, 0, 0, NULL };
expression = cfg_lookup(section, key);
if (expression != NULL) {
if (*expression == '\0')
return "";
if (Compile(expression, &tree) == 0 && Eval(tree, &result) == 0) {
retval = strdup(R2S(&result));
DelTree(tree);
DelResult(&result);
return (retval);
}
DelTree(tree);
DelResult(&result);
}
if (defval)
return strdup(defval);
return NULL;
}
int cfg_number(const char *section, const char *key, const int defval, const int min, const int max, int *value)
{
char *expression;
void *tree = NULL;
RESULT result = { 0, 0, 0, NULL };
/* start with default value */
/* in case of an (uncatched) error, you have the */
/* default value set, which may be handy... */
*value = defval;
expression = cfg_get_raw(section, key, NULL);
if (expression == NULL || *expression == '\0') {
return 0;
}
if (Compile(expression, &tree) != 0) {
DelTree(tree);
return -1;
}
if (Eval(tree, &result) != 0) {
DelTree(tree);
DelResult(&result);
return -1;
}
*value = R2N(&result);
DelTree(tree);
DelResult(&result);
if (*value < min) {
error("bad '%s.%s' value '%d' in %s, minimum is %d", section, key, *value, cfg_source(), min);
*value = min;
return -1;
}
if (max > min && max != -1 && *value > max) {
error("bad '%s.%s' value '%d' in %s, maximum is %d", section, key, *value, cfg_source(), max);
*value = max;
return -1;
}
return 1;
}
static int cfg_check_source(const char *file)
{
/* as passwords and commands are stored in the config file,
* we will check that:
* - file is a normal file (or /dev/null)
* - file owner is owner of program
* - file is not accessible by group
* - file is not accessible by other
*/
struct stat stbuf;
uid_t uid, gid;
int error;
uid = geteuid();
gid = getegid();
if (stat(file, &stbuf) == -1) {
error("stat(%s) failed: %s", file, strerror(errno));
return -1;
}
if (S_ISCHR(stbuf.st_mode) && strcmp(file, "/dev/null") == 0)
return 0;
error = 0;
if (!S_ISREG(stbuf.st_mode)) {
error("security error: '%s' is not a regular file", file);
error = -1;
}
if (stbuf.st_uid != uid || stbuf.st_gid != gid) {
error("security error: owner and/or group of '%s' don't match", file);
error = -1;
}
#if ! defined(__CYGWIN__)
if (stbuf.st_mode & S_IRWXG || stbuf.st_mode & S_IRWXO) {
error("security error: group or other have access to '%s'", file);
error = -1;
}
#endif
return error;
}
static int cfg_read(const char *file)
{
FILE *stream;
char buffer[256];
char section[256];
char *line, *key, *val, *end;
int section_open, section_close;
int error, lineno;
stream = fopen(file, "r");
if (stream == NULL) {
error("open(%s) failed: %s", file, strerror(errno));
return -1;
}
/* start with empty section */
strcpy(section, "");
error = 0;
lineno = 0;
while ((line = fgets(buffer, 256, stream)) != NULL) {
/* increment line number */
lineno++;
/* skip empty lines */
if (*(line = strip(line, 1)) == '\0')
continue;
/* reset section flags */
section_open = 0;
section_close = 0;
/* key is first word */
key = line;
/* search first blank between key and value */
for (val = line; *val; val++) {
if (isblank(*val)) {
*val++ = '\0';
break;
}
}
/* strip value */
val = strip(val, 1);
/* search end of value */
if (*val)
for (end = val; *(end + 1); end++);
else
end = val;
/* if last char is '{', a section has been opened */
if (*end == '{') {
section_open = 1;
*end = '\0';
val = strip(val, 0);
}
/* provess "value" in double-quotes */
if (*val == '"' && *end == '"') {
*end = '\0';
val++;
}
/* if key is '}', a section has been closed */
if (strcmp(key, "}") == 0) {
section_close = 1;
*key = '\0';
}
/* sanity check: '}' should be the only char in a line */
if (section_close && (section_open || *val != '\0')) {
error("error in config file '%s' line %d: garbage after '}'", file, lineno);
error = 1;
break;
}
/* check key for valid chars */
if (!validchars(key, 0)) {
error("error in config file '%s' line %d: key '%s' is invalid", file, lineno, key);
error = 1;
break;
}
/* on section-open, check value for valid chars */
if (section_open && !validchars(val, 1)) {
error("error in config file '%s' line %d: section '%s' is invalid", file, lineno, val);
error = 1;
break;
}
/* on section-open, append new section name */
if (section_open) {
/* is the section[] array big enough? */
if (strlen(section) + strlen(key) + 3 > sizeof(section)) {
error("error in config file '%s' line %d: section buffer overflow", file, lineno);
error = 1;
break;
}
if (*section != '\0')
strcat(section, ".");
strcat(section, key);
if (*val != '\0') {
strcat(section, ":");
strcat(section, val);
}
continue;
}
/* on section-close, remove last section name */
if (section_close) {
/* sanity check: section already empty? */
if (*section == '\0') {
error("error in config file '%s' line %d: unmatched closing brace", file, lineno);
error = 1;
break;
}
end = strrchr(section, '.');
if (end == NULL)
*section = '\0';
else
*end = '\0';
continue;
}
/* finally: add key */
cfg_add(section, key, val, 0);
}
/* sanity check: are the braces balanced? */
if (!error && *section != '\0') {
error("error in config file '%s' line %d: unbalanced braces", file, lineno);
error = 1;
}
fclose(stream);
return -error;
}
static void cfg_dump(void)
{
int i, len;
/* find longest key for pretty output */
len = 1;
for (i = 0; i < nConfig; i++) {
int l = strlen(Config[i].key);
if (l > len)
len = l;
}
info("Dump of %s:", Config_File);
for (i = 0; i < nConfig; i++) {
info(" %-*s %s", len, Config[i].key, Config[i].val);
}
info(" ");
}
int cfg_init(const char *file)
{
if (cfg_check_source(file) == -1) {
return -1;
}
if (cfg_read(file) < 0)
return -1;
if (Config_File)
free(Config_File);
Config_File = strdup(file);
if (verbose_level > 1)
cfg_dump();
return 0;
}
char *cfg_source(void)
{
if (Config_File)
return Config_File;
else
return "";
}
int cfg_exit(void)
{
int i;
for (i = 0; i < nConfig; i++) {
if (Config[i].key)
free(Config[i].key);
if (Config[i].val)
free(Config[i].val);
}
if (Config) {
free(Config);
Config = NULL;
}
if (Config_File) {
free(Config_File);
Config_File = NULL;
}
return 0;
}