/*- * Copyright (c) 2008, 2009, 2012, 2013, 2016 - 2019 Peter Pentchev * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #ifdef HAVE_PCRE #include #endif #include "confget.h" #include "confget_http_get.h" #include "confget_ini.h" struct var { char *name; char *value; char *section; bool display; }; /* The configuration filename specified by the user. */ const char *filename; /* The configuration section specified by the user. */ const char *section; /* The number of the variable or pattern parameters after the options. */ static int margc; /* The variables to extract or patterns to match against. */ static char * const *margv; /* Display the variable names, or only the values? */ static bool showvarname; /* Look for more variables after a match is found? */ static bool manyvars; /* Should section variables override default ones? */ bool override; /* The pattern to match the variable value */ static const char *matchvalue; /* The prefix to use when displaying the variable name */ static const char *output_prefix; /* The postfix to use when displaying the variable name */ static const char *output_postfix; size_t varsalloc, varscount; struct var *vars; static const confget_backend * const backends[] = { &confget_http_get_backend, &confget_ini_backend, }; #define BACKEND_CNT (sizeof(backends) / sizeof(backends[0])) static size_t find_backend(const char * const _name); static void init(int _argc, char * const *_argv); static void usage(bool _error); static void version(void); /* The currently used configuration backend (ini, http_get, etc.) */ const confget_backend *backend; /* The currently processed input file */ FILE *conffile; /* Has the "-c" (check only) option been specified? */ bool cflag; /* In check-only mode, has the variable been found? */ bool cfound; /* Has the "-l" (list the section) option been specified? */ static bool lflag; /* Has the "-L" (match varname against patterns) option been specified? */ static bool Lflag; /* Query for the names of the sections in the configuration file? */ bool qsections; /* Query for a single feature? */ bool qfeature; /* Query for all the features? */ bool qfeatures; /* Shell-quote the variable values? */ static bool Sflag; #ifdef HAVE_PCRE /* Treat patterns as regular expressions? */ static bool xflag; static const pcre *x_matchvalue, **x_margv; static const pcre_extra *x_matchvalue_extra, **x_margv_extra; #endif /*** * Function: * usage - display program usage information and exit * Inputs: * error - is this invoked in error? * Returns: * Nothing. * Modifies: * Writes the usage information to the standard output or standard * error depending on the "error" flag. */ void usage(const bool error) { const char * const s1 = "Usage:\n" "confget [-cOSx] [-N | -n] [-f filename] [-m pattern] [-P postfix]" " [-p prefix]\n" " [-s section] [-t type] var...\n\n" "confget [-OSx] [-N | -n] [-f filename] [-m pattern] [-P postfix]" " [-p prefix]\n" " [-s section] [-t type] -L pattern...\n\n" "confget [-OSx] [-N | -n] [-f filename] [-m pattern] [-P postfix]" " [-p prefix]\n" " [-s section] [-t type] -l\n" "confget [-f filename] -q sections [-t type]\n\n" "confget -q features\n" "confget -q feature NAME\n\n" "confget [-hTV]\n\n"; const char * const s2 = "\t-c\tcheck if the variable is defined in the file;\n" "\t-f\tspecify the configuration file to read from,\n" "\t\tor \"-\" for standard input;\n" "\t-h\tdisplay usage information and exit;\n" "\t-L\tspecify which variables to display;\n" "\t-l\tlist all variables in the specified section;\n" "\t-m\tonly display values that match the specified pattern;\n" "\t-N\talways display the variable name;\n" "\t-n\tnever display the variable name;\n" "\t-O\tallow variables in the specified section to override\n" "\t\tthose placed before any section definitions;\n"; const char * const s3 = "\t-P\tdisplay this string after the variable name;\n" "\t-p\tdisplay this string before the variable name;\n" "\t-q\tquery for a specific type of information, e.g. the list of\n" "\t\tsections defined in the configuration file;\n" "\t-S\tquote the values suitably for the Bourne shell;\n" "\t-s\tspecify the configuration section to read;\n" "\t-T\tlist the available configuration file types;\n" "\t-t\tspecify the configuration file type;\n" "\t-x\ttreat the match patterns as regular expressions;\n" "\t-V\tdisplay program version information and exit."; if (error) { fprintf(stderr, "%s%s%s\n", s1, s2, s3); exit(1); } printf("%s%s%s\n", s1, s2, s3); } /*** * Function: * version - display program version information * Inputs: * None. * Returns: * Nothing. * Modifies: * Nothing; displays program version information on the standard output. */ void version(void) { puts("confget 2.2.0"); } /*** * Function: * init - parse the command-line options * Inputs: * argc, argv - the command-line parameter * Returns: * 0 on success, -1 on error. * Modifies: * Sets section and varname on success. * Writes to the standard error on error. */ static void init(const int argc, char * const * const argv) { conffile = NULL; cflag = Lflag = lflag = Sflag = showvarname = manyvars = override = false; bool do_help = false, do_list = false, do_version = false, show_name = false, hide_name = false; matchvalue = NULL; output_prefix = output_postfix = ""; size_t bidx = find_backend("ini"); int ch; while ((ch = getopt(argc, argv, "cf:hLlm:NnOP:p:q:Ss:Tt:xV-:")) != EOF) { switch (ch) { case 'c': cflag = true; manyvars = false; break; case 'f': filename = optarg; break; case 'h': do_help = true; break; case 'L': Lflag = true; showvarname = true; manyvars = true; break; case 'l': lflag = true; showvarname = true; manyvars = true; break; case 'm': matchvalue = optarg; break; case 'N': show_name = true; break; case 'n': hide_name = true; break; case 'O': override = true; break; case 'P': output_postfix = optarg; break; case 'p': output_prefix = optarg; break; case 'q': if (strcmp(optarg, "sections") == 0) { qsections = true; } else if (strcmp(optarg, "feature") == 0) { qfeature = true; } else if (strcmp(optarg, "features") == 0) { qfeatures = true; } else { errx(1, "Unsupported query type"); } break; case 'S': Sflag = true; break; case 's': section = optarg; break; case 'T': do_list = true; break; case 't': bidx = find_backend(optarg); break; case 'x': #ifdef HAVE_PCRE xflag = true; break; #else errx(1, "No regular expression support in this confget build"); #endif case 'V': do_version = true; break; case '-': if (strcmp(optarg, "help") == 0) do_help = true; else if (strcmp(optarg, "version") == 0) do_version = true; else errx(1, "Unknown long option"); break; case '?': default: usage(true); break; } } if (do_version) version(); if (do_help) usage(false); if (do_list) { printf("Supported backends:"); for (size_t i = 0; i < BACKEND_CNT; i++) printf(" %s", backends[i]->name); puts(""); } if (do_help || do_list || do_version) exit(0); margc = argc - optind; margv = argv + optind; const char * const features[][2] = { { "BASE", "2.1" }, { NULL, NULL }, }; if (qsections + qfeature + qfeatures + lflag + Lflag + (margc > 0 && !(Lflag || qfeature)) > 1) { errx(1, "Only a single query at a time, please!"); } else if (qfeatures) { if (margc > 0) errx(1, "No arguments with -q features"); bool started = false; for (size_t i = 0; features[i][0] != NULL; i++) { if (started) putchar(' '); else started = true; printf("%s=%s", features[i][0], features[i][1]); } putchar('\n'); exit(0); } else if (qfeature) { if (margc != 1) errx(1, "A single feature name expected"); for (size_t i = 0; features[i][0] != NULL; i++) if (strcmp(margv[0], features[i][0]) == 0) { puts(features[i][1]); exit(0); } /* Feature not found */ exit(1); } if (cflag && (lflag || Lflag)) errx(1, "The -c flag may not be used with -l or -L"); if (margc == 0 && !lflag && !qsections) errx(1, "No matching criteria specified"); if ((margc > 0) && lflag) errx(1, "Too many matching criteria specified"); if (override && (section == NULL || section[0] == '\0')) errx(1, "The -O flag only makes sense with a non-empty section name"); if (margc > 1 && !lflag) { showvarname = true; manyvars = true; } if (show_name) { if (hide_name) errx(1, "The -N and -n flags may not be used together"); showvarname = true; } else if (hide_name) { showvarname = false; } #ifdef HAVE_PCRE if (xflag) { if (matchvalue) { const char *pcre_err; int pcre_ofs; x_matchvalue = pcre_compile(matchvalue, 0, &pcre_err, &pcre_ofs, NULL); if (pcre_err != NULL) errx(1, "Invalid match value: %s", pcre_err); x_matchvalue_extra = pcre_study(x_matchvalue, 0, &pcre_err); if (pcre_err != NULL) errx(1, "Invalid match value: %s", pcre_err); } else { x_matchvalue = NULL; } x_margv = malloc(margc * sizeof(*x_margv)); if (x_margv == NULL) err(1, "Could not allocate memory"); x_margv_extra = malloc(margc * sizeof(*x_margv_extra)); if (x_margv_extra == NULL) err(1, "Could not allocate memory"); for (size_t t = 0; t < (size_t)margc; t++) { const char *pcre_err; int pcre_ofs; x_margv[t] = pcre_compile(margv[t], 0, &pcre_err, &pcre_ofs, NULL); if (pcre_err != NULL) errx(1, "Invalid match pattern: %s", pcre_err); x_margv_extra[t] = pcre_study(x_margv[t], 0, &pcre_err); if (pcre_err != NULL) errx(1, "Invalid match pattern: %s", pcre_err); } } #endif if (qsections && strcmp(backends[bidx]->name, "ini") != 0) errx(1, "The query for sections is only supported for the " "'ini' backend for the present"); backend = backends[bidx]; } /*** * Function: * find_backend - find a confget backend by name * Inputs: * name - the name of the backend * Returns: * The index in backends[] if found; exits on error. * Modifies: * Nothing. */ static size_t find_backend(const char * const name) { const size_t len = strlen(name); size_t tentative = BACKEND_CNT; for (size_t i = 0; i < BACKEND_CNT; i++) { if (strncmp(name, backends[i]->name, len) != 0) continue; if (backends[i]->name[len] == '\0') return (i); if (tentative != BACKEND_CNT) errx(1, "Ambiguous backend prefix '%s'", name); tentative = i; } if (tentative == BACKEND_CNT) errx(1, "Unknown backend '%s'", name); return (tentative); } /*** * Function: * openfile - open the requested file for reading * Inputs: * None. * Returns: * Nothing; exits on error. * Modifies: * Stores the opened file into fp. */ static void openfile(void) { if (backend->openfile == NULL) errx(2, "INTERNAL ERROR: backend '%s' does not define a openfile routine\n", backend->name); backend->openfile(); } /*** * Function: * readfile - scan an INI file for the requested variable * Inputs: * None. * Returns: * Nothing; exits on error. * Modifies: * May write to standard output if the variable has been found. */ static void readfile(void) { if (backend->readfile == NULL) errx(2, "INTERNAL ERROR: backend '%s' does not define a readfile routine\n", backend->name); backend->readfile(); } /*** * Function: * closefile - close a scanned INI file * Inputs: * None. * Returns: * Nothing; exits on error. * Modifies: * Closes the file pointed to by fp. */ static void closefile(void) { if (backend->closefile == NULL) errx(2, "INTERNAL ERROR: backend '%s' does not define a closefile routine\n", backend->name); backend->closefile(); } /*** * Function: * foundvar - process the user-requested variable * Inputs: * sec - the section the variable was found in * name - the variable name * value - the variable value * Returns: * Nothing; exits on error. * Modifies: * In check-only mode, sets cfound. * In normal mode, writes to standard output. */ void foundvar(const char * const sec, const char * const name, const char * const value) { if (!lflag) { bool found = false; for (int i = 0; i < margc; i++) if (Lflag) { #ifdef HAVE_PCRE if (xflag) { const int r = pcre_exec(x_margv[i], x_margv_extra[i], name, strlen(name), 0, 0, NULL, 0); if (r == 0) { found = true; break; } if (r != PCRE_ERROR_NOMATCH) errx(1, "Could not match '%s' against the '%s' pattern", name, margv[i]); } else #endif if (fnmatch(margv[i], name, 0) == 0) { found = true; break; } } else { if (strcmp(name, margv[i]) == 0) { found = true; break; } } if (!found) return; } bool display = true; if (matchvalue != NULL) { #ifdef HAVE_PCRE if (xflag) { const int r = pcre_exec(x_matchvalue, x_matchvalue_extra, value, strlen(value), 0, 0, NULL, 0); if (r == PCRE_ERROR_NOMATCH) display = false; else if (r != 0) errx(1, "Could not match '%s' against the '%s' pattern", value, matchvalue); } else #endif if (fnmatch(matchvalue, value, 0) == FNM_NOMATCH) display = false; if (!display && !override) return; } for (size_t i = 0; i < varscount; i++) { struct var * const var = &vars[i]; if (strcmp(var->name, name) != 0) continue; free(var->value); var->value = strdup(value); free(var->section); var->section = sec == NULL? NULL: strdup(sec); var->display = display; return; } if (varscount == varsalloc) { varsalloc += 16; vars = realloc(vars, varsalloc * sizeof(*vars)); if (vars == NULL) err(1, "Could not allocate memory for the variables' values"); } vars[varscount].name = strdup(name); vars[varscount].value = strdup(value); vars[varscount].section = sec == NULL? NULL: strdup(sec); vars[varscount].display = display; varscount++; } static void displayvars(void) { cfound = false; for (size_t i = 0; i < varscount; i++) { if (!vars[i].display) continue; if (cflag) { cfound = true; return; } const char * const name = vars[i].name; const char * const value = vars[i].value; if (showvarname) printf("%s%s%s=", output_prefix, name, output_postfix); if (!Sflag) { printf("%s\n", value); } else { printf("'"); const char *p = value; while (*p) { while (*p && *p != '\'') putchar(*p++); if (*p == '\0') break; printf("'\""); while (*p == '\'') putchar(*p++); printf("\"'"); } printf("'\n"); } } } /*** * Function: * foundsection - process a new section header * Inputs: * sec - the name of the new section * Returns: * Nothing; exits on error. * Modifies: * In "-q sections" mode, writes to standard output. */ void foundsection(const char * const _sec) { if (!qsections) return; static size_t alloc = 0, count = 0; static char **names = NULL; for (size_t i = 0; i < count; i++) if (strcmp(_sec, names[i]) == 0) return; /* A new section! */ if (alloc == count) { const size_t nalloc = 2 * alloc + 1; char ** const newnames = realloc(names, nalloc * sizeof(names[0])); if (newnames == NULL) err(1, "Out of memory for the section names list"); names = newnames; alloc = nalloc; } names[count] = strdup(_sec); if (names[count] == NULL) err(1, "Out of memory for the new section name"); count++; /* And finally output it :) */ puts(_sec); } /*** * Main routine */ int main(const int argc, char * const * const argv) { init(argc, argv); openfile(); readfile(); closefile(); displayvars(); return cflag? !cfound: 0; }