blob: e3840dcbf929e1a91d8d917c0ab03685494d8d36 [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
16////config: Typical usage of parse API:
17////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
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000045 opt_complementary = "-1:n+:m+:f+";
Denys Vlasenko3a649362011-06-18 09:23:09 +020046 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
Tanguy Pruvot6fef6a32012-05-05 15:26:43 +0200125 if ((ssize_t)parser->line_alloc < len + nlen + 1) {
Timo Terasadcabf32011-06-20 09:49:56 +0200126 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{
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000164 char *line;
165 int ntokens, mintokens;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200166 int t;
167
168 if (!parser)
169 return 0;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000170
Denys Vlasenko63144be2010-06-26 04:00:52 +0200171 ntokens = (uint8_t)flags;
172 mintokens = (uint8_t)(flags >> 8);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000173
Timo Terasadcabf32011-06-20 09:49:56 +0200174 again:
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000175 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000176
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000177 /* Read one line (handling continuations with backslash) */
Timo Terasadcabf32011-06-20 09:49:56 +0200178 if (get_line_with_continuation(parser) < 0)
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000179 return 0;
Timo Terasadcabf32011-06-20 09:49:56 +0200180
181 line = parser->line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000182
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000183 /* Skip token in the start of line? */
184 if (flags & PARSE_TRIM)
185 line += strspn(line, delims + 1);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000186
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000187 if (line[0] == '\0' || line[0] == delims[0])
188 goto again;
189
Timo Terasadcabf32011-06-20 09:49:56 +0200190 if (flags & PARSE_KEEP_COPY) {
191 free(parser->data);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000192 parser->data = xstrdup(line);
Timo Terasadcabf32011-06-20 09:49:56 +0200193 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000194
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000195 /* Tokenize the line */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200196 t = 0;
197 do {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000198 /* Pin token */
199 tokens[t] = line;
200
201 /* Combine remaining arguments? */
202 if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
203 /* Vanilla token, find next delimiter */
204 line += strcspn(line, delims[0] ? delims : delims + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000205 } else {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000206 /* Combining, find comment char if any */
Tanguy Pruvot8a6c2c22012-04-28 00:24:09 +0200207 line = strchrnul(line, PARSE_EOL_COMMENTS ? delims[0] : '\0');
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000208
209 /* Trim any extra delimiters from the end */
210 if (flags & PARSE_TRIM) {
211 while (strchr(delims + 1, line[-1]) != NULL)
212 line--;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000213 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000214 }
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000215
216 /* Token not terminated? */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200217 if (*line == delims[0])
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000218 *line = '\0';
Denys Vlasenko63144be2010-06-26 04:00:52 +0200219 else if (*line != '\0')
220 *line++ = '\0';
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000221
222#if 0 /* unused so far */
223 if (flags & PARSE_ESCAPE) {
Denys Vlasenko53600592010-10-23 21:06:06 +0200224 strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000225 }
226#endif
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000227 /* Skip possible delimiters */
228 if (flags & PARSE_COLLAPSE)
229 line += strspn(line, delims + 1);
Denys Vlasenko63144be2010-06-26 04:00:52 +0200230
231 t++;
232 } while (*line && *line != delims[0] && t < ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000233
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000234 if (t < mintokens) {
Denis Vlasenko5415c852008-07-21 23:05:26 +0000235 bb_error_msg("bad line %u: %d tokens found, %d needed",
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000236 parser->lineno, t, mintokens);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000237 if (flags & PARSE_MIN_DIE)
238 xfunc_die();
239 goto again;
240 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000241
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000242 return t;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000243}