From e559e0a75738044403e9a43f54ccb0ac3091cd9a Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Tue, 15 Jul 2008 21:09:30 +0000 Subject: 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 --- include/libbb.h | 22 +++++ libbb/Kbuild | 1 + libbb/parse_config.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++ util-linux/mdev.c | 81 ++++++----------- 4 files changed, 295 insertions(+), 56 deletions(-) create mode 100644 libbb/parse_config.c diff --git a/include/libbb.h b/include/libbb.h index 2bd614c71..0db1658f4 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -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 "/". diff --git a/libbb/Kbuild b/libbb/Kbuild index ab85ffc9e..726200675 100644 --- a/libbb/Kbuild +++ b/libbb/Kbuild @@ -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 index 000000000..6612db367 --- /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 + * + * 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 dd9522999..c61521cbb 100644 --- a/util-linux/mdev.c +++ b/util-linux/mdev.c @@ -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: -- cgit v1.2.3