You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

698 lines
19 KiB
C

/*
* cfgfile.c -- routines to handle external configuration files
* Written by Andrew Church <achurch@achurch.org>
*
* This file is part of transcode, a video stream processing tool.
* transcode is free software, distributable under the terms of the GNU
* General Public License (version 2 or later). See the file COPYING
* for details.
*/
#define _ISOC99_SOURCE /* needed by glibc to declare strtof() */
#include "transcode.h"
#include "libtc.h"
#include "cfgfile.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static char *config_dir = NULL;
static int parse_line(const char *buf, TCConfigEntry *conf, const char *tag,
const char *filename, int line);
static void parse_line_error(const char *buf, const char *filename, int line,
const char *tag, const char *format, ...)
#ifdef HAVE_GCC_ATTRIBUTES
__attribute__((format(printf,5,6)))
#endif
;
/*************************************************************************/
/* helpers macros and functions */
#define CLEANUP_LINE(line) do { \
/* skip comments, if any */ \
char *s = strchr((line), '#'); \
if (s) { \
*s = 0; \
} \
/* Remove leading and trailing spaces, if any */ \
tc_strstrip((line)); \
} while (0)
/**
* fopen_verbose: Opens a configuration file in read only mode, printing
* meaningful error messages in case of failure.
*
* Parameters:
* name: Name of configuration file to open.
* tag: Tag to use in log messages.
* Return value:
* Read-only FILE pointer to configuration file, NULL if failed.
*/
static FILE *fopen_verbose(const char *name, const char *tag)
{
FILE *f = fopen(name, "r");
if (!f) {
if (errno == EEXIST) {
tc_log_warn(tag, "Configuration file %s does not exist!",
name);
} else if (errno == EACCES) {
tc_log_warn(tag, "Configuration file %s cannot be read!",
name);
} else {
tc_log_warn(tag, "Error opening configuration file %s: %s",
name, strerror(errno));
}
}
return f;
}
/**
* lookup_section: Move FILE pointer to begining of data belonging to
* given section.
*
* Parameters:
* f: Already open FILE pointer to configuration file.
* section: Name of section to lookup.
* tag: Tag to use in log messages.
* Return value:
* number of lines skipped (>= 0) if succesfull.
* < 0 if error occurs.
*/
static int lookup_section(FILE *f, const char *section, const char *tag)
{
char expect[TC_BUF_MAX], buf[TC_BUF_MAX];
int line = 0;
tc_snprintf(expect, sizeof(expect), "[%s]", section);
do {
if (!fgets(buf, sizeof(buf), f)) {
tc_log_warn(tag, "Section [%s] not found in configuration"
" file!", section);
return -1;
}
line++;
CLEANUP_LINE(buf);
} while (strcmp(buf, expect) != 0);
return line;
}
/*************************************************************************/
/**
* tc_set_config_dir: Sets the directory in which configuration files are
* searched for.
*
* Parameters:
* dir: Directory to search for configuration files in. If NULL, the
* current directory is used.
* Return value:
* None.
*/
void tc_set_config_dir(const char *dir)
{
tc_free(config_dir);
config_dir = dir ? tc_strdup(dir) : NULL;
}
/*************************************************************************/
/**
* module_read_config: Reads in configuration information from an external
* file.
*
* Parameters:
* filename: Name of the configuration file to read.
* section: Section to read within the file, or NULL to read the
* entire file regardless of sections.
* conf: Array of configuration entries.
* tag: Tag to use in log messages.
* Return value:
* Nonzero on success, zero on failure.
*/
int module_read_config(const char *filename, const char *section,
TCConfigEntry *conf, const char *tag)
{
char buf[TC_BUF_MAX], path_buf[PATH_MAX+1];
FILE *f = NULL;
int line = 0;
/* Sanity checks */
if (!tag)
tag = __FILE__;
if (!filename || !conf) {
tc_log_error(tag, "module_read_config(): %s == NULL!",
!filename ? "filename" : !conf ? "conf" : "???");
return 0;
}
/* Open the file */
tc_snprintf(path_buf, sizeof(path_buf), "%s/%s",
config_dir ? config_dir : ".", filename);
f = fopen_verbose(path_buf, tag);
if (!f) {
return 0;
}
if (section) {
line = lookup_section(f, section, tag);
if (line == -1) {
/* error */
fclose(f);
return 0;
}
}
/* Read in the configuration values (up to the end of the section, if
* a section name was given) */
while (fgets(buf, sizeof(buf), f)) {
line++;
CLEANUP_LINE(buf);
/* Ignore empty lines and comment lines */
if (!*buf || *buf == '#')
continue;
/* If it's a section name, this is the end of the current section */
if (*buf == '[') {
if (section)
break;
else
continue;
}
/* Pass it on to the parser */
parse_line(buf, conf, tag, path_buf, line);
}
fclose(f);
return 1;
}
/*************************************************************************/
/**
* module_read_config_line: Processes a string as if it were a line read
* from a configuration file. The string must have all leading and
* trailing whitespace stripped.
*
* Parameters:
* string: String to process.
* conf: Array of configuration entries.
* tag: Tag to use in log messages.
* Return value:
* Nonzero if the string was successfully processed, else zero.
*/
int module_read_config_line(const char *string, TCConfigEntry *conf,
const char *tag)
{
if (!tag)
tag = __FILE__;
if (!string || !conf) {
tc_log_error(tag, "module_read_config_line(): %s == NULL!",
!string ? "string" : !conf ? "conf" : "???");
return 0;
}
return parse_line(string, conf, tag, NULL, 0);
}
/*************************************************************************/
/**
* module_print_config: Prints the given array of configuration data.
*
* Parameters:
* conf: Array of configuration data.
* tag: Tag to use in log messages.
* Return value:
* None.
*/
void module_print_config(const TCConfigEntry *conf, const char *tag)
{
/* Sanity checks */
if (!tag)
tag = __FILE__;
if (!conf) {
tc_log_error(tag, "module_print_config(): conf == NULL!");
return;
}
while (conf->name) {
char buf[TC_BUF_MAX];
switch (conf->type) {
case TCCONF_TYPE_FLAG:
tc_snprintf(buf, sizeof(buf), "%d", *((int *)conf->ptr) ? 1 : 0);
break;
case TCCONF_TYPE_INT:
tc_snprintf(buf, sizeof(buf), "%d", *((int *)conf->ptr));
break;
case TCCONF_TYPE_FLOAT:
tc_snprintf(buf, sizeof(buf), "%f", *((float *)conf->ptr));
break;
case TCCONF_TYPE_STRING:
tc_snprintf(buf, sizeof(buf), "%s", *((char **)conf->ptr));
break;
}
tc_log_info(tag, "%s = %s", conf->name, buf);
conf++;
}
}
/*************************************************************************/
/*************************************************************************/
/**
* parse_line: Internal routine to parse a single line of a configuration
* file and set the appropriate variable.
*
* Parameters:
* buf: Line to process. Leading and trailing whitespace
* (including newlines) are assumed to have been stripped.
* conf: Array of configuration entries.
* tag: Tag to use in log messages.
* filename: Name of file being processed, or NULL if none.
* line: Current line number in file.
* Return value:
* Nonzero if the buffer was successfully parsed, else 0.
* Preconditions:
* buf != NULL
* conf != NULL
* tag != NULL
*/
static int parse_line(const char *buf, TCConfigEntry *conf, const char *tag,
const char *filename, int line)
{
char workbuf[TC_BUF_MAX];
char *name, *value, *s;
/* Make a working copy of the string */
if (strlcpy(workbuf, buf, sizeof(workbuf)) >= sizeof(workbuf)) {
parse_line_error(buf, filename, line, tag,
"Buffer overflow while parsing configuration data");
return 0;
}
/* Split string into name and value */
name = workbuf;
s = strchr(workbuf, '=');
if (s) {
value = s+1;
while (s > workbuf && isspace(s[-1]))
s--;
*s = 0;
while (isspace(*value))
value++;
} else {
value = NULL;
}
if (!*name) {
parse_line_error(buf, filename, line, tag,
"Syntax error in option (missing variable name)");
return 0;
} else if (value && !*value) {
parse_line_error(buf, filename, line, tag,
"Syntax error in option (missing value)");
return 0;
}
/* Look for a matching configuration entry */
while (conf->name) {
if (strcmp(conf->name, name) == 0)
break;
conf++;
}
if (!conf->name) {
parse_line_error(buf, filename, line, tag,
"Unknown configuration variable `%s'", name);
return 0;
}
/* Make sure non-flag entries have values */
if (conf->type != TCCONF_TYPE_FLAG && !value) {
parse_line_error(buf, filename, line, tag,
"Syntax error in option (missing value)");
return 0;
}
/* Set the value appropriately */
switch (conf->type) {
case TCCONF_TYPE_FLAG:
if (!value
|| strcmp(value,"1" ) == 0
|| strcmp(value,"yes" ) == 0
|| strcmp(value,"on " ) == 0
|| strcmp(value,"true") == 0
) {
*((int *)(conf->ptr)) = (int)conf->max;
} else if (strcmp(value,"0" ) == 0
|| strcmp(value,"no" ) == 0
|| strcmp(value,"off" ) == 0
|| strcmp(value,"false") == 0
) {
*((int *)(conf->ptr)) = 0;
} else {
parse_line_error(buf, filename, line, tag,
"Value for variable `%s' must be either 1 or 0",
name);
return 0;
}
break;
case TCCONF_TYPE_INT: {
long lvalue;
errno = 0;
lvalue = strtol(value, &value, 0);
if (*value) {
parse_line_error(buf, filename, line, tag,
"Value for variable `%s' must be an integer",
name);
return 0;
} else if (errno == ERANGE
#if LONG_MIN < INT_MIN
|| lvalue < INT_MIN
#endif
#if LONG_MAX < INT_MAX
|| lvalue > INT_MAX
#endif
|| ((conf->flags & TCCONF_FLAG_MIN) && lvalue < conf->min)
|| ((conf->flags & TCCONF_FLAG_MAX) && lvalue > conf->max)
) {
parse_line_error(buf, filename, line, tag,
"Value for variable `%s' is out of range", name);
return 0;
} else {
*((int *)(conf->ptr)) = (int)lvalue;
}
break;
}
case TCCONF_TYPE_FLOAT: {
float fvalue;
errno = 0;
#ifdef HAVE_STRTOF
fvalue = strtof(value, &value);
#else
fvalue = (float)strtod(value, &value);
#endif
if (*value) {
parse_line_error(buf, filename, line, tag,
"Value for variable `%s' must be a number", name);
return 0;
} else if (errno == ERANGE
|| ((conf->flags & TCCONF_FLAG_MIN) && fvalue < conf->min)
|| ((conf->flags & TCCONF_FLAG_MAX) && fvalue > conf->max)
) {
parse_line_error(buf, filename, line, tag,
"Value for variable `%s' is out of range", name);
return 0;
} else {
*((float *)(conf->ptr)) = fvalue;
}
break;
}
case TCCONF_TYPE_STRING: {
char *newval = tc_strdup(value);
if (!newval) {
parse_line_error(buf, filename, line, tag,
"Out of memory setting variable `%s'", name);
return 0;
} else {
*((char **)(conf->ptr)) = newval;
}
break;
}
default:
parse_line_error(buf, filename, line, tag,
"Unknown type %d for variable `%s' (bug?)",
conf->type, name);
return 0;
} /* switch (conf->type) */
return 1;
}
/*************************************************************************/
/**
* parse_line_error: Helper routine for parse_line() to print an error
* message, formatted appropriately depending on whether a filename was
* given or not.
*
* Parameters:
* buf: String that caused the error.
* filename: Name of file being processed, or NULL if none.
* line: Current line number in file.
* tag: Tag to use in log message.
* format: Format string for message.
* Return value:
* None.
* Preconditions:
* buf != NULL
* tag != NULL
* format != NULL
*/
static void parse_line_error(const char *buf, const char *filename, int line,
const char *tag, const char *format, ...)
{
char msgbuf[TC_BUF_MAX];
va_list args;
va_start(args, format);
tc_vsnprintf(msgbuf, sizeof(msgbuf), format, args);
va_end(args);
if (filename) {
tc_log_warn(tag, "%s:%d: %s", filename, line, msgbuf);
} else {
tc_log_warn(tag, "\"%s\": %s", buf, msgbuf);
}
}
/*************************************************************************/
/**
* list_new: create a new list item storing a copy of given data string.
*
* Parameters:
* value: string to copy into new list item.
* Return value:
* pointer to new list item if succesfull.
* NULL otherwise.
*/
static TCConfigList *list_new(const char *value)
{
TCConfigList *list = tc_malloc(sizeof(TCConfigList));
if (list) {
list->next = NULL;
list->last = NULL;
list->value = NULL;
if (value) {
list->value = tc_strdup(value);
if (!list->value) {
tc_free(list);
list = NULL;
}
}
}
return list;
}
/**
* list_append: append a new item storing given data on a given list.
*
* Parameters:
* list: list to append new item
* value: value to be copied into new item
* Return value:
* 0 succesfull
* !0 error
*/
static int list_append(TCConfigList *list, const char *value)
{
int ret = 1;
TCConfigList *item = list_new(value);
if (item) {
if (list->last != NULL) {
/* typical case */
list->last->next = item;
} else {
/* second item, special case */
list->next = item;
}
list->last = item;
ret = 0;
}
return ret;
}
/**
* module_read_config_list: Read a list section from given configuration
* file and return the corresponding data list.
*
* Parameters:
* filename: Name of file being processed.
* section: Name of the section to be read.
* tag: Tag to use in log message.
* Return value:
* A pointer to a valid configuration list structure if succesfull,
* NULL otherwise.
*/
TCConfigList *module_read_config_list(const char *filename,
const char *section, const char *tag)
{
char buf[TC_BUF_MAX], path_buf[PATH_MAX+1];
TCConfigList *list = NULL;
FILE *f = NULL;
int line = 0;
/* Sanity checks */
if (!tag)
tag = __FILE__;
if (!filename) {
tc_log_error(tag, "module_read_config(): missing filename");
return 0;
}
if (!section) {
tc_log_error(tag, "module_read_config(): missing section");
return 0;
}
/* Open the file */
tc_snprintf(path_buf, sizeof(path_buf), "%s/%s",
config_dir ? config_dir : ".", filename);
f = fopen_verbose(path_buf, tag);
if (!f) {
return NULL;
}
line = lookup_section(f, section, tag);
if (line == -1) {
/* error */
fclose(f);
return NULL;
}
/* Read in the configuration values (up to the end of the section, if
* a section name was given) */
while (fgets(buf, sizeof(buf), f)) {
line++;
CLEANUP_LINE(buf);
/* Ignore empty lines and comment lines */
if (!*buf || *buf == '#')
continue;
/* If it's a section name, this is the end of the current section */
if (*buf == '[') {
break;
} else {
int err = 1;
if (list) {
err = list_append(list, buf);
} else {
list = list_new(buf);
err = (list == NULL) ?1 :0;
}
if (err) {
tc_log_error(tag, "out of memory at line %i", line);
module_free_config_list(list, 0);
list = NULL;
}
}
}
fclose(f);
return list;
}
/**
* module_free_config_list: Dispose a configuration list created by
* module_read_config_list.
*
* Parameters:
* list: Head of configuration list to free.
* refonly: If !0, DO NOT free data pointed to list;
* If 0, free list itslef AND data as well.
* Recap: use 0 to free everything, use !0 if you
* do want to preserve data for some reasons.
* Return value:
* None.
*/
void module_free_config_list(TCConfigList *list, int refonly)
{
TCConfigList *item = NULL;
while (list) {
item = list->next;
if (!refonly) {
tc_free((void*)list->value);
}
tc_free(list);
list = item;
}
}
/**
* module_print_config_list: Prints the given configuration list.
*
* Parameters:
* list: Head of configuration list.
* section: Name of section on which configuration list belongs.
* tag: Tag to use in log messages.
* Return value:
* None.
*/
void module_print_config_list(const TCConfigList *list,
const char *section, const char *tag)
{
/* Sanity checks */
if (!tag)
tag = __FILE__;
if (!section) {
tc_log_error(tag, "module_print_config_list(): section == NULL!");
return;
}
if (!list) {
tc_log_error(tag, "module_print_config_list(): list == NULL!");
return;
}
tc_log_info(tag, "[%s]", section);
for (; list != NULL; list = list->next) {
tc_log_info(tag, "%s", list->value);
}
}
/*************************************************************************/
/*
* Local variables:
* c-file-style: "stroustrup"
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
* indent-tabs-mode: nil
* End:
*
* vim: expandtab shiftwidth=4:
*/