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