blob: 769ae51038a93204969d6f184aeea11de1567931 [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
Pere Orga5bc8c002011-04-11 03:29:49 +020011/*
12//usage:#define parse_trivial_usage
13//usage: "[-n MAXTOKENS] [-m MINTOKENS] [-d DELIMS] [-f FLAGS] FILE..."
14//usage:#define parse_full_usage ""
15*/
16
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000017#include "libbb.h"
18
Denis Vlasenko2d5bd802008-10-24 10:49:49 +000019#if defined ENABLE_PARSE && ENABLE_PARSE
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000020int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
21int parse_main(int argc UNUSED_PARAM, char **argv)
22{
23 const char *delims = "# \t";
Denis Vlasenko084266e2008-07-26 23:08:31 +000024 unsigned flags = PARSE_NORMAL;
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000025 int mintokens = 0, ntokens = 128;
Denis Vlasenko084266e2008-07-26 23:08:31 +000026
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000027 opt_complementary = "-1:n+:m+:f+";
28 getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
29 //argc -= optind;
30 argv += optind;
31 while (*argv) {
32 parser_t *p = config_open(*argv);
33 if (p) {
34 int n;
35 char **t = xmalloc(sizeof(char *) * ntokens);
Denis Vlasenko4a717e02008-07-20 13:01:56 +000036 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000037 for (int i = 0; i < n; ++i)
38 printf("[%s]", t[i]);
39 puts("");
40 }
41 config_close(p);
42 }
43 argv++;
44 }
45 return EXIT_SUCCESS;
46}
47#endif
48
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000049/*
50
51Typical usage:
52
53----- CUT -----
Denys Vlasenkoe4dcba12010-10-28 18:57:19 +020054 char *t[3]; // tokens placeholder
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000055 parser_t *p = config_open(filename);
56 if (p) {
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000057 // parse line-by-line
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000058 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000059 // use tokens
60 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
61 }
62 ...
63 // free parser
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000064 config_close(p);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000065 }
66----- CUT -----
67
68*/
69
Denis Vlasenko5415c852008-07-21 23:05:26 +000070parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000071{
Denis Vlasenko084266e2008-07-26 23:08:31 +000072 FILE* fp;
73 parser_t *parser;
74
75 fp = fopen_func(filename);
76 if (!fp)
77 return NULL;
78 parser = xzalloc(sizeof(*parser));
79 parser->fp = fp;
80 return parser;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000081}
82
Denis Vlasenko5415c852008-07-21 23:05:26 +000083parser_t* FAST_FUNC config_open(const char *filename)
84{
85 return config_open2(filename, fopen_or_warn_stdin);
86}
87
Denis Vlasenkoc7cc5a92009-04-19 01:27:20 +000088static void config_free_data(parser_t *parser)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +000089{
Denis Vlasenkoc01340f2008-07-16 22:12:18 +000090 free(parser->line);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000091 parser->line = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000092 if (PARSE_KEEP_COPY) { /* compile-time constant */
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000093 free(parser->data);
94 parser->data = NULL;
Denis Vlasenkodcb3fcb2008-07-19 22:57:00 +000095 }
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000096}
Denis Vlasenko2e157dd2008-07-19 09:27:19 +000097
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +000098void FAST_FUNC config_close(parser_t *parser)
99{
Denis Vlasenko084266e2008-07-26 23:08:31 +0000100 if (parser) {
101 config_free_data(parser);
102 fclose(parser->fp);
103 free(parser);
104 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000105}
106
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200107/* This function reads an entire line from a text file, up to a newline
108 * or NUL byte, exclusive. It returns a malloc'ed char*.
109 * *lineno is incremented for each line.
110 * Trailing '\' is recognized as line continuation.
111 * Returns NULL if EOF/error.
112 */
113static char* get_line_with_continuation(FILE *file, int *lineno)
114{
115 int ch;
116 unsigned idx = 0;
117 char *linebuf = NULL;
118
119 while ((ch = getc(file)) != EOF) {
120 /* grow the line buffer as necessary */
121 if (!(idx & 0xff))
122 linebuf = xrealloc(linebuf, idx + 0x101);
123 if (ch == '\n')
124 ch = '\0';
125 linebuf[idx] = (char) ch;
126 if (ch == '\0') {
127 (*lineno)++;
128 if (idx == 0 || linebuf[idx-1] != '\\')
129 break;
130 idx--; /* go back to '/' */
131 continue;
132 }
133 idx++;
134 }
135 if (ch == EOF) {
136 /* handle corner case when the file is not ended with '\n' */
137 (*lineno)++;
138 if (linebuf)
139 linebuf[idx] = '\0';
140 }
141 return linebuf;
142}
143
144
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000145/*
Denis Vlasenko084266e2008-07-26 23:08:31 +00001460. If parser is NULL return 0.
1471. Read a line from config file. If nothing to read then return 0.
148 Handle continuation character. Advance lineno for each physical line.
Denys Vlasenko5370bfb2009-09-06 02:58:59 +0200149 Discard everything past comment character.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001502. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001513. If resulting line is empty goto 1.
Denis Vlasenko084266e2008-07-26 23:08:31 +00001524. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
153 remember the token as empty.
1545. Else (default) if number of seen tokens is equal to max number of tokens
155 (token is the last one) and PARSE_GREEDY is set then the remainder
156 of the line is the last token.
157 Else (token is not last or PARSE_GREEDY is not set) just replace
158 first delimiter with '\0' thus delimiting the token.
1596. Advance line pointer past the end of token. If number of seen tokens
160 is less than required number of tokens then goto 4.
1617. Check the number of seen tokens is not less the min number of tokens.
162 Complain or die otherwise depending on PARSE_MIN_DIE.
Denis Vlasenko2e157dd2008-07-19 09:27:19 +00001638. Return the number of seen tokens.
164
Denis Vlasenko084266e2008-07-26 23:08:31 +0000165mintokens > 0 make config_read() print error message if less than mintokens
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000166(but more than 0) are found. Empty lines are always skipped (not warned about).
167*/
168#undef config_read
169int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000170{
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000171 char *line;
172 int ntokens, mintokens;
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200173 int t;
174
175 if (!parser)
176 return 0;
Denis Vlasenko084266e2008-07-26 23:08:31 +0000177
Denys Vlasenko63144be2010-06-26 04:00:52 +0200178 ntokens = (uint8_t)flags;
179 mintokens = (uint8_t)(flags >> 8);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000180
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000181again:
182 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
Bernhard Reutner-Fischer67921282008-07-17 11:59:13 +0000183 config_free_data(parser);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000184
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000185 /* Read one line (handling continuations with backslash) */
Denys Vlasenkoa1a44832011-06-17 03:37:43 +0200186 line = get_line_with_continuation(parser->fp, &parser->lineno);
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000187 if (line == NULL)
188 return 0;
189 parser->line = line;
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000190
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000191 /* Skip token in the start of line? */
192 if (flags & PARSE_TRIM)
193 line += strspn(line, delims + 1);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000194
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000195 if (line[0] == '\0' || line[0] == delims[0])
196 goto again;
197
198 if (flags & PARSE_KEEP_COPY)
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000199 parser->data = xstrdup(line);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000200
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000201 /* Tokenize the line */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200202 t = 0;
203 do {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000204 /* Pin token */
205 tokens[t] = line;
206
207 /* Combine remaining arguments? */
208 if ((t != (ntokens-1)) || !(flags & PARSE_GREEDY)) {
209 /* Vanilla token, find next delimiter */
210 line += strcspn(line, delims[0] ? delims : delims + 1);
Denis Vlasenko2e157dd2008-07-19 09:27:19 +0000211 } else {
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000212 /* Combining, find comment char if any */
213 line = strchrnul(line, delims[0]);
214
215 /* Trim any extra delimiters from the end */
216 if (flags & PARSE_TRIM) {
217 while (strchr(delims + 1, line[-1]) != NULL)
218 line--;
Denis Vlasenko0f99d492008-07-24 23:38:04 +0000219 }
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000220 }
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000221
222 /* Token not terminated? */
Denys Vlasenko63144be2010-06-26 04:00:52 +0200223 if (*line == delims[0])
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000224 *line = '\0';
Denys Vlasenko63144be2010-06-26 04:00:52 +0200225 else if (*line != '\0')
226 *line++ = '\0';
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000227
228#if 0 /* unused so far */
229 if (flags & PARSE_ESCAPE) {
Denys Vlasenko53600592010-10-23 21:06:06 +0200230 strcpy_and_process_escape_sequences(tokens[t], tokens[t]);
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000231 }
232#endif
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000233 /* Skip possible delimiters */
234 if (flags & PARSE_COLLAPSE)
235 line += strspn(line, delims + 1);
Denys Vlasenko63144be2010-06-26 04:00:52 +0200236
237 t++;
238 } while (*line && *line != delims[0] && t < ntokens);
Denis Vlasenkoe559e0a2008-07-15 21:09:30 +0000239
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000240 if (t < mintokens) {
Denis Vlasenko5415c852008-07-21 23:05:26 +0000241 bb_error_msg("bad line %u: %d tokens found, %d needed",
Denis Vlasenko69f4f9a2008-08-09 17:16:40 +0000242 parser->lineno, t, mintokens);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000243 if (flags & PARSE_MIN_DIE)
244 xfunc_die();
Denys Vlasenko63144be2010-06-26 04:00:52 +0200245 if (flags & PARSE_KEEP_COPY)
246 free(parser->data);
Denis Vlasenko5415c852008-07-21 23:05:26 +0000247 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}