From: David Lamparter Date: Wed, 28 Sep 2016 21:19:50 +0000 (+0200) Subject: lib: "ferr" error-information system X-Git-Tag: frr-4.0-dev~342^2~1 X-Git-Url: https://git.puffer.fish/?a=commitdiff_plain;h=3155489aa23304b718bb1bbd8964db0461a1c3b1;p=mirror%2Ffrr.git lib: "ferr" error-information system This provides an API to pass around extra information for errors, more than a simple return value can carry. This is particularly used for the Cap'n Proto interface to be able to report more useful errors. Signed-off-by: David Lamparter --- diff --git a/lib/ferr.c b/lib/ferr.c new file mode 100644 index 0000000000..2a039d2089 --- /dev/null +++ b/lib/ferr.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "ferr.h" +#include "vty.h" +#include "jhash.h" +#include "memory.h" + +DEFINE_MTYPE_STATIC(LIB, ERRINFO, "error information") + +static pthread_key_t errkey; + +static void ferr_free(void *arg) +{ + XFREE(MTYPE_ERRINFO, arg); +} + +static void err_key_init(void) __attribute__((_CONSTRUCTOR(500))); +static void err_key_init(void) +{ + pthread_key_create(&errkey, ferr_free); +} + +static void err_key_fini(void) __attribute__((_DESTRUCTOR(500))); +static void err_key_fini(void) +{ + pthread_key_delete(errkey); +} + +const struct ferr *ferr_get_last(ferr_r errval) +{ + struct ferr *last_error = pthread_getspecific(errkey); + if (!last_error || last_error->kind == 0) + return NULL; + return last_error; +} + +ferr_r ferr_clear(void) +{ + struct ferr *last_error = pthread_getspecific(errkey); + if (last_error) + last_error->kind = 0; + return ferr_ok(); +} + +static ferr_r ferr_set_va(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, int errno_val, + const char *text, va_list va) +{ + struct ferr *error = pthread_getspecific(errkey); + + if (!error) { + error = XCALLOC(MTYPE_ERRINFO, sizeof(*error)); + if (!error) { + /* we're screwed */ + zlog_err("out of memory while allocating error info"); + raise(SIGSEGV); + } + + pthread_setspecific(errkey, error); + } + + error->file = file; + error->line = line; + error->func = func; + error->kind = kind; + + error->unique_id = jhash(text, strlen(text), + jhash(file, strlen(file), 0xd4ed0298)); + + error->errno_val = errno_val; + if (pathname) + snprintf(error->pathname, sizeof(error->pathname), + "%s", pathname); + else + error->pathname[0] = '\0'; + + vsnprintf(error->message, sizeof(error->message), text, va); + return -1; +} + +ferr_r ferr_set_internal(const char *file, int line, const char *func, + enum ferr_kind kind, const char *text, ...) +{ + ferr_r rv; + va_list va; + va_start(va, text); + rv = ferr_set_va(file, line, func, kind, NULL, 0, text, va); + va_end(va); + return rv; +} + +ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, int errno_val, + const char *text, ...) +{ + ferr_r rv; + va_list va; + va_start(va, text); + rv = ferr_set_va(file, line, func, kind, pathname, errno_val, text, va); + va_end(va); + return rv; +} + +#define REPLACE "$ERR" +void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...) +{ + char tmpmsg[512], *replacepos; + const struct ferr *last_error = ferr_get_last(err); + + va_list va; + va_start(va, msg); + vsnprintf(tmpmsg, sizeof(tmpmsg), msg, va); + va_end(va); + + replacepos = strstr(tmpmsg, REPLACE); + if (!replacepos) + vty_out(vty, "%s\n", tmpmsg); + else { + replacepos[0] = '\0'; + replacepos += sizeof(REPLACE) - 1; + vty_out(vty, "%s%s%s\n", + tmpmsg, + last_error ? last_error->message : "(no error?)", + replacepos); + } +} + diff --git a/lib/ferr.h b/lib/ferr.h new file mode 100644 index 0000000000..a570637372 --- /dev/null +++ b/lib/ferr.h @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2015-16 David Lamparter, for NetDEF, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _FRR_FERR_H +#define _FRR_FERR_H + +/*********************************************************** + * scroll down to the end of this file for a full example! * + ***********************************************************/ + +#include +#include +#include + +/* return type when this error indication stuff is used. + * + * guaranteed to have boolean evaluation to "false" when OK, "true" when error + * (i.e. can be changed to pointer in the future if neccessary) + * + * For checking, always use "if (value)", nothing else. + * Do _NOT_ use any integer constant (!= 0), or sign check (< 0). + */ +typedef int ferr_r; + +/* rough category of error indication */ +enum ferr_kind { + /* no error */ + FERR_OK = 0, + + /* something isn't the way it's supposed to be. + * (things that might otherwise be asserts, really) + */ + FERR_CODE_BUG, + + /* user-supplied parameters don't make sense or is inconsistent + * if you can express a rule for it (e.g. "holdtime > 2 * keepalive"), + * it's this category. + */ + FERR_CONFIG_INVALID, + + /* user-supplied parameters don't line up with reality + * (IP address or interface not available, etc.) + * NB: these are really TODOs where the code needs to be fixed to + * respond to future changes! + */ + FERR_CONFIG_REALITY, + + /* out of some system resource (probably memory) + * aka "you didn't spend enough money error" */ + FERR_RESOURCE, + + /* system error (permission denied, etc.) */ + FERR_SYSTEM, + + /* error return from some external library + * (FERR_SYSTEM and FERR_LIBRARY are not strongly distinct) */ + FERR_LIBRARY, +}; + +struct ferr { + /* code location */ + const char *file; + const char *func; + int line; + + enum ferr_kind kind; + + /* unique_id is calculated as a checksum of source filename and error + * message format (*before* calling vsnprintf). Line number and + * function name are not used; this keeps the number reasonably static + * across changes. + */ + uint32_t unique_id; + + char message[384]; + + /* valid if != 0. note "errno" might be preprocessor foobar. */ + int errno_val; + /* valid if pathname[0] != '\0' */ + char pathname[PATH_MAX]; +}; + +/* get error details. + * + * NB: errval/ferr_r does NOT carry the full error information. It's only + * passed around for future API flexibility. ferr_get_last always returns + * the last error set in the current thread. + */ +const struct ferr *ferr_get_last(ferr_r errval); + +/* can optionally be called at strategic locations. + * always returns 0. */ +ferr_r ferr_clear(void); + +/* do NOT call these functions directly. only for macro use! */ +ferr_r ferr_set_internal(const char *file, int line, const char *func, + enum ferr_kind kind, const char *text, ...); +ferr_r ferr_set_internal_ext(const char *file, int line, const char *func, + enum ferr_kind kind, const char *pathname, int errno_val, + const char *text, ...); + +#define ferr_ok() \ + 0 + +/* Report an error. + * + * If you need to do cleanup (free memory, etc.), save the return value in a + * variable of type ferr_r. + * + * Don't put a \n at the end of the error message. + */ +#define ferr_code_bug(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CODE_BUG, \ + __VA_ARGS__) +#define ferr_cfg_invalid(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_INVALID, \ + __VA_ARGS__) +#define ferr_cfg_reality(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_CONFIG_REALITY, \ + __VA_ARGS__) +#define ferr_cfg_resource(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_RESOURCE, \ + __VA_ARGS__) +#define ferr_system(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_SYSTEM, \ + __VA_ARGS__) +#define ferr_library(...) \ + ferr_set_internal(__FILE__, __LINE__, __func__, FERR_LIBRARY, \ + __VA_ARGS__) + +/* extended information variants */ +#define ferr_system_errno(...) \ + ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, \ + NULL, errno, __VA_ARGS__) +#define ferr_system_path_errno(path, ...) \ + ferr_set_internal_ext(__FILE__, __LINE__, __func__, FERR_SYSTEM, \ + path, errno, __VA_ARGS__) + +#include "vty.h" +/* print error message to vty; $ERR is replaced by the error's message */ +void vty_print_error(struct vty *vty, ferr_r err, const char *msg, ...); + +#define CMD_FERR_DO(func, action, ...) \ + do { ferr_r cmd_retval = func; \ + if (cmd_retval) { \ + vty_print_error(vty, cmd_retval, __VA_ARGS__); \ + action; \ + } \ + } while (0) + +#define CMD_FERR_RETURN(func, ...) \ + CMD_FERR_DO(func, return CMD_WARNING, __VA_ARGS__) +#define CMD_FERR_GOTO(func, label, ...) \ + CMD_FERR_DO(func, goto label, __VA_ARGS__) + +/* example: + +ferr_r foo_bar_set(struct object *obj, int bar) +{ + if (bar < 1 || bar >= 100) + return ferr_config_invalid("bar setting (%d) must be 0bar = bar; + if (ioctl (obj->fd, bar)) + return ferr_system_errno("couldn't set bar to %d", bar); + + return ferr_ok(); +} + +DEFUN("bla") +{ + CMD_FERR_RETURN(foo_bar_set(obj, atoi(argv[1])), + "command failed: $ERR\n"); + return CMD_SUCCESS; +} + +*/ + +#endif /* _FERR_H */ diff --git a/lib/subdir.am b/lib/subdir.am index d6349ba22d..eaa6cd6ec8 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -17,6 +17,7 @@ lib_libfrr_la_SOURCES = \ lib/csv.c \ lib/distribute.c \ lib/event_counter.c \ + lib/ferr.c \ lib/filter.c \ lib/frr_pthread.c \ lib/getopt.c \ @@ -89,6 +90,7 @@ pkginclude_HEADERS += \ lib/csv.h \ lib/distribute.h \ lib/event_counter.h \ + lib/ferr.h \ lib/fifo.h \ lib/filter.h \ lib/freebsd-queue.h \