add sanity checks for optmap section type vs sectionmap type
[project/uci.git] / ucimap.c
1 /*
2 * ucimap - library for mapping uci sections into data structures
3 * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14 #include <strings.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <limits.h>
20 #include <stdio.h>
21 #include <ctype.h>
22 #include "ucimap.h"
23 #include "uci_internal.h"
24
25 struct uci_alloc {
26 enum ucimap_type type;
27 union {
28 void **ptr;
29 } data;
30 };
31
32 struct uci_alloc_custom {
33 void *section;
34 struct uci_optmap *om;
35 void *ptr;
36 };
37
38 struct uci_fixup {
39 struct list_head list;
40 struct uci_sectionmap *sm;
41 const char *name;
42 enum ucimap_type type;
43 union ucimap_data *data;
44 };
45
46 #define ucimap_foreach_option(_sm, _o) \
47 if (!(_sm)->options_size) \
48 (_sm)->options_size = sizeof(struct uci_optmap); \
49 for (_o = &(_sm)->options[0]; \
50 ((char *)(_o)) < ((char *) &(_sm)->options[0] + \
51 (_sm)->options_size * (_sm)->n_options); \
52 _o = (struct uci_optmap *) ((char *)(_o) + \
53 (_sm)->options_size))
54
55
56 static inline bool
57 ucimap_is_alloc(enum ucimap_type type)
58 {
59 switch(type & UCIMAP_SUBTYPE) {
60 case UCIMAP_STRING:
61 return true;
62 default:
63 return false;
64 }
65 }
66
67 static inline bool
68 ucimap_is_fixup(enum ucimap_type type)
69 {
70 switch(type & UCIMAP_SUBTYPE) {
71 case UCIMAP_SECTION:
72 return true;
73 default:
74 return false;
75 }
76 }
77
78 static inline bool
79 ucimap_is_simple(enum ucimap_type type)
80 {
81 return ((type & UCIMAP_TYPE) == UCIMAP_SIMPLE);
82 }
83
84 static inline bool
85 ucimap_is_list(enum ucimap_type type)
86 {
87 return ((type & UCIMAP_TYPE) == UCIMAP_LIST);
88 }
89
90 static inline bool
91 ucimap_is_list_auto(enum ucimap_type type)
92 {
93 return ucimap_is_list(type) && !!(type & UCIMAP_LIST_AUTO);
94 }
95
96 static inline bool
97 ucimap_is_custom(enum ucimap_type type)
98 {
99 return ((type & UCIMAP_SUBTYPE) == UCIMAP_CUSTOM);
100 }
101
102 static inline void *
103 ucimap_section_ptr(struct ucimap_section_data *sd)
104 {
105 return ((char *) sd - sd->sm->smap_offset);
106 }
107
108 static inline union ucimap_data *
109 ucimap_get_data(struct ucimap_section_data *sd, struct uci_optmap *om)
110 {
111 void *data;
112
113 data = (char *) ucimap_section_ptr(sd) + om->offset;
114 return data;
115 }
116
117 int
118 ucimap_init(struct uci_map *map)
119 {
120 INIT_LIST_HEAD(&map->pending);
121 INIT_LIST_HEAD(&map->sdata);
122 INIT_LIST_HEAD(&map->fixup);
123 return 0;
124 }
125
126 static void
127 ucimap_free_item(struct uci_alloc *a)
128 {
129 switch(a->type & UCIMAP_TYPE) {
130 case UCIMAP_SIMPLE:
131 case UCIMAP_LIST:
132 free(a->data.ptr);
133 break;
134 }
135 }
136
137 static void
138 ucimap_add_alloc(struct ucimap_section_data *sd, void *ptr)
139 {
140 struct uci_alloc *a = &sd->allocmap[sd->allocmap_len++];
141 a->type = UCIMAP_SIMPLE;
142 a->data.ptr = ptr;
143 }
144
145 void
146 ucimap_free_section(struct uci_map *map, struct ucimap_section_data *sd)
147 {
148 void *section;
149 int i;
150
151 section = ucimap_section_ptr(sd);
152 if (!list_empty(&sd->list))
153 list_del(&sd->list);
154
155 if (sd->sm->free)
156 sd->sm->free(map, section);
157
158 for (i = 0; i < sd->allocmap_len; i++) {
159 ucimap_free_item(&sd->allocmap[i]);
160 }
161
162 if (sd->alloc_custom) {
163 for (i = 0; i < sd->alloc_custom_len; i++) {
164 struct uci_alloc_custom *a = &sd->alloc_custom[i];
165 a->om->free(a->section, a->om, a->ptr);
166 }
167 free(sd->alloc_custom);
168 }
169
170 free(sd->allocmap);
171 free(sd);
172 }
173
174 void
175 ucimap_cleanup(struct uci_map *map)
176 {
177 struct list_head *ptr, *tmp;
178
179 list_for_each_safe(ptr, tmp, &map->sdata) {
180 struct ucimap_section_data *sd = list_entry(ptr, struct ucimap_section_data, list);
181 ucimap_free_section(map, sd);
182 }
183 }
184
185 static void *
186 ucimap_find_section(struct uci_map *map, struct uci_fixup *f)
187 {
188 struct ucimap_section_data *sd;
189 struct list_head *p;
190
191 list_for_each(p, &map->sdata) {
192 sd = list_entry(p, struct ucimap_section_data, list);
193 if (sd->sm != f->sm)
194 continue;
195 if (strcmp(f->name, sd->section_name) != 0)
196 continue;
197 return ucimap_section_ptr(sd);
198 }
199 list_for_each(p, &map->pending) {
200 sd = list_entry(p, struct ucimap_section_data, list);
201 if (sd->sm != f->sm)
202 continue;
203 if (strcmp(f->name, sd->section_name) != 0)
204 continue;
205 return ucimap_section_ptr(sd);
206 }
207 return NULL;
208 }
209
210 static bool
211 ucimap_handle_fixup(struct uci_map *map, struct uci_fixup *f)
212 {
213 void *ptr = ucimap_find_section(map, f);
214 struct ucimap_list *list;
215
216 if (!ptr)
217 return false;
218
219 switch(f->type & UCIMAP_TYPE) {
220 case UCIMAP_SIMPLE:
221 f->data->ptr = ptr;
222 break;
223 case UCIMAP_LIST:
224 list = f->data->list;
225 list->item[list->n_items++].ptr = ptr;
226 break;
227 }
228 return true;
229 }
230
231 static void
232 ucimap_add_fixup(struct ucimap_section_data *sd, union ucimap_data *data, struct uci_optmap *om, const char *str)
233 {
234 struct uci_fixup *f, tmp;
235 struct uci_map *map = sd->map;
236
237 INIT_LIST_HEAD(&tmp.list);
238 tmp.sm = om->data.sm;
239 tmp.name = str;
240 tmp.type = om->type;
241 tmp.data = data;
242 if (ucimap_handle_fixup(map, &tmp))
243 return;
244
245 f = malloc(sizeof(struct uci_fixup));
246 if (!f)
247 return;
248
249 memcpy(f, &tmp, sizeof(tmp));
250 list_add_tail(&f->list, &map->fixup);
251 }
252
253 static void
254 ucimap_add_custom_alloc(struct ucimap_section_data *sd, struct uci_optmap *om, void *ptr)
255 {
256 struct uci_alloc_custom *a = &sd->alloc_custom[sd->alloc_custom_len++];
257
258 a->section = ucimap_section_ptr(sd);
259 a->om = om;
260 a->ptr = ptr;
261 }
262
263 static void
264 ucimap_add_value(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
265 {
266 union ucimap_data tdata = *data;
267 char *eptr = NULL;
268 long lval;
269 char *s;
270 int val;
271
272 if (ucimap_is_list(om->type) && !ucimap_is_fixup(om->type))
273 data = &data->list->item[data->list->n_items++];
274
275 switch(om->type & UCIMAP_SUBTYPE) {
276 case UCIMAP_STRING:
277 if ((om->data.s.maxlen > 0) &&
278 (strlen(str) > om->data.s.maxlen))
279 return;
280
281 s = strdup(str);
282 tdata.s = s;
283 ucimap_add_alloc(sd, s);
284 break;
285 case UCIMAP_BOOL:
286 if (!strcmp(str, "on"))
287 val = true;
288 else if (!strcmp(str, "1"))
289 val = true;
290 else if (!strcmp(str, "enabled"))
291 val = true;
292 else if (!strcmp(str, "off"))
293 val = false;
294 else if (!strcmp(str, "0"))
295 val = false;
296 else if (!strcmp(str, "disabled"))
297 val = false;
298 else
299 return;
300
301 tdata.b = val;
302 break;
303 case UCIMAP_INT:
304 lval = strtol(str, &eptr, om->data.i.base);
305 if (lval < INT_MIN || lval > INT_MAX)
306 return;
307
308 if (!eptr || *eptr == '\0')
309 tdata.i = (int) lval;
310 else
311 return;
312 break;
313 case UCIMAP_SECTION:
314 ucimap_add_fixup(sd, data, om, str);
315 return;
316 case UCIMAP_CUSTOM:
317 tdata.s = (char *) data;
318 break;
319 }
320 if (om->parse) {
321 if (om->parse(ucimap_section_ptr(sd), om, &tdata, str) < 0)
322 return;
323 if (ucimap_is_custom(om->type) && om->free) {
324 if (tdata.ptr != data->ptr)
325 ucimap_add_custom_alloc(sd, om, data->ptr);
326 }
327 }
328 if (ucimap_is_custom(om->type))
329 return;
330 memcpy(data, &tdata, sizeof(union ucimap_data));
331 }
332
333
334 static void
335 ucimap_convert_list(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
336 {
337 char *s, *p;
338
339 s = strdup(str);
340 if (!s)
341 return;
342
343 ucimap_add_alloc(sd, s);
344
345 do {
346 while (isspace(*s))
347 s++;
348
349 if (!*s)
350 break;
351
352 p = s;
353 while (*s && !isspace(*s))
354 s++;
355
356 if (isspace(*s)) {
357 *s = 0;
358 s++;
359 }
360
361 ucimap_add_value(data, om, sd, p);
362 } while (*s);
363 }
364
365 static int
366 ucimap_parse_options(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
367 {
368 struct uci_element *e, *l;
369 struct uci_option *o;
370 union ucimap_data *data;
371
372 uci_foreach_element(&s->options, e) {
373 struct uci_optmap *om = NULL, *tmp;
374
375 ucimap_foreach_option(sm, tmp) {
376 if (strcmp(e->name, tmp->name) == 0) {
377 om = tmp;
378 break;
379 }
380 }
381 if (!om)
382 continue;
383
384 data = ucimap_get_data(sd, om);
385 o = uci_to_option(e);
386 if ((o->type == UCI_TYPE_STRING) && ucimap_is_simple(om->type)) {
387 ucimap_add_value(data, om, sd, o->v.string);
388 } else if ((o->type == UCI_TYPE_LIST) && ucimap_is_list(om->type)) {
389 uci_foreach_element(&o->v.list, l) {
390 ucimap_add_value(data, om, sd, l->name);
391 }
392 } else if ((o->type == UCI_TYPE_STRING) && ucimap_is_list_auto(om->type)) {
393 ucimap_convert_list(data, om, sd, o->v.string);
394 }
395 }
396
397 return 0;
398 }
399
400 static void
401 ucimap_add_section(struct ucimap_section_data *sd)
402 {
403 struct uci_map *map = sd->map;
404
405 if (sd->sm->add(map, ucimap_section_ptr(sd)) < 0)
406 ucimap_free_section(map, sd);
407 else
408 list_add_tail(&sd->list, &map->sdata);
409 }
410
411 static const char *ucimap_type_names[] = {
412 [UCIMAP_STRING] = "string",
413 [UCIMAP_INT] = "integer",
414 [UCIMAP_BOOL] = "boolean",
415 [UCIMAP_SECTION] = "section",
416 [UCIMAP_LIST] = "list",
417 };
418
419 static inline const char *
420 ucimap_get_type_name(int type)
421 {
422 static char buf[32];
423 const char *name;
424
425 if (ucimap_is_list(type))
426 return ucimap_type_names[UCIMAP_LIST];
427
428 name = ucimap_type_names[type & UCIMAP_SUBTYPE];
429 if (!name) {
430 sprintf(buf, "Unknown (%d)", type & UCIMAP_SUBTYPE);
431 name = buf;
432 }
433
434 return name;
435 }
436
437 static bool
438 ucimap_check_optmap_type(struct uci_sectionmap *sm, struct uci_optmap *om)
439 {
440 unsigned int type;
441
442 if (unlikely(sm->type_name != om->type_name) &&
443 unlikely(strcmp(sm->type_name, om->type_name) != 0)) {
444 DPRINTF("Option '%s' of section type '%s' refereces unknown "
445 "section type '%s', should be '%s'.\n",
446 om->name, sm->type, om->type_name, sm->type_name);
447 return false;
448 }
449
450 if (om->detected_type < 0)
451 return true;
452
453 if (ucimap_is_custom(om->type))
454 return true;
455
456 if (ucimap_is_list(om->type) !=
457 ucimap_is_list(om->detected_type))
458 goto failed;
459
460 if (ucimap_is_list(om->type))
461 return true;
462
463 type = om->type & UCIMAP_SUBTYPE;
464 switch(type) {
465 case UCIMAP_STRING:
466 case UCIMAP_INT:
467 case UCIMAP_BOOL:
468 if (type != om->detected_type)
469 goto failed;
470 break;
471 case UCIMAP_SECTION:
472 goto failed;
473 default:
474 break;
475 }
476 return true;
477
478 failed:
479 DPRINTF("Invalid type in option '%s' of section type '%s', "
480 "declared type is %s, detected type is %s\n",
481 om->name, sm->type,
482 ucimap_get_type_name(om->type),
483 ucimap_get_type_name(om->detected_type));
484 return false;
485 }
486
487 static void
488 ucimap_count_alloc(struct uci_optmap *om, int *n_alloc, int *n_custom)
489 {
490 if (ucimap_is_alloc(om->type))
491 (*n_alloc)++;
492 else if (ucimap_is_custom(om->type) && om->free)
493 (*n_custom)++;
494 }
495
496 int
497 ucimap_parse_section(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
498 {
499 struct uci_optmap *om;
500 char *section_name;
501 void *section;
502 int n_alloc = 2;
503 int n_alloc_custom = 0;
504 int err;
505
506 INIT_LIST_HEAD(&sd->list);
507 sd->map = map;
508 sd->sm = sm;
509
510 ucimap_foreach_option(sm, om) {
511 if (!ucimap_check_optmap_type(sm, om))
512 continue;
513
514 if (ucimap_is_list(om->type)) {
515 union ucimap_data *data;
516 struct uci_element *e;
517 int n_elements = 0;
518 int n_elements_custom = 0;
519 int size;
520
521 data = ucimap_get_data(sd, om);
522 uci_foreach_element(&s->options, e) {
523 struct uci_option *o = uci_to_option(e);
524 struct uci_element *tmp;
525
526 if (strcmp(e->name, om->name) != 0)
527 continue;
528
529 if (o->type == UCI_TYPE_LIST) {
530 uci_foreach_element(&o->v.list, tmp) {
531 ucimap_count_alloc(om, &n_elements, &n_elements_custom);
532 }
533 } else if ((o->type == UCI_TYPE_STRING) &&
534 ucimap_is_list_auto(om->type)) {
535 const char *data = o->v.string;
536 do {
537 while (isspace(*data))
538 data++;
539
540 if (!*data)
541 break;
542
543 n_elements++;
544 ucimap_count_alloc(om, &n_elements, &n_elements_custom);
545
546 while (*data && !isspace(*data))
547 data++;
548 } while (*data);
549
550 /* for the duplicated data string */
551 if (n_elements)
552 n_alloc++;
553 }
554 break;
555 }
556 /* add one more for the ucimap_list */
557 n_alloc += n_elements + 1;
558 n_alloc_custom += n_elements_custom;
559 size = sizeof(struct ucimap_list) +
560 n_elements * sizeof(union ucimap_data);
561 data->list = malloc(size);
562 memset(data->list, 0, size);
563 } else {
564 ucimap_count_alloc(om, &n_alloc, &n_alloc_custom);
565 }
566 }
567
568 sd->allocmap = calloc(n_alloc, sizeof(struct uci_alloc));
569 if (!sd->allocmap)
570 goto error_mem;
571
572 if (n_alloc_custom > 0) {
573 sd->alloc_custom = calloc(n_alloc_custom, sizeof(struct uci_alloc_custom));
574 if (!sd->alloc_custom)
575 goto error_mem;
576 }
577
578 section_name = strdup(s->e.name);
579 if (!section_name)
580 goto error_mem;
581
582 sd->section_name = section_name;
583
584 sd->cmap = calloc(1, BITFIELD_SIZE(sm->n_options));
585 if (!sd->cmap)
586 goto error_mem;
587
588 ucimap_add_alloc(sd, (void *)section_name);
589 ucimap_add_alloc(sd, (void *)sd->cmap);
590 ucimap_foreach_option(sm, om) {
591 if (!ucimap_is_list(om->type))
592 continue;
593
594 ucimap_add_alloc(sd, ucimap_get_data(sd, om)->list);
595 }
596
597 section = ucimap_section_ptr(sd);
598 err = sm->init(map, section, s);
599 if (err)
600 goto error;
601
602 if (map->parsed) {
603 ucimap_add_section(sd);
604 } else {
605 list_add_tail(&sd->list, &map->pending);
606 }
607
608 err = ucimap_parse_options(map, sm, sd, s);
609 if (err)
610 goto error;
611
612 return 0;
613
614 error_mem:
615 if (sd->allocmap)
616 free(sd->allocmap);
617 free(sd);
618 return UCI_ERR_MEM;
619
620 error:
621 ucimap_free_section(map, sd);
622 return err;
623 }
624
625 static int
626 ucimap_fill_ptr(struct uci_ptr *ptr, struct uci_section *s, const char *option)
627 {
628 struct uci_package *p = s->package;
629
630 memset(ptr, 0, sizeof(struct uci_ptr));
631
632 ptr->package = p->e.name;
633 ptr->p = p;
634
635 ptr->section = s->e.name;
636 ptr->s = s;
637
638 ptr->option = option;
639 return uci_lookup_ptr(p->ctx, ptr, NULL, false);
640 }
641
642 void
643 ucimap_set_changed(struct ucimap_section_data *sd, void *field)
644 {
645 void *section = ucimap_section_ptr(sd);
646 struct uci_sectionmap *sm = sd->sm;
647 struct uci_optmap *om;
648 int ofs = (char *)field - (char *)section;
649 int i = 0;
650
651 ucimap_foreach_option(sm, om) {
652 if (om->offset == ofs) {
653 SET_BIT(sd->cmap, i);
654 break;
655 }
656 i++;
657 }
658 }
659
660 int
661 ucimap_store_section(struct uci_map *map, struct uci_package *p, struct ucimap_section_data *sd)
662 {
663 struct uci_sectionmap *sm = sd->sm;
664 struct uci_section *s = NULL;
665 struct uci_optmap *om;
666 struct uci_element *e;
667 struct uci_ptr ptr;
668 int i = 0;
669 int ret;
670
671 uci_foreach_element(&p->sections, e) {
672 if (!strcmp(e->name, sd->section_name)) {
673 s = uci_to_section(e);
674 break;
675 }
676 }
677 if (!s)
678 return UCI_ERR_NOTFOUND;
679
680 ucimap_foreach_option(sm, om) {
681 union ucimap_data *data;
682 static char buf[32];
683 char *str = NULL;
684
685 i++;
686 if (ucimap_is_list(om->type))
687 continue;
688
689 data = ucimap_get_data(sd, om);
690 if (!TEST_BIT(sd->cmap, i - 1))
691 continue;
692
693 ucimap_fill_ptr(&ptr, s, om->name);
694 switch(om->type & UCIMAP_SUBTYPE) {
695 case UCIMAP_STRING:
696 str = data->s;
697 break;
698 case UCIMAP_INT:
699 sprintf(buf, "%d", data->i);
700 str = buf;
701 break;
702 case UCIMAP_BOOL:
703 sprintf(buf, "%d", !!data->b);
704 str = buf;
705 break;
706 case UCIMAP_CUSTOM:
707 break;
708 default:
709 continue;
710 }
711 if (om->format) {
712 union ucimap_data tdata, *data;
713
714 data = ucimap_get_data(sd, om);
715 if (ucimap_is_custom(om->type)) {
716 tdata.s = (char *)data;
717 data = &tdata;
718 }
719
720 if (om->format(ucimap_section_ptr(sd), om, data, &str) < 0)
721 continue;
722 }
723 if (!str)
724 continue;
725 ptr.value = str;
726
727 ret = uci_set(s->package->ctx, &ptr);
728 if (ret)
729 return ret;
730
731 CLR_BIT(sd->cmap, i - 1);
732 }
733
734 return 0;
735 }
736
737 void
738 ucimap_parse(struct uci_map *map, struct uci_package *pkg)
739 {
740 struct uci_element *e;
741 struct list_head *p, *tmp;
742 int i;
743
744 INIT_LIST_HEAD(&map->fixup);
745 uci_foreach_element(&pkg->sections, e) {
746 struct uci_section *s = uci_to_section(e);
747
748 for (i = 0; i < map->n_sections; i++) {
749 struct uci_sectionmap *sm = map->sections[i];
750 struct ucimap_section_data *sd;
751
752 if (strcmp(s->type, map->sections[i]->type) != 0)
753 continue;
754
755 if (sm->alloc) {
756 sd = sm->alloc(map, sm, s);
757 memset(sd, 0, sizeof(struct ucimap_section_data));
758 } else {
759 sd = malloc(sm->alloc_len);
760 memset(sd, 0, sm->alloc_len);
761 }
762 if (!sd)
763 continue;
764
765 ucimap_parse_section(map, sm, sd, s);
766 }
767 }
768 map->parsed = true;
769
770 list_for_each_safe(p, tmp, &map->fixup) {
771 struct uci_fixup *f = list_entry(p, struct uci_fixup, list);
772 ucimap_handle_fixup(map, f);
773 list_del(&f->list);
774 free(f);
775 }
776
777 list_for_each_safe(p, tmp, &map->pending) {
778 struct ucimap_section_data *sd;
779 sd = list_entry(p, struct ucimap_section_data, list);
780
781 list_del_init(&sd->list);
782 ucimap_add_section(sd);
783 }
784 }