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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt