244 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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 source tree.
 | 
						|
 * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
 | 
						|
 */
 | 
						|
 | 
						|
/* Uncomment to enable test applet */
 | 
						|
////config:config PARSE
 | 
						|
////config:	bool "Uniform config file parser debugging applet: parse"
 | 
						|
////config:	default n
 | 
						|
////config:	help
 | 
						|
////config:	  Typical usage of parse API:
 | 
						|
////config:		char *t[3];
 | 
						|
////config:		parser_t *p = config_open(filename);
 | 
						|
////config:		while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
 | 
						|
////config:			bb_error_msg("TOKENS: '%s''%s''%s'", t[0], t[1], t[2]);
 | 
						|
////config:		}
 | 
						|
////config:		config_close(p);
 | 
						|
 | 
						|
////applet:IF_PARSE(APPLET(parse, BB_DIR_USR_BIN, BB_SUID_DROP))
 | 
						|
 | 
						|
//kbuild:lib-y += parse_config.o
 | 
						|
 | 
						|
//usage:#define parse_trivial_usage
 | 
						|
//usage:       "[-x] [-n MAXTOKENS] [-m MINTOKENS] [-d DELIMS] [-f FLAGS] FILE..."
 | 
						|
//usage:#define parse_full_usage "\n\n"
 | 
						|
//usage:       "	-x	Suppress output (for benchmarking)"
 | 
						|
 | 
						|
#include "libbb.h"
 | 
						|
 | 
						|
#if defined ENABLE_PARSE && 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";
 | 
						|
	char **t;
 | 
						|
	unsigned flags = PARSE_NORMAL;
 | 
						|
	int mintokens = 0, ntokens = 128;
 | 
						|
	unsigned noout;
 | 
						|
 | 
						|
	opt_complementary = "-1:n+:m+:f+";
 | 
						|
	noout = 1 & getopt32(argv, "xn:m:d:f:", &ntokens, &mintokens, &delims, &flags);
 | 
						|
	//argc -= optind;
 | 
						|
	argv += optind;
 | 
						|
 | 
						|
	t = xmalloc(sizeof(t[0]) * ntokens);
 | 
						|
	while (*argv) {
 | 
						|
		int n;
 | 
						|
		parser_t *p = config_open(*argv);
 | 
						|
		while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
 | 
						|
			if (!noout) {
 | 
						|
				for (int i = 0; i < n; ++i)
 | 
						|
					printf("[%s]", t[i]);
 | 
						|
				puts("");
 | 
						|
			}
 | 
						|
		}
 | 
						|
		config_close(p);
 | 
						|
		argv++;
 | 
						|
	}
 | 
						|
	return EXIT_SUCCESS;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
 | 
						|
{
 | 
						|
	FILE* fp;
 | 
						|
	parser_t *parser;
 | 
						|
 | 
						|
	fp = fopen_func(filename);
 | 
						|
	if (!fp)
 | 
						|
		return NULL;
 | 
						|
	parser = xzalloc(sizeof(*parser));
 | 
						|
	parser->fp = fp;
 | 
						|
	return parser;
 | 
						|
}
 | 
						|
 | 
						|
parser_t* FAST_FUNC config_open(const char *filename)
 | 
						|
{
 | 
						|
	return config_open2(filename, fopen_or_warn_stdin);
 | 
						|
}
 | 
						|
 | 
						|
void FAST_FUNC config_close(parser_t *parser)
 | 
						|
{
 | 
						|
	if (parser) {
 | 
						|
		if (PARSE_KEEP_COPY) /* compile-time constant */
 | 
						|
			free(parser->data);
 | 
						|
		fclose(parser->fp);
 | 
						|
		free(parser->line);
 | 
						|
		free(parser->nline);
 | 
						|
		free(parser);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* This function reads an entire line from a text file,
 | 
						|
 * up to a newline, exclusive.
 | 
						|
 * Trailing '\' is recognized as line continuation.
 | 
						|
 * Returns -1 if EOF/error.
 | 
						|
 */
 | 
						|
static int get_line_with_continuation(parser_t *parser)
 | 
						|
{
 | 
						|
	ssize_t len, nlen;
 | 
						|
	char *line;
 | 
						|
 | 
						|
	len = getline(&parser->line, &parser->line_alloc, parser->fp);
 | 
						|
	if (len <= 0)
 | 
						|
		return len;
 | 
						|
 | 
						|
	line = parser->line;
 | 
						|
	for (;;) {
 | 
						|
		parser->lineno++;
 | 
						|
		if (line[len - 1] == '\n')
 | 
						|
			len--;
 | 
						|
		if (len == 0 || line[len - 1] != '\\')
 | 
						|
			break;
 | 
						|
		len--;
 | 
						|
 | 
						|
		nlen = getline(&parser->nline, &parser->nline_alloc, parser->fp);
 | 
						|
		if (nlen <= 0)
 | 
						|
			break;
 | 
						|
 | 
						|
		if (parser->line_alloc < len + nlen + 1) {
 | 
						|
			parser->line_alloc = len + nlen + 1;
 | 
						|
			line = parser->line = xrealloc(line, parser->line_alloc);
 | 
						|
		}
 | 
						|
		memcpy(&line[len], parser->nline, nlen);
 | 
						|
		len += nlen;
 | 
						|
	}
 | 
						|
 | 
						|
	line[len] = '\0';
 | 
						|
	return len;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
0. If parser is NULL return 0.
 | 
						|
1. Read a line from config file. If nothing to read then return 0.
 | 
						|
   Handle continuation character. Advance lineno for each physical line.
 | 
						|
   Discard everything past comment character.
 | 
						|
2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
 | 
						|
3. If resulting line is empty goto 1.
 | 
						|
4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
 | 
						|
   remember the token as empty.
 | 
						|
5. Else (default) if number of seen tokens is equal to max number of tokens
 | 
						|
   (token is the last one) and PARSE_GREEDY is set then the remainder
 | 
						|
   of the line is the last token.
 | 
						|
   Else (token is not last or PARSE_GREEDY is not set) just replace
 | 
						|
   first delimiter with '\0' thus delimiting the token.
 | 
						|
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. Check the number of seen tokens is not less the min number of tokens.
 | 
						|
   Complain or die otherwise depending on PARSE_MIN_DIE.
 | 
						|
8. Return the number of seen tokens.
 | 
						|
 | 
						|
mintokens > 0 make config_read() print 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;
 | 
						|
	int ntokens, mintokens;
 | 
						|
	int t;
 | 
						|
 | 
						|
	if (!parser)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	ntokens = (uint8_t)flags;
 | 
						|
	mintokens = (uint8_t)(flags >> 8);
 | 
						|
 | 
						|
 again:
 | 
						|
	memset(tokens, 0, sizeof(tokens[0]) * ntokens);
 | 
						|
 | 
						|
	/* Read one line (handling continuations with backslash) */
 | 
						|
	if (get_line_with_continuation(parser) < 0)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	line = parser->line;
 | 
						|
 | 
						|
	/* Skip token in the start of line? */
 | 
						|
	if (flags & PARSE_TRIM)
 | 
						|
		line += strspn(line, delims + 1);
 | 
						|
 | 
						|
	if (line[0] == '\0' || line[0] == delims[0])
 | 
						|
		goto again;
 | 
						|
 | 
						|
	if (flags & PARSE_KEEP_COPY) {
 | 
						|
		free(parser->data);
 | 
						|
		parser->data = xstrdup(line);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Tokenize the line */
 | 
						|
	t = 0;
 | 
						|
	do {
 | 
						|
		/* Pin token */
 | 
						|
		tokens[t] = line;
 | 
						|
 | 
						|
		/* Combine remaining arguments? */
 | 
						|
		if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
 | 
						|
			/* Vanilla token, find next delimiter */
 | 
						|
			line += strcspn(line, delims[0] ? delims : delims + 1);
 | 
						|
		} else {
 | 
						|
			/* Combining, find comment char if any */
 | 
						|
			line = strchrnul(line, PARSE_EOL_COMMENTS ? delims[0] : '\0');
 | 
						|
 | 
						|
			/* Trim any extra delimiters from the end */
 | 
						|
			if (flags & PARSE_TRIM) {
 | 
						|
				while (strchr(delims + 1, line[-1]) != NULL)
 | 
						|
					line--;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* Token not terminated? */
 | 
						|
		if (*line == delims[0])
 | 
						|
			*line = '\0';
 | 
						|
		else if (*line != '\0')
 | 
						|
			*line++ = '\0';
 | 
						|
 | 
						|
#if 0 /* unused so far */
 | 
						|
		if (flags & PARSE_ESCAPE) {
 | 
						|
			strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
 | 
						|
		}
 | 
						|
#endif
 | 
						|
		/* Skip possible delimiters */
 | 
						|
		if (flags & PARSE_COLLAPSE)
 | 
						|
			line += strspn(line, delims + 1);
 | 
						|
 | 
						|
		t++;
 | 
						|
	} while (*line && *line != delims[0] && t < ntokens);
 | 
						|
 | 
						|
	if (t < mintokens) {
 | 
						|
		bb_error_msg("bad line %u: %d tokens found, %d needed",
 | 
						|
				parser->lineno, t, mintokens);
 | 
						|
		if (flags & PARSE_MIN_DIE)
 | 
						|
			xfunc_die();
 | 
						|
		goto again;
 | 
						|
	}
 | 
						|
 | 
						|
	return t;
 | 
						|
}
 |