blob: a0599d4b4505b4cfdbc0e37073c19012409624fd [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{
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000126 char *line;
127 int ntokens, mintokens;
128 int t, len;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000129
Denis Vlasenko084266e2008-07-26 23:08:31 +0000130 ntokens = flags & 0xFF;
131 mintokens = (flags & 0xFF00) >> 8;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000132
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000133 if (parser == NULL)
Denis Vlasenko084266e2008-07-26 23:08:31 +0000134 return 0;
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000135
136again:
137 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000138 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000139
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000140 /* Read one line (handling continuations with backslash) */
141 line = bb_get_chunk_with_continuation(parser->fp, &len, &parser->lineno);
142 if (line == NULL)
143 return 0;
144 parser->line = line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000145
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000146 /* Strip trailing line-feed if any */
147 if (len && line[len-1] == '\n')
148 line[len-1] = '\0';
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000149
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000150 /* Skip token in the start of line? */
151 if (flags & PARSE_TRIM)
152 line += strspn(line, delims + 1);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000153
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000154 if (line[0] == '\0' || line[0] == delims[0])
155 goto again;
156
157 if (flags & PARSE_KEEP_COPY)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000158 parser->data = xstrdup(line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000159
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000160 /* Tokenize the line */
161 for (t = 0; *line && *line != delims[0] && t < ntokens; t++) {
162 /* Pin token */
163 tokens[t] = line;
164
165 /* Combine remaining arguments? */
166 if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
167 /* Vanilla token, find next delimiter */
168 line += strcspn(line, delims[0] ? delims : delims + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000169 } else {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000170 /* Combining, find comment char if any */
171 line = strchrnul(line, delims[0]);
172
173 /* Trim any extra delimiters from the end */
174 if (flags & PARSE_TRIM) {
175 while (strchr(delims + 1, line[-1]) != NULL)
176 line--;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000177 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000178 }
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000179
180 /* Token not terminated? */
181 if (line[0] == delims[0])
182 *line = '\0';
183 else if (line[0] != '\0')
184 *(line++) = '\0';
185
186#if 0 /* unused so far */
187 if (flags & PARSE_ESCAPE) {
188 const char *from;
189 char *to;
190
191 from = to = tokens[t];
192 while (*from) {
193 if (*from == '\\') {
194 from++;
195 *to++ = bb_process_escape_sequence(&from);
196 } else {
197 *to++ = *from++;
198 }
199 }
200 *to = '\0';
201 }
202#endif
203
204 /* Skip possible delimiters */
205 if (flags & PARSE_COLLAPSE)
206 line += strspn(line, delims + 1);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000207 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000208
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000209 if (t < mintokens) {
Denis Vlasenko5415c852008-07-21 23:05:26 +0000210 bb_error_msg("bad line %u: %d tokens found, %d needed",
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000211 parser->lineno, t, mintokens);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000212 if (flags & PARSE_MIN_DIE)
213 xfunc_die();
214 goto again;
215 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000216
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000217 return t;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000218}