osmocom-analog/src/liboptions/options.c

352 lines
9.7 KiB
C

/* command line options and config file parsing
*
* (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
* All Rights Reserved
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "options.h"
#include "../liblogging/logging.h"
const char *selected_config_file = NULL;
typedef struct option {
struct option *next;
int short_option;
const char *long_option;
int parameter_count;
} option_t;
static option_t *option_head = NULL;
static option_t **option_tailp = &option_head;
static int first_option = 1;
static struct options_strdup_entry {
struct options_strdup_entry *next;
char s[1];
} *options_strdup_list = NULL;
char *options_strdup(const char *s)
{
struct options_strdup_entry *o;
o = malloc(sizeof(struct options_strdup_entry) + strlen(s));
if (!o) {
LOGP(DOPTIONS, LOGL_ERROR, "No mem!\n");
abort();
}
o->next = options_strdup_list;
options_strdup_list = o;
strcpy(o->s, s);
return o->s;
}
void option_add(int short_option, const char *long_option, int parameter_count)
{
option_t *option;
/* check if option already exists or is not allowed */
if (!strcmp(long_option, "config") || !strcmp(long_option, "no-config")) {
LOGP(DOPTIONS, LOGL_ERROR, "Option '%s' is not allowed to add, please fix!\n", option->long_option);
abort();
}
for (option = option_head; option; option = option->next) {
if (option->short_option == short_option
|| !strcmp(option->long_option, long_option)) {
LOGP(DOPTIONS, LOGL_ERROR, "Option '%s' added twice, please fix!\n", option->long_option);
abort();
}
}
option = calloc(1, sizeof(*option));
if (!option) {
LOGP(DOPTIONS, LOGL_ERROR, "No mem!\n");
abort();
}
option->short_option = short_option;
option->long_option = long_option;
option->parameter_count = parameter_count;
*option_tailp = option;
option_tailp = &(option->next);
}
int options_config_file(int argc, char *argv[], const char *_config_file, int (*handle_options)(int short_option, int argi, char *argv[]))
{
static const char *home;
char config[256];
FILE *fp;
char buffer[256], opt[256], param[256], *p, *args[16];
char params[1024];
int line;
int rc = 1;
int i, j, quote;
option_t *option;
/* select for alternative config file */
if (argc > 2 && !strcmp(argv[1], "--config"))
selected_config_file = argv[2];
else
selected_config_file = _config_file;
/* select for alternative config file */
if (argc > 1 && !strcmp(argv[1], "--no-config")) {
selected_config_file = NULL;
return 1;
}
/* add home directory */
if (selected_config_file[0] == '~' && selected_config_file[1] == '/') {
home = getenv("HOME");
if (home == NULL)
return 1;
sprintf(config, "%s/%s", home, selected_config_file + 2);
} else
strcpy(config, selected_config_file);
/* open config file */
fp = fopen(config, "r");
if (!fp) {
LOGP(DOPTIONS, LOGL_INFO, "Config file '%s' seems not to exist, using command line options only.\n", config);
return 1;
}
/* parse config file */
line = 0;
while((fgets(buffer, sizeof(buffer), fp))) {
line++;
/* prevent buffer overflow */
buffer[sizeof(buffer) - 1] = '\0';
/* cut away new-line and white spaces */
while (buffer[0] && buffer[strlen(buffer) - 1] <= ' ')
buffer[strlen(buffer) - 1] = '\0';
p = buffer;
/* remove white spaces in front of first keyword */
while (*p > '\0' && *p <= ' ')
p++;
/* ignore '#' lines */
if (*p == '#')
continue;
/* get option form line */
i = 0;
while (*p > ' ')
opt[i++] = *p++;
opt[i] = '\0';
if (opt[0] == '\0')
continue;
/* skip white spaces behind option */
while (*p > '\0' && *p <= ' ')
p++;
/* get param from line */
params[0] = '\0';
i = 0;
while (*p) {
/* copy parameter */
j = 0;
quote = 0;
while (*p) {
/* escape allows all following characters */
if (*p == '\\') {
p++;
if (*p)
param[j++] = *p++;
continue;
}
/* no quote, check for them or break on white space */
if (quote == 0) {
if (*p == '\'') {
quote = 1;
p++;
continue;
}
if (*p == '\"') {
quote = 2;
p++;
continue;
}
if (*p <= ' ')
break;
}
/* single quote, check for unquote */
if (quote == 1 && *p == '\'') {
quote = 0;
p++;
continue;
}
/* double quote, check for unquote */
if (quote == 2 && *p == '\"') {
quote = 0;
p++;
continue;
}
/* copy character */
param[j++] = *p++;
}
param[j] = '\0';
args[i] = options_strdup(param);
sprintf(strchr(params, '\0'), " '%s'", param);
/* skip white spaces behind option */
while (*p > '\0' && *p <= ' ')
p++;
i++;
}
/* search option */
for (option = option_head; option; option = option->next) {
if (opt[0] == option->short_option && opt[1] == '\0') {
LOGP(DOPTIONS, LOGL_INFO, "Config file option '%s' ('%s'), parameter%s\n", opt, option->long_option, params);
break;
}
if (!strcmp(opt, option->long_option)) {
LOGP(DOPTIONS, LOGL_INFO, "Config file option '%s', parameter%s\n", opt, params);
break;
}
}
if (!option) {
LOGP(DOPTIONS, LOGL_ERROR, "Given option '%s' in config file '%s' at line %d is not a valid option, use '-h' for help!\n", opt, selected_config_file, line);
rc = -EINVAL;
goto done;
}
if (option->parameter_count != i) {
LOGP(DOPTIONS, LOGL_ERROR, "Given option '%s' in config file '%s' at line %d requires %d parameter(s), use '-h' for help!\n", opt, selected_config_file, line, option->parameter_count);
return -EINVAL;
}
rc = handle_options(option->short_option, 0, args);
if (rc <= 0)
goto done;
first_option = 0;
}
done:
/* close config file */
fclose(fp);
return rc;
}
int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[]))
{
option_t *option;
char params[1024];
int argi, i;
int rc;
for (argi = 1; argi < argc; argi++) {
/* --config */
if (!strcmp(argv[argi], "--config")) {
if (argi > 1) {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' must be the first option specified, use '-h' for help!\n", argv[argi]);
return -EINVAL;
}
if (argc <= 2) {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' requires 1 parameter, use '-h' for help!\n", argv[argi]);
return -EINVAL;
}
argi += 1;
continue;
}
if (!strcmp(argv[argi], "--no-config"))
continue;
if (argv[argi][0] == '-') {
if (argv[argi][1] != '-') {
if (strlen(argv[argi]) != 2) {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' exceeds one character, use '-h' for help!\n", argv[argi]);
return -EINVAL;
}
/* -x */
for (option = option_head; option; option = option->next) {
if (argv[argi][1] == option->short_option) {
if (option->parameter_count && argi + option->parameter_count < argc) {
params[0] = '\0';
for (i = 0; i < option->parameter_count; i++)
sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]);
LOGP(DOPTIONS, LOGL_INFO, "Command line option '%s' ('--%s'), parameter%s\n", argv[argi], option->long_option, params);
} else
LOGP(DOPTIONS, LOGL_INFO, "Command line option '%s' ('--%s')\n", argv[argi], option->long_option);
break;
}
}
} else {
/* --xxxxxx */
for (option = option_head; option; option = option->next) {
if (!strcmp(argv[argi] + 2, option->long_option)) {
if (option->parameter_count && argi + option->parameter_count < argc) {
params[0] = '\0';
for (i = 0; i < option->parameter_count; i++)
sprintf(strchr(params, '\0'), " '%s'", argv[argi + 1 + i]);
LOGP(DOPTIONS, LOGL_INFO, "Command line option '%s', parameter%s\n", argv[argi], params);
} else
LOGP(DOPTIONS, LOGL_INFO, "Command line option '%s'\n", argv[argi]);
break;
}
}
}
if (!option) {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' is not a valid option, use '-h' for help!\n", argv[argi]);
return -EINVAL;
}
if (argi + option->parameter_count >= argc) {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' requires %d parameter(s), use '-h' for help!\n", argv[argi], option->parameter_count);
return -EINVAL;
}
rc = handle_options(option->short_option, argi + 1, argv);
if (rc <= 0)
return rc;
first_option = 0;
argi += option->parameter_count;
} else
break;
}
/* no more options, so we check if there is an option after a non-option parameter */
for (i = argi; i < argc; i++) {
if (argv[i][0] == '-') {
LOGP(DOPTIONS, LOGL_ERROR, "Given command line option '%s' behind command line parameter '%s' not allowed! Please put all command line options before command line parameter(s).\n", argv[i], argv[argi]);
return -EINVAL;
}
}
return argi;
}
int option_is_first(void)
{
return first_option;
}
void options_free(void)
{
while (options_strdup_list) {
struct options_strdup_entry *o;
o = options_strdup_list;
options_strdup_list = o->next;
free(o);
}
while (option_head) {
option_t *o;
o = option_head;
option_head = o->next;
free(o);
}
option_tailp = &option_head;
}