1 /* 2 * libuci - Library for the Unified Configuration Interface 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 Lesser General Public License version 2.1 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 Lesser General Public License for more details. 13 */ 14 15 static bool uci_list_set_pos(struct uci_list *head, struct uci_list *ptr, int pos) 16 { 17 struct uci_list *old_head = ptr->prev; 18 struct uci_list *new_head = head; 19 struct uci_element *p = NULL; 20 21 uci_list_del(ptr); 22 uci_foreach_element(head, p) { 23 if (pos-- <= 0) 24 break; 25 new_head = &p->list; 26 } 27 28 uci_list_add(new_head->next, ptr); 29 30 return (old_head != new_head); 31 } 32 33 /* 34 * uci_alloc_generic allocates a new uci_element with payload 35 * payload is appended to the struct to save memory and reduce fragmentation 36 */ 37 __private struct uci_element * 38 uci_alloc_generic(struct uci_context *ctx, int type, const char *name, int size) 39 { 40 struct uci_element *e; 41 int datalen = size; 42 void *ptr; 43 44 ptr = uci_malloc(ctx, datalen); 45 e = (struct uci_element *) ptr; 46 e->type = type; 47 if (name) { 48 UCI_TRAP_SAVE(ctx, error); 49 e->name = uci_strdup(ctx, name); 50 UCI_TRAP_RESTORE(ctx); 51 } 52 uci_list_init(&e->list); 53 goto done; 54 55 error: 56 free(ptr); 57 UCI_THROW(ctx, ctx->err); 58 59 done: 60 return e; 61 } 62 63 __private void 64 uci_free_element(struct uci_element *e) 65 { 66 free(e->name); 67 if (!uci_list_empty(&e->list)) 68 uci_list_del(&e->list); 69 free(e); 70 } 71 72 static struct uci_option * 73 uci_alloc_option(struct uci_section *s, const char *name, const char *value, struct uci_list *after) 74 { 75 struct uci_package *p = s->package; 76 struct uci_context *ctx = p->ctx; 77 struct uci_option *o; 78 79 o = uci_alloc_element(ctx, option, name, strlen(value) + 1); 80 o->type = UCI_TYPE_STRING; 81 o->v.string = uci_dataptr(o); 82 o->section = s; 83 strcpy(o->v.string, value); 84 uci_list_insert(after ? after : s->options.prev, &o->e.list); 85 86 return o; 87 } 88 89 static inline void 90 uci_free_option(struct uci_option *o) 91 { 92 struct uci_element *e, *tmp; 93 94 switch(o->type) { 95 case UCI_TYPE_STRING: 96 if ((o->v.string != uci_dataptr(o)) && 97 (o->v.string != NULL)) 98 free(o->v.string); 99 break; 100 case UCI_TYPE_LIST: 101 uci_foreach_element_safe(&o->v.list, tmp, e) { 102 uci_free_element(e); 103 } 104 break; 105 default: 106 break; 107 } 108 uci_free_element(&o->e); 109 } 110 111 static struct uci_option * 112 uci_alloc_list(struct uci_section *s, const char *name, struct uci_list *after) 113 { 114 struct uci_package *p = s->package; 115 struct uci_context *ctx = p->ctx; 116 struct uci_option *o; 117 118 o = uci_alloc_element(ctx, option, name, 0); 119 o->type = UCI_TYPE_LIST; 120 o->section = s; 121 uci_list_init(&o->v.list); 122 uci_list_insert(after ? after : s->options.prev, &o->e.list); 123 124 return o; 125 } 126 127 /* Based on an efficient hash function published by D. J. Bernstein */ 128 static unsigned int djbhash(unsigned int hash, char *str) 129 { 130 int len = strlen(str); 131 int i; 132 133 /* initial value */ 134 if (hash == ~0U) 135 hash = 5381; 136 137 for(i = 0; i < len; i++) { 138 hash = ((hash << 5) + hash) + str[i]; 139 } 140 return (hash & 0x7FFFFFFF); 141 } 142 143 /* fix up an unnamed section, e.g. after adding options to it */ 144 static void uci_fixup_section(struct uci_context *ctx, struct uci_section *s) 145 { 146 unsigned int hash = ~0U; 147 struct uci_element *e; 148 char buf[16]; 149 150 if (!s || s->e.name) 151 return; 152 153 /* 154 * Generate a name for unnamed sections. This is used as reference 155 * when locating or updating the section from apps/scripts. 156 * To make multiple concurrent versions somewhat safe for updating, 157 * the name is generated from a hash of its type and name/value 158 * pairs of its option, and it is prefixed by a counter value. 159 * If the order of the unnamed sections changes for some reason, 160 * updates to them will be rejected. 161 */ 162 hash = djbhash(hash, s->type); 163 uci_foreach_element(&s->options, e) { 164 struct uci_option *o; 165 hash = djbhash(hash, e->name); 166 o = uci_to_option(e); 167 switch(o->type) { 168 case UCI_TYPE_STRING: 169 hash = djbhash(hash, o->v.string); 170 break; 171 default: 172 break; 173 } 174 } 175 sprintf(buf, "cfg%02x%04x", s->package->n_section, hash % (1 << 16)); 176 s->e.name = uci_strdup(ctx, buf); 177 } 178 179 /* transfer options between two sections */ 180 static void uci_section_transfer_options(struct uci_section *dst, struct uci_section *src) 181 { 182 struct uci_element *e; 183 184 /* transfer the option list by inserting the new list HEAD and removing the old */ 185 uci_list_insert(&src->options, &dst->options); 186 uci_list_del(&src->options); 187 188 /* update pointer to section in options */ 189 uci_foreach_element(&dst->options, e) { 190 struct uci_option *o; 191 192 o = uci_to_option(e); 193 o->section = dst; 194 } 195 } 196 197 static struct uci_section * 198 uci_alloc_section(struct uci_package *p, const char *type, const char *name, struct uci_list *after) 199 { 200 struct uci_context *ctx = p->ctx; 201 struct uci_section *s; 202 203 if (name && !name[0]) 204 name = NULL; 205 206 s = uci_alloc_element(ctx, section, name, strlen(type) + 1); 207 uci_list_init(&s->options); 208 s->type = uci_dataptr(s); 209 s->package = p; 210 strcpy(s->type, type); 211 if (name == NULL) 212 s->anonymous = true; 213 p->n_section++; 214 215 uci_list_insert(after ? after : p->sections.prev, &s->e.list); 216 217 return s; 218 } 219 220 static void 221 uci_free_section(struct uci_section *s) 222 { 223 struct uci_element *o, *tmp; 224 225 uci_foreach_element_safe(&s->options, tmp, o) { 226 uci_free_option(uci_to_option(o)); 227 } 228 if ((s->type != uci_dataptr(s)) && 229 (s->type != NULL)) 230 free(s->type); 231 uci_free_element(&s->e); 232 } 233 234 __private struct uci_package * 235 uci_alloc_package(struct uci_context *ctx, const char *name) 236 { 237 struct uci_package *p; 238 239 p = uci_alloc_element(ctx, package, name, 0); 240 p->ctx = ctx; 241 uci_list_init(&p->sections); 242 uci_list_init(&p->delta); 243 uci_list_init(&p->saved_delta); 244 return p; 245 } 246 247 __private void 248 uci_free_package(struct uci_package **package) 249 { 250 struct uci_element *e, *tmp; 251 struct uci_package *p = *package; 252 253 if(!p) 254 return; 255 256 free(p->path); 257 uci_foreach_element_safe(&p->sections, tmp, e) { 258 uci_free_section(uci_to_section(e)); 259 } 260 uci_foreach_element_safe(&p->delta, tmp, e) { 261 uci_free_delta(uci_to_delta(e)); 262 } 263 uci_foreach_element_safe(&p->saved_delta, tmp, e) { 264 uci_free_delta(uci_to_delta(e)); 265 } 266 uci_free_element(&p->e); 267 *package = NULL; 268 } 269 270 static void 271 uci_free_any(struct uci_element **e) 272 { 273 switch((*e)->type) { 274 case UCI_TYPE_SECTION: 275 uci_free_section(uci_to_section(*e)); 276 break; 277 case UCI_TYPE_OPTION: 278 uci_free_option(uci_to_option(*e)); 279 break; 280 default: 281 break; 282 } 283 *e = NULL; 284 } 285 286 __private struct uci_element * 287 uci_lookup_list(struct uci_list *list, const char *name) 288 { 289 struct uci_element *e; 290 291 uci_foreach_element(list, e) { 292 if (!strcmp(e->name, name)) 293 return e; 294 } 295 return NULL; 296 } 297 298 static struct uci_element * 299 uci_lookup_ext_section(struct uci_context *ctx, struct uci_ptr *ptr) 300 { 301 char *idxstr, *t, *section, *name; 302 struct uci_element *e = NULL; 303 struct uci_section *s; 304 int idx, c; 305 306 section = uci_strdup(ctx, ptr->section); 307 name = idxstr = section + 1; 308 309 if (section[0] != '@') 310 goto error; 311 312 /* parse the section index part */ 313 idxstr = strchr(idxstr, '['); 314 if (!idxstr) 315 goto error; 316 *idxstr = 0; 317 idxstr++; 318 319 t = strchr(idxstr, ']'); 320 if (!t) 321 goto error; 322 if (t[1] != 0) 323 goto error; 324 *t = 0; 325 326 t = NULL; 327 idx = strtol(idxstr, &t, 10); 328 if (t && *t) 329 goto error; 330 331 if (!*name) 332 name = NULL; 333 else if (!uci_validate_type(name)) 334 goto error; 335 336 /* if the given index is negative, it specifies the section number from 337 * the end of the list */ 338 if (idx < 0) { 339 c = 0; 340 uci_foreach_element(&ptr->p->sections, e) { 341 s = uci_to_section(e); 342 if (name && (strcmp(s->type, name) != 0)) 343 continue; 344 345 c++; 346 } 347 idx += c; 348 } 349 350 c = 0; 351 uci_foreach_element(&ptr->p->sections, e) { 352 s = uci_to_section(e); 353 if (name && (strcmp(s->type, name) != 0)) 354 continue; 355 356 if (idx == c) 357 goto done; 358 c++; 359 } 360 e = NULL; 361 goto done; 362 363 error: 364 free(section); 365 memset(ptr, 0, sizeof(struct uci_ptr)); 366 UCI_THROW(ctx, UCI_ERR_INVAL); 367 done: 368 free(section); 369 if (e) 370 ptr->section = e->name; 371 return e; 372 } 373 374 int 375 uci_lookup_next(struct uci_context *ctx, struct uci_element **e, struct uci_list *list, const char *name) 376 { 377 UCI_HANDLE_ERR(ctx); 378 379 *e = uci_lookup_list(list, name); 380 if (!*e) 381 UCI_THROW(ctx, UCI_ERR_NOTFOUND); 382 383 return 0; 384 } 385 386 int 387 uci_lookup_ptr(struct uci_context *ctx, struct uci_ptr *ptr, char *str, bool extended) 388 { 389 struct uci_element *e; 390 391 UCI_HANDLE_ERR(ctx); 392 UCI_ASSERT(ctx, ptr != NULL); 393 394 if (str) 395 UCI_INTERNAL(uci_parse_ptr, ctx, ptr, str); 396 397 ptr->flags |= UCI_LOOKUP_DONE; 398 399 /* look up the package first */ 400 if (ptr->p) 401 e = &ptr->p->e; 402 else 403 e = uci_lookup_list(&ctx->root, ptr->package); 404 405 if (!e) { 406 UCI_INTERNAL(uci_load, ctx, ptr->package, &ptr->p); 407 if (!ptr->p) 408 goto notfound; 409 ptr->last = &ptr->p->e; 410 } else { 411 ptr->p = uci_to_package(e); 412 ptr->last = e; 413 } 414 415 if (!ptr->section && !ptr->s) 416 goto complete; 417 418 /* if the section name validates as a regular name, pass through 419 * to the regular uci_lookup function call */ 420 if (ptr->s) { 421 e = &ptr->s->e; 422 } else if (ptr->flags & UCI_LOOKUP_EXTENDED) { 423 if (extended) 424 e = uci_lookup_ext_section(ctx, ptr); 425 else 426 UCI_THROW(ctx, UCI_ERR_INVAL); 427 } else { 428 e = uci_lookup_list(&ptr->p->sections, ptr->section); 429 } 430 431 if (!e) 432 goto abort; 433 434 ptr->last = e; 435 ptr->s = uci_to_section(e); 436 437 if (ptr->option) { 438 e = uci_lookup_list(&ptr->s->options, ptr->option); 439 if (!e) 440 goto abort; 441 442 ptr->o = uci_to_option(e); 443 ptr->last = e; 444 } 445 446 complete: 447 ptr->flags |= UCI_LOOKUP_COMPLETE; 448 abort: 449 return UCI_OK; 450 451 notfound: 452 UCI_THROW(ctx, UCI_ERR_NOTFOUND); 453 /* not a chance here */ 454 return UCI_ERR_NOTFOUND; 455 } 456 457 __private struct uci_element * 458 uci_expand_ptr(struct uci_context *ctx, struct uci_ptr *ptr, bool complete) 459 { 460 UCI_ASSERT(ctx, ptr != NULL); 461 462 if (!(ptr->flags & UCI_LOOKUP_DONE)) 463 UCI_INTERNAL(uci_lookup_ptr, ctx, ptr, NULL, 1); 464 if (complete && !(ptr->flags & UCI_LOOKUP_COMPLETE)) 465 UCI_THROW(ctx, UCI_ERR_NOTFOUND); 466 UCI_ASSERT(ctx, ptr->p != NULL); 467 468 /* fill in missing string info */ 469 if (ptr->p && !ptr->package) 470 ptr->package = ptr->p->e.name; 471 if (ptr->s && !ptr->section) 472 ptr->section = ptr->s->e.name; 473 if (ptr->o && !ptr->option) 474 ptr->option = ptr->o->e.name; 475 476 if (ptr->o) 477 return &ptr->o->e; 478 if (ptr->s) 479 return &ptr->s->e; 480 if (ptr->p) 481 return &ptr->p->e; 482 else 483 return NULL; 484 } 485 486 int uci_rename(struct uci_context *ctx, struct uci_ptr *ptr) 487 { 488 /* NB: UCI_INTERNAL use means without delta tracking */ 489 bool internal = ctx && ctx->internal; 490 struct uci_element *e; 491 struct uci_package *p; 492 char *n; 493 494 UCI_HANDLE_ERR(ctx); 495 496 e = uci_expand_ptr(ctx, ptr, true); 497 p = ptr->p; 498 499 UCI_ASSERT(ctx, ptr->s); 500 UCI_ASSERT(ctx, ptr->value); 501 502 if (!internal && p->has_delta) 503 uci_add_delta(ctx, &p->delta, UCI_CMD_RENAME, ptr->section, ptr->option, ptr->value); 504 505 n = uci_strdup(ctx, ptr->value); 506 free(e->name); 507 e->name = n; 508 509 if (e->type == UCI_TYPE_SECTION) 510 uci_to_section(e)->anonymous = false; 511 512 return 0; 513 } 514 515 int uci_reorder_section(struct uci_context *ctx, struct uci_section *s, int pos) 516 { 517 struct uci_package *p = s->package; 518 bool internal = ctx && ctx->internal; 519 bool changed = false; 520 char order[32]; 521 522 UCI_HANDLE_ERR(ctx); 523 524 changed = uci_list_set_pos(&s->package->sections, &s->e.list, pos); 525 if (!internal && p->has_delta && changed) { 526 sprintf(order, "%d", pos); 527 uci_add_delta(ctx, &p->delta, UCI_CMD_REORDER, s->e.name, NULL, order); 528 } 529 530 return 0; 531 } 532 533 int uci_add_section(struct uci_context *ctx, struct uci_package *p, const char *type, struct uci_section **res) 534 { 535 bool internal = ctx && ctx->internal; 536 struct uci_section *s; 537 538 UCI_HANDLE_ERR(ctx); 539 UCI_ASSERT(ctx, p != NULL); 540 s = uci_alloc_section(p, type, NULL, NULL); 541 if (s && s->anonymous) 542 uci_fixup_section(ctx, s); 543 *res = s; 544 if (!internal && p->has_delta) 545 uci_add_delta(ctx, &p->delta, UCI_CMD_ADD, s->e.name, NULL, type); 546 547 return 0; 548 } 549 550 int uci_delete(struct uci_context *ctx, struct uci_ptr *ptr) 551 { 552 /* NB: pass on internal flag to uci_del_element */ 553 bool internal = ctx && ctx->internal; 554 struct uci_package *p; 555 struct uci_element *e1, *e2, *tmp; 556 int index; 557 558 UCI_HANDLE_ERR(ctx); 559 560 e1 = uci_expand_ptr(ctx, ptr, true); 561 p = ptr->p; 562 563 UCI_ASSERT(ctx, ptr->s); 564 565 if (ptr->o && ptr->o->type == UCI_TYPE_LIST && ptr->value && *ptr->value) { 566 if (!sscanf(ptr->value, "%d", &index)) 567 return 1; 568 569 uci_foreach_element_safe(&ptr->o->v.list, tmp, e2) { 570 if (index == 0) { 571 if (!internal && p->has_delta) 572 uci_add_delta(ctx, &p->delta, UCI_CMD_REMOVE, ptr->section, ptr->option, ptr->value); 573 uci_free_option(uci_to_option(e2)); 574 return 0; 575 } 576 index--; 577 } 578 579 return 0; 580 } 581 582 if (!internal && p->has_delta) 583 uci_add_delta(ctx, &p->delta, UCI_CMD_REMOVE, ptr->section, ptr->option, NULL); 584 585 uci_free_any(&e1); 586 587 if (ptr->option) 588 ptr->o = NULL; 589 else if (ptr->section) 590 ptr->s = NULL; 591 592 return 0; 593 } 594 595 int uci_add_list(struct uci_context *ctx, struct uci_ptr *ptr) 596 { 597 /* NB: UCI_INTERNAL use means without delta tracking */ 598 bool internal = ctx && ctx->internal; 599 struct uci_element *volatile e1 = NULL, *volatile e2 = NULL; 600 601 UCI_HANDLE_ERR(ctx); 602 603 uci_expand_ptr(ctx, ptr, false); 604 UCI_ASSERT(ctx, ptr->s); 605 UCI_ASSERT(ctx, ptr->value); 606 607 if (ptr->o && ptr->o->type != UCI_TYPE_LIST && ptr->o->type != UCI_TYPE_STRING) { 608 UCI_THROW(ctx, UCI_ERR_INVAL); 609 } 610 611 /* create new item */ 612 e1 = uci_alloc_generic(ctx, UCI_TYPE_ITEM, ptr->value, sizeof(struct uci_option)); 613 614 if (!ptr->o) { 615 /* create new list */ 616 UCI_TRAP_SAVE(ctx, error); 617 ptr->o = uci_alloc_list(ptr->s, ptr->option, NULL); 618 UCI_TRAP_RESTORE(ctx); 619 ptr->last = &ptr->o->e; 620 } else if (ptr->o->type == UCI_TYPE_STRING) { 621 /* create new list and add old string value as item to list */ 622 struct uci_option *old = ptr->o; 623 UCI_TRAP_SAVE(ctx, error); 624 e2 = uci_alloc_generic(ctx, UCI_TYPE_ITEM, old->v.string, sizeof(struct uci_option)); 625 ptr->o = uci_alloc_list(ptr->s, ptr->option, &old->e.list); 626 UCI_TRAP_RESTORE(ctx); 627 uci_list_add(&ptr->o->v.list, &e2->list); 628 629 /* remove old option */ 630 if (ptr->option == old->e.name) 631 ptr->option = ptr->o->e.name; 632 uci_free_option(old); 633 ptr->last = &ptr->o->e; 634 } 635 636 /* add new item to list */ 637 uci_list_add(&ptr->o->v.list, &e1->list); 638 639 if (!internal && ptr->p->has_delta) 640 uci_add_delta(ctx, &ptr->p->delta, UCI_CMD_LIST_ADD, ptr->section, ptr->option, ptr->value); 641 642 return 0; 643 error: 644 if (e1 != NULL) 645 uci_free_element(e1); 646 if (e2 != NULL) 647 uci_free_element(e2); 648 UCI_THROW(ctx, ctx->err); 649 } 650 651 int uci_del_list(struct uci_context *ctx, struct uci_ptr *ptr) 652 { 653 /* NB: pass on internal flag to uci_del_element */ 654 bool internal = ctx && ctx->internal; 655 struct uci_element *e, *tmp; 656 struct uci_package *p; 657 658 UCI_HANDLE_ERR(ctx); 659 660 uci_expand_ptr(ctx, ptr, false); 661 UCI_ASSERT(ctx, ptr->s); 662 UCI_ASSERT(ctx, ptr->value); 663 664 if (!(ptr->o && ptr->option)) 665 return 0; 666 667 if ((ptr->o->type != UCI_TYPE_LIST)) 668 return 0; 669 670 p = ptr->p; 671 if (!internal && p->has_delta) 672 uci_add_delta(ctx, &p->delta, UCI_CMD_LIST_DEL, ptr->section, ptr->option, ptr->value); 673 674 uci_foreach_element_safe(&ptr->o->v.list, tmp, e) { 675 if (!strcmp(ptr->value, uci_to_option(e)->e.name)) { 676 uci_free_option(uci_to_option(e)); 677 } 678 } 679 680 return 0; 681 } 682 683 int uci_set(struct uci_context *ctx, struct uci_ptr *ptr) 684 { 685 /* NB: UCI_INTERNAL use means without delta tracking */ 686 bool internal = ctx && ctx->internal; 687 688 UCI_HANDLE_ERR(ctx); 689 uci_expand_ptr(ctx, ptr, false); 690 UCI_ASSERT(ctx, ptr->value); 691 UCI_ASSERT(ctx, ptr->s || (!ptr->option && ptr->section)); 692 if (!ptr->option && ptr->value[0]) { 693 UCI_ASSERT(ctx, uci_validate_type(ptr->value)); 694 } 695 696 if (!ptr->o && ptr->s && ptr->option) { 697 struct uci_element *e; 698 e = uci_lookup_list(&ptr->s->options, ptr->option); 699 if (e) 700 ptr->o = uci_to_option(e); 701 } 702 if (!ptr->value[0]) { 703 /* if setting a nonexistant option/section to a nonexistant value, 704 * exit without errors */ 705 if (!(ptr->flags & UCI_LOOKUP_COMPLETE)) 706 return 0; 707 708 return uci_delete(ctx, ptr); 709 } else if (!ptr->o && ptr->option) { /* new option */ 710 ptr->o = uci_alloc_option(ptr->s, ptr->option, ptr->value, NULL); 711 ptr->last = &ptr->o->e; 712 } else if (!ptr->s && ptr->section) { /* new section */ 713 ptr->s = uci_alloc_section(ptr->p, ptr->value, ptr->section, NULL); 714 ptr->last = &ptr->s->e; 715 } else if (ptr->o && ptr->option) { /* update option */ 716 if (ptr->o->type == UCI_TYPE_STRING && !strcmp(ptr->o->v.string, ptr->value)) 717 return 0; 718 719 if (ptr->o->type == UCI_TYPE_STRING && strlen(ptr->o->v.string) == strlen(ptr->value)) { 720 strcpy(ptr->o->v.string, ptr->value); 721 } else { 722 struct uci_option *old = ptr->o; 723 ptr->o = uci_alloc_option(ptr->s, ptr->option, ptr->value, &old->e.list); 724 if (ptr->option == old->e.name) 725 ptr->option = ptr->o->e.name; 726 uci_free_option(old); 727 ptr->last = &ptr->o->e; 728 } 729 } else if (ptr->s && ptr->section) { /* update section */ 730 if (!strcmp(ptr->s->type, ptr->value)) 731 return 0; 732 733 if (strlen(ptr->s->type) == strlen(ptr->value)) { 734 strcpy(ptr->s->type, ptr->value); 735 } else { 736 struct uci_section *old = ptr->s; 737 ptr->s = uci_alloc_section(ptr->p, ptr->value, old->e.name, &old->e.list); 738 uci_section_transfer_options(ptr->s, old); 739 if (ptr->section == old->e.name) 740 ptr->section = ptr->s->e.name; 741 uci_free_section(old); 742 ptr->s->package->n_section--; 743 ptr->last = &ptr->s->e; 744 } 745 } else { 746 UCI_THROW(ctx, UCI_ERR_INVAL); 747 } 748 749 if (!internal && ptr->p->has_delta) 750 uci_add_delta(ctx, &ptr->p->delta, UCI_CMD_CHANGE, ptr->section, ptr->option, ptr->value); 751 752 return 0; 753 } 754 755 int uci_unload(struct uci_context *ctx, struct uci_package *p) 756 { 757 UCI_HANDLE_ERR(ctx); 758 UCI_ASSERT(ctx, p != NULL); 759 760 uci_free_package(&p); 761 return 0; 762 } 763 764
This page was automatically generated by LXR 0.3.1. • OpenWrt