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 }