libbb: updated config_parse() from Vladimir

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
This commit is contained in:
Denis Vlasenko 2008-07-19 09:27:19 +00:00
parent bd28f6bf7f
commit 2e157ddf9e
10 changed files with 205 additions and 53 deletions

View File

@ -479,6 +479,13 @@ config INCLUDE_SUSv2
will be supported in head, tail, and fold. (Note: should will be supported in head, tail, and fold. (Note: should
affect renice too.) 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 endmenu
menu 'Installation Options' menu 'Installation Options'

View 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_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_OD(APPLET(od, _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_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_PASSWD(APPLET(passwd, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS))
USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER))
USE_PGREP(APPLET(pgrep, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) USE_PGREP(APPLET(pgrep, _BB_DIR_USR_BIN, _BB_SUID_NEVER))

View File

@ -988,16 +988,22 @@ int bb_ask_confirmation(void) FAST_FUNC;
int bb_parse_mode(const char* s, mode_t* theMode) 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 { typedef struct parser_t {
FILE *fp; FILE *fp;
char *line, *data; char *line;
USE_FEATURE_PARSE_COPY(char *data;)
int lineno; int lineno;
} parser_t; } parser_t;
parser_t* config_open(const char *filename) FAST_FUNC; 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, unsigned flags, const char *delims) FAST_FUNC;
int config_read(parser_t *parser, char **tokens, int ntokens, int mintokens, const char *delims, char comment) 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; void config_close(parser_t *parser) FAST_FUNC;
/* Concatenate path and filename to new allocated buffer. /* Concatenate path and filename to new allocated buffer.

View File

@ -2903,6 +2903,11 @@
#define openvt_example_usage \ #define openvt_example_usage \
"openvt 2 /bin/ash\n" "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 \ #define passwd_trivial_usage \
"[OPTION] [name]" "[OPTION] [name]"
#define passwd_full_usage "\n\n" \ #define passwd_full_usage "\n\n" \

View File

@ -808,7 +808,7 @@ static void parse_inittab(void)
/* optional_tty:ignored_runlevel:action:command /* optional_tty:ignored_runlevel:action:command
* Delims are not to be collapsed and need exactly 4 tokens * 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; int action;
char *tty = token[0]; char *tty = token[0];
@ -828,7 +828,7 @@ static void parse_inittab(void)
free(tty); free(tty);
continue; continue;
bad_entry: 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); config_close(parser);
#endif #endif

View File

@ -9,23 +9,51 @@
#include "libbb.h" #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: Typical usage:
----- CUT ----- ----- CUT -----
char *t[3]; // tokens placeholder char *t[3]; // tokens placeholder
parser_t p; // parser structure parser_t *p = config_open(filename);
// open file if (p) {
if (config_open(filename, &p)) {
// parse line-by-line // 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 // use tokens
bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]); bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
} }
... ...
// free parser // free parser
config_close(&p); config_close(p);
} }
----- CUT ----- ----- CUT -----
@ -35,10 +63,9 @@ parser_t* FAST_FUNC config_open(const char *filename)
{ {
parser_t *parser = xzalloc(sizeof(parser_t)); parser_t *parser = xzalloc(sizeof(parser_t));
/* empty file configures nothing */ /* empty file configures nothing */
parser->fp = fopen_or_warn(filename, "r"); parser->fp = fopen_or_warn_stdin(filename);
if (parser->fp) if (parser->fp)
return parser; return parser;
config_close (parser);
if (ENABLE_FEATURE_CLEAN_UP) if (ENABLE_FEATURE_CLEAN_UP)
free(parser); free(parser);
return NULL; return NULL;
@ -47,24 +74,50 @@ parser_t* FAST_FUNC config_open(const char *filename)
static void config_free_data(parser_t *const parser) static void config_free_data(parser_t *const parser)
{ {
free(parser->line); free(parser->line);
parser->line = NULL;
USE_FEATURE_PARSE_COPY(
free(parser->data); free(parser->data);
parser->line = parser->data = NULL; parser->data = NULL;
)
} }
void FAST_FUNC config_close(parser_t *parser) void FAST_FUNC config_close(parser_t *parser)
{ {
config_free_data(parser); config_free_data(parser);
fclose(parser->fp); 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; char *line, *q;
int ii, seen; char comment = *delims++;
/* do not treat consecutive delimiters as one delimiter */ int ii;
bool noreduce = (ntokens < 0); int ntokens = flags & 0xFF;
if (noreduce) int mintokens = (flags & 0xFF00) >> 8;
ntokens = -ntokens;
/*
// 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); memset(tokens, 0, sizeof(tokens[0]) * ntokens);
config_free_data(parser); config_free_data(parser);
@ -72,7 +125,7 @@ int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mint
//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc //TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
line = xmalloc_fgetline(parser->fp); line = xmalloc_fgetline(parser->fp);
if (!line) if (!line)
return -1; return 0;
parser->lineno++; parser->lineno++;
// handle continuations. Tito's code stolen :) // 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'; *q = '\0';
ii = q - line; ii = q - line;
} }
// skip leading delimiters // skip leading and trailing delimiters
seen = strspn(line, delims); if (!(flags & PARSE_DONT_TRIM)) {
if (seen) { // skip leading
ii -= seen; int n = strspn(line, delims);
strcpy(line, line + seen); 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) if (ii)
break; break;
@ -112,36 +175,39 @@ int FAST_FUNC config_read(parser_t *parser, char **tokens, int ntokens, int mint
free(line); free(line);
} }
// non-empty line found, parse and return // non-empty line found, parse and return the number of tokens
// store line // store line
parser->line = line = xrealloc(line, ii + 1); parser->line = line = xrealloc(line, ii + 1);
USE_FEATURE_PARSE_COPY(
parser->data = xstrdup(line); parser->data = xstrdup(line);
)
/* now split line to tokens */ /* now split line to tokens */
ii = noreduce ? seen : 0;
ntokens--; // now it's max allowed token no 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 // get next token
if (ii == ntokens) // at the last token and need greedy token ->
break; 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 = line + strcspn(line, delims);
if (!*q)
break;
// pin token
*q++ = '\0'; *q++ = '\0';
if (noreduce || *line) { }
// pin token
if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
//bb_info_msg("N[%d] T[%s]", ii, line);
tokens[ii++] = line; tokens[ii++] = line;
//bb_info_msg("L[%d] T[%s]\n", ii, line);
} }
line = q; 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) if (ii < mintokens)
bb_error_msg_and_die("bad line %u: %d tokens found, %d needed", bb_error_msg_and_die("bad line %u: %d tokens found, %d needed",
parser->lineno, ii, mintokens); parser->lineno, ii, mintokens);

View File

@ -469,12 +469,15 @@ static void SynchronizeFile(const char *fileName)
file->cf_User = xstrdup(fileName); file->cf_User = xstrdup(fileName);
pline = &file->cf_LineBase; 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; CronLine *line;
if (DebugOpt) { USE_FEATURE_PARSE_COPY(
if (DebugOpt)
crondlog(LVL5 "user:%s entry:%s", fileName, parser->data); crondlog(LVL5 "user:%s entry:%s", fileName, parser->data);
} )
/* check if line is setting MAILTO= */ /* check if line is setting MAILTO= */
if (0 == strncmp(tokens[0], "MAILTO=", 7)) { if (0 == strncmp(tokens[0], "MAILTO=", 7)) {
@ -485,7 +488,7 @@ static void SynchronizeFile(const char *fileName)
continue; continue;
} }
/* check if a minimum of tokens is specified */ /* check if a minimum of tokens is specified */
if (n < 5) if (n < 6)
continue; continue;
*pline = line = xzalloc(sizeof(CronLine)); *pline = line = xzalloc(sizeof(CronLine));
/* parse date ranges */ /* parse date ranges */

View File

@ -163,7 +163,7 @@ int nameif_main(int argc, char **argv)
struct parser_t *parser = config_open(fname); struct parser_t *parser = config_open(fname);
if (parser) { if (parser) {
char *tokens[2]; 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]); prepend_new_eth_table(&clist, tokens[0], tokens[1]);
config_close(parser); config_close(parser);
} }

64
testsuite/parse.tests Executable file
View File

@ -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

View File

@ -101,7 +101,7 @@ static void make_device(char *path, int delete)
if (!parser) if (!parser)
goto end_parse; 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]; regmatch_t off[1+9*ENABLE_FEATURE_MDEV_RENAME_REGEXP];
char *val; char *val;