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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt