blob: 83dc997f6fffe7b5793677e4323ac67ddf307ea7 [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";
17 unsigned flags = 0;
18 int mintokens = 0, ntokens = 128;
19 opt_complementary = "-1:n+:m+:f+";
20 getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
21 //argc -= optind;
22 argv += optind;
23 while (*argv) {
24 parser_t *p = config_open(*argv);
25 if (p) {
26 int n;
27 char **t = xmalloc(sizeof(char *) * ntokens);
Denis Vlasenko4a717e02008-07-20 13:01:56 +000028 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000029 for (int i = 0; i < n; ++i)
30 printf("[%s]", t[i]);
31 puts("");
32 }
33 config_close(p);
34 }
35 argv++;
36 }
37 return EXIT_SUCCESS;
38}
39#endif
40
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000041/*
42
43Typical usage:
44
45----- CUT -----
46 char *t[3]; // tokens placeholder
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000047 parser_t *p = config_open(filename);
48 if (p) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000049 // parse line-by-line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000050 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000051 // use tokens
52 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
53 }
54 ...
55 // free parser
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000056 config_close(p);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000057 }
58----- CUT -----
59
60*/
61
Denis Vlasenko5415c852008-07-21 23:05:26 +000062parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000063{
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000064 parser_t *parser = xzalloc(sizeof(parser_t));
65 /* empty file configures nothing */
Denis Vlasenko5415c852008-07-21 23:05:26 +000066 parser->fp = fopen_func(filename);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000067 if (parser->fp)
68 return parser;
Denis Vlasenko0f99d492008-07-24 23:38:04 +000069 free(parser);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000070 return NULL;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000071}
72
Denis Vlasenko5415c852008-07-21 23:05:26 +000073parser_t* FAST_FUNC config_open(const char *filename)
74{
75 return config_open2(filename, fopen_or_warn_stdin);
76}
77
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000078static void config_free_data(parser_t *const parser)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000079{
Denis Vlasenkoc01340f2008-07-16 22:12:18 +000080 free(parser->line);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000081 parser->line = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000082 if (PARSE_KEEP_COPY) { /* compile-time constant */
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000083 free(parser->data);
84 parser->data = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000085 }
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000086}
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000087
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000088void FAST_FUNC config_close(parser_t *parser)
89{
90 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000091 fclose(parser->fp);
92}
93
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000094/*
951. Read a line from config file. If nothing to read then bail out returning 0.
96 Handle continuation character. Advance lineno for each physical line. Cut comments.
972. if PARSE_DONT_TRIM is not set (default) skip leading and cut trailing delimiters, if any.
983. If resulting line is empty goto 1.
994. Look for first delimiter. If PARSE_DONT_REDUCE or PARSE_DONT_TRIM is set then pin empty token.
1005. Else (default) if number of seen tokens is equal to max number of tokens (token is the last one)
101 and PARSE_LAST_IS_GREEDY is set then pin the remainder of the line as the last token.
102 Else (token is not last or PARSE_LAST_IS_GREEDY is not set) just replace first delimiter with '\0'
103 thus delimiting token and pin it.
1046. Advance line pointer past the end of token. If number of seen tokens is less than required number
105 of tokens then goto 4.
1067. Control the number of seen tokens is not less the min number of tokens. Die if condition is not met.
1078. Return the number of seen tokens.
108
109mintokens > 0 make config_read() exit with error message if less than mintokens
110(but more than 0) are found. Empty lines are always skipped (not warned about).
111*/
112#undef config_read
113int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000114{
115 char *line, *q;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000116 char comment = *delims++;
117 int ii;
118 int ntokens = flags & 0xFF;
119 int mintokens = (flags & 0xFF00) >> 8;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000120
Denis Vlasenko5415c852008-07-21 23:05:26 +0000121 again:
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000122 // N.B. this could only be used in read-in-one-go version, or when tokens use xstrdup(). TODO
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000123 //if (!parser->lineno || !(flags & PARSE_DONT_NULL))
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000124 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000125 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000126
127 while (1) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000128//TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
129 line = xmalloc_fgetline(parser->fp);
130 if (!line)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000131 return 0;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000132
133 parser->lineno++;
134 // handle continuations. Tito's code stolen :)
135 while (1) {
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000136 ii = strlen(line);
137 if (!ii)
138 goto next_line;
139 if (line[ii - 1] != '\\')
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000140 break;
141 // multi-line object
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000142 line[--ii] = '\0';
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000143//TODO: add xmalloc_fgetline-like iface but with appending to existing str
144 q = xmalloc_fgetline(parser->fp);
145 if (q) {
146 parser->lineno++;
147 line = xasprintf("%s%s", line, q);
148 free(q);
149 }
150 }
151 // comments mean EOLs
152 if (comment) {
153 q = strchrnul(line, comment);
154 *q = '\0';
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000155 ii = q - line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000156 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000157 // skip leading and trailing delimiters
158 if (!(flags & PARSE_DONT_TRIM)) {
159 // skip leading
160 int n = strspn(line, delims);
161 if (n) {
162 ii -= n;
Denis Vlasenko0f293b92008-07-22 20:16:55 +0000163 overlapping_strcpy(line, line + n);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000164 }
165 // cut trailing
166 if (ii) {
167 while (strchr(delims, line[--ii]))
168 continue;
169 line[++ii] = '\0';
170 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000171 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000172 // if something still remains -> return it
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000173 if (ii)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000174 break;
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000175
176 next_line:
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000177 // skip empty line
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000178 free(line);
179 }
180
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000181 // non-empty line found, parse and return the number of tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000182
183 // store line
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000184 parser->line = line = xrealloc(line, ii + 1);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000185 if (flags & PARSE_KEEP_COPY) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000186 parser->data = xstrdup(line);
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +0000187 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000188
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000189 // split line to tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000190 ntokens--; // now it's max allowed token no
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000191 // N.B. non-empty remainder is also a token,
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000192 // so if ntokens <= 1, we just return the whole line
193 // N.B. if PARSE_LAST_IS_GREEDY is set the remainder of the line is stuck to the last token
194 for (ii = 0; *line && ii <= ntokens; ) {
195 //bb_info_msg("L[%s]", line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000196 // get next token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000197 // at the last token and need greedy token ->
198 if ((flags & PARSE_LAST_IS_GREEDY) && (ii == ntokens)) {
Denis Vlasenko4a717e02008-07-20 13:01:56 +0000199 // skip possible delimiters
200 if (!(flags & PARSE_DONT_REDUCE))
201 line += strspn(line, delims);
202 // don't cut the line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000203 q = line + strlen(line);
204 } else {
205 // vanilla token. cut the line at the first delim
206 q = line + strcspn(line, delims);
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000207 if (*q) // watch out: do not step past the line end!
208 *q++ = '\0';
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000209 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000210 // pin token
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000211 if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
212 //bb_info_msg("N[%d] T[%s]", ii, line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000213 tokens[ii++] = line;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000214 // process escapes in token
215 if (flags & PARSE_ESCAPE) {
216 char *s = line;
217 while (*s) {
218 if (*s == '\\') {
219 s++;
220 *line++ = bb_process_escape_sequence((const char **)&s);
221 } else {
222 *line++ = *s++;
223 }
224 }
225 *line = '\0';
226 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000227 }
228 line = q;
Denis Vlasenko9b366f42008-07-20 17:50:58 +0000229 //bb_info_msg("A[%s]", line);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000230 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000231
Denis Vlasenko5415c852008-07-21 23:05:26 +0000232 if (ii < mintokens) {
233 bb_error_msg("bad line %u: %d tokens found, %d needed",
234 parser->lineno, ii, mintokens);
235 if (flags & PARSE_MIN_DIE)
236 xfunc_die();
237 goto again;
238 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000239
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000240 return ii;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000241}