mirror of
https://github.com/netfun2000/lcd4linux.git
synced 2026-02-27 09:44:34 +08:00
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1006 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
715 lines
15 KiB
C
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;
|
|
}
|