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

This page was automatically generated by LXR 0.3.1.  •  OpenWrt