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        noout = 1 & getopt32(argv, "^" "xn:+m:+d:f:+" "\0" "-1",
  46                                &ntokens, &mintokens, &delims, &flags
  47        );
  48        //argc -= optind;
  49        argv += optind;
  50
  51        t = xmalloc(sizeof(t[0]) * ntokens);
  52        while (*argv) {
  53                int n;
  54                parser_t *p = config_open(*argv);
  55                while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
  56                        if (!noout) {
  57                                for (int i = 0; i < n; ++i)
  58                                        printf("[%s]", t[i]);
  59                                puts("");
  60                        }
  61                }
  62                config_close(p);
  63                argv++;
  64        }
  65        return EXIT_SUCCESS;
  66}
  67#endif
  68
  69parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
  70{
  71        FILE* fp;
  72        parser_t *parser;
  73
  74        fp = fopen_func(filename);
  75        if (!fp)
  76                return NULL;
  77        parser = xzalloc(sizeof(*parser));
  78        parser->fp = fp;
  79        return parser;
  80}
  81
  82parser_t* FAST_FUNC config_open(const char *filename)
  83{
  84        return config_open2(filename, fopen_or_warn_stdin);
  85}
  86
  87void FAST_FUNC config_close(parser_t *parser)
  88{
  89        if (parser) {
  90                if (PARSE_KEEP_COPY) /* compile-time constant */
  91                        free(parser->data);
  92                fclose(parser->fp);
  93                free(parser->line);
  94                free(parser->nline);
  95                free(parser);
  96        }
  97}
  98
  99/* This function reads an entire line from a text file,
 100 * up to a newline, exclusive.
 101 * Trailing '\' is recognized as line continuation.
 102 * Returns -1 if EOF/error.
 103 */
 104static int get_line_with_continuation(parser_t *parser)
 105{
 106        ssize_t len, nlen;
 107        char *line;
 108
 109        len = getline(&parser->line, &parser->line_alloc, parser->fp);
 110        if (len <= 0)
 111                return len;
 112
 113        line = parser->line;
 114        for (;;) {
 115                parser->lineno++;
 116                if (line[len - 1] == '\n')
 117                        len--;
 118                if (len == 0 || line[len - 1] != '\\')
 119                        break;
 120                len--;
 121
 122                nlen = getline(&parser->nline, &parser->nline_alloc, parser->fp);
 123                if (nlen <= 0)
 124                        break;
 125
 126                if (parser->line_alloc < len + nlen + 1) {
 127                        parser->line_alloc = len + nlen + 1;
 128                        line = parser->line = xrealloc(line, parser->line_alloc);
 129                }
 130                memcpy(&line[len], parser->nline, nlen);
 131                len += nlen;
 132        }
 133
 134        line[len] = '\0';
 135        return len;
 136}
 137
 138
 139/*
 1400. If parser is NULL return 0.
 1411. Read a line from config file. If nothing to read then return 0.
 142   Handle continuation character. Advance lineno for each physical line.
 143   Discard everything past comment character.
 1442. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
 1453. If resulting line is empty goto 1.
 1464. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
 147   remember the token as empty.
 1485. Else (default) if number of seen tokens is equal to max number of tokens
 149   (token is the last one) and PARSE_GREEDY is set then the remainder
 150   of the line is the last token.
 151   Else (token is not last or PARSE_GREEDY is not set) just replace
 152   first delimiter with '\0' thus delimiting the token.
 1536. Advance line pointer past the end of token. If number of seen tokens
 154   is less than required number of tokens then goto 4.
 1557. Check the number of seen tokens is not less the min number of tokens.
 156   Complain or die otherwise depending on PARSE_MIN_DIE.
 1578. Return the number of seen tokens.
 158
 159mintokens > 0 make config_read() print error message if less than mintokens
 160(but more than 0) are found. Empty lines are always skipped (not warned about).
 161*/
 162#undef config_read
 163int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
 164{
 165        char *line, *p;
 166        int ntokens, mintokens;
 167        int t;
 168        char alt_comment_ch;
 169
 170        if (!parser)
 171                return 0;
 172
 173        alt_comment_ch = '\0';
 174        if (flags & PARSE_ALT_COMMENTS)
 175                alt_comment_ch = *delims++;
 176
 177        ntokens = (uint8_t)flags;
 178        mintokens = (uint8_t)(flags >> 8);
 179
 180 again:
 181        memset(tokens, 0, sizeof(tokens[0]) * ntokens);
 182
 183        /* Read one line (handling continuations with backslash) */
 184        if (get_line_with_continuation(parser) < 0)
 185                return 0;
 186
 187        line = parser->line;
 188
 189        /* Skip token in the start of line? */
 190        if (flags & PARSE_TRIM)
 191                line += strspn(line, delims + 1);
 192
 193        p = line;
 194        if (flags & PARSE_WS_COMMENTS)
 195                p = skip_whitespace(p);
 196        if (p[0] == '\0' || p[0] == delims[0] || p[0] == alt_comment_ch)
 197                goto again;
 198
 199        if (flags & PARSE_KEEP_COPY) {
 200                free(parser->data);
 201                parser->data = xstrdup(line);
 202        }
 203
 204        /* Tokenize the line */
 205        t = 0;
 206        do {
 207                /* Pin token */
 208                tokens[t] = line;
 209
 210                /* Combine remaining arguments? */
 211                if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
 212                        /* Vanilla token, find next delimiter */
 213                        line += strcspn(line, (delims[0] && (flags & PARSE_EOL_COMMENTS)) ? delims : delims + 1);
 214                } else {
 215                        /* Combining, find comment char if any */
 216                        line = strchrnul(line, (flags & PARSE_EOL_COMMENTS) ? delims[0] : '\0');
 217
 218                        /* Trim any extra delimiters from the end */
 219                        if (flags & PARSE_TRIM) {
 220                                while (strchr(delims + 1, line[-1]) != NULL)
 221                                        line--;
 222                        }
 223                }
 224
 225                /* Token not terminated? */
 226                if ((flags & PARSE_EOL_COMMENTS) && *line == delims[0])
 227                        *line = '\0'; /* ends with comment char: this line is done */
 228                else if (*line != '\0')
 229                        *line++ = '\0'; /* token is done, continue parsing line */
 230
 231#if 0 /* unused so far */
 232                if (flags & PARSE_ESCAPE) {
 233                        strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
 234                }
 235#endif
 236                /* Skip possible delimiters */
 237                if (flags & PARSE_COLLAPSE)
 238                        line += strspn(line, delims + 1);
 239
 240                t++;
 241        } while (*line && *line != delims[0] && t < ntokens);
 242
 243        if (t < mintokens) {
 244                bb_error_msg("bad line %u: %d tokens found, %d needed",
 245                                parser->lineno, t, mintokens);
 246                if (flags & PARSE_MIN_DIE)
 247                        xfunc_die();
 248                goto again;
 249        }
 250
 251        return t;
 252}
 253