From 0645c9fc2c7f801ba3c7d68a17c137a63ada299f Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:58:34 +0100 Subject: Implement new output functionality --- configure.ac | 13 + lib/Makefile.am | 5 +- lib/monitoringplug.h | 8 + lib/output.c | 464 ++++++ lib/output.h | 87 ++ lib/perfdata.c | 516 +++++++ lib/perfdata.h | 203 +++ lib/states.h | 71 + lib/tests/Makefile.am | 6 +- lib/tests/test_generic_output.c | 315 ++++ lib/tests/test_generic_output.t | 6 + lib/thresholds.c | 59 + lib/thresholds.h | 28 + lib/utils_base.c | 167 +- lib/utils_base.h | 26 +- lib/vendor/cJSON/.deps/.dirstamp | 0 lib/vendor/cJSON/.deps/cJSON.Po | 170 ++ lib/vendor/cJSON/.dirstamp | 0 lib/vendor/cJSON/cJSON.c | 3165 ++++++++++++++++++++++++++++++++++++++ lib/vendor/cJSON/cJSON.h | 306 ++++ plugins/common.h | 9 +- plugins/popen.c | 1 - plugins/runcmd.c | 1 + plugins/sslutils.c | 1 + plugins/utils.c | 48 - plugins/utils.h | 11 +- 26 files changed, 5551 insertions(+), 135 deletions(-) create mode 100644 lib/monitoringplug.h create mode 100644 lib/output.c create mode 100644 lib/output.h create mode 100644 lib/perfdata.c create mode 100644 lib/perfdata.h create mode 100644 lib/states.h create mode 100644 lib/tests/test_generic_output.c create mode 100644 lib/tests/test_generic_output.t create mode 100644 lib/thresholds.c create mode 100644 lib/thresholds.h create mode 100644 lib/vendor/cJSON/.deps/.dirstamp create mode 100644 lib/vendor/cJSON/.deps/cJSON.Po create mode 100644 lib/vendor/cJSON/.dirstamp create mode 100644 lib/vendor/cJSON/cJSON.c create mode 100644 lib/vendor/cJSON/cJSON.h diff --git a/configure.ac b/configure.ac index 0432336b..b12cf6c5 100644 --- a/configure.ac +++ b/configure.ac @@ -190,6 +190,19 @@ if test "$enable_libtap" = "yes" ; then AC_SUBST(EXTRA_PLUGIN_TESTS) fi +dnl JSON capabilities (cjson) +AC_ARG_ENABLE([json-output], + AC_HELP_STRING([--enable-json-output], + [Enables switching the output format to JSON (default: yes)]), + [case "${enableval}" in + yes) json-ouput=true ;; + no) json-ouput=false ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-json-ouput]) ;; + esac], [json_output=true]) + +AM_CONDITIONAL([ENALBE_JSON_OUTPUT], [test x$json_output = xtrue]) + + dnl INI Parsing AC_ARG_ENABLE(extra-opts, AC_HELP_STRING([--enable-extra-opts], diff --git a/lib/Makefile.am b/lib/Makefile.am index dc3ee893..0ea33dd8 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -7,8 +7,8 @@ noinst_LIBRARIES = libmonitoringplug.a AM_CPPFLAGS = -DNP_STATE_DIR_PREFIX=\"$(localstatedir)\" \ -I$(srcdir) -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/plugins -libmonitoringplug_a_SOURCES = utils_base.c utils_disk.c utils_tcp.c utils_cmd.c maxfd.c -EXTRA_DIST = utils_base.h utils_disk.h utils_tcp.h utils_cmd.h parse_ini.h extra_opts.h maxfd.h +libmonitoringplug_a_SOURCES = utils_base.c utils_disk.c utils_tcp.c utils_cmd.c maxfd.c output.c perfdata.c output.c thresholds.c vendor/cJSON/cJSON.c +EXTRA_DIST = utils_base.h utils_disk.h utils_tcp.h utils_cmd.h parse_ini.h extra_opts.h maxfd.h perfdata.h output.h thresholds.h states.h vendor/cJSON/cJSON.h if USE_PARSE_INI libmonitoringplug_a_SOURCES += parse_ini.c extra_opts.c @@ -16,4 +16,3 @@ endif USE_PARSE_INI test test-debug: cd tests && make $@ - diff --git a/lib/monitoringplug.h b/lib/monitoringplug.h new file mode 100644 index 00000000..5f05a974 --- /dev/null +++ b/lib/monitoringplug.h @@ -0,0 +1,8 @@ +#pragma once + +#include "./states.h" +#include "./utils.h" +#include "./utils_base.h" +#include "./thresholds.h" +#include "./maxfd.h" +#include "./output.h" diff --git a/lib/output.c b/lib/output.c new file mode 100644 index 00000000..9ba049e2 --- /dev/null +++ b/lib/output.c @@ -0,0 +1,464 @@ +#include "./output.h" +#include "./utils_base.h" +#include "../plugins/utils.h" + +#include +#include +#include +#include +// #include +#include "./vendor/cJSON/cJSON.h" + +// == Prototypes == +static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation); +static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck); + +// == Implementation == + +/* + * Generate output string for a mp_subcheck object + */ +static inline char *fmt_subcheck_perfdata(mp_subcheck check) { + char *result = strdup(""); + int added = 0; + + if (check.perfdata != NULL) { + added = xasprintf(&result, "%s", pd_list_to_string(*check.perfdata)); + } + + if (check.subchecks == NULL) { + // No subchecks, return here + return result; + } + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + if (added > 0) { + added = xasprintf(&result, "%s%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); + } else { + // TODO free previous result here? + added = xasprintf(&result, "%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); + } + + subchecks = subchecks->next; + } + + return result; +} + +/* + * Initialiser for a mp_check object. Always use this to get a new one! + * It sets useful defaults + */ +mp_check mp_check_init(void) { + mp_check check = {0}; + check.format = MP_FORMAT_DEFAULT; + return check; +} + +/* + * Initialiser for a mp_subcheck object. Always use this to get a new one! + * It sets useful defaults + */ +mp_subcheck mp_subcheck_init(void) { + mp_subcheck tmp = {0}; + tmp.default_state = STATE_UNKNOWN; // Default state is unknown + tmp.state_set_explicitly = false; + return tmp; +} + +/* + * Add a subcheck to a (the one and only) check object + */ +int mp_add_subcheck_to_check(mp_check check[static 1], mp_subcheck subcheck) { + assert(subcheck.output != NULL); // There must be output in a subcheck + + mp_subcheck_list *tmp = NULL; + + if (check->subchecks == NULL) { + check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list)); + if (check->subchecks == NULL) { + die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed"); + } + + check->subchecks->subcheck = subcheck; + check->subchecks->next = NULL; + } else { + // Search for the end + tmp = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list)); + if (tmp == NULL) { + die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed"); + } + + tmp->subcheck = subcheck; + tmp->next = check->subchecks; + + check->subchecks = tmp; + } + + return 0; +} + +/* + * Add a mp_perfdata data point to a mp_subcheck object + */ +void mp_add_perfdata_to_subcheck(mp_subcheck check[static 1], const mp_perfdata perfData) { + if (check->perfdata == NULL) { + check->perfdata = pd_list_init(); + } + pd_list_append(check->perfdata, perfData); +} + +/* + * Add a mp_subcheck object to another one. The seconde mp_subcheck (argument) is the lower in the + * hierarchy + */ +int mp_add_subcheck_to_subcheck(mp_subcheck check[static 1], mp_subcheck subcheck) { + if (subcheck.output == NULL) { + die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "Sub check output is NULL"); + } + + mp_subcheck_list *tmp = NULL; + + if (check->subchecks == NULL) { + check->subchecks = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list)); + if (check->subchecks == NULL) { + die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed"); + } + + tmp = check->subchecks; + } else { + // Search for the end + tmp = check->subchecks; + + while (tmp->next != NULL) { + tmp = tmp->next; + } + + tmp->next = (mp_subcheck_list *)calloc(1, sizeof(mp_subcheck_list)); + if (tmp->next == NULL) { + die(STATE_UNKNOWN, "%s - %s #%d: %s", __FILE__, __func__, __LINE__, "malloc failed"); + } + + tmp = tmp->next; + } + + tmp->subcheck = subcheck; + + return 0; +} + +/* + * Add a manual summary to a mp_check object, effectively replacing + * the autogenerated one + */ +void mp_add_summary(mp_check check[static 1], char *summary) { check->summary = summary; } + +/* + * Generate the summary string of a mp_check object based on it's subchecks + */ +char *get_subcheck_summary(mp_check check) { + mp_subcheck_list *subchecks = check.subchecks; + + unsigned int ok = 0; + unsigned int warning = 0; + unsigned int critical = 0; + unsigned int unknown = 0; + while (subchecks != NULL) { + switch (subchecks->subcheck.state) { + case STATE_OK: + ok++; + break; + case STATE_WARNING: + warning++; + break; + case STATE_CRITICAL: + critical++; + break; + case STATE_UNKNOWN: + unknown++; + break; + default: + die(STATE_UNKNOWN, "Unknown state in get_subcheck_summary"); + } + subchecks = subchecks->next; + } + char *result = NULL; + xasprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown); + return result; +} + +/* + * Generate the result state of a mp_subcheck object based on it's own state and it's subchecks states + */ +mp_state_enum mp_compute_subcheck_state(const mp_subcheck check) { + if (check.state_set_explicitly) { + return check.state; + } + + mp_subcheck_list *scl = check.subchecks; + mp_state_enum result = check.default_state; + + while (scl != NULL) { + result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck)); + scl = scl->next; + } + + return result; +} + +/* + * Generate the result state of a mp_check object based on it's own state and it's subchecks states + */ +mp_state_enum mp_compute_check_state(const mp_check check) { + assert(check.subchecks != NULL); // a mp_check without subchecks is invalid, die here + + mp_subcheck_list *scl = check.subchecks; + mp_state_enum result = STATE_OK; + + while (scl != NULL) { + result = max_state_alt(result, mp_compute_subcheck_state(scl->subcheck)); + scl = scl->next; + } + + return result; +} + +/* + * Generate output string for a mp_check object + * Non static to be available for testing functions + */ +char *mp_fmt_output(mp_check check) { + char *result = NULL; + + switch (check.format) { + case MP_FORMAT_SUMMARY_ONLY: + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + return result; + + case MP_FORMAT_ONE_LINE: { + /* SERVICE STATUS: First line of output | First part of performance data + * Any number of subsequent lines of output, but note that buffers + * may have a limited size | Second part of performance data, which + * may have continuation lines, too + */ + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1)); + subchecks = subchecks->next; + } + + break; + } + case MP_FORMAT_ICINGA_WEB_2: { + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + xasprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1)); + subchecks = subchecks->next; + } + + char *pd_string = NULL; + subchecks = check.subchecks; + + while (subchecks != NULL) { + if (pd_string == NULL) { + xasprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck)); + } else { + xasprintf(&pd_string, "%s %s", pd_string, fmt_subcheck_perfdata(subchecks->subcheck)); + } + + subchecks = subchecks->next; + } + + if (pd_string != NULL && strlen(pd_string) > 0) { + xasprintf(&result, "%s|%s", result, pd_string); + } + + break; + } + case MP_FORMAT_TEST_JSON: { + cJSON *resultObject = cJSON_CreateObject(); + if (resultObject == NULL) { + die(STATE_UNKNOWN, "cJSON_CreateObject failed"); + } + + cJSON *resultState = cJSON_CreateString(state_text(mp_compute_check_state(check))); + cJSON_AddItemToObject(resultObject, "state", resultState); + + if (check.summary == NULL) { + check.summary = get_subcheck_summary(check); + } + + cJSON *summary = cJSON_CreateString(check.summary); + cJSON_AddItemToObject(resultObject, "summary", summary); + + if (check.subchecks != NULL) { + cJSON *subchecks = cJSON_CreateArray(); + + mp_subcheck_list *sc = check.subchecks; + + while (sc != NULL) { + cJSON *sc_json = json_serialize_subcheck(sc->subcheck); + cJSON_AddItemToArray(subchecks, sc_json); + sc = sc->next; + } + + cJSON_AddItemToObject(resultObject, "checks", subchecks); + } + + result = cJSON_PrintUnformatted(resultObject); + break; + } + default: + die(STATE_UNKNOWN, "Invalid format"); + } + + return result; +} + +/* + * Helper function to properly indent the output lines when using multiline + * formats + */ +static char *generate_indentation_string(unsigned int indentation) { + char *result = calloc(indentation + 1, sizeof(char)); + + for (unsigned int i = 0; i < indentation; i++) { + result[i] = '\t'; + } + + return result; +} + +/* + * Helper function to generate the output string of mp_subcheck + */ +static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation) { + char *result = NULL; + + switch (output_format) { + case MP_FORMAT_ICINGA_WEB_2: + xasprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), + check.output); + + mp_subcheck_list *subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); + subchecks = subchecks->next; + } + return result; + case MP_FORMAT_ONE_LINE: + return result; + case MP_FORMAT_SUMMARY_ONLY: + return result; + default: + die(STATE_UNKNOWN, "Invalid format"); + } +} + +static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) { + cJSON *result = cJSON_CreateObject(); + cJSON *output = cJSON_CreateString(subcheck.output); + cJSON_AddItemToObject(result, "output", output); + cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck))); + cJSON_AddItemToObject(result, "state", state); + + if (subcheck.subchecks != NULL) { + cJSON *subchecks = cJSON_CreateArray(); + + mp_subcheck_list *sc = subcheck.subchecks; + + while (sc != NULL) { + cJSON *sc_json = json_serialize_subcheck(sc->subcheck); + cJSON_AddItemToArray(subchecks, sc_json); + sc = sc->next; + } + + cJSON_AddItemToObject(result, "checks", subchecks); + } + + return result; +} + +/* + * Wrapper function to print the output string of a mp_check object + * Use this in concrete plugins. + */ +void mp_print_output(mp_check check) { puts(mp_fmt_output(check)); } + +/* + * Convenience function to print the output string of a mp_check object and exit + * the program with the resulting state. + * Intended to be used to exit a monitoring plugin. + */ +void mp_exit(mp_check check) { + mp_print_output(check); + exit(mp_compute_check_state(check)); +} + +/* + * Function to set the result state of a mp_subcheck object explicitly. + * This will overwrite the default state AND states derived from it's subchecks + */ +mp_subcheck mp_set_subcheck_state(mp_subcheck check, mp_state_enum state) { + check.state = state; + check.state_set_explicitly = true; + return check; +} + +/* + * Function to set the default result state of a mp_subcheck object. This state + * will be used if neither an explicit state is set (see *mp_set_subcheck_state*) + * nor does it include other subchecks + */ +mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state) { + check.default_state = state; + return check; +} + +char *mp_output_format_map[] = { + [MP_FORMAT_ONE_LINE] = "one-line", + [MP_FORMAT_ICINGA_WEB_2] = "icingaweb2", + [MP_FORMAT_SUMMARY_ONLY] = "summary-only", + [MP_FORMAT_TEST_JSON] = "mp-test-json", +}; + +/* + * Function to parse the output from a string + */ +parsed_output_format mp_parse_output_format(char *format_string) { + parsed_output_format result = { + .parsing_success = false, + .output_format = MP_FORMAT_DEFAULT, + }; + + for (mp_output_format i = 0; i < (sizeof(mp_output_format_map) / sizeof(char *)); i++) { + if (strcasecmp(mp_output_format_map[i], format_string) == 0) { + result.parsing_success = true; + result.output_format = i; + break; + } + } + + return result; +} diff --git a/lib/output.h b/lib/output.h new file mode 100644 index 00000000..c7455d29 --- /dev/null +++ b/lib/output.h @@ -0,0 +1,87 @@ +#pragma once + +#include "../config.h" +#include "./perfdata.h" +#include "./states.h" + +/* + * A partial check result + */ +typedef struct { + mp_state_enum state; // OK, Warning, Critical ... set explicitly + mp_state_enum default_state; // OK, Warning, Critical .. if not set explicitly + bool state_set_explicitly; // was the state set explicitly (or should it be derived from subchecks) + + char *output; // Text output for humans ("Filesystem xyz is fine", "Could not create TCP connection to..") + pd_list *perfdata; // Performance data for this check + struct subcheck_list *subchecks; // subchecks deeper in the hierarchy +} mp_subcheck; + +/* + * A list of subchecks, used in subchecks and the main check + */ +typedef struct subcheck_list { + mp_subcheck subcheck; + struct subcheck_list *next; +} mp_subcheck_list; + +/* + * Possible output formats + */ +typedef enum output_format { + MP_FORMAT_ONE_LINE, + MP_FORMAT_ICINGA_WEB_2, + MP_FORMAT_SUMMARY_ONLY, + MP_FORMAT_TEST_JSON, +} mp_output_format; + +#define MP_FORMAT_DEFAULT MP_FORMAT_ICINGA_WEB_2 + +/* + * The main state object of a plugin. Exists only ONCE per plugin. + * This is the "root" of a tree of singular checks. + * The final result is always derived from the children and the "worst" state + * in the first layer of subchecks + */ +typedef struct { + mp_output_format format; // The output format + char *summary; // Overall summary, if not set a summary will be automatically generated + mp_subcheck_list *subchecks; +} mp_check; + +mp_check mp_check_init(void); +mp_subcheck mp_subcheck_init(void); + +mp_subcheck mp_set_subcheck_state(mp_subcheck, mp_state_enum); +mp_subcheck mp_set_subcheck_default_state(mp_subcheck, mp_state_enum); + +int mp_add_subcheck_to_check(mp_check check[static 1], mp_subcheck); +int mp_add_subcheck_to_subcheck(mp_subcheck check[static 1], mp_subcheck); + +void mp_add_perfdata_to_subcheck(mp_subcheck check[static 1], mp_perfdata); + +void mp_add_summary(mp_check check[static 1], char *summary); + +mp_state_enum mp_compute_check_state(mp_check); +mp_state_enum mp_compute_subcheck_state(mp_subcheck); + +typedef struct { + bool parsing_success; + mp_output_format output_format; +} parsed_output_format; +parsed_output_format mp_parse_output_format(char *format_string); + +// TODO free and stuff +// void mp_cleanup_check(mp_check check[static 1]); + +char *mp_fmt_output(mp_check); + +void mp_print_output(mp_check); + +/* + * ================== + * Exit functionality + * ================== + */ + +void mp_exit(mp_check) __attribute__((noreturn)); diff --git a/lib/perfdata.c b/lib/perfdata.c new file mode 100644 index 00000000..f894df39 --- /dev/null +++ b/lib/perfdata.c @@ -0,0 +1,516 @@ +#include "./perfdata.h" +#include "../plugins/common.h" +#include "../plugins/utils.h" +#include "utils_base.h" + +#include +#include +#include + +char *pd_value_to_string(const mp_perfdata_value pd) { + char *result = NULL; + + assert(pd.type != PD_TYPE_NONE); + + switch (pd.type) { + case PD_TYPE_INT: + xasprintf(&result, "%lli", pd.pd_int); + break; + case PD_TYPE_UINT: + xasprintf(&result, "%llu", pd.pd_int); + break; + case PD_TYPE_DOUBLE: + xasprintf(&result, "%f", pd.pd_double); + break; + default: + // die here + die(STATE_UNKNOWN, "Invalid mp_perfdata mode\n"); + } + + return result; +} + +char *pd_to_string(mp_perfdata pd) { + assert(pd.label != NULL); + char *result = NULL; + xasprintf(&result, "%s=", pd.label); + + xasprintf(&result, "%s%s", result, pd_value_to_string(pd.value)); + + if (pd.uom != NULL) { + xasprintf(&result, "%s%s", result, pd.uom); + } + + if (pd.warn_present) { + xasprintf(&result, "%s;%s", result, mp_range_to_string(pd.warn)); + } else { + xasprintf(&result, "%s;", result); + } + + if (pd.crit_present) { + xasprintf(&result, "%s;%s", result, mp_range_to_string(pd.crit)); + } else { + xasprintf(&result, "%s;", result); + } + if (pd.min_present) { + xasprintf(&result, "%s;%s", result, pd_value_to_string(pd.min)); + } else { + xasprintf(&result, "%s;", result); + } + + if (pd.max_present) { + xasprintf(&result, "%s;%s", result, pd_value_to_string(pd.max)); + } + + /*printf("pd_to_string: %s\n", result); */ + + return result; +} + +char *pd_list_to_string(const pd_list pd) { + char *result = pd_to_string(pd.data); + + for (pd_list *elem = pd.next; elem != NULL; elem = elem->next) { + xasprintf(&result, "%s %s", result, pd_to_string(elem->data)); + } + + return result; +} + +mp_perfdata perfdata_init() { + mp_perfdata pd = {}; + return pd; +} + +pd_list *pd_list_init() { + pd_list *tmp = (pd_list *)calloc(1, sizeof(pd_list)); + if (tmp == NULL) { + die(STATE_UNKNOWN, "calloc failed\n"); + } + tmp->next = NULL; + return tmp; +} + +mp_range mp_range_init() { + mp_range result = { + .alert_on_inside_range = OUTSIDE, + .start = {}, + .start_infinity = true, + .end = {}, + .end_infinity = true, + }; + + return result; +} + +mp_range mp_range_set_start(mp_range input, mp_perfdata_value perf_val) { + input.start = perf_val; + input.start_infinity = false; + return input; +} + +mp_range mp_range_set_end(mp_range input, mp_perfdata_value perf_val) { + input.end = perf_val; + input.end_infinity = false; + return input; +} + +void pd_list_append(pd_list pdl[1], const mp_perfdata pd) { + assert(pdl != NULL); + + if (pdl->data.value.type == PD_TYPE_NONE) { + // first entry is still empty + pdl->data = pd; + } else { + // find last element in the list + pd_list *curr = pdl; + pd_list *next = pdl->next; + + while (next != NULL) { + curr = next; + next = next->next; + } + + if (curr->data.value.type == PD_TYPE_NONE) { + // still empty + curr->data = pd; + } else { + // new a new one + curr->next = pd_list_init(); + curr->next->data = pd; + } + } +} + +void pd_list_free(pd_list pdl[1]) { + while (pdl != NULL) { + pd_list *old = pdl; + pdl = pdl->next; + free(old); + } +} + +/* + * returns -1 if a < b, 0 if a == b, 1 if a > b + */ +int cmp_perfdata_value(const mp_perfdata_value a, const mp_perfdata_value b) { + // Test if types are different + if (a.type == b.type) { + + switch (a.type) { + case PD_TYPE_UINT: + if (a.pd_uint < b.pd_uint) { + return -1; + } else if (a.pd_uint == b.pd_uint) { + return 0; + } else { + return 1; + } + break; + case PD_TYPE_INT: + if (a.pd_int < b.pd_int) { + return -1; + } else if (a.pd_int == b.pd_int) { + return 0; + } else { + return 1; + } + break; + case PD_TYPE_DOUBLE: + if (a.pd_int < b.pd_int) { + return -1; + } else if (a.pd_int == b.pd_int) { + return 0; + } else { + return 1; + } + break; + default: + die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__); + } + } + + // Get dirty here + long double floating_a = 0; + + switch (a.type) { + case PD_TYPE_UINT: + floating_a = a.pd_uint; + break; + case PD_TYPE_INT: + floating_a = a.pd_int; + break; + case PD_TYPE_DOUBLE: + floating_a = a.pd_double; + break; + default: + die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__); + } + + long double floating_b = 0; + switch (b.type) { + case PD_TYPE_UINT: + floating_b = b.pd_uint; + break; + case PD_TYPE_INT: + floating_b = b.pd_int; + break; + case PD_TYPE_DOUBLE: + floating_b = b.pd_double; + break; + default: + die(STATE_UNKNOWN, "Error in %s line: %d!", __FILE__, __LINE__); + } + + if (floating_a < floating_b) { + return -1; + } + if (floating_a == floating_b) { + return 0; + } + return 1; +} + +char *mp_range_to_string(const mp_range input) { + char *result = ""; + if (input.alert_on_inside_range == INSIDE) { + xasprintf(&result, "@"); + } + + if (input.start_infinity) { + xasprintf(&result, "%s~:", result); + } else { + xasprintf(&result, "%s%s:", result, pd_value_to_string(input.start)); + } + + if (!input.end_infinity) { + xasprintf(&result, "%s%s", result, pd_value_to_string(input.end)); + } + return result; +} + +mp_perfdata mp_set_pd_value_float(mp_perfdata pd, float value) { return mp_set_pd_value_double(pd, value); } + +mp_perfdata mp_set_pd_value_double(mp_perfdata pd, double value) { + pd.value.pd_double = value; + pd.value.type = PD_TYPE_DOUBLE; + return pd; +} + +mp_perfdata mp_set_pd_value_int(mp_perfdata pd, int value) { return mp_set_pd_value_long_long(pd, (long long)value); } + +mp_perfdata mp_set_pd_value_u_int(mp_perfdata pd, unsigned int value) { return mp_set_pd_value_u_long_long(pd, (unsigned long long)value); } + +mp_perfdata mp_set_pd_value_long(mp_perfdata pd, long value) { return mp_set_pd_value_long_long(pd, (long long)value); } + +mp_perfdata mp_set_pd_value_u_long(mp_perfdata pd, unsigned long value) { + return mp_set_pd_value_u_long_long(pd, (unsigned long long)value); +} + +mp_perfdata mp_set_pd_value_long_long(mp_perfdata pd, long long value) { + pd.value.pd_int = value; + pd.value.type = PD_TYPE_INT; + return pd; +} + +mp_perfdata mp_set_pd_value_u_long_long(mp_perfdata pd, unsigned long long value) { + pd.value.pd_uint = value; + pd.value.type = PD_TYPE_UINT; + return pd; +} + +mp_perfdata_value mp_create_pd_value_double(double value) { + mp_perfdata_value res = {0}; + res.type = PD_TYPE_DOUBLE; + res.pd_double = value; + return res; +} + +mp_perfdata_value mp_create_pd_value_float(float value) { return mp_create_pd_value_double((double)value); } + +mp_perfdata_value mp_create_pd_value_int(int value) { return mp_create_pd_value_long_long((long long)value); } + +mp_perfdata_value mp_create_pd_value_u_int(unsigned int value) { return mp_create_pd_value_u_long_long((unsigned long long)value); } + +mp_perfdata_value mp_create_pd_value_long(long value) { return mp_create_pd_value_long_long((long long)value); } + +mp_perfdata_value mp_create_pd_value_u_long(unsigned long value) { return mp_create_pd_value_u_long_long((unsigned long long)value); } + +mp_perfdata_value mp_create_pd_value_long_long(long long value) { + mp_perfdata_value res = {0}; + res.type = PD_TYPE_INT; + res.pd_int = value; + return res; +} + +mp_perfdata_value mp_create_pd_value_u_long_long(unsigned long long value) { + mp_perfdata_value res = {0}; + res.type = PD_TYPE_UINT; + res.pd_uint = value; + return res; +} + +char *fmt_range(range foo) { return foo.text; } + +typedef struct integer_parser_wrapper { + int error; + mp_perfdata_value value; +} integer_parser_wrapper; + +typedef struct double_parser_wrapper { + int error; + mp_perfdata_value value; +} double_parser_wrapper; + +typedef struct perfdata_value_parser_wrapper { + int error; + mp_perfdata_value value; +} perfdata_value_parser_wrapper; + +double_parser_wrapper parse_double(const char *input); +integer_parser_wrapper parse_integer(const char *input); +perfdata_value_parser_wrapper parse_pd_value(const char *input); + +mp_range_parsed mp_parse_range_string(const char *input) { + if (input == NULL) { + mp_range_parsed result = { + .error = MP_RANGE_PARSING_FAILURE, + }; + return result; + } + + if (strlen(input) == 0) { + mp_range_parsed result = { + .error = MP_RANGE_PARSING_FAILURE, + }; + return result; + } + + mp_range_parsed result = { + .range = mp_range_init(), + .error = MP_PARSING_SUCCES, + }; + + if (input[0] == '@') { + // found an '@' at beginning, so invert the range logic + result.range.alert_on_inside_range = INSIDE; + + // advance the pointer one symbol + input++; + } + + char *working_copy = strdup(input); + input = working_copy; + + char *separator = index(working_copy, ':'); + if (separator != NULL) { + // Found a separator + // set the separator to 0, so we have two different strings + *separator = '\0'; + + if (input[0] == '~') { + // the beginning starts with '~', so it might be infinity + if (&input[1] != separator) { + // the next symbol after '~' is not the separator! + // so input is probably wrong + result.error = MP_RANGE_PARSING_FAILURE; + free(working_copy); + return result; + } + + result.range.start_infinity = true; + } else { + // No '~' at the beginning, so this should be a number + result.range.start_infinity = false; + perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input); + + if (parsed_pd.error != MP_PARSING_SUCCES) { + result.error = parsed_pd.error; + free(working_copy); + return result; + } + + result.range.start = parsed_pd.value; + result.range.start_infinity = false; + } + // got the first part now + // advance the pointer + input = separator + 1; + } + + // End part or no separator + if (input[0] == '\0') { + // the end is infinite + result.range.end_infinity = true; + } else { + perfdata_value_parser_wrapper parsed_pd = parse_pd_value(input); + + if (parsed_pd.error != MP_PARSING_SUCCES) { + result.error = parsed_pd.error; + return result; + } + result.range.end = parsed_pd.value; + result.range.end_infinity = false; + } + free(working_copy); + return result; +} + +double_parser_wrapper parse_double(const char *input) { + double_parser_wrapper result = { + .error = MP_PARSING_SUCCES, + }; + + if (input == NULL) { + result.error = MP_PARSING_FAILURE; + return result; + } + + char *endptr = NULL; + errno = 0; + double tmp = strtod(input, &endptr); + + if (input == endptr) { + // man 3 strtod says, no conversion performed + result.error = MP_PARSING_FAILURE; + return result; + } + + if (errno) { + // some other error + // TODO maybe differentiate a little bit + result.error = MP_PARSING_FAILURE; + return result; + } + + result.value = mp_create_pd_value(tmp); + return result; +} + +integer_parser_wrapper parse_integer(const char *input) { + integer_parser_wrapper result = { + .error = MP_PARSING_SUCCES, + }; + + if (input == NULL) { + result.error = MP_PARSING_FAILURE; + return result; + } + + char *endptr = NULL; + errno = 0; + long long tmp = strtoll(input, &endptr, 0); + + // validating *sigh* + if (*endptr != '\0') { + // something went wrong in strtoll + if (tmp == LLONG_MIN) { + // underflow + result.error = MP_RANGE_PARSING_UNDERFLOW; + return result; + } + + if (tmp == LLONG_MAX) { + // overflow + result.error = MP_RANGE_PARSING_OVERFLOW; + return result; + } + + // still wrong, but not sure why, probably invalid characters + if (errno == EINVAL) { + result.error = MP_RANGE_PARSING_INVALID_CHAR; + return result; + } + + // some other error, do catch all here + result.error = MP_RANGE_PARSING_FAILURE; + return result; + } + + // no error, should be fine + result.value = mp_create_pd_value(tmp); + return result; +} + +perfdata_value_parser_wrapper parse_pd_value(const char *input) { + // try integer first + integer_parser_wrapper tmp_int = parse_integer(input); + + if (tmp_int.error == MP_PARSING_SUCCES) { + perfdata_value_parser_wrapper result = { + .error = tmp_int.error, + .value = tmp_int.value, + }; + return result; + } + + double_parser_wrapper tmp_double = parse_double(input); + perfdata_value_parser_wrapper result = {}; + if (tmp_double.error == MP_PARSING_SUCCES) { + result.error = tmp_double.error; + result.value = tmp_double.value; + } else { + result.error = tmp_double.error; + } + return result; +} diff --git a/lib/perfdata.h b/lib/perfdata.h new file mode 100644 index 00000000..74583ee5 --- /dev/null +++ b/lib/perfdata.h @@ -0,0 +1,203 @@ +#pragma once + +#include "../config.h" + +#include +#include + +// Enum for the specific type of a perfdata_value +typedef enum pd_value_type { + PD_TYPE_NONE = 0, + PD_TYPE_INT, + PD_TYPE_UINT, + PD_TYPE_DOUBLE +} pd_value_type; + +typedef struct { + enum pd_value_type type; + union { + long long pd_int; + unsigned long long pd_uint; + double pd_double; + }; +} mp_perfdata_value; + +#define MP_OUTSIDE false +#define MP_INSIDE true + +/* + * New range type with generic numerical values + */ +typedef struct mp_range_struct { + mp_perfdata_value start; + bool start_infinity; /* false (default) or true */ + + mp_perfdata_value end; + bool end_infinity; + + bool alert_on_inside_range; /* OUTSIDE (default) or INSIDE */ +} mp_range; + +/* + * Old range type with floating point values + */ +typedef struct range_struct { + double start; + bool start_infinity; + double end; + int end_infinity; + int alert_on; /* OUTSIDE (default) or INSIDE */ + char *text; /* original unparsed text input */ +} range; + +/* + * Perfdata type for storing perfdata output + */ +typedef struct perfdata_struct { + char *label; + char *uom; + mp_perfdata_value value; + + bool warn_present; + mp_range warn; + + bool crit_present; + mp_range crit; + + bool min_present; + mp_perfdata_value min; + + bool max_present; + mp_perfdata_value max; +} mp_perfdata; + +/* + * List of mp_perfdata values + */ +typedef struct pd_list_struct { + mp_perfdata data; + struct pd_list_struct *next; +} pd_list; + +/* + * ============ + * Initializers + * ============ + */ +/* + * Initialize mp_perfdata value. Always use this to generate a new one + */ +mp_perfdata perfdata_init(void); + +/* + * Initialize pd_list value. Always use this to generate a new one + */ +pd_list *pd_list_init(void); + +/* + * Initialize a new mp_range value, with unset values (start and end are infinite + */ +mp_range mp_range_init(void); + +/* + * Worker functions + */ + +mp_range mp_range_set_start(mp_range, mp_perfdata_value); +mp_range mp_range_set_end(mp_range, mp_perfdata_value); + +/* + * Parsing a range from a string + */ + +typedef enum { + MP_PARSING_SUCCES = 0, + MP_PARSING_FAILURE, + MP_RANGE_PARSING_FAILURE, + MP_RANGE_PARSING_UNDERFLOW, + MP_RANGE_PARSING_OVERFLOW, + MP_RANGE_PARSING_INVALID_CHAR, +} mp_range_parser_error; + +typedef struct mp_range_parsed { + mp_range_parser_error error; + mp_range range; +} mp_range_parsed; + +mp_range_parsed mp_parse_range_string(const char * /*input*/); + +/* + * Appends a mp_perfdata value to a pd_list + */ +void pd_list_append(pd_list[1], mp_perfdata); + +#define mp_set_pd_value(P, V) \ + _Generic((V), \ + float: mp_set_pd_value_float, \ + double: mp_set_pd_value_double, \ + int: mp_set_pd_value_int, \ + unsigned int: mp_set_pd_value_u_int, \ + long: mp_set_pd_value_long, \ + unsigned long: mp_set_pd_value_u_long, \ + long long: mp_set_pd_value_long_long, \ + unsigned long long: mp_set_pd_value_u_long_long)(P, V) + +mp_perfdata mp_set_pd_value_float(mp_perfdata, float); +mp_perfdata mp_set_pd_value_double(mp_perfdata, double); +mp_perfdata mp_set_pd_value_int(mp_perfdata, int); +mp_perfdata mp_set_pd_value_u_int(mp_perfdata, unsigned int); +mp_perfdata mp_set_pd_value_long(mp_perfdata, long); +mp_perfdata mp_set_pd_value_u_long(mp_perfdata, unsigned long); +mp_perfdata mp_set_pd_value_long_long(mp_perfdata, long long); +mp_perfdata mp_set_pd_value_u_long_long(mp_perfdata, unsigned long long); + +#define mp_create_pd_value(V) \ + _Generic((V), \ + float: mp_create_pd_value_float, \ + double: mp_create_pd_value_double, \ + int: mp_create_pd_value_int, \ + unsigned int: mp_create_pd_value_u_int, \ + long: mp_create_pd_value_long, \ + unsigned long: mp_create_pd_value_u_long, \ + long long: mp_create_pd_value_long_long, \ + unsigned long long: mp_create_pd_value_u_long_long)(V) + +mp_perfdata_value mp_create_pd_value_float(float); +mp_perfdata_value mp_create_pd_value_double(double); +mp_perfdata_value mp_create_pd_value_int(int); +mp_perfdata_value mp_create_pd_value_u_int(unsigned int); +mp_perfdata_value mp_create_pd_value_long(long); +mp_perfdata_value mp_create_pd_value_u_long(unsigned long); +mp_perfdata_value mp_create_pd_value_long_long(long long); +mp_perfdata_value mp_create_pd_value_u_long_long(unsigned long long); + +/* + * Free the memory used by a pd_list + */ +void pd_list_free(pd_list[1]); + +int cmp_perfdata_value(mp_perfdata_value, mp_perfdata_value); + +// ================= +// String formatters +// ================= +/* + * Generate string from mp_perfdata value + */ +char *pd_to_string(mp_perfdata); + +/* + * Generate string from perfdata_value value + */ +char *pd_value_to_string(mp_perfdata_value); + +/* + * Generate string from pd_list value for the final output + */ +char *pd_list_to_string(pd_list); + +/* + * Generate string from a mp_range value + */ +char *mp_range_to_string(mp_range); +char *fmt_range(range); diff --git a/lib/states.h b/lib/states.h new file mode 100644 index 00000000..4a170caa --- /dev/null +++ b/lib/states.h @@ -0,0 +1,71 @@ +#ifndef _MP_STATES_ +#define _MP_STATES_ + +#include "../config.h" +#include + +typedef enum state_enum { + STATE_OK, + STATE_WARNING, + STATE_CRITICAL, + STATE_UNKNOWN, + STATE_DEPENDENT +} mp_state_enum; + +/* ************************************************************************** + * max_state(STATE_x, STATE_y) + * compares STATE_x to STATE_y and returns result based on the following + * STATE_UNKNOWN < STATE_OK < STATE_WARNING < STATE_CRITICAL + * + * Note that numerically the above does not hold + ****************************************************************************/ + +static inline mp_state_enum max_state(mp_state_enum a, mp_state_enum b) { + if (a == STATE_CRITICAL || b == STATE_CRITICAL) { + return STATE_CRITICAL; + } + if (a == STATE_WARNING || b == STATE_WARNING) { + return STATE_WARNING; + } + if (a == STATE_OK || b == STATE_OK) { + return STATE_OK; + } + if (a == STATE_UNKNOWN || b == STATE_UNKNOWN) { + return STATE_UNKNOWN; + } + if (a == STATE_DEPENDENT || b == STATE_DEPENDENT) { + return STATE_DEPENDENT; + } + return MAX(a, b); +} + +/* ************************************************************************** + * max_state_alt(STATE_x, STATE_y) + * compares STATE_x to STATE_y and returns result based on the following + * STATE_OK < STATE_DEPENDENT < STATE_UNKNOWN < STATE_WARNING < STATE_CRITICAL + * + * The main difference between max_state_alt and max_state it that it doesn't + * allow setting a default to UNKNOWN. It will instead prioritixe any valid + * non-OK state. + ****************************************************************************/ + +static inline mp_state_enum max_state_alt(mp_state_enum a, mp_state_enum b) { + if (a == STATE_CRITICAL || b == STATE_CRITICAL) { + return STATE_CRITICAL; + } + if (a == STATE_WARNING || b == STATE_WARNING) { + return STATE_WARNING; + } + if (a == STATE_UNKNOWN || b == STATE_UNKNOWN) { + return STATE_UNKNOWN; + } + if (a == STATE_DEPENDENT || b == STATE_DEPENDENT) { + return STATE_DEPENDENT; + } + if (a == STATE_OK || b == STATE_OK) { + return STATE_OK; + } + return MAX(a, b); +} + +#endif diff --git a/lib/tests/Makefile.am b/lib/tests/Makefile.am index 31d79df6..9be94f6d 100644 --- a/lib/tests/Makefile.am +++ b/lib/tests/Makefile.am @@ -8,9 +8,9 @@ check_PROGRAMS = @EXTRA_TEST@ AM_CPPFLAGS = -DNP_STATE_DIR_PREFIX=\"$(localstatedir)\" \ -I$(top_srcdir)/lib -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/plugins -EXTRA_PROGRAMS = test_utils test_disk test_tcp test_cmd test_base64 test_ini1 test_ini3 test_opts1 test_opts2 test_opts3 +EXTRA_PROGRAMS = test_utils test_disk test_tcp test_cmd test_base64 test_ini1 test_ini3 test_opts1 test_opts2 test_opts3 test_generic_output -np_test_scripts = test_base64.t test_cmd.t test_disk.t test_ini1.t test_ini3.t test_opts1.t test_opts2.t test_opts3.t test_tcp.t test_utils.t +np_test_scripts = test_base64.t test_cmd.t test_disk.t test_ini1.t test_ini3.t test_opts1.t test_opts2.t test_opts3.t test_tcp.t test_utils.t test_generic_output.t np_test_files = config-dos.ini config-opts.ini config-tiny.ini plugin.ini plugins.ini EXTRA_DIST = $(np_test_scripts) $(np_test_files) var @@ -29,7 +29,7 @@ AM_CFLAGS = -g -I$(top_srcdir)/lib -I$(top_srcdir)/gl $(tap_cflags) AM_LDFLAGS = $(tap_ldflags) -ltap LDADD = $(top_srcdir)/lib/libmonitoringplug.a $(top_srcdir)/gl/libgnu.a $(LIB_CRYPTO) -SOURCES = test_utils.c test_disk.c test_tcp.c test_cmd.c test_base64.c test_ini1.c test_ini3.c test_opts1.c test_opts2.c test_opts3.c +SOURCES = test_utils.c test_disk.c test_tcp.c test_cmd.c test_base64.c test_ini1.c test_ini3.c test_opts1.c test_opts2.c test_opts3.c test_generic_output.c test: ${noinst_PROGRAMS} perl -MTest::Harness -e '$$Test::Harness::switches=""; runtests(map {$$_ .= ".t"} @ARGV)' $(EXTRA_PROGRAMS) diff --git a/lib/tests/test_generic_output.c b/lib/tests/test_generic_output.c new file mode 100644 index 00000000..e67aefc9 --- /dev/null +++ b/lib/tests/test_generic_output.c @@ -0,0 +1,315 @@ +#include "../lib/output.h" +#include "../../tap/tap.h" +#include "./states.h" + +#include + +void test_one_subcheck(void); +void test_two_subchecks(void); + +void test_perfdata_formatting(void); +void test_perfdata_formatting2(void); + +void test_deep_check_hierarchy(void); +void test_deep_check_hierarchy2(void); + +void test_default_states1(void); +void test_default_states2(void); + +int main(void) { + plan_tests(19); + + diag("Simple test with one subcheck"); + test_one_subcheck(); + + diag("Test with two subchecks"); + test_two_subchecks(); + + diag("Test for performance data formatting"); + test_perfdata_formatting(); + + diag("Another test for performance data formatting"); + test_perfdata_formatting2(); + + diag("Test for deeper hierarchies"); + test_deep_check_hierarchy(); + + diag("Another test for deeper hierarchies"); + test_deep_check_hierarchy2(); + + diag("Testing the default state logic"); + test_default_states1(); + + diag("Testing the default state logic #2"); + test_default_states2(); + + return exit_status(); +} + +void test_one_subcheck(void) { + mp_subcheck sc1 = mp_subcheck_init(); + + sc1.output = "foobar"; + sc1 = mp_set_subcheck_state(sc1, STATE_WARNING); + + mp_check check = mp_check_init(); + mp_add_subcheck_to_check(&check, sc1); + + ok(mp_compute_check_state(check) == STATE_WARNING, "Main state should be warning"); + + char *output = mp_fmt_output(check); + + // diag("Formatted output"); + // diag(output); + + char expected[] = "[WARNING] - ok=0, warning=1, critical=0, unknown=0\n" + "\t\\_[WARNING] - foobar\n"; + + // diag("Expected output"); + // diag(expected); + + ok(strcmp(output, expected) == 0, "Simple output test"); +} + +void test_perfdata_formatting2(void) { + mp_perfdata pd1 = perfdata_init(); + mp_perfdata pd2 = perfdata_init(); + + pd1.label = "foo"; + pd2.label = "bar"; + + pd1 = mp_set_pd_value(pd1, 23); + pd2 = mp_set_pd_value(pd2, 1LL); + + pd_list *tmp = pd_list_init(); + + pd_list_append(tmp, pd1); + pd_list_append(tmp, pd2); + + char *result = pd_list_to_string(*tmp); + + ok(strcmp(result, "foo=23;;; bar=1;;;") == 0, "Perfdata string formatting"); +} + +void test_perfdata_formatting(void) { + mp_perfdata pd1 = perfdata_init(); + + pd1.uom = "s"; + pd1.label = "foo"; + + pd1 = mp_set_pd_value(pd1, 23); + + char *pd_string = pd_to_string(pd1); + + ok(strcmp(pd_string, "foo=23s;;;") == 0, "Perfdata string formatting"); +} + +void test_two_subchecks(void) { + mp_subcheck sc1 = mp_subcheck_init(); + + sc1.output = "foobar"; + sc1 = mp_set_subcheck_state(sc1, STATE_WARNING); + + ok(mp_compute_subcheck_state(sc1) == STATE_WARNING, "Test subcheck state directly after setting it"); + + mp_perfdata pd1 = perfdata_init(); + + pd1 = mp_set_pd_value(pd1, 23); + + pd1.uom = "s"; + pd1.label = "foo"; + + mp_add_perfdata_to_subcheck(&sc1, pd1); + + mp_subcheck sc2 = mp_subcheck_init(); + sc2.output = "baz"; + sc2 = mp_set_subcheck_state(sc2, STATE_OK); + + ok(mp_compute_subcheck_state(sc2) == STATE_OK, "Test subcheck 2 state after setting it"); + + mp_add_subcheck_to_subcheck(&sc1, sc2); + + ok(mp_compute_subcheck_state(sc1) == STATE_WARNING, "Test subcheck state after adding a subcheck"); + + mp_check check = mp_check_init(); + mp_add_subcheck_to_check(&check, sc1); + + ok(mp_compute_check_state(check) == STATE_WARNING, "Test main check result"); + + char *output = mp_fmt_output(check); + + // diag("Formatted output. Length: %u", strlen(output)); + // diag(output); + + ok(output != NULL, "Output should not be NULL"); + + char expected[] = "[WARNING] - ok=0, warning=1, critical=0, unknown=0\n" + "\t\\_[WARNING] - foobar\n" + "\t\t\\_[OK] - baz\n" + "|foo=23s;;; \n"; + + // diag("Expected output. Length: %u", strlen(expected)); + // diag(expected); + + ok(strcmp(output, expected) == 0, "Output is as expected"); +} + +void test_deep_check_hierarchy(void) { + // level 4 + mp_subcheck sc4 = mp_subcheck_init(); + sc4.output = "level4"; + sc4 = mp_set_subcheck_state(sc4, STATE_OK); + + // level 3 + mp_subcheck sc3 = mp_subcheck_init(); + sc3.output = "level3"; + sc3 = mp_set_subcheck_state(sc3, STATE_OK); + + // level 2 + mp_subcheck sc2 = mp_subcheck_init(); + sc2.output = "baz"; + sc2 = mp_set_subcheck_state(sc2, STATE_OK); + + // level 1 + mp_subcheck sc1 = mp_subcheck_init(); + + sc1.output = "foobar"; + sc1 = mp_set_subcheck_state(sc1, STATE_WARNING); + + mp_perfdata pd1 = perfdata_init(); + + pd1.uom = "s"; + pd1.label = "foo"; + pd1 = mp_set_pd_value(pd1, 23); + + mp_add_perfdata_to_subcheck(&sc1, pd1); + + // main check + mp_check check = mp_check_init(); + + mp_add_subcheck_to_subcheck(&sc3, sc4); + mp_add_subcheck_to_subcheck(&sc2, sc3); + mp_add_subcheck_to_subcheck(&sc1, sc2); + mp_add_subcheck_to_check(&check, sc1); + + char *output = mp_fmt_output(check); + + size_t output_length = strlen(output); + + // diag("Formatted output of length %i", output_length); + // diag(output); + + ok(output != NULL, "Output should not be NULL"); + + char expected[] = "[WARNING] - ok=0, warning=1, critical=0, unknown=0\n" + "\t\\_[WARNING] - foobar\n" + "\t\t\\_[OK] - baz\n" + "\t\t\t\\_[OK] - level3\n" + "\t\t\t\t\\_[OK] - level4\n" + "|foo=23s;;; \n"; + + size_t expected_length = strlen(expected); + + // diag("Expected output of length: %i", expected_length); + // diag(expected); + + ok(output_length == expected_length, "Outputs are of equal length"); + ok(strcmp(output, expected) == 0, "Output is as expected"); +} + +void test_deep_check_hierarchy2(void) { + // level 1 + mp_subcheck sc1 = mp_subcheck_init(); + + sc1.output = "foobar"; + sc1 = mp_set_subcheck_state(sc1, STATE_WARNING); + + mp_perfdata pd1 = perfdata_init(); + pd1.uom = "s"; + pd1.label = "foo"; + pd1 = mp_set_pd_value(pd1, 23); + + mp_add_perfdata_to_subcheck(&sc1, pd1); + + // level 2 + mp_subcheck sc2 = mp_subcheck_init(); + sc2.output = "baz"; + sc2 = mp_set_subcheck_state(sc2, STATE_OK); + + mp_perfdata pd2 = perfdata_init(); + pd2.uom = "B"; + pd2.label = "baz"; + pd2 = mp_set_pd_value(pd2, 1024); + mp_add_perfdata_to_subcheck(&sc2, pd2); + + // level 3 + mp_subcheck sc3 = mp_subcheck_init(); + sc3.output = "level3"; + sc3 = mp_set_subcheck_state(sc3, STATE_OK); + + mp_perfdata pd3 = perfdata_init(); + pd3.label = "floatMe"; + pd3 = mp_set_pd_value(pd3, 1024.1024); + mp_add_perfdata_to_subcheck(&sc3, pd3); + + // level 4 + mp_subcheck sc4 = mp_subcheck_init(); + sc4.output = "level4"; + sc4 = mp_set_subcheck_state(sc4, STATE_OK); + + mp_check check = mp_check_init(); + + mp_add_subcheck_to_subcheck(&sc3, sc4); + mp_add_subcheck_to_subcheck(&sc2, sc3); + mp_add_subcheck_to_subcheck(&sc1, sc2); + mp_add_subcheck_to_check(&check, sc1); + + char *output = mp_fmt_output(check); + + // diag("Formatted output of length: %i", strlen(output)); + // diag(output); + + ok(output != NULL, "Output should not be NULL"); + + char expected[] = "[WARNING] - ok=0, warning=1, critical=0, unknown=0\n" + "\t\\_[WARNING] - foobar\n" + "\t\t\\_[OK] - baz\n" + "\t\t\t\\_[OK] - level3\n" + "\t\t\t\t\\_[OK] - level4\n" + "|foo=23s;;; baz=1024B;;; floatMe=1024.102400;;; \n"; + + // diag("Expected output of length: %i", strlen(expected)); + // diag(expected); + + ok(strcmp(output, expected) == 0, "Output is as expected"); +} + +void test_default_states1(void) { + mp_subcheck sc = mp_subcheck_init(); + + mp_state_enum state1 = mp_compute_subcheck_state(sc); + ok(state1 == STATE_UNKNOWN, "Default default state is Unknown"); + + sc = mp_set_subcheck_default_state(sc, STATE_CRITICAL); + + mp_state_enum state2 = mp_compute_subcheck_state(sc); + ok(state2 == STATE_CRITICAL, "Default state is Critical"); + + sc = mp_set_subcheck_state(sc, STATE_OK); + + mp_state_enum state3 = mp_compute_subcheck_state(sc); + ok(state3 == STATE_OK, "Default state is Critical"); +} + +void test_default_states2(void) { + mp_check check = mp_check_init(); + + mp_subcheck sc = mp_subcheck_init(); + sc.output = "placeholder"; + sc = mp_set_subcheck_default_state(sc, STATE_CRITICAL); + + mp_add_subcheck_to_check(&check, sc); + + mp_state_enum result_state = mp_compute_check_state(check); + ok(result_state == STATE_CRITICAL, "Derived state is the proper default state"); +} diff --git a/lib/tests/test_generic_output.t b/lib/tests/test_generic_output.t new file mode 100644 index 00000000..48c2ddf4 --- /dev/null +++ b/lib/tests/test_generic_output.t @@ -0,0 +1,6 @@ +#!/usr/bin/perl +use Test::More; +if (! -e "./test_generic_output") { + plan skip_all => "./test_generic_output not compiled - please enable libtap library to test"; +} +exec "./test_generic_output"; diff --git a/lib/thresholds.c b/lib/thresholds.c new file mode 100644 index 00000000..ddefae37 --- /dev/null +++ b/lib/thresholds.c @@ -0,0 +1,59 @@ +#include "./thresholds.h" +#include "./utils_base.h" +#include "perfdata.h" + +#include + +mp_thresholds mp_thresholds_init() { + mp_thresholds tmp = { + .critical = {}, + .critical_is_set = false, + .warning = {}, + .warning_is_set = false, + }; + return tmp; +} + +char *fmt_threshold_warning(const thresholds th) { + if (th.warning == NULL) { + return ""; + } + + return fmt_range(*th.warning); +} + +char *fmt_threshold_critical(const thresholds th) { + if (th.critical == NULL) { + return ""; + } + return fmt_range(*th.critical); +} + +mp_perfdata mp_pd_set_thresholds(mp_perfdata perfdata, mp_thresholds threshold) { + if (threshold.critical_is_set) { + perfdata.crit = threshold.critical; + perfdata.crit_present = true; + } + + if (threshold.warning_is_set) { + perfdata.warn = threshold.warning; + perfdata.warn_present = true; + } + + return perfdata; +} + +mp_state_enum mp_get_pd_status(mp_perfdata perfdata) { + if (perfdata.crit_present) { + if (mp_check_range(perfdata.value, perfdata.crit)) { + return STATE_CRITICAL; + } + } + if (perfdata.warn_present) { + if (mp_check_range(perfdata.value, perfdata.warn)) { + return STATE_CRITICAL; + } + } + + return STATE_OK; +} diff --git a/lib/thresholds.h b/lib/thresholds.h new file mode 100644 index 00000000..4e7defee --- /dev/null +++ b/lib/thresholds.h @@ -0,0 +1,28 @@ +#pragma once + +#include "./perfdata.h" +#include "states.h" + +/* + * Old threshold type using the old range type + */ +typedef struct thresholds_struct { + range *warning; + range *critical; +} thresholds; + +typedef struct mp_thresholds_struct { + bool warning_is_set; + mp_range warning; + bool critical_is_set; + mp_range critical; +} mp_thresholds; + +mp_thresholds mp_thresholds_init(void); + +mp_perfdata mp_pd_set_thresholds(mp_perfdata /* pd */, mp_thresholds /* th */); + +mp_state_enum mp_get_pd_status(mp_perfdata /* pd */); + +char *fmt_threshold_warning(thresholds th); +char *fmt_threshold_critical(thresholds th); diff --git a/lib/utils_base.c b/lib/utils_base.c index 90a4aaa5..ff9540c7 100644 --- a/lib/utils_base.c +++ b/lib/utils_base.c @@ -55,22 +55,24 @@ void np_init(char *plugin_name, int argc, char **argv) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); } this_monitoring_plugin->plugin_name = strdup(plugin_name); - if (this_monitoring_plugin->plugin_name == NULL) + if (this_monitoring_plugin->plugin_name == NULL) { die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno)); + } this_monitoring_plugin->argc = argc; this_monitoring_plugin->argv = argv; } } void np_set_args(int argc, char **argv) { - if (this_monitoring_plugin == NULL) + if (this_monitoring_plugin == NULL) { die(STATE_UNKNOWN, _("This requires np_init to be called")); + } this_monitoring_plugin->argc = argc; this_monitoring_plugin->argv = argv; } -void np_cleanup() { +void np_cleanup(void) { if (this_monitoring_plugin != NULL) { if (this_monitoring_plugin->state != NULL) { if (this_monitoring_plugin->state->state_data) { @@ -162,8 +164,9 @@ range *parse_range_string(char *str) { int _set_thresholds(thresholds **my_thresholds, char *warn_string, char *critical_string) { thresholds *temp_thresholds = NULL; - if ((temp_thresholds = calloc(1, sizeof(thresholds))) == NULL) + if ((temp_thresholds = calloc(1, sizeof(thresholds))) == NULL) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } temp_thresholds->warning = NULL; temp_thresholds->critical = NULL; @@ -215,6 +218,46 @@ void print_thresholds(const char *threshold_name, thresholds *my_threshold) { printf("\n"); } +/* Returns true if alert should be raised based on the range, false otherwise */ +bool mp_check_range(const mp_perfdata_value value, const mp_range my_range) { + bool is_inside = false; + + if (my_range.end_infinity == false && my_range.start_infinity == false) { + // range: .........|---inside---|........... + // value + if ((cmp_perfdata_value(my_range.start, value) < 1) && (cmp_perfdata_value(value, my_range.end) <= 0)) { + is_inside = true; + } else { + is_inside = false; + } + } else if (my_range.start_infinity == false && my_range.end_infinity == true) { + // range: .........|---inside--------- + // value + if (cmp_perfdata_value(my_range.start, value) < 0) { + is_inside = true; + } else { + is_inside = false; + } + } else if (my_range.start_infinity == true && my_range.end_infinity == false) { + // range: -inside--------|.................... + // value + if (cmp_perfdata_value(value, my_range.end) == -1) { + is_inside = true; + } else { + is_inside = false; + } + } else { + // range from -inf to inf, so always inside + is_inside = true; + } + + if ((is_inside && my_range.alert_on_inside_range == INSIDE) || (!is_inside && my_range.alert_on_inside_range == OUTSIDE)) { + return true; + } + + return false; +} + /* Returns true if alert should be raised based on the range */ bool check_range(double value, range *my_range) { bool no = false; @@ -228,24 +271,24 @@ bool check_range(double value, range *my_range) { if (my_range->end_infinity == false && my_range->start_infinity == false) { if ((my_range->start <= value) && (value <= my_range->end)) { return no; - } else { - return yes; } - } else if (my_range->start_infinity == false && my_range->end_infinity == true) { + return yes; + } + + if (my_range->start_infinity == false && my_range->end_infinity == true) { if (my_range->start <= value) { return no; - } else { - return yes; } - } else if (my_range->start_infinity == true && my_range->end_infinity == false) { + return yes; + } + + if (my_range->start_infinity == true && my_range->end_infinity == false) { if (value <= my_range->end) { return no; - } else { - return yes; } - } else { - return no; + return yes; } + return no; } /* Returns status */ @@ -265,7 +308,8 @@ int get_status(double value, thresholds *my_thresholds) { char *np_escaped_string(const char *string) { char *data; - int i, j = 0; + int i; + int j = 0; data = strdup(string); for (i = 0; data[i]; i++) { if (data[i] == '\\') { @@ -302,7 +346,8 @@ int np_check_if_root(void) { return (geteuid() == 0); } * data strings. */ char *np_extract_value(const char *varlist, const char *name, char sep) { - char *tmp = NULL, *value = NULL; + char *tmp = NULL; + char *value = NULL; int i; while (1) { @@ -325,15 +370,17 @@ char *np_extract_value(const char *varlist, const char *name, char sep) { if ((tmp = index(varlist, sep))) { /* Value is delimited by a comma */ - if (tmp - varlist == 0) + if (tmp - varlist == 0) { continue; + } value = (char *)calloc(1, tmp - varlist + 1); strncpy(value, varlist, tmp - varlist); value[tmp - varlist] = '\0'; } else { /* Value is delimited by a \0 */ - if (strlen(varlist) == 0) + if (strlen(varlist) == 0) { continue; + } value = (char *)calloc(1, strlen(varlist) + 1); strncpy(value, varlist, strlen(varlist)); value[strlen(varlist)] = '\0'; @@ -351,9 +398,11 @@ char *np_extract_value(const char *varlist, const char *name, char sep) { } /* Clean-up trailing spaces/newlines */ - if (value) - for (i = strlen(value) - 1; isspace(value[i]); i--) + if (value) { + for (i = strlen(value) - 1; isspace(value[i]); i--) { value[i] = '\0'; + } + } return value; } @@ -378,14 +427,18 @@ const char *state_text(int result) { * return the corresponding STATE_ value or ERROR) */ int mp_translate_state(char *state_text) { - if (!strcasecmp(state_text, "OK") || !strcmp(state_text, "0")) + if (!strcasecmp(state_text, "OK") || !strcmp(state_text, "0")) { return STATE_OK; - if (!strcasecmp(state_text, "WARNING") || !strcmp(state_text, "1")) + } + if (!strcasecmp(state_text, "WARNING") || !strcmp(state_text, "1")) { return STATE_WARNING; - if (!strcasecmp(state_text, "CRITICAL") || !strcmp(state_text, "2")) + } + if (!strcasecmp(state_text, "CRITICAL") || !strcmp(state_text, "2")) { return STATE_CRITICAL; - if (!strcasecmp(state_text, "UNKNOWN") || !strcmp(state_text, "3")) + } + if (!strcasecmp(state_text, "UNKNOWN") || !strcmp(state_text, "3")) { return STATE_UNKNOWN; + } return ERROR; } @@ -394,7 +447,7 @@ int mp_translate_state(char *state_text) { * hopefully a unique key per service/plugin invocation. Use the extra-opts * parse of argv, so that uniqueness in parameters are reflected there. */ -char *_np_state_generate_key() { +char *_np_state_generate_key(void) { int i; char **argv = this_monitoring_plugin->argv; char keyname[41]; @@ -441,7 +494,7 @@ char *_np_state_generate_key() { return p; } -void _cleanup_state_data() { +void _cleanup_state_data(void) { if (this_monitoring_plugin->state->state_data != NULL) { np_free(this_monitoring_plugin->state->state_data->data); np_free(this_monitoring_plugin->state->state_data); @@ -453,19 +506,21 @@ void _cleanup_state_data() { * envvar NAGIOS_PLUGIN_STATE_DIRECTORY * statically compiled shared state directory */ -char *_np_state_calculate_location_prefix() { +char *_np_state_calculate_location_prefix(void) { char *env_dir; /* Do not allow passing MP_STATE_PATH in setuid plugins * for security reasons */ if (!mp_suid()) { env_dir = getenv("MP_STATE_PATH"); - if (env_dir && env_dir[0] != '\0') + if (env_dir && env_dir[0] != '\0') { return env_dir; + } /* This is the former ENV, for backward-compatibility */ env_dir = getenv("NAGIOS_PLUGIN_STATE_DIRECTORY"); - if (env_dir && env_dir[0] != '\0') + if (env_dir && env_dir[0] != '\0') { return env_dir; + } } return NP_STATE_DIR_PREFIX; @@ -483,19 +538,22 @@ void np_enable_state(char *keyname, int expected_data_version) { char *p = NULL; int ret; - if (this_monitoring_plugin == NULL) + if (this_monitoring_plugin == NULL) { die(STATE_UNKNOWN, _("This requires np_init to be called")); + } this_state = (state_key *)calloc(1, sizeof(state_key)); - if (this_state == NULL) + if (this_state == NULL) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } if (keyname == NULL) { temp_keyname = _np_state_generate_key(); } else { temp_keyname = strdup(keyname); - if (temp_keyname == NULL) + if (temp_keyname == NULL) { die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno)); + } } /* Die if invalid characters used for keyname */ p = temp_keyname; @@ -513,8 +571,9 @@ void np_enable_state(char *keyname, int expected_data_version) { /* Calculate filename */ ret = asprintf(&temp_filename, "%s/%lu/%s/%s", _np_state_calculate_location_prefix(), (unsigned long)geteuid(), this_monitoring_plugin->plugin_name, this_state->name); - if (ret < 0) + if (ret < 0) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } this_state->_filename = temp_filename; @@ -528,21 +587,23 @@ void np_enable_state(char *keyname, int expected_data_version) { * If numerically lower, then return as no previous state. die with UNKNOWN * if exceptional error. */ -state_data *np_state_read() { +state_data *np_state_read(void) { state_data *this_state_data = NULL; FILE *statefile; bool rc = false; - if (this_monitoring_plugin == NULL) + if (this_monitoring_plugin == NULL) { die(STATE_UNKNOWN, _("This requires np_init to be called")); + } /* Open file. If this fails, no previous state found */ statefile = fopen(this_monitoring_plugin->state->_filename, "r"); if (statefile != NULL) { this_state_data = (state_data *)calloc(1, sizeof(state_data)); - if (this_state_data == NULL) + if (this_state_data == NULL) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } this_state_data->data = NULL; this_monitoring_plugin->state->state_data = this_state_data; @@ -581,8 +642,9 @@ bool _np_state_read_file(FILE *f) { /* Note: This introduces a limit of 1024 bytes in the string data */ line = (char *)calloc(1, 1024); - if (line == NULL) + if (line == NULL) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } while (!failure && (fgets(line, 1024, f)) != NULL) { pos = strlen(line); @@ -590,38 +652,42 @@ bool _np_state_read_file(FILE *f) { line[pos - 1] = '\0'; } - if (line[0] == '#') + if (line[0] == '#') { continue; + } switch (expected) { case STATE_FILE_VERSION: i = atoi(line); - if (i != NP_STATE_FORMAT_VERSION) + if (i != NP_STATE_FORMAT_VERSION) { failure++; - else + } else { expected = STATE_DATA_VERSION; + } break; case STATE_DATA_VERSION: i = atoi(line); - if (i != this_monitoring_plugin->state->data_version) + if (i != this_monitoring_plugin->state->data_version) { failure++; - else + } else { expected = STATE_DATA_TIME; + } break; case STATE_DATA_TIME: /* If time > now, error */ data_time = strtoul(line, NULL, 10); - if (data_time > current_time) + if (data_time > current_time) { failure++; - else { + } else { this_monitoring_plugin->state->state_data->time = data_time; expected = STATE_DATA_TEXT; } break; case STATE_DATA_TEXT: this_monitoring_plugin->state->state_data->data = strdup(line); - if (this_monitoring_plugin->state->state_data->data == NULL) + if (this_monitoring_plugin->state->state_data->data == NULL) { die(STATE_UNKNOWN, _("Cannot execute strdup: %s"), strerror(errno)); + } expected = STATE_DATA_END; status = true; break; @@ -648,16 +714,18 @@ void np_state_write_string(time_t data_time, char *data_string) { char *directories = NULL; char *p = NULL; - if (data_time == 0) + if (data_time == 0) { time(¤t_time); - else + } else { current_time = data_time; + } /* If file doesn't currently exist, create directories */ if (access(this_monitoring_plugin->state->_filename, F_OK) != 0) { result = asprintf(&directories, "%s", this_monitoring_plugin->state->_filename); - if (result < 0) + if (result < 0) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } for (p = directories + 1; *p; p++) { if (*p == '/') { @@ -674,8 +742,9 @@ void np_state_write_string(time_t data_time, char *data_string) { } result = asprintf(&temp_file, "%s.XXXXXX", this_monitoring_plugin->state->_filename); - if (result < 0) + if (result < 0) { die(STATE_UNKNOWN, _("Cannot allocate memory: %s"), strerror(errno)); + } if ((fd = mkstemp(temp_file)) == -1) { np_free(temp_file); diff --git a/lib/utils_base.h b/lib/utils_base.h index a209cb6d..123066f8 100644 --- a/lib/utils_base.h +++ b/lib/utils_base.h @@ -2,6 +2,13 @@ #define _UTILS_BASE_ /* Header file for Monitoring Plugins utils_base.c */ +#include "../config.h" +#include + +#include "./perfdata.h" +#include "./thresholds.h" + + #ifndef USE_OPENSSL # include "sha256.h" #endif @@ -19,20 +26,6 @@ #define OUTSIDE 0 #define INSIDE 1 -typedef struct range_struct { - double start; - bool start_infinity; - double end; - int end_infinity; - int alert_on; /* OUTSIDE (default) or INSIDE */ - char *text; /* original unparsed text input */ -} range; - -typedef struct thresholds_struct { - range *warning; - range *critical; -} thresholds; - #define NP_STATE_FORMAT_VERSION 1 typedef struct state_data_struct { @@ -61,6 +54,7 @@ int _set_thresholds(thresholds **, char *, char *); void set_thresholds(thresholds **, char *, char *); void print_thresholds(const char *, thresholds *); bool check_range(double, range *); +bool mp_check_range(mp_perfdata_value, mp_range); int get_status(double, thresholds *); /* Handle timeouts */ @@ -107,12 +101,12 @@ char *np_extract_value(const char *, const char *, char); int mp_translate_state(char *); void np_enable_state(char *, int); -state_data *np_state_read(); +state_data *np_state_read(void); void np_state_write_string(time_t, char *); void np_init(char *, int argc, char **argv); void np_set_args(int argc, char **argv); -void np_cleanup(); +void np_cleanup(void); const char *state_text(int); #endif /* _UTILS_BASE_ */ diff --git a/lib/vendor/cJSON/.deps/.dirstamp b/lib/vendor/cJSON/.deps/.dirstamp new file mode 100644 index 00000000..e69de29b diff --git a/lib/vendor/cJSON/.deps/cJSON.Po b/lib/vendor/cJSON/.deps/cJSON.Po new file mode 100644 index 00000000..16cfe859 --- /dev/null +++ b/lib/vendor/cJSON/.deps/cJSON.Po @@ -0,0 +1,170 @@ +vendor/cJSON/cJSON.o: vendor/cJSON/cJSON.c /usr/include/stdc-predef.h \ + vendor/cJSON/../../../config.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h /usr/include/assert.h \ + /usr/include/features.h /usr/include/features-time64.h \ + /usr/include/x86_64-linux-gnu/bits/wordsize.h \ + /usr/include/x86_64-linux-gnu/bits/timesize.h \ + /usr/include/x86_64-linux-gnu/sys/cdefs.h \ + /usr/include/x86_64-linux-gnu/bits/long-double.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs.h \ + /usr/include/x86_64-linux-gnu/gnu/stubs-64.h ../gl/string.h \ + /usr/include/string.h \ + /usr/include/x86_64-linux-gnu/bits/libc-header-start.h ../gl/stddef.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \ + /usr/include/x86_64-linux-gnu/bits/types/locale_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__locale_t.h ../gl/strings.h \ + /usr/include/strings.h ../gl/stdio.h /usr/include/stdio.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \ + /usr/include/x86_64-linux-gnu/bits/types.h \ + /usr/include/x86_64-linux-gnu/bits/typesizes.h \ + /usr/include/x86_64-linux-gnu/bits/time64.h \ + /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \ + /usr/include/x86_64-linux-gnu/bits/types/FILE.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \ + /usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h \ + /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ + /usr/include/x86_64-linux-gnu/bits/floatn.h \ + /usr/include/x86_64-linux-gnu/bits/floatn-common.h \ + /usr/include/x86_64-linux-gnu/bits/stdio.h ../gl/sys/types.h \ + /usr/include/x86_64-linux-gnu/sys/types.h \ + /usr/include/x86_64-linux-gnu/bits/types/clock_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/clockid_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/time_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/timer_t.h \ + /usr/include/x86_64-linux-gnu/bits/stdint-intn.h /usr/include/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endian.h \ + /usr/include/x86_64-linux-gnu/bits/endianness.h \ + /usr/include/x86_64-linux-gnu/bits/byteswap.h \ + /usr/include/x86_64-linux-gnu/bits/uintn-identity.h \ + /usr/include/x86_64-linux-gnu/sys/select.h \ + /usr/include/x86_64-linux-gnu/bits/select.h \ + /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \ + /usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ + /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \ + /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \ + /usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h \ + /usr/include/x86_64-linux-gnu/bits/struct_mutex.h \ + /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h ../gl/math.h \ + /usr/include/math.h /usr/include/x86_64-linux-gnu/bits/math-vector.h \ + /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \ + /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \ + /usr/include/x86_64-linux-gnu/bits/fp-logb.h \ + /usr/include/x86_64-linux-gnu/bits/fp-fast.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls.h \ + /usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h \ + /usr/include/x86_64-linux-gnu/bits/iscanonical.h ../gl/stdlib.h \ + /usr/include/stdlib.h /usr/include/x86_64-linux-gnu/bits/waitflags.h \ + /usr/include/x86_64-linux-gnu/bits/waitstatus.h ../gl/alloca.h \ + /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \ + /usr/include/x86_64-linux-gnu/bits/stdlib-float.h ../gl/limits.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/limits.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/syslimits.h \ + /usr/include/limits.h /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ + /usr/include/x86_64-linux-gnu/bits/local_lim.h \ + /usr/include/linux/limits.h \ + /usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h \ + /usr/include/x86_64-linux-gnu/bits/posix2_lim.h \ + /usr/include/x86_64-linux-gnu/bits/xopen_lim.h \ + /usr/include/x86_64-linux-gnu/bits/uio_lim.h /usr/include/ctype.h \ + /usr/lib/gcc/x86_64-linux-gnu/14/include/float.h vendor/cJSON/cJSON.h +/usr/include/stdc-predef.h: +vendor/cJSON/../../../config.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h: +/usr/include/assert.h: +/usr/include/features.h: +/usr/include/features-time64.h: +/usr/include/x86_64-linux-gnu/bits/wordsize.h: +/usr/include/x86_64-linux-gnu/bits/timesize.h: +/usr/include/x86_64-linux-gnu/sys/cdefs.h: +/usr/include/x86_64-linux-gnu/bits/long-double.h: +/usr/include/x86_64-linux-gnu/gnu/stubs.h: +/usr/include/x86_64-linux-gnu/gnu/stubs-64.h: +../gl/string.h: +/usr/include/string.h: +/usr/include/x86_64-linux-gnu/bits/libc-header-start.h: +../gl/stddef.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h: +/usr/include/x86_64-linux-gnu/bits/types/locale_t.h: +/usr/include/x86_64-linux-gnu/bits/types/__locale_t.h: +../gl/strings.h: +/usr/include/strings.h: +../gl/stdio.h: +/usr/include/stdio.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h: +/usr/include/x86_64-linux-gnu/bits/types.h: +/usr/include/x86_64-linux-gnu/bits/typesizes.h: +/usr/include/x86_64-linux-gnu/bits/time64.h: +/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h: +/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h: +/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h: +/usr/include/x86_64-linux-gnu/bits/types/__FILE.h: +/usr/include/x86_64-linux-gnu/bits/types/FILE.h: +/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h: +/usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h: +/usr/include/x86_64-linux-gnu/bits/stdio_lim.h: +/usr/include/x86_64-linux-gnu/bits/floatn.h: +/usr/include/x86_64-linux-gnu/bits/floatn-common.h: +/usr/include/x86_64-linux-gnu/bits/stdio.h: +../gl/sys/types.h: +/usr/include/x86_64-linux-gnu/sys/types.h: +/usr/include/x86_64-linux-gnu/bits/types/clock_t.h: +/usr/include/x86_64-linux-gnu/bits/types/clockid_t.h: +/usr/include/x86_64-linux-gnu/bits/types/time_t.h: +/usr/include/x86_64-linux-gnu/bits/types/timer_t.h: +/usr/include/x86_64-linux-gnu/bits/stdint-intn.h: +/usr/include/endian.h: +/usr/include/x86_64-linux-gnu/bits/endian.h: +/usr/include/x86_64-linux-gnu/bits/endianness.h: +/usr/include/x86_64-linux-gnu/bits/byteswap.h: +/usr/include/x86_64-linux-gnu/bits/uintn-identity.h: +/usr/include/x86_64-linux-gnu/sys/select.h: +/usr/include/x86_64-linux-gnu/bits/select.h: +/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h: +/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h: +/usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h: +/usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h: +/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h: +/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h: +/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h: +/usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h: +/usr/include/x86_64-linux-gnu/bits/struct_mutex.h: +/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h: +../gl/math.h: +/usr/include/math.h: +/usr/include/x86_64-linux-gnu/bits/math-vector.h: +/usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h: +/usr/include/x86_64-linux-gnu/bits/flt-eval-method.h: +/usr/include/x86_64-linux-gnu/bits/fp-logb.h: +/usr/include/x86_64-linux-gnu/bits/fp-fast.h: +/usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h: +/usr/include/x86_64-linux-gnu/bits/mathcalls.h: +/usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h: +/usr/include/x86_64-linux-gnu/bits/iscanonical.h: +../gl/stdlib.h: +/usr/include/stdlib.h: +/usr/include/x86_64-linux-gnu/bits/waitflags.h: +/usr/include/x86_64-linux-gnu/bits/waitstatus.h: +../gl/alloca.h: +/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h: +/usr/include/x86_64-linux-gnu/bits/stdlib-float.h: +../gl/limits.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/limits.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/syslimits.h: +/usr/include/limits.h: +/usr/include/x86_64-linux-gnu/bits/posix1_lim.h: +/usr/include/x86_64-linux-gnu/bits/local_lim.h: +/usr/include/linux/limits.h: +/usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h: +/usr/include/x86_64-linux-gnu/bits/posix2_lim.h: +/usr/include/x86_64-linux-gnu/bits/xopen_lim.h: +/usr/include/x86_64-linux-gnu/bits/uio_lim.h: +/usr/include/ctype.h: +/usr/lib/gcc/x86_64-linux-gnu/14/include/float.h: +vendor/cJSON/cJSON.h: diff --git a/lib/vendor/cJSON/.dirstamp b/lib/vendor/cJSON/.dirstamp new file mode 100644 index 00000000..e69de29b diff --git a/lib/vendor/cJSON/cJSON.c b/lib/vendor/cJSON/cJSON.c new file mode 100644 index 00000000..12076e92 --- /dev/null +++ b/lib/vendor/cJSON/cJSON.c @@ -0,0 +1,3165 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include "../../../config.h" +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + size_t v1_len; + size_t v2_len; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + + v1_len = strlen(valuestring); + v2_len = strlen(object->valuestring); + + if (v1_len <= v2_len) + { + /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */ + if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring )) + { + return NULL; + } + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} diff --git a/lib/vendor/cJSON/cJSON.h b/lib/vendor/cJSON/cJSON.h new file mode 100644 index 00000000..37520bbc --- /dev/null +++ b/lib/vendor/cJSON/cJSON.h @@ -0,0 +1,306 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* Limits the length of circular references can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_CIRCULAR_LIMIT +#define CJSON_CIRCULAR_LIMIT 10000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugins/common.h b/plugins/common.h index 833479ce..47b1e4df 100644 --- a/plugins/common.h +++ b/plugins/common.h @@ -32,6 +32,7 @@ #define _COMMON_H_ #include "config.h" +#include "../lib/monitoringplug.h" #ifdef HAVE_FEATURES_H #include @@ -184,14 +185,6 @@ enum { ERROR = -1 }; -enum { - STATE_OK, - STATE_WARNING, - STATE_CRITICAL, - STATE_UNKNOWN, - STATE_DEPENDENT -}; - enum { DEFAULT_SOCKET_TIMEOUT = 10, /* timeout after 10 seconds */ MAX_INPUT_BUFFER = 8192, /* max size of most buffers we use */ diff --git a/plugins/popen.c b/plugins/popen.c index 2b9824bc..cfe930b6 100644 --- a/plugins/popen.c +++ b/plugins/popen.c @@ -40,7 +40,6 @@ #include "./common.h" #include "./utils.h" -#include "../lib/maxfd.h" /* extern so plugin has pid to kill exec'd process on timeouts */ extern pid_t *childpid; diff --git a/plugins/runcmd.c b/plugins/runcmd.c index 74843149..4429ceb0 100644 --- a/plugins/runcmd.c +++ b/plugins/runcmd.c @@ -40,6 +40,7 @@ /** includes **/ #include "runcmd.h" +#include "../lib/monitoringplug.h" #ifdef HAVE_SYS_WAIT_H # include #endif diff --git a/plugins/sslutils.c b/plugins/sslutils.c index 3c928413..719de575 100644 --- a/plugins/sslutils.c +++ b/plugins/sslutils.c @@ -29,6 +29,7 @@ #define MAX_CN_LENGTH 256 #include "common.h" #include "netutils.h" +#include "../lib/monitoringplug.h" #ifdef HAVE_SSL static SSL_CTX *ctx = NULL; diff --git a/plugins/utils.c b/plugins/utils.c index 6d366e3d..09649429 100644 --- a/plugins/utils.c +++ b/plugins/utils.c @@ -42,54 +42,6 @@ extern const char *progname; time_t start_time, end_time; -/* ************************************************************************** - * max_state(STATE_x, STATE_y) - * compares STATE_x to STATE_y and returns result based on the following - * STATE_UNKNOWN < STATE_OK < STATE_WARNING < STATE_CRITICAL - * - * Note that numerically the above does not hold - ****************************************************************************/ - -int max_state(int a, int b) { - if (a == STATE_CRITICAL || b == STATE_CRITICAL) - return STATE_CRITICAL; - else if (a == STATE_WARNING || b == STATE_WARNING) - return STATE_WARNING; - else if (a == STATE_OK || b == STATE_OK) - return STATE_OK; - else if (a == STATE_UNKNOWN || b == STATE_UNKNOWN) - return STATE_UNKNOWN; - else if (a == STATE_DEPENDENT || b == STATE_DEPENDENT) - return STATE_DEPENDENT; - else - return max(a, b); -} - -/* ************************************************************************** - * max_state_alt(STATE_x, STATE_y) - * compares STATE_x to STATE_y and returns result based on the following - * STATE_OK < STATE_DEPENDENT < STATE_UNKNOWN < STATE_WARNING < STATE_CRITICAL - * - * The main difference between max_state_alt and max_state it that it doesn't - * allow setting a default to UNKNOWN. It will instead prioritixe any valid - * non-OK state. - ****************************************************************************/ - -int max_state_alt(int a, int b) { - if (a == STATE_CRITICAL || b == STATE_CRITICAL) - return STATE_CRITICAL; - else if (a == STATE_WARNING || b == STATE_WARNING) - return STATE_WARNING; - else if (a == STATE_UNKNOWN || b == STATE_UNKNOWN) - return STATE_UNKNOWN; - else if (a == STATE_DEPENDENT || b == STATE_DEPENDENT) - return STATE_DEPENDENT; - else if (a == STATE_OK || b == STATE_OK) - return STATE_OK; - else - return max(a, b); -} - void usage(const char *msg) { printf("%s\n", msg); print_usage(); diff --git a/plugins/utils.h b/plugins/utils.h index f939e337..c7073990 100644 --- a/plugins/utils.h +++ b/plugins/utils.h @@ -13,11 +13,11 @@ in order to resist overflow attacks. In addition, a few functions are provided to standardize version and error reporting across the entire suite of plugins. */ -/* now some functions etc are being defined in ../lib/utils_base.c */ -#include "utils_base.h" - +#include "../config.h" #include - +#include +#include +#include #ifdef NP_EXTRA_OPTS /* Include extra-opts functions if compiled in */ @@ -78,9 +78,6 @@ char *strpcat (char *, const char *, const char *); int xvasprintf (char **strp, const char *fmt, va_list ap); int xasprintf (char **strp, const char *fmt, ...); -int max_state (int a, int b); -int max_state_alt (int a, int b); - void usage (const char *) __attribute__((noreturn)); void usage2(const char *, const char *) __attribute__((noreturn)); void usage3(const char *, int) __attribute__((noreturn)); -- cgit v1.2.3-74-g34f1 From 5acd14fcfb419c9ef0d6bc38384dde4cd6b70bd9 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:58:59 +0100 Subject: Implement new output functionality for check_swap --- plugins/check_swap.c | 132 ++++++++++++++++++++++++++++---------- plugins/check_swap.d/check_swap.h | 4 ++ plugins/check_swap.d/swap.c | 2 + 3 files changed, 104 insertions(+), 34 deletions(-) diff --git a/plugins/check_swap.c b/plugins/check_swap.c index bc90a90b..1f2d0273 100644 --- a/plugins/check_swap.c +++ b/plugins/check_swap.c @@ -28,6 +28,9 @@ *****************************************************************************/ #include "common.h" +#include "output.h" +#include "states.h" +#include #ifdef HAVE_DECL_SWAPCTL # ifdef HAVE_SYS_PARAM_H # include @@ -69,8 +72,6 @@ int main(int argc, char **argv) { bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); - char *status = strdup(""); - /* Parse extra opts if any */ argv = np_extra_opts(&argc, argv, progname); @@ -90,59 +91,101 @@ int main(int argc, char **argv) { } double percent_used; + mp_check overall = mp_check_init(); + if (config.output_format_is_set) { + overall.format = config.output_format; + } + mp_subcheck sc1 = mp_subcheck_init(); + sc1 = mp_set_subcheck_default_state(sc1, STATE_OK); + /* if total_swap_mb == 0, let's not divide by 0 */ if (data.metrics.total != 0) { percent_used = HUNDRED_PERCENT * ((double)data.metrics.used) / ((double)data.metrics.total); } else { - printf(_("SWAP %s - Swap is either disabled, not present, or of zero " - "size."), - state_text(data.statusCode)); - exit(config.no_swap_state); + sc1 = mp_set_subcheck_state(sc1, config.no_swap_state); + sc1.output = (char *)_("Swap is either disabled, not present, or of zero size."); + + mp_add_subcheck_to_check(&overall, sc1); + mp_exit(overall); } if (verbose) { printf("Computed usage percentage: %g\n", percent_used); } - uint64_t warn_print = config.warn.value; - if (config.warn.is_percentage) { - warn_print = config.warn.value * (data.metrics.total / HUNDRED_PERCENT); + mp_perfdata pd = perfdata_init(); + pd.label = "swap"; + pd = mp_set_pd_value(pd, data.metrics.free); + pd.uom = "B"; + + if (config.warn_is_set) { + uint64_t warn_print = config.warn.value; + if (config.warn.is_percentage) { + warn_print = config.warn.value * (data.metrics.total / HUNDRED_PERCENT); + } + + mp_perfdata_value warn_pd = mp_create_pd_value(warn_print); + + mp_range warn_range = mp_range_init(); + warn_range.end_infinity = false; + warn_range.end = warn_pd; + + pd.warn = warn_range; + pd.warn_present = true; } - uint64_t crit_print = config.crit.value; - if (config.crit.is_percentage) { - crit_print = config.crit.value * (data.metrics.total / HUNDRED_PERCENT); + if (config.crit_is_set) { + uint64_t crit_print = config.crit.value; + if (config.crit.is_percentage) { + crit_print = config.crit.value * (data.metrics.total / HUNDRED_PERCENT); + } + + mp_perfdata_value crit_pd = mp_create_pd_value(crit_print); + + mp_range crit_range = mp_range_init(); + crit_range.end_infinity = false; + crit_range.end = crit_pd; + + pd.crit = crit_range; + pd.crit_present = true; } - char *perfdata = perfdata_uint64("swap", data.metrics.free, "B", config.warn_is_set, warn_print, config.crit_is_set, crit_print, true, - 0, true, data.metrics.total); + mp_perfdata_value max = mp_create_pd_value(data.metrics.total); + pd.max = max; + pd.max_present = true; + + mp_perfdata_value min = mp_create_pd_value(0); + pd.min = min; + pd.min_present = true; + + mp_add_perfdata_to_subcheck(&sc1, pd); + if (verbose > 1) { + printf("Warn threshold value: %" PRIu64 "\n", config.warn.value); + } if (config.warn_is_set) { - if (verbose > 1) { - printf("Warn threshold value: %" PRIu64 "\n", config.warn.value); + if ((config.warn.is_percentage && (percent_used >= (100 - (double)config.warn.value))) || config.warn.value >= data.metrics.free) { + sc1 = mp_set_subcheck_state(sc1, STATE_WARNING); } + } - if ((config.warn.is_percentage && (percent_used >= (double)(HUNDRED_PERCENT - config.warn.value))) || - config.warn.value >= data.metrics.free) { - data.statusCode = max_state(data.statusCode, STATE_WARNING); - } + if (verbose > 1) { + printf("Crit threshold value: %" PRIu64 "\n", config.crit.value); } if (config.crit_is_set) { - if (verbose > 1) { - printf("Crit threshold value: %" PRIu64 "\n", config.crit.value); - } - - if ((config.crit.is_percentage && (percent_used >= (double)(HUNDRED_PERCENT - config.crit.value))) || - config.crit.value >= data.metrics.free) { - data.statusCode = max_state(data.statusCode, STATE_CRITICAL); + if ((config.crit.is_percentage && (percent_used >= (100 - (double)config.crit.value))) || config.crit.value >= data.metrics.free) { + sc1 = mp_set_subcheck_state(sc1, STATE_CRITICAL); } } - printf(_("SWAP %s - %g%% free (%lluMiB out of %lluMiB) %s|%s\n"), state_text(data.statusCode), (HUNDRED_PERCENT - percent_used), - BYTES_TO_MiB(data.metrics.free), BYTES_TO_MiB(data.metrics.total), status, perfdata); + xasprintf(&sc1.output, _("%g%% free (%lluMiB out of %lluMiB)"), (100 - percent_used), data.metrics.free >> 20, + data.metrics.total >> 20); + + overall.summary = "Swap"; + mp_add_subcheck_to_check(&overall, sc1); - exit(data.statusCode); + mp_exit(overall); } int check_swap(float free_swap_mb, float total_swap_mb, swap_config config) { @@ -172,15 +215,22 @@ int check_swap(float free_swap_mb, float total_swap_mb, swap_config config) { return STATE_OK; } +#define output_format_index CHAR_MAX + 1 + /* process command-line arguments */ swap_config_wrapper process_arguments(int argc, char **argv) { swap_config_wrapper conf_wrapper = {.errorcode = OK}; conf_wrapper.config = swap_config_init(); - static struct option longopts[] = {{"warning", required_argument, 0, 'w'}, {"critical", required_argument, 0, 'c'}, - {"allswaps", no_argument, 0, 'a'}, {"no-swap", required_argument, 0, 'n'}, - {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, - {"help", no_argument, 0, 'h'}, {0, 0, 0, 0}}; + static struct option longopts[] = {{"warning", required_argument, 0, 'w'}, + {"critical", required_argument, 0, 'c'}, + {"allswaps", no_argument, 0, 'a'}, + {"no-swap", required_argument, 0, 'n'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {"output-format", required_argument, 0, output_format_index}, + {0, 0, 0, 0}}; while (true) { int option = 0; @@ -263,6 +313,18 @@ swap_config_wrapper process_arguments(int argc, char **argv) { case 'v': /* verbose */ verbose++; break; + case output_format_index: { + parsed_output_format parser = mp_parse_output_format(optarg); + if (!parser.parsing_success) { + // TODO List all available formats here, maybe add anothoer usage function + printf("Invalid output format: %s\n", optarg); + exit(STATE_UNKNOWN); + } + + conf_wrapper.config.output_format_is_set = true; + conf_wrapper.config.output_format = parser.output_format; + break; + } case 'V': /* version */ print_revision(progname, NP_VERSION); exit(STATE_UNKNOWN); @@ -319,6 +381,8 @@ void print_help(swap_config config) { _("Resulting state when there is no swap regardless of thresholds. " "Default:"), state_text(config.no_swap_state)); + printf(" %s\n", "--output-format"); + printf(" %s\n", _("Select output format. Valid values: \"one-line\", \"icingaweb2\", \"summary-only\", \"mp-test-json\"")); printf(UT_VERBOSE); printf("\n"); diff --git a/plugins/check_swap.d/check_swap.h b/plugins/check_swap.d/check_swap.h index 99039b21..1000fc9e 100644 --- a/plugins/check_swap.d/check_swap.h +++ b/plugins/check_swap.d/check_swap.h @@ -1,6 +1,7 @@ #pragma once #include "../common.h" +#include "output.h" #ifndef SWAP_CONVERSION # define SWAP_CONVERSION 1 @@ -32,6 +33,9 @@ typedef struct { check_swap_threshold crit; bool on_aix; int conversion_factor; + + bool output_format_is_set; + mp_output_format output_format; } swap_config; swap_config swap_config_init(void); diff --git a/plugins/check_swap.d/swap.c b/plugins/check_swap.d/swap.c index 2fe4544f..180d5037 100644 --- a/plugins/check_swap.d/swap.c +++ b/plugins/check_swap.d/swap.c @@ -14,6 +14,8 @@ swap_config swap_config_init(void) { tmp.warn_is_set = false; tmp.crit_is_set = false; + tmp.output_format_is_set = false; + #ifdef _AIX tmp.on_aix = true; #else -- cgit v1.2.3-74-g34f1 From 24172ca0e014454a7a6f3f1a12104175f9ff7c71 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:49:43 +0100 Subject: Exit with 0 in JSON-format if everything else works --- lib/output.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/output.c b/lib/output.c index 9ba049e2..2c40bc7f 100644 --- a/lib/output.c +++ b/lib/output.c @@ -413,6 +413,10 @@ void mp_print_output(mp_check check) { puts(mp_fmt_output(check)); } */ void mp_exit(mp_check check) { mp_print_output(check); + if (check.format == MP_FORMAT_TEST_JSON) { + exit(0); + } + exit(mp_compute_check_state(check)); } -- cgit v1.2.3-74-g34f1 From 6320a4e9534595d9c5f0d6d73e6858f548092a3e Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:50:08 +0100 Subject: Fix one-line formatting --- lib/output.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/output.c b/lib/output.c index 2c40bc7f..62a00fed 100644 --- a/lib/output.c +++ b/lib/output.c @@ -260,7 +260,7 @@ char *mp_fmt_output(mp_check check) { subchecks = subchecks->next; } - break; + return result; } case MP_FORMAT_ICINGA_WEB_2: { if (check.summary == NULL) { @@ -354,13 +354,14 @@ static char *generate_indentation_string(unsigned int indentation) { */ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation) { char *result = NULL; + mp_subcheck_list *subchecks = NULL; switch (output_format) { case MP_FORMAT_ICINGA_WEB_2: xasprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), check.output); - mp_subcheck_list *subchecks = check.subchecks; + subchecks = check.subchecks; while (subchecks != NULL) { xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); @@ -368,6 +369,14 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch } return result; case MP_FORMAT_ONE_LINE: + xasprintf(&result, "[%s] - %s", state_text(mp_compute_subcheck_state(check)), check.output); + + subchecks = check.subchecks; + + while (subchecks != NULL) { + xasprintf(&result, " - %s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); + subchecks = subchecks->next; + } return result; case MP_FORMAT_SUMMARY_ONLY: return result; -- cgit v1.2.3-74-g34f1 From d27e0862a9e514149d81622a82622d31ab3bee9b Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 11:57:25 +0100 Subject: Fix previous check_swap tests --- plugins/t/check_swap.t | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/plugins/t/check_swap.t b/plugins/t/check_swap.t index eaa81083..6ef2323d 100644 --- a/plugins/t/check_swap.t +++ b/plugins/t/check_swap.t @@ -5,39 +5,41 @@ # use strict; +use warnings; use Test::More tests => 14; use NPTest; -my $successOutput = '/^SWAP OK - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; -my $failureOutput = '/^SWAP CRITICAL - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; -my $warnOutput = '/^SWAP WARNING - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; +my $successOutput = '/^OK.* - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; +my $failureOutput = '/^CRITICAL: .*- [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; +my $warnOutput = '/^WARNING: .*- [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; +my $outputFormat = '--output-format one-line'; my $result; -$result = NPTest->testCmd( "./check_swap" ); # Always OK +$result = NPTest->testCmd( "./check_swap $outputFormat" ); # Always OK cmp_ok( $result->return_code, "==", 0, "Always OK" ); like( $result->output, $successOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -w 1048576 -c 1048576" ); # 1 MB free +$result = NPTest->testCmd( "./check_swap -w 1048576 -c 1048576 $outputFormat" ); # 1 MB free cmp_ok( $result->return_code, "==", 0, "At least 1MB free" ); like( $result->output, $successOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -w 1% -c 1%" ); # 1% free +$result = NPTest->testCmd( "./check_swap -w 1% -c 1% $outputFormat" ); # 1% free cmp_ok( $result->return_code, "==", 0, 'At least 1% free' ); like( $result->output, $successOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -w 100% -c 100%" ); # 100% (always critical) +$result = NPTest->testCmd( "./check_swap -w 100% -c 100% $outputFormat" ); # 100% (always critical) cmp_ok( $result->return_code, "==", 2, 'Get critical because not 100% free' ); like( $result->output, $failureOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -w 100% -c 1%" ); # 100% (always warn) +$result = NPTest->testCmd( "./check_swap -w 100% -c 1% $outputFormat" ); # 100% (always warn) cmp_ok( $result->return_code, "==", 1, 'Get warning because not 100% free' ); like( $result->output, $warnOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -w 100%" ); # 100% (single threshold, always warn) +$result = NPTest->testCmd( "./check_swap -w 100% $outputFormat" ); # 100% (single threshold, always warn) cmp_ok( $result->return_code, "==", 1, 'Get warning because not 100% free' ); like( $result->output, $warnOutput, "Right output" ); -$result = NPTest->testCmd( "./check_swap -c 100%" ); # 100% (single threshold, always critical) +$result = NPTest->testCmd( "./check_swap -c 100% $outputFormat" ); # 100% (single threshold, always critical) cmp_ok( $result->return_code, "==", 2, 'Get critical because not 100% free' ); like( $result->output, $failureOutput, "Right output" ); -- cgit v1.2.3-74-g34f1 From ec18b80cdbdc2c4c1e7eb587e251177e8cc7ca11 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:07:01 +0100 Subject: Add tests for check_swap with JSON output --- plugins/t/check_swap.t | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/plugins/t/check_swap.t b/plugins/t/check_swap.t index 6ef2323d..93e481c3 100644 --- a/plugins/t/check_swap.t +++ b/plugins/t/check_swap.t @@ -6,8 +6,9 @@ use strict; use warnings; -use Test::More tests => 14; +use Test::More tests => 35; use NPTest; +use JSON; my $successOutput = '/^OK.* - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; my $failureOutput = '/^CRITICAL: .*- [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; @@ -43,3 +44,50 @@ like( $result->output, $warnOutput, "Right output" ); $result = NPTest->testCmd( "./check_swap -c 100% $outputFormat" ); # 100% (single threshold, always critical) cmp_ok( $result->return_code, "==", 2, 'Get critical because not 100% free' ); like( $result->output, $failureOutput, "Right output" ); + + +$outputFormat = '--output-format mp-test-json'; +my $output; +my $message = '/^[0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; + +$result = NPTest->testCmd( "./check_swap $outputFormat" ); # Always OK +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "OK", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -w 1048576 -c 1048576 $outputFormat" ); # 1 MB free +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "OK", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -w 1% -c 1% $outputFormat" ); # 1% free +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "OK", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -w 100% -c 100% $outputFormat" ); # 100% (always critical) +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "CRITICAL", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -w 100% -c 1% $outputFormat" ); # 100% (always warn) +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "WARNING", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -w 100% $outputFormat" ); # 100% (single threshold, always warn) +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "WARNING", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); + +$result = NPTest->testCmd( "./check_swap -c 100% $outputFormat" ); # 100% (single threshold, always critical) +cmp_ok( $result->return_code, "==", 0, "Always OK" ); +$output = decode_json($result->output); +is($output->{'state'}, "CRITICAL", "State was correct"); +like($output->{'checks'}->[0]->{'output'}, $message, "Output was correct"); -- cgit v1.2.3-74-g34f1 From e6f0bb802c64d21ef1f942306a2d53521f565d9d Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:23:37 +0100 Subject: Add new files to EXTRA_DIST --- lib/Makefile.am | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/Makefile.am b/lib/Makefile.am index 0ea33dd8..e41201c4 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -8,7 +8,20 @@ AM_CPPFLAGS = -DNP_STATE_DIR_PREFIX=\"$(localstatedir)\" \ -I$(srcdir) -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/plugins libmonitoringplug_a_SOURCES = utils_base.c utils_disk.c utils_tcp.c utils_cmd.c maxfd.c output.c perfdata.c output.c thresholds.c vendor/cJSON/cJSON.c -EXTRA_DIST = utils_base.h utils_disk.h utils_tcp.h utils_cmd.h parse_ini.h extra_opts.h maxfd.h perfdata.h output.h thresholds.h states.h vendor/cJSON/cJSON.h + +EXTRA_DIST = utils_base.h \ + utils_disk.h \ + utils_tcp.h \ + utils_cmd.h \ + parse_ini.h \ + extra_opts.h \ + maxfd.h \ + perfdata.h \ + output.h \ + thresholds.h \ + states.h \ + vendor/cJSON/cJSON.h \ + monitoringplug.h if USE_PARSE_INI libmonitoringplug_a_SOURCES += parse_ini.c extra_opts.c -- cgit v1.2.3-74-g34f1 From 4ca309dd993735c6e9d07e9defc71f20bba776a9 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:33:08 +0100 Subject: Maybe fix compilation errors --- lib/monitoringplug.h | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/monitoringplug.h b/lib/monitoringplug.h index 5f05a974..a555d736 100644 --- a/lib/monitoringplug.h +++ b/lib/monitoringplug.h @@ -1,7 +1,6 @@ #pragma once #include "./states.h" -#include "./utils.h" #include "./utils_base.h" #include "./thresholds.h" #include "./maxfd.h" -- cgit v1.2.3-74-g34f1 From d4274cfdac8125160f43a8098a578eb3077fcda0 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:58:05 +0100 Subject: Ignore generated stuff for vendored stuff in lib --- .gitignore | 1 + lib/vendor/cJSON/.deps/.dirstamp | 0 lib/vendor/cJSON/.deps/cJSON.Po | 170 --------------------------------------- 3 files changed, 1 insertion(+), 170 deletions(-) delete mode 100644 lib/vendor/cJSON/.deps/.dirstamp delete mode 100644 lib/vendor/cJSON/.deps/cJSON.Po diff --git a/.gitignore b/.gitignore index 0b5028ed..1af41279 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ NP-VERSION-FILE /lib/libmonitoringplug.a /lib/Makefile /lib/Makefile.in +/lib/vendor/cJSON/.deps # /lib/tests/ /lib/tests/base64.Po diff --git a/lib/vendor/cJSON/.deps/.dirstamp b/lib/vendor/cJSON/.deps/.dirstamp deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/vendor/cJSON/.deps/cJSON.Po b/lib/vendor/cJSON/.deps/cJSON.Po deleted file mode 100644 index 16cfe859..00000000 --- a/lib/vendor/cJSON/.deps/cJSON.Po +++ /dev/null @@ -1,170 +0,0 @@ -vendor/cJSON/cJSON.o: vendor/cJSON/cJSON.c /usr/include/stdc-predef.h \ - vendor/cJSON/../../../config.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h /usr/include/assert.h \ - /usr/include/features.h /usr/include/features-time64.h \ - /usr/include/x86_64-linux-gnu/bits/wordsize.h \ - /usr/include/x86_64-linux-gnu/bits/timesize.h \ - /usr/include/x86_64-linux-gnu/sys/cdefs.h \ - /usr/include/x86_64-linux-gnu/bits/long-double.h \ - /usr/include/x86_64-linux-gnu/gnu/stubs.h \ - /usr/include/x86_64-linux-gnu/gnu/stubs-64.h ../gl/string.h \ - /usr/include/string.h \ - /usr/include/x86_64-linux-gnu/bits/libc-header-start.h ../gl/stddef.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h \ - /usr/include/x86_64-linux-gnu/bits/types/locale_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/__locale_t.h ../gl/strings.h \ - /usr/include/strings.h ../gl/stdio.h /usr/include/stdio.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h \ - /usr/include/x86_64-linux-gnu/bits/types.h \ - /usr/include/x86_64-linux-gnu/bits/typesizes.h \ - /usr/include/x86_64-linux-gnu/bits/time64.h \ - /usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/__FILE.h \ - /usr/include/x86_64-linux-gnu/bits/types/FILE.h \ - /usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h \ - /usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h \ - /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ - /usr/include/x86_64-linux-gnu/bits/floatn.h \ - /usr/include/x86_64-linux-gnu/bits/floatn-common.h \ - /usr/include/x86_64-linux-gnu/bits/stdio.h ../gl/sys/types.h \ - /usr/include/x86_64-linux-gnu/sys/types.h \ - /usr/include/x86_64-linux-gnu/bits/types/clock_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/clockid_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/time_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/timer_t.h \ - /usr/include/x86_64-linux-gnu/bits/stdint-intn.h /usr/include/endian.h \ - /usr/include/x86_64-linux-gnu/bits/endian.h \ - /usr/include/x86_64-linux-gnu/bits/endianness.h \ - /usr/include/x86_64-linux-gnu/bits/byteswap.h \ - /usr/include/x86_64-linux-gnu/bits/uintn-identity.h \ - /usr/include/x86_64-linux-gnu/sys/select.h \ - /usr/include/x86_64-linux-gnu/bits/select.h \ - /usr/include/x86_64-linux-gnu/bits/types/sigset_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h \ - /usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h \ - /usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h \ - /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h \ - /usr/include/x86_64-linux-gnu/bits/thread-shared-types.h \ - /usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h \ - /usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h \ - /usr/include/x86_64-linux-gnu/bits/struct_mutex.h \ - /usr/include/x86_64-linux-gnu/bits/struct_rwlock.h ../gl/math.h \ - /usr/include/math.h /usr/include/x86_64-linux-gnu/bits/math-vector.h \ - /usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h \ - /usr/include/x86_64-linux-gnu/bits/flt-eval-method.h \ - /usr/include/x86_64-linux-gnu/bits/fp-logb.h \ - /usr/include/x86_64-linux-gnu/bits/fp-fast.h \ - /usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h \ - /usr/include/x86_64-linux-gnu/bits/mathcalls.h \ - /usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h \ - /usr/include/x86_64-linux-gnu/bits/iscanonical.h ../gl/stdlib.h \ - /usr/include/stdlib.h /usr/include/x86_64-linux-gnu/bits/waitflags.h \ - /usr/include/x86_64-linux-gnu/bits/waitstatus.h ../gl/alloca.h \ - /usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h \ - /usr/include/x86_64-linux-gnu/bits/stdlib-float.h ../gl/limits.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/limits.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/syslimits.h \ - /usr/include/limits.h /usr/include/x86_64-linux-gnu/bits/posix1_lim.h \ - /usr/include/x86_64-linux-gnu/bits/local_lim.h \ - /usr/include/linux/limits.h \ - /usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h \ - /usr/include/x86_64-linux-gnu/bits/posix2_lim.h \ - /usr/include/x86_64-linux-gnu/bits/xopen_lim.h \ - /usr/include/x86_64-linux-gnu/bits/uio_lim.h /usr/include/ctype.h \ - /usr/lib/gcc/x86_64-linux-gnu/14/include/float.h vendor/cJSON/cJSON.h -/usr/include/stdc-predef.h: -vendor/cJSON/../../../config.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/stdbool.h: -/usr/include/assert.h: -/usr/include/features.h: -/usr/include/features-time64.h: -/usr/include/x86_64-linux-gnu/bits/wordsize.h: -/usr/include/x86_64-linux-gnu/bits/timesize.h: -/usr/include/x86_64-linux-gnu/sys/cdefs.h: -/usr/include/x86_64-linux-gnu/bits/long-double.h: -/usr/include/x86_64-linux-gnu/gnu/stubs.h: -/usr/include/x86_64-linux-gnu/gnu/stubs-64.h: -../gl/string.h: -/usr/include/string.h: -/usr/include/x86_64-linux-gnu/bits/libc-header-start.h: -../gl/stddef.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/stddef.h: -/usr/include/x86_64-linux-gnu/bits/types/locale_t.h: -/usr/include/x86_64-linux-gnu/bits/types/__locale_t.h: -../gl/strings.h: -/usr/include/strings.h: -../gl/stdio.h: -/usr/include/stdio.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/stdarg.h: -/usr/include/x86_64-linux-gnu/bits/types.h: -/usr/include/x86_64-linux-gnu/bits/typesizes.h: -/usr/include/x86_64-linux-gnu/bits/time64.h: -/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h: -/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h: -/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h: -/usr/include/x86_64-linux-gnu/bits/types/__FILE.h: -/usr/include/x86_64-linux-gnu/bits/types/FILE.h: -/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h: -/usr/include/x86_64-linux-gnu/bits/types/cookie_io_functions_t.h: -/usr/include/x86_64-linux-gnu/bits/stdio_lim.h: -/usr/include/x86_64-linux-gnu/bits/floatn.h: -/usr/include/x86_64-linux-gnu/bits/floatn-common.h: -/usr/include/x86_64-linux-gnu/bits/stdio.h: -../gl/sys/types.h: -/usr/include/x86_64-linux-gnu/sys/types.h: -/usr/include/x86_64-linux-gnu/bits/types/clock_t.h: -/usr/include/x86_64-linux-gnu/bits/types/clockid_t.h: -/usr/include/x86_64-linux-gnu/bits/types/time_t.h: -/usr/include/x86_64-linux-gnu/bits/types/timer_t.h: -/usr/include/x86_64-linux-gnu/bits/stdint-intn.h: -/usr/include/endian.h: -/usr/include/x86_64-linux-gnu/bits/endian.h: -/usr/include/x86_64-linux-gnu/bits/endianness.h: -/usr/include/x86_64-linux-gnu/bits/byteswap.h: -/usr/include/x86_64-linux-gnu/bits/uintn-identity.h: -/usr/include/x86_64-linux-gnu/sys/select.h: -/usr/include/x86_64-linux-gnu/bits/select.h: -/usr/include/x86_64-linux-gnu/bits/types/sigset_t.h: -/usr/include/x86_64-linux-gnu/bits/types/__sigset_t.h: -/usr/include/x86_64-linux-gnu/bits/types/struct_timeval.h: -/usr/include/x86_64-linux-gnu/bits/types/struct_timespec.h: -/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h: -/usr/include/x86_64-linux-gnu/bits/thread-shared-types.h: -/usr/include/x86_64-linux-gnu/bits/pthreadtypes-arch.h: -/usr/include/x86_64-linux-gnu/bits/atomic_wide_counter.h: -/usr/include/x86_64-linux-gnu/bits/struct_mutex.h: -/usr/include/x86_64-linux-gnu/bits/struct_rwlock.h: -../gl/math.h: -/usr/include/math.h: -/usr/include/x86_64-linux-gnu/bits/math-vector.h: -/usr/include/x86_64-linux-gnu/bits/libm-simd-decl-stubs.h: -/usr/include/x86_64-linux-gnu/bits/flt-eval-method.h: -/usr/include/x86_64-linux-gnu/bits/fp-logb.h: -/usr/include/x86_64-linux-gnu/bits/fp-fast.h: -/usr/include/x86_64-linux-gnu/bits/mathcalls-helper-functions.h: -/usr/include/x86_64-linux-gnu/bits/mathcalls.h: -/usr/include/x86_64-linux-gnu/bits/mathcalls-narrow.h: -/usr/include/x86_64-linux-gnu/bits/iscanonical.h: -../gl/stdlib.h: -/usr/include/stdlib.h: -/usr/include/x86_64-linux-gnu/bits/waitflags.h: -/usr/include/x86_64-linux-gnu/bits/waitstatus.h: -../gl/alloca.h: -/usr/include/x86_64-linux-gnu/bits/stdlib-bsearch.h: -/usr/include/x86_64-linux-gnu/bits/stdlib-float.h: -../gl/limits.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/limits.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/syslimits.h: -/usr/include/limits.h: -/usr/include/x86_64-linux-gnu/bits/posix1_lim.h: -/usr/include/x86_64-linux-gnu/bits/local_lim.h: -/usr/include/linux/limits.h: -/usr/include/x86_64-linux-gnu/bits/pthread_stack_min-dynamic.h: -/usr/include/x86_64-linux-gnu/bits/posix2_lim.h: -/usr/include/x86_64-linux-gnu/bits/xopen_lim.h: -/usr/include/x86_64-linux-gnu/bits/uio_lim.h: -/usr/include/ctype.h: -/usr/lib/gcc/x86_64-linux-gnu/14/include/float.h: -vendor/cJSON/cJSON.h: -- cgit v1.2.3-74-g34f1 From 24f21473fb493edf7fced3b4f680c99123145014 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:59:32 +0100 Subject: Use asprintf in lib directly instead of xasprintf --- lib/output.c | 32 ++++++++++++++++---------------- lib/perfdata.c | 36 ++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/output.c b/lib/output.c index 62a00fed..4c5041c8 100644 --- a/lib/output.c +++ b/lib/output.c @@ -23,7 +23,7 @@ static inline char *fmt_subcheck_perfdata(mp_subcheck check) { int added = 0; if (check.perfdata != NULL) { - added = xasprintf(&result, "%s", pd_list_to_string(*check.perfdata)); + added = asprintf(&result, "%s", pd_list_to_string(*check.perfdata)); } if (check.subchecks == NULL) { @@ -35,10 +35,10 @@ static inline char *fmt_subcheck_perfdata(mp_subcheck check) { while (subchecks != NULL) { if (added > 0) { - added = xasprintf(&result, "%s%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); + added = asprintf(&result, "%s%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); } else { // TODO free previous result here? - added = xasprintf(&result, "%s", result, fmt_subcheck_perfdata(subchecks->subcheck)); + added = asprintf(&result, "%s", fmt_subcheck_perfdata(subchecks->subcheck)); } subchecks = subchecks->next; @@ -185,7 +185,7 @@ char *get_subcheck_summary(mp_check check) { subchecks = subchecks->next; } char *result = NULL; - xasprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown); + asprintf(&result, "ok=%d, warning=%d, critical=%d, unknown=%d", ok, warning, critical, unknown); return result; } @@ -238,7 +238,7 @@ char *mp_fmt_output(mp_check check) { check.summary = get_subcheck_summary(check); } - xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + asprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); return result; case MP_FORMAT_ONE_LINE: { @@ -251,12 +251,12 @@ char *mp_fmt_output(mp_check check) { check.summary = get_subcheck_summary(check); } - xasprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); + asprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); mp_subcheck_list *subchecks = check.subchecks; while (subchecks != NULL) { - xasprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1)); + asprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1)); subchecks = subchecks->next; } @@ -267,12 +267,12 @@ char *mp_fmt_output(mp_check check) { check.summary = get_subcheck_summary(check); } - xasprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary); + asprintf(&result, "[%s] - %s", state_text(mp_compute_check_state(check)), check.summary); mp_subcheck_list *subchecks = check.subchecks; while (subchecks != NULL) { - xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1)); + asprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1)); subchecks = subchecks->next; } @@ -281,16 +281,16 @@ char *mp_fmt_output(mp_check check) { while (subchecks != NULL) { if (pd_string == NULL) { - xasprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck)); + asprintf(&pd_string, "%s", fmt_subcheck_perfdata(subchecks->subcheck)); } else { - xasprintf(&pd_string, "%s %s", pd_string, fmt_subcheck_perfdata(subchecks->subcheck)); + asprintf(&pd_string, "%s %s", pd_string, fmt_subcheck_perfdata(subchecks->subcheck)); } subchecks = subchecks->next; } if (pd_string != NULL && strlen(pd_string) > 0) { - xasprintf(&result, "%s|%s", result, pd_string); + asprintf(&result, "%s|%s", result, pd_string); } break; @@ -358,23 +358,23 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch switch (output_format) { case MP_FORMAT_ICINGA_WEB_2: - xasprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), + asprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), check.output); subchecks = check.subchecks; while (subchecks != NULL) { - xasprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); + asprintf(&result, "%s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); subchecks = subchecks->next; } return result; case MP_FORMAT_ONE_LINE: - xasprintf(&result, "[%s] - %s", state_text(mp_compute_subcheck_state(check)), check.output); + asprintf(&result, "[%s] - %s", state_text(mp_compute_subcheck_state(check)), check.output); subchecks = check.subchecks; while (subchecks != NULL) { - xasprintf(&result, " - %s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); + asprintf(&result, " - %s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); subchecks = subchecks->next; } return result; diff --git a/lib/perfdata.c b/lib/perfdata.c index f894df39..661756c5 100644 --- a/lib/perfdata.c +++ b/lib/perfdata.c @@ -14,13 +14,13 @@ char *pd_value_to_string(const mp_perfdata_value pd) { switch (pd.type) { case PD_TYPE_INT: - xasprintf(&result, "%lli", pd.pd_int); + asprintf(&result, "%lli", pd.pd_int); break; case PD_TYPE_UINT: - xasprintf(&result, "%llu", pd.pd_int); + asprintf(&result, "%llu", pd.pd_int); break; case PD_TYPE_DOUBLE: - xasprintf(&result, "%f", pd.pd_double); + asprintf(&result, "%f", pd.pd_double); break; default: // die here @@ -33,33 +33,33 @@ char *pd_value_to_string(const mp_perfdata_value pd) { char *pd_to_string(mp_perfdata pd) { assert(pd.label != NULL); char *result = NULL; - xasprintf(&result, "%s=", pd.label); + asprintf(&result, "%s=", pd.label); - xasprintf(&result, "%s%s", result, pd_value_to_string(pd.value)); + asprintf(&result, "%s%s", result, pd_value_to_string(pd.value)); if (pd.uom != NULL) { - xasprintf(&result, "%s%s", result, pd.uom); + asprintf(&result, "%s%s", result, pd.uom); } if (pd.warn_present) { - xasprintf(&result, "%s;%s", result, mp_range_to_string(pd.warn)); + asprintf(&result, "%s;%s", result, mp_range_to_string(pd.warn)); } else { - xasprintf(&result, "%s;", result); + asprintf(&result, "%s;", result); } if (pd.crit_present) { - xasprintf(&result, "%s;%s", result, mp_range_to_string(pd.crit)); + asprintf(&result, "%s;%s", result, mp_range_to_string(pd.crit)); } else { - xasprintf(&result, "%s;", result); + asprintf(&result, "%s;", result); } if (pd.min_present) { - xasprintf(&result, "%s;%s", result, pd_value_to_string(pd.min)); + asprintf(&result, "%s;%s", result, pd_value_to_string(pd.min)); } else { - xasprintf(&result, "%s;", result); + asprintf(&result, "%s;", result); } if (pd.max_present) { - xasprintf(&result, "%s;%s", result, pd_value_to_string(pd.max)); + asprintf(&result, "%s;%s", result, pd_value_to_string(pd.max)); } /*printf("pd_to_string: %s\n", result); */ @@ -71,7 +71,7 @@ char *pd_list_to_string(const pd_list pd) { char *result = pd_to_string(pd.data); for (pd_list *elem = pd.next; elem != NULL; elem = elem->next) { - xasprintf(&result, "%s %s", result, pd_to_string(elem->data)); + asprintf(&result, "%s %s", result, pd_to_string(elem->data)); } return result; @@ -234,17 +234,17 @@ int cmp_perfdata_value(const mp_perfdata_value a, const mp_perfdata_value b) { char *mp_range_to_string(const mp_range input) { char *result = ""; if (input.alert_on_inside_range == INSIDE) { - xasprintf(&result, "@"); + asprintf(&result, "@"); } if (input.start_infinity) { - xasprintf(&result, "%s~:", result); + asprintf(&result, "%s~:", result); } else { - xasprintf(&result, "%s%s:", result, pd_value_to_string(input.start)); + asprintf(&result, "%s%s:", result, pd_value_to_string(input.start)); } if (!input.end_infinity) { - xasprintf(&result, "%s%s", result, pd_value_to_string(input.end)); + asprintf(&result, "%s%s", result, pd_value_to_string(input.end)); } return result; } -- cgit v1.2.3-74-g34f1 From 7d378949670fa8941d14f78e0279992f390bcd2c Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 17:32:56 +0100 Subject: Install libjson-perl in test runner --- .github/prepare_debian.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/prepare_debian.sh b/.github/prepare_debian.sh index 3640e500..c79211cd 100755 --- a/.github/prepare_debian.sh +++ b/.github/prepare_debian.sh @@ -61,7 +61,8 @@ apt-get -y install perl \ libmariadb-dev \ cron \ iputils-ping \ - iproute2 + iproute2 \ + libjson-perl # remove ipv6 interface from hosts sed '/^::1/d' /etc/hosts > /tmp/hosts -- cgit v1.2.3-74-g34f1 From 90b7df9980cd307afb31f2d0489c0a0702e2b81f Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 18:28:50 +0100 Subject: Implement JSON serialising of perfdata --- lib/output.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/lib/output.c b/lib/output.c index 4c5041c8..2c537a01 100644 --- a/lib/output.c +++ b/lib/output.c @@ -8,6 +8,8 @@ #include // #include #include "./vendor/cJSON/cJSON.h" +#include "perfdata.h" +#include "states.h" // == Prototypes == static char *fmt_subcheck_output(mp_output_format output_format, mp_subcheck check, unsigned int indentation); @@ -359,7 +361,7 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch switch (output_format) { case MP_FORMAT_ICINGA_WEB_2: asprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), - check.output); + check.output); subchecks = check.subchecks; @@ -385,13 +387,112 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch } } +static inline cJSON *json_serialise_pd_value(mp_perfdata_value value) { + cJSON *result = cJSON_CreateObject(); + + switch (value.type) { + case PD_TYPE_DOUBLE: + cJSON_AddStringToObject(result, "type", "double"); + break; + case PD_TYPE_INT: + cJSON_AddStringToObject(result, "type", "int"); + break; + case PD_TYPE_UINT: + cJSON_AddStringToObject(result, "type", "uint"); + break; + case PD_TYPE_NONE: + die(STATE_UNKNOWN, "Perfdata type was None in json_serialise_pd_value"); + } + cJSON_AddStringToObject(result, "value", pd_value_to_string(value)); + + return result; +} + +static inline cJSON *json_serialise_range(mp_range range) { + cJSON *result = cJSON_CreateObject(); + + if (range.alert_on_inside_range) { + cJSON_AddBoolToObject(result, "alert_on_inside", true); + } else { + cJSON_AddBoolToObject(result, "alert_on_inside", false); + } + + if (range.end_infinity) { + cJSON_AddStringToObject(result, "end", "inf"); + } else { + cJSON_AddItemToObject(result, "end", json_serialise_pd_value(range.end)); + } + + if (range.start_infinity) { + cJSON_AddStringToObject(result, "start", "inf"); + } else { + cJSON_AddItemToObject(result, "start", json_serialise_pd_value(range.end)); + } + + return result; +} + +static inline cJSON *json_serialise_pd(mp_perfdata pd_val) { + cJSON *result = cJSON_CreateObject(); + + // Label + cJSON_AddStringToObject(result, "label", pd_val.label); + + // Value + cJSON_AddItemToObject(result, "value", json_serialise_pd_value(pd_val.value)); + + // Uom + cJSON_AddStringToObject(result, "uom", pd_val.uom); + + // Warn/Crit + if (pd_val.warn_present) { + cJSON *warn = json_serialise_range(pd_val.warn); + cJSON_AddItemToObject(result, "warn", warn); + } + if (pd_val.crit_present) { + cJSON *crit = json_serialise_range(pd_val.crit); + cJSON_AddItemToObject(result, "crit", crit); + } + + if (pd_val.min_present) { + cJSON_AddItemToObject(result, "min", json_serialise_pd_value(pd_val.min)); + } + if (pd_val.max_present) { + cJSON_AddItemToObject(result, "max", json_serialise_pd_value(pd_val.max)); + } + + return result; +} + +static inline cJSON *json_serialise_pd_list(pd_list *list) { + cJSON *result = cJSON_CreateArray(); + + do { + cJSON *pd_value = json_serialise_pd(list->data); + cJSON_AddItemToArray(result, pd_value); + list = list->next; + } while (list != NULL); + + return result; +} + static inline cJSON *json_serialize_subcheck(mp_subcheck subcheck) { cJSON *result = cJSON_CreateObject(); + + // Human readable output cJSON *output = cJSON_CreateString(subcheck.output); cJSON_AddItemToObject(result, "output", output); + + // Test state (aka Exit Code) cJSON *state = cJSON_CreateString(state_text(mp_compute_subcheck_state(subcheck))); cJSON_AddItemToObject(result, "state", state); + // Perfdata + if (subcheck.perfdata != NULL) { + cJSON *perfdata = json_serialise_pd_list(subcheck.perfdata); + cJSON_AddItemToObject(result, "perfdata", perfdata); + } + if (subcheck.subchecks != NULL) { cJSON *subchecks = cJSON_CreateArray(); -- cgit v1.2.3-74-g34f1 From 119e935b6f810ba2f9d343fa654e13c173ecb8b5 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:17:16 +0100 Subject: Remove errornously commited file --- .gitignore | 1 + lib/vendor/cJSON/.dirstamp | 0 2 files changed, 1 insertion(+) delete mode 100644 lib/vendor/cJSON/.dirstamp diff --git a/.gitignore b/.gitignore index 1af41279..0efc2b66 100644 --- a/.gitignore +++ b/.gitignore @@ -104,6 +104,7 @@ NP-VERSION-FILE /lib/Makefile /lib/Makefile.in /lib/vendor/cJSON/.deps +/lib/vendor/cJSON/.dirstamp # /lib/tests/ /lib/tests/base64.Po diff --git a/lib/vendor/cJSON/.dirstamp b/lib/vendor/cJSON/.dirstamp deleted file mode 100644 index e69de29b..00000000 -- cgit v1.2.3-74-g34f1 From f7f958eee739ae1a61859c834ec3531de384fe6f Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 19 Feb 2025 19:19:54 +0100 Subject: Remove cJSON related part of autoconf, it's just not optional right now --- configure.ac | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/configure.ac b/configure.ac index e9254566..ef3d26e2 100644 --- a/configure.ac +++ b/configure.ac @@ -188,19 +188,6 @@ if test "$enable_libtap" = "yes" ; then AC_SUBST(EXTRA_PLUGIN_TESTS) fi -dnl JSON capabilities (cjson) -AC_ARG_ENABLE([json-output], - AC_HELP_STRING([--enable-json-output], - [Enables switching the output format to JSON (default: yes)]), - [case "${enableval}" in - yes) json-ouput=true ;; - no) json-ouput=false ;; - *) AC_MSG_ERROR([bad value ${enableval} for --enable-json-ouput]) ;; - esac], [json_output=true]) - -AM_CONDITIONAL([ENALBE_JSON_OUTPUT], [test x$json_output = xtrue]) - - dnl INI Parsing AC_ARG_ENABLE(extra-opts, AS_HELP_STRING([--enable-extra-opts],[Enables parsing of plugins ini config files for extra options (default: no)]), -- cgit v1.2.3-74-g34f1 From 3cd29d86cc51b763a0cf706e64884602cb3c9314 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:45:13 +0100 Subject: Remove output formats one-line and summary-only --- lib/output.c | 43 ------------------------------------------- lib/output.h | 2 -- plugins/check_swap.c | 2 +- 3 files changed, 1 insertion(+), 46 deletions(-) diff --git a/lib/output.c b/lib/output.c index 2c537a01..07a77165 100644 --- a/lib/output.c +++ b/lib/output.c @@ -235,35 +235,6 @@ char *mp_fmt_output(mp_check check) { char *result = NULL; switch (check.format) { - case MP_FORMAT_SUMMARY_ONLY: - if (check.summary == NULL) { - check.summary = get_subcheck_summary(check); - } - - asprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); - return result; - - case MP_FORMAT_ONE_LINE: { - /* SERVICE STATUS: First line of output | First part of performance data - * Any number of subsequent lines of output, but note that buffers - * may have a limited size | Second part of performance data, which - * may have continuation lines, too - */ - if (check.summary == NULL) { - check.summary = get_subcheck_summary(check); - } - - asprintf(&result, "%s: %s", state_text(mp_compute_check_state(check)), check.summary); - - mp_subcheck_list *subchecks = check.subchecks; - - while (subchecks != NULL) { - asprintf(&result, "%s - %s", result, fmt_subcheck_output(MP_FORMAT_ONE_LINE, subchecks->subcheck, 1)); - subchecks = subchecks->next; - } - - return result; - } case MP_FORMAT_ICINGA_WEB_2: { if (check.summary == NULL) { check.summary = get_subcheck_summary(check); @@ -370,18 +341,6 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch subchecks = subchecks->next; } return result; - case MP_FORMAT_ONE_LINE: - asprintf(&result, "[%s] - %s", state_text(mp_compute_subcheck_state(check)), check.output); - - subchecks = check.subchecks; - - while (subchecks != NULL) { - asprintf(&result, " - %s\n%s", result, fmt_subcheck_output(output_format, subchecks->subcheck, indentation + 1)); - subchecks = subchecks->next; - } - return result; - case MP_FORMAT_SUMMARY_ONLY: - return result; default: die(STATE_UNKNOWN, "Invalid format"); } @@ -551,9 +510,7 @@ mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state } char *mp_output_format_map[] = { - [MP_FORMAT_ONE_LINE] = "one-line", [MP_FORMAT_ICINGA_WEB_2] = "icingaweb2", - [MP_FORMAT_SUMMARY_ONLY] = "summary-only", [MP_FORMAT_TEST_JSON] = "mp-test-json", }; diff --git a/lib/output.h b/lib/output.h index c7455d29..14c4bcf4 100644 --- a/lib/output.h +++ b/lib/output.h @@ -29,9 +29,7 @@ typedef struct subcheck_list { * Possible output formats */ typedef enum output_format { - MP_FORMAT_ONE_LINE, MP_FORMAT_ICINGA_WEB_2, - MP_FORMAT_SUMMARY_ONLY, MP_FORMAT_TEST_JSON, } mp_output_format; diff --git a/plugins/check_swap.c b/plugins/check_swap.c index 1f2d0273..262d8d51 100644 --- a/plugins/check_swap.c +++ b/plugins/check_swap.c @@ -382,7 +382,7 @@ void print_help(swap_config config) { "Default:"), state_text(config.no_swap_state)); printf(" %s\n", "--output-format"); - printf(" %s\n", _("Select output format. Valid values: \"one-line\", \"icingaweb2\", \"summary-only\", \"mp-test-json\"")); + printf(" %s\n", _("Select output format. Valid values: \"icingaweb2\", \"mp-test-json\"")); printf(UT_VERBOSE); printf("\n"); -- cgit v1.2.3-74-g34f1 From 07873c765b73c85482436880f122882dfccd7e1b Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:49:22 +0100 Subject: Place output-format help string with the other common ones --- plugins/check_swap.c | 3 +-- plugins/utils.h | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/check_swap.c b/plugins/check_swap.c index 262d8d51..4d3b6099 100644 --- a/plugins/check_swap.c +++ b/plugins/check_swap.c @@ -381,8 +381,7 @@ void print_help(swap_config config) { _("Resulting state when there is no swap regardless of thresholds. " "Default:"), state_text(config.no_swap_state)); - printf(" %s\n", "--output-format"); - printf(" %s\n", _("Select output format. Valid values: \"icingaweb2\", \"mp-test-json\"")); + printf(UT_OUTPUT_FORMAT); printf(UT_VERBOSE); printf("\n"); diff --git a/plugins/utils.h b/plugins/utils.h index c7073990..bc26f704 100644 --- a/plugins/utils.h +++ b/plugins/utils.h @@ -195,4 +195,8 @@ The Monitoring Plugins come with ABSOLUTELY NO WARRANTY. You may redistribute\n\ copies of the plugins under the terms of the GNU General Public License.\n\ For more information about these matters, see the file named COPYING.\n") +#define UT_OUTPUT_FORMAT _("\ + --output-format=OUTPUT_FORMAT\n\ + Select output format. Valid values: \"icingaweb2\", \"mp-test-json\"\n") + #endif /* NP_UTILS_H */ -- cgit v1.2.3-74-g34f1 From 1d590a0efe4193ac9298d3eba448ab8bc3bb665b Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:52:32 +0100 Subject: Rename icingaweb2 format to multi-line --- lib/output.c | 8 ++++---- lib/output.h | 4 ++-- plugins/utils.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/output.c b/lib/output.c index 07a77165..17919afc 100644 --- a/lib/output.c +++ b/lib/output.c @@ -235,7 +235,7 @@ char *mp_fmt_output(mp_check check) { char *result = NULL; switch (check.format) { - case MP_FORMAT_ICINGA_WEB_2: { + case MP_FORMAT_MULTI_LINE: { if (check.summary == NULL) { check.summary = get_subcheck_summary(check); } @@ -245,7 +245,7 @@ char *mp_fmt_output(mp_check check) { mp_subcheck_list *subchecks = check.subchecks; while (subchecks != NULL) { - asprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_ICINGA_WEB_2, subchecks->subcheck, 1)); + asprintf(&result, "%s\n%s", result, fmt_subcheck_output(MP_FORMAT_MULTI_LINE, subchecks->subcheck, 1)); subchecks = subchecks->next; } @@ -330,7 +330,7 @@ static inline char *fmt_subcheck_output(mp_output_format output_format, mp_subch mp_subcheck_list *subchecks = NULL; switch (output_format) { - case MP_FORMAT_ICINGA_WEB_2: + case MP_FORMAT_MULTI_LINE: asprintf(&result, "%s\\_[%s] - %s", generate_indentation_string(indentation), state_text(mp_compute_subcheck_state(check)), check.output); @@ -510,7 +510,7 @@ mp_subcheck mp_set_subcheck_default_state(mp_subcheck check, mp_state_enum state } char *mp_output_format_map[] = { - [MP_FORMAT_ICINGA_WEB_2] = "icingaweb2", + [MP_FORMAT_MULTI_LINE] = "multi-line", [MP_FORMAT_TEST_JSON] = "mp-test-json", }; diff --git a/lib/output.h b/lib/output.h index 14c4bcf4..ffc36f53 100644 --- a/lib/output.h +++ b/lib/output.h @@ -29,11 +29,11 @@ typedef struct subcheck_list { * Possible output formats */ typedef enum output_format { - MP_FORMAT_ICINGA_WEB_2, + MP_FORMAT_MULTI_LINE, MP_FORMAT_TEST_JSON, } mp_output_format; -#define MP_FORMAT_DEFAULT MP_FORMAT_ICINGA_WEB_2 +#define MP_FORMAT_DEFAULT MP_FORMAT_MULTI_LINE /* * The main state object of a plugin. Exists only ONCE per plugin. diff --git a/plugins/utils.h b/plugins/utils.h index bc26f704..029ae5a6 100644 --- a/plugins/utils.h +++ b/plugins/utils.h @@ -197,6 +197,6 @@ For more information about these matters, see the file named COPYING.\n") #define UT_OUTPUT_FORMAT _("\ --output-format=OUTPUT_FORMAT\n\ - Select output format. Valid values: \"icingaweb2\", \"mp-test-json\"\n") + Select output format. Valid values: \"multi-line\", \"mp-test-json\"\n") #endif /* NP_UTILS_H */ -- cgit v1.2.3-74-g34f1 From 7c8c9d9b3e7bb6c29d82788d05d74e3f18f01aa5 Mon Sep 17 00:00:00 2001 From: Lorenz Kästle <12514511+RincewindsHat@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:40:09 +0100 Subject: Remove check_swap tests with one-line format --- plugins/t/check_swap.t | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/plugins/t/check_swap.t b/plugins/t/check_swap.t index 93e481c3..7e61b766 100644 --- a/plugins/t/check_swap.t +++ b/plugins/t/check_swap.t @@ -6,47 +6,12 @@ use strict; use warnings; -use Test::More tests => 35; +use Test::More tests => 21; use NPTest; use JSON; -my $successOutput = '/^OK.* - [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; -my $failureOutput = '/^CRITICAL: .*- [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; -my $warnOutput = '/^WARNING: .*- [0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; - -my $outputFormat = '--output-format one-line'; my $result; - -$result = NPTest->testCmd( "./check_swap $outputFormat" ); # Always OK -cmp_ok( $result->return_code, "==", 0, "Always OK" ); -like( $result->output, $successOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -w 1048576 -c 1048576 $outputFormat" ); # 1 MB free -cmp_ok( $result->return_code, "==", 0, "At least 1MB free" ); -like( $result->output, $successOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -w 1% -c 1% $outputFormat" ); # 1% free -cmp_ok( $result->return_code, "==", 0, 'At least 1% free' ); -like( $result->output, $successOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -w 100% -c 100% $outputFormat" ); # 100% (always critical) -cmp_ok( $result->return_code, "==", 2, 'Get critical because not 100% free' ); -like( $result->output, $failureOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -w 100% -c 1% $outputFormat" ); # 100% (always warn) -cmp_ok( $result->return_code, "==", 1, 'Get warning because not 100% free' ); -like( $result->output, $warnOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -w 100% $outputFormat" ); # 100% (single threshold, always warn) -cmp_ok( $result->return_code, "==", 1, 'Get warning because not 100% free' ); -like( $result->output, $warnOutput, "Right output" ); - -$result = NPTest->testCmd( "./check_swap -c 100% $outputFormat" ); # 100% (single threshold, always critical) -cmp_ok( $result->return_code, "==", 2, 'Get critical because not 100% free' ); -like( $result->output, $failureOutput, "Right output" ); - - -$outputFormat = '--output-format mp-test-json'; +my $outputFormat = '--output-format mp-test-json'; my $output; my $message = '/^[0-9]+\% free \([0-9]+MiB out of [0-9]+MiB\)/'; -- cgit v1.2.3-74-g34f1