libbb: unified config parser (By Vladimir Dronnikov)
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
diff --git a/include/libbb.h b/include/libbb.h
index 2bd614c..0db1658 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -987,6 +987,28 @@
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 "/".
diff --git a/libbb/Kbuild b/libbb/Kbuild
index ab85ffc..7262006 100644
--- a/libbb/Kbuild
+++ b/libbb/Kbuild
@@ -63,6 +63,7 @@
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
index 0000000..6612db3
--- /dev/null
+++ b/libbb/parse_config.c
@@ -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
diff --git a/util-linux/mdev.c b/util-linux/mdev.c
index dd95229..c61521c 100644
--- a/util-linux/mdev.c
+++ b/util-linux/mdev.c
@@ -25,16 +25,6 @@
/* 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 @@
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 @@
if (result || off[0].rm_so
|| ((int)off[0].rm_eo != (int)strlen(device_name))
) {
- goto next_line;
+ continue;
}
}
@@ -155,15 +138,11 @@
* 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 @@
}
/* 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 @@
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 @@
}
/* 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: