libbb: unified config parser (By Vladimir Dronnikov)
authorDenis Vlasenko <vda.linux@googlemail.com>
Tue, 15 Jul 2008 21:09:30 +0000 (21:09 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Tue, 15 Jul 2008 21:09:30 +0000 (21:09 -0000)
mdev: use it

function                                             old     new   delta
config_read                                            -     400    +400
config_open                                            -      43     +43
config_close                                           -       9      +9
qrealloc                                              33      36      +3
compare_keys                                         735     737      +2
xstrtoull_range_sfx                                  296     295      -1
qgravechar                                           109     106      -3
get_address                                          181     178      -3
next_token                                           928     923      -5
sv_main                                             1228    1222      -6
find_main                                            418     406     -12
next_field                                            32       -     -32
make_device                                         1269    1184     -85
------------------------------------------------------------------------------
(add/remove: 3/1 grow/shrink: 2/7 up/down: 457/-147)          Total: 310 bytes

include/libbb.h
libbb/Kbuild
libbb/parse_config.c [new file with mode: 0644]
util-linux/mdev.c

index 2bd614c7195006f9069fa795bc5f40936fe0f28a..0db1658f438b96321b1517f4ba941c54535bf9f6 100644 (file)
@@ -987,6 +987,28 @@ int bb_ask_confirmation(void) FAST_FUNC;
 
 extern int bb_parse_mode(const char* s, mode_t* theMode) FAST_FUNC;
 
+/*
+ * Uniform config file parser helpers
+ */
+#define PARSER_STDIO_BASED 1
+#if !PARSER_STDIO_BASED
+typedef struct parser_t {
+       char *data;
+       char *line;
+       int lineno;
+} parser_t;
+extern char* config_open(parser_t *parser, const char *filename) FAST_FUNC;
+#else
+typedef struct parser_t {
+       FILE *fp;
+       char *line;
+       int lineno;
+} parser_t;
+extern FILE* config_open(parser_t *parser, const char *filename) FAST_FUNC;
+#endif
+extern char* config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment) FAST_FUNC;
+extern void config_close(parser_t *parser) FAST_FUNC;
+
 /* Concatenate path and filename to new allocated buffer.
  * Add "/" only as needed (no duplicate "//" are produced).
  * If path is NULL, it is assumed to be "/".
index ab85ffc9e93021e7d5dae1832d08daa3de8dba5a..7262006753de2f7a7b0a7151088c72813cbfdd80 100644 (file)
@@ -63,6 +63,7 @@ lib-y += mode_string.o
 lib-y += mtab_file.o
 lib-y += obscure.o
 lib-y += parse_mode.o
+lib-y += parse_config.o
 lib-y += perror_msg.o
 lib-y += perror_msg_and_die.o
 lib-y += perror_nomsg.o
diff --git a/libbb/parse_config.c b/libbb/parse_config.c
new file mode 100644 (file)
index 0000000..6612db3
--- /dev/null
@@ -0,0 +1,247 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * config file parser helper
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+/*
+
+Typical usage:
+
+----- CUT -----
+       char *t[3];     // tokens placeholder
+       parser_t p;     // parser structure
+       // open file
+       if (config_open(filename, &p)) {
+               // parse line-by-line
+               while (*config_read(&p, t, 3, 0, delimiters, comment_char)) { // 0..3 tokens
+                       // use tokens
+                       bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
+               }
+               ...
+               // free parser
+               config_close(&p);
+       }
+----- CUT -----
+
+*/
+
+#if !PARSER_STDIO_BASED
+
+char* FAST_FUNC config_open(parser_t *parser, const char *filename)
+{
+       // empty file configures nothing!
+       char *data = xmalloc_open_read_close(filename, NULL);
+       if (!data)
+               return data;
+
+       // convert 0x5c 0x0a (backslashes at the very end of line) to 0x20 0x20 (spaces)
+       for (char *s = data; (s = strchr(s, '\\')) != NULL; ++s)
+               if ('\n' == s[1]) {
+                       s[0] = s[1] = ' ';
+               }
+
+       // init parser
+       parser->line = parser->data = data;
+       parser->lineno = 0;
+
+       return data;
+}
+
+void FAST_FUNC config_close(parser_t *parser)
+{
+       // for now just free config data
+       free(parser->data);
+}
+
+char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
+{
+       char *ret, *line;
+       int noreduce = (ntokens<0); // do not treat subsequent delimiters as one delimiter
+       if (ntokens < 0)
+               ntokens = -ntokens;
+       ret = line = parser->line;
+       // nullify tokens
+       memset(tokens, 0, sizeof(void *) * ntokens);
+       // now split to lines
+       while (*line) {
+               int token_num = 0;
+               // limit the line
+               char *ptr = strchrnul(line, '\n');
+               *ptr++ = '\0';
+               // line number
+               parser->lineno++;
+               // comments mean EOLs
+               if (comment)
+                       *strchrnul(line, comment) = '\0';
+               // skip leading delimiters
+               while (*line && strchr(delims, *line))
+                       line++;
+               // skip empty lines
+               if (*line) {
+                       char *s;
+                       // now split line to tokens
+                       s = line;
+                       while (s) {
+                               char *p;
+                               // get next token
+                               if (token_num+1 >= ntokens)
+                                       break;
+                               p = s;
+                               while (*p && !strchr(delims, *p))
+                                       p++;
+                               if (!*p)
+                                       break;
+                               *p++ = '\0';
+                               // pin token
+                               if (noreduce || *s) {
+                                       tokens[token_num++] = s;
+//bb_error_msg("L[%d] T[%s]", token_num, s);
+                               }
+                               s = p;
+                       }
+                       // non-empty remainder is also a token. So if ntokens == 0, we just return the whole line
+                       if (s && (noreduce || *s))
+                               tokens[token_num++] = s;
+                       // sanity check: have we got all required tokens?
+                       if (token_num < mintokens)
+                               bb_error_msg_and_die("bad line %u, %d tokens found, %d needed", parser->lineno, token_num, mintokens);
+                       // advance data for the next call
+                       line = ptr;
+                       break;
+               }
+               // line didn't contain any token -> try next line
+               ret = line = ptr;
+       }
+       parser->line = line;
+
+       // return current line. caller must check *ret to determine whether to continue
+       return ret;
+}
+
+#else // stdio-based
+
+FILE* FAST_FUNC config_open(parser_t *parser, const char *filename)
+{
+       // empty file configures nothing!
+       parser->fp = fopen_or_warn(filename, "r");
+       if (!parser->fp)
+               return parser->fp;
+
+       // init parser
+       parser->line = NULL;
+       parser->lineno = 0;
+
+       return parser->fp;
+}
+
+void FAST_FUNC config_close(parser_t *parser)
+{
+       fclose(parser->fp);
+}
+
+char* FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment)
+{
+       char *line, *q;
+       int token_num, len;
+       int noreduce = (ntokens < 0); // do not treat subsequent delimiters as one delimiter
+
+       if (ntokens < 0)
+               ntokens = -ntokens;
+
+       // nullify tokens
+       memset(tokens, 0, sizeof(void *) * ntokens);
+
+       // free used line
+       free(parser->line);
+       parser->line = NULL;
+
+       while (1) {
+               int n;
+
+               // get fresh line
+//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
+               line = xmalloc_fgetline(parser->fp);
+               if (!line)
+                       return line;
+
+               parser->lineno++;
+               // handle continuations. Tito's code stolen :)
+               while (1) {
+                       len = strlen(line);
+                       if (!len)
+                               goto free_and_cont;
+                       if (line[len - 1] != '\\')
+                               break;
+                       // multi-line object
+                       line[--len] = '\0';
+//TODO: add xmalloc_fgetline-like iface but with appending to existing str
+                       q = xmalloc_fgetline(parser->fp);
+                       if (q) {
+                               parser->lineno++;
+                               line = xasprintf("%s%s", line, q);
+                               free(q);
+                       }
+               }
+               // comments mean EOLs
+               if (comment) {
+                       q = strchrnul(line, comment);
+                       *q = '\0';
+                       len = q - line;
+               }
+               // skip leading delimiters
+               n = strspn(line, delims);
+               if (n) {
+                       len -= n;
+                       strcpy(line, line + n);
+               }
+               if (len)
+                       break;
+               // skip empty lines
+ free_and_cont:
+               free(line);
+       }
+
+       // non-empty line found, parse and return
+
+       // store line
+       parser->line = line = xrealloc(line, len + 1);
+
+       // now split line to tokens
+//TODO: discard consecutive delimiters?
+       token_num = 0;
+       ntokens--; // now it's max allowed token no
+       while (1) {
+               // get next token
+               if (token_num == ntokens)
+                       break;
+               q = line + strcspn(line, delims);
+               if (!*q)
+                       break;
+               // pin token
+               *q++ = '\0';
+               if (noreduce || *line) {
+                       tokens[token_num++] = line;
+//bb_error_msg("L[%d] T[%s]", token_num, line);
+               }
+               line = q;
+       }
+
+       // non-empty remainder is also a token,
+       // so if ntokens <= 1, we just return the whole line
+       if (noreduce || *line)
+               tokens[token_num++] = line;
+
+       if (token_num < mintokens)
+               bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
+                               parser->lineno, token_num, mintokens);
+
+       return parser->line; // maybe token_num instead?
+}
+
+#endif
index dd9522999fe2cc3c781c5dea6ceaf5debaee4ea6..c61521cbb70d9b92667ee96c2240842bb618cbc6 100644 (file)
@@ -25,16 +25,6 @@ struct globals {
 /* We use additional 64+ bytes in make_device() */
 #define SCRATCH_SIZE 80
 
-static char *next_field(char *s)
-{
-       char *end = skip_non_whitespace(s);
-       s = skip_whitespace(end);
-       *end = '\0';
-       if (*s == '\0')
-               s = NULL;
-       return s;
-}
-
 /* Builds an alias path.
  * This function potentionally reallocates the alias parameter.
  */
@@ -104,33 +94,26 @@ static void make_device(char *path, int delete)
                type = S_IFBLK;
 
        if (ENABLE_FEATURE_MDEV_CONF) {
-               FILE *fp;
-               char *line, *val, *next;
-               unsigned lineno = 0;
+               parser_t parser;
+               char *tokens[5];
 
                /* If we have config file, look up user settings */
-               fp = fopen_or_warn("/etc/mdev.conf", "r");
-               if (!fp)
+               if (!config_open(&parser, "/etc/mdev.conf"))
                        goto end_parse;
 
-               while ((line = xmalloc_fgetline(fp)) != NULL) {
+               while (config_read(&parser, tokens, 4, 3, " \t", '#')) {
                        regmatch_t off[1+9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
-
-                       ++lineno;
-                       trim(line);
-                       if (!line[0])
-                               goto next_line;
+                       char *val;
 
                        /* Fields: regex uid:gid mode [alias] [cmd] */
 
                        /* 1st field: regex to match this device */
-                       next = next_field(line);
                        {
                                regex_t match;
                                int result;
 
                                /* Is this it? */
-                               xregcomp(&match, line, REG_EXTENDED);
+                               xregcomp(&match, tokens[0], REG_EXTENDED);
                                result = regexec(&match, device_name, ARRAY_SIZE(off), off, 0);
                                regfree(&match);
 
@@ -147,7 +130,7 @@ static void make_device(char *path, int delete)
                                if (result || off[0].rm_so
                                 || ((int)off[0].rm_eo != (int)strlen(device_name))
                                ) {
-                                       goto next_line;
+                                       continue;
                                }
                        }
 
@@ -155,15 +138,11 @@ static void make_device(char *path, int delete)
                         * after parsing the rest of fields */
 
                        /* 2nd field: uid:gid - device ownership */
-                       if (!next) /* field must exist */
-                               bb_error_msg_and_die("bad line %u", lineno);
-                       val = next;
-                       next = next_field(val);
                        {
                                struct passwd *pass;
                                struct group *grp;
-                               char *str_uid = val;
-                               char *str_gid = strchrnul(val, ':');
+                               char *str_uid = tokens[1];
+                               char *str_gid = strchrnul(str_uid, ':');
 
                                if (*str_gid)
                                        *str_gid++ = '\0';
@@ -182,33 +161,30 @@ static void make_device(char *path, int delete)
                        }
 
                        /* 3rd field: mode - device permissions */
-                       if (!next) /* field must exist */
-                               bb_error_msg_and_die("bad line %u", lineno);
-                       val = next;
-                       next = next_field(val);
-                       mode = strtoul(val, NULL, 8);
+                       mode = strtoul(tokens[2], NULL, 8);
 
+                       val = tokens[3];
                        /* 4th field (opt): >alias */
 #if ENABLE_FEATURE_MDEV_RENAME
-                       if (!next)
+                       if (!val)
                                break;
-                       if (*next == '>' || *next == '=') {
-#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
+                       aliaslink = *val;
+                       if (aliaslink == '>' || aliaslink == '=') {
                                char *s, *p;
                                unsigned i, n;
-
-                               aliaslink = *next;
-                               val = next;
-                               next = next_field(val);
+                               char *a = val;
+                               s = strchr(val, ' ');
+                               val = (s && s[1]) ? s+1 : NULL;
+#if ENABLE_FEATURE_MDEV_RENAME_REGEXP
                                /* substitute %1..9 with off[1..9], if any */
                                n = 0;
-                               s = val;
+                               s = a;
                                while (*s)
                                        if (*s++ == '%')
                                                n++;
 
-                               p = alias = xzalloc(strlen(val) + n * strlen(device_name));
-                               s = val + 1;
+                               p = alias = xzalloc(strlen(a) + n * strlen(device_name));
+                               s = a + 1;
                                while (*s) {
                                        *p = *s;
                                        if ('%' == *s) {
@@ -224,24 +200,20 @@ static void make_device(char *path, int delete)
                                        s++;
                                }
 #else
-                               aliaslink = *next;
-                               val = next;
-                               next = next_field(val);
-                               alias = xstrdup(val + 1);
+                               alias = xstrdup(a + 1);
 #endif
                        }
 #endif /* ENABLE_FEATURE_MDEV_RENAME */
 
                        /* The rest (opt): command to run */
-                       if (!next)
+                       if (!val)
                                break;
-                       val = next;
                        if (ENABLE_FEATURE_MDEV_EXEC) {
                                const char *s = "@$*";
                                const char *s2 = strchr(s, *val);
 
                                if (!s2)
-                                       bb_error_msg_and_die("bad line %u", lineno);
+                                       bb_error_msg_and_die("bad line %u", parser.lineno);
 
                                /* Correlate the position in the "@$*" with the delete
                                 * step so that we get the proper behavior:
@@ -255,12 +227,9 @@ static void make_device(char *path, int delete)
                        }
                        /* end of field parsing */
                        break; /* we found matching line, stop */
- next_line:
-                       free(line);
                } /* end of "while line is read from /etc/mdev.conf" */
 
-               free(line); /* in case we used "break" to get here */
-               fclose(fp);
+               config_close(&parser);
        }
  end_parse: