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