6d97b070c7c2a986d3237c20e4f06c0988142ab8
[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 }