• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/uci.c

  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