9aafe0b1cc8cca5f5c16fc9e7953d71c39284b1d
[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 }