blob: eaf69d97f995eeb9566dbcc16ca318f67abd08a3 [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 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02007 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
Bernhard Reutner-Fischerf3b39a22009-02-23 16:21:53 +00008 * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +00009 */
10
Denys Vlasenko3a649362011-06-18 09:23:09 +020011/* Uncomment to enable test applet */
12////config:config PARSE
13////config: bool "Uniform config file parser debugging applet: parse"
14////config: default n
15////config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020016////config: Typical usage of parse API:
Denys Vlasenko3a649362011-06-18 09:23:09 +020017////config: char *t[3];
18////config: parser_t *p = config_open(filename);
19////config: while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
20////config: bb_error_msg("TOKENS: '%s''%s''%s'", t[0], t[1], t[2]);
21////config: }
22////config: config_close(p);
23
24////applet:IF_PARSE(APPLET(parse, BB_DIR_USR_BIN, BB_SUID_DROP))
25
26//kbuild:lib-y += parse_config.o
27
Pere Orga5bc8c002011-04-11 03:29:49 +020028//usage:#define parse_trivial_usage
Denys Vlasenko3a649362011-06-18 09:23:09 +020029//usage: "[-x] [-n MAXTOKENS] [-m MINTOKENS] [-d DELIMS] [-f FLAGS] FILE..."
30//usage:#define parse_full_usage "\n\n"
31//usage: " -x Suppress output (for benchmarking)"
Pere Orga5bc8c002011-04-11 03:29:49 +020032
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000033#include "libbb.h"
34
Denis Vlasenko2d5bd802008-10-24 10:49:49 +000035#if defined ENABLE_PARSE && ENABLE_PARSE
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000036int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
37int parse_main(int argc UNUSED_PARAM, char **argv)
38{
39 const char *delims = "# \t";
Denys Vlasenko3a649362011-06-18 09:23:09 +020040 char **t;
Denis Vlasenko084266e2008-07-26 23:08:31 +000041 unsigned flags = PARSE_NORMAL;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000042 int mintokens = 0, ntokens = 128;
Denys Vlasenko3a649362011-06-18 09:23:09 +020043 unsigned noout;
Denis Vlasenko084266e2008-07-26 23:08:31 +000044
Denys Vlasenko237bedd2016-07-06 21:58:02 +020045 opt_complementary = "-1";
46 noout = 1 & getopt32(argv, "xn:+m:+d:f:+", &ntokens, &mintokens, &delims, &flags);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000047 //argc -= optind;
48 argv += optind;
Denys Vlasenko3a649362011-06-18 09:23:09 +020049
50 t = xmalloc(sizeof(t[0]) * ntokens);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000051 while (*argv) {
Denys Vlasenko3a649362011-06-18 09:23:09 +020052 int n;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000053 parser_t *p = config_open(*argv);
Denys Vlasenko3a649362011-06-18 09:23:09 +020054 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
55 if (!noout) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000056 for (int i = 0; i < n; ++i)
57 printf("[%s]", t[i]);
58 puts("");
59 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000060 }
Denys Vlasenko3a649362011-06-18 09:23:09 +020061 config_close(p);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000062 argv++;
63 }
64 return EXIT_SUCCESS;
65}
66#endif
67
Denis Vlasenko5415c852008-07-21 23:05:26 +000068parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000069{
Denis Vlasenko084266e2008-07-26 23:08:31 +000070 FILE* fp;
71 parser_t *parser;
72
73 fp = fopen_func(filename);
74 if (!fp)
75 return NULL;
76 parser = xzalloc(sizeof(*parser));
77 parser->fp = fp;
78 return parser;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000079}
80
Denis Vlasenko5415c852008-07-21 23:05:26 +000081parser_t* FAST_FUNC config_open(const char *filename)
82{
83 return config_open2(filename, fopen_or_warn_stdin);
84}
85
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000086void FAST_FUNC config_close(parser_t *parser)
87{
Denis Vlasenko084266e2008-07-26 23:08:31 +000088 if (parser) {
Timo Terasadcabf32011-06-20 09:49:56 +020089 if (PARSE_KEEP_COPY) /* compile-time constant */
90 free(parser->data);
Denis Vlasenko084266e2008-07-26 23:08:31 +000091 fclose(parser->fp);
Timo Terasadcabf32011-06-20 09:49:56 +020092 free(parser->line);
93 free(parser->nline);
Denis Vlasenko084266e2008-07-26 23:08:31 +000094 free(parser);
95 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000096}
97
Timo Terasadcabf32011-06-20 09:49:56 +020098/* This function reads an entire line from a text file,
99 * up to a newline, exclusive.
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200100 * Trailing '\' is recognized as line continuation.
Timo Terasadcabf32011-06-20 09:49:56 +0200101 * Returns -1 if EOF/error.
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200102 */
Timo Terasadcabf32011-06-20 09:49:56 +0200103static int get_line_with_continuation(parser_t *parser)
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200104{
Timo Terasadcabf32011-06-20 09:49:56 +0200105 ssize_t len, nlen;
106 char *line;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200107
Timo Terasadcabf32011-06-20 09:49:56 +0200108 len = getline(&parser->line, &parser->line_alloc, parser->fp);
109 if (len <= 0)
110 return len;
111
112 line = parser->line;
113 for (;;) {
114 parser->lineno++;
115 if (line[len - 1] == '\n')
116 len--;
117 if (len == 0 || line[len - 1] != '\\')
118 break;
119 len--;
120
121 nlen = getline(&parser->nline, &parser->nline_alloc, parser->fp);
122 if (nlen <= 0)
123 break;
124
125 if (parser->line_alloc < len + nlen + 1) {
126 parser->line_alloc = len + nlen + 1;
127 line = parser->line = xrealloc(line, parser->line_alloc);
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200128 }
Timo Terasadcabf32011-06-20 09:49:56 +0200129 memcpy(&line[len], parser->nline, nlen);
130 len += nlen;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200131 }
Timo Terasadcabf32011-06-20 09:49:56 +0200132
133 line[len] = '\0';
134 return len;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200135}
136
137
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000138/*
Denis Vlasenko084266e2008-07-26 23:08:31 +00001390. If parser is NULL return 0.
1401. Read a line from config file. If nothing to read then return 0.
141 Handle continuation character. Advance lineno for each physical line.
Denys Vlasenko5370bfb2009-09-06 02:58:59 +0200142 Discard everything past comment character.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001432. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001443. If resulting line is empty goto 1.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001454. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
146 remember the token as empty.
1475. Else (default) if number of seen tokens is equal to max number of tokens
148 (token is the last one) and PARSE_GREEDY is set then the remainder
149 of the line is the last token.
150 Else (token is not last or PARSE_GREEDY is not set) just replace
151 first delimiter with '\0' thus delimiting the token.
1526. Advance line pointer past the end of token. If number of seen tokens
153 is less than required number of tokens then goto 4.
1547. Check the number of seen tokens is not less the min number of tokens.
155 Complain or die otherwise depending on PARSE_MIN_DIE.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001568. Return the number of seen tokens.
157
Denis Vlasenko084266e2008-07-26 23:08:31 +0000158mintokens > 0 make config_read() print error message if less than mintokens
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000159(but more than 0) are found. Empty lines are always skipped (not warned about).
160*/
161#undef config_read
162int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000163{
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200164 char *line, *p;
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000165 int ntokens, mintokens;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200166 int t;
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200167 char alt_comment_ch;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200168
169 if (!parser)
170 return 0;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000171
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200172 alt_comment_ch = '\0';
173 if (flags & PARSE_ALT_COMMENTS)
174 alt_comment_ch = *delims++;
175
Denys Vlasenko63144be2010-06-26 04:00:52 +0200176 ntokens = (uint8_t)flags;
177 mintokens = (uint8_t)(flags >> 8);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000178
Timo Terasadcabf32011-06-20 09:49:56 +0200179 again:
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000180 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000181
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000182 /* Read one line (handling continuations with backslash) */
Timo Terasadcabf32011-06-20 09:49:56 +0200183 if (get_line_with_continuation(parser) < 0)
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000184 return 0;
Timo Terasadcabf32011-06-20 09:49:56 +0200185
186 line = parser->line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000187
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000188 /* Skip token in the start of line? */
189 if (flags & PARSE_TRIM)
190 line += strspn(line, delims + 1);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000191
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200192 p = line;
193 if (flags & PARSE_WS_COMMENTS)
194 p = skip_whitespace(p);
195 if (p[0] == '\0' || p[0] == delims[0] || p[0] == alt_comment_ch)
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000196 goto again;
197
Timo Terasadcabf32011-06-20 09:49:56 +0200198 if (flags & PARSE_KEEP_COPY) {
199 free(parser->data);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000200 parser->data = xstrdup(line);
Timo Terasadcabf32011-06-20 09:49:56 +0200201 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000202
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000203 /* Tokenize the line */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200204 t = 0;
205 do {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000206 /* Pin token */
207 tokens[t] = line;
208
209 /* Combine remaining arguments? */
210 if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
211 /* Vanilla token, find next delimiter */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200212 line += strcspn(line, (delims[0] && (flags & PARSE_EOL_COMMENTS)) ? delims : delims + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000213 } else {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000214 /* Combining, find comment char if any */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200215 line = strchrnul(line, (flags & PARSE_EOL_COMMENTS) ? delims[0] : '\0');
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000216
217 /* Trim any extra delimiters from the end */
218 if (flags & PARSE_TRIM) {
219 while (strchr(delims + 1, line[-1]) != NULL)
220 line--;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000221 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000222 }
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000223
224 /* Token not terminated? */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200225 if ((flags & PARSE_EOL_COMMENTS) && *line == delims[0])
226 *line = '\0'; /* ends with comment char: this line is done */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200227 else if (*line != '\0')
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200228 *line++ = '\0'; /* token is done, continue parsing line */
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000229
230#if 0 /* unused so far */
231 if (flags & PARSE_ESCAPE) {
Denys Vlasenko53600592010-10-23 21:06:06 +0200232 strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000233 }
234#endif
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000235 /* Skip possible delimiters */
236 if (flags & PARSE_COLLAPSE)
237 line += strspn(line, delims + 1);
Denys Vlasenko63144be2010-06-26 04:00:52 +0200238
239 t++;
240 } while (*line && *line != delims[0] && t < ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000241
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000242 if (t < mintokens) {
Denis Vlasenko5415c852008-07-21 23:05:26 +0000243 bb_error_msg("bad line %u: %d tokens found, %d needed",
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000244 parser->lineno, t, mintokens);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000245 if (flags & PARSE_MIN_DIE)
246 xfunc_die();
247 goto again;
248 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000249
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000250 return t;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000251}