blob: ace6f3ad3e807020c5d8e3a01017708066d49a24 [file] [log] [blame]
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +00001/* vi: set sw=4 ts=4: */
2/*
3 * config file parser helper
4 *
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
6 *
7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
8 */
9
10#include "libbb.h"
11
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000012#if ENABLE_PARSE
13int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
14int parse_main(int argc UNUSED_PARAM, char **argv)
15{
16 const char *delims = "# \t";
Denis Vlasenko084266e2008-07-26 23:08:31 +000017 unsigned flags = PARSE_NORMAL;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000018 int mintokens = 0, ntokens = 128;
Denis Vlasenko084266e2008-07-26 23:08:31 +000019
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000020 opt_complementary = "-1:n+:m+:f+";
21 getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
22 //argc -= optind;
23 argv += optind;
24 while (*argv) {
25 parser_t *p = config_open(*argv);
26 if (p) {
27 int n;
28 char **t = xmalloc(sizeof(char *) * ntokens);
Denis Vlasenko4a717e02008-07-20 13:01:56 +000029 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000030 for (int i = 0; i < n; ++i)
31 printf("[%s]", t[i]);
32 puts("");
33 }
34 config_close(p);
35 }
36 argv++;
37 }
38 return EXIT_SUCCESS;
39}
40#endif
41
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000042/*
43
44Typical usage:
45
46----- CUT -----
47 char *t[3]; // tokens placeholder
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000048 parser_t *p = config_open(filename);
49 if (p) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000050 // parse line-by-line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000051 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000052 // use tokens
53 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
54 }
55 ...
56 // free parser
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000057 config_close(p);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000058 }
59----- CUT -----
60
61*/
62
Denis Vlasenko5415c852008-07-21 23:05:26 +000063parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000064{
Denis Vlasenko084266e2008-07-26 23:08:31 +000065 FILE* fp;
66 parser_t *parser;
67
68 fp = fopen_func(filename);
69 if (!fp)
70 return NULL;
71 parser = xzalloc(sizeof(*parser));
72 parser->fp = fp;
73 return parser;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000074}
75
Denis Vlasenko5415c852008-07-21 23:05:26 +000076parser_t* FAST_FUNC config_open(const char *filename)
77{
78 return config_open2(filename, fopen_or_warn_stdin);
79}
80
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000081static void config_free_data(parser_t *const parser)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000082{
Denis Vlasenkoc01340f2008-07-16 22:12:18 +000083 free(parser->line);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000084 parser->line = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000085 if (PARSE_KEEP_COPY) { /* compile-time constant */
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000086 free(parser->data);
87 parser->data = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000088 }
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000089}
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000090
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000091void FAST_FUNC config_close(parser_t *parser)
92{
Denis Vlasenko084266e2008-07-26 23:08:31 +000093 if (parser) {
94 config_free_data(parser);
95 fclose(parser->fp);
96 free(parser);
97 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000098}
99
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000100/*
Denis Vlasenko084266e2008-07-26 23:08:31 +00001010. If parser is NULL return 0.
1021. Read a line from config file. If nothing to read then return 0.
103 Handle continuation character. Advance lineno for each physical line.
104 Discard everything past comment characher.
1052. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001063. If resulting line is empty goto 1.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001074. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
108 remember the token as empty.
1095. Else (default) if number of seen tokens is equal to max number of tokens
110 (token is the last one) and PARSE_GREEDY is set then the remainder
111 of the line is the last token.
112 Else (token is not last or PARSE_GREEDY is not set) just replace
113 first delimiter with '\0' thus delimiting the token.
1146. Advance line pointer past the end of token. If number of seen tokens
115 is less than required number of tokens then goto 4.
1167. Check the number of seen tokens is not less the min number of tokens.
117 Complain or die otherwise depending on PARSE_MIN_DIE.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001188. Return the number of seen tokens.
119
Denis Vlasenko084266e2008-07-26 23:08:31 +0000120mintokens > 0 make config_read() print error message if less than mintokens
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000121(but more than 0) are found. Empty lines are always skipped (not warned about).
122*/
123#undef config_read
124int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000125{
126 char *line, *q;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000127 char comment;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000128 int ii;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000129 int ntokens;
130 int mintokens;
131
132 comment = *delims++;
133 ntokens = flags & 0xFF;
134 mintokens = (flags & 0xFF00) >> 8;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000135
Denis Vlasenko5415c852008-07-21 23:05:26 +0000136 again:
Denis Vlasenko084266e2008-07-26 23:08:31 +0000137 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
138 if (!parser)
139 return 0;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000140 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000141
142 while (1) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000143//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
144 line = xmalloc_fgetline(parser->fp);
145 if (!line)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000146 return 0;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000147
148 parser->lineno++;
149 // handle continuations. Tito's code stolen :)
150 while (1) {
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000151 ii = strlen(line);
152 if (!ii)
153 goto next_line;
154 if (line[ii - 1] != '\\')
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000155 break;
156 // multi-line object
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000157 line[--ii] = '\0';
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000158//TODO: add xmalloc_fgetline-like iface but with appending to existing str
159 q = xmalloc_fgetline(parser->fp);
Denis Vlasenko084266e2008-07-26 23:08:31 +0000160 if (!q)
161 break;
162 parser->lineno++;
163 line = xasprintf("%s%s", line, q);
164 free(q);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000165 }
Denis Vlasenko084266e2008-07-26 23:08:31 +0000166 // discard comments
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000167 if (comment) {
168 q = strchrnul(line, comment);
169 *q = '\0';
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000170 ii = q - line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000171 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000172 // skip leading and trailing delimiters
Denis Vlasenko084266e2008-07-26 23:08:31 +0000173 if (flags & PARSE_TRIM) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000174 // skip leading
175 int n = strspn(line, delims);
176 if (n) {
177 ii -= n;
Denis Vlasenko0f293b92008-07-22 20:16:55 +0000178 overlapping_strcpy(line, line + n);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000179 }
180 // cut trailing
181 if (ii) {
182 while (strchr(delims, line[--ii]))
183 continue;
184 line[++ii] = '\0';
185 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000186 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000187 // if something still remains -> return it
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000188 if (ii)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000189 break;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000190
191 next_line:
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000192 // skip empty line
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000193 free(line);
194 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000195 // non-empty line found, parse and return the number of tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000196
197 // store line
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000198 parser->line = line = xrealloc(line, ii + 1);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000199 if (flags & PARSE_KEEP_COPY) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000200 parser->data = xstrdup(line);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000201 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000202
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000203 // split line to tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000204 ntokens--; // now it's max allowed token no
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000205 // N.B. non-empty remainder is also a token,
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000206 // so if ntokens <= 1, we just return the whole line
Denis Vlasenko084266e2008-07-26 23:08:31 +0000207 // N.B. if PARSE_GREEDY is set the remainder of the line is stuck to the last token
208 ii = 0;
209 while (*line && ii <= ntokens) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000210 //bb_info_msg("L[%s]", line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000211 // get next token
Denis Vlasenko084266e2008-07-26 23:08:31 +0000212 // at last token and need greedy token ->
213 if ((flags & PARSE_GREEDY) && (ii == ntokens)) {
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000214 // skip possible delimiters
Denis Vlasenko084266e2008-07-26 23:08:31 +0000215 if (flags & PARSE_COLLAPSE)
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000216 line += strspn(line, delims);
217 // don't cut the line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000218 q = line + strlen(line);
219 } else {
220 // vanilla token. cut the line at the first delim
221 q = line + strcspn(line, delims);
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000222 if (*q) // watch out: do not step past the line end!
223 *q++ = '\0';
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000224 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000225 // pin token
Denis Vlasenko084266e2008-07-26 23:08:31 +0000226 if (!(flags & (PARSE_COLLAPSE | PARSE_TRIM)) || *line) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000227 //bb_info_msg("N[%d] T[%s]", ii, line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000228 tokens[ii++] = line;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000229 // process escapes in token
Denis Vlasenko084266e2008-07-26 23:08:31 +0000230#if 0 // unused so far
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000231 if (flags & PARSE_ESCAPE) {
232 char *s = line;
233 while (*s) {
234 if (*s == '\\') {
235 s++;
236 *line++ = bb_process_escape_sequence((const char **)&s);
237 } else {
238 *line++ = *s++;
239 }
240 }
241 *line = '\0';
242 }
Denis Vlasenko084266e2008-07-26 23:08:31 +0000243#endif
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000244 }
245 line = q;
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000246 //bb_info_msg("A[%s]", line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000247 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000248
Denis Vlasenko5415c852008-07-21 23:05:26 +0000249 if (ii < mintokens) {
250 bb_error_msg("bad line %u: %d tokens found, %d needed",
251 parser->lineno, ii, mintokens);
252 if (flags & PARSE_MIN_DIE)
253 xfunc_die();
Denis Vlasenko084266e2008-07-26 23:08:31 +0000254 ntokens++;
Denis Vlasenko5415c852008-07-21 23:05:26 +0000255 goto again;
256 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000257
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000258 return ii;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000259}