Error handling for CLI

Continue, do not exit on read/expand errors
Accept -1 error without clicon_err in callbacks / expand
C-API: Three-value return of clicon_cliread
This commit is contained in:
Olof hagsand 2022-11-19 09:22:44 +01:00
parent f82ce896a9
commit caef594dbe
4 changed files with 58 additions and 35 deletions

View file

@ -156,7 +156,6 @@ cli_history_save(clicon_handle h)
return retval; return retval;
} }
/*! Clean and close all state of cli process (but dont exit). /*! Clean and close all state of cli process (but dont exit).
* Cannot use h after this * Cannot use h after this
* @param[in] h Clixon handle * @param[in] h Clixon handle
@ -183,7 +182,7 @@ cli_terminate(clicon_handle h)
/* Delete all plugins, and RPC callbacks */ /* Delete all plugins, and RPC callbacks */
clixon_plugin_module_exit(h); clixon_plugin_module_exit(h);
/* Delete CLI syntax et al */ /* Delete CLI syntax et al */
cli_plugin_finish(h); cli_plugin_finish(h);
cli_history_save(h); cli_history_save(h);
cli_handle_exit(h); cli_handle_exit(h);
@ -208,7 +207,7 @@ static int
cli_signal_init (clicon_handle h) cli_signal_init (clicon_handle h)
{ {
int retval = -1; int retval = -1;
cli_signal_block(h); cli_signal_block(h);
if (set_signal(SIGTERM, cli_sig_term, NULL) < 0){ if (set_signal(SIGTERM, cli_sig_term, NULL) < 0){
clicon_err(OE_UNIX, errno, "Setting SIGTERM signal"); clicon_err(OE_UNIX, errno, "Setting SIGTERM signal");
@ -236,17 +235,22 @@ cli_interactive(clicon_handle h)
char *cmd; char *cmd;
char *new_mode; char *new_mode;
cligen_result result; cligen_result result;
int ret;
/* Loop through all commands */ /* Loop through all commands */
while(!cligen_exiting(cli_cligen(h))) { while(!cligen_exiting(cli_cligen(h))) {
new_mode = cli_syntax_mode(h); new_mode = cli_syntax_mode(h);
cmd = NULL; cmd = NULL;
if (clicon_cliread(h, &cmd) < 0) /* Read a CLI string, including expand handling */
if ((ret = clicon_cliread(h, &cmd)) < 0)
goto done; goto done;
if (ret == 0)
continue;
if (cmd == NULL) { /* EOF */ if (cmd == NULL) { /* EOF */
cligen_exiting_set(cli_cligen(h), 1); cligen_exiting_set(cli_cligen(h), 1);
continue; continue;
} }
/* Here errors are handled */
if (clicon_parse(h, cmd, &new_mode, &result, NULL) < 0) if (clicon_parse(h, cmd, &new_mode, &result, NULL) < 0)
goto done; goto done;
/* Why not check result? */ /* Why not check result? */
@ -265,7 +269,7 @@ autocli_trees_default(clicon_handle h)
int retval = -1; int retval = -1;
cbuf *cb = NULL; cbuf *cb = NULL;
int mode = 0; int mode = 0;
parse_tree *pt = NULL; parse_tree *pt = NULL;
pt_head *ph; pt_head *ph;
/* Create backward compatible tree: @datamodel */ /* Create backward compatible tree: @datamodel */
@ -446,7 +450,7 @@ main(int argc,
char **argv) char **argv)
{ {
int retval = -1; int retval = -1;
int c; int c;
int once; int once;
char *tmp; char *tmp;
char *argv0 = argv[0]; char *argv0 = argv[0];
@ -470,7 +474,7 @@ main(int argc,
once = 0; once = 0;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst); clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Initiate CLICON handle. CLIgen is also initialized */ /* Initiate CLICON handle. CLIgen is also initialized */
if ((h = cli_handle_init()) == NULL) if ((h = cli_handle_init()) == NULL)
@ -502,7 +506,7 @@ main(int argc,
But this means that we need to check if 'help' is set before But this means that we need to check if 'help' is set before
exiting, and then call usage() before exit. exiting, and then call usage() before exit.
*/ */
help = 1; help = 1;
break; break;
case 'D' : /* debug */ case 'D' : /* debug */
if (sscanf(optarg, "%d", &dbg) != 1) if (sscanf(optarg, "%d", &dbg) != 1)
@ -531,7 +535,7 @@ main(int argc,
* Logs, error and debug to stderr or syslog, set debug level * Logs, error and debug to stderr or syslog, set debug level
*/ */
clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst); clicon_log_init(__PROGRAM__, dbg?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(dbg, NULL); clicon_debug_init(dbg, NULL);
yang_init(h); yang_init(h);
/* Find, read and parse configfile */ /* Find, read and parse configfile */
@ -555,7 +559,7 @@ main(int argc,
fprintf(stderr, "freopen: %s\n", strerror(errno)); fprintf(stderr, "freopen: %s\n", strerror(errno));
return -1; return -1;
} }
break; break;
case '1' : /* Quit after reading database once - dont wait for events */ case '1' : /* Quit after reading database once - dont wait for events */
once = 1; once = 1;
break; break;
@ -672,7 +676,7 @@ main(int argc,
Should be 0 but default is 1 since all legacy apps use 1 Should be 0 but default is 1 since all legacy apps use 1
Test legacy before shifting default to 0 Test legacy before shifting default to 0
*/ */
cligen_exclude_keys_set(cli_cligen(h), clicon_cli_varonly(h)); cligen_exclude_keys_set(cli_cligen(h), clicon_cli_varonly(h));
/* Initialize plugin module by creating a handle holding plugin and callback lists */ /* Initialize plugin module by creating a handle holding plugin and callback lists */
if (clixon_plugin_module_init(h) < 0) if (clixon_plugin_module_init(h) < 0)
@ -702,7 +706,7 @@ main(int argc,
/* Create top-level and store as option */ /* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
goto done; goto done;
clicon_dbspec_yang_set(h, yspec); clicon_dbspec_yang_set(h, yspec);
/* Load Yang modules /* Load Yang modules
* 1. Load a yang module as a specific absolute filename */ * 1. Load a yang module as a specific absolute filename */
@ -815,7 +819,6 @@ main(int argc,
goto done; goto done;
} }
/* Go into event-loop unless -1 command-line */ /* Go into event-loop unless -1 command-line */
if (!once){ if (!once){
retval = cli_interactive(h); retval = cli_interactive(h);

View file

@ -516,15 +516,17 @@ cli_plugin_finish(clicon_handle h)
int int
cli_handler_err(FILE *f) cli_handler_err(FILE *f)
{ {
if (clicon_errno && if (clicon_errno){
(clicon_get_logflags() & CLICON_LOG_STDERR) == 0){ /* Check if error is already logged on stderr */
fprintf(f, "%s: %s", clicon_strerror(clicon_errno), clicon_err_reason); if ((clicon_get_logflags() & CLICON_LOG_STDERR) == 0){
if (clicon_suberrno) fprintf(f, "%s: %s", clicon_strerror(clicon_errno), clicon_err_reason);
fprintf(f, ": %s", strerror(clicon_suberrno)); if (clicon_suberrno)
fprintf(f, "\n"); fprintf(f, ": %s", strerror(clicon_suberrno));
fprintf(f, "\n");
}
else
fprintf(f, "CLI command error\n");
} }
else
fprintf(f, "CLI command error\n");
return 0; return 0;
} }
@ -595,8 +597,8 @@ clicon_parse(clicon_handle h,
clicon_debug(1, "%s result:%d command: \"%s\"", __FUNCTION__, *result, cmd); clicon_debug(1, "%s result:%d command: \"%s\"", __FUNCTION__, *result, cmd);
switch (*result) { switch (*result) {
case CG_EOF: /* eof */ case CG_EOF: /* eof */
case CG_ERROR: case CG_ERROR:
fprintf(f, "CLI parse error: %s\n", cmd); fprintf(f, "CLI parse error: %s\n", cmd); // In practice never happens
break; break;
case CG_NOMATCH: /* no match */ case CG_NOMATCH: /* no match */
fprintf(f, "CLI syntax error: \"%s\": %s\n", cmd, reason); fprintf(f, "CLI syntax error: \"%s\": %s\n", cmd, reason);
@ -741,7 +743,8 @@ cli_prompt_get(clicon_handle h,
/*! Read command from CLIgen's cliread() using current syntax mode. /*! Read command from CLIgen's cliread() using current syntax mode.
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[out] stringp Pointer to command buffer or NULL on EOF * @param[out] stringp Pointer to command buffer or NULL on EOF
* @retval 0 OK * @retval 1 OK
* @retval 0 Fail but continue
* @retval -1 Error * @retval -1 Error
*/ */
int int
@ -755,7 +758,7 @@ clicon_cliread(clicon_handle h,
cli_prompthook_t *fn; cli_prompthook_t *fn;
clixon_plugin_t *cp; clixon_plugin_t *cp;
char *promptstr; char *promptstr;
stx = cli_syntax(h); stx = cli_syntax(h);
mode = stx->stx_active_mode; mode = stx->stx_active_mode;
/* Get prompt from plugin callback? */ /* Get prompt from plugin callback? */
@ -776,15 +779,22 @@ clicon_cliread(clicon_handle h,
} }
cligen_ph_active_set_byname(cli_cligen(h), mode->csm_name); cligen_ph_active_set_byname(cli_cligen(h), mode->csm_name);
clicon_err_reset();
if (cliread(cli_cligen(h), stringp) < 0){ if (cliread(cli_cligen(h), stringp) < 0){
clicon_err(OE_FATAL, errno, "CLIgen"); cli_handler_err(stdout);
goto done; if (clicon_suberrno == ESHUTDOWN)
goto done;
goto fail;
} }
retval = 0;
retval = 1;
done: done:
if (pfmt) if (pfmt)
free(pfmt); free(pfmt);
return retval; return retval;
fail:
retval = 0;
goto done;
} }
/* /*

View file

@ -101,7 +101,7 @@ extern char clicon_err_reason[ERR_STRLEN];
/* /*
* Macros * Macros
*/ */
#define clicon_err(e,s,_fmt, args...) clicon_err_fn(__FUNCTION__, __LINE__, (e), (s), _fmt , ##args) #define clicon_err(e,s,_fmt, args...) clicon_err_fn(__FUNCTION__, __LINE__, (e), (s), _fmt , ##args)
/* /*
* Prototypes * Prototypes

View file

@ -97,8 +97,8 @@ static clixon_err_cats *_err_cat_list = NULL;
/* /*
* Variables * Variables
*/ */
int clicon_errno = 0; /* See enum clicon_err XXX: hide this and change to err_category */ int clicon_errno = 0; /* See enum clicon_err XXX: hide this and change to err_category */
int clicon_suberrno = 0; /* Corresponds to errno.h XXX: change to errno */ int clicon_suberrno = 0; /* Corresponds to errno.h XXX: change to errno */
char clicon_err_reason[ERR_STRLEN] = {0, }; char clicon_err_reason[ERR_STRLEN] = {0, };
/* /*
@ -155,6 +155,10 @@ clicon_err_reset(void)
} }
/*! Find error category struct given name /*! Find error category struct given name
*
* @param[in] category Error category
* @retval cec error category callback structs
* @retval NULL Not found
*/ */
static struct clixon_err_cats * static struct clixon_err_cats *
find_category(int category) find_category(int category)
@ -181,6 +185,13 @@ find_category(int category)
* - Logs to syslog with LOG_ERR * - Logs to syslog with LOG_ERR
* - Set global error variable name clicon_errno * - Set global error variable name clicon_errno
* - Set global reason string clicon_err_reason * - Set global reason string clicon_err_reason
* Typically a call to clicon_err() is followed by a return -1 (or NULL) that signals
* a fatal error that fails early and loud.
* However there are some cases where such an error does not cause an exit. This includes
* CLI operations of callbacks and expand functions. The reason is that user-defined errors
* should just signal an error and not terminate. To override this one can set a suberr to
* ESHUTDOWN.
*
* @note: err direction (syslog and/or stderr) controlled by clicon_log_init() * @note: err direction (syslog and/or stderr) controlled by clicon_log_init()
* *
* @param[in] fn Inline function name (when called from clicon_err() macro) * @param[in] fn Inline function name (when called from clicon_err() macro)
@ -227,7 +238,6 @@ clicon_err_fn(const char *fn,
} }
va_end(args); va_end(args);
strncpy(clicon_err_reason, msg, ERR_STRLEN-1); strncpy(clicon_err_reason, msg, ERR_STRLEN-1);
/* Check category callbacks as defined in clixon_err_cat_reg */ /* Check category callbacks as defined in clixon_err_cat_reg */
if ((cec = find_category(category)) != NULL && if ((cec = find_category(category)) != NULL &&
cec->cec_logfn){ cec->cec_logfn){
@ -282,7 +292,7 @@ clicon_err_fn(const char *fn,
msg); msg);
} }
retval = 0; retval = 0;
done: done:
if (msg) if (msg)
free(msg); free(msg);
return retval; return retval;