ucimap: add custom free() callbacks for options, only used on custom datatypes
[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 (om->detected_type < 0)
443 return true;
444
445 if (ucimap_is_custom(om->type))
446 return true;
447
448 if (ucimap_is_list(om->type) !=
449 ucimap_is_list(om->detected_type))
450 goto failed;
451
452 if (ucimap_is_list(om->type))
453 return true;
454
455 type = om->type & UCIMAP_SUBTYPE;
456 switch(type) {
457 case UCIMAP_STRING:
458 case UCIMAP_INT:
459 case UCIMAP_BOOL:
460 if (type != om->detected_type)
461 goto failed;
462 break;
463 case UCIMAP_SECTION:
464 goto failed;
465 default:
466 break;
467 }
468 return true;
469
470 failed:
471 DPRINTF("Invalid type in option '%s' of section type '%s', "
472 "declared type is %s, detected type is %s\n",
473 om->name, sm->type,
474 ucimap_get_type_name(om->type),
475 ucimap_get_type_name(om->detected_type));
476 return false;
477 }
478
479 static void
480 ucimap_count_alloc(struct uci_optmap *om, int *n_alloc, int *n_custom)
481 {
482 if (ucimap_is_alloc(om->type))
483 (*n_alloc)++;
484 else if (ucimap_is_custom(om->type) && om->free)
485 (*n_custom)++;
486 }
487
488 int
489 ucimap_parse_section(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
490 {
491 struct uci_optmap *om;
492 char *section_name;
493 void *section;
494 int n_alloc = 2;
495 int n_alloc_custom = 0;
496 int err;
497
498 INIT_LIST_HEAD(&sd->list);
499 sd->map = map;
500 sd->sm = sm;
501
502 ucimap_foreach_option(sm, om) {
503 if (!ucimap_check_optmap_type(sm, om))
504 continue;
505
506 if (ucimap_is_list(om->type)) {
507 union ucimap_data *data;
508 struct uci_element *e;
509 int n_elements = 0;
510 int n_elements_custom = 0;
511 int size;
512
513 data = ucimap_get_data(sd, om);
514 uci_foreach_element(&s->options, e) {
515 struct uci_option *o = uci_to_option(e);
516 struct uci_element *tmp;
517
518 if (strcmp(e->name, om->name) != 0)
519 continue;
520
521 if (o->type == UCI_TYPE_LIST) {
522 uci_foreach_element(&o->v.list, tmp) {
523 ucimap_count_alloc(om, &n_elements, &n_elements_custom);
524 }
525 } else if ((o->type == UCI_TYPE_STRING) &&
526 ucimap_is_list_auto(om->type)) {
527 const char *data = o->v.string;
528 do {
529 while (isspace(*data))
530 data++;
531
532 if (!*data)
533 break;
534
535 n_elements++;
536 ucimap_count_alloc(om, &n_elements, &n_elements_custom);
537
538 while (*data && !isspace(*data))
539 data++;
540 } while (*data);
541
542 /* for the duplicated data string */
543 if (n_elements)
544 n_alloc++;
545 }
546 break;
547 }
548 /* add one more for the ucimap_list */
549 n_alloc += n_elements + 1;
550 n_alloc_custom += n_elements_custom;
551 size = sizeof(struct ucimap_list) +
552 n_elements * sizeof(union ucimap_data);
553 data->list = malloc(size);
554 memset(data->list, 0, size);
555 } else {
556 ucimap_count_alloc(om, &n_alloc, &n_alloc_custom);
557 }
558 }
559
560 sd->allocmap = calloc(n_alloc, sizeof(struct uci_alloc));
561 if (!sd->allocmap)
562 goto error_mem;
563
564 if (n_alloc_custom > 0) {
565 sd->alloc_custom = calloc(n_alloc_custom, sizeof(struct uci_alloc_custom));
566 if (!sd->alloc_custom)
567 goto error_mem;
568 }
569
570 section_name = strdup(s->e.name);
571 if (!section_name)
572 goto error_mem;
573
574 sd->section_name = section_name;
575
576 sd->cmap = calloc(1, BITFIELD_SIZE(sm->n_options));
577 if (!sd->cmap)
578 goto error_mem;
579
580 ucimap_add_alloc(sd, (void *)section_name);
581 ucimap_add_alloc(sd, (void *)sd->cmap);
582 ucimap_foreach_option(sm, om) {
583 if (!ucimap_is_list(om->type))
584 continue;
585
586 ucimap_add_alloc(sd, ucimap_get_data(sd, om)->list);
587 }
588
589 section = ucimap_section_ptr(sd);
590 err = sm->init(map, section, s);
591 if (err)
592 goto error;
593
594 if (map->parsed) {
595 ucimap_add_section(sd);
596 } else {
597 list_add_tail(&sd->list, &map->pending);
598 }
599
600 err = ucimap_parse_options(map, sm, sd, s);
601 if (err)
602 goto error;
603
604 return 0;
605
606 error_mem:
607 if (sd->allocmap)
608 free(sd->allocmap);
609 free(sd);
610 return UCI_ERR_MEM;
611
612 error:
613 ucimap_free_section(map, sd);
614 return err;
615 }
616
617 static int
618 ucimap_fill_ptr(struct uci_ptr *ptr, struct uci_section *s, const char *option)
619 {
620 struct uci_package *p = s->package;
621
622 memset(ptr, 0, sizeof(struct uci_ptr));
623
624 ptr->package = p->e.name;
625 ptr->p = p;
626
627 ptr->section = s->e.name;
628 ptr->s = s;
629
630 ptr->option = option;
631 return uci_lookup_ptr(p->ctx, ptr, NULL, false);
632 }
633
634 void
635 ucimap_set_changed(struct ucimap_section_data *sd, void *field)
636 {
637 void *section = ucimap_section_ptr(sd);
638 struct uci_sectionmap *sm = sd->sm;
639 struct uci_optmap *om;
640 int ofs = (char *)field - (char *)section;
641 int i = 0;
642
643 ucimap_foreach_option(sm, om) {
644 if (om->offset == ofs) {
645 SET_BIT(sd->cmap, i);
646 break;
647 }
648 i++;
649 }
650 }
651
652 int
653 ucimap_store_section(struct uci_map *map, struct uci_package *p, struct ucimap_section_data *sd)
654 {
655 struct uci_sectionmap *sm = sd->sm;
656 struct uci_section *s = NULL;
657 struct uci_optmap *om;
658 struct uci_element *e;
659 struct uci_ptr ptr;
660 int i = 0;
661 int ret;
662
663 uci_foreach_element(&p->sections, e) {
664 if (!strcmp(e->name, sd->section_name)) {
665 s = uci_to_section(e);
666 break;
667 }
668 }
669 if (!s)
670 return UCI_ERR_NOTFOUND;
671
672 ucimap_foreach_option(sm, om) {
673 union ucimap_data *data;
674 static char buf[32];
675 char *str = NULL;
676
677 i++;
678 if (ucimap_is_list(om->type))
679 continue;
680
681 data = ucimap_get_data(sd, om);
682 if (!TEST_BIT(sd->cmap, i - 1))
683 continue;
684
685 ucimap_fill_ptr(&ptr, s, om->name);
686 switch(om->type & UCIMAP_SUBTYPE) {
687 case UCIMAP_STRING:
688 str = data->s;
689 break;
690 case UCIMAP_INT:
691 sprintf(buf, "%d", data->i);
692 str = buf;
693 break;
694 case UCIMAP_BOOL:
695 sprintf(buf, "%d", !!data->b);
696 str = buf;
697 break;
698 case UCIMAP_CUSTOM:
699 break;
700 default:
701 continue;
702 }
703 if (om->format) {
704 union ucimap_data tdata, *data;
705
706 data = ucimap_get_data(sd, om);
707 if (ucimap_is_custom(om->type)) {
708 tdata.s = (char *)data;
709 data = &tdata;
710 }
711
712 if (om->format(ucimap_section_ptr(sd), om, data, &str) < 0)
713 continue;
714 }
715 if (!str)
716 continue;
717 ptr.value = str;
718
719 ret = uci_set(s->package->ctx, &ptr);
720 if (ret)
721 return ret;
722
723 CLR_BIT(sd->cmap, i - 1);
724 }
725
726 return 0;
727 }
728
729 void
730 ucimap_parse(struct uci_map *map, struct uci_package *pkg)
731 {
732 struct uci_element *e;
733 struct list_head *p, *tmp;
734 int i;
735
736 INIT_LIST_HEAD(&map->fixup);
737 uci_foreach_element(&pkg->sections, e) {
738 struct uci_section *s = uci_to_section(e);
739
740 for (i = 0; i < map->n_sections; i++) {
741 struct uci_sectionmap *sm = map->sections[i];
742 struct ucimap_section_data *sd;
743
744 if (strcmp(s->type, map->sections[i]->type) != 0)
745 continue;
746
747 if (sm->alloc) {
748 sd = sm->alloc(map, sm, s);
749 memset(sd, 0, sizeof(struct ucimap_section_data));
750 } else {
751 sd = malloc(sm->alloc_len);
752 memset(sd, 0, sm->alloc_len);
753 }
754 if (!sd)
755 continue;
756
757 ucimap_parse_section(map, sm, sd, s);
758 }
759 }
760 map->parsed = true;
761
762 list_for_each_safe(p, tmp, &map->fixup) {
763 struct uci_fixup *f = list_entry(p, struct uci_fixup, list);
764 ucimap_handle_fixup(map, f);
765 list_del(&f->list);
766 free(f);
767 }
768
769 list_for_each_safe(p, tmp, &map->pending) {
770 struct ucimap_section_data *sd;
771 sd = list_entry(p, struct ucimap_section_data, list);
772
773 list_del_init(&sd->list);
774 ucimap_add_section(sd);
775 }
776 }