1 /*
2  * config file parser helper
3  *
4  * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
5  *
6  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7  * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
8  */
9 
10 #if !defined _LIBC
11 #include "libbb.h"
12 
13 #if defined ENABLE_PARSE && ENABLE_PARSE
14 int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
parse_main(int argc UNUSED_PARAM,char ** argv)15 int parse_main(int argc UNUSED_PARAM, char **argv)
16 {
17 	const char *delims = "# \t";
18 	unsigned flags = PARSE_NORMAL;
19 	int mintokens = 0, ntokens = 128;
20 
21 	opt_complementary = "-1:n+:m+:f+";
22 	getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
23 	//argc -= optind;
24 	argv += optind;
25 	while (*argv) {
26 		parser_t *p = config_open(*argv);
27 		if (p) {
28 			int n;
29 			char **t = xmalloc(sizeof(char *) * ntokens);
30 			while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
31 				for (int i = 0; i < n; ++i)
32 					printf("[%s]", t[i]);
33 				puts("");
34 			}
35 			config_close(p);
36 		}
37 		argv++;
38 	}
39 	return EXIT_SUCCESS;
40 }
41 #endif
42 #else
43 # include <unistd.h>
44 # include <string.h>
45 # include <malloc.h>
46 # include <bits/uClibc_page.h>
47 # include "internal/parse_config.h"
48 # ifndef FAST_FUNC
49 #  define FAST_FUNC
50 # endif
51 # define fopen_or_warn_stdin fopen
52 # define bb_error_msg(...)
53 # define xstrdup strdup
54 # define xfunc_die() return 0
55 /* Read up to EOF or EOL, treat line-continuations as extending the line.
56    Return number of bytes read into .line, -1 otherwise  */
bb_get_chunk_with_continuation(parser_t * parsr)57 static off_t bb_get_chunk_with_continuation(parser_t* parsr)
58 {
59 	off_t pos = 0;
60 	char *chp;
61 
62 	while (1) {
63 		if (fgets(parsr->line + pos, parsr->line_len - pos, parsr->fp) == NULL) {
64 			memset(parsr->line, 0, parsr->line_len);
65 			pos = -1;
66 			break;
67 		}
68 		pos += strlen(parsr->line + pos);
69 		chp = strchr(parsr->line, '\n');
70 		if (chp) {
71 			--pos;
72 			if (--*chp == '\\')
73 				--pos;
74 			else
75 				break;
76 		} else if (parsr->allocated) {
77 			parsr->line_len += PAGE_SIZE;
78 			parsr->data = realloc(parsr->data,
79 								   parsr->data_len + parsr->line_len);
80 			parsr->line = parsr->data + parsr->data_len;
81 		} else {
82 			/* discard rest of line if not enough space in buffer */
83 			int c;
84 			do {
85 				c = fgetc(parsr->fp);
86 			} while (c != EOF && c != '\n');
87 			break;
88 		}
89 	}
90 	return pos;
91 }
92 #endif
93 
94 /*
95 
96 Typical usage:
97 
98 ----- CUT -----
99 	char *t[3];	// tokens placeholder
100 	parser_t *p = config_open(filename);
101 	if (p) {
102 		// parse line-by-line
103 		while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
104 			// use tokens
105 			bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
106 		}
107 		...
108 		// free parser
109 		config_close(p);
110 	}
111 ----- CUT -----
112 
113 */
114 
config_open2(const char * filename,FILE * FAST_FUNC (* fopen_func)(const char * path,const char * mode))115 static __always_inline parser_t * FAST_FUNC config_open2(const char *filename,
116 	FILE* FAST_FUNC (*fopen_func)(const char *path, const char *mode))
117 {
118 	parser_t *parser;
119 	FILE* fp;
120 
121 	fp = fopen_func(filename, "r");
122 	if (!fp)
123 		return NULL;
124 	parser = calloc(1, sizeof(*parser));
125 	if (parser) {
126 		parser->fp = fp;
127 	}
128 	return parser;
129 }
130 
config_open(const char * filename)131 parser_t * FAST_FUNC config_open(const char *filename)
132 {
133 	return config_open2(filename, fopen_or_warn_stdin);
134 }
135 
136 #ifdef UNUSED
config_free_data(parser_t * parser)137 static void config_free_data(parser_t *parser)
138 {
139 	free(parser->data);
140 	parser->data = parser->line = NULL;
141 }
142 #endif
143 
config_close(parser_t * parser)144 void FAST_FUNC config_close(parser_t *parser)
145 {
146 	if (parser) {
147 		fclose(parser->fp);
148 		if (parser->allocated)
149 			free(parser->data);
150 		free(parser);
151 	}
152 }
153 
154 /*
155 0. If parser is NULL return 0.
156 1. Read a line from config file. If nothing to read then return 0.
157    Handle continuation character. Advance lineno for each physical line.
158    Discard everything past comment character.
159 2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
160 3. If resulting line is empty goto 1.
161 4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
162    remember the token as empty.
163 5. Else (default) if number of seen tokens is equal to max number of tokens
164    (token is the last one) and PARSE_GREEDY is set then the remainder
165    of the line is the last token.
166    Else (token is not last or PARSE_GREEDY is not set) just replace
167    first delimiter with '\0' thus delimiting the token.
168 6. Advance line pointer past the end of token. If number of seen tokens
169    is less than required number of tokens then goto 4.
170 7. Check the number of seen tokens is not less the min number of tokens.
171    Complain or die otherwise depending on PARSE_MIN_DIE.
172 8. Return the number of seen tokens.
173 
174 mintokens > 0 make config_read() print error message if less than mintokens
175 (but more than 0) are found. Empty lines are always skipped (not warned about).
176 */
177 #undef config_read
config_read(parser_t * parser,char *** tokens,unsigned flags,const char * delims)178 int FAST_FUNC config_read(parser_t *parser, char ***tokens,
179 											unsigned flags, const char *delims)
180 {
181 	char *line;
182 	int ntokens, mintokens;
183 	off_t len;
184 	int t;
185 
186 	if (parser == NULL)
187 		return 0;
188 	ntokens = flags & 0xFF;
189 	mintokens = (flags & 0xFF00) >> 8;
190 again:
191 	if (parser->data == NULL) {
192 		if (parser->line_len == 0)
193 			parser->line_len = 81;
194 		if (parser->data_len == 0)
195 			parser->data_len += 1 + ntokens * sizeof(char *);
196 		parser->data = malloc(parser->data_len + parser->line_len);
197 		if (parser->data == NULL)
198 			return 0;
199 		parser->allocated |= 1;
200 	} /* else { assert(parser->data_len > 0); } */
201 	parser->line = parser->data + parser->data_len;
202 	/*config_free_data(parser);*/
203 
204 	/* Read one line (handling continuations with backslash) */
205 	len = bb_get_chunk_with_continuation(parser);
206 	if (len == -1)
207 		return 0;
208 	line = parser->line;
209 
210 	/* Skip multiple token-delimiters in the start of line? */
211 	if (flags & PARSE_TRIM)
212 		line += strspn(line, delims + 1);
213 
214 	if (line[0] == '\0' || line[0] == delims[0])
215 		goto again;
216 
217 	*tokens = (char **) parser->data;
218 	memset(*tokens, 0, sizeof(*tokens[0]) * ntokens);
219 
220 	/* Tokenize the line */
221 	for (t = 0; *line && *line != delims[0] && t < ntokens; t++) {
222 		/* Pin token */
223 		*(*tokens + t) = line;
224 
225 		/* Combine remaining arguments? */
226 		if ((t != ntokens-1) || !(flags & PARSE_GREEDY)) {
227 			/* Vanilla token, find next delimiter */
228 			line += strcspn(line, delims[0] ? delims : delims + 1);
229 		} else {
230 			/* Combining, find comment char if any */
231 			line = strchrnul(line, delims[0]);
232 
233 			/* Trim any extra delimiters from the end */
234 			if (flags & PARSE_TRIM) {
235 				while (strchr(delims + 1, line[-1]) != NULL)
236 					line--;
237 			}
238 		}
239 
240 		/* Token not terminated? */
241 		if (line[0] == delims[0])
242 			*line = '\0';
243 		else if (line[0] != '\0')
244 			*(line++) = '\0';
245 
246 #if 0 /* unused so far */
247 		if (flags & PARSE_ESCAPE) {
248 			const char *from;
249 			char *to;
250 
251 			from = to = tokens[t];
252 			while (*from) {
253 				if (*from == '\\') {
254 					from++;
255 					*to++ = bb_process_escape_sequence(&from);
256 				} else {
257 					*to++ = *from++;
258 				}
259 			}
260 			*to = '\0';
261 		}
262 #endif
263 
264 		/* Skip possible delimiters */
265 		if (flags & PARSE_COLLAPSE)
266 			line += strspn(line, delims + 1);
267 	}
268 
269 	if (t < mintokens) {
270 		bb_error_msg(/*"bad line %u: "*/"%d tokens found, %d needed",
271 				/*parser->lineno, */t, mintokens);
272 		if (flags & PARSE_MIN_DIE)
273 			xfunc_die();
274 		goto again;
275 	}
276 	return t;
277 }
278