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