1 #include <ctype.h>
2 #include <errno.h>
3 #include <limits.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 #include "simpleconf.h"
9
10 #ifndef SC_MAX_ARG_LENGTH
11 # define SC_MAX_ARG_LENGTH 65536
12 #endif
13 #ifndef SC_MAX_RECURSION
14 # define SC_MAX_RECURSION 16
15 #endif
16
17 typedef enum State_ {
18 STATE_UNDEFINED,
19 STATE_PROPNAME,
20 STATE_AFTERPROPNAME,
21 STATE_AFTERPROPNAME2,
22 STATE_AFTERPROPNAMESEP,
23 STATE_RCHAR,
24 STATE_MATCH_ALPHA,
25 STATE_MATCH_ALNUM,
26 STATE_MATCH_DIGITS,
27 STATE_MATCH_XDIGITS,
28 STATE_MATCH_NOSPACE,
29 STATE_MATCH_ANY,
30 STATE_MATCH_ANY_WITHINQUOTES,
31 STATE_MATCH_ANY_AFTERQUOTES,
32 STATE_MATCH_ANY_WITHOUTQUOTES,
33 STATE_MATCH_ANY_UNQUOTED,
34 STATE_MATCH_SPACES,
35 STATE_MATCH_BOOLEAN,
36
37 STATE_TEMPLATE_UNDEFINED,
38 STATE_TEMPLATE_RCHAR,
39 STATE_TEMPLATE_SUBST_ESC
40 } State;
41
42 typedef struct Match_ {
43 const char *str;
44 size_t str_len;
45 } Match;
46
47 typedef enum EntryResult_ {
48 ENTRYRESULT_UNDEFINED,
49 ENTRYRESULT_OK,
50 ENTRYRESULT_IGNORE,
51 ENTRYRESULT_PROPNOTFOUND,
52 ENTRYRESULT_MISMATCH,
53 ENTRYRESULT_SYNTAX,
54 ENTRYRESULT_INVALID_ENTRY,
55 ENTRYRESULT_INTERNAL,
56 ENTRYRESULT_E2BIG,
57 ENTRYRESULT_SPECIAL
58 } EntryResult;
59
60 static const char *
skip_spaces(const char * str)61 skip_spaces(const char *str)
62 {
63 while (*str != 0 && isspace((unsigned char)*str)) {
64 str++;
65 }
66 return str;
67 }
68
69 static int
prefix_match(const char ** str,const char * prefix)70 prefix_match(const char **str, const char *prefix)
71 {
72 size_t prefix_len = strlen(prefix);
73 size_t str_len = strlen(*str);
74 size_t i;
75 int x = 0;
76
77 if (str_len < prefix_len) {
78 return 0;
79 }
80 for (i = 0; i < prefix_len; i++) {
81 x |= tolower((unsigned char)(*str)[i]) ^
82 tolower((unsigned char)prefix[i]);
83 }
84 if (x == 0) {
85 *str += prefix_len;
86 return 1;
87 }
88 return 0;
89 }
90
91 static int
add_to_matches(Match * const matches,size_t * matches_len,const char * start,const char * end)92 add_to_matches(Match *const matches, size_t *matches_len,
93 const char *start, const char *end)
94 {
95 size_t len;
96
97 start = skip_spaces(start);
98 len = (size_t)(end - start);
99 while (len > 0 && isspace((unsigned char)start[len - 1])) {
100 len--;
101 }
102 if (len == 0) {
103 return -1;
104 }
105 matches[*matches_len].str = start;
106 matches[*matches_len].str_len = len;
107 (*matches_len)++;
108
109 return 0;
110 }
111
112 static EntryResult
err_mismatch(const char ** err_p,const char * line_pnt,const char * line)113 err_mismatch(const char **err_p, const char *line_pnt, const char *line)
114 {
115 *err_p = *line_pnt != 0 ? line_pnt : line;
116
117 return ENTRYRESULT_MISMATCH;
118 }
119
120 static EntryResult
err_syntax(const char ** err_p,const char * line_pnt,const char * line)121 err_syntax(const char **err_p, const char *line_pnt, const char *line)
122 {
123 *err_p = *line_pnt != 0 ? line_pnt : line;
124
125 return ENTRYRESULT_SYNTAX;
126 }
127
128 static EntryResult
try_entry(const SimpleConfEntry * const entry,const char * line,char ** arg_p,const char ** err_p)129 try_entry(const SimpleConfEntry *const entry, const char *line,
130 char **arg_p, const char **err_p)
131 {
132 const char *in_pnt;
133 const char *line_pnt;
134 const char *match_start = NULL;
135 const char *out_pnt;
136 const char *prop_name = NULL;
137 const char *wildcard_start = NULL;
138 char * arg;
139 Match matches[10];
140 size_t arg_len;
141 size_t matches_len;
142 size_t wildcard_len;
143 State state = STATE_UNDEFINED;
144 int expect_char;
145 int is_boolean;
146 int is_enabled;
147 int is_special;
148 int c = 0;
149 int d = 0;
150
151 *err_p = NULL;
152 *arg_p = NULL;
153 line = skip_spaces(line);
154 if (*line == 0 || *line == '#') {
155 return ENTRYRESULT_IGNORE;
156 }
157 in_pnt = entry->in;
158 line_pnt = line;
159 prop_name = in_pnt;
160 matches_len = 0;
161 expect_char = 0;
162 is_boolean = 0;
163 is_enabled = 0;
164 is_special = 0;
165 state = STATE_PROPNAME;
166 while (*in_pnt != 0 || *line_pnt != 0) {
167 c = *(const unsigned char *)line_pnt;
168 d = *(const unsigned char *)in_pnt;
169 switch (state) {
170 case STATE_PROPNAME:
171 if (isspace(d)) {
172 in_pnt++;
173 state = STATE_AFTERPROPNAME;
174 } else if (d == '?' && is_boolean == 0) {
175 is_boolean = 1;
176 in_pnt++;
177 } else if (d == '!' && is_special == 0) {
178 is_special = 1;
179 in_pnt++;
180 } else if (c != 0 && d != 0 && tolower(c) == tolower(d)) {
181 in_pnt++;
182 line_pnt++;
183 } else {
184 return ENTRYRESULT_PROPNOTFOUND;
185 }
186 continue;
187 case STATE_AFTERPROPNAME:
188 if (c == '=' || c == ':') {
189 state = STATE_AFTERPROPNAMESEP;
190 line_pnt++;
191 } else if (isspace(c)) {
192 state = STATE_AFTERPROPNAME2;
193 line_pnt++;
194 } else {
195 return err_syntax(err_p, line_pnt, line);
196 }
197 continue;
198 case STATE_AFTERPROPNAME2:
199 if (c == '=' || c == ':') {
200 state = STATE_AFTERPROPNAMESEP;
201 line_pnt++;
202 } else if (isspace(c)) {
203 line_pnt++;
204 } else {
205 state = STATE_AFTERPROPNAMESEP;
206 }
207 continue;
208 case STATE_AFTERPROPNAMESEP:
209 if (c == '=' || c == ':') {
210 return err_syntax(err_p, line_pnt, line);
211 } else if (isspace(c)) {
212 line_pnt++;
213 } else {
214 if (*in_pnt == 0 || in_pnt == prop_name) {
215 return err_syntax(err_p, line_pnt, line);
216 }
217 wildcard_start = line_pnt;
218 state = STATE_RCHAR;
219 }
220 continue;
221 case STATE_RCHAR:
222 if (d == 0) {
223 return err_mismatch(err_p, line_pnt, line);
224 } else if (d == '(') {
225 match_start = line_pnt;
226 in_pnt++;
227 } else if (d == ')') {
228 if (match_start == NULL ||
229 matches_len >= (sizeof matches) / (sizeof matches[0]) ||
230 add_to_matches(matches, &matches_len, match_start,
231 line_pnt) != 0) {
232 return err_mismatch(err_p, line, line);
233 }
234 in_pnt++;
235 } else if (prefix_match(&in_pnt, "<alpha>")) {
236 expect_char = 1;
237 state = STATE_MATCH_ALPHA;
238 } else if (prefix_match(&in_pnt, "<alnum>")) {
239 expect_char = 1;
240 state = STATE_MATCH_ALNUM;
241 } else if (prefix_match(&in_pnt, "<digits>")) {
242 expect_char = 1;
243 state = STATE_MATCH_DIGITS;
244 } else if (prefix_match(&in_pnt, "<xdigits>")) {
245 expect_char = 1;
246 state = STATE_MATCH_XDIGITS;
247 } else if (prefix_match(&in_pnt, "<nospace>")) {
248 expect_char = 1;
249 state = STATE_MATCH_NOSPACE;
250 } else if (prefix_match(&in_pnt, "<any>")) {
251 expect_char = 1;
252 state = STATE_MATCH_ANY;
253 } else if (prefix_match(&in_pnt, "<any*>")) {
254 expect_char = 1;
255 state = STATE_MATCH_ANY_UNQUOTED;
256 } else if (prefix_match(&in_pnt, "<bool>")) {
257 if (is_enabled) {
258 return ENTRYRESULT_INVALID_ENTRY;
259 }
260 state = STATE_MATCH_BOOLEAN;
261 } else if (d == '<') {
262 return ENTRYRESULT_INVALID_ENTRY;
263 } else if (isspace(d)) {
264 in_pnt++;
265 expect_char = 1;
266 state = STATE_MATCH_SPACES;
267 } else if (isgraph(d)) {
268 if (c == d) {
269 in_pnt++;
270 line_pnt++;
271 } else {
272 return err_mismatch(err_p, line_pnt, line);
273 }
274 } else {
275 return err_mismatch(err_p, line_pnt, line);
276 }
277 continue;
278 case STATE_MATCH_ALPHA:
279 if (isalpha(c)) {
280 expect_char = 0;
281 line_pnt++;
282 } else {
283 state = STATE_RCHAR;
284 }
285 continue;
286 case STATE_MATCH_ALNUM:
287 if (isalnum(c)) {
288 expect_char = 0;
289 line_pnt++;
290 } else {
291 state = STATE_RCHAR;
292 }
293 continue;
294 case STATE_MATCH_DIGITS:
295 if (isdigit(c)) {
296 expect_char = 0;
297 line_pnt++;
298 } else {
299 state = STATE_RCHAR;
300 }
301 continue;
302 case STATE_MATCH_XDIGITS:
303 if (isxdigit(c)) {
304 line_pnt++;
305 } else {
306 state = STATE_RCHAR;
307 }
308 continue;
309 case STATE_MATCH_NOSPACE:
310 if (isgraph(c)) {
311 expect_char = 0;
312 line_pnt++;
313 } else {
314 state = STATE_RCHAR;
315 }
316 continue;
317 case STATE_MATCH_ANY:
318 if (c == '"') {
319 if (match_start == line_pnt) {
320 match_start++;
321 } else if (match_start != NULL) {
322 return ENTRYRESULT_INVALID_ENTRY;
323 }
324 line_pnt++;
325 state = STATE_MATCH_ANY_WITHINQUOTES;
326 } else if (isgraph(c)) {
327 expect_char = 0;
328 line_pnt++;
329 state = STATE_MATCH_ANY_WITHOUTQUOTES;
330 } else {
331 state = STATE_RCHAR;
332 }
333 continue;
334 case STATE_MATCH_ANY_WITHINQUOTES:
335 if (c == '"') {
336 state = STATE_MATCH_ANY_AFTERQUOTES;
337 } else if (isprint(c)) {
338 expect_char = 0;
339 line_pnt++;
340 } else {
341 return err_syntax(err_p, line_pnt, line);
342 }
343 continue;
344 case STATE_MATCH_ANY_AFTERQUOTES:
345 if (d == ')') {
346 if (match_start == NULL ||
347 matches_len >= (sizeof matches) / (sizeof matches[0]) ||
348 add_to_matches(matches, &matches_len, match_start,
349 line_pnt) != 0) {
350 return err_mismatch(err_p, line, line);
351 }
352 match_start = NULL;
353 line_pnt++;
354 in_pnt++;
355 }
356 state = STATE_RCHAR;
357 continue;
358 case STATE_MATCH_ANY_UNQUOTED:
359 if (isprint(c)) {
360 expect_char = 0;
361 line_pnt++;
362 } else {
363 state = STATE_RCHAR;
364 }
365 continue;
366 case STATE_MATCH_ANY_WITHOUTQUOTES:
367 if (isgraph(c)) {
368 expect_char = 0;
369 line_pnt++;
370 } else {
371 state = STATE_RCHAR;
372 }
373 continue;
374 case STATE_MATCH_SPACES:
375 if (isspace(c)) {
376 expect_char = 0;
377 line_pnt++;
378 } else {
379 state = STATE_RCHAR;
380 }
381 continue;
382 case STATE_MATCH_BOOLEAN: {
383 if (prefix_match(&line_pnt, "yes") || prefix_match(&line_pnt, "on") ||
384 prefix_match(&line_pnt, "true") || prefix_match(&line_pnt, "1")) {
385 is_enabled = 1;
386 state = STATE_RCHAR;
387 } else if (prefix_match(&line_pnt, "no") || prefix_match(&line_pnt, "off") ||
388 prefix_match(&line_pnt, "false") || prefix_match(&line_pnt, "0")) {
389 is_enabled = 0;
390 state = STATE_RCHAR;
391 } else {
392 return err_syntax(err_p, line_pnt, line);
393 }
394 continue;
395 }
396 default:
397 return ENTRYRESULT_INVALID_ENTRY;
398 }
399 }
400 switch (state) {
401 case STATE_RCHAR:
402 case STATE_MATCH_ALPHA:
403 case STATE_MATCH_ALNUM:
404 case STATE_MATCH_DIGITS:
405 case STATE_MATCH_XDIGITS:
406 case STATE_MATCH_NOSPACE:
407 case STATE_MATCH_ANY:
408 case STATE_MATCH_ANY_AFTERQUOTES:
409 case STATE_MATCH_ANY_WITHOUTQUOTES:
410 case STATE_MATCH_ANY_UNQUOTED:
411 case STATE_MATCH_SPACES:
412 case STATE_MATCH_BOOLEAN:
413 break;
414 default:
415 return err_mismatch(err_p, line_pnt, line);
416 }
417 if (expect_char != 0) {
418 return err_mismatch(err_p, line_pnt, line);
419 }
420 if (*in_pnt == ')') {
421 if (match_start == NULL ||
422 matches_len >= (sizeof matches) / (sizeof matches[0]) ||
423 add_to_matches(matches, &matches_len, match_start, line_pnt) != 0) {
424 return ENTRYRESULT_SYNTAX;
425 }
426 match_start = NULL;
427 }
428 if (is_boolean != 0 && is_enabled == 0) {
429 return ENTRYRESULT_IGNORE;
430 }
431 if (wildcard_start == NULL) {
432 wildcard_len = 0;
433 } else {
434 wildcard_len = (size_t) (line_pnt - wildcard_start);
435 }
436 out_pnt = entry->out;
437 if ((arg = malloc(SC_MAX_ARG_LENGTH + 1)) == NULL) {
438 return ENTRYRESULT_INTERNAL;
439 }
440 arg_len = 0;
441 state = STATE_TEMPLATE_RCHAR;
442 while (arg_len < SC_MAX_ARG_LENGTH && *out_pnt != 0) {
443 d = *(const unsigned char *)out_pnt;
444 switch (state) {
445 case STATE_TEMPLATE_RCHAR:
446 if (d == '$') {
447 out_pnt++;
448 state = STATE_TEMPLATE_SUBST_ESC;
449 } else {
450 arg[arg_len] = (char)d;
451 arg_len++;
452 out_pnt++;
453 }
454 continue;
455 case STATE_TEMPLATE_SUBST_ESC:
456 if (d == '*') {
457 size_t i = 0;
458
459 while (arg_len < SC_MAX_ARG_LENGTH && i < wildcard_len) {
460 arg[arg_len++] = wildcard_start[i++];
461 }
462 out_pnt++;
463 state = STATE_TEMPLATE_RCHAR;
464 } else if (d >= '0' && d <= '9') {
465 size_t match_id = (size_t)(d - '0');
466 size_t i = 0;
467
468 if (match_id >= matches_len) {
469 return ENTRYRESULT_INVALID_ENTRY;
470 }
471 while (
472 arg_len < SC_MAX_ARG_LENGTH && i < matches[match_id].str_len) {
473 arg[arg_len++] = matches[match_id].str[i++];
474 }
475 out_pnt++;
476 state = STATE_TEMPLATE_RCHAR;
477 } else {
478 free(arg);
479 return ENTRYRESULT_INVALID_ENTRY;
480 }
481 continue;
482 default:
483 abort();
484 }
485 }
486 if (arg_len >= SC_MAX_ARG_LENGTH) {
487 free(arg);
488 errno = E2BIG;
489 return ENTRYRESULT_E2BIG;
490 }
491 arg[arg_len] = 0;
492 *arg_p = arg;
493
494 if (is_special) {
495 return ENTRYRESULT_SPECIAL;
496 }
497 return ENTRYRESULT_OK;
498 }
499
500 static char *
chomp(char * str)501 chomp(char *str)
502 {
503 size_t i = strlen(str);
504 int c;
505
506 if (i == 0) {
507 return str;
508 }
509 do {
510 i--;
511 c = (unsigned char) str[i];
512 if (isspace(c)) {
513 str[i] = 0;
514 } else {
515 break;
516 }
517 } while (i != 0);
518
519 return str;
520 }
521
522 void
sc_argv_free(int argc,char * argv[])523 sc_argv_free(int argc, char *argv[])
524 {
525 int i;
526
527 for (i = 0; i < argc; i++) {
528 free(argv[i]);
529 }
530 free(argv);
531 }
532
533 static int
append_to_command_line_from_file(const char * file_name,const SimpleConfConfig * config,const SimpleConfEntry entries[],size_t entries_count,int * argc_p,char *** argv_p,unsigned int depth)534 append_to_command_line_from_file(const char *file_name,
535 const SimpleConfConfig *config,
536 const SimpleConfEntry entries[],
537 size_t entries_count,
538 int *argc_p, char ***argv_p,
539 unsigned int depth)
540 {
541 char line[SC_MAX_ARG_LENGTH];
542 FILE *fp = NULL;
543 char *arg;
544 char **argv_tmp;
545 const char *err = NULL;
546 const char *err_tmp;
547 size_t i;
548 unsigned int line_count = 0;
549 int try_next = 1;
550
551 if (depth >= SC_MAX_RECURSION) {
552 fprintf(stderr, "[%s]: too many levels of recursion\n", file_name);
553 return -1;
554 }
555 if ((fp = fopen(file_name, "r")) == NULL) {
556 fprintf(stderr, "Unable to open [%s]: %s\n", file_name, strerror(errno));
557 return -1;
558 }
559 while (fgets(line, (int)(sizeof line), fp) != NULL) {
560 chomp(line);
561 line_count++;
562 for (i = 0; i < entries_count; i++) {
563 try_next = 1;
564 switch (try_entry(&entries[i], line, &arg, &err_tmp)) {
565 case ENTRYRESULT_IGNORE:
566 try_next = 0;
567 break;
568 case ENTRYRESULT_PROPNOTFOUND:
569 break;
570 case ENTRYRESULT_E2BIG:
571 fclose(fp);
572 return -1;
573 case ENTRYRESULT_INVALID_ENTRY:
574 fprintf(stderr, "Bogus rule: [%s]\n", entries[i].in);
575 abort();
576 case ENTRYRESULT_INTERNAL:
577 fclose(fp);
578 return -1;
579 case ENTRYRESULT_MISMATCH:
580 err = err_tmp;
581 continue;
582 case ENTRYRESULT_SYNTAX: {
583 err = err_tmp;
584 if (err != NULL && *err != 0) {
585 fprintf(stderr, "%s:%u:%u: syntax error line %u: [%s].\n",
586 file_name, line_count, (unsigned int)(err - line + 1),
587 line_count, err);
588 } else {
589 fprintf(stderr, "%s:%u:1: syntax error line %u: [%s].\n",
590 file_name, line_count, line_count, line);
591 }
592 fclose(fp);
593 return -1;
594 }
595 case ENTRYRESULT_OK:
596 try_next = 0;
597 if (arg == NULL || *arg == 0) {
598 free(arg);
599 break;
600 }
601 if (*argc_p >= INT_MAX / (int)(sizeof *arg)) {
602 abort();
603 }
604 if ((argv_tmp = realloc(*argv_p, (sizeof arg) *
605 ((size_t) *argc_p + 1))) == NULL) {
606 free(arg);
607 fclose(fp);
608 return -1;
609 }
610 *argv_p = argv_tmp;
611 (*argv_p)[(*argc_p)++] = arg;
612 break;
613 case ENTRYRESULT_SPECIAL: {
614 char *output = NULL;
615 SimpleConfSpecialHandlerResult special_result;
616
617 try_next = 0;
618 if (config == NULL || config->special_handler == NULL) {
619 fprintf(stderr, "Undefined handler for special keywords\n");
620 abort();
621 }
622 special_result = config->special_handler((void **) &output, arg,
623 config->user_data);
624 if (special_result == SC_SPECIAL_HANDLER_RESULT_NEXT) {
625 free(arg);
626 break;
627 } else if (special_result == SC_SPECIAL_HANDLER_RESULT_ERROR) {
628 free(arg);
629 fclose(fp);
630 return -1;
631 } else if (special_result == SC_SPECIAL_HANDLER_RESULT_INCLUDE) {
632 const int ret = append_to_command_line_from_file
633 ((const char *) output, config, entries, entries_count,
634 argc_p, argv_p, depth + 1U);
635 free(output);
636 free(arg);
637 if (ret != 0) {
638 fclose(fp);
639 return -1;
640 }
641 break;
642 }
643 abort();
644 }
645 default:
646 abort();
647 }
648 if (try_next == 0) {
649 break;
650 }
651 }
652 if (try_next != 0 && i >= entries_count) {
653 if (err != NULL && *err != 0) {
654 fprintf(stderr, "%s:%u:%u: syntax error line %u: [%s].\n",
655 file_name, line_count, (unsigned int)(err - line + 1),
656 line_count, err);
657 } else {
658 fprintf(stderr, "%s:%u:1: property not found line %u: [%s].\n",
659 file_name, line_count, line_count, line);
660 }
661 fclose(fp);
662 return -1;
663 }
664 }
665 (void) fclose(fp);
666
667 return 0;
668 }
669
670 int
sc_build_command_line_from_file(const char * file_name,const SimpleConfConfig * config,const SimpleConfEntry entries[],size_t entries_count,char * app_name,int * argc_p,char *** argv_p)671 sc_build_command_line_from_file(const char *file_name,
672 const SimpleConfConfig *config,
673 const SimpleConfEntry entries[],
674 size_t entries_count, char *app_name,
675 int *argc_p, char ***argv_p)
676 {
677 char **argv = NULL;
678 int argc = 0;
679
680 *argc_p = 0;
681 *argv_p = NULL;
682 if ((argv = malloc(sizeof *argv)) == NULL ||
683 (app_name = strdup(app_name)) == NULL) {
684 sc_argv_free(argc, argv);
685 return -1;
686 }
687 argv[argc++] = app_name;
688 if (append_to_command_line_from_file(file_name, config,
689 entries, entries_count,
690 &argc, &argv, 0U) != 0) {
691 sc_argv_free(argc, argv);
692 return -1;
693 }
694 *argc_p = argc;
695 *argv_p = argv;
696
697 return 0;
698 }
699