busybox/libbb/parse_config.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * config file parser helper
   4 *
   5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
   6 *
   7 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   8 * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
   9 */
  10
  11/* Uncomment to enable test applet */
  12////config:config PARSE
  13////config:     bool "Uniform config file parser debugging applet: parse"
  14////config:     default n
  15////config:     help
  16////config:       Typical usage of parse API:
  17////config:             char *t[3];
  18////config:             parser_t *p = config_open(filename);
  19////config:             while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
  20////config:                     bb_error_msg("TOKENS: '%s''%s''%s'", t[0], t[1], t[2]);
  21////config:             }
  22////config:             config_close(p);
  23
  24////applet:IF_PARSE(APPLET(parse, BB_DIR_USR_BIN, BB_SUID_DROP))
  25
  26//kbuild:lib-y += parse_config.o
  27
  28//usage:#define parse_trivial_usage
  29//usage:       "[-x] [-n MAXTOKENS] [-m MINTOKENS] [-d DELIMS] [-f FLAGS] FILE..."
  30//usage:#define parse_full_usage "\n\n"
  31//usage:       "        -x      Suppress output (for benchmarking)"
  32
  33#include "libbb.h"
  34
  35#if defined ENABLE_PARSE && ENABLE_PARSE
  36int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  37int parse_main(int argc UNUSED_PARAM, char **argv)
  38{
  39        const char *delims = "# \t";
  40        char **t;
  41        unsigned flags = PARSE_NORMAL;
  42        int mintokens = 0, ntokens = 128;
  43        unsigned noout;
  44
  45        opt_complementary = "-1:n+:m+:f+";
  46        noout = 1 & getopt32(argv, "xn:m:d:f:", &ntokens, &mintokens, &delims, &flags);
  47        //argc -= optind;
  48        argv += optind;
  49
  50        t = xmalloc(sizeof(t[0]) * ntokens);
  51        while (*argv) {
  52                int n;
  53                parser_t *p = config_open(*argv);
  54                while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
  55                        if (!noout) {
  56                                for (int i = 0; i < n; ++i)
  57                                        printf("[%s]", t[i]);
  58                                puts("");
  59                        }
  60                }
  61                config_close(p);
  62                argv++;
  63        }
  64        return EXIT_SUCCESS;
  65}
  66#endif
  67
  68parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
  69{
  70        FILE* fp;
  71        parser_t *parser;
  72
  73        fp = fopen_func(filename);
  74        if (!fp)
  75                return NULL;
  76        parser = xzalloc(sizeof(*parser));
  77        parser->fp = fp;
  78        return parser;
  79}
  80
  81parser_t* FAST_FUNC config_open(const char *filename)
  82{
  83        return config_open2(filename, fopen_or_warn_stdin);
  84}
  85
  86void FAST_FUNC config_close(parser_t *parser)
  87{
  88        if (parser) {
  89                if (PARSE_KEEP_COPY) /* compile-time constant */
  90                        free(parser->data);
  91                fclose(parser->fp);
  92                free(parser->line);
  93                free(parser->nline);
  94                free(parser);
  95        }
  96}
  97
  98/* This function reads an entire line from a text file,
  99 * up to a newline, exclusive.
 100 * Trailing '\' is recognized as line continuation.
 101 * Returns -1 if EOF/error.
 102 */
 103static int get_line_with_continuation(parser_t *parser)
 104{
 105        ssize_t len, nlen;
 106        char *line;
 107
 108        len = getline(&parser->line, &parser->line_alloc, parser->fp);
 109        if (len <= 0)
 110                return len;
 111
 112        line = parser->line;
 113        for (;;) {
 114                parser->lineno++;
 115                if (line[len - 1] == '\n')
 116                        len--;
 117                if (len == 0 || line[len - 1] != '\\')
 118                        break;
 119                len--;
 120
 121                nlen = getline(&parser->nline, &parser->nline_alloc, parser->fp);
 122                if (nlen <= 0)
 123                        break;
 124
 125                if (parser->line_alloc < len + nlen + 1) {
 126                        parser->line_alloc = len + nlen + 1;
 127                        line = parser->line = xrealloc(line, parser->line_alloc);
 128                }
 129                memcpy(&line[len], parser->nline, nlen);
 130                len += nlen;
 131        }
 132
 133        line[len] = '\0';
 134        return len;
 135}
 136
 137
 138/*
 1390. If parser is NULL return 0.
 1401. Read a line from config file. If nothing to read then return 0.
 141   Handle continuation character. Advance lineno for each physical line.
 142   Discard everything past comment character.
 1432. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
 1443. If resulting line is empty goto 1.
 1454. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
 146   remember the token as empty.
 1475. Else (default) if number of seen tokens is equal to max number of tokens
 148   (token is the last one) and PARSE_GREEDY is set then the remainder
 149   of the line is the last token.
 150   Else (token is not last or PARSE_GREEDY is not set) just replace
 151   first delimiter with '\0' thus delimiting the token.
 1526. Advance line pointer past the end of token. If number of seen tokens
 153   is less than required number of tokens then goto 4.
 1547. Check the number of seen tokens is not less the min number of tokens.
 155   Complain or die otherwise depending on PARSE_MIN_DIE.
 1568. Return the number of seen tokens.
 157
 158mintokens > 0 make config_read() print error message if less than mintokens
 159(but more than 0) are found. Empty lines are always skipped (not warned about).
 160*/
 161#undef config_read
 162int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
 163{
 164        char *line;
 165        int ntokens, mintokens;
 166        int t;
 167
 168        if (!parser)
 169                return 0;
 170
 171        ntokens = (uint8_t)flags;
 172        mintokens = (uint8_t)(flags >> 8);
 173
 174 again:
 175        memset(tokens, 0, sizeof(tokens[0]) * ntokens);
 176
 177        /* Read one line (handling continuations with backslash) */
 178        if (get_line_with_continuation(parser) < 0)
 179                return 0;
 180
 181        line = parser->line;
 182
 183        /* Skip token in the start of line? */
 184        if (flags & PARSE_TRIM)
 185                line += strspn(line, delims + 1);
 186
 187        if (line[0] == '\0' || line[0] == delims[0])
 188                goto again;
 189
 190        if (flags & PARSE_KEEP_COPY) {
 191                free(parser->data);
 192                parser->data = xstrdup(line);
 193        }
 194
 195        /* Tokenize the line */
 196        t = 0;
 197        do {
 198                /* Pin token */
 199                tokens[t] = line;
 200
 201                /* Combine remaining arguments? */
 202                if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
 203                        /* Vanilla token, find next delimiter */
 204                        line += strcspn(line, delims[0] ? delims : delims + 1);
 205                } else {
 206                        /* Combining, find comment char if any */
 207                        line = strchrnul(line, delims[0]);
 208
 209                        /* Trim any extra delimiters from the end */
 210                        if (flags & PARSE_TRIM) {
 211                                while (strchr(delims + 1, line[-1]) != NULL)
 212                                        line--;
 213                        }
 214                }
 215
 216                /* Token not terminated? */
 217                if (*line == delims[0])
 218                        *line = '\0';
 219                else if (*line != '\0')
 220                        *line++ = '\0';
 221
 222#if 0 /* unused so far */
 223                if (flags & PARSE_ESCAPE) {
 224                        strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
 225                }
 226#endif
 227                /* Skip possible delimiters */
 228                if (flags & PARSE_COLLAPSE)
 229                        line += strspn(line, delims + 1);
 230
 231                t++;
 232        } while (*line && *line != delims[0] && t < ntokens);
 233
 234        if (t < mintokens) {
 235                bb_error_msg("bad line %u: %d tokens found, %d needed",
 236                                parser->lineno, t, mintokens);
 237                if (flags & PARSE_MIN_DIE)
 238                        xfunc_die();
 239                goto again;
 240        }
 241
 242        return t;
 243}
 244