1 /* 2 * Copyright (C) 2020-2021 Jo-Philipp Wich <jo@mein.io> 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 /** 18 * # OpenWrt UCI configuration 19 * 20 * The `uci` module provides access to the native OpenWrt 21 * {@link https://github.com/openwrt/uci libuci} API for reading and 22 * manipulating UCI configuration files. 23 * 24 * Functions can be individually imported and directly accessed using the 25 * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} 26 * syntax: 27 * 28 * ``` 29 * import { cursor } from 'uci'; 30 * 31 * let ctx = cursor(); 32 * let hostname = ctx.get_first('system', 'system', 'hostname'); 33 * ``` 34 * 35 * Alternatively, the module namespace can be imported 36 * using a wildcard import statement: 37 * 38 * ``` 39 * import * as uci from 'uci'; 40 * 41 * let ctx = uci.cursor(); 42 * let hostname = ctx.get_first('system', 'system', 'hostname'); 43 * ``` 44 * 45 * Additionally, the uci module namespace may also be imported by invoking 46 * the `ucode` interpreter with the `-luci` switch. 47 * 48 * @module uci 49 */ 50 51 #include <string.h> 52 #include <uci.h> 53 54 #include "ucode/module.h" 55 56 #define err_return(err) do { last_error = err; return NULL; } while(0) 57 58 static int last_error = 0; 59 static uc_resource_type_t *cursor_type; 60 61 enum pkg_cmd { 62 CMD_SAVE, 63 CMD_COMMIT, 64 CMD_REVERT 65 }; 66 67 /** 68 * Query error information. 69 * 70 * Returns a string containing a description of the last occurred error or 71 * `null` if there is no error information. 72 * 73 * @function module:uci#error 74 * 75 * @returns {?string} 76 * 77 * @example 78 * // Trigger error 79 * const ctx = cursor(); 80 * ctx.set("not_existing_config", "test", "1"); 81 * 82 * // Print error (should yield "Entry not found") 83 * print(ctx.error(), "\n"); 84 */ 85 static uc_value_t * 86 uc_uci_error(uc_vm_t *vm, size_t nargs) 87 { 88 char buf[sizeof("Unknown error: -9223372036854775808")]; 89 uc_value_t *errmsg; 90 91 const char *errstr[] = { 92 [UCI_ERR_MEM] = "Out of memory", 93 [UCI_ERR_INVAL] = "Invalid argument", 94 [UCI_ERR_NOTFOUND] = "Entry not found", 95 [UCI_ERR_IO] = "I/O error", 96 [UCI_ERR_PARSE] = "Parse error", 97 [UCI_ERR_DUPLICATE] = "Duplicate entry", 98 [UCI_ERR_UNKNOWN] = "Unknown error", 99 }; 100 101 if (last_error == 0) 102 return NULL; 103 104 if (last_error >= 0 && (unsigned)last_error < ARRAY_SIZE(errstr)) { 105 errmsg = ucv_string_new(errstr[last_error]); 106 } 107 else { 108 snprintf(buf, sizeof(buf), "Unknown error: %d", last_error); 109 errmsg = ucv_string_new(buf); 110 } 111 112 last_error = 0; 113 114 return errmsg; 115 } 116 117 118 /** 119 * Instantiate uci cursor. 120 * 121 * A uci cursor is a context for interacting with uci configuration files. It's 122 * purpose is to cache and hold changes made to loaded configuration states 123 * until those changes are written out to disk or discared. 124 * 125 * Unsaved and uncommitted changes in a cursor instance are private and not 126 * visible to other cursor instances instantiated by the same program or other 127 * processes on the system. 128 * 129 * Returns the instantiated cursor on success. 130 * 131 * Returns `null` on error, e.g. if an invalid path argument was provided. 132 * 133 * @function module:uci#cursor 134 * 135 * @param {string} [config_dir=/etc/config] 136 * The directory to search for configuration files. It defaults to the well 137 * known uci configuration directory `/etc/config` but may be set to a different 138 * path for special purpose applications. 139 * 140 * @param {string} [delta_dir=/tmp/.uci] 141 * The directory to save delta records in. It defaults to the well known 142 * `/tmp/.uci` path which is used as default by the uci command line tool. 143 * 144 * By changing this path to a different location, it is possible to isolate 145 * uncommitted application changes from the uci cli or other processes on the 146 * system. 147 * 148 * @returns {?module:uci.cursor} 149 */ 150 static uc_value_t * 151 uc_uci_cursor(uc_vm_t *vm, size_t nargs) 152 { 153 uc_value_t *cdir = uc_fn_arg(0); 154 uc_value_t *sdir = uc_fn_arg(1); 155 struct uci_context *c; 156 int rv; 157 158 if ((cdir && ucv_type(cdir) != UC_STRING) || 159 (sdir && ucv_type(sdir) != UC_STRING)) 160 err_return(UCI_ERR_INVAL); 161 162 c = uci_alloc_context(); 163 164 if (!c) 165 err_return(UCI_ERR_MEM); 166 167 if (cdir) { 168 rv = uci_set_confdir(c, ucv_string_get(cdir)); 169 170 if (rv) 171 err_return(rv); 172 } 173 174 if (sdir) { 175 rv = uci_set_savedir(c, ucv_string_get(sdir)); 176 177 if (rv) 178 err_return(rv); 179 } 180 181 return uc_resource_new(cursor_type, c); 182 } 183 184 185 /** 186 * Represents a context for interacting with uci configuration files. 187 * 188 * Operations on uci configurations are performed through a uci cursor object 189 * which operates on in-memory representations of loaded configuration files. 190 * 191 * Any changes made to configuration values are local to the cursor object and 192 * held in memory only until they're written out to the filesystem using the 193 * `save()` and `commit()` methods. 194 * 195 * Changes performed in one cursor instance are not reflected in another, unless 196 * the first instance writes those changes to the filesystem and the other 197 * instance explicitly (re)loads the affected configuration files. 198 * 199 * @class module:uci.cursor 200 * @hideconstructor 201 * 202 * @borrows module:uci#error as module.uci.cursor#error 203 * 204 * @see {@link module:uci#cursor|cursor()} 205 * 206 * @example 207 * 208 * const ctx = cursor(…); 209 * 210 * // Enumerate configuration files 211 * ctx.configs(); 212 * 213 * // Load configuration files 214 * ctx.load(…); 215 * ctx.unload(…); 216 * 217 * // Query values 218 * ctx.get(…); 219 * ctx.get_all(…); 220 * ctx.get_first(…); 221 * ctx.foreach(…); 222 * 223 * // Modify values 224 * ctx.add(…); 225 * ctx.set(…); 226 * ctx.rename(…); 227 * ctx.reorder(…); 228 * ctx.delete(…); 229 * 230 * // Stage, revert, save changes 231 * ctx.changes(…); 232 * ctx.save(…); 233 * ctx.revert(…); 234 * ctx.commit(…); 235 */ 236 237 /** 238 * A uci change record is a plain array containing the change operation name as 239 * first element, the affected section ID as second argument and an optional 240 * third and fourth argument whose meanings depend on the operation. 241 * 242 * @typedef {string[]} ChangeRecord 243 * @memberof module:uci.cursor 244 * 245 * @property {string} 0 246 * The operation name - may be one of `add`, `set`, `remove`, `order`, 247 * `list-add`, `list-del` or `rename`. 248 * 249 * @property {string} 1 250 * The section ID targeted by the operation. 251 * 252 * @property {string} 2 253 * The meaning of the third element depends on the operation. 254 * - For `add` it is type of the section that has been added 255 * - For `set` it either is the option name if a fourth element exists, or the 256 * type of a named section which has been added when the change entry only 257 * contains three elements. 258 * - For `remove` it contains the name of the option that has been removed. 259 * - For `order` it specifies the new sort index of the section. 260 * - For `list-add` it contains the name of the list option a new value has been 261 * added to. 262 * - For `list-del` it contains the name of the list option a value has been 263 * removed from. 264 * - For `rename` it contains the name of the option that has been renamed if a 265 * fourth element exists, else it contains the new name a section has been 266 * renamed to if the change entry only contains three elements. 267 * 268 * @property {string} 4 269 * The meaning of the fourth element depends on the operation. 270 * - For `set` it is the value an option has been set to. 271 * - For `list-add` it is the new value that has been added to a list option. 272 * - For `rename` it is the new name of an option that has been renamed. 273 */ 274 275 /** 276 * A section object represents the options and their corresponding values 277 * enclosed within a configuration section, as well as some additional meta data 278 * such as sort indexes and internal ID. 279 * 280 * Any internal metadata fields are prefixed with a dot which isn't an allowed 281 * character for normal option names. 282 * 283 * @typedef {Object<string, boolean|number|string|string[]>} SectionObject 284 * @memberof module:uci.cursor 285 * 286 * @property {boolean} .anonymous 287 * The `.anonymous` property specifies whether the configuration is 288 * anonymous (`true`) or named (`false`). 289 * 290 * @property {number} .index 291 * The `.index` property specifies the sort order of the section. 292 * 293 * @property {string} .name 294 * The `.name` property holds the name of the section object. It may be either 295 * an anonymous ID in the form `cfgXXXXXX` with `X` being a hexadecimal digit or 296 * a string holding the name of the section. 297 * 298 * @property {string} .type 299 * The `.type` property contains the type of the corresponding uci 300 * section. 301 * 302 * @property {string|string[]} * 303 * A section object may contain an arbitrary number of further properties 304 * representing the uci option enclosed in the section. 305 * 306 * All option property names will be in the form `[A-Za-z0-9_]+` and either 307 * contain a string value or an array of strings, in case the underlying option 308 * is an UCI list. 309 */ 310 311 /** 312 * The sections callback is invoked for each section found within the given 313 * configuration and receives the section object and its associated name as 314 * arguments. 315 * 316 * @callback module:uci.cursor.SectionCallback 317 * 318 * @param {module:uci.cursor.SectionObject} section 319 * The section object. 320 */ 321 322 /** 323 * Explicitly reload configuration file. 324 * 325 * Usually, any attempt to query or modify a value within a given configuration 326 * will implicitly load the underlying file into memory. By invoking `load()` 327 * explicitly, a potentially loaded stale configuration is discarded and 328 * reloaded from the file system, ensuring that the latest state is reflected in 329 * the cursor. 330 * 331 * Returns `true` if the configuration was successfully loaded. 332 * 333 * Returns `null` on error, e.g. if the requested configuration does not exist. 334 * 335 * @function module:uci.cursor#load 336 * 337 * @param {string} config 338 * The name of the configuration file to load, e.g. `"system"` to load 339 * `/etc/config/system` into the cursor. 340 * 341 * @returns {?boolean} 342 */ 343 static uc_value_t * 344 uc_uci_load(uc_vm_t *vm, size_t nargs) 345 { 346 struct uci_context **c = uc_fn_this("uci.cursor"); 347 uc_value_t *conf = uc_fn_arg(0); 348 struct uci_element *e; 349 char *s; 350 351 if (!c || !*c) 352 err_return(UCI_ERR_INVAL); 353 354 if (ucv_type(conf) != UC_STRING) 355 err_return(UCI_ERR_INVAL); 356 357 s = ucv_string_get(conf); 358 359 uci_foreach_element(&(*c)->root, e) { 360 if (!strcmp(e->name, s)) { 361 uci_unload(*c, uci_to_package(e)); 362 break; 363 } 364 } 365 366 if (uci_load(*c, s, NULL)) 367 err_return((*c)->err); 368 369 return ucv_boolean_new(true); 370 } 371 372 /** 373 * Explicitly unload configuration file. 374 * 375 * The `unload()` function forcibly discards a loaded configuration state from 376 * the cursor so that the next attempt to read or modify that configuration 377 * will load it anew from the file system. 378 * 379 * Returns `true` if the configuration was successfully unloaded. 380 * 381 * Returns `false` if the configuration was not loaded to begin with. 382 * 383 * Returns `null` on error, e.g. if the requested configuration does not exist. 384 * 385 * @function module:uci.cursor#unload 386 * 387 * @param {string} config 388 * The name of the configuration file to unload. 389 * 390 * @returns {?boolean} 391 */ 392 static uc_value_t * 393 uc_uci_unload(uc_vm_t *vm, size_t nargs) 394 { 395 struct uci_context **c = uc_fn_this("uci.cursor"); 396 uc_value_t *conf = uc_fn_arg(0); 397 struct uci_element *e; 398 399 if (!c || !*c) 400 err_return(UCI_ERR_INVAL); 401 402 if (ucv_type(conf) != UC_STRING) 403 err_return(UCI_ERR_INVAL); 404 405 uci_foreach_element(&(*c)->root, e) { 406 if (!strcmp(e->name, ucv_string_get(conf))) { 407 uci_unload(*c, uci_to_package(e)); 408 409 return ucv_boolean_new(true); 410 } 411 } 412 413 return ucv_boolean_new(false); 414 } 415 416 static int 417 lookup_extended(struct uci_context *ctx, struct uci_ptr *ptr, bool extended) 418 { 419 int rv; 420 struct uci_ptr lookup; 421 422 /* use a copy of the passed ptr since failing lookups will 423 * clobber the state */ 424 lookup = *ptr; 425 lookup.flags |= UCI_LOOKUP_EXTENDED; 426 427 rv = uci_lookup_ptr(ctx, &lookup, NULL, extended); 428 429 /* copy to passed ptr on success */ 430 if (!rv) 431 *ptr = lookup; 432 433 return rv; 434 } 435 436 static int 437 lookup_ptr(struct uci_context *ctx, struct uci_ptr *ptr, bool extended) 438 { 439 if (ptr && !ptr->s && ptr->section && *ptr->section == '@') 440 return lookup_extended(ctx, ptr, extended); 441 442 return uci_lookup_ptr(ctx, ptr, NULL, extended); 443 } 444 445 static uc_value_t * 446 option_to_uval(uc_vm_t *vm, struct uci_option *o) 447 { 448 struct uci_element *e; 449 uc_value_t *arr; 450 451 switch (o->type) { 452 case UCI_TYPE_STRING: 453 return ucv_string_new(o->v.string); 454 455 case UCI_TYPE_LIST: 456 arr = ucv_array_new(vm); 457 458 if (arr) 459 uci_foreach_element(&o->v.list, e) 460 ucv_array_push(arr, ucv_string_new(e->name)); 461 462 return arr; 463 464 default: 465 return NULL; 466 } 467 } 468 469 static uc_value_t * 470 section_to_uval(uc_vm_t *vm, struct uci_section *s, int index) 471 { 472 uc_value_t *so = ucv_object_new(vm); 473 struct uci_element *e; 474 struct uci_option *o; 475 476 if (!so) 477 return NULL; 478 479 ucv_object_add(so, ".anonymous", ucv_boolean_new(s->anonymous)); 480 ucv_object_add(so, ".type", ucv_string_new(s->type)); 481 ucv_object_add(so, ".name", ucv_string_new(s->e.name)); 482 483 if (index >= 0) 484 ucv_object_add(so, ".index", ucv_int64_new(index)); 485 486 uci_foreach_element(&s->options, e) { 487 o = uci_to_option(e); 488 ucv_object_add(so, o->e.name, option_to_uval(vm, o)); 489 } 490 491 return so; 492 } 493 494 static uc_value_t * 495 package_to_uval(uc_vm_t *vm, struct uci_package *p) 496 { 497 uc_value_t *po = ucv_object_new(vm); 498 uc_value_t *so; 499 struct uci_element *e; 500 int i = 0; 501 502 if (!po) 503 return NULL; 504 505 uci_foreach_element(&p->sections, e) { 506 so = section_to_uval(vm, uci_to_section(e), i++); 507 ucv_object_add(po, e->name, so); 508 } 509 510 return po; 511 } 512 513 static uc_value_t * 514 uc_uci_get_any(uc_vm_t *vm, size_t nargs, bool all) 515 { 516 struct uci_context **c = uc_fn_this("uci.cursor"); 517 uc_value_t *conf = uc_fn_arg(0); 518 uc_value_t *sect = uc_fn_arg(1); 519 uc_value_t *opt = uc_fn_arg(2); 520 struct uci_ptr ptr = { 0 }; 521 int rv; 522 523 if (!c || !*c) 524 err_return(UCI_ERR_INVAL); 525 526 if ((ucv_type(conf) != UC_STRING) || 527 (sect && ucv_type(sect) != UC_STRING) || 528 (opt && ucv_type(opt) != UC_STRING)) 529 err_return(UCI_ERR_INVAL); 530 531 if ((!sect && !all) || (opt && all)) 532 err_return(UCI_ERR_INVAL); 533 534 ptr.package = ucv_string_get(conf); 535 ptr.section = sect ? ucv_string_get(sect) : NULL; 536 ptr.option = opt ? ucv_string_get(opt) : NULL; 537 538 rv = lookup_ptr(*c, &ptr, true); 539 540 if (rv != UCI_OK) 541 err_return(rv); 542 543 if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) 544 err_return(UCI_ERR_NOTFOUND); 545 546 if (all) { 547 if (ptr.section) { 548 if (!ptr.s) 549 err_return(UCI_ERR_NOTFOUND); 550 551 return section_to_uval(vm, ptr.s, -1); 552 } 553 554 if (!ptr.p) 555 err_return(UCI_ERR_NOTFOUND); 556 557 return package_to_uval(vm, ptr.p); 558 } 559 560 if (ptr.option) { 561 if (!ptr.o) 562 err_return(UCI_ERR_NOTFOUND); 563 564 return option_to_uval(vm, ptr.o); 565 } 566 567 if (!ptr.s) 568 err_return(UCI_ERR_NOTFOUND); 569 570 return ucv_string_new(ptr.s->type); 571 } 572 573 /** 574 * Query a single option value or section type. 575 * 576 * When invoked with three arguments, the function returns the value of the 577 * given option, within the specified section of the given configuration. 578 * 579 * When invoked with just a config and section argument, the function returns 580 * the type of the specified section. 581 * 582 * In either case, the given configuration is implicitly loaded into the cursor 583 * if not already present. 584 * 585 * Returns the configuration value or section type on success. 586 * 587 * Returns `null` on error, e.g. if the requested configuration does not exist 588 * or if an invalid argument was passed. 589 * 590 * @function module:uci.cursor#get 591 * 592 * @param {string} config 593 * The name of the configuration file to query, e.g. `"system"` to query values 594 * in `/etc/config/system`. 595 * 596 * @param {string} section 597 * The name of the section to query within the configuration. 598 * 599 * @param {string} [option] 600 * The name of the option to query within the section. If omitted, the type of 601 * the section is returned instead. 602 * 603 * @returns {?(string|string[])} 604 * 605 * @example 606 * const ctx = cursor(…); 607 * 608 * // Query an option, extended section notation is supported 609 * ctx.get('system', '@system[0]', 'hostname'); 610 * 611 * // Query a section type (should yield 'interface') 612 * ctx.get('network', 'lan'); 613 */ 614 static uc_value_t * 615 uc_uci_get(uc_vm_t *vm, size_t nargs) 616 { 617 return uc_uci_get_any(vm, nargs, false); 618 } 619 620 /** 621 * Query a complete section or configuration. 622 * 623 * When invoked with two arguments, the function returns all values of the 624 * specified section within the given configuration as dictionary. 625 * 626 * When invoked with just a config argument, the function returns a nested 627 * dictionary of all sections present within the given configuration. 628 * 629 * In either case, the given configuration is implicitly loaded into the cursor 630 * if not already present. 631 * 632 * Returns the section or configuration dictionary on success. 633 * 634 * Returns `null` on error, e.g. if the requested configuration does not exist 635 * or if an invalid argument was passed. 636 * 637 * @function module:uci.cursor#get_all 638 * 639 * @param {string} config 640 * The name of the configuration file to query, e.g. `"system"` to query values 641 * in `/etc/config/system`. 642 * 643 * @param {string} [section] 644 * The name of the section to query within the configuration. If omitted a 645 * nested dictionary containing all section values is returned. 646 * 647 * @returns {?(Object<string, module:uci.cursor.SectionObject>|module:uci.cursor.SectionObject)} 648 * 649 * @example 650 * const ctx = cursor(…); 651 * 652 * // Query all lan interface details 653 * ctx.get_all('network', 'lan'); 654 * 655 * // Dump the entire dhcp configuration 656 * ctx.get_all('dhcp'); 657 */ 658 static uc_value_t * 659 uc_uci_get_all(uc_vm_t *vm, size_t nargs) 660 { 661 return uc_uci_get_any(vm, nargs, true); 662 } 663 664 /** 665 * Query option value or name of first section of given type. 666 * 667 * When invoked with three arguments, the function returns the value of the 668 * given option within the first found section of the specified type in the 669 * given configuration. 670 * 671 * When invoked with just a config and section type argument, the function 672 * returns the name of the first found section of the given type. 673 * 674 * In either case, the given configuration is implicitly loaded into the cursor 675 * if not already present. 676 * 677 * Returns the configuration value or section name on success. 678 * 679 * Returns `null` on error, e.g. if the requested configuration does not exist 680 * or if an invalid argument was passed. 681 * 682 * @function module:uci.cursor#get_first 683 * 684 * @param {string} config 685 * The name of the configuration file to query, e.g. `"system"` to query values 686 * in `/etc/config/system`. 687 * 688 * @param {string} type 689 * The section type to find the first section for within the configuration. 690 * 691 * @param {string} [option] 692 * The name of the option to query within the section. If omitted, the name of 693 * the section is returned instead. 694 * 695 * @returns {?(string|string[])} 696 * 697 * @example 698 * const ctx = cursor(…); 699 * 700 * // Query hostname in first anonymous "system" section of /etc/config/system 701 * ctx.get_first('system', 'system', 'hostname'); 702 * 703 * // Figure out name of first network interface section (usually "loopback") 704 * ctx.get_first('network', 'interface'); 705 */ 706 static uc_value_t * 707 uc_uci_get_first(uc_vm_t *vm, size_t nargs) 708 { 709 struct uci_context **c = uc_fn_this("uci.cursor"); 710 uc_value_t *conf = uc_fn_arg(0); 711 uc_value_t *type = uc_fn_arg(1); 712 uc_value_t *opt = uc_fn_arg(2); 713 struct uci_package *p = NULL; 714 struct uci_section *sc; 715 struct uci_element *e; 716 struct uci_ptr ptr = { 0 }; 717 int rv; 718 719 if (ucv_type(conf) != UC_STRING || 720 ucv_type(type) != UC_STRING || 721 (opt && ucv_type(opt) != UC_STRING)) 722 err_return(UCI_ERR_INVAL); 723 724 uci_foreach_element(&(*c)->root, e) { 725 if (strcmp(e->name, ucv_string_get(conf))) 726 continue; 727 728 p = uci_to_package(e); 729 break; 730 } 731 732 if (!p && uci_load(*c, ucv_string_get(conf), &p)) 733 err_return((*c)->err); 734 735 uci_foreach_element(&p->sections, e) { 736 sc = uci_to_section(e); 737 738 if (strcmp(sc->type, ucv_string_get(type))) 739 continue; 740 741 if (!opt) 742 return ucv_string_new(sc->e.name); 743 744 ptr.package = ucv_string_get(conf); 745 ptr.section = sc->e.name; 746 ptr.option = ucv_string_get(opt); 747 ptr.p = p; 748 ptr.s = sc; 749 750 rv = lookup_ptr(*c, &ptr, false); 751 752 if (rv != UCI_OK) 753 err_return(rv); 754 755 if (!(ptr.flags & UCI_LOOKUP_COMPLETE)) 756 err_return(UCI_ERR_NOTFOUND); 757 758 return option_to_uval(vm, ptr.o); 759 } 760 761 err_return(UCI_ERR_NOTFOUND); 762 } 763 764 /** 765 * Add anonymous section to given configuration. 766 * 767 * Adds a new anonymous (unnamed) section of the specified type to the given 768 * configuration. In order to add a named section, the three argument form of 769 * `set()` should be used instead. 770 * 771 * In contrast to other query functions, `add()` will not implicitly load the 772 * configuration into the cursor. The configuration either needs to be loaded 773 * explicitly through `load()` beforehand, or implicitly by querying it through 774 * one of the `get()`, `get_all()`, `get_first()` or `foreach()` functions. 775 * 776 * Returns the autogenerated, ephemeral name of the added unnamed section 777 * on success. 778 * 779 * Returns `null` on error, e.g. if the targeted configuration was not loaded or 780 * if an invalid section type value was passed. 781 * 782 * @function module:uci.cursor#add 783 * 784 * @param {string} config 785 * The name of the configuration file to add the section to, e.g. `"system"` to 786 * modify `/etc/config/system`. 787 * 788 * @param {string} type 789 * The type value to use for the added section. 790 * 791 * @returns {?string} 792 * 793 * @example 794 * const ctx = cursor(…); 795 * 796 * // Load firewall configuration 797 * ctx.load('firewall'); 798 * 799 * // Add unnamed `config rule` section 800 * const sid = ctx.add('firewall', 'rule'); 801 * 802 * // Set values on the newly added section 803 * ctx.set('firewall', sid, 'name', 'A test'); 804 * ctx.set('firewall', sid, 'target', 'ACCEPT'); 805 * … 806 */ 807 static uc_value_t * 808 uc_uci_add(uc_vm_t *vm, size_t nargs) 809 { 810 struct uci_context **c = uc_fn_this("uci.cursor"); 811 uc_value_t *conf = uc_fn_arg(0); 812 uc_value_t *type = uc_fn_arg(1); 813 struct uci_element *e = NULL; 814 struct uci_package *p = NULL; 815 struct uci_section *sc = NULL; 816 int rv; 817 818 if (ucv_type(conf) != UC_STRING || 819 ucv_type(type) != UC_STRING) 820 err_return(UCI_ERR_INVAL); 821 822 uci_foreach_element(&(*c)->root, e) { 823 if (!strcmp(e->name, ucv_string_get(conf))) { 824 p = uci_to_package(e); 825 break; 826 } 827 } 828 829 if (!p) 830 err_return(UCI_ERR_NOTFOUND); 831 832 rv = uci_add_section(*c, p, ucv_string_get(type), &sc); 833 834 if (rv != UCI_OK) 835 err_return(rv); 836 else if (!sc) 837 err_return(UCI_ERR_NOTFOUND); 838 839 return ucv_string_new(sc->e.name); 840 } 841 842 static bool 843 uval_to_uci(uc_vm_t *vm, uc_value_t *val, const char **p, bool *is_list) 844 { 845 uc_value_t *item; 846 847 *p = NULL; 848 849 if (is_list) 850 *is_list = false; 851 852 switch (ucv_type(val)) { 853 case UC_ARRAY: 854 if (ucv_array_length(val) == 0) 855 return false; 856 857 item = ucv_array_get(val, 0); 858 859 /* don't recurse */ 860 if (ucv_type(item) == UC_ARRAY) 861 return false; 862 863 if (is_list) 864 *is_list = true; 865 866 return uval_to_uci(vm, item, p, NULL); 867 868 case UC_BOOLEAN: 869 *p = xstrdup(ucv_boolean_get(val) ? "1" : ""); 870 871 return true; 872 873 case UC_DOUBLE: 874 case UC_INTEGER: 875 case UC_STRING: 876 *p = ucv_to_string(vm, val); 877 /* fall through */ 878 879 case UC_NULL: 880 return true; 881 882 default: 883 return false; 884 } 885 } 886 887 /** 888 * Set option value or add named section in given configuration. 889 * 890 * When invoked with four arguments, the function sets the value of the given 891 * option within the specified section of the given configuration to the 892 * provided value. A value of `""` (empty string) can be used to delete an 893 * existing option. 894 * 895 * When invoked with three arguments, the function adds a new named section to 896 * the given configuration, using the specified type. 897 * 898 * In either case, the given configuration is implicitly loaded into the cursor 899 * if not already present. 900 * 901 * Returns the `true` if the named section was added or the specified option was 902 * set. 903 * 904 * Returns `null` on error, e.g. if the targeted configuration was not found or 905 * if an invalid value was passed. 906 * 907 * @function module:uci.cursor#set 908 * 909 * @param {string} config 910 * The name of the configuration file to set values in, e.g. `"system"` to 911 * modify `/etc/config/system`. 912 * 913 * @param {string} section 914 * The section name to create or set a value in. 915 * 916 * @param {string} option_or_type 917 * The option name to set within the section or, when the subsequent value 918 * argument is omitted, the type of the section to create within the 919 * configuration. 920 * 921 * @param {(Array<string|boolean|number>|string|boolean|number)} [value] 922 * The option value to set. 923 * 924 * @returns {?boolean} 925 * 926 * @example 927 * const ctx = cursor(…); 928 * 929 * // Add named `config interface guest` section 930 * ctx.set('network', 'guest', 'interface'); 931 * 932 * // Set values on the newly added section 933 * ctx.set('network', 'guest', 'proto', 'static'); 934 * ctx.set('network', 'guest', 'ipaddr', '10.0.0.1/24'); 935 * ctx.set('network', 'guest', 'dns', ['8.8.4.4', '8.8.8.8']); 936 * … 937 * 938 * // Delete 'disabled' option in first wifi-iface section 939 * ctx.set('wireless', '@wifi-iface[0]', 'disabled', ''); 940 */ 941 static uc_value_t * 942 uc_uci_set(uc_vm_t *vm, size_t nargs) 943 { 944 struct uci_context **c = uc_fn_this("uci.cursor"); 945 uc_value_t *conf = uc_fn_arg(0); 946 uc_value_t *sect = uc_fn_arg(1); 947 uc_value_t *opt = NULL, *val = NULL; 948 struct uci_ptr ptr = { 0 }; 949 bool is_list = false; 950 size_t i; 951 int rv; 952 953 if (ucv_type(conf) != UC_STRING || 954 ucv_type(sect) != UC_STRING) 955 err_return(UCI_ERR_INVAL); 956 957 switch (nargs) { 958 /* conf, sect, opt, val */ 959 case 4: 960 opt = uc_fn_arg(2); 961 val = uc_fn_arg(3); 962 963 if (ucv_type(opt) != UC_STRING) 964 err_return(UCI_ERR_INVAL); 965 966 break; 967 968 /* conf, sect, type */ 969 case 3: 970 val = uc_fn_arg(2); 971 972 if (ucv_type(val) != UC_STRING) 973 err_return(UCI_ERR_INVAL); 974 975 break; 976 977 default: 978 err_return(UCI_ERR_INVAL); 979 } 980 981 ptr.package = ucv_string_get(conf); 982 ptr.section = ucv_string_get(sect); 983 ptr.option = opt ? ucv_string_get(opt) : NULL; 984 985 rv = lookup_ptr(*c, &ptr, true); 986 987 if (rv != UCI_OK) 988 err_return(rv); 989 990 if (!ptr.s && ptr.option) 991 err_return(UCI_ERR_NOTFOUND); 992 993 if (!uval_to_uci(vm, val, &ptr.value, &is_list)) 994 err_return(UCI_ERR_INVAL); 995 996 if (is_list) { 997 /* if we got a one-element array, delete existing option (if any) 998 * and iterate array at offset 0 */ 999 if (ucv_array_length(val) == 1) { 1000 i = 0; 1001 1002 free((char *)ptr.value); 1003 ptr.value = NULL; 1004 1005 if (ptr.o) { 1006 rv = uci_delete(*c, &ptr); 1007 1008 if (rv != UCI_OK) 1009 err_return(rv); 1010 } 1011 } 1012 /* if we get a multi element array, overwrite existing option (if any) 1013 * with first value and iterate remaining array at offset 1 */ 1014 else { 1015 i = 1; 1016 1017 rv = uci_set(*c, &ptr); 1018 free((char *)ptr.value); 1019 1020 if (rv != UCI_OK) 1021 err_return(rv); 1022 } 1023 1024 for (; i < ucv_array_length(val); i++) { 1025 if (!uval_to_uci(vm, ucv_array_get(val, i), &ptr.value, NULL)) 1026 continue; 1027 1028 rv = uci_add_list(*c, &ptr); 1029 free((char *)ptr.value); 1030 1031 if (rv != UCI_OK) 1032 err_return(rv); 1033 } 1034 } 1035 else { 1036 rv = uci_set(*c, &ptr); 1037 free((char *)ptr.value); 1038 1039 if (rv != UCI_OK) 1040 err_return(rv); 1041 } 1042 1043 return ucv_boolean_new(true); 1044 } 1045 1046 /** 1047 * Delete an option or section from given configuration. 1048 * 1049 * When invoked with three arguments, the function deletes the given option 1050 * within the specified section of the given configuration. 1051 * 1052 * When invoked with two arguments, the function deletes the entire specified 1053 * section within the given configuration. 1054 * 1055 * In either case, the given configuration is implicitly loaded into the cursor 1056 * if not already present. 1057 * 1058 * Returns the `true` if specified option or section has been deleted. 1059 * 1060 * Returns `null` on error, e.g. if the targeted configuration was not found or 1061 * if an invalid value was passed. 1062 * 1063 * @function module:uci.cursor#delete 1064 * 1065 * @param {string} config 1066 * The name of the configuration file to delete values in, e.g. `"system"` to 1067 * modify `/etc/config/system`. 1068 * 1069 * @param {string} section 1070 * The section name to remove the specified option in or, when the subsequent 1071 * argument is omitted, the section to remove entirely. 1072 * 1073 * @param {string} [option] 1074 * The option name to remove within the section. 1075 * 1076 * @returns {?boolean} 1077 * 1078 * @example 1079 * const ctx = cursor(…); 1080 * 1081 * // Delete 'disabled' option in first wifi-iface section 1082 * ctx.delete('wireless', '@wifi-iface[0]', 'disabled'); 1083 * 1084 * // Delete 'wan' interface 1085 * ctx.delete('network', 'lan'); 1086 * 1087 * // Delete last firewall rule 1088 * ctx.delete('firewall', '@rule[-1]'); 1089 */ 1090 static uc_value_t * 1091 uc_uci_delete(uc_vm_t *vm, size_t nargs) 1092 { 1093 struct uci_context **c = uc_fn_this("uci.cursor"); 1094 uc_value_t *conf = uc_fn_arg(0); 1095 uc_value_t *sect = uc_fn_arg(1); 1096 uc_value_t *opt = uc_fn_arg(2); 1097 struct uci_ptr ptr = { 0 }; 1098 int rv; 1099 1100 if (ucv_type(conf) != UC_STRING || 1101 ucv_type(sect) != UC_STRING || 1102 (opt && ucv_type(opt) != UC_STRING)) 1103 err_return(UCI_ERR_INVAL); 1104 1105 ptr.package = ucv_string_get(conf); 1106 ptr.section = ucv_string_get(sect); 1107 ptr.option = opt ? ucv_string_get(opt) : NULL; 1108 1109 rv = lookup_ptr(*c, &ptr, true); 1110 1111 if (rv != UCI_OK) 1112 err_return(rv); 1113 1114 if (opt ? !ptr.o : !ptr.s) 1115 err_return(UCI_ERR_NOTFOUND); 1116 1117 rv = uci_delete(*c, &ptr); 1118 1119 if (rv != UCI_OK) 1120 err_return(rv); 1121 1122 return ucv_boolean_new(true); 1123 } 1124 1125 /** 1126 * Rename an option or section in given configuration. 1127 * 1128 * When invoked with four arguments, the function renames the given option 1129 * within the specified section of the given configuration to the provided 1130 * value. 1131 * 1132 * When invoked with three arguments, the function renames the entire specified 1133 * section to the provided value. 1134 * 1135 * In either case, the given configuration is implicitly loaded into the cursor 1136 * if not already present. 1137 * 1138 * Returns the `true` if specified option or section has been renamed. 1139 * 1140 * Returns `null` on error, e.g. if the targeted configuration was not found or 1141 * if an invalid value was passed. 1142 * 1143 * @function module:uci.cursor#rename 1144 * 1145 * @param {string} config 1146 * The name of the configuration file to rename values in, e.g. `"system"` to 1147 * modify `/etc/config/system`. 1148 * 1149 * @param {string} section 1150 * The section name to rename or to rename an option in. 1151 * 1152 * @param {string} option_or_name 1153 * The option name to rename within the section or, when the subsequent name 1154 * argument is omitted, the new name of the renamed section within the 1155 * configuration. 1156 * 1157 * @param {string} [name] 1158 * The new name of the option to rename. 1159 * 1160 * @returns {?boolean} 1161 * 1162 * @example 1163 * const ctx = cursor(…); 1164 * 1165 * // Assign explicit name to last anonymous firewall rule section 1166 * ctx.rename('firewall', '@rule[-1]', 'my_block_rule'); 1167 * 1168 * // Rename 'server' to 'orig_server_list' in ntp section of system config 1169 * ctx.rename('system', 'ntp', 'server', 'orig_server_list'); 1170 * 1171 * // Rename 'wan' interface to 'external' 1172 * ctx.rename('network', 'wan', 'external'); 1173 */ 1174 static uc_value_t * 1175 uc_uci_rename(uc_vm_t *vm, size_t nargs) 1176 { 1177 struct uci_context **c = uc_fn_this("uci.cursor"); 1178 uc_value_t *conf = uc_fn_arg(0); 1179 uc_value_t *sect = uc_fn_arg(1); 1180 uc_value_t *opt = NULL, *val = NULL; 1181 struct uci_ptr ptr = { 0 }; 1182 int rv; 1183 1184 if (ucv_type(conf) != UC_STRING || 1185 ucv_type(sect) != UC_STRING) 1186 err_return(UCI_ERR_INVAL); 1187 1188 switch (nargs) { 1189 /* conf, sect, opt, val */ 1190 case 4: 1191 opt = uc_fn_arg(2); 1192 val = uc_fn_arg(3); 1193 1194 if (ucv_type(opt) != UC_STRING || 1195 ucv_type(val) != UC_STRING) 1196 err_return(UCI_ERR_INVAL); 1197 1198 break; 1199 1200 /* conf, sect, type */ 1201 case 3: 1202 val = uc_fn_arg(2); 1203 1204 if (ucv_type(val) != UC_STRING) 1205 err_return(UCI_ERR_INVAL); 1206 1207 break; 1208 1209 default: 1210 err_return(UCI_ERR_INVAL); 1211 } 1212 1213 ptr.package = ucv_string_get(conf); 1214 ptr.section = ucv_string_get(sect); 1215 ptr.option = opt ? ucv_string_get(opt) : NULL; 1216 ptr.value = ucv_string_get(val); 1217 1218 rv = lookup_ptr(*c, &ptr, true); 1219 1220 if (rv != UCI_OK) 1221 err_return(rv); 1222 1223 if (!ptr.s && ptr.option) 1224 err_return(UCI_ERR_NOTFOUND); 1225 1226 rv = uci_rename(*c, &ptr); 1227 1228 if (rv != UCI_OK) 1229 err_return(rv); 1230 1231 return ucv_boolean_new(true); 1232 } 1233 1234 /** 1235 * Reorder sections in given configuration. 1236 * 1237 * The `reorder()` function moves a single section by repositioning it to the 1238 * given index within the configurations section list. 1239 * 1240 * The given configuration is implicitly loaded into the cursor if not already 1241 * present. 1242 * 1243 * Returns the `true` if specified section has been moved. 1244 * 1245 * Returns `null` on error, e.g. if the targeted configuration was not found or 1246 * if an invalid value was passed. 1247 * 1248 * @function module:uci.cursor#reorder 1249 * 1250 * @param {string} config 1251 * The name of the configuration file to move the section in, e.g. `"system"` to 1252 * modify `/etc/config/system`. 1253 * 1254 * @param {string} section 1255 * The section name to move. 1256 * 1257 * @param {number} index 1258 * The target index to move the section to, starting from `0`. 1259 * 1260 * @param {string} [name] 1261 * The new name of the option to rename. 1262 * 1263 * @returns {?boolean} 1264 * 1265 * @example 1266 * const ctx = cursor(…); 1267 * 1268 * // Query whole firewall config and reorder resulting dict by type and name 1269 * const type_order = ['defaults', 'zone', 'forwarding', 'redirect', 'rule']; 1270 * const values = ctx.get_all('firewall'); 1271 * 1272 * sort(values, (k1, k2, s1, s2) => { 1273 * // Get weight from type_order array 1274 * let w1 = index(type_order, s1['.type']); 1275 * let w2 = index(type_order, s2['.type']); 1276 * 1277 * // For unknown type orders, use type value itself as weight 1278 * if (w1 == -1) w1 = s1['.type']; 1279 * if (w2 == -1) w2 = s2['.type']; 1280 * 1281 * // Get name from name option, fallback to section name 1282 * let n1 = s1.name ?? k1; 1283 * let n2 = s2.name ?? k2; 1284 * 1285 * // Order by weight 1286 * if (w1 < w2) return -1; 1287 * if (w1 > w2) return 1; 1288 * 1289 * // For same weight order by name 1290 * if (n1 < n2) return -1; 1291 * if (n1 > n2) return 1; 1292 * 1293 * return 0; 1294 * }); 1295 * 1296 * // Sequentially reorder sorted sections in firewall configuration 1297 * let position = 0; 1298 * 1299 * for (let sid in values) 1300 * ctx.reorder('firewall', sid, position++); 1301 */ 1302 static uc_value_t * 1303 uc_uci_reorder(uc_vm_t *vm, size_t nargs) 1304 { 1305 struct uci_context **c = uc_fn_this("uci.cursor"); 1306 uc_value_t *conf = uc_fn_arg(0); 1307 uc_value_t *sect = uc_fn_arg(1); 1308 uc_value_t *val = uc_fn_arg(2); 1309 struct uci_ptr ptr = { 0 }; 1310 int64_t n; 1311 int rv; 1312 1313 if (ucv_type(conf) != UC_STRING || 1314 ucv_type(sect) != UC_STRING || 1315 ucv_type(val) != UC_INTEGER) 1316 err_return(UCI_ERR_INVAL); 1317 1318 n = ucv_int64_get(val); 1319 1320 if (n < 0) 1321 err_return(UCI_ERR_INVAL); 1322 1323 ptr.package = ucv_string_get(conf); 1324 ptr.section = ucv_string_get(sect); 1325 1326 rv = lookup_ptr(*c, &ptr, true); 1327 1328 if (rv != UCI_OK) 1329 err_return(rv); 1330 1331 if (!ptr.s) 1332 err_return(UCI_ERR_NOTFOUND); 1333 1334 rv = uci_reorder_section(*c, ptr.s, n); 1335 1336 if (rv != UCI_OK) 1337 err_return(rv); 1338 1339 return ucv_boolean_new(true); 1340 } 1341 1342 static int 1343 uc_uci_pkg_command_single(struct uci_context *ctx, enum pkg_cmd cmd, 1344 struct uci_package *pkg) 1345 { 1346 struct uci_ptr ptr = { 0 }; 1347 1348 switch (cmd) { 1349 case CMD_COMMIT: 1350 return uci_commit(ctx, &pkg, false); 1351 1352 case CMD_SAVE: 1353 return uci_save(ctx, pkg); 1354 1355 case CMD_REVERT: 1356 ptr.p = pkg; 1357 1358 return uci_revert(ctx, &ptr); 1359 1360 default: 1361 return UCI_ERR_INVAL; 1362 } 1363 } 1364 1365 static uc_value_t * 1366 uc_uci_pkg_command(uc_vm_t *vm, size_t nargs, enum pkg_cmd cmd) 1367 { 1368 struct uci_context **c = uc_fn_this("uci.cursor"); 1369 uc_value_t *conf = uc_fn_arg(0); 1370 struct uci_package *p; 1371 char **configs = NULL; 1372 int rv, res = UCI_OK; 1373 size_t i; 1374 1375 if (conf) { 1376 if (ucv_type(conf) != UC_STRING) 1377 err_return(UCI_ERR_INVAL); 1378 1379 if (!(p = uci_lookup_package(*c, ucv_string_get(conf)))) 1380 err_return(UCI_ERR_NOTFOUND); 1381 1382 res = uc_uci_pkg_command_single(*c, cmd, p); 1383 } 1384 else { 1385 if (uci_list_configs(*c, &configs)) 1386 err_return((*c)->err); 1387 1388 if (!configs || !configs[0]) { 1389 free(configs); 1390 err_return(UCI_ERR_NOTFOUND); 1391 } 1392 1393 for (i = 0; configs[i]; i++) { 1394 if (!(p = uci_lookup_package(*c, configs[i]))) 1395 continue; 1396 1397 rv = uc_uci_pkg_command_single(*c, cmd, p); 1398 1399 if (rv != UCI_OK) 1400 res = rv; 1401 } 1402 1403 free(configs); 1404 } 1405 1406 if (res != UCI_OK) 1407 err_return(res); 1408 1409 return ucv_boolean_new(true); 1410 } 1411 1412 /** 1413 * Save accumulated cursor changes to delta directory. 1414 * 1415 * The `save()` function writes consolidated changes made to in-memory copies of 1416 * loaded configuration files to the uci delta directory which effectively makes 1417 * them available to other processes using the same delta directory path as well 1418 * as the `uci changes` cli command when using the default delta directory. 1419 * 1420 * Note that uci deltas are overlayed over the actual configuration file values 1421 * so they're reflected by `get()`, `foreach()` etc. even if the underlying 1422 * configuration files are not actually changed (yet). The delta records may be 1423 * either permanently merged into the configuration by invoking `commit()` or 1424 * reverted through `revert()` in order to restore the current state of the 1425 * underlying configuration file. 1426 * 1427 * When the optional "config" parameter is omitted, delta records for all 1428 * currently loaded configuration files are written. 1429 * 1430 * In case that neither sharing changes with other processes nor any revert 1431 * functionality is required, changes may be committed directly using `commit()` 1432 * instead, bypassing any delta record creation. 1433 * 1434 * Returns the `true` if operation completed successfully. 1435 * 1436 * Returns `null` on error, e.g. if the requested configuration was not loaded 1437 * or when a file system error occurred. 1438 * 1439 * @function module:uci.cursor#save 1440 * 1441 * @param {string} [config] 1442 * The name of the configuration file to save delta records for, e.g. `"system"` 1443 * to store changes for `/etc/config/system`. 1444 * 1445 * @returns {?boolean} 1446 * 1447 * @example 1448 * const ctx = cursor(…); 1449 * 1450 * ctx.set('wireless', '@wifi-iface[0]', 'disabled', '1'); 1451 * ctx.save('wireless'); 1452 * 1453 * @see {@link module:uci.cursor#commit|commit()} 1454 * @see {@link module:uci.cursor#revert|revert()} 1455 */ 1456 static uc_value_t * 1457 uc_uci_save(uc_vm_t *vm, size_t nargs) 1458 { 1459 return uc_uci_pkg_command(vm, nargs, CMD_SAVE); 1460 } 1461 1462 /** 1463 * Update configuration files with accumulated cursor changes. 1464 * 1465 * The `commit()` function merges changes made to in-memory copies of loaded 1466 * configuration files as well as existing delta records in the cursors 1467 * configured delta directory and writes them back into the underlying 1468 * configuration files, persistently committing changes to the file system. 1469 * 1470 * When the optional "config" parameter is omitted, all currently loaded 1471 * configuration files with either present delta records or yet unsaved 1472 * cursor changes are updated. 1473 * 1474 * Returns the `true` if operation completed successfully. 1475 * 1476 * Returns `null` on error, e.g. if the requested configuration was not loaded 1477 * or when a file system error occurred. 1478 * 1479 * @function module:uci.cursor#commit 1480 * 1481 * @param {string} [config] 1482 * The name of the configuration file to commit, e.g. `"system"` to update the 1483 * `/etc/config/system` file. 1484 * 1485 * @returns {?boolean} 1486 * 1487 * @example 1488 * const ctx = cursor(…); 1489 * 1490 * ctx.set('system', '@system[0]', 'hostname', 'example.org'); 1491 * ctx.commit('system'); 1492 */ 1493 static uc_value_t * 1494 uc_uci_commit(uc_vm_t *vm, size_t nargs) 1495 { 1496 return uc_uci_pkg_command(vm, nargs, CMD_COMMIT); 1497 } 1498 1499 /** 1500 * Revert accumulated cursor changes and associated delta records. 1501 * 1502 * The `revert()` function discards any changes made to in-memory copies of 1503 * loaded configuration files and discards any related existing delta records in 1504 * the cursors configured delta directory. 1505 * 1506 * When the optional "config" parameter is omitted, all currently loaded 1507 * configuration files with either present delta records or yet unsaved 1508 * cursor changes are reverted. 1509 * 1510 * Returns the `true` if operation completed successfully. 1511 * 1512 * Returns `null` on error, e.g. if the requested configuration was not loaded 1513 * or when a file system error occurred. 1514 * 1515 * @function module:uci.cursor#revert 1516 * 1517 * @param {string} [config] 1518 * The name of the configuration file to revert, e.g. `"system"` to discard any 1519 * changes for the `/etc/config/system` file. 1520 * 1521 * @returns {?boolean} 1522 * 1523 * @example 1524 * const ctx = cursor(…); 1525 * 1526 * ctx.set('system', '@system[0]', 'hostname', 'example.org'); 1527 * ctx.revert('system'); 1528 * 1529 * @see {@link module:uci.cursor#save|save()} 1530 */ 1531 static uc_value_t * 1532 uc_uci_revert(uc_vm_t *vm, size_t nargs) 1533 { 1534 return uc_uci_pkg_command(vm, nargs, CMD_REVERT); 1535 } 1536 1537 static uc_value_t * 1538 change_to_uval(uc_vm_t *vm, struct uci_delta *d) 1539 { 1540 const char *types[] = { 1541 [UCI_CMD_REORDER] = "order", 1542 [UCI_CMD_REMOVE] = "remove", 1543 [UCI_CMD_RENAME] = "rename", 1544 [UCI_CMD_ADD] = "add", 1545 [UCI_CMD_LIST_ADD] = "list-add", 1546 [UCI_CMD_LIST_DEL] = "list-del", 1547 [UCI_CMD_CHANGE] = "set", 1548 }; 1549 1550 uc_value_t *a; 1551 1552 if (!d->section) 1553 return NULL; 1554 1555 a = ucv_array_new(vm); 1556 1557 if (!a) 1558 return NULL; 1559 1560 ucv_array_push(a, ucv_string_new(types[d->cmd])); 1561 ucv_array_push(a, ucv_string_new(d->section)); 1562 1563 if (d->e.name) 1564 ucv_array_push(a, ucv_string_new(d->e.name)); 1565 1566 if (d->value) { 1567 if (d->cmd == UCI_CMD_REORDER) 1568 ucv_array_push(a, ucv_int64_new(strtoul(d->value, NULL, 10))); 1569 else 1570 ucv_array_push(a, ucv_string_new(d->value)); 1571 } 1572 1573 return a; 1574 } 1575 1576 static uc_value_t * 1577 changes_to_uval(uc_vm_t *vm, struct uci_context *ctx, const char *package) 1578 { 1579 uc_value_t *a = NULL, *c; 1580 struct uci_package *p = NULL; 1581 struct uci_element *e; 1582 bool unload = false; 1583 1584 uci_foreach_element(&ctx->root, e) { 1585 if (strcmp(e->name, package)) 1586 continue; 1587 1588 p = uci_to_package(e); 1589 } 1590 1591 if (!p) { 1592 unload = true; 1593 uci_load(ctx, package, &p); 1594 } 1595 1596 if (!p) 1597 return NULL; 1598 1599 if (!uci_list_empty(&p->delta) || !uci_list_empty(&p->saved_delta)) { 1600 a = ucv_array_new(vm); 1601 1602 if (!a) 1603 err_return(UCI_ERR_MEM); 1604 1605 uci_foreach_element(&p->saved_delta, e) { 1606 c = change_to_uval(vm, uci_to_delta(e)); 1607 1608 if (c) 1609 ucv_array_push(a, c); 1610 } 1611 1612 uci_foreach_element(&p->delta, e) { 1613 c = change_to_uval(vm, uci_to_delta(e)); 1614 1615 if (c) 1616 ucv_array_push(a, c); 1617 } 1618 } 1619 1620 if (unload) 1621 uci_unload(ctx, p); 1622 1623 return a; 1624 } 1625 1626 /** 1627 * Enumerate pending changes. 1628 * 1629 * The `changes()` function returns a list of change records for currently 1630 * loaded configuration files, originating both from the cursors associated 1631 * delta directory and yet unsaved cursor changes. 1632 * 1633 * When the optional "config" parameter is specified, the requested 1634 * configuration is implicitly loaded if it not already loaded into the cursor. 1635 * 1636 * Returns a dictionary of change record arrays, keyed by configuration name. 1637 * 1638 * Returns `null` on error, e.g. if the requested configuration could not be 1639 * loaded. 1640 * 1641 * @function module:uci.cursor#changes 1642 * 1643 * @param {string} [config] 1644 * The name of the configuration file to enumerate changes for, e.g. `"system"` 1645 * to query pending changes for the `/etc/config/system` file. 1646 * 1647 * @returns {?Object<string, module:uci.cursor.ChangeRecord[]>} 1648 * 1649 * @example 1650 * const ctx = cursor(…); 1651 * 1652 * // Enumerate changes for all currently loaded configurations 1653 * const deltas = ctx.changes(); 1654 * 1655 * // Explicitly load and enumerate changes for the "system" configuration 1656 * const deltas = ctx.changes('system'); 1657 */ 1658 static uc_value_t * 1659 uc_uci_changes(uc_vm_t *vm, size_t nargs) 1660 { 1661 struct uci_context **c = uc_fn_this("uci.cursor"); 1662 uc_value_t *conf = uc_fn_arg(0); 1663 uc_value_t *res, *chg; 1664 char **configs; 1665 int rv, i; 1666 1667 if (conf && ucv_type(conf) != UC_STRING) 1668 err_return(UCI_ERR_INVAL); 1669 1670 rv = uci_list_configs(*c, &configs); 1671 1672 if (rv != UCI_OK) 1673 err_return(rv); 1674 1675 res = ucv_object_new(vm); 1676 1677 for (i = 0; configs[i]; i++) { 1678 if (conf && strcmp(configs[i], ucv_string_get(conf))) 1679 continue; 1680 1681 chg = changes_to_uval(vm, *c, configs[i]); 1682 1683 if (chg) 1684 ucv_object_add(res, configs[i], chg); 1685 } 1686 1687 free(configs); 1688 1689 return res; 1690 } 1691 1692 /** 1693 * Iterate configuration sections. 1694 * 1695 * The `foreach()` function iterates all sections of the given configuration, 1696 * optionally filtered by type, and invokes the given callback function for 1697 * each encountered section. 1698 * 1699 * When the optional "type" parameter is specified, the callback is only invoked 1700 * for sections of the given type, otherwise it is invoked for all sections. 1701 * 1702 * The requested configuration is implicitly loaded into the cursor. 1703 * 1704 * Returns `true` if the callback was executed successfully at least once. 1705 * 1706 * Returns `false` if the callback was never invoked, e.g. when the 1707 * configuration is empty or contains no sections of the given type. 1708 * 1709 * Returns `null` on error, e.g. when an invalid callback was passed or the 1710 * requested configuration not found. 1711 * 1712 * @function module:uci.cursor#foreach 1713 * 1714 * @param {string} config 1715 * The configuration to iterate sections for, e.g. `"system"` to read the 1716 * `/etc/config/system` file. 1717 * 1718 * @param {?string} type 1719 * Invoke the callback only for sections of the specified type. 1720 * 1721 * @param {module:uci.cursor.SectionCallback} callback 1722 * The callback to invoke for each section, will receive a section dictionary 1723 * as sole argument. 1724 * 1725 * @returns {?boolean} 1726 * 1727 * @example 1728 * const ctx = cursor(…); 1729 * 1730 * // Iterate all network interfaces 1731 * ctx.foreach('network', 'interface', 1732 * section => print(`Have interface ${section[".name"]}\n`)); 1733 */ 1734 static uc_value_t * 1735 uc_uci_foreach(uc_vm_t *vm, size_t nargs) 1736 { 1737 struct uci_context **c = uc_fn_this("uci.cursor"); 1738 uc_value_t *conf = uc_fn_arg(0); 1739 uc_value_t *type = uc_fn_arg(1); 1740 uc_value_t *func = uc_fn_arg(2); 1741 uc_value_t *rv = NULL; 1742 struct uci_package *p = NULL; 1743 struct uci_element *e, *tmp; 1744 struct uci_section *sc; 1745 uc_exception_type_t ex; 1746 bool stop = false; 1747 bool ret = false; 1748 int i = 0; 1749 1750 if (ucv_type(conf) != UC_STRING || 1751 (type && ucv_type(type) != UC_STRING)) 1752 err_return(UCI_ERR_INVAL); 1753 1754 uci_foreach_element(&(*c)->root, e) { 1755 if (strcmp(e->name, ucv_string_get(conf))) 1756 continue; 1757 1758 p = uci_to_package(e); 1759 break; 1760 } 1761 1762 if (!p && uci_load(*c, ucv_string_get(conf), &p)) 1763 err_return((*c)->err); 1764 1765 uci_foreach_element_safe(&p->sections, tmp, e) { 1766 sc = uci_to_section(e); 1767 i++; 1768 1769 if (type && strcmp(sc->type, ucv_string_get(type))) 1770 continue; 1771 1772 uc_value_push(ucv_get(func)); 1773 uc_value_push(section_to_uval(vm, sc, i - 1)); 1774 1775 ex = uc_call(1); 1776 1777 /* stop on exception in callback */ 1778 if (ex) 1779 break; 1780 1781 ret = true; 1782 rv = uc_value_pop(); 1783 stop = (ucv_type(rv) == UC_BOOLEAN && !ucv_boolean_get(rv)); 1784 1785 ucv_put(rv); 1786 1787 if (stop) 1788 break; 1789 } 1790 1791 return ucv_boolean_new(ret); 1792 } 1793 1794 /** 1795 * Enumerate existing configurations. 1796 * 1797 * The `configs()` function yields an array of configuration files present in 1798 * the cursors associated configuration directory, `/etc/config/` by default. 1799 * 1800 * Returns an array of configuration names on success. 1801 * 1802 * Returns `null` on error, e.g. due to filesystem errors. 1803 * 1804 * @function module:uci.cursor#configs 1805 * 1806 * @returns {?string[]} 1807 * 1808 * @example 1809 * const ctx = cursor(…); 1810 * 1811 * // Enumerate all present configuration file names 1812 * const configurations = ctx.configs(); 1813 */ 1814 static uc_value_t * 1815 uc_uci_configs(uc_vm_t *vm, size_t nargs) 1816 { 1817 struct uci_context **c = uc_fn_this("uci.cursor"); 1818 uc_value_t *a; 1819 char **configs; 1820 int i, rv; 1821 1822 rv = uci_list_configs(*c, &configs); 1823 1824 if (rv != UCI_OK) 1825 err_return(rv); 1826 1827 a = ucv_array_new(vm); 1828 1829 for (i = 0; configs[i]; i++) 1830 ucv_array_push(a, ucv_string_new(configs[i])); 1831 1832 free(configs); 1833 1834 return a; 1835 } 1836 1837 1838 static const uc_function_list_t cursor_fns[] = { 1839 { "load", uc_uci_load }, 1840 { "unload", uc_uci_unload }, 1841 { "get", uc_uci_get }, 1842 { "get_all", uc_uci_get_all }, 1843 { "get_first", uc_uci_get_first }, 1844 { "add", uc_uci_add }, 1845 { "set", uc_uci_set }, 1846 { "rename", uc_uci_rename }, 1847 { "save", uc_uci_save }, 1848 { "delete", uc_uci_delete }, 1849 { "commit", uc_uci_commit }, 1850 { "revert", uc_uci_revert }, 1851 { "reorder", uc_uci_reorder }, 1852 { "changes", uc_uci_changes }, 1853 { "foreach", uc_uci_foreach }, 1854 { "configs", uc_uci_configs }, 1855 { "error", uc_uci_error }, 1856 }; 1857 1858 static const uc_function_list_t global_fns[] = { 1859 { "error", uc_uci_error }, 1860 { "cursor", uc_uci_cursor }, 1861 }; 1862 1863 1864 static void close_uci(void *ud) { 1865 uci_free_context((struct uci_context *)ud); 1866 } 1867 1868 void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 1869 { 1870 uc_function_list_register(scope, global_fns); 1871 1872 cursor_type = uc_type_declare(vm, "uci.cursor", cursor_fns, close_uci); 1873 } 1874
This page was automatically generated by LXR 0.3.1. • OpenWrt