libbb: updated config_parse() from Vladimir
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 19 Jul 2008 09:27:19 +0000 (09:27 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 19 Jul 2008 09:27:19 +0000 (09:27 -0000)
function                                             old     new   delta
config_read                                          385     460     +75
runsvdir_main                                       1701    1716     +15
readit                                               331     338      +7
passwd_main                                         1049    1053      +4
parse_command                                       1504    1507      +3
decode_format_string                                 822     824      +2
bb__parsespent                                       117     119      +2
udhcp_get_option                                     221     222      +1
changepath                                           196     194      -2
parse_inittab                                        400     396      -4
nameif_main                                          683     679      -4
make_device                                         1176    1172      -4
config_open                                           48      40      -8
expand_main                                          698     689      -9
readcmd                                             1012    1002     -10
config_free_data                                      37      21     -16
SynchronizeFile                                      683     643     -40
sleep_main                                           474     362    -112
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 8/10 up/down: 109/-209)        Total: -100 bytes

Config.in
include/applets.h
include/libbb.h
include/usage.h
init/init.c
libbb/parse_config.c
miscutils/crond.c
networking/nameif.c
testsuite/parse.tests [new file with mode: 0755]
util-linux/mdev.c

index 1cbdf61..8de6678 100644 (file)
--- a/Config.in
+++ b/Config.in
@@ -479,6 +479,13 @@ config INCLUDE_SUSv2
          will be supported in head, tail, and fold.  (Note: should
          affect renice too.)
 
+config PARSE
+       bool "Uniform config file parser debugging applet: parse"
+
+config FEATURE_PARSE_COPY
+       bool "Keep a copy of current line"
+       depends on PARSE
+
 endmenu
 
 menu 'Installation Options'
index aff9070..5dd485a 100644 (file)
@@ -268,6 +268,7 @@ USE_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_OPENVT(APPLET(openvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
+USE_PARSE(APPLET(parse, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_PASSWD(APPLET(passwd, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS))
 USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
 USE_PGREP(APPLET(pgrep, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
index 14af136..af6c138 100644 (file)
@@ -988,16 +988,22 @@ int bb_ask_confirmation(void) FAST_FUNC;
 int bb_parse_mode(const char* s, mode_t* theMode) FAST_FUNC;
 
 /*
- * Uniform config file parser helpers
+ * Config file parser
  */
+#define PARSE_DONT_REDUCE       0x00010000 // do not treat consecutive delimiters as one
+#define PARSE_DONT_TRIM         0x00020000 // do not trim line of leading and trailing delimiters
+#define PARSE_LAST_IS_GREEDY    0x00040000 // last token takes whole remainder of the line
+//#define PARSE_DONT_NULL         0x00080000 // do not set tokens[] to NULL
 typedef struct parser_t {
        FILE *fp;
-       char *line, *data;
+       char *line;
+       USE_FEATURE_PARSE_COPY(char *data;)
        int lineno;
 } parser_t;
 parser_t* config_open(const char *filename) FAST_FUNC;
-/* TODO: add define magic to collapse ntokens/mintokens/comment into one int param */
-int config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment) FAST_FUNC;
+int config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims) FAST_FUNC;
+#define config_read(parser, tokens, max, min, str, flags) \
+       config_read(parser, tokens, ((flags) | (((min) & 0xFF) << 8) | ((max) & 0xFF)), str)
 void config_close(parser_t *parser) FAST_FUNC;
 
 /* Concatenate path and filename to new allocated buffer.
index f9a993a..61c5c8e 100644 (file)
 #define openvt_example_usage \
        "openvt 2 /bin/ash\n"
 
+#define parse_trivial_usage \
+       "[-n maxtokens] [-m mintokens] [-d delims] [-f flags] file ..."
+#define parse_full_usage "\n\n" \
+       "[-n maxtokens] [-m mintokens] [-d delims] [-f flags] file ..."
+
 #define passwd_trivial_usage \
        "[OPTION] [name]"
 #define passwd_full_usage "\n\n" \
index 9637589..0e4a8f1 100644 (file)
@@ -808,7 +808,7 @@ static void parse_inittab(void)
        /* optional_tty:ignored_runlevel:action:command
         * Delims are not to be collapsed and need exactly 4 tokens
         */
-       while (config_read(parser, token, -4, 0, ":", '#') >= 0) {
+       while (config_read(parser, token, 4, 0, "#:", PARSE_DONT_TRIM|PARSE_DONT_REDUCE|PARSE_LAST_IS_GREEDY)) {
                int action;
                char *tty = token[0];
 
@@ -828,7 +828,7 @@ static void parse_inittab(void)
                        free(tty);
                continue;
  bad_entry:
-               message(L_LOG | L_CONSOLE, "Bad inittab entry: %s", parser->line);
+               message(L_LOG | L_CONSOLE, "Bad inittab entry at line %d", parser->lineno);
        }
        config_close(parser);
 #endif
index 5f6dbbd..3945501 100644 (file)
@@ -9,23 +9,51 @@
 
 #include "libbb.h"
 
+#if ENABLE_PARSE
+int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int parse_main(int argc UNUSED_PARAM, char **argv)
+{
+       const char *delims = "# \t";
+       unsigned flags = 0;
+       int mintokens = 0, ntokens = 128;
+       opt_complementary = "-1:n+:m+:f+";
+       getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
+       //argc -= optind;
+       argv += optind;
+       while (*argv) {
+               parser_t *p = config_open(*argv);
+               if (p) {
+                       int n;
+                       char **t = xmalloc(sizeof(char *) * ntokens);
+                       while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) > 0) {
+                               for (int i = 0; i < n; ++i)
+                                       printf("[%s]", t[i]);
+                               puts("");
+                       }
+                       config_close(p);
+               }
+               argv++;
+       }
+       return EXIT_SUCCESS;
+}
+#endif
+
 /*
 
 Typical usage:
 
 ----- CUT -----
        char *t[3];     // tokens placeholder
-       parser_t p;     // parser structure
-       // open file
-       if (config_open(filename, &p)) {
+       parser_t *p = config_open(filename);
+       if (p) {
                // parse line-by-line
-               while (*config_read(&p, t, 3, 0, delimiters, comment_char) >= 0) { // 0..3 tokens
+               while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
                        // use tokens
                        bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
                }
                ...
                // free parser
-               config_close(&p);
+               config_close(p);
        }
 ----- CUT -----
 
@@ -35,44 +63,69 @@ parser_t* FAST_FUNC config_open(const char *filename)
 {
        parser_t *parser = xzalloc(sizeof(parser_t));
        /* empty file configures nothing */
-       parser->fp = fopen_or_warn(filename, "r");
+       parser->fp = fopen_or_warn_stdin(filename);
        if (parser->fp)
                return parser;
-       config_close (parser);
        if (ENABLE_FEATURE_CLEAN_UP)
-         free(parser);
+               free(parser);
        return NULL;
 }
 
 static void config_free_data(parser_t *const parser)
 {
        free(parser->line);
-       free(parser->data);
-       parser->line = parser->data = NULL;
+       parser->line = NULL;
+       USE_FEATURE_PARSE_COPY(
+               free(parser->data);
+               parser->data = NULL;
+       )
 }
+
 void FAST_FUNC config_close(parser_t *parser)
 {
        config_free_data(parser);
        fclose(parser->fp);
 }
 
-int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
+/*
+1. Read a line from config file. If nothing to read then bail out returning 0.
+   Handle continuation character. Advance lineno for each physical line. Cut comments.
+2. if PARSE_DONT_TRIM is not set (default) skip leading and cut trailing delimiters, if any.
+3. If resulting line is empty goto 1.
+4. Look for first delimiter. If PARSE_DONT_REDUCE or PARSE_DONT_TRIM is set then pin empty token.
+5. Else (default) if number of seen tokens is equal to max number of tokens (token is the last one)
+   and PARSE_LAST_IS_GREEDY is set then pin the remainder of the line as the last token.
+   Else (token is not last or PARSE_LAST_IS_GREEDY is not set) just replace first delimiter with '\0'
+   thus delimiting token and pin it.
+6. Advance line pointer past the end of token. If number of seen tokens is less than required number
+   of tokens then goto 4.
+7. Control the number of seen tokens is not less the min number of tokens. Die if condition is not met.
+8. Return the number of seen tokens.
+
+mintokens > 0 make config_read() exit with error message if less than mintokens
+(but more than 0) are found. Empty lines are always skipped (not warned about).
+*/
+#undef config_read
+int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
 {
        char *line, *q;
-       int ii, seen;
-       /* do not treat consecutive delimiters as one delimiter */
-       bool noreduce = (ntokens < 0);
-       if (noreduce)
-               ntokens = -ntokens;
-
-       memset(tokens, 0, sizeof(tokens[0]) * ntokens);
+       char comment = *delims++;
+       int ii;
+       int ntokens = flags & 0xFF;
+       int mintokens = (flags & 0xFF00) >> 8;
+
+       /*
+       // N.B. this could only be used in read-in-one-go version, or when tokens use xstrdup(). TODO
+       if (!parser->lineno || !(flags & PARSE_DONT_NULL))
+       */
+               memset(tokens, 0, sizeof(tokens[0]) * ntokens);
        config_free_data(parser);
 
        while (1) {
 //TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
                line = xmalloc_fgetline(parser->fp);
                if (!line)
-                       return -1;
+                       return 0;
 
                parser->lineno++;
                // handle continuations. Tito's code stolen :)
@@ -98,12 +151,22 @@ int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mint
                        *q = '\0';
                        ii = q - line;
                }
-               // skip leading delimiters
-               seen = strspn(line, delims);
-               if (seen) {
-                       ii -= seen;
-                       strcpy(line, line + seen);
+               // skip leading and trailing delimiters
+               if (!(flags & PARSE_DONT_TRIM)) {
+                       // skip leading
+                       int n = strspn(line, delims);
+                       if (n) {
+                               ii -= n;
+                               strcpy(line, line + n);
+                       }
+                       // cut trailing
+                       if (ii) {
+                               while (strchr(delims, line[--ii]))
+                                       continue;
+                               line[++ii] = '\0';
+                       }
                }
+               // if something still remains -> return it
                if (ii)
                        break;
 
@@ -112,36 +175,39 @@ int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mint
                free(line);
        }
 
-       // non-empty line found, parse and return
+       // non-empty line found, parse and return the number of tokens
 
        // store line
        parser->line = line = xrealloc(line, ii + 1);
-       parser->data = xstrdup(line);
+       USE_FEATURE_PARSE_COPY(
+               parser->data = xstrdup(line);
+       )
 
        /* now split line to tokens */
-       ii = noreduce ? seen : 0;
        ntokens--; // now it's max allowed token no
-       while (1) {
+       // N.B, non-empty remainder is also a token,
+       // so if ntokens <= 1, we just return the whole line
+       // N.B. if PARSE_LAST_IS_GREEDY is set the remainder of the line is stuck to the last token
+       for (ii = 0; *line && ii <= ntokens; ) {
+               //bb_info_msg("L[%s]", line);
                // get next token
-               if (ii == ntokens)
-                       break;
-               q = line + strcspn(line, delims);
-               if (!*q)
-                       break;
+               // at the last token and need greedy token ->
+               if ((flags & PARSE_LAST_IS_GREEDY) && (ii == ntokens)) {
+                       // ... don't cut the line
+                       q = line + strlen(line);
+               } else {
+                       // vanilla token. cut the line at the first delim
+                       q = line + strcspn(line, delims);
+                       *q++ = '\0';
+               }
                // pin token
-               *q++ = '\0';
-               if (noreduce || *line) {
+               if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
+                       //bb_info_msg("N[%d] T[%s]", ii, line);
                        tokens[ii++] = line;
-//bb_info_msg("L[%d] T[%s]\n", ii, line);
                }
                line = q;
        }
 
-       // non-empty remainder is also a token,
-       // so if ntokens <= 1, we just return the whole line
-       if (noreduce || *line)
-               tokens[ii++] = line;
-
        if (ii < mintokens)
                bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
                                parser->lineno, ii, mintokens);
index d8423cf..154243c 100644 (file)
@@ -469,12 +469,15 @@ static void SynchronizeFile(const char *fileName)
                file->cf_User = xstrdup(fileName);
                pline = &file->cf_LineBase;
 
-               while (--maxLines && (n=config_read(parser, tokens, 6, 0, " \t", '#')) >= 0) {
+               while (--maxLines
+                && (n = config_read(parser, tokens, 6, 1, "# \t", PARSE_LAST_IS_GREEDY))
+               ) {
                        CronLine *line;
 
-                       if (DebugOpt) {
-                               crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
-                       }
+                       USE_FEATURE_PARSE_COPY(
+                               if (DebugOpt)
+                                       crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
+                       )
 
                        /* check if line is setting MAILTO= */
                        if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
@@ -485,7 +488,7 @@ static void SynchronizeFile(const char *fileName)
                                continue;
                        }
                        /* check if a minimum of tokens is specified */
-                       if (n < 5)
+                       if (n < 6)
                                continue;
                        *pline = line = xzalloc(sizeof(CronLine));
                        /* parse date ranges */
index 291780a..76a8cb7 100644 (file)
@@ -163,7 +163,7 @@ int nameif_main(int argc, char **argv)
                struct parser_t *parser = config_open(fname);
                if (parser) {
                        char *tokens[2];
-                       while (config_read(parser, tokens, 2, 2, " \t", '#') >= 0)
+                       while (config_read(parser, tokens, 2, 2, "# \t", 0))
                                prepend_new_eth_table(&clist, tokens[0], tokens[1]);
                        config_close(parser);
                }
diff --git a/testsuite/parse.tests b/testsuite/parse.tests
new file mode 100755 (executable)
index 0000000..1b43f9c
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+# Copyright 2008 by Denys Vlasenko <vda.linux@googlemail.com>
+# Licensed under GPL v2, see file LICENSE for details.
+
+. testing.sh
+
+NO_REDUCE=65536
+NO_TRIM=131072
+GREEDY=262144
+
+# testing "description" "command" "result" "infile" "stdin"
+
+testing "mdev.conf" \
+       "parse -n 4 -m 3 -f $GREEDY -" \
+       "[sda][0:0][644][@echo @echo TEST]\n" \
+       "-" \
+       " sda 0:0 644 @echo @echo TEST # echo trap\n"
+
+testing "notrim" \
+       "parse -n 4 -m 3 -f $(($GREEDY+$NO_TRIM)) -" \
+       "[][sda][0:0][644 @echo @echo TEST ]\n" \
+       "-" \
+       " sda 0:0 644 @echo @echo TEST \n"
+
+FILE=__parse.fstab
+cat >$FILE <<EOF
+#
+# Device         Point               System                       Options
+#_______________________________________________________________
+/dev/hdb3       /                       ext2                 defaults      1          0
+   /dev/hdb1       /dosc               hpfs                 ro      1          0
+ /dev/fd0          /dosa              vfat                  rw,user,noauto,nohide       0              0
+       /dev/fd1          /dosb              vfat                  rw,user,noauto,nohide         0              0
+#
+ /dev/cdrom     /cdrom            iso9660          ro,user,noauto,nohide        0              0
+/dev/hdb5       /redhat            ext2                 rw,root,noauto,nohide   0              0 #sssd
+       /dev/hdb6       /win2home     ntfs                  rw,root,noauto,nohide        0              0# ssdsd
+/dev/hdb7       /win2skul        ntfs                  rw,root,noauto,nohide none       0              0
+none     /dev/pts           devpts             gid=5,mode=620                 0    0 
+     none                /proc               proc                defaults     0          0
+EOF
+
+cat >$FILE.res <<EOF
+[/dev/hdb3][/][ext2][defaults][1][0]
+[/dev/hdb1][/dosc][hpfs][ro][1][0]
+[/dev/fd0][/dosa][vfat][rw,user,noauto,nohide][0][0]
+[/dev/fd1][/dosb][vfat][rw,user,noauto,nohide][0][0]
+[/dev/cdrom][/cdrom][iso9660][ro,user,noauto,nohide][0][0]
+[/dev/hdb5][/redhat][ext2][rw,root,noauto,nohide][0][0]
+[/dev/hdb6][/win2home][ntfs][rw,root,noauto,nohide][0][0]
+[/dev/hdb7][/win2skul][ntfs][rw,root,noauto,nohide][none][0]
+[none][/dev/pts][devpts][gid=5,mode=620][0][0]
+[none][/proc][proc][defaults][0][0]
+EOF
+
+testing "polluted fstab" \
+       "parse -n 6 -m 6 $FILE" \
+       "`cat $FILE.res`\n" \
+       "" \
+       ""
+rm -f $FILE $FILE.res
+
+exit $FAILCOUNT
index f83dd6a..7ad55c8 100644 (file)
@@ -101,7 +101,7 @@ static void make_device(char *path, int delete)
                if (!parser)
                        goto end_parse;
 
-               while (config_read(parser, tokens, 4, 3, " \t", '#') >= 0) {
+               while (config_read(parser, tokens, 4, 3, "# \t", PARSE_LAST_IS_GREEDY)) {
                        regmatch_t off[1+9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
                        char *val;