diff options
author | Jo-Philipp Wich <jo@mein.io> | 2023-10-11 16:50:35 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-11 16:50:35 +0200 |
commit | 2b2a47240c089a2cb10223efd4a1f133638f5dd5 (patch) | |
tree | 8aca5fedc9c5584e7d66ce9774a663ea2de073bb /lib | |
parent | f56394f0fc53e5c22cbe52cac0506d9fbef33cb3 (diff) | |
parent | 1c1899301a4452582ade76bb5249eaac71cceb22 (diff) |
Merge pull request #175 from jow-/docs-improvements
Various documentation improvements
Diffstat (limited to 'lib')
-rw-r--r-- | lib/debug.c | 14 | ||||
-rw-r--r-- | lib/fs.c | 150 | ||||
-rw-r--r-- | lib/uci.c | 842 |
3 files changed, 897 insertions, 109 deletions
diff --git a/lib/debug.c b/lib/debug.c index 40b044e..0b227d9 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -667,7 +667,7 @@ debug_setup(uc_vm_t *vm) * @param {string|module:fs.file|module:fs.proc} file * The file path or open file handle to write report to. * - * @return {boolean|null} + * @return {?boolean} */ static uc_value_t * uc_memdump(uc_vm_t *vm, size_t nargs) @@ -818,7 +818,7 @@ uc_traceback(uc_vm_t *vm, size_t nargs) * * @function module:debug#sourcepos * - * @return {module:debug.SourcePosition|null} + * @return {?module:debug.SourcePosition} */ /** @@ -955,7 +955,7 @@ uc_getinfo_upvals(uc_vm_t *vm, uc_closure_t *closure) * @param {*} value * The value to query information for. * - * @return {module:debug.ValueInformation|null} + * @return {?module:debug.ValueInformation} */ /** @@ -1362,7 +1362,7 @@ uc_xlocal(uc_vm_t *vm, uc_value_t *level, uc_value_t *var, uc_value_t **set) * @param {string|number} variable * The variable index or variable name to obtain information for. * - * @returns {module:debug.LocalInfo|null} + * @returns {?module:debug.LocalInfo} */ static uc_value_t * uc_getlocal(uc_vm_t *vm, size_t nargs) @@ -1406,7 +1406,7 @@ uc_getlocal(uc_vm_t *vm, size_t nargs) * @param {*} [value=null] * The value to set the local variable to. * - * @returns {module:debug.LocalInfo|null} + * @returns {?module:debug.LocalInfo} */ static uc_value_t * uc_setlocal(uc_vm_t *vm, size_t nargs) @@ -1552,7 +1552,7 @@ uc_xupval(uc_vm_t *vm, uc_value_t *target, uc_value_t *var, uc_value_t **set) * @param {string|number} variable * The variable index or variable name to obtain information for. * - * @returns {module:debug.UpvalInfo|null} + * @returns {?module:debug.UpvalInfo} */ static uc_value_t * uc_getupval(uc_vm_t *vm, size_t nargs) @@ -1601,7 +1601,7 @@ uc_getupval(uc_vm_t *vm, size_t nargs) * @param {*} value * The value to set the variable to. * - * @returns {module:debug.UpvalInfo|null} + * @returns {?module:debug.UpvalInfo} */ static uc_value_t * uc_setupval(uc_vm_t *vm, size_t nargs) @@ -79,7 +79,7 @@ static int last_error = 0; * @function module:fs#error * * - * @returns {string|null} + * @returns {?string} * * @example * // Trigger file system error @@ -258,6 +258,8 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type) * @class module:fs.proc * @hideconstructor * + * @borrows module:fs#error as module:fs.proc#error + * * @see {@link module:fs#popen|popen()} * * @example @@ -276,26 +278,6 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type) */ /** - * Query error information. - * - * Returns a string containing a description of the last occurred error or - * `null` if there is no error information. - * - * @function module:fs.proc#error - * - * @returns {string|null} - * - * @example - * // Trigger error - * const fp = popen("command"); - * fp.close(); - * fp.close(); // already closed - * - * // Print error (should yield "Bad file descriptor") - * print(fp.error(), "\n"); - */ - -/** * Closes the program handle and awaits program termination. * * Upon calling `close()` on the handle, the program's input or output stream @@ -316,7 +298,7 @@ uc_fs_fileno_common(uc_vm_t *vm, size_t nargs, const char *type) * * @function module:fs.proc#close * - * @returns {number|null} + * @returns {?number} */ static uc_value_t * uc_fs_pclose(uc_vm_t *vm, size_t nargs) @@ -378,7 +360,7 @@ uc_fs_pclose(uc_vm_t *vm, size_t nargs) * The length of data to read. Can be a number, the string "line", the string * "all", or a single character string. * - * @returns {string|null} + * @returns {?string} * * @example * const fp = popen("command", "r"); @@ -426,7 +408,7 @@ uc_fs_pread(uc_vm_t *vm, size_t nargs) * @param {*} data * The data to be written. * - * @returns {number|null} + * @returns {?number} * * @example * const fp = popen("command", "w"); @@ -448,7 +430,7 @@ uc_fs_pwrite(uc_vm_t *vm, size_t nargs) * * @function module:fs.proc#flush * - * @returns {boolean|null} + * @returns {?boolean} * */ static uc_value_t * @@ -466,7 +448,7 @@ uc_fs_pflush(uc_vm_t *vm, size_t nargs) * * @function module:fs.proc#fileno * - * @returns {number|null} + * @returns {?number} */ static uc_value_t * uc_fs_pfileno(uc_vm_t *vm, size_t nargs) @@ -498,7 +480,7 @@ uc_fs_pfileno(uc_vm_t *vm, size_t nargs) * @param {string} [mode="r"] * The open mode of the process handle. * - * @returns {module:fs.proc|null} + * @returns {?module:fs.proc} * * @example * // Open a process @@ -531,6 +513,8 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs) * @class module:fs.file * @hideconstructor * + * @borrows module:fs#error as module:fs.file#error + * * @see {@link module:fs#open|open()} * @see {@link module:fs#fdopen|fdopen()} * @see {@link module:fs#mkstemp|mkstemp()} @@ -556,26 +540,6 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs) */ /** - * Query error information. - * - * Returns a string containing a description of the last occurred error or - * `null` if there is no error information. - * - * @function module:fs.file#error - * - * @returns {string|null} - * - * @example - * // Trigger error - * const fp = open("file.txt"); - * fp.close(); - * fp.close(); // already closed - * - * // Print error (should yield "Bad file descriptor") - * print(fp.error(), "\n"); - */ - -/** * Closes the file handle. * * Upon calling `close()` on the handle, buffered data is flushed and the @@ -587,7 +551,7 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs) * * @function module:fs.file#close * - * @returns {boolean|null} + * @returns {?boolean} */ static uc_value_t * uc_fs_close(uc_vm_t *vm, size_t nargs) @@ -639,7 +603,7 @@ uc_fs_close(uc_vm_t *vm, size_t nargs) * The length of data to read. Can be a number, the string "line", the string * "all", or a single character string. * - * @returns {string|null} + * @returns {?string} * * @example * const fp = open("file.txt", "r"); @@ -687,7 +651,7 @@ uc_fs_read(uc_vm_t *vm, size_t nargs) * @param {*} data * The data to be written. * - * @returns {number|null} + * @returns {?number} * * @example * const fp = open("file.txt", "w"); @@ -724,7 +688,7 @@ uc_fs_write(uc_vm_t *vm, size_t nargs) * | `1` | The given offset is relative to the current read position. | * | `2` | The given offset is relative to the end of the file. | * - * @returns {boolean|null} + * @returns {?boolean} * * @example * const fp = open("file.txt", "r"); @@ -785,7 +749,7 @@ uc_fs_seek(uc_vm_t *vm, size_t nargs) * * @function module:fs.file#tell * - * @returns {number|null} + * @returns {?number} */ static uc_value_t * uc_fs_tell(uc_vm_t *vm, size_t nargs) @@ -818,7 +782,7 @@ uc_fs_tell(uc_vm_t *vm, size_t nargs) * * @function module:fs.file#isatty * - * @returns {boolean|null} + * @returns {?boolean} * */ static uc_value_t * @@ -847,7 +811,7 @@ uc_fs_isatty(uc_vm_t *vm, size_t nargs) * * @function module:fs.file#flush * - * @returns {boolean|null} + * @returns {?boolean} * */ static uc_value_t * @@ -865,7 +829,7 @@ uc_fs_flush(uc_vm_t *vm, size_t nargs) * * @function module:fs.file#fileno * - * @returns {number|null} + * @returns {?number} */ static uc_value_t * uc_fs_fileno(uc_vm_t *vm, size_t nargs) @@ -913,7 +877,7 @@ uc_fs_fileno(uc_vm_t *vm, size_t nargs) * @param {number} [perm=0o666] * The file creation permissions (for modes `w…` and `a…`) * - * @returns {module:fs.file|null} + * @returns {?module:fs.file} * * @example * // Open a file in read-only mode @@ -1058,6 +1022,8 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs) * @class module:fs.dir * @hideconstructor * + * @borrows module:fs#error as module:fs.dir#error + * * @see {@link module:fs#opendir|opendir()} * * @example @@ -1075,26 +1041,6 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs) */ /** - * Query error information. - * - * Returns a string containing a description of the last occurred error or - * `null` if there is no error information. - * - * @function module:fs.dir#error - * - * @returns {string|null} - * - * @example - * // Trigger error - * const fp = opendir("/tmp"); - * fp.close(); - * fp.close(); // already closed - * - * // Print error (should yield "Bad file descriptor") - * print(fp.error(), "\n"); - */ - -/** * Read the next entry from the open directory. * * Returns a string containing the entry name. @@ -1105,7 +1051,7 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs) * * @function module:fs.dir#read * - * @returns {string|null} + * @returns {?string} */ static uc_value_t * uc_fs_readdir(uc_vm_t *vm, size_t nargs) @@ -1139,7 +1085,7 @@ uc_fs_readdir(uc_vm_t *vm, size_t nargs) * * @function module:fs.dir#tell * - * @returns {number|null} + * @returns {?number} */ static uc_value_t * uc_fs_telldir(uc_vm_t *vm, size_t nargs) @@ -1174,7 +1120,7 @@ uc_fs_telldir(uc_vm_t *vm, size_t nargs) * @param {number} offset * Position value obtained by `tell()`. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * @@ -1218,7 +1164,7 @@ uc_fs_seekdir(uc_vm_t *vm, size_t nargs) * * @function module:fs.dir#close * - * @returns {boolean|null} + * @returns {?boolean} */ static uc_value_t * uc_fs_closedir(uc_vm_t *vm, size_t nargs) @@ -1247,7 +1193,7 @@ uc_fs_closedir(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the directory. * - * @returns {module:fs.dir|null} + * @returns {?module:fs.dir} * * @example * // Open a directory @@ -1282,7 +1228,7 @@ uc_fs_opendir(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the symbolic link. * - * @returns {string|null} + * @returns {?string} * * @example * // Read the value of a symbolic link @@ -1456,7 +1402,7 @@ uc_fs_stat_common(uc_vm_t *vm, size_t nargs, bool use_lstat) * @param {string} path * The path to the file or directory. * - * @returns {module:fs.FileStatResult|null} + * @returns {?module:fs.FileStatResult} * * @example * // Get information about a file @@ -1481,7 +1427,7 @@ uc_fs_stat(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the file or directory. * - * @returns {module:fs.FileStatResult|null} + * @returns {?module:fs.FileStatResult} * * @example * // Get information about a directory @@ -1505,7 +1451,7 @@ uc_fs_lstat(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the new directory. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Create a directory @@ -1539,7 +1485,7 @@ uc_fs_mkdir(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the directory to be removed. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Remove a directory @@ -1574,7 +1520,7 @@ uc_fs_rmdir(uc_vm_t *vm, size_t nargs) * @param {string} path * The path of the symbolic link. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Create a symbolic link @@ -1608,7 +1554,7 @@ uc_fs_symlink(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the file or symbolic link. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Remove a file @@ -1637,7 +1583,7 @@ uc_fs_unlink(uc_vm_t *vm, size_t nargs) * * @function module:fs#getcwd * - * @returns {string|null} + * @returns {?string} * * @example * // Get the current working directory @@ -1692,7 +1638,7 @@ uc_fs_getcwd(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the new working directory. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Change the current working directory @@ -1728,7 +1674,7 @@ uc_fs_chdir(uc_vm_t *vm, size_t nargs) * @param {number} mode * The new mode (permissions). * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Change the mode of a file @@ -1872,7 +1818,7 @@ uc_fs_resolve_group(uc_value_t *v, gid_t *gid) * The new group's ID. When given as number, it is used as-is, when given as * string, the group name is resolved to the corresponding gid first. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Change the owner of a file @@ -1918,7 +1864,7 @@ uc_fs_chown(uc_vm_t *vm, size_t nargs) * @param {string} newPath * The new path of the file or directory. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Rename a file @@ -1980,7 +1926,7 @@ uc_fs_glob(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to extract the directory name from. * - * @returns {string|null} + * @returns {?string} * * @example * // Get the directory name of a path @@ -2029,7 +1975,7 @@ uc_fs_dirname(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to extract the base name from. * - * @returns {string|null} + * @returns {?string} * * @example * // Get the base name of a path @@ -2083,7 +2029,7 @@ uc_fs_lsdir_sort_fn(const void *k1, const void *k2) * @param {string} path * The path to the directory. * - * @returns {string[]|null} + * @returns {?string[]} * * @example * // List the content of a directory @@ -2169,7 +2115,7 @@ uc_fs_lsdir(uc_vm_t *vm, size_t nargs) * @param {string} [template="/tmp/XXXXXX"] * The path template to use when forming the temporary file name. * - * @returns {module:fs.file|null} + * @returns {?module:fs.file} * * @example * // Create a unique temporary file in the current working directory @@ -2261,7 +2207,7 @@ uc_fs_mkstemp(uc_vm_t *vm, size_t nargs) * @param {number} [mode="f"] * Optional access mode. * - * @returns {boolean|null} + * @returns {?boolean} * * @example * // Check file read and write accessibility @@ -2329,7 +2275,7 @@ uc_fs_access(uc_vm_t *vm, size_t nargs) * Number of bytes to limit the result to. When omitted, the entire content is * returned. * - * @returns {string|null} + * @returns {?string} * * @example * // Read first 100 bytes of content @@ -2441,7 +2387,7 @@ uc_fs_readfile(uc_vm_t *vm, size_t nargs) * Truncates the amount of data to be written to the specified amount of bytes. * When omitted, the entire content is written. * - * @returns {number|null} + * @returns {?number} * * @example * // Write string to a file @@ -2522,7 +2468,7 @@ uc_fs_writefile(uc_vm_t *vm, size_t nargs) * @param {string} path * The path to the file or directory. * - * @returns {string|null} + * @returns {?string} * * @example * // Resolve the absolute path of a file @@ -2561,7 +2507,7 @@ uc_fs_realpath(uc_vm_t *vm, size_t nargs) * * @function module:fs#pipe * - * @returns {module:fs.file[]|null} + * @returns {?module:fs.file[]} * * @example * // Create a pipe @@ -14,6 +14,40 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/** + * # OpenWrt UCI configuration + * + * The `uci` module provides access to the native OpenWrt + * {@link https://github.com/openwrt/uci libuci} API for reading and + * manipulating UCI configuration files. + * + * Functions can be individually imported and directly accessed using the + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import named import} + * syntax: + * + * ``` + * import { cursor } from 'uci'; + * + * let ctx = cursor(); + * let hostname = ctx.get_first('system', 'system', 'hostname'); + * ``` + * + * Alternatively, the module namespace can be imported + * using a wildcard import statement: + * + * ``` + * import * as uci from 'uci'; + * + * let ctx = uci.cursor(); + * let hostname = ctx.get_first('system', 'system', 'hostname'); + * ``` + * + * Additionally, the uci module namespace may also be imported by invoking + * the `ucode` interpreter with the `-luci` switch. + * + * @module uci + */ + #include <string.h> #include <uci.h> @@ -30,6 +64,24 @@ enum pkg_cmd { CMD_REVERT }; +/** + * Query error information. + * + * Returns a string containing a description of the last occurred error or + * `null` if there is no error information. + * + * @function module:uci#error + * + * @returns {?string} + * + * @example + * // Trigger error + * const ctx = cursor(); + * ctx.set("not_existing_config", "test", "1"); + * + * // Print error (should yield "Entry not found") + * print(ctx.error(), "\n"); + */ static uc_value_t * uc_uci_error(uc_vm_t *vm, size_t nargs) { @@ -63,6 +115,38 @@ uc_uci_error(uc_vm_t *vm, size_t nargs) } +/** + * Instantiate uci cursor. + * + * A uci cursor is a context for interacting with uci configuration files. It's + * purpose is to cache and hold changes made to loaded configuration states + * until those changes are written out to disk or discared. + * + * Unsaved and uncommitted changes in a cursor instance are private and not + * visible to other cursor instances instantiated by the same program or other + * processes on the system. + * + * Returns the instantiated cursor on success. + * + * Returns `null` on error, e.g. if an invalid path argument was provided. + * + * @function module:uci#cursor + * + * @param {string} [config_dir=/etc/config] + * The directory to search for configuration files. It defaults to the well + * known uci configuration directory `/etc/config` but may be set to a different + * path for special purpose applications. + * + * @param {string} [delta_dir=/tmp/.uci] + * The directory to save delta records in. It defaults to the well known + * `/tmp/.uci` path which is used as default by the uci command line tool. + * + * By changing this path to a different location, it is possible to isolate + * uncommitted application changes from the uci cli or other processes on the + * system. + * + * @returns {?module:uci.cursor} + */ static uc_value_t * uc_uci_cursor(uc_vm_t *vm, size_t nargs) { @@ -98,6 +182,164 @@ uc_uci_cursor(uc_vm_t *vm, size_t nargs) } +/** + * Represents a context for interacting with uci configuration files. + * + * Operations on uci configurations are performed through a uci cursor object + * which operates on in-memory representations of loaded configuration files. + * + * Any changes made to configuration values are local to the cursor object and + * held in memory only until they're written out to the filesystem using the + * `save()` and `commit()` methods. + * + * Changes performed in one cursor instance are not reflected in another, unless + * the first instance writes those changes to the filesystem and the other + * instance explicitly (re)loads the affected configuration files. + * + * @class module:uci.cursor + * @hideconstructor + * + * @borrows module:uci#error as module.uci.cursor#error + * + * @see {@link module:uci#cursor|cursor()} + * + * @example + * + * const ctx = cursor(…); + * + * // Enumerate configuration files + * ctx.configs(); + * + * // Load configuration files + * ctx.load(…); + * ctx.unload(…); + * + * // Query values + * ctx.get(…); + * ctx.get_all(…); + * ctx.get_first(…); + * ctx.foreach(…); + * + * // Modify values + * ctx.add(…); + * ctx.set(…); + * ctx.rename(…); + * ctx.reorder(…); + * ctx.delete(…); + * + * // Stage, revert, save changes + * ctx.changes(…); + * ctx.save(…); + * ctx.revert(…); + * ctx.commit(…); + */ + +/** + * A uci change record is a plain array containing the change operation name as + * first element, the affected section ID as second argument and an optional + * third and fourth argument whose meanings depend on the operation. + * + * @typedef {string[]} ChangeRecord + * @memberof module:uci.cursor + * + * @property {string} 0 + * The operation name - may be one of `add`, `set`, `remove`, `order`, + * `list-add`, `list-del` or `rename`. + * + * @property {string} 1 + * The section ID targeted by the operation. + * + * @property {string} 2 + * The meaning of the third element depends on the operation. + * - For `add` it is type of the section that has been added + * - For `set` it either is the option name if a fourth element exists, or the + * type of a named section which has been added when the change entry only + * contains three elements. + * - For `remove` it contains the name of the option that has been removed. + * - For `order` it specifies the new sort index of the section. + * - For `list-add` it contains the name of the list option a new value has been + * added to. + * - For `list-del` it contains the name of the list option a value has been + * removed from. + * - For `rename` it contains the name of the option that has been renamed if a + * fourth element exists, else it contains the new name a section has been + * renamed to if the change entry only contains three elements. + * + * @property {string} 4 + * The meaning of the fourth element depends on the operation. + * - For `set` it is the value an option has been set to. + * - For `list-add` it is the new value that has been added to a list option. + * - For `rename` it is the new name of an option that has been renamed. + */ + +/** + * A section object represents the options and their corresponding values + * enclosed within a configuration section, as well as some additional meta data + * such as sort indexes and internal ID. + * + * Any internal metadata fields are prefixed with a dot which isn't an allowed + * character for normal option names. + * + * @typedef {Object<string, boolean|number|string|string[]>} SectionObject + * @memberof module:uci.cursor + * + * @property {boolean} .anonymous + * The `.anonymous` property specifies whether the configuration is + * anonymous (`true`) or named (`false`). + * + * @property {number} .index + * The `.index` property specifies the sort order of the section. + * + * @property {string} .name + * The `.name` property holds the name of the section object. It may be either + * an anonymous ID in the form `cfgXXXXXX` with `X` being a hexadecimal digit or + * a string holding the name of the section. + * + * @property {string} .type + * The `.type` property contains the type of the corresponding uci + * section. + * + * @property {string|string[]} * + * A section object may contain an arbitrary number of further properties + * representing the uci option enclosed in the section. + * + * All option property names will be in the form `[A-Za-z0-9_]+` and either + * contain a string value or an array of strings, in case the underlying option + * is an UCI list. + */ + +/** + * The sections callback is invoked for each section found within the given + * configuration and receives the section object and its associated name as + * arguments. + * + * @callback module:uci.cursor.SectionCallback + * + * @param {module:uci.cursor.SectionObject} section + * The section object. + */ + +/** + * Explicitly reload configuration file. + * + * Usually, any attempt to query or modify a value within a given configuration + * will implicitly load the underlying file into memory. By invoking `load()` + * explicitly, a potentially loaded stale configuration is discarded and + * reloaded from the file system, ensuring that the latest state is reflected in + * the cursor. + * + * Returns `true` if the configuration was successfully loaded. + * + * Returns `null` on error, e.g. if the requested configuration does not exist. + * + * @function module:uci.cursor#load + * + * @param {string} config + * The name of the configuration file to load, e.g. `"system"` to load + * `/etc/config/system` into the cursor. + * + * @returns {?boolean} + */ static uc_value_t * uc_uci_load(uc_vm_t *vm, size_t nargs) { @@ -127,6 +369,26 @@ uc_uci_load(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(true); } +/** + * Explicitly unload configuration file. + * + * The `unload()` function forcibly discards a loaded configuration state from + * the cursor so that the next attempt to read or modify that configuration + * will load it anew from the file system. + * + * Returns `true` if the configuration was successfully unloaded. + * + * Returns `false` if the configuration was not loaded to begin with. + * + * Returns `null` on error, e.g. if the requested configuration does not exist. + * + * @function module:uci.cursor#unload + * + * @param {string} config + * The name of the configuration file to unload. + * + * @returns {?boolean} + */ static uc_value_t * uc_uci_unload(uc_vm_t *vm, size_t nargs) { @@ -308,18 +570,139 @@ uc_uci_get_any(uc_vm_t *vm, size_t nargs, bool all) return ucv_string_new(ptr.s->type); } +/** + * Query a single option value or section type. + * + * When invoked with three arguments, the function returns the value of the + * given option, within the specified section of the given configuration. + * + * When invoked with just a config and section argument, the function returns + * the type of the specified section. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the configuration value or section type on success. + * + * Returns `null` on error, e.g. if the requested configuration does not exist + * or if an invalid argument was passed. + * + * @function module:uci.cursor#get + * + * @param {string} config + * The name of the configuration file to query, e.g. `"system"` to query values + * in `/etc/config/system`. + * + * @param {string} section + * The name of the section to query within the configuration. + * + * @param {string} [option] + * The name of the option to query within the section. If omitted, the type of + * the section is returned instead. + * + * @returns {?(string|string[])} + * + * @example + * const ctx = cursor(…); + * + * // Query an option, extended section notation is supported + * ctx.get('system', '@system[0]', 'hostname'); + * + * // Query a section type (should yield 'interface') + * ctx.get('network', 'lan'); + */ static uc_value_t * uc_uci_get(uc_vm_t *vm, size_t nargs) { return uc_uci_get_any(vm, nargs, false); } +/** + * Query a complete section or configuration. + * + * When invoked with two arguments, the function returns all values of the + * specified section within the given configuration as dictionary. + * + * When invoked with just a config argument, the function returns a nested + * dictionary of all sections present within the given configuration. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the section or configuration dictionary on success. + * + * Returns `null` on error, e.g. if the requested configuration does not exist + * or if an invalid argument was passed. + * + * @function module:uci.cursor#get_all + * + * @param {string} config + * The name of the configuration file to query, e.g. `"system"` to query values + * in `/etc/config/system`. + * + * @param {string} [section] + * The name of the section to query within the configuration. If omitted a + * nested dictionary containing all section values is returned. + * + * @returns {?(Object<string, module:uci.cursor.SectionObject>|module:uci.cursor.SectionObject)} + * + * @example + * const ctx = cursor(…); + * + * // Query all lan interface details + * ctx.get_all('network', 'lan'); + * + * // Dump the entire dhcp configuration + * ctx.get_all('dhcp'); + */ static uc_value_t * uc_uci_get_all(uc_vm_t *vm, size_t nargs) { return uc_uci_get_any(vm, nargs, true); } +/** + * Query option value or name of first section of given type. + * + * When invoked with three arguments, the function returns the value of the + * given option within the first found section of the specified type in the + * given configuration. + * + * When invoked with just a config and section type argument, the function + * returns the name of the first found section of the given type. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the configuration value or section name on success. + * + * Returns `null` on error, e.g. if the requested configuration does not exist + * or if an invalid argument was passed. + * + * @function module:uci.cursor#get_first + * + * @param {string} config + * The name of the configuration file to query, e.g. `"system"` to query values + * in `/etc/config/system`. + * + * @param {string} type + * The section type to find the first section for within the configuration. + * + * @param {string} [option] + * The name of the option to query within the section. If omitted, the name of + * the section is returned instead. + * + * @returns {?(string|string[])} + * + * @example + * const ctx = cursor(…); + * + * // Query hostname in first anonymous "system" section of /etc/config/system + * ctx.get_first('system', 'system', 'hostname'); + * + * // Figure out name of first network interface section (usually "loopback") + * ctx.get_first('network', 'interface'); + */ static uc_value_t * uc_uci_get_first(uc_vm_t *vm, size_t nargs) { @@ -378,6 +761,49 @@ uc_uci_get_first(uc_vm_t *vm, size_t nargs) err_return(UCI_ERR_NOTFOUND); } +/** + * Add anonymous section to given configuration. + * + * Adds a new anonymous (unnamed) section of the specified type to the given + * configuration. In order to add a named section, the three argument form of + * `set()` should be used instead. + * + * In contrast to other query functions, `add()` will not implicitly load the + * configuration into the cursor. The configuration either needs to be loaded + * explicitly through `load()` beforehand, or implicitly by querying it through + * one of the `get()`, `get_all()`, `get_first()` or `foreach()` functions. + * + * Returns the autogenerated, ephemeral name of the added unnamed section + * on success. + * + * Returns `null` on error, e.g. if the targeted configuration was not loaded or + * if an invalid section type value was passed. + * + * @function module:uci.cursor#add + * + * @param {string} config + * The name of the configuration file to add the section to, e.g. `"system"` to + * modify `/etc/config/system`. + * + * @param {string} type + * The type value to use for the added section. + * + * @returns {?string} + * + * @example + * const ctx = cursor(…); + * + * // Load firewall configuration + * ctx.load('firewall'); + * + * // Add unnamed `config rule` section + * const sid = ctx.add('firewall', 'rule'); + * + * // Set values on the newly added section + * ctx.set('firewall', sid, 'name', 'A test'); + * ctx.set('firewall', sid, 'target', 'ACCEPT'); + * … + */ static uc_value_t * uc_uci_add(uc_vm_t *vm, size_t nargs) { @@ -458,6 +884,60 @@ uval_to_uci(uc_vm_t *vm, uc_value_t *val, const char **p, bool *is_list) } } +/** + * Set option value or add named section in given configuration. + * + * When invoked with four arguments, the function sets the value of the given + * option within the specified section of the given configuration to the + * provided value. A value of `""` (empty string) can be used to delete an + * existing option. + * + * When invoked with three arguments, the function adds a new named section to + * the given configuration, using the specified type. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the `true` if the named section was added or the specified option was + * set. + * + * Returns `null` on error, e.g. if the targeted configuration was not found or + * if an invalid value was passed. + * + * @function module:uci.cursor#set + * + * @param {string} config + * The name of the configuration file to set values in, e.g. `"system"` to + * modify `/etc/config/system`. + * + * @param {string} section + * The section name to create or set a value in. + * + * @param {string} option_or_type + * The option name to set within the section or, when the subsequent value + * argument is omitted, the type of the section to create within the + * configuration. + * + * @param {(Array<string|boolean|number>|string|boolean|number)} [value] + * The option value to set. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * // Add named `config interface guest` section + * ctx.set('network', 'guest', 'interface'); + * + * // Set values on the newly added section + * ctx.set('network', 'guest', 'proto', 'static'); + * ctx.set('network', 'guest', 'ipaddr', '10.0.0.1/24'); + * ctx.set('network', 'guest', 'dns', ['8.8.4.4', '8.8.8.8']); + * … + * + * // Delete 'disabled' option in first wifi-iface section + * ctx.set('wireless', '@wifi-iface[0]', 'disabled', ''); + */ static uc_value_t * uc_uci_set(uc_vm_t *vm, size_t nargs) { @@ -563,6 +1043,50 @@ uc_uci_set(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(true); } +/** + * Delete an option or section from given configuration. + * + * When invoked with three arguments, the function deletes the given option + * within the specified section of the given configuration. + * + * When invoked with two arguments, the function deletes the entire specified + * section within the given configuration. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the `true` if specified option or section has been deleted. + * + * Returns `null` on error, e.g. if the targeted configuration was not found or + * if an invalid value was passed. + * + * @function module:uci.cursor#delete + * + * @param {string} config + * The name of the configuration file to delete values in, e.g. `"system"` to + * modify `/etc/config/system`. + * + * @param {string} section + * The section name to remove the specified option in or, when the subsequent + * argument is omitted, the section to remove entirely. + * + * @param {string} [option] + * The option name to remove within the section. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * // Delete 'disabled' option in first wifi-iface section + * ctx.delete('wireless', '@wifi-iface[0]', 'disabled'); + * + * // Delete 'wan' interface + * ctx.delete('network', 'lan'); + * + * // Delete last firewall rule + * ctx.delete('firewall', '@rule[-1]'); + */ static uc_value_t * uc_uci_delete(uc_vm_t *vm, size_t nargs) { @@ -598,6 +1122,55 @@ uc_uci_delete(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(true); } +/** + * Rename an option or section in given configuration. + * + * When invoked with four arguments, the function renames the given option + * within the specified section of the given configuration to the provided + * value. + * + * When invoked with three arguments, the function renames the entire specified + * section to the provided value. + * + * In either case, the given configuration is implicitly loaded into the cursor + * if not already present. + * + * Returns the `true` if specified option or section has been renamed. + * + * Returns `null` on error, e.g. if the targeted configuration was not found or + * if an invalid value was passed. + * + * @function module:uci.cursor#rename + * + * @param {string} config + * The name of the configuration file to rename values in, e.g. `"system"` to + * modify `/etc/config/system`. + * + * @param {string} section + * The section name to rename or to rename an option in. + * + * @param {string} option_or_name + * The option name to rename within the section or, when the subsequent name + * argument is omitted, the new name of the renamed section within the + * configuration. + * + * @param {string} [name] + * The new name of the option to rename. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * // Assign explicit name to last anonymous firewall rule section + * ctx.rename('firewall', '@rule[-1]', 'my_block_rule'); + * + * // Rename 'server' to 'orig_server_list' in ntp section of system config + * ctx.rename('system', 'ntp', 'server', 'orig_server_list'); + * + * // Rename 'wan' interface to 'external' + * ctx.rename('network', 'wan', 'external'); + */ static uc_value_t * uc_uci_rename(uc_vm_t *vm, size_t nargs) { @@ -658,6 +1231,74 @@ uc_uci_rename(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(true); } +/** + * Reorder sections in given configuration. + * + * The `reorder()` function moves a single section by repositioning it to the + * given index within the configurations section list. + * + * The given configuration is implicitly loaded into the cursor if not already + * present. + * + * Returns the `true` if specified section has been moved. + * + * Returns `null` on error, e.g. if the targeted configuration was not found or + * if an invalid value was passed. + * + * @function module:uci.cursor#reorder + * + * @param {string} config + * The name of the configuration file to move the section in, e.g. `"system"` to + * modify `/etc/config/system`. + * + * @param {string} section + * The section name to move. + * + * @param {number} index + * The target index to move the section to, starting from `0`. + * + * @param {string} [name] + * The new name of the option to rename. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * // Query whole firewall config and reorder resulting dict by type and name + * const type_order = ['defaults', 'zone', 'forwarding', 'redirect', 'rule']; + * const values = ctx.get_all('firewall'); + * + * sort(values, (k1, k2, s1, s2) => { + * // Get weight from type_order array + * let w1 = index(type_order, s1['.type']); + * let w2 = index(type_order, s2['.type']); + * + * // For unknown type orders, use type value itself as weight + * if (w1 == -1) w1 = s1['.type']; + * if (w2 == -1) w2 = s2['.type']; + * + * // Get name from name option, fallback to section name + * let n1 = s1.name ?? k1; + * let n2 = s2.name ?? k2; + * + * // Order by weight + * if (w1 < w2) return -1; + * if (w1 > w2) return 1; + * + * // For same weight order by name + * if (n1 < n2) return -1; + * if (n1 > n2) return 1; + * + * return 0; + * }); + * + * // Sequentially reorder sorted sections in firewall configuration + * let position = 0; + * + * for (let sid in values) + * ctx.reorder('firewall', sid, position++); + */ static uc_value_t * uc_uci_reorder(uc_vm_t *vm, size_t nargs) { @@ -766,18 +1407,125 @@ uc_uci_pkg_command(uc_vm_t *vm, size_t nargs, enum pkg_cmd cmd) return ucv_boolean_new(true); } +/** + * Save accumulated cursor changes to delta directory. + * + * The `save()` function writes consolidated changes made to in-memory copies of + * loaded configuration files to the uci delta directory which effectively makes + * them available to other processes using the same delta directory path as well + * as the `uci changes` cli command when using the default delta directory. + * + * Note that uci deltas are overlayed over the actual configuration file values + * so they're reflected by `get()`, `foreach()` etc. even if the underlying + * configuration files are not actually changed (yet). The delta records may be + * either permanently merged into the configuration by invoking `commit()` or + * reverted through `revert()` in order to restore the current state of the + * underlying configuration file. + * + * When the optional "config" parameter is omitted, delta records for all + * currently loaded configuration files are written. + * + * In case that neither sharing changes with other processes nor any revert + * functionality is required, changes may be committed directly using `commit()` + * instead, bypassing any delta record creation. + * + * Returns the `true` if operation completed successfully. + * + * Returns `null` on error, e.g. if the requested configuration was not loaded + * or when a file system error occurred. + * + * @function module:uci.cursor#save + * + * @param {string} [config] + * The name of the configuration file to save delta records for, e.g. `"system"` + * to store changes for `/etc/config/system`. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * ctx.set('wireless', '@wifi-iface[0]', 'disabled', '1'); + * ctx.save('wireless'); + * + * @see {@link module:uci.cursor#commit|commit()} + * @see {@link module:uci.cursor#revert|revert()} + */ static uc_value_t * uc_uci_save(uc_vm_t *vm, size_t nargs) { return uc_uci_pkg_command(vm, nargs, CMD_SAVE); } +/** + * Update configuration files with accumulated cursor changes. + * + * The `commit()` function merges changes made to in-memory copies of loaded + * configuration files as well as existing delta records in the cursors + * configured delta directory and writes them back into the underlying + * configuration files, persistently committing changes to the file system. + * + * When the optional "config" parameter is omitted, all currently loaded + * configuration files with either present delta records or yet unsaved + * cursor changes are updated. + * + * Returns the `true` if operation completed successfully. + * + * Returns `null` on error, e.g. if the requested configuration was not loaded + * or when a file system error occurred. + * + * @function module:uci.cursor#commit + * + * @param {string} [config] + * The name of the configuration file to commit, e.g. `"system"` to update the + * `/etc/config/system` file. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * ctx.set('system', '@system[0]', 'hostname', 'example.org'); + * ctx.commit('system'); + */ static uc_value_t * uc_uci_commit(uc_vm_t *vm, size_t nargs) { return uc_uci_pkg_command(vm, nargs, CMD_COMMIT); } +/** + * Revert accumulated cursor changes and associated delta records. + * + * The `revert()` function discards any changes made to in-memory copies of + * loaded configuration files and discards any related existing delta records in + * the cursors configured delta directory. + * + * When the optional "config" parameter is omitted, all currently loaded + * configuration files with either present delta records or yet unsaved + * cursor changes are reverted. + * + * Returns the `true` if operation completed successfully. + * + * Returns `null` on error, e.g. if the requested configuration was not loaded + * or when a file system error occurred. + * + * @function module:uci.cursor#revert + * + * @param {string} [config] + * The name of the configuration file to revert, e.g. `"system"` to discard any + * changes for the `/etc/config/system` file. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * ctx.set('system', '@system[0]', 'hostname', 'example.org'); + * ctx.revert('system'); + * + * @see {@link module:uci.cursor#save|save()} + */ static uc_value_t * uc_uci_revert(uc_vm_t *vm, size_t nargs) { @@ -873,6 +1621,38 @@ changes_to_uval(uc_vm_t *vm, struct uci_context *ctx, const char *package) return a; } +/** + * Enumerate pending changes. + * + * The `changes()` function returns a list of change records for currently + * loaded configuration files, originating both from the cursors associated + * delta directory and yet unsaved cursor changes. + * + * When the optional "config" parameter is specified, the requested + * configuration is implicitly loaded if it not already loaded into the cursor. + * + * Returns a dictionary of change record arrays, keyed by configuration name. + * + * Returns `null` on error, e.g. if the requested configuration could not be + * loaded. + * + * @function module:uci.cursor#changes + * + * @param {string} [config] + * The name of the configuration file to enumerate changes for, e.g. `"system"` + * to query pending changes for the `/etc/config/system` file. + * + * @returns {?Object<string, module:uci.cursor.ChangeRecord[]>} + * + * @example + * const ctx = cursor(…); + * + * // Enumerate changes for all currently loaded configurations + * const deltas = ctx.changes(); + * + * // Explicitly load and enumerate changes for the "system" configuration + * const deltas = ctx.changes('system'); + */ static uc_value_t * uc_uci_changes(uc_vm_t *vm, size_t nargs) { @@ -907,6 +1687,48 @@ uc_uci_changes(uc_vm_t *vm, size_t nargs) return res; } +/** + * Iterate configuration sections. + * + * The `foreach()` function iterates all sections of the given configuration, + * optionally filtered by type, and invokes the given callback function for + * each encountered section. + * + * When the optional "type" parameter is specified, the callback is only invoked + * for sections of the given type, otherwise it is invoked for all sections. + * + * The requested configuration is implicitly loaded into the cursor. + * + * Returns `true` if the callback was executed successfully at least once. + * + * Returns `false` if the callback was never invoked, e.g. when the + * configuration is empty or contains no sections of the given type. + * + * Returns `null` on error, e.g. when an invalid callback was passed or the + * requested configuration not found. + * + * @function module:uci.cursor#foreach + * + * @param {string} config + * The configuration to iterate sections for, e.g. `"system"` to read the + * `/etc/config/system` file. + * + * @param {?string} type + * Invoke the callback only for sections of the specified type. + * + * @param {module:uci.cursor.SectionCallback} callback + * The callback to invoke for each section, will receive a section dictionary + * as sole argument. + * + * @returns {?boolean} + * + * @example + * const ctx = cursor(…); + * + * // Iterate all network interfaces + * ctx.foreach('network', 'interface', + * section => print(`Have interface ${section[".name"]}\n`)); + */ static uc_value_t * uc_uci_foreach(uc_vm_t *vm, size_t nargs) { @@ -967,6 +1789,26 @@ uc_uci_foreach(uc_vm_t *vm, size_t nargs) return ucv_boolean_new(ret); } +/** + * Enumerate existing configurations. + * + * The `configs()` function yields an array of configuration files present in + * the cursors associated configuration directory, `/etc/config/` by default. + * + * Returns an array of configuration names on success. + * + * Returns `null` on error, e.g. due to filesystem errors. + * + * @function module:uci.cursor#configs + * + * @returns {?string[]} + * + * @example + * const ctx = cursor(…); + * + * // Enumerate all present configuration file names + * const configurations = ctx.configs(); + */ static uc_value_t * uc_uci_configs(uc_vm_t *vm, size_t nargs) { |