83dc997f6fffe7b5793677e4323ac67ddf307ea7
[platform/upstream/busybox.git] / libbb / parse_config.c
1 /* 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
12 #if ENABLE_PARSE
13 int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
14 int parse_main(int argc UNUSED_PARAM, char **argv)
15 {
16         const char *delims = "# \t";
17         unsigned flags = 0;
18         int mintokens = 0, ntokens = 128;
19         opt_complementary = "-1:n+:m+:f+";
20         getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
21         //argc -= optind;
22         argv += optind;
23         while (*argv) {
24                 parser_t *p = config_open(*argv);
25                 if (p) {
26                         int n;
27                         char **t = xmalloc(sizeof(char *) * ntokens);
28                         while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
29                                 for (int i = 0; i < n; ++i)
30                                         printf("[%s]", t[i]);
31                                 puts("");
32                         }
33                         config_close(p);
34                 }
35                 argv++;
36         }
37         return EXIT_SUCCESS;
38 }
39 #endif
40
41 /*
42
43 Typical usage:
44
45 ----- CUT -----
46         char *t[3];     // tokens placeholder
47         parser_t *p = config_open(filename);
48         if (p) {
49                 // parse line-by-line
50                 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
51                         // use tokens
52                         bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
53                 }
54                 ...
55                 // free parser
56                 config_close(p);
57         }
58 ----- CUT -----
59
60 */
61
62 parser_t* FAST_FUNC config_open2(const char *filename, FILE* FAST_FUNC (*fopen_func)(const char *path))
63 {
64         parser_t *parser = xzalloc(sizeof(parser_t));
65         /* empty file configures nothing */
66         parser->fp = fopen_func(filename);
67         if (parser->fp)
68                 return parser;
69         free(parser);
70         return NULL;
71 }
72
73 parser_t* FAST_FUNC config_open(const char *filename)
74 {
75         return config_open2(filename, fopen_or_warn_stdin);
76 }
77
78 static void config_free_data(parser_t *const parser)
79 {
80         free(parser->line);
81         parser->line = NULL;
82         if (PARSE_KEEP_COPY) { /* compile-time constant */
83                 free(parser->data);
84                 parser->data = NULL;
85         }
86 }
87
88 void FAST_FUNC config_close(parser_t *parser)
89 {
90         config_free_data(parser);
91         fclose(parser->fp);
92 }
93
94 /*
95 1. Read a line from config file. If nothing to read then bail out returning 0.
96    Handle continuation character. Advance lineno for each physical line. Cut comments.
97 2. if PARSE_DONT_TRIM is not set (default) skip leading and cut trailing delimiters, if any.
98 3. If resulting line is empty goto 1.
99 4. Look for first delimiter. If PARSE_DONT_REDUCE or PARSE_DONT_TRIM is set then pin empty token.
100 5. Else (default) if number of seen tokens is equal to max number of tokens (token is the last one)
101    and PARSE_LAST_IS_GREEDY is set then pin the remainder of the line as the last token.
102    Else (token is not last or PARSE_LAST_IS_GREEDY is not set) just replace first delimiter with '\0'
103    thus delimiting token and pin it.
104 6. Advance line pointer past the end of token. If number of seen tokens is less than required number
105    of tokens then goto 4.
106 7. Control the number of seen tokens is not less the min number of tokens. Die if condition is not met.
107 8. Return the number of seen tokens.
108
109 mintokens > 0 make config_read() exit with error message if less than mintokens
110 (but more than 0) are found. Empty lines are always skipped (not warned about).
111 */
112 #undef config_read
113 int FAST_FUNC config_read(parser_t *parser, char **tokens, unsigned flags, const char *delims)
114 {
115         char *line, *q;
116         char comment = *delims++;
117         int ii;
118         int ntokens = flags & 0xFF;
119         int mintokens = (flags & 0xFF00) >> 8;
120
121  again:
122         // N.B. this could only be used in read-in-one-go version, or when tokens use xstrdup(). TODO
123         //if (!parser->lineno || !(flags & PARSE_DONT_NULL))
124                 memset(tokens, 0, sizeof(tokens[0]) * ntokens);
125         config_free_data(parser);
126
127         while (1) {
128 //TODO: speed up xmalloc_fgetline by internally using fgets, not fgetc
129                 line = xmalloc_fgetline(parser->fp);
130                 if (!line)
131                         return 0;
132
133                 parser->lineno++;
134                 // handle continuations. Tito's code stolen :)
135                 while (1) {
136                         ii = strlen(line);
137                         if (!ii)
138                                 goto next_line;
139                         if (line[ii - 1] != '\\')
140                                 break;
141                         // multi-line object
142                         line[--ii] = '\0';
143 //TODO: add xmalloc_fgetline-like iface but with appending to existing str
144                         q = xmalloc_fgetline(parser->fp);
145                         if (q) {
146                                 parser->lineno++;
147                                 line = xasprintf("%s%s", line, q);
148                                 free(q);
149                         }
150                 }
151                 // comments mean EOLs
152                 if (comment) {
153                         q = strchrnul(line, comment);
154                         *q = '\0';
155                         ii = q - line;
156                 }
157                 // skip leading and trailing delimiters
158                 if (!(flags & PARSE_DONT_TRIM)) {
159                         // skip leading
160                         int n = strspn(line, delims);
161                         if (n) {
162                                 ii -= n;
163                                 overlapping_strcpy(line, line + n);
164                         }
165                         // cut trailing
166                         if (ii) {
167                                 while (strchr(delims, line[--ii]))
168                                         continue;
169                                 line[++ii] = '\0';
170                         }
171                 }
172                 // if something still remains -> return it
173                 if (ii)
174                         break;
175
176  next_line:
177                 // skip empty line
178                 free(line);
179         }
180
181         // non-empty line found, parse and return the number of tokens
182
183         // store line
184         parser->line = line = xrealloc(line, ii + 1);
185         if (flags & PARSE_KEEP_COPY) {
186                 parser->data = xstrdup(line);
187         }
188
189         // split line to tokens
190         ntokens--; // now it's max allowed token no
191         // N.B. non-empty remainder is also a token,
192         // so if ntokens <= 1, we just return the whole line
193         // N.B. if PARSE_LAST_IS_GREEDY is set the remainder of the line is stuck to the last token
194         for (ii = 0; *line && ii <= ntokens; ) {
195                 //bb_info_msg("L[%s]", line);
196                 // get next token
197                 // at the last token and need greedy token ->
198                 if ((flags & PARSE_LAST_IS_GREEDY) && (ii == ntokens)) {
199                         // skip possible delimiters
200                         if (!(flags & PARSE_DONT_REDUCE))
201                                 line += strspn(line, delims);
202                         // don't cut the line
203                         q = line + strlen(line);
204                 } else {
205                         // vanilla token. cut the line at the first delim
206                         q = line + strcspn(line, delims);
207                         if (*q) // watch out: do not step past the line end!
208                                 *q++ = '\0';
209                 }
210                 // pin token
211                 if ((flags & (PARSE_DONT_REDUCE|PARSE_DONT_TRIM)) || *line) {
212                         //bb_info_msg("N[%d] T[%s]", ii, line);
213                         tokens[ii++] = line;
214                         // process escapes in token
215                         if (flags & PARSE_ESCAPE) {
216                                 char *s = line;
217                                 while (*s) {
218                                         if (*s == '\\') {
219                                                 s++;
220                                                 *line++ = bb_process_escape_sequence((const char **)&s);
221                                         } else {
222                                                 *line++ = *s++;
223                                         }
224                                 }
225                                 *line = '\0';
226                         }
227                 }
228                 line = q;
229                 //bb_info_msg("A[%s]", line);
230         }
231
232         if (ii < mintokens) {
233                 bb_error_msg("bad line %u: %d tokens found, %d needed",
234                                 parser->lineno, ii, mintokens);
235                 if (flags & PARSE_MIN_DIE)
236                         xfunc_die();
237                 goto again;
238         }
239
240         return ii;
241 }