1 /*
2 * config file parser helper
3 *
4 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
5 *
6 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7 * Also for use in uClibc (http://uclibc.org/) licensed under LGPLv2.1 or later.
8 */
9
10 #if !defined _LIBC
11 #include "libbb.h"
12
13 #if defined ENABLE_PARSE && ENABLE_PARSE
14 int parse_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
parse_main(int argc UNUSED_PARAM,char ** argv)15 int parse_main(int argc UNUSED_PARAM, char **argv)
16 {
17 const char *delims = "# \t";
18 unsigned flags = PARSE_NORMAL;
19 int mintokens = 0, ntokens = 128;
20
21 opt_complementary = "-1:n+:m+:f+";
22 getopt32(argv, "n:m:d:f:", &ntokens, &mintokens, &delims, &flags);
23 //argc -= optind;
24 argv += optind;
25 while (*argv) {
26 parser_t *p = config_open(*argv);
27 if (p) {
28 int n;
29 char **t = xmalloc(sizeof(char *) * ntokens);
30 while ((n = config_read(p, t, ntokens, mintokens, delims, flags)) != 0) {
31 for (int i = 0; i < n; ++i)
32 printf("[%s]", t[i]);
33 puts("");
34 }
35 config_close(p);
36 }
37 argv++;
38 }
39 return EXIT_SUCCESS;
40 }
41 #endif
42 #else
43 # include <unistd.h>
44 # include <string.h>
45 # include <malloc.h>
46 # include <bits/uClibc_page.h>
47 # include "internal/parse_config.h"
48 # ifndef FAST_FUNC
49 # define FAST_FUNC
50 # endif
51 # define fopen_or_warn_stdin fopen
52 # define bb_error_msg(...)
53 # define xstrdup strdup
54 # define xfunc_die() return 0
55 /* Read up to EOF or EOL, treat line-continuations as extending the line.
56 Return number of bytes read into .line, -1 otherwise */
bb_get_chunk_with_continuation(parser_t * parsr)57 static off_t bb_get_chunk_with_continuation(parser_t* parsr)
58 {
59 off_t pos = 0;
60 char *chp;
61
62 while (1) {
63 if (fgets(parsr->line + pos, parsr->line_len - pos, parsr->fp) == NULL) {
64 memset(parsr->line, 0, parsr->line_len);
65 pos = -1;
66 break;
67 }
68 pos += strlen(parsr->line + pos);
69 chp = strchr(parsr->line, '\n');
70 if (chp) {
71 --pos;
72 if (--*chp == '\\')
73 --pos;
74 else
75 break;
76 } else if (parsr->allocated) {
77 parsr->line_len += PAGE_SIZE;
78 parsr->data = realloc(parsr->data,
79 parsr->data_len + parsr->line_len);
80 parsr->line = parsr->data + parsr->data_len;
81 } else {
82 /* discard rest of line if not enough space in buffer */
83 int c;
84 do {
85 c = fgetc(parsr->fp);
86 } while (c != EOF && c != '\n');
87 break;
88 }
89 }
90 return pos;
91 }
92 #endif
93
94 /*
95
96 Typical usage:
97
98 ----- CUT -----
99 char *t[3]; // tokens placeholder
100 parser_t *p = config_open(filename);
101 if (p) {
102 // parse line-by-line
103 while (config_read(p, t, 3, 0, delimiters, flags)) { // 1..3 tokens
104 // use tokens
105 bb_error_msg("TOKENS: [%s][%s][%s]", t[0], t[1], t[2]);
106 }
107 ...
108 // free parser
109 config_close(p);
110 }
111 ----- CUT -----
112
113 */
114
config_open2(const char * filename,FILE * FAST_FUNC (* fopen_func)(const char * path,const char * mode))115 static __always_inline parser_t * FAST_FUNC config_open2(const char *filename,
116 FILE* FAST_FUNC (*fopen_func)(const char *path, const char *mode))
117 {
118 parser_t *parser;
119 FILE* fp;
120
121 fp = fopen_func(filename, "r");
122 if (!fp)
123 return NULL;
124 parser = calloc(1, sizeof(*parser));
125 if (parser) {
126 parser->fp = fp;
127 }
128 return parser;
129 }
130
config_open(const char * filename)131 parser_t * FAST_FUNC config_open(const char *filename)
132 {
133 return config_open2(filename, fopen_or_warn_stdin);
134 }
135
136 #ifdef UNUSED
config_free_data(parser_t * parser)137 static void config_free_data(parser_t *parser)
138 {
139 free(parser->data);
140 parser->data = parser->line = NULL;
141 }
142 #endif
143
config_close(parser_t * parser)144 void FAST_FUNC config_close(parser_t *parser)
145 {
146 if (parser) {
147 fclose(parser->fp);
148 if (parser->allocated)
149 free(parser->data);
150 free(parser);
151 }
152 }
153
154 /*
155 0. If parser is NULL return 0.
156 1. Read a line from config file. If nothing to read then return 0.
157 Handle continuation character. Advance lineno for each physical line.
158 Discard everything past comment character.
159 2. if PARSE_TRIM is set (default), remove leading and trailing delimiters.
160 3. If resulting line is empty goto 1.
161 4. Look for first delimiter. If !PARSE_COLLAPSE or !PARSE_TRIM is set then
162 remember the token as empty.
163 5. Else (default) if number of seen tokens is equal to max number of tokens
164 (token is the last one) and PARSE_GREEDY is set then the remainder
165 of the line is the last token.
166 Else (token is not last or PARSE_GREEDY is not set) just replace
167 first delimiter with '\0' thus delimiting the token.
168 6. Advance line pointer past the end of token. If number of seen tokens
169 is less than required number of tokens then goto 4.
170 7. Check the number of seen tokens is not less the min number of tokens.
171 Complain or die otherwise depending on PARSE_MIN_DIE.
172 8. Return the number of seen tokens.
173
174 mintokens > 0 make config_read() print error message if less than mintokens
175 (but more than 0) are found. Empty lines are always skipped (not warned about).
176 */
177 #undef config_read
config_read(parser_t * parser,char *** tokens,unsigned flags,const char * delims)178 int FAST_FUNC config_read(parser_t *parser, char ***tokens,
179 unsigned flags, const char *delims)
180 {
181 char *line;
182 int ntokens, mintokens;
183 off_t len;
184 int t;
185
186 if (parser == NULL)
187 return 0;
188 ntokens = flags & 0xFF;
189 mintokens = (flags & 0xFF00) >> 8;
190 again:
191 if (parser->data == NULL) {
192 if (parser->line_len == 0)
193 parser->line_len = 81;
194 if (parser->data_len == 0)
195 parser->data_len += 1 + ntokens * sizeof(char *);
196 parser->data = malloc(parser->data_len + parser->line_len);
197 if (parser->data == NULL)
198 return 0;
199 parser->allocated |= 1;
200 } /* else { assert(parser->data_len > 0); } */
201 parser->line = parser->data + parser->data_len;
202 /*config_free_data(parser);*/
203
204 /* Read one line (handling continuations with backslash) */
205 len = bb_get_chunk_with_continuation(parser);
206 if (len == -1)
207 return 0;
208 line = parser->line;
209
210 /* Skip multiple token-delimiters in the start of line? */
211 if (flags & PARSE_TRIM)
212 line += strspn(line, delims + 1);
213
214 if (line[0] == '\0' || line[0] == delims[0])
215 goto again;
216
217 *tokens = (char **) parser->data;
218 memset(*tokens, 0, sizeof(*tokens[0]) * ntokens);
219
220 /* Tokenize the line */
221 for (t = 0; *line && *line != delims[0] && t < ntokens; t++) {
222 /* Pin token */
223 *(*tokens + t) = line;
224
225 /* Combine remaining arguments? */
226 if ((t != ntokens-1) || !(flags & PARSE_GREEDY)) {
227 /* Vanilla token, find next delimiter */
228 line += strcspn(line, delims[0] ? delims : delims + 1);
229 } else {
230 /* Combining, find comment char if any */
231 line = strchrnul(line, delims[0]);
232
233 /* Trim any extra delimiters from the end */
234 if (flags & PARSE_TRIM) {
235 while (strchr(delims + 1, line[-1]) != NULL)
236 line--;
237 }
238 }
239
240 /* Token not terminated? */
241 if (line[0] == delims[0])
242 *line = '\0';
243 else if (line[0] != '\0')
244 *(line++) = '\0';
245
246 #if 0 /* unused so far */
247 if (flags & PARSE_ESCAPE) {
248 const char *from;
249 char *to;
250
251 from = to = tokens[t];
252 while (*from) {
253 if (*from == '\\') {
254 from++;
255 *to++ = bb_process_escape_sequence(&from);
256 } else {
257 *to++ = *from++;
258 }
259 }
260 *to = '\0';
261 }
262 #endif
263
264 /* Skip possible delimiters */
265 if (flags & PARSE_COLLAPSE)
266 line += strspn(line, delims + 1);
267 }
268
269 if (t < mintokens) {
270 bb_error_msg(/*"bad line %u: "*/"%d tokens found, %d needed",
271 /*parser->lineno, */t, mintokens);
272 if (flags & PARSE_MIN_DIE)
273 xfunc_die();
274 goto again;
275 }
276 return t;
277 }
278