/* 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 "../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; }