diff --git a/CHANGELOG.md b/CHANGELOG.md index fa81dac5..1b5e14fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,9 @@ Expected: June 2024 * CLI support for multiple inline commands separated by semi-colon * New `clixon-config@2024-04-01.yang` revision * Added options: - - `CLICON_DEBUG`: Debug flags, partly implemented. + - `CLICON_LOG_DESTINATION`: Default log destination + - `CLICON_LOG_FILE`: Which file to log to if file logging + - `CLICON_DEBUG`: Debug flags - `CLICON_YANG_SCHEMA_MOUNT_SHARE`: Share same YANGs of several moint-points - `CLICON_SOCK_PRIO`: Enable socket event priority - `CLICON_XMLDB_MULTI`: Split datastore into multiple sub files diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 27fef31a..219ea6c3 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -537,6 +537,7 @@ main(int argc, int config_dump; enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; + int32_t d; /* Initiate CLICON handle */ if ((h = backend_handle_init()) == NULL) @@ -572,16 +573,15 @@ main(int argc, cligen_output(stdout, "Clixon version: %s\n", CLIXON_GITHASH); print_version++; /* plugins may also print versions w ca-version callback */ break; - case 'D' : { /* debug */ - int d = 0; - /* Try first symbolic, then numeric match */ + case 'D' : /* debug */ + /* Try first symbolic, then numeric match + * Cant use yang_bits_map, too early in bootstrap, there is no yang */ if ((d = clixon_debug_str2key(optarg)) < 0 && - sscanf(optarg, "%d", &d) != 1){ + sscanf(optarg, "%u", &d) != 1){ usage(h, argv[0]); } dbg |= d; break; - } case 'f': /* config file */ if (!strlen(optarg)) usage(h, argv[0]); @@ -592,13 +592,18 @@ main(int argc, usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; - case 'l': /* Log destination: s|e|o */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv[0]); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; + case 'l': /* Log destination: s|e|o|f */ + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; break; } /* @@ -618,7 +623,9 @@ main(int argc, usage(h, argv[0]); goto done; } - + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; /* Initialize plugin module by creating a handle holding plugin and callback lists */ if (clixon_plugin_module_init(h) < 0) goto done; diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index e168356e..e447cf97 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -562,7 +562,7 @@ main(int argc, clixon_handle h; int logclisyntax = 0; int help = 0; - int logdst = CLIXON_LOG_STDERR; + uint32_t logdst = 0; char *restarg = NULL; /* what remains after options */ yang_stmt *yspec; struct passwd *pw; @@ -571,11 +571,12 @@ main(int argc, cvec *nsctx_global = NULL; /* Global namespace context */ size_t cligen_buflen; size_t cligen_bufthreshold; - int dbg=0; + uint32_t dbg=0; int nr; int config_dump; enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; + int32_t d; /* Defaults */ once = 0; @@ -608,7 +609,7 @@ main(int argc, */ optind = 1; opterr = 0; - while ((c = getopt(argc, argv, CLI_OPTS)) != -1) + while ((c = getopt(argc, argv, CLI_OPTS)) != -1) { switch (c) { case 'h': /* Defer the call to usage() to later. Reason is that for helpful @@ -622,16 +623,15 @@ main(int argc, cligen_output(stdout, "Clixon version: %s\n", CLIXON_GITHASH); print_version++; /* plugins may also print versions w ca-version callback */ break; - case 'D' : { /* debug */ - int d = 0; - /* Try first symbolic, then numeric match */ + case 'D' : /* debug, if set here overrides option CLICON_DEBUG */ + /* Try first symbolic, then numeric match + * Cant use yang_bits_map, too early in bootstrap, there is no yang */ if ((d = clixon_debug_str2key(optarg)) < 0 && sscanf(optarg, "%d", &d) != 1){ usage(h, argv[0]); } dbg |= d; break; - } case 'f': /* config file */ if (!strlen(optarg)) usage(h, argv[0]); @@ -642,19 +642,25 @@ main(int argc, usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; - case 'l': /* Log destination: s|e|o|f */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv[0]); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; + case 'l': /* Log destination: s|e|o|f */ + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; break; } + } /* * Logs, error and debug to stderr or syslog, set debug level */ - clixon_log_init(h, __PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst); + clixon_log_init(h, __PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst?logdst:CLIXON_LOG_STDERR); clixon_debug_init(h, dbg); yang_init(h); @@ -763,25 +769,10 @@ main(int argc, /* Defer: Wait to the last minute to print help message */ if (help) usage(h, argv[0]); - /* Unless -D, set debug level to CLICON_DEBUG set - * Only works for one value. - */ - { - char *dstr; - int d = 0; - - dstr = clicon_option_str(h, "CLICON_DEBUG"); - if (dbg == 0 && dstr && strlen(dstr)){ - if ((d = clixon_debug_str2key(dstr)) < 0 && - sscanf(optarg, "%d", &d) != 1){ - clixon_err(OE_CFG, 0, "Parsing CLICON_DEBUG: %s", dstr); - goto done; - } - clixon_debug_init(h, d); - clixon_log_init(h, __PROGRAM__, d?LOG_DEBUG:LOG_INFO, logdst); - } - } + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; /* Split remaining argv/argc into and */ if (options_split(h, argv0, argc, argv, &restarg) < 0) goto done; @@ -983,8 +974,9 @@ main(int argc, done: if (restarg) free(restarg); -// Gets in your face if we log on stderr - clixon_log_init(h, __PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ + /* Dont log terminate on stderr or stdout */ + clixon_log_init(h, __PROGRAM__, LOG_INFO, + clixon_get_logflags() & ~(CLIXON_LOG_STDERR|CLIXON_LOG_STDOUT)); clixon_log(h, LOG_NOTICE, "%s: %u Terminated", __PROGRAM__, getpid()); if (h) cli_terminate(h); diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index b33a27c0..1e0372b1 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -116,7 +116,7 @@ netconf_add_request_attr(cxobj *xrpc, if (xml_find_type(xrep, NULL, xml_name(xa), CX_ATTR) != NULL) continue; /* Skip already present (dont overwrite) */ /* Filter all clixon-lib attributes and namespace declaration - * to acvoid leaking internal attributes to external NETCONF + * to avoid leaking internal attributes to external NETCONF * note this is only done on top-level. */ if (xml_prefix(xa) && strcmp(xml_prefix(xa), CLIXON_LIB_PREFIX) == 0) @@ -687,6 +687,7 @@ main(int argc, int config_dump = 0; enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; + int32_t d; /* Create handle */ if ((h = clixon_handle_init()) == NULL) @@ -703,7 +704,7 @@ main(int argc, } if (clicon_username_set(h, pw->pw_name) < 0) goto done; - while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1) + while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1) { switch (c) { case 'h' : /* help */ usage(h, argv[0]); @@ -712,16 +713,14 @@ main(int argc, cligen_output(stdout, "Clixon version: %s\n", CLIXON_GITHASH); print_version++; /* plugins may also print versions w ca-version callback */ break; - case 'D' : { /* debug */ - int d = 0; + case 'D' : /* debug */ /* Try first symbolic, then numeric match */ if ((d = clixon_debug_str2key(optarg)) < 0 && - sscanf(optarg, "%d", &d) != 1){ + sscanf(optarg, "%u", &d) != 1){ usage(h, argv[0]); } dbg |= d; break; - } case 'f': /* override config file */ if (!strlen(optarg)) usage(h, argv[0]); @@ -733,14 +732,22 @@ main(int argc, clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; case 'l': /* Log destination: s|e|o */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv[0]); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; + int32_t d; + d = 0; + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; break; } + } /* * Logs, error and debug to stderr or syslog, set debug level @@ -833,6 +840,9 @@ main(int argc, argc -= optind; argv += optind; + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 24017004..58c17eae 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -329,6 +329,7 @@ main(int argc, enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; int stream_timeout = 0; + int32_t d; /* Create handle */ if ((h = restconf_handle_init()) == NULL) @@ -351,7 +352,6 @@ main(int argc, print_version++; /* plugins may also print versions w ca-version callback */ break; case 'D' : { /* debug */ - int d = 0; /* Try first symbolic, then numeric match */ if ((d = clixon_debug_str2key(optarg)) < 0 && sscanf(optarg, "%d", &d) != 1){ @@ -371,12 +371,17 @@ main(int argc, clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; case 'l': /* Log destination: s|e|o */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv[0]); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; break; } /* switch getopt */ @@ -466,6 +471,9 @@ main(int argc, argc -= optind; argv += optind; + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index e203ba4f..385529cc 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -1168,6 +1168,7 @@ main(int argc, enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; int stream_timeout = 0; + int32_t d; /* Create handle */ if ((h = restconf_handle_init()) == NULL) @@ -1187,17 +1188,13 @@ main(int argc, cligen_output(stdout, "Clixon version: %s\n", CLIXON_GITHASH); print_version++; /* plugins may also print versions w ca-version callback */ break; - case 'D' : { /* debug. Note this overrides any setting in the config */ - int d = 0; + case 'D' : /* debug. Note this overrides any setting in the config */ /* Try first symbolic, then numeric match */ if ((d = clixon_debug_str2key(optarg)) < 0 && sscanf(optarg, "%d", &d) != 1){ usage(h, argv[0]); } dbg |= d; - break; - } - break; case 'f': /* override config file */ if (!strlen(optarg)) @@ -1210,13 +1207,18 @@ main(int argc, clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg); break; case 'l': /* Log destination: s|e|o */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv0); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; - break; + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; + break; } /* switch getopt */ /* @@ -1324,6 +1326,10 @@ main(int argc, argc -= optind; argv += optind; + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; + /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); diff --git a/apps/snmp/snmp_main.c b/apps/snmp/snmp_main.c index 94a5ce4f..4bb5d13c 100644 --- a/apps/snmp/snmp_main.c +++ b/apps/snmp/snmp_main.c @@ -359,6 +359,7 @@ main(int argc, int config_dump = 0; enum format_enum config_dump_format = FORMAT_XML; int print_version = 0; + int32_t d; /* Create handle */ if ((h = clixon_handle_init()) == NULL) @@ -386,7 +387,7 @@ main(int argc, print_version++; /* plugins may also print versions w ca-version callback */ break; case 'D' : { /* debug */ - int d = 0; + int32_t d = 0; /* Try first symbolic, then numeric match */ if ((d = clixon_debug_str2key(optarg)) < 0 && sscanf(optarg, "%d", &d) != 1){ @@ -400,13 +401,18 @@ main(int argc, usage(h, argv[0]); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); break; - case 'l': /* Log destination: s|e|o */ - if ((logdst = clixon_log_opt(optarg[0])) < 0) - usage(h, argv[0]); - if (logdst == CLIXON_LOG_FILE && - strlen(optarg)>1 && - clixon_log_file(optarg+1) < 0) - goto done; + case 'l': /* Log destination: s|e|o */ + if ((d = clixon_logdst_str2key(optarg)) < 0){ + if (optarg[0] == 'f'){ /* Check for special -lf syntax */ + d = CLIXON_LOG_FILE; + if (strlen(optarg) > 1 && + clixon_log_file(optarg+1) < 0) + goto done; + } + else + usage(h, argv[0]); + } + logdst = d; break; } if (print_version) @@ -467,6 +473,9 @@ main(int argc, argc -= optind; argv += optind; + /* Read debug and log options from config file if not given by command-line */ + if (clixon_options_main_helper(h, dbg, logdst, __PROGRAM__) < 0) + goto done; /* Access the remaining argv/argc options (after --) w clicon-argv_get() */ clicon_argv_set(h, argv0, argc, argv); diff --git a/lib/clixon/clixon_debug.h b/lib/clixon/clixon_debug.h index ca1b0171..9f351fd1 100644 --- a/lib/clixon/clixon_debug.h +++ b/lib/clixon/clixon_debug.h @@ -46,9 +46,10 @@ * Constants */ -/* Debug flags are seperated into subject areas and detail +/*! Debug flags are separated into subject areas and detail + * * @see dbgmap Symbolic mapping (if you change here you may need to change dbgmap) - * @see clixon_debug in clixon-lib.yang + * @see also clixon_debug_t in clixon-lib.yang */ /* Detail level */ #define CLIXON_DBG_ALWAYS 0x00000000 /* Unconditionally logged */ diff --git a/lib/clixon/clixon_log.h b/lib/clixon/clixon_log.h index a80c57c5..df7de5b4 100644 --- a/lib/clixon/clixon_log.h +++ b/lib/clixon/clixon_log.h @@ -45,11 +45,15 @@ /* * Constants */ -/* Where to log (masks) */ -#define CLIXON_LOG_SYSLOG 1 /* print logs on syslog */ -#define CLIXON_LOG_STDERR 2 /* print logs on stderr */ -#define CLIXON_LOG_STDOUT 4 /* print logs on stdout */ -#define CLIXON_LOG_FILE 8 /* print logs on clicon_log_filename */ +/*! Log destination as bitfields (masks) + * + * @see logdstmap Symbolic mapping (if you change here you may need to change logdstmap) + * @see also log_desination_t in clixon-config.yang + */ +#define CLIXON_LOG_SYSLOG 0x01 /* print logs on syslog */ +#define CLIXON_LOG_STDERR 0x02 /* print logs on stderr */ +#define CLIXON_LOG_STDOUT 0x04 /* print logs on stdout */ +#define CLIXON_LOG_FILE 0x08 /* print logs on clixon_log_filename */ /* What kind of log (only for customizable error/logs) */ enum clixon_log_type{ @@ -67,6 +71,8 @@ enum clixon_log_type{ /* * Prototypes */ +char *clixon_logdst_key2str(int keyword); +int clixon_logdst_str2key(char *str); int clixon_log_init(clixon_handle h, char *ident, int upto, int flags); int clixon_log_exit(void); int clixon_log_opt(char c); diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 33615de3..e4bc0103 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -112,6 +112,9 @@ int clicon_option_add(clixon_handle h, const char *name, char *value); /* Initialize options: set defaults, read config-file, etc */ int clicon_options_main(clixon_handle h); +/* Options debug and log helper function */ +int clixon_options_main_helper(clixon_handle h, uint32_t dbg, uint32_t logdst, char *ident); + /*! Check if a clicon option has a value */ int clicon_option_exists(clixon_handle h, const char *name); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index e1a782d1..882e0a24 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -77,8 +77,10 @@ int assign_namespace_element(cxobj *x0, cxobj *x1, cxobj *x1p); int assign_namespace_body(cxobj *x0, cxobj *x1); int xml_merge(cxobj *x0, cxobj *x1, yang_stmt *yspec, char **reason); int yang_valstr2enum(yang_stmt *ytype, char *valstr, char **enumstr); -int yang_bitsstr2val(clixon_handle h, yang_stmt *ytype, char *bitsstr, unsigned char **snmpval, size_t *snmplen); -int yang_val2bitsstr(clixon_handle h, yang_stmt *ytype, unsigned char *snmpval, size_t snmplen, cbuf *cb); +int yang_bitsstr2val(clixon_handle h, yang_stmt *ytype, char *bitsstr, unsigned char **outval, size_t *outlen); +int yang_bitsstr2flags(yang_stmt *ytype, char *bitsstr, uint32_t *flags); +int yang_val2bitsstr(clixon_handle h, yang_stmt *ytype, unsigned char *outval, size_t snmplen, cbuf *cb); +int yang_bits_map(yang_stmt *yt, char *str, char *nodeid, uint32_t *flags); int yang_enum2valstr(yang_stmt *ytype, char *enumstr, char **valstr); int yang_enum2int(yang_stmt *ytype, char *enumstr, int32_t *val); int yang_enum_int_value(cxobj *node, int32_t *val); diff --git a/lib/src/clixon_debug.c b/lib/src/clixon_debug.c index 783d70ae..f36abc74 100644 --- a/lib/src/clixon_debug.c +++ b/lib/src/clixon_debug.c @@ -79,9 +79,20 @@ /* Cache handle since debug calls do not have handle parameter */ static clixon_handle _debug_clixon_h = NULL; +/*! The global debug level. 0 means no debug + * + * @note There are pros and cons in having the debug state as a global variable. The + * alternative to bind it to the clicon handle (h) was considered but it limits its + * usefulness, since not all functions have access to a handle. + * A compromise solution is now in place where h can be provided in the function call, but + * tolerates NULL, in which case a cached handle is used. + */ +static int _debug_level = 0; + /*! Mapping between Clixon debug symbolic names <--> bitfields * * Mapping between specific bitfields and symbolic names, note only perfect matches + * @note yang_bits_map can be used as alternative but this still neeeded in bootstrapping */ static const map_str2int dbgmap[] = { {"default", CLIXON_DBG_DEFAULT}, @@ -152,16 +163,6 @@ clixon_debug_key_dump(FILE *f) return -1; } -/*! The global debug level. 0 means no debug - * - * @note There are pros and cons in having the debug state as a global variable. The - * alternative to bind it to the clicon handle (h) was considered but it limits its - * usefulness, since not all functions have access to a handle. - * A compromise solution is now in place where h can be provided in the function call, but - * tolerates NULL, in which case a cached handle is used. - */ -static int _debug_level = 0; - /*! Initialize debug messages. Set debug level. * * Initialize debug module. The level is used together with clixon_debug(dbglevel) calls as follows: diff --git a/lib/src/clixon_log.c b/lib/src/clixon_log.c index 5a5593bd..86e45796 100644 --- a/lib/src/clixon_log.c +++ b/lib/src/clixon_log.c @@ -60,6 +60,7 @@ /* clixon */ #include "clixon_queue.h" #include "clixon_hash.h" +#include "clixon_string.h" #include "clixon_handle.h" #include "clixon_yang.h" #include "clixon_xml.h" @@ -87,6 +88,47 @@ static FILE *_log_file = NULL; /* Truncate debug strings to this length. 0 means unlimited */ static int _log_trunc = 0; +/*! Mapping between Clixon debug symbolic names <--> bitfields + * + * Also inclode shorthands: s|e|o|f|n + * Mapping between specific bitfields and symbolic names, note only perfect matches + * @see typedef log_destination_t in clixon-config.yang + */ +static const map_str2int logdstmap[] = { + {"syslog", CLIXON_LOG_SYSLOG}, + {"s", CLIXON_LOG_SYSLOG}, + {"stderr", CLIXON_LOG_STDERR}, + {"e", CLIXON_LOG_STDERR}, + {"stdout", CLIXON_LOG_STDOUT}, + {"o", CLIXON_LOG_STDOUT}, + {"file", CLIXON_LOG_FILE}, + {"f", CLIXON_LOG_FILE}, + {"n", 0x0}, + {NULL, -1} +}; + +/*! Map from clixon debug (specific) bitmask to string + * + * @param[in] int Bitfield, see CLIXON_LOG_SYSLOG and others + * @retval str String representation of bitfield + */ +char * +clixon_logdst_key2str(int keyword) +{ + return (char*)clicon_int2str(logdstmap, keyword); +} + +/*! Map from clixon log destination symbolic string to bitfield + * + * @param[in] str String representation of Clixon log destination bit + * @retval int Bit representation of bitfield + */ +int +clixon_logdst_str2key(char *str) +{ + return clicon_str2int(logdstmap, str); +} + /*! Initialize system logger. * * Make syslog(3) calls with specified ident and gates calls of level upto specified level (upto). @@ -96,9 +138,7 @@ static int _log_trunc = 0; * @param[in] h Clixon handle * @param[in] ident prefix that appears on syslog (eg 'cli') * @param[in] upto log priority, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG (see syslog(3)). - * @param[in] flags bitmask: if CLIXON_LOG_STDERR, then print logs to stderr - * if CLIXON_LOG_SYSLOG, then print logs to syslog - * You can do a combination of both + * @param[in] flags Log destination bitmask * @retval 0 OK * @code * clixon_log_init(__PROGRAM__, LOG_INFO, CLIXON_LOG_STDERR); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 839f245c..e48512bd 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -73,7 +73,6 @@ #include "clixon_debug.h" #include "clixon_string.h" #include "clixon_file.h" -#include "clixon_xml_sort.h" #include "clixon_json.h" #include "clixon_text_syntax.h" #include "clixon_proto.h" @@ -83,8 +82,10 @@ #include "clixon_xpath.h" #include "clixon_yang_parse_lib.h" #include "clixon_netconf_lib.h" +#include "clixon_xml_sort.h" #include "clixon_xml_nsctx.h" #include "clixon_xml_io.h" +#include "clixon_xml_map.h" #include "clixon_validate.h" #include "clixon_xml_default.h" @@ -725,6 +726,59 @@ clicon_options_main(clixon_handle h) return retval; } +/*! Options debug and log helper function + * + * If no debug or log option set in command-line, read debug or log options from + * configure file. + * Also set log file if CLICON_LOG_FILE is set + * @param[in] h Clixon handle + * @param[in] dbg Debug bitmask + * @param[in] logdst Log destination bitmask + * @param[in] ident prefix that appears on syslog + * @retval 0 OK + * @retval -1 Error + */ +int +clixon_options_main_helper(clixon_handle h, + uint32_t dbg, + uint32_t logdst, + char *ident) +{ + int retval = -1; + int relog = 0; + char *dstr; + + relog = 0; + dstr = clicon_option_str(h, "CLICON_DEBUG"); + if (dbg == 0 && dstr && strlen(dstr)){ + if (yang_bits_map(clicon_config_yang(h), + dstr, + "/cc:clixon-config/cc:CLICON_DEBUG", + &dbg) < 0) + goto done; + relog++; + } + dstr = clicon_option_str(h, "CLICON_LOG_DESTINATION"); + if (logdst == 0 && dstr && strlen(dstr)){ + logdst = 0; + if (yang_bits_map(clicon_config_yang(h), + dstr, + "/cc:clixon-config/cc:CLICON_LOG_DESTINATION", + &logdst) < 0) + goto done; + relog++; + } + if (relog){ + clixon_debug_init(h, dbg); + clixon_log_init(h, ident, dbg?LOG_DEBUG:LOG_INFO, logdst?logdst:CLIXON_LOG_STDERR); + } + if ((dstr = clicon_option_str(h, "CLICON_LOG_FILE")) != NULL) + clixon_log_file(dstr); + retval = 0; + done: + return retval; +} + /*! Check if a clicon option has a value * * @param[in] h clixon_handle diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index fb345315..b40dfcd2 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1669,25 +1669,25 @@ yang_bits_pos(yang_stmt *ytype, goto done; } -/*! Given a YANG (bits) type node and string value, return SNMP value for bits set. +/*! Given a YANG (bits) type node and string value, return value for bits set. * * @param[in] h Clixon handle * @param[in] ytype YANG type noden * @param[in] bitsstr Value of bits as space separated string - * @param[out] snmpval SNMP value with all bits set for given bitsstr - * @param[out] snmplen length of snmpval - * @retval 1 OK, result in snmpval + * @param[out] outval Value with all bits set for given bitsstr (free with free) + * @param[out] outlen Length of outval + * @retval 1 OK, result in outval * @retval 0 Invalid, not found * @retval -1 Error * @see yang_val2bitsstr - * XXX de-snmp:ize + * @note that the output is a vector of bits originally made for SNMP bitvectors (not integers) */ int yang_bitsstr2val(clixon_handle h, yang_stmt *ytype, char *bitsstr, - unsigned char **snmpval, - size_t *snmplen) + unsigned char **outval, + size_t *outlen) { int retval = -1; int i = 0; @@ -1699,7 +1699,7 @@ yang_bitsstr2val(clixon_handle h, int ret = 0; uint32_t bitpos; - *snmplen = 0; + *outlen = 0; if ((buffer = calloc(CLIXON_BITS_POS_MAX / 8, sizeof(unsigned char))) == NULL){ clixon_err(OE_UNIX, errno, "calloc"); goto done; @@ -1719,19 +1719,19 @@ yang_bitsstr2val(clixon_handle h, /* Set bit at correct byte and bit position */ byte = bitpos / 8; buffer[byte] = buffer[byte] | (1 << (7 - (bitpos % 8))); - *snmplen = byte + 1; - if (*snmplen >= CLIXON_BITS_POS_MAX) { + *outlen = byte + 1; + if (*outlen >= CLIXON_BITS_POS_MAX) { clixon_err(OE_UNIX, EINVAL, "bit position %zu out of range. (max. allowed %d)", - *snmplen, CLIXON_BITS_POS_MAX); + *outlen, CLIXON_BITS_POS_MAX); goto done; } } } - if ((*snmpval = malloc(*snmplen)) == NULL){ + if ((*outval = malloc(*outlen)) == NULL){ clixon_err(OE_UNIX, errno, "calloc"); goto done; } - memcpy(*snmpval, buffer, *snmplen); + memcpy(*outval, buffer, *outlen); retval = 1; done: if (buffer) @@ -1744,24 +1744,81 @@ yang_bitsstr2val(clixon_handle h, goto done; } -/*! Given a YANG (bits) type node and SNMP value, return the string value for all bits (flags) that are set. +/*! Given a YANG (bits) type node and string value, return bit values in a uint64 + * + * @param[in] ytype YANG type noden + * @param[in] bitsstr Value of bits as space separated string + * @param[out] flags Pointer to integer with bit values set according to C type + * @retval 1 OK, result in u64 + * @retval 0 Invalid, not found + * @retval -1 Error + * @see yang_bitsstr2val for bit vector (snmp-like) + */ +int +yang_bitsstr2flags(yang_stmt *ytype, + char *bitsstr, + uint32_t *flags) +{ + int retval = -1; + int i = 0; + char **vec = NULL; + char *v; + int nvec; + int ret = 0; + uint32_t bitpos; + + if (flags == NULL){ + clixon_err(OE_UNIX, EINVAL, "flags is NULL"); + goto done; + } + if ((vec = clicon_strsep(bitsstr, " ", &nvec)) == NULL){ + clixon_err(OE_UNIX, EINVAL, "split string failed"); + goto done; + } + /* Go over all set flags in given bitstring */ + for (i=0; i 0) { + if ((ret = yang_bits_pos(ytype, v, &bitpos)) < 0) + goto done; + if (ret == 0) + goto fail; + if (bitpos >= 32) { + clixon_err(OE_UNIX, EINVAL, "bit position %u out of range. (max. allowed %d)", + bitpos, 32); + goto done; + } + *flags |= (1 << bitpos); + } + } + retval = 1; + done: + if (vec) + free(vec); + return retval; + fail: + retval = 0; + goto done; +} + +/*! Given a YANG (bits) type node and value, return the string value for all bits (flags) that are set. * * @param[in] h Clixon handle * @param[in] ytype YANG type noden - * @param[in] snmpval SNMP value - * @param[in] snmplen length of snmpval - * @param[out] cb space separated string with bit labes for all bits that are set in snmpval + * @param[in] inval Input string + * @param[in] inlen Length of inval + * @param[out] cb space separated string with bit labels for all bits that are set in inval * @retval 1 OK, result in cb * @retval 0 Invalid, not found * @retval -1 Error * @see yang_bitsstr2val - * XXX de-snmp:ize + * @note that the output is a vector of bits originally made for SNMP bitvectors (not integers) */ int yang_val2bitsstr(clixon_handle h, yang_stmt *ytype, - unsigned char *snmpval, - size_t snmplen, + unsigned char *inval, + size_t inlen, cbuf *cb) { int retval = -1; @@ -1778,7 +1835,7 @@ yang_val2bitsstr(clixon_handle h, goto done; } /* Go over all defined bits and check if it is seet in intval */ - while ((yprev = yn_each(ytype, yprev)) != NULL && byte < snmplen){ + while ((yprev = yn_each(ytype, yprev)) != NULL && byte < inlen){ if (yang_keyword_get(yprev) == Y_BIT) { /* Use position from Y_POSITION statement if defined */ if ((ypos = yang_find(yprev, Y_POSITION, NULL)) != NULL){ @@ -1793,7 +1850,7 @@ yang_val2bitsstr(clixon_handle h, if (is_first == 0) bitpos++; } byte = bitpos / 8; - if (snmpval[byte] & (1 << (7 - (bitpos % 8)))){ + if (inval[byte] & (1 << (7 - (bitpos % 8)))){ if (is_first == 0) cbuf_append_str(cb, " "); cbuf_append_str(cb, yang_argument_get(yprev)); } @@ -1813,6 +1870,47 @@ yang_val2bitsstr(clixon_handle h, goto done; } +/*! Map from bit string to integer bitfield given YANG mapping + * + * Given YANG node, schema-nodeid and a bits string, return a bitmap as u64 + * Example: "default app2" --> CLIXON_DBG_DEFAULT | CLIXON_DBG_APP2 + * @param[in] yt YANG node in tree (eg yspec) + * @param[in] str String representation of Clixon debug bits, such as "msg app2" + * @param[in] nodeid Absolute schema node identifier to leaf of option + * @param[out] u64 Bit representation + */ +int +yang_bits_map(yang_stmt *yt, + char *str, + char *nodeid, + uint32_t *flags) +{ + int retval = -1; + yang_stmt *yn = NULL; + yang_stmt *yrestype; + int ret; + + if (yang_abs_schema_nodeid(yt, nodeid, &yn) < 0) + goto done; + if (yn == NULL){ + clixon_err(OE_YANG, 0, "yang node not found: %s", nodeid); + goto done; + } + if (yang_type_get(yn, NULL, &yrestype, NULL, NULL, NULL, NULL, NULL) < 0) + goto done; + if (yrestype != NULL) { + if ((ret = yang_bitsstr2flags(yrestype, str, flags)) < 0) + goto done; + if (ret == 0){ + clixon_err(OE_YANG, 0, "Bit string invalid: %s", str); + goto done; + } + } + retval = 0; + done: + return retval; +} + /*! Get integer value from xml node from yang enumeration * * @param[in] node XML node in a tree diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index b879259a..d48b41e5 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -3313,9 +3313,9 @@ schema_nodeid_iterate(yang_stmt *yn, * @see yang_desc_schema_nodeid */ int -yang_abs_schema_nodeid(yang_stmt *yn, - char *schema_nodeid, - yang_stmt **yres) +yang_abs_schema_nodeid(yang_stmt *yn, + char *schema_nodeid, + yang_stmt **yres) { int retval = -1; cvec *nodeid_cvv = NULL; diff --git a/lib/src/clixon_yang_module.c b/lib/src/clixon_yang_module.c index 678aeec9..8ca7643c 100644 --- a/lib/src/clixon_yang_module.c +++ b/lib/src/clixon_yang_module.c @@ -838,12 +838,12 @@ yang_metadata_init(clixon_handle h) * Skip module if already loaded * This function is used where a yang-lib module-set is available to populate * an XML mount-point. - * @param[in] h Clixon handle + * @param[in] h Clixon handle * @param[in] xyanglib XML tree on the form ... - * @param[in] yspec Will be populated with YANGs, is consumed - * @retval 1 OK - * @retval 0 Parse error - * @retval -1 Error + * @param[in] yspec Will be populated with YANGs, is consumed + * @retval 1 OK + * @retval 0 Parse error + * @retval -1 Error * @see xml_schema_add_mount_points * XXX: Ensure yang-lib is always there otherwise get state dont work for mountpoint */ diff --git a/test/test_cli_apipath.sh b/test/test_cli_apipath.sh index fc9900ab..8177b387 100755 --- a/test/test_cli_apipath.sh +++ b/test/test_cli_apipath.sh @@ -120,10 +120,10 @@ expectpart "$($clixon_cli -1 -f $cfg show conf x)" 0 "x m1 a (null) b 22/22 c 44 # Negative tests new "err x" -expectpart "$($clixon_cli -1 -f $cfg -l n err x)" 255 "Config error: api-path syntax error \"/example2:x\": application unknown-element No such yang module prefix example2: Invalid argument" +expectpart "$($clixon_cli -1 -f $cfg -l o err x)" 255 "Config error: api-path syntax error \"/example2:x\": application unknown-element No such yang module prefix example2: Invalid argument" new "err x a" -expectpart "$($clixon_cli -1 -f $cfg -l n err x a 99)" 255 "Config error: api-path syntax error \"/example:x/m1=%s\": rpc malformed-message List key m1 length mismatch : Invalid argument" +expectpart "$($clixon_cli -1 -f $cfg -l o err x a 99)" 255 "Config error: api-path syntax error \"/example:x/m1=%s\": rpc malformed-message List key m1 length mismatch : Invalid argument" if [ $BE -ne 0 ]; then new "Kill backend" diff --git a/test/test_cli_err.sh b/test/test_cli_err.sh index 932060a8..9ed8def5 100755 --- a/test/test_cli_err.sh +++ b/test/test_cli_err.sh @@ -89,11 +89,11 @@ new "wait backend" wait_backend new "orig error" -expectpart "$($clixon_cli -1 -f $cfg -l n example error orig)" 255 "Config error: api-path syntax error " ": application invalid-value Invalid api-path: (must start with '/')" +expectpart "$($clixon_cli -1 -f $cfg -l o example error orig)" 255 "Config error: api-path syntax error " ": application invalid-value Invalid api-path: (must start with '/')" if [ ${LINKAGE} = dynamic ]; then new "customized error" -expectpart "$($clixon_cli -1 -f $cfg -l n example error custom)" 255 "My new err-string" +expectpart "$($clixon_cli -1 -f $cfg -l o example error custom)" 255 "My new err-string" fi if [ $BE -ne 0 ]; then diff --git a/test/test_debug.sh b/test/test_debug.sh index fe1b4fe2..5d3e2e1e 100755 --- a/test/test_debug.sh +++ b/test/test_debug.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Turn on debug on backend/cli/netconf -# Note, restconf debug used to be tested but is no longer tested here, -# maybe in test_restconf_internal? +# Also some log destination tests +# Note no restconf debug test # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -84,15 +84,57 @@ wait_restconf new "Set backend debug using netconf" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "1" "" "" -new "Set cli debug using cli" +# Debug +new "cli debug cli" expectpart "$($clixon_cli -1 -f $cfg -l o debug cli 1)" 0 "^$" -# Run cli debug -new "get cli debug, expect 0" +new "cli debug, expect 0" expectpart "$($clixon_cli -1 -f $cfg show debug cli)" 0 "CLI debug:0x0" -new "get cli debug expect 2" -expectpart "$($clixon_cli -1 -f $cfg -o CLICON_DEBUG=msg show debug cli)" 0 "CLI debug:0x2" +new "cli debug -o single" +expectpart "$($clixon_cli -1 -f $cfg -o CLICON_DEBUG=msg show debug cli)" 0 "CLI debug:0x2" --not-- "CLI debug:0x20" + +new "cli debug -o multi" +expectpart "$($clixon_cli -1 -f $cfg -o CLICON_DEBUG="msg app2" show debug cli)" 0 "CLI debug:0x200002" + +new "cli debug -D multi" +expectpart "$($clixon_cli -1 -f $cfg -D msg -D app2 show debug cli)" 0 + +# Log destination +new "cli log -lf" +rm -f $dir/clixon.log +expectpart "$($clixon_cli -1 -lf$dir/clixon.log -f $cfg show version)" 0 +if [ ! -f "$dir/clixon.log" ]; then + err "$dir/clixon.log" "No file" +fi + +new "cli log -lfile" +rm -f $dir/clixon.log +expectpart "$($clixon_cli -1 -lfile -f $cfg show version)" 0 +if [ -f "$dir/clixon.log" ]; then + err "No file" "$dir/clixon.log" +fi + +new "cli log -lfile + CLICON_LOG_FILE" +rm -f $dir/clixon.log +expectpart "$($clixon_cli -1 -lfile -o CLICON_LOG_FILE=$dir/clixon.log -f $cfg show version)" 0 +if [ ! -f "$dir/clixon.log" ]; then + err "$dir/clixon.log" "No file" +fi + +rm -f $dir/clixon.log +new "cli log -o CLICON_LOG_DESTINATION + CLICON_LOG_FILE" +expectpart "$($clixon_cli -1 -o CLICON_LOG_DESTINATION=file -o CLICON_LOG_FILE=$dir/clixon.log -f $cfg show version)" 0 +if [ ! -f "$dir/clixon.log" ]; then + err "$dir/clixon.log" "No file" +fi + +rm -f $dir/clixon.log +new "cli log -o CLICON_LOG_DESTINATION + CLICON_LOG_FILE multi" +expectpart "$($clixon_cli -1 -o CLICON_LOG_DESTINATION="stdout file" -o CLICON_LOG_FILE=$dir/clixon.log -f $cfg show version)" 0 +if [ ! -f "$dir/clixon.log" ]; then + err "$dir/clixon.log" "No file" +fi new "Set backend debug using cli" expectpart "$($clixon_cli -1 -f $cfg -l o debug backend 1)" 0 "^$" diff --git a/yang/clixon/clixon-config@2024-04-01.yang b/yang/clixon/clixon-config@2024-04-01.yang index 75e652ea..2b967f11 100644 --- a/yang/clixon/clixon-config@2024-04-01.yang +++ b/yang/clixon/clixon-config@2024-04-01.yang @@ -52,6 +52,8 @@ module clixon-config { revision 2024-04-01 { description "Added options: + CLICON_LOG_DESTINATION: Default log destination + CLICON_LOG_FILE: Which file to log to if file logging CLICON_DEBUG: Debug flags. CLICON_YANG_SCHEMA_MOUNT_SHARE: Share same YANGs of equal moint-points. CLICON_SOCK_PRIO: Enable socket event priority @@ -426,6 +428,33 @@ module clixon-config { } } } + typedef log_destination_t { + description + "Log destination flags + Can also be given directly as -l to clixon commands + Note there are also constants in the code (logdstmap) that need to be + in sync with these values. + The duplication is because of bootstrapping, logging is needed before YANG + loaded"; + type bits { + bit syslog { + position 0; + description "Syslog"; + } + bit stderr { + position 1; + description "Standard I/O Error"; + } + bit stdout { + position 2; + description "Standard I/O Output"; + } + bit file { + position 3; + description "Log to file. By default clixon.log int current directory"; + } + } + } container clixon-config { container restconf { uses clrc:clixon-restconf; @@ -456,14 +485,7 @@ module clixon-config { Ensure that YANG_INSTALLDIR (default /usr/local/share/clixon) is present in the path"; } - leaf CLICON_DEBUG{ - type cl:clixon_debug_t; - description - "Debug flags as bitfields. - Can also be given directly as -D to clixon commands (which overrides this) - Note only partly implemented; - - Only CLI, only single value, cannot be combined with -D, not in RPC"; - } + /* Configuration */ leaf CLICON_CONFIGFILE{ type string; description @@ -497,6 +519,7 @@ module clixon-config { This field is a 'bootstrap' field. "; } + /* YANG */ leaf CLICON_YANG_MAIN_FILE { type string; description @@ -599,6 +622,7 @@ module clixon-config { See also CLICON_XMLDB_MODSTATE where the module state info is used to tag datastores with module information."; } + /* Backend */ leaf CLICON_BACKEND_DIR { type string; description @@ -653,6 +677,7 @@ module clixon-config { - on enable change, make the state as configured Disable if you start the restconf daemon by other means."; } + /* Netconf */ leaf CLICON_NETCONF_DIR{ type string; description "Location of netconf (frontend) .so plugins"; @@ -725,6 +750,7 @@ module clixon-config { apart from NETCONF. Only if CLICON_NETCONF_MONITORING"; } + /* HTTP and Restconf */ leaf CLICON_RESTCONF_API_ROOT { type string; default "/restconf"; @@ -840,6 +866,7 @@ module clixon-config { Both feature clixon-restconf:http-data and restconf/enable-http-data must be enabled for this match to occur."; } + /* Clixon CLI */ leaf CLICON_CLI_DIR { type string; description @@ -987,6 +1014,7 @@ module clixon-config { description "Default CLI output format."; } + /* Internal socket */ leaf CLICON_SOCK_FAMILY { type socket_address_family; default UNIX; @@ -1056,6 +1084,7 @@ module clixon-config { Also, any edits in candidate are discarded if the client closes the connection. This effectively disables shared candidate"; } + /* Datastore XMLDB */ leaf CLICON_DATASTORE_CACHE { type datastore_cache; default cache; @@ -1201,6 +1230,7 @@ module clixon-config { The current only case where such a user is used is in RESTCONF authentication when auth-type=none and no known user is known."; } + /* Network Configuration Access Control Model (NACM) */ leaf CLICON_NACM_MODE { type nacm_mode; default disabled; @@ -1255,6 +1285,7 @@ module clixon-config { If CLICON_MODULE_LIBRARY_RFC7895 is enabled, it sets the modules-state/module-set-id instead"; } + /* Notification streams */ leaf CLICON_STREAM_DISCOVERY_RFC5277 { type boolean; default false; @@ -1311,6 +1342,27 @@ module clixon-config { description "Retention for stream replay buffers in seconds, ie how much data to store before dropping. 0 means no retention"; } + /* Log and debug */ + leaf CLICON_DEBUG{ + type cl:clixon_debug_t; + description + "Debug flags as bitfields. + Can also be given directly as -D to clixon commands (which overrides this)."; + } + leaf CLICON_LOG_DESTINATION { + type log_destination_t; + description + "Log destination. + If not given, default log destination is syslog for all applications, + except clixon_cli where default is stderr. + See also command-line option -l "; + } + leaf CLICON_LOG_FILE { + type string; + description + "Which file to log to if log destination is file + That is CLIXON_LOG_DESTINATION is FILE or command started with -l f"; + } leaf CLICON_LOG_STRING_LIMIT { type uint32; default 0; @@ -1318,8 +1370,8 @@ module clixon-config { "Length limitation of debug and log strings. Especially useful for dynamic debug strings, such as packet dumps. 0 means no limit"; - } + /* SNMP */ leaf-list CLICON_SNMP_MIB { description "Names of MIBs that are used by clixon_snmp. diff --git a/yang/clixon/clixon-lib@2024-04-01.yang b/yang/clixon/clixon-lib@2024-04-01.yang index 0ebfb11e..fcd541bb 100644 --- a/yang/clixon/clixon-lib@2024-04-01.yang +++ b/yang/clixon/clixon-lib@2024-04-01.yang @@ -193,7 +193,7 @@ module clixon-lib { "Debug flags. Flags are seperated into subject areas and detail Can also be given directly as -D to clixon commands - Note there are also constants in the code thaht need to be in sync with these values"; + Note there are also constants in the code that need to be in sync with these values"; type bits { /* Subjects: */ bit default { @@ -345,7 +345,9 @@ module clixon-lib { A sub-object will not be noted"; } rpc debug { - description "Set debug level of backend."; + description + "Set debug flags of backend. + Note only numerical values"; input { leaf level { type uint32;