7f2cf38bd3fed7a8e3a7dc6a888c2af9acd5cb7f
[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_find_section(struct uci_map *map, struct uci_fixup *f)
170 {
171         struct ucimap_section_data *sd;
172         struct list_head *p;
173
174         list_for_each(p, &map->sdata) {
175                 sd = list_entry(p, struct ucimap_section_data, list);
176                 if (sd->sm != f->sm)
177                         continue;
178                 if (strcmp(f->name, sd->section_name) != 0)
179                         continue;
180                 return ucimap_section_ptr(sd);
181         }
182         return NULL;
183 }
184
185 static bool
186 ucimap_handle_fixup(struct uci_map *map, struct uci_fixup *f)
187 {
188         void *ptr = ucimap_find_section(map, f);
189         struct ucimap_list *list;
190
191         if (!ptr)
192                 return false;
193
194         switch(f->type & UCIMAP_TYPE) {
195         case UCIMAP_SIMPLE:
196                 f->data->ptr = ptr;
197                 break;
198         case UCIMAP_LIST:
199                 list = f->data->list;
200                 list->item[list->n_items++].ptr = ptr;
201                 break;
202         }
203         return true;
204 }
205
206 static void
207 ucimap_add_fixup(struct uci_map *map, union ucimap_data *data, struct uci_optmap *om, const char *str)
208 {
209         struct uci_fixup *f, tmp;
210
211         INIT_LIST_HEAD(&tmp.list);
212         tmp.sm = om->data.sm;
213         tmp.name = str;
214         tmp.type = om->type;
215         tmp.data = data;
216         if (ucimap_handle_fixup(map, &tmp))
217                 return;
218
219         f = malloc(sizeof(struct uci_fixup));
220         if (!f)
221                 return;
222
223         memcpy(f, &tmp, sizeof(tmp));
224         list_add_tail(&f->list, &map->fixup);
225 }
226
227 static void
228 ucimap_add_value(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
229 {
230         union ucimap_data tdata = *data;
231         char *eptr = NULL;
232         long lval;
233         char *s;
234         int val;
235
236         if (ucimap_is_list(om->type) && !ucimap_is_fixup(om->type))
237                 data = &data->list->item[data->list->n_items++];
238
239         switch(om->type & UCIMAP_SUBTYPE) {
240         case UCIMAP_STRING:
241                 if ((om->data.s.maxlen > 0) &&
242                         (strlen(str) > om->data.s.maxlen))
243                         return;
244
245                 s = strdup(str);
246                 tdata.s = s;
247                 ucimap_add_alloc(sd, s);
248                 break;
249         case UCIMAP_BOOL:
250                 if (!strcmp(str, "on"))
251                         val = true;
252                 else if (!strcmp(str, "1"))
253                         val = true;
254                 else if (!strcmp(str, "enabled"))
255                         val = true;
256                 else if (!strcmp(str, "off"))
257                         val = false;
258                 else if (!strcmp(str, "0"))
259                         val = false;
260                 else if (!strcmp(str, "disabled"))
261                         val = false;
262                 else
263                         return;
264
265                 tdata.b = val;
266                 break;
267         case UCIMAP_INT:
268                 lval = strtol(str, &eptr, om->data.i.base);
269                 if (lval < INT_MIN || lval > INT_MAX)
270                         return;
271
272                 if (!eptr || *eptr == '\0')
273                         tdata.i = (int) lval;
274                 else
275                         return;
276                 break;
277         case UCIMAP_SECTION:
278                 ucimap_add_fixup(sd->map, data, om, str);
279                 return;
280         case UCIMAP_CUSTOM:
281                 tdata.s = (char *) data;
282                 break;
283         }
284         if (om->parse) {
285                 if (om->parse(ucimap_section_ptr(sd), om, &tdata, str) < 0)
286                         return;
287         }
288         if (ucimap_is_custom(om->type))
289                 return;
290         memcpy(data, &tdata, sizeof(union ucimap_data));
291 }
292
293
294 static void
295 ucimap_convert_list(union ucimap_data *data, struct uci_optmap *om, struct ucimap_section_data *sd, const char *str)
296 {
297         char *s, *p;
298
299         s = strdup(str);
300         if (!s)
301                 return;
302
303         ucimap_add_alloc(sd, s);
304
305         do {
306                 while (isspace(*s))
307                         s++;
308
309                 if (!*s)
310                         break;
311
312                 p = s;
313                 while (*s && !isspace(*s))
314                         s++;
315
316                 if (isspace(*s)) {
317                         *s = 0;
318                         s++;
319                 }
320
321                 ucimap_add_value(data, om, sd, p);
322         } while (*s);
323 }
324
325 static int
326 ucimap_parse_options(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
327 {
328         struct uci_element *e, *l;
329         struct uci_option *o;
330         union ucimap_data *data;
331
332         uci_foreach_element(&s->options, e) {
333                 struct uci_optmap *om = NULL, *tmp;
334
335                 ucimap_foreach_option(sm, tmp) {
336                         if (strcmp(e->name, tmp->name) == 0) {
337                                 om = tmp;
338                                 break;
339                         }
340                 }
341                 if (!om)
342                         continue;
343
344                 data = ucimap_get_data(sd, om);
345                 o = uci_to_option(e);
346                 if ((o->type == UCI_TYPE_STRING) && ucimap_is_simple(om->type)) {
347                         ucimap_add_value(data, om, sd, o->v.string);
348                 } else if ((o->type == UCI_TYPE_LIST) && ucimap_is_list(om->type)) {
349                         uci_foreach_element(&o->v.list, l) {
350                                 ucimap_add_value(data, om, sd, l->name);
351                         }
352                 } else if ((o->type == UCI_TYPE_STRING) && ucimap_is_list_auto(om->type)) {
353                         ucimap_convert_list(data, om, sd, o->v.string);
354                 }
355         }
356
357         return 0;
358 }
359
360
361 int
362 ucimap_parse_section(struct uci_map *map, struct uci_sectionmap *sm, struct ucimap_section_data *sd, struct uci_section *s)
363 {
364         struct uci_optmap *om;
365         char *section_name;
366         void *section;
367         int n_alloc = 2;
368         int err;
369
370         INIT_LIST_HEAD(&sd->list);
371         sd->map = map;
372         sd->sm = sm;
373
374         ucimap_foreach_option(sm, om) {
375                 if (ucimap_is_list(om->type)) {
376                         union ucimap_data *data;
377                         struct uci_element *e;
378                         int n_elements = 0;
379                         int size;
380
381                         data = ucimap_get_data(sd, om);
382                         uci_foreach_element(&s->options, e) {
383                                 struct uci_option *o = uci_to_option(e);
384                                 struct uci_element *tmp;
385
386                                 if (strcmp(e->name, om->name) != 0)
387                                         continue;
388
389                                 if (o->type == UCI_TYPE_LIST) {
390                                         uci_foreach_element(&o->v.list, tmp) {
391                                                 n_elements++;
392                                         }
393                                 } else if ((o->type == UCI_TYPE_STRING) &&
394                                            ucimap_is_list_auto(om->type)) {
395                                         const char *data = o->v.string;
396                                         do {
397                                                 while (isspace(*data))
398                                                         data++;
399
400                                                 if (!*data)
401                                                         break;
402
403                                                 n_elements++;
404
405                                                 while (*data && !isspace(*data))
406                                                         data++;
407                                         } while (*data);
408
409                                         /* for the duplicated data string */
410                                         if (n_elements > 0)
411                                                 n_alloc++;
412                                 }
413                                 break;
414                         }
415                         /* add one more for the ucimap_list */
416                         n_alloc += n_elements + 1;
417                         size = sizeof(struct ucimap_list) +
418                                 n_elements * sizeof(union ucimap_data);
419                         data->list = malloc(size);
420                         memset(data->list, 0, size);
421                 } else if (ucimap_is_alloc(om->type)) {
422                         n_alloc++;
423                 }
424         }
425
426         sd->allocmap = malloc(n_alloc * sizeof(struct uci_alloc));
427         if (!sd->allocmap)
428                 goto error_mem;
429
430         section_name = strdup(s->e.name);
431         if (!section_name)
432                 goto error_mem;
433
434         sd->section_name = section_name;
435
436         sd->cmap = malloc(BITFIELD_SIZE(sm->n_options));
437         if (!sd->cmap)
438                 goto error_mem;
439
440         memset(sd->cmap, 0, BITFIELD_SIZE(sm->n_options));
441         ucimap_add_alloc(sd, (void *)section_name);
442         ucimap_add_alloc(sd, (void *)sd->cmap);
443         ucimap_foreach_option(sm, om) {
444                 if (!ucimap_is_list(om->type))
445                         continue;
446
447                 ucimap_add_alloc(sd, ucimap_get_data(sd, om)->list);
448         }
449
450         section = ucimap_section_ptr(sd);
451         err = sm->init(map, section, s);
452         if (err)
453                 goto error;
454
455         list_add(&sd->list, &map->sdata);
456         err = ucimap_parse_options(map, sm, sd, s);
457         if (err)
458                 goto error;
459
460         return 0;
461
462 error_mem:
463         if (sd->allocmap)
464                 free(sd->allocmap);
465         free(sd);
466         return UCI_ERR_MEM;
467
468 error:
469         ucimap_free_section(map, sd);
470         return err;
471 }
472
473 static int
474 ucimap_fill_ptr(struct uci_ptr *ptr, struct uci_section *s, const char *option)
475 {
476         struct uci_package *p = s->package;
477
478         memset(ptr, 0, sizeof(struct uci_ptr));
479
480         ptr->package = p->e.name;
481         ptr->p = p;
482
483         ptr->section = s->e.name;
484         ptr->s = s;
485
486         ptr->option = option;
487         return uci_lookup_ptr(p->ctx, ptr, NULL, false);
488 }
489
490 void
491 ucimap_set_changed(struct ucimap_section_data *sd, void *field)
492 {
493         void *section = ucimap_section_ptr(sd);
494         struct uci_sectionmap *sm = sd->sm;
495         struct uci_optmap *om;
496         int ofs = (char *)field - (char *)section;
497         int i = 0;
498
499         ucimap_foreach_option(sm, om) {
500                 if (om->offset == ofs) {
501                         SET_BIT(sd->cmap, i);
502                         break;
503                 }
504                 i++;
505         }
506 }
507
508 int
509 ucimap_store_section(struct uci_map *map, struct uci_package *p, struct ucimap_section_data *sd)
510 {
511         struct uci_sectionmap *sm = sd->sm;
512         struct uci_section *s = NULL;
513         struct uci_optmap *om;
514         struct uci_element *e;
515         struct uci_ptr ptr;
516         int i = 0;
517         int ret;
518
519         uci_foreach_element(&p->sections, e) {
520                 if (!strcmp(e->name, sd->section_name)) {
521                         s = uci_to_section(e);
522                         break;
523                 }
524         }
525         if (!s)
526                 return UCI_ERR_NOTFOUND;
527
528         ucimap_foreach_option(sm, om) {
529                 union ucimap_data *data;
530                 static char buf[32];
531                 char *str = NULL;
532
533                 i++;
534                 if (ucimap_is_list(om->type))
535                         continue;
536
537                 data = ucimap_get_data(sd, om);
538                 if (!TEST_BIT(sd->cmap, i - 1))
539                         continue;
540
541                 ucimap_fill_ptr(&ptr, s, om->name);
542                 switch(om->type & UCIMAP_SUBTYPE) {
543                 case UCIMAP_STRING:
544                         str = data->s;
545                         break;
546                 case UCIMAP_INT:
547                         sprintf(buf, "%d", data->i);
548                         str = buf;
549                         break;
550                 case UCIMAP_BOOL:
551                         sprintf(buf, "%d", !!data->b);
552                         str = buf;
553                         break;
554                 case UCIMAP_CUSTOM:
555                         break;
556                 default:
557                         continue;
558                 }
559                 if (om->format) {
560                         union ucimap_data tdata, *data;
561
562                         data = ucimap_get_data(sd, om);
563                         if (ucimap_is_custom(om->type)) {
564                                 tdata.s = (char *)data;
565                                 data = &tdata;
566                         }
567
568                         if (om->format(ucimap_section_ptr(sd), om, data, &str) < 0)
569                                 continue;
570                 }
571                 if (!str)
572                         continue;
573                 ptr.value = str;
574
575                 ret = uci_set(s->package->ctx, &ptr);
576                 if (ret)
577                         return ret;
578
579                 CLR_BIT(sd->cmap, i - 1);
580         }
581
582         return 0;
583 }
584
585 void
586 ucimap_parse(struct uci_map *map, struct uci_package *pkg)
587 {
588         struct uci_element *e;
589         struct list_head *p, *tmp;
590         int i;
591
592         INIT_LIST_HEAD(&map->fixup);
593         uci_foreach_element(&pkg->sections, e) {
594                 struct uci_section *s = uci_to_section(e);
595
596                 for (i = 0; i < map->n_sections; i++) {
597                         struct uci_sectionmap *sm = map->sections[i];
598                         struct ucimap_section_data *sd;
599
600                         if (strcmp(s->type, map->sections[i]->type) != 0)
601                                 continue;
602
603                         if (sm->alloc) {
604                                 sd = sm->alloc(map, sm, s);
605                                 memset(sd, 0, sizeof(struct ucimap_section_data));
606                         } else {
607                                 sd = malloc(sm->alloc_len);
608                                 memset(sd, 0, sm->alloc_len);
609                         }
610                         if (!sd)
611                                 continue;
612
613                         ucimap_parse_section(map, sm, sd, s);
614                 }
615         }
616         list_for_each_safe(p, tmp, &map->fixup) {
617                 struct uci_fixup *f = list_entry(p, struct uci_fixup, list);
618                 ucimap_handle_fixup(map, f);
619                 list_del(&f->list);
620                 free(f);
621         }
622         list_for_each_safe(p, tmp, &map->sdata) {
623                 struct ucimap_section_data *sd = list_entry(p, struct ucimap_section_data, list);
624                 void *section;
625
626                 if (sd->done)
627                         continue;
628
629                 section = ucimap_section_ptr(sd);
630                 if (sd->sm->add(map, section) != 0)
631                         ucimap_free_section(map, sd);
632         }
633 }