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

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