libbb: unified config parser (By Vladimir Dronnikov) mdev: use it
No binary (build failed)
—
(log yok)
| @@ -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 "/". | |
| @@ -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 | |
| @@ -0,0 +1,247 @@ | |
| + | /* vi: set sw=4 ts=4: */ |
| + | /* |
| + | * config file parser helper |
| + | * |
| + | * Copyright (C) 2008 by Vladimir Dronnikov <[email protected]> |
| + | * |
| + | * 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 |
| @@ -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: | |