blob: 8701b010c6498346b52bf7958b0a5e3f135c1764 [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 Vlasenko22542ec2017-08-08 21:55:02 +020045 noout = 1 & getopt32(argv, "^" "xn:+m:+d:f:+" "\0" "-1",
46 &ntokens, &mintokens, &delims, &flags
47 );
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000048 //argc -= optind;
49 argv += optind;
Denys Vlasenko3a649362011-06-18 09:23:09 +020050
51 t = xmalloc(sizeof(t[0]) * ntokens);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000052 while (*argv) {
Denys Vlasenko3a649362011-06-18 09:23:09 +020053 int n;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000054 parser_t *p = config_open(*argv);
Denys Vlasenko3a649362011-06-18 09:23:09 +020055 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
56 if (!noout) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000057 for (int i = 0; i < n; ++i)
58 printf("[%s]", t[i]);
59 puts("");
60 }
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000061 }
Denys Vlasenko3a649362011-06-18 09:23:09 +020062 config_close(p);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000063 argv++;
64 }
65 return EXIT_SUCCESS;
66}
67#endif
68
Denis Vlasenko5415c852008-07-21 23:05:26 +000069parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000070{
Denis Vlasenko084266e2008-07-26 23:08:31 +000071 FILE* fp;
72 parser_t *parser;
73
74 fp = fopen_func(filename);
75 if (!fp)
76 return NULL;
77 parser = xzalloc(sizeof(*parser));
78 parser->fp = fp;
79 return parser;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000080}
81
Denis Vlasenko5415c852008-07-21 23:05:26 +000082parser_t* FAST_FUNC config_open(const char *filename)
83{
84 return config_open2(filename, fopen_or_warn_stdin);
85}
86
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000087void FAST_FUNC config_close(parser_t *parser)
88{
Denis Vlasenko084266e2008-07-26 23:08:31 +000089 if (parser) {
Timo Terasadcabf32011-06-20 09:49:56 +020090 if (PARSE_KEEP_COPY) /* compile-time constant */
91 free(parser->data);
Denis Vlasenko084266e2008-07-26 23:08:31 +000092 fclose(parser->fp);
Timo Terasadcabf32011-06-20 09:49:56 +020093 free(parser->line);
94 free(parser->nline);
Denis Vlasenko084266e2008-07-26 23:08:31 +000095 free(parser);
96 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000097}
98
Timo Terasadcabf32011-06-20 09:49:56 +020099/* This function reads an entire line from a text file,
100 * up to a newline, exclusive.
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200101 * Trailing '\' is recognized as line continuation.
Timo Terasadcabf32011-06-20 09:49:56 +0200102 * Returns -1 if EOF/error.
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200103 */
Timo Terasadcabf32011-06-20 09:49:56 +0200104static int get_line_with_continuation(parser_t *parser)
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200105{
Timo Terasadcabf32011-06-20 09:49:56 +0200106 ssize_t len, nlen;
107 char *line;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200108
Timo Terasadcabf32011-06-20 09:49:56 +0200109 len = getline(&parser->line, &parser->line_alloc, parser->fp);
110 if (len <= 0)
111 return len;
112
113 line = parser->line;
114 for (;;) {
115 parser->lineno++;
116 if (line[len - 1] == '\n')
117 len--;
118 if (len == 0 || line[len - 1] != '\\')
119 break;
120 len--;
121
122 nlen = getline(&parser->nline, &parser->nline_alloc, parser->fp);
123 if (nlen <= 0)
124 break;
125
126 if (parser->line_alloc < len + nlen + 1) {
127 parser->line_alloc = len + nlen + 1;
128 line = parser->line = xrealloc(line, parser->line_alloc);
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200129 }
Timo Terasadcabf32011-06-20 09:49:56 +0200130 memcpy(&line[len], parser->nline, nlen);
131 len += nlen;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200132 }
Timo Terasadcabf32011-06-20 09:49:56 +0200133
134 line[len] = '\0';
135 return len;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200136}
137
138
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000139/*
Denis Vlasenko084266e2008-07-26 23:08:31 +00001400. If parser is NULL return 0.
1411. Read a line from config file. If nothing to read then return 0.
142 Handle continuation character. Advance lineno for each physical line.
Denys Vlasenko5370bfb2009-09-06 02:58:59 +0200143 Discard everything past comment character.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001442. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001453. If resulting line is empty goto 1.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001464. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
147 remember the token as empty.
1485. Else (default) if number of seen tokens is equal to max number of tokens
149 (token is the last one) and PARSE_GREEDY is set then the remainder
150 of the line is the last token.
151 Else (token is not last or PARSE_GREEDY is not set) just replace
152 first delimiter with '\0' thus delimiting the token.
1536. Advance line pointer past the end of token. If number of seen tokens
154 is less than required number of tokens then goto 4.
1557. Check the number of seen tokens is not less the min number of tokens.
156 Complain or die otherwise depending on PARSE_MIN_DIE.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001578. Return the number of seen tokens.
158
Denis Vlasenko084266e2008-07-26 23:08:31 +0000159mintokens > 0 make config_read() print error message if less than mintokens
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000160(but more than 0) are found. Empty lines are always skipped (not warned about).
161*/
162#undef config_read
163int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000164{
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200165 char *line, *p;
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000166 int ntokens, mintokens;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200167 int t;
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200168 char alt_comment_ch;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200169
170 if (!parser)
171 return 0;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000172
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200173 alt_comment_ch = '\0';
174 if (flags & PARSE_ALT_COMMENTS)
175 alt_comment_ch = *delims++;
176
Denys Vlasenko63144be2010-06-26 04:00:52 +0200177 ntokens = (uint8_t)flags;
178 mintokens = (uint8_t)(flags >> 8);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000179
Timo Terasadcabf32011-06-20 09:49:56 +0200180 again:
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000181 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000182
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000183 /* Read one line (handling continuations with backslash) */
Timo Terasadcabf32011-06-20 09:49:56 +0200184 if (get_line_with_continuation(parser) < 0)
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000185 return 0;
Timo Terasadcabf32011-06-20 09:49:56 +0200186
187 line = parser->line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000188
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000189 /* Skip token in the start of line? */
190 if (flags & PARSE_TRIM)
191 line += strspn(line, delims + 1);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000192
Denys Vlasenko50db1f22017-08-05 18:20:34 +0200193 p = line;
194 if (flags & PARSE_WS_COMMENTS)
195 p = skip_whitespace(p);
196 if (p[0] == '\0' || p[0] == delims[0] || p[0] == alt_comment_ch)
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000197 goto again;
198
Timo Terasadcabf32011-06-20 09:49:56 +0200199 if (flags & PARSE_KEEP_COPY) {
200 free(parser->data);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000201 parser->data = xstrdup(line);
Timo Terasadcabf32011-06-20 09:49:56 +0200202 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000203
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000204 /* Tokenize the line */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200205 t = 0;
206 do {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000207 /* Pin token */
208 tokens[t] = line;
209
210 /* Combine remaining arguments? */
211 if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
212 /* Vanilla token, find next delimiter */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200213 line += strcspn(line, (delims[0] && (flags & PARSE_EOL_COMMENTS)) ? delims : delims + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000214 } else {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000215 /* Combining, find comment char if any */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200216 line = strchrnul(line, (flags & PARSE_EOL_COMMENTS) ? delims[0] : '\0');
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000217
218 /* Trim any extra delimiters from the end */
219 if (flags & PARSE_TRIM) {
220 while (strchr(delims + 1, line[-1]) != NULL)
221 line--;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000222 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000223 }
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000224
225 /* Token not terminated? */
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200226 if ((flags & PARSE_EOL_COMMENTS) && *line == delims[0])
227 *line = '\0'; /* ends with comment char: this line is done */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200228 else if (*line != '\0')
Denys Vlasenko9cf89cd2017-08-05 13:45:22 +0200229 *line++ = '\0'; /* token is done, continue parsing line */
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000230
231#if 0 /* unused so far */
232 if (flags & PARSE_ESCAPE) {
Denys Vlasenko53600592010-10-23 21:06:06 +0200233 strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000234 }
235#endif
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000236 /* Skip possible delimiters */
237 if (flags & PARSE_COLLAPSE)
238 line += strspn(line, delims + 1);
Denys Vlasenko63144be2010-06-26 04:00:52 +0200239
240 t++;
241 } while (*line && *line != delims[0] && t < ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000242
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000243 if (t < mintokens) {
Denis Vlasenko5415c852008-07-21 23:05:26 +0000244 bb_error_msg("bad line %u: %d tokens found, %d needed",
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000245 parser->lineno, t, mintokens);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000246 if (flags & PARSE_MIN_DIE)
247 xfunc_die();
248 goto again;
249 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000250
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000251 return t;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000252}