Files
Thomas Vincent 9582b71704 fix(runtime): [develop] retain reviewed probe and compiler stages after result normalization (#660)
The runtime/probe changes from #642 and GNU C17/build changes from #643
were squash-merged into #641's source branch, which made the
result-normalization review include later stages. This follow-up
preserves those approved changes while restoring #641's focused diff
against develop. No force-push or approved-code deletion is required.

Merge after #641 and before #644. #644 will target this branch. This
combines the already reviewed steps 5 and 6; it introduces no new
worker-stack or module-extraction changes.

The UDP follow-up also corrects a validated finding: EHOSTUNREACH on a
connected socket is ambiguous and can represent an unreachable host or
failed neighbour lookup. It now reports down; ECONNREFUSED/ECONNRESET
and actual peer datagrams retain their existing alive behavior. The
production-linked test rejects the original EHOSTUNREACH behavior.

Validation: Ubuntu 24.04/aarch64, clean GCC build and make check: 17
passed, one existing setuid prerequisite skip, zero failures. The full
tracked source tree is byte-identical to the independently validated
#641 functional-fix snapshot. The UDP and poll-host regressions fail
against the original code and pass after the fixes. The isolated step-4
tree independently passes 15 tests with one existing skip. No live
endpoints, host installation, privilege changes, or test suppression.

Repository squash merges still require restacking/retargeting the next
child after each merge; clean ordinary merge simulations are
insufficient by themselves.

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-10-19 14:17:35 -04:00

201 lines
8.6 KiB
C

/*
ex: set tabstop=4 shiftwidth=4 autoindent:*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public |
| License along with this library; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301, USA |
| |
+-------------------------------------------------------------------------+
| spine: a backend data gatherer for cacti |
+-------------------------------------------------------------------------+
| This poller would not have been possible without: |
| - Larry Adams (current development and enhancements) |
| - Rivo Nurges (rrd support, mysql poller cache, misc functions) |
| - RTG (core poller code, pthreads, snmp, autoconf examples) |
| - Brady Alleman/Doug Warner (threading ideas, implementation details) |
+-------------------------------------------------------------------------+
| - Cacti - http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
#ifndef SPINE_UTIL_H
#define SPINE_UTIL_H
/* cacti config reading functions */
extern void read_config_options(void);
extern int read_spine_config(const char *file);
extern void config_defaults(void);
extern bool poller_transfer_status(MYSQL *source, MYSQL *destination);
extern void poller_push_data_to_main(void);
/* cacti logging function */
extern int spine_log(const char *format, ...)
__attribute__((format(printf, 1, 2)));
extern void die(const char *format, ...)
__attribute__((noreturn))
__attribute__((format(printf, 1, 2)));
/* option processing function */
extern void set_option(const char *setting, const char *value);
/* number validation functions */
extern int is_ipaddress(const char *string);
extern int all_digits(const char *str);
/* what a poll returned; text is what poller_output stores, U if unknown */
typedef enum {
RESULT_UNKNOWN = 0,
RESULT_COUNTER,
RESULT_SIGNED,
RESULT_FLOAT,
RESULT_HEX_COUNTER,
RESULT_MULTIPART
} result_kind_t;
typedef struct {
result_kind_t kind;
union {
uint64_t counter;
int64_t integer;
double real;
} value;
char text[RESULTS_BUFFER];
} classified_result_t;
extern result_kind_t classify_result(const char *raw, classified_result_t *out);
/* determine if a device is a debug device */
extern int is_debug_device(int device_id);
extern void parse_debug_devices(char *device_list, int *devices, size_t capacity);
extern bool spine_should_log_device(int host_id, int verbosity);
/* string and file functions */
extern char *add_slashes(const char *string);
extern int file_exists(const char *filename);
extern char *strip_alpha(char *string);
extern char *strncopy(char *dst, const char *src, size_t n);
extern int spine_snprintf(char *output, size_t capacity, const char *format, ...)
__attribute__((format(printf, 3, 4)));
extern void spine_clear_sensitive(void *buffer, size_t length);
extern void spine_sanitize_log_message(char *message);
extern char *trim(char *str);
extern char *rtrim(char *str);
extern char *ltrim(char *str);
extern char *reverse(char *str);
extern int strpos(const char *haystack, const char *needle) ;
extern int char_count(const char *str, int chr);
/* custom regex replace to return a value if matches */
#define MAX_MATCHES 5
#define REGEX_NUMBER "([-+]*)([0-9]*)([.][0-9]+)"
#define CAPABILITY_PROTOCOL_LIST_MAX 480
char *regex_replace(const char *exp, char *value);
int format_spine_capabilities(char *output, size_t output_size,
const char *auth_protocols, const char *priv_protocols);
/* macro to copy string to string with an ending null */
#define STRNCOPY(dst, src) strncopy((dst), (src), sizeof(dst))
#define USTRNCOPY(dst, src) ustrncopy((dst), (src), sizeof(dst))
/* macro to duplicate string and die if fails */
#define STRDUP_OR_DIE(dst, src, reason) \
if ((dst = strdup(src)) == NULL) {\
die("FATAL: malloc() failed during strdup() for %s", reason);\
}\
/* get highres time as double */
extern double get_time_as_double(void);
/* function to check to see if program has capability to use raw socket with
out uid = 0 */
extern void drop_privileges(void);
extern int privileges_dropped(uid_t uid, gid_t gid);
extern int hasCaps(void);
/* see if we can do things as root */
extern void checkAsRoot(void);
/* log format */
extern void set_date_format(void);
extern char *get_date_format(void);
/* remote/main server synchronization */
extern void poller_push_data_to_main(void);
/* MySQL supports INSERT row aliases from 8.0.20; MariaDB does not. */
extern int db_row_alias_upsert_supported(const char *version, unsigned long version_number);
/* start time for spine */
extern double start_time;
/* the version of Cacti as a decimal */
int get_cacti_version(MYSQL *psql, int mode);
extern void spine_sleep_usec(unsigned int microseconds);
extern double spine_monotonic_time(void);
extern int spine_count_to_int(unsigned long long count);
extern int spine_wait_readable(int fd, double deadline);
extern int spine_wait_writable(int fd, double deadline);
extern int spine_permits_init(spine_permits_t *permits, int count);
extern int spine_permits_destroy(spine_permits_t *permits);
extern int spine_permits_try_acquire(spine_permits_t *permits);
extern int spine_permits_release(spine_permits_t *permits);
extern int spine_permits_available(spine_permits_t *permits);
/* Stack for every thread spine creates. The deepest worker path, child()
* through poll_host() into the SNMP, script, ping and logging code, peaked at
* 179,024 bytes (glibc, gcc ASan, live MariaDB and snmpd) and sums to 175,856
* bytes statically (gcc 15 -fcallgraph-info=su on musl). About five results
* buffers of that grow with --with-results-buffer. musl's 128 KiB default
* overflows on entry to poll_host(). Reserve twice 180 KiB plus five results
* buffers, rounded up to 64 KiB so the size is a page multiple everywhere:
* 384 KiB with the default 2048-byte results buffer. */
/* Check the unrounded expression before evaluating its size_t arithmetic. */
_Static_assert(RESULTS_BUFFER >= 2, "RESULTS_BUFFER must hold a value and terminator");
_Static_assert((size_t) RESULTS_BUFFER <= (SIZE_MAX - ((size_t) 2 * 180 * 1024 + 65535)) / 10,
"RESULTS_BUFFER overflows the worker stack size");
#define SPINE_THREAD_STACK_SIZE \
(((size_t) 2 * ((size_t) 180 * 1024 + 5 * (size_t) RESULTS_BUFFER) + 65535) & ~(size_t) 65535)
/*! \fn int spine_thread_attr_init(pthread_attr_t *attributes)
* \brief initialize thread attributes with at least SPINE_THREAD_STACK_SIZE
* \return 0, or the pthread error; on error nothing is left to destroy
*/
extern int spine_thread_attr_init(pthread_attr_t *attributes);
/*! \fn int spine_appendf(char **cursor, size_t *remaining, const char *fmt, ...)
* \brief append to a bounded buffer without walking off the end
*
* snprintf() returns the length it would have written, so `p += snprintf(p,
* remaining, ...)` moves the cursor past the buffer the first time a value is
* truncated. The next `remaining` is then negative, and as a size_t it is
* effectively unbounded, at a destination already out of bounds.
*
* This advances the cursor by what was actually written, stops on the
* terminator when the text does not fit, and says so.
*
* \return TRUE when the whole string was appended, FALSE on truncation or a
* formatting error, in which case the buffer stays NUL-terminated
*/
extern int spine_appendf(char **cursor, size_t *remaining, const char *fmt, ...)
__attribute__((format(printf, 3, 4)));
#endif /* SPINE_UTIL_H */