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