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