]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - lib/libfigpar/figpar.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / lib / libfigpar / figpar.c
1 /*-
2  * Copyright (c) 2002-2014 Devin Teske <dteske@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29
30 #include <sys/param.h>
31
32 #include <ctype.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <fnmatch.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #include "figpar.h"
41 #include "string_m.h"
42
43 struct fp_config fp_dummy_config = {0, NULL, {0}, NULL};
44
45 /*
46  * Search for config option (struct fp_config) in the array of config options,
47  * returning the struct whose directive matches the given parameter. If no
48  * match is found, a pointer to the static dummy array (above) is returned.
49  *
50  * This is to eliminate dependency on the index position of an item in the
51  * array, since the index position is more apt to be changed as code grows.
52  */
53 struct fp_config *
54 get_config_option(struct fp_config options[], const char *directive)
55 {
56         uint32_t n;
57
58         /* Check arguments */
59         if (options == NULL || directive == NULL)
60                 return (&fp_dummy_config);
61
62         /* Loop through the array, return the index of the first match */
63         for (n = 0; options[n].directive != NULL; n++)
64                 if (strcmp(options[n].directive, directive) == 0)
65                         return (&(options[n]));
66
67         /* Re-initialize the dummy variable in case it was written to */
68         fp_dummy_config.directive       = NULL;
69         fp_dummy_config.type            = 0;
70         fp_dummy_config.action          = NULL;
71         fp_dummy_config.value.u_num     = 0;
72
73         return (&fp_dummy_config);
74 }
75
76 /*
77  * Parse the configuration file at `path' and execute the `action' call-back
78  * functions for any directives defined by the array of config options (first
79  * argument).
80  *
81  * For unknown directives that are encountered, you can optionally pass a
82  * call-back function for the third argument to be called for unknowns.
83  *
84  * Returns zero on success; otherwise returns -1 and errno should be consulted.
85 */
86 int
87 parse_config(struct fp_config options[], const char *path,
88     int (*unknown)(struct fp_config *option, uint32_t line, char *directive,
89     char *value), uint16_t processing_options)
90 {
91         uint8_t bequals;
92         uint8_t bsemicolon;
93         uint8_t case_sensitive;
94         uint8_t comment = 0;
95         uint8_t end;
96         uint8_t found;
97         uint8_t have_equals = 0;
98         uint8_t quote;
99         uint8_t require_equals;
100         uint8_t strict_equals;
101         char p[2];
102         char *directive;
103         char *t;
104         char *value;
105         int error;
106         int fd;
107         ssize_t r = 1;
108         uint32_t dsize;
109         uint32_t line = 1;
110         uint32_t n;
111         uint32_t vsize;
112         uint32_t x;
113         off_t charpos;
114         off_t curpos;
115         char rpath[PATH_MAX];
116
117         /* Sanity check: if no options and no unknown function, return */
118         if (options == NULL && unknown == NULL)
119                 return (-1);
120
121         /* Processing options */
122         bequals = (processing_options & FP_BREAK_ON_EQUALS) == 0 ? 0 : 1;
123         bsemicolon = (processing_options & FP_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
124         case_sensitive = (processing_options & FP_CASE_SENSITIVE) == 0 ? 0 : 1;
125         require_equals = (processing_options & FP_REQUIRE_EQUALS) == 0 ? 0 : 1;
126         strict_equals = (processing_options & FP_STRICT_EQUALS) == 0 ? 0 : 1;
127
128         /* Initialize strings */
129         directive = value = 0;
130         vsize = dsize = 0;
131
132         /* Resolve the file path */
133         if (realpath(path, rpath) == 0)
134                 return (-1);
135
136         /* Open the file */
137         if ((fd = open(rpath, O_RDONLY)) < 0)
138                 return (-1);
139
140         /* Read the file until EOF */
141         while (r != 0) {
142                 r = read(fd, p, 1);
143
144                 /* skip to the beginning of a directive */
145                 while (r != 0 && (isspace(*p) || *p == '#' || comment ||
146                     (bsemicolon && *p == ';'))) {
147                         if (*p == '#')
148                                 comment = 1;
149                         else if (*p == '\n') {
150                                 comment = 0;
151                                 line++;
152                         }
153                         r = read(fd, p, 1);
154                 }
155                 /* Test for EOF; if EOF then no directive was found */
156                 if (r == 0) {
157                         close(fd);
158                         return (0);
159                 }
160
161                 /* Get the current offset */
162                 curpos = lseek(fd, 0, SEEK_CUR) - 1;
163                 if (curpos == -1) {
164                         close(fd);
165                         return (-1);
166                 }
167
168                 /* Find the length of the directive */
169                 for (n = 0; r != 0; n++) {
170                         if (isspace(*p))
171                                 break;
172                         if (bequals && *p == '=') {
173                                 have_equals = 1;
174                                 break;
175                         }
176                         if (bsemicolon && *p == ';')
177                                 break;
178                         r = read(fd, p, 1);
179                 }
180
181                 /* Test for EOF, if EOF then no directive was found */
182                 if (n == 0 && r == 0) {
183                         close(fd);
184                         return (0);
185                 }
186
187                 /* Go back to the beginning of the directive */
188                 error = (int)lseek(fd, curpos, SEEK_SET);
189                 if (error == (curpos - 1)) {
190                         close(fd);
191                         return (-1);
192                 }
193
194                 /* Allocate and read the directive into memory */
195                 if (n > dsize) {
196                         if ((directive = realloc(directive, n + 1)) == NULL) {
197                                 close(fd);
198                                 return (-1);
199                         }
200                         dsize = n;
201                 }
202                 r = read(fd, directive, n);
203
204                 /* Advance beyond the equals sign if appropriate/desired */
205                 if (bequals && *p == '=') {
206                         if (lseek(fd, 1, SEEK_CUR) != -1)
207                                 r = read(fd, p, 1);
208                         if (strict_equals && isspace(*p))
209                                 *p = '\n';
210                 }
211
212                 /* Terminate the string */
213                 directive[n] = '\0';
214
215                 /* Convert directive to lower case before comparison */
216                 if (!case_sensitive)
217                         strtolower(directive);
218
219                 /* Move to what may be the start of the value */
220                 if (!(bsemicolon && *p == ';') &&
221                     !(strict_equals && *p == '=')) {
222                         while (r != 0 && isspace(*p) && *p != '\n')
223                                 r = read(fd, p, 1);
224                 }
225
226                 /* An equals sign may have stopped us, should we eat it? */
227                 if (r != 0 && bequals && *p == '=' && !strict_equals) {
228                         have_equals = 1;
229                         r = read(fd, p, 1);
230                         while (r != 0 && isspace(*p) && *p != '\n')
231                                 r = read(fd, p, 1);
232                 }
233
234                 /* If no value, allocate a dummy value and jump to action */
235                 if (r == 0 || *p == '\n' || *p == '#' ||
236                     (bsemicolon && *p == ';')) {
237                         /* Initialize the value if not already done */
238                         if (value == NULL && (value = malloc(1)) == NULL) {
239                                 close(fd);
240                                 return (-1);
241                         }
242                         value[0] = '\0';
243                         goto call_function;
244                 }
245
246                 /* Get the current offset */
247                 curpos = lseek(fd, 0, SEEK_CUR) - 1;
248                 if (curpos == -1) {
249                         close(fd);
250                         return (-1);
251                 }
252
253                 /* Find the end of the value */
254                 quote = 0;
255                 end = 0;
256                 while (r != 0 && end == 0) {
257                         /* Advance to the next character if we know we can */
258                         if (*p != '\"' && *p != '#' && *p != '\n' &&
259                             (!bsemicolon || *p != ';')) {
260                                 r = read(fd, p, 1);
261                                 continue;
262                         }
263
264                         /*
265                          * If we get this far, we've hit an end-key
266                          */
267
268                         /* Get the current offset */
269                         charpos = lseek(fd, 0, SEEK_CUR) - 1;
270                         if (charpos == -1) {
271                                 close(fd);
272                                 return (-1);
273                         }
274
275                         /*
276                          * Go back so we can read the character before the key
277                          * to check if the character is escaped (which means we
278                          * should continue).
279                          */
280                         error = (int)lseek(fd, -2, SEEK_CUR);
281                         if (error == -3) {
282                                 close(fd);
283                                 return (-1);
284                         }
285                         r = read(fd, p, 1);
286
287                         /*
288                          * Count how many backslashes there are (an odd number
289                          * means the key is escaped, even means otherwise).
290                          */
291                         for (n = 1; *p == '\\'; n++) {
292                                 /* Move back another offset to read */
293                                 error = (int)lseek(fd, -2, SEEK_CUR);
294                                 if (error == -3) {
295                                         close(fd);
296                                         return (-1);
297                                 }
298                                 r = read(fd, p, 1);
299                         }
300
301                         /* Move offset back to the key and read it */
302                         error = (int)lseek(fd, charpos, SEEK_SET);
303                         if (error == (charpos - 1)) {
304                                 close(fd);
305                                 return (-1);
306                         }
307                         r = read(fd, p, 1);
308
309                         /*
310                          * If an even number of backslashes was counted meaning
311                          * key is not escaped, we should evaluate what to do.
312                          */
313                         if ((n & 1) == 1) {
314                                 switch (*p) {
315                                 case '\"':
316                                         /*
317                                          * Flag current sequence of characters
318                                          * to follow as being quoted (hashes
319                                          * are not considered comments).
320                                          */
321                                         quote = !quote;
322                                         break;
323                                 case '#':
324                                         /*
325                                          * If we aren't in a quoted series, we
326                                          * just hit an inline comment and have
327                                          * found the end of the value.
328                                          */
329                                         if (!quote)
330                                                 end = 1;
331                                         break;
332                                 case '\n':
333                                         /*
334                                          * Newline characters must always be
335                                          * escaped, whether inside a quoted
336                                          * series or not, otherwise they
337                                          * terminate the value.
338                                          */
339                                         end = 1;
340                                 case ';':
341                                         if (!quote && bsemicolon)
342                                                 end = 1;
343                                         break;
344                                 }
345                         } else if (*p == '\n')
346                                 /* Escaped newline character. increment */
347                                 line++;
348
349                         /* Advance to the next character */
350                         r = read(fd, p, 1);
351                 }
352
353                 /* Get the current offset */
354                 charpos = lseek(fd, 0, SEEK_CUR) - 1;
355                 if (charpos == -1) {
356                         close(fd);
357                         return (-1);
358                 }
359
360                 /* Get the length of the value */
361                 n = (uint32_t)(charpos - curpos);
362                 if (r != 0) /* more to read, but don't read ending key */
363                         n--;
364
365                 /* Move offset back to the beginning of the value */
366                 error = (int)lseek(fd, curpos, SEEK_SET);
367                 if (error == (curpos - 1)) {
368                         close(fd);
369                         return (-1);
370                 }
371
372                 /* Allocate and read the value into memory */
373                 if (n > vsize) {
374                         if ((value = realloc(value, n + 1)) == NULL) {
375                                 close(fd);
376                                 return (-1);
377                         }
378                         vsize = n;
379                 }
380                 r = read(fd, value, n);
381
382                 /* Terminate the string */
383                 value[n] = '\0';
384
385                 /* Cut trailing whitespace off by termination */
386                 t = value + n;
387                 while (isspace(*--t))
388                         *t = '\0';
389
390                 /* Escape the escaped quotes (replaceall is in string_m.c) */
391                 x = strcount(value, "\\\""); /* in string_m.c */
392                 if (x != 0 && (n + x) > vsize) {
393                         if ((value = realloc(value, n + x + 1)) == NULL) {
394                                 close(fd);
395                                 return (-1);
396                         }
397                         vsize = n + x;
398                 }
399                 if (replaceall(value, "\\\"", "\\\\\"") < 0) {
400                         /* Replace operation failed for some unknown reason */
401                         close(fd);
402                         return (-1);
403                 }
404
405                 /* Remove all new line characters */
406                 if (replaceall(value, "\\\n", "") < 0) {
407                         /* Replace operation failed for some unknown reason */
408                         close(fd);
409                         return (-1);
410                 }
411
412                 /* Resolve escape sequences */
413                 strexpand(value); /* in string_m.c */
414
415 call_function:
416                 /* Abort if we're seeking only assignments */
417                 if (require_equals && !have_equals)
418                         return (-1);
419
420                 found = have_equals = 0; /* reset */
421
422                 /* If there are no options defined, call unknown and loop */
423                 if (options == NULL && unknown != NULL) {
424                         error = unknown(NULL, line, directive, value);
425                         if (error != 0) {
426                                 close(fd);
427                                 return (error);
428                         }
429                         continue;
430                 }
431
432                 /* Loop through the array looking for a match for the value */
433                 for (n = 0; options[n].directive != NULL; n++) {
434                         error = fnmatch(options[n].directive, directive,
435                             FNM_NOESCAPE);
436                         if (error == 0) {
437                                 found = 1;
438                                 /* Call function for array index item */
439                                 if (options[n].action != NULL) {
440                                         error = options[n].action(
441                                             &options[n],
442                                             line, directive, value);
443                                         if (error != 0) {
444                                                 close(fd);
445                                                 return (error);
446                                         }
447                                 }
448                         } else if (error != FNM_NOMATCH) {
449                                 /* An error has occurred */
450                                 close(fd);
451                                 return (-1);
452                         }
453                 }
454                 if (!found && unknown != NULL) {
455                         /*
456                          * No match was found for the value we read from the
457                          * file; call function designated for unknown values.
458                          */
459                         error = unknown(NULL, line, directive, value);
460                         if (error != 0) {
461                                 close(fd);
462                                 return (error);
463                         }
464                 }
465         }
466
467         close(fd);
468         return (0);
469 }