From f90f421b165c7880cd88db8795f00073dd768f60 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 13 Mar 2021 17:10:08 +0100 Subject: Add libs --- src/liboptions/Makefile.am | 7 + src/liboptions/options.c | 339 +++++++++++++++++++++++++++++++++++++++++++++ src/liboptions/options.h | 8 ++ 3 files changed, 354 insertions(+) create mode 100644 src/liboptions/Makefile.am create mode 100644 src/liboptions/options.c create mode 100644 src/liboptions/options.h (limited to 'src/liboptions') diff --git a/src/liboptions/Makefile.am b/src/liboptions/Makefile.am new file mode 100644 index 0000000..0d10fa7 --- /dev/null +++ b/src/liboptions/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -Wall -Wextra -g $(all_includes) + +noinst_LIBRARIES = liboptions.a + +liboptions_a_SOURCES = \ + options.c + diff --git a/src/liboptions/options.c b/src/liboptions/options.c new file mode 100644 index 0000000..d49e698 --- /dev/null +++ b/src/liboptions/options.c @@ -0,0 +1,339 @@ +/* command line options and config file parsing + * + * (C) 2018 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include "options.h" +#include "../libdebug/debug.h" + +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) { + PDEBUG(DOPTIONS, DEBUG_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 */ + for (option = option_head; option; option = option->next) { + if (!strcmp(option->long_option, "config")) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' is not allowed to add, please fix!\n", option->long_option); + abort(); + } + if (option->short_option == short_option + || !strcmp(option->long_option, long_option)) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Option '%s' added twice, please fix!\n", option->long_option); + abort(); + } + } + + option = calloc(1, sizeof(*option)); + if (!option) { + PDEBUG(DOPTIONS, DEBUG_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")) + config_file = argv[2]; + + /* add home directory */ + if (config_file[0] == '~' && config_file[1] == '/') { + home = getenv("HOME"); + if (home == NULL) + return 1; + sprintf(config, "%s/%s", home, config_file + 2); + } else + strcpy(config, config_file); + + /* open config file */ + fp = fopen(config, "r"); + if (!fp) { + PDEBUG(DOPTIONS, DEBUG_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') { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s' ('%s'), parameter%s\n", opt, option->long_option, params); + break; + } + if (!strcmp(opt, option->long_option)) { + PDEBUG(DOPTIONS, DEBUG_INFO, "Config file option '%s', parameter%s\n", opt, params); + break; + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d is not a valid option, use '-h' for help!\n", opt, config_file, line); + rc = -EINVAL; + goto done; + } + if (option->parameter_count != i) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given option '%s' in config file '%s' at line %d requires %d parameter(s), use '-h' for help!\n", opt, 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) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' must be the first option specified, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + if (argc <= 2) { + PDEBUG(DOPTIONS, DEBUG_ERROR, "Given command line option '%s' requires 1 parameter, use '-h' for help!\n", argv[argi]); + return -EINVAL; + } + argi += 1; + continue; + } + if (argv[argi][0] == '-') { + if (argv[argi][1] != '-') { + if (strlen(argv[argi]) != 2) { + PDEBUG(DOPTIONS, DEBUG_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]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s' ('--%s'), parameter%s\n", argv[argi], option->long_option, params); + } else + PDEBUG(DOPTIONS, DEBUG_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]); + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s', parameter%s\n", argv[argi], params); + } else + PDEBUG(DOPTIONS, DEBUG_INFO, "Command line option '%s'\n", argv[argi]); + break; + } + } + } + if (!option) { + PDEBUG(DOPTIONS, DEBUG_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) { + PDEBUG(DOPTIONS, DEBUG_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] == '-') { + PDEBUG(DOPTIONS, DEBUG_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; +} + diff --git a/src/liboptions/options.h b/src/liboptions/options.h new file mode 100644 index 0000000..5f494e0 --- /dev/null +++ b/src/liboptions/options.h @@ -0,0 +1,8 @@ + +char *options_strdup(const char *s); +void option_add(int short_option, const char *long_option, int parameter_count); +int options_config_file(int argc, char *argv[], const char *config_file, int (*handle_options)(int short_option, int argi, char *argv[])); +int options_command_line(int argc, char *argv[], int (*handle_options)(int short_option, int argi, char *argv[])); +int option_is_first(void); +void options_free(void); + -- cgit v1.2.3