preserve section list order
[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_tail(&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 }