mirror of
https://github.com/Cacti/spine.git
synced 2026-10-23 03:27:10 +09:00
Classify numeric, undefined, multipart and Cacti octet results through one parser, normalize undefined agent values before persistence and flush every SNMP batch consistently. Validate whole non-negative uptimes, retain unknown reindex baselines and free script results through their owning pointer. The mocked poll_host suite checks session-profile boundaries, spike handling, output_regex input and completion writes. Originally step 4/14 of the C17/native-correctness series; its preceding steps have merged. The current base is develop. Earlier validation, before this review follow-up, in isolated copied snapshots on Linux arm64: - Clean GCC build and make check: 15 passed, 1 existing prerequisite skips, 0 failures. - ASan/UBSan: 14 passed, 2 existing privilege/allocator-interposition prerequisite skips, 0 failures; no sanitizer findings. - Production source/profile integrity verification against disposable MariaDB and loopback SNMP fixtures passed. - Release archive extracted into a separate source tree and built/tested out of tree; fuzz checks passed. Earlier local profiles retain source hashes for the copied validation tree; GitHub checks validate the published commit. No production database/device, host installation or live privilege change is part of validation. Existing prerequisite skips were retained; no test or finding was suppressed to obtain a pass. Review follow-up (2026-10-09): Restacked on the corrected preceding branches. The SNMP status fix is introduced in #638, the reindex/completion fixes in #640, and focused regression coverage in #650. Restacked tip validation: isolated Linux arm64 build and make check passed (18 passed, 1 setuid prerequisite skip, no failures). GitHub checks validate the updated published commits; earlier profile/archive/sanitizer evidence above belongs to the previous revisions. ## Current merge dependencies Current base: `develop`. Immediate dependency: `develop` (earlier steps already merged). Next open PR: [#644](https://github.com/Cacti/spine/pull/644). Merge order: #641 → #644 → #654 → #646 → #648 → #649 → #650 → #651 → #655 → #658 → #659. This repository uses squash-only merges and deletes merged branches. Record the old parent SHA, squash one PR at a time, then restack and retarget dependents before merging the next PR. Re-run merge validation and checks on the resulting heads; an ordinary clean merge does not replace squash/restack validation. --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
1843 lines
75 KiB
C
1843 lines
75 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/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "spine.h"
|
|
|
|
int format_poller_output_row(char *output, size_t output_size,
|
|
int local_data_id, const char *escaped_rrd_name,
|
|
const char *host_time, const char *escaped_result) {
|
|
const char *timep;
|
|
int decimal_points = 0;
|
|
int digits = 0;
|
|
int written;
|
|
|
|
if (output == NULL || output_size == 0 || escaped_rrd_name == NULL ||
|
|
host_time == NULL || host_time[0] == '\0' || escaped_result == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
/* host_time is emitted outside SQL quotes. Accept the integer timestamps
|
|
* Spine generates and a single fractional part, but no SQL syntax. */
|
|
for (timep = host_time; *timep != '\0'; timep++) {
|
|
if (*timep == '.') {
|
|
decimal_points++;
|
|
if (decimal_points > 1) {
|
|
return FALSE;
|
|
}
|
|
} else if (!isdigit((unsigned char)*timep)) {
|
|
return FALSE;
|
|
} else {
|
|
digits++;
|
|
}
|
|
}
|
|
if (digits == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
written = snprintf(output, output_size,
|
|
" (%i, '%s', FROM_UNIXTIME(%s), '%s')",
|
|
local_data_id, escaped_rrd_name, host_time, escaped_result);
|
|
|
|
return written >= 0 && (size_t)written < output_size;
|
|
}
|
|
|
|
void child_cleanup(void *arg) {
|
|
poller_thread_t poller_details = *(poller_thread_t*) arg;
|
|
|
|
SPINE_LOG_DEVDBG(("Device[%i] HT[%i] DEBUG: The Device Thread has cleaned up.", poller_details.host_id, poller_details.host_thread));
|
|
|
|
free(arg);
|
|
child_cleanup_thread(NULL);
|
|
}
|
|
|
|
void child_cleanup_thread(void *arg) {
|
|
(void)arg;
|
|
int a_threads_value;
|
|
a_threads_value = spine_permits_available(&available_threads) + 1;
|
|
|
|
SPINE_LOG_DEVDBG(("DEBUG: Available Threads is %i (%i outstanding)", a_threads_value, set.poller.threads - a_threads_value));
|
|
/* Releasing the permit publishes completion: no shared state is used after it. */
|
|
spine_permits_release(&available_threads);
|
|
}
|
|
|
|
void child_cleanup_script(void *arg) {
|
|
(void)arg;
|
|
spine_permits_release(&available_scripts);
|
|
|
|
int a_scripts_value;
|
|
a_scripts_value = spine_permits_available(&available_scripts);
|
|
|
|
SPINE_LOG_DEVDBG(("DEBUG: Available Scripts is %i (%i outstanding)", a_scripts_value, MAX_SIMULTANEOUS_SCRIPTS - a_scripts_value));
|
|
}
|
|
|
|
/*! \fn void *child(void *arg)
|
|
* \brief function is called via the fork command and initiates a poll of a host
|
|
* \param arg a pointer to an integer point to the host_id to be polled
|
|
*
|
|
* This function will call the primary Spine polling function to poll a host
|
|
* and then reduce the number of active threads by one so that the next host
|
|
* can be polled.
|
|
*
|
|
*/
|
|
void *child(void *arg) {
|
|
pthread_cleanup_push(child_cleanup, arg);
|
|
|
|
int host_errors = 0;
|
|
poller_thread_t poller_details = *(poller_thread_t*)arg;
|
|
/* Allows main thread to proceed with creation of other threads. */
|
|
spine_permits_release(poller_details.thread_init_sem);
|
|
SPINE_LOG_DEVICE(poller_details.host_id, POLLER_VERBOSITY_DEBUG, ("Device[%i] HT[%i] DEBUG: In Poller, About to Start Polling", poller_details.host_id, poller_details.host_thread));
|
|
if (mysql_thread_init() != 0) die("ERROR: Unable to initialize polling thread database state");
|
|
poll_host(&poller_details, &host_errors);
|
|
|
|
pthread_cleanup_pop(1);
|
|
|
|
/* end the thread */
|
|
pthread_exit(0);
|
|
}
|
|
|
|
/*! \fn void poll_host(const poller_thread_t *work, int *host_errors)
|
|
* \brief core Spine function that polls a host
|
|
* \param work borrowed polling instructions, valid until the synchronous call returns
|
|
* \param host_errors receives the count of invalid polling results
|
|
*
|
|
* This function is core to Spine. It takes a host_id and polls it, first
|
|
* checking reachability and any required data-query reindexing.
|
|
*/
|
|
enum poll_result_status normalize_poll_result(char *result, bool snmp) {
|
|
classified_result_t classified;
|
|
bool unknown_marker;
|
|
|
|
if (IS_UNDEFINED(result)) return POLL_RESULT_UNDEFINED;
|
|
unknown_marker = snmp && (STRIMATCH(result, "U") || STRIMATCH(result, "Nan"));
|
|
if (classify_result(result, &classified) != RESULT_UNKNOWN) {
|
|
strncopy(result, classified.text, RESULTS_BUFFER);
|
|
return POLL_RESULT_VALID;
|
|
}
|
|
/* An agent's "u" or "NaN" is still unknown; store the U RRDtool expects. */
|
|
SET_UNDEFINED(result);
|
|
return unknown_marker ? POLL_RESULT_UNDEFINED : POLL_RESULT_INVALID;
|
|
}
|
|
|
|
typedef struct poll_error_context {
|
|
char *buffer;
|
|
int *size;
|
|
int *count;
|
|
int *errors;
|
|
int host_id;
|
|
int thread_id;
|
|
} poll_error_context_t;
|
|
|
|
static void record_result_error(const poll_error_context_t *context, const host_t *host,
|
|
const target_t *item, const char *result, bool snmp) {
|
|
buffer_output_errors(context->buffer, context->size, context->count,
|
|
context->host_id, context->thread_id, item->local_data_id, false);
|
|
(*context->errors)++;
|
|
if (set.logging.spine_log_level != 2) return;
|
|
if (snmp) {
|
|
SPINE_LOG(("WARNING: Invalid Response, Device[%i] HT[%i] DS[%i] SNMP: v%i: %s, dsname: %s, oid: %s, value: %s",
|
|
context->host_id, context->thread_id, item->local_data_id,
|
|
host->snmp.profile.version, host->hostname, item->rrd_name, item->arg1, result));
|
|
} else {
|
|
char script[SMALL_BUFSIZE];
|
|
|
|
php_command_script(item->arg1, script, sizeof(script));
|
|
SPINE_LOG(("WARNING: Invalid Response, Device[%i] HT[%i] DS[%i] SCRIPT: %s, output: %s",
|
|
context->host_id, context->thread_id, item->local_data_id, script, result));
|
|
}
|
|
}
|
|
|
|
static void normalize_snmp_item(const host_t *host, const target_t *item,
|
|
char *result, const poll_error_context_t *errors) {
|
|
if (host->ignore_host) {
|
|
SPINE_LOG(("Device[%i] HT[%i] DS[%i] WARNING: SNMP timeout detected [%i ms], ignoring host '%s'",
|
|
errors->host_id, errors->thread_id, item->local_data_id, host->snmp.profile.timeout, host->hostname));
|
|
SET_UNDEFINED(result);
|
|
return;
|
|
}
|
|
enum poll_result_status status = normalize_poll_result(result, true);
|
|
if (status == POLL_RESULT_VALID) return;
|
|
record_result_error(errors, host, item, result, true);
|
|
if (status == POLL_RESULT_INVALID) SET_UNDEFINED(result);
|
|
}
|
|
|
|
|
|
static void store_snmp_results(const host_t *host, target_t *poller_items, snmp_oids_t *snmp_oids,
|
|
int num_oids, const poll_error_context_t *errors, double thread_start, bool spike_kill) {
|
|
for (int j = 0; j < num_oids; j++) {
|
|
const target_t *item = &poller_items[snmp_oids[j].array_position];
|
|
normalize_snmp_item(host, item, snmp_oids[j].result, errors);
|
|
|
|
if (strlen(item->output_regex)) {
|
|
char temp_result[RESULTS_BUFFER];
|
|
snprintf(temp_result, sizeof(temp_result), "%s", regex_replace(item->output_regex, snmp_oids[j].result));
|
|
snprintf(snmp_oids[j].result, RESULTS_BUFFER, "%s", temp_result);
|
|
}
|
|
|
|
snprintf(poller_items[snmp_oids[j].array_position].result, RESULTS_BUFFER, "%s", snmp_oids[j].result);
|
|
|
|
double thread_end = get_time_as_double();
|
|
|
|
SPINE_LOG_DEVICE(errors->host_id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DS[%i] TT[%.2f] SNMP: v%i: %s, dsname: %s, oid: %s, value: %s", errors->host_id, errors->thread_id, poller_items[snmp_oids[j].array_position].local_data_id, (float) ((thread_end - thread_start) * 1000), host->snmp.profile.version, host->hostname, poller_items[snmp_oids[j].array_position].rrd_name, poller_items[snmp_oids[j].array_position].arg1, poller_items[snmp_oids[j].array_position].result));
|
|
|
|
if ((!IS_UNDEFINED(poller_items[snmp_oids[j].array_position].result)) && (spike_kill && (!strstr(poller_items[snmp_oids[j].array_position].result,":")))) {
|
|
SET_UNDEFINED(poller_items[snmp_oids[j].array_position].result);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
int host_id;
|
|
const char *limits;
|
|
bool due_only;
|
|
bool group_ports;
|
|
} poller_query_filter_t;
|
|
|
|
static void poller_item_query(char *buffer, size_t capacity, const char *columns, const poller_query_filter_t *filter) {
|
|
char *cursor = buffer;
|
|
cursor += spine_snprintf(cursor, capacity, "SELECT SQL_NO_CACHE %s FROM poller_item WHERE host_id = %i", columns, filter->host_id);
|
|
if (set.poller.poller_id != 0) cursor += spine_snprintf(cursor, capacity - (size_t)(cursor - buffer), " AND poller_id = %i", set.poller.poller_id);
|
|
if (filter->due_only) cursor += spine_snprintf(cursor, capacity - (size_t)(cursor - buffer), " AND rrd_next_step <= 0");
|
|
if (filter->group_ports) {
|
|
cursor += spine_snprintf(cursor, capacity - (size_t)(cursor - buffer), " GROUP BY snmp_port");
|
|
} else if (set.snmp.total_snmp_ports != 1) {
|
|
cursor += spine_snprintf(cursor, capacity - (size_t)(cursor - buffer), " ORDER BY snmp_port");
|
|
}
|
|
spine_snprintf(cursor, capacity - (size_t)(cursor - buffer), " %s", filter->limits);
|
|
}
|
|
|
|
void poller_prepare_queries(poller_queries_t *queries, int host_id, int host_thread, int host_data_ids) {
|
|
char limits[SMALL_BUFSIZE] = "";
|
|
if (host_data_ids > 0) {
|
|
if (host_thread < 1) die("ERROR: Invalid device thread for poller item selection");
|
|
long long offset = (long long)host_data_ids * ((long long)host_thread - 1);
|
|
spine_snprintf(limits, sizeof(limits), "LIMIT %lld, %i", offset, host_data_ids);
|
|
}
|
|
char columns[BUFSIZE];
|
|
/* optional output_regex column (added in Cacti 1.3.1) */
|
|
spine_snprintf(columns, sizeof(columns), "%s%s",
|
|
"action, hostname, snmp_community, snmp_version, snmp_username, snmp_password, "
|
|
"rrd_name, rrd_path, arg1, arg2, arg3, local_data_id, rrd_num, snmp_port, snmp_timeout, "
|
|
"snmp_auth_protocol, snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id",
|
|
set.hosts.has_output_regex ? ", output_regex" : "");
|
|
poller_query_filter_t filter = {host_id, limits, FALSE, FALSE};
|
|
poller_item_query(queries->items, sizeof(queries->items), columns, &filter);
|
|
filter.due_only = set.poller.active_profiles != 1;
|
|
poller_item_query(queries->due_items, sizeof(queries->due_items), columns, &filter);
|
|
filter.group_ports = TRUE;
|
|
filter.due_only = FALSE;
|
|
poller_item_query(queries->agents, sizeof(queries->agents), "snmp_port, count(snmp_port)", &filter);
|
|
filter.due_only = set.poller.active_profiles != 1;
|
|
poller_item_query(queries->due_agents, sizeof(queries->due_agents), "snmp_port, count(snmp_port)", &filter);
|
|
spine_snprintf(queries->host, sizeof(queries->host),
|
|
"SELECT SQL_NO_CACHE id, hostname, snmp_community, snmp_version, snmp_username, snmp_password, snmp_auth_protocol, "
|
|
"snmp_priv_passphrase, snmp_priv_protocol, snmp_context, snmp_engine_id, snmp_port, snmp_timeout, max_oids, "
|
|
"availability_method, ping_method, ping_port, ping_timeout, ping_retries, status, status_event_count, "
|
|
"UNIX_TIMESTAMP(status_fail_date), UNIX_TIMESTAMP(status_rec_date), status_last_error, min_time, max_time, "
|
|
"cur_time, avg_time, total_polls, failed_polls, availability, snmp_sysUpTimeInstance, snmp_sysDescr, snmp_sysObjectID, "
|
|
"snmp_sysContact, snmp_sysName, snmp_sysLocation FROM host WHERE id = %i AND deleted = ''", host_id);
|
|
spine_snprintf(queries->reindex, sizeof(queries->reindex),
|
|
"SELECT SQL_NO_CACHE data_query_id, action, op, assert_value, arg1 FROM poller_reindex WHERE host_id = %i", host_id);
|
|
spine_snprintf(queries->schedule, sizeof(queries->schedule),
|
|
"UPDATE poller_item SET rrd_next_step = IF(rrd_step = %i, 0, IF(rrd_next_step - %i < 0, rrd_step - %i, rrd_next_step - %i)) WHERE host_id = %i",
|
|
set.poller.poller_interval, set.poller.poller_interval, set.poller.poller_interval, set.poller.poller_interval, host_id);
|
|
if (set.poller.poller_id != 0) {
|
|
size_t length = strlen(queries->schedule);
|
|
spine_snprintf(queries->schedule + length, sizeof(queries->schedule) - length, " AND poller_id = %i", set.poller.poller_id);
|
|
}
|
|
strncopy(queries->output, "INSERT INTO poller_output (local_data_id, rrd_name, time, output) VALUES", sizeof(queries->output));
|
|
strncopy(queries->boost_output, "INSERT INTO poller_output_boost (local_data_id, rrd_name, time, output) VALUES", sizeof(queries->boost_output));
|
|
/* The cached upsert capability describes the local connection, while output can
|
|
* go to the remote one. VALUES() is accepted by both MySQL and MariaDB. */
|
|
strncopy(queries->suffix, " ON DUPLICATE KEY UPDATE output=VALUES(output)", sizeof(queries->suffix));
|
|
}
|
|
|
|
static void host_metadata_defaults(host_t *host) {
|
|
/* initialize variables first */
|
|
host->id = 0; // 0
|
|
host->hostname[0] = '\0'; // 1
|
|
host->snmp.session = NULL; // -
|
|
host->snmp.profile.community[0] = '\0'; // 2
|
|
host->snmp.profile.version = 1; // 3
|
|
host->snmp.profile.username[0] = '\0'; // 4
|
|
host->snmp.profile.password[0] = '\0'; // 5
|
|
host->snmp.profile.auth_protocol[0] = '\0'; // 6
|
|
host->snmp.profile.priv_passphrase[0] = '\0'; // 7
|
|
host->snmp.profile.priv_protocol[0] = '\0'; // 8
|
|
host->snmp.profile.context[0] = '\0'; // 9
|
|
host->snmp.profile.engine_id[0] = '\0'; // 10
|
|
host->snmp.profile.port = 161; // 11
|
|
host->snmp.profile.timeout = 500; // 12
|
|
host->snmp.retries = set.snmp.snmp_retries; // -
|
|
host->snmp.max_oids = 10; // 13
|
|
host->availability.method = 0; // 14
|
|
host->availability.ping_method = 0; // 15
|
|
host->availability.port = 23; // 16
|
|
host->availability.timeout = 500; // 17
|
|
host->availability.retries = 2; // 18
|
|
host->state.status = HOST_UP; // 19
|
|
host->state.status_event_count = 0; // 20
|
|
host->state.status_fail_date[0] = '\0'; // 21
|
|
host->state.status_rec_date[0] = '\0'; // 22
|
|
host->state.status_last_error[0] = '\0'; // 23
|
|
host->statistics.min_time = 0; // 24
|
|
host->statistics.max_time = 0; // 25
|
|
host->statistics.cur_time = 0; // 26
|
|
host->statistics.avg_time = 0; // 27
|
|
host->statistics.total_polls = 0; // 28
|
|
host->statistics.failed_polls = 0; // 29
|
|
host->statistics.availability = 100; // 30
|
|
host->system.snmp_sysUpTimeInstance = 0; // 31
|
|
host->system.snmp_sysDescr[0] = '\0'; // 32
|
|
host->system.snmp_sysObjectID[0] = '\0'; // 33
|
|
host->system.snmp_sysContact[0] = '\0'; // 34
|
|
host->system.snmp_sysName[0] = '\0'; // 35
|
|
host->system.snmp_sysLocation[0] = '\0'; // 36
|
|
}
|
|
|
|
static void host_metadata_connection(host_t *host, MYSQL_ROW row) {
|
|
/* populate host structure */
|
|
host->ignore_host = FALSE;
|
|
if (row[0] != NULL) host->id = atoi(row[0]);
|
|
|
|
if (row[1] != NULL) {
|
|
name_t *name = get_namebyhost(row[1], NULL);
|
|
STRNCOPY(host->hostname, name->hostname);
|
|
host->availability.port = name->port;
|
|
SPINE_FREE(name);
|
|
}
|
|
|
|
if (row[2] != NULL) STRNCOPY(host->snmp.profile.community, row[2]);
|
|
|
|
if (row[3] != NULL) host->snmp.profile.version = atoi(row[3]);
|
|
|
|
if (row[4] != NULL) STRNCOPY(host->snmp.profile.username, row[4]);
|
|
if (row[5] != NULL) STRNCOPY(host->snmp.profile.password, row[5]);
|
|
if (row[6] != NULL) STRNCOPY(host->snmp.profile.auth_protocol, row[6]);
|
|
if (row[7] != NULL) STRNCOPY(host->snmp.profile.priv_passphrase, row[7]);
|
|
if (row[8] != NULL) STRNCOPY(host->snmp.profile.priv_protocol, row[8]);
|
|
if (row[9] != NULL) STRNCOPY(host->snmp.profile.context, row[9]);
|
|
if (row[10] != NULL) STRNCOPY(host->snmp.profile.engine_id, row[10]);
|
|
|
|
if (row[11] != NULL) host->snmp.profile.port = atoi(row[11]);
|
|
if (row[12] != NULL) host->snmp.profile.timeout = atoi(row[12]);
|
|
if (row[13] != NULL) host->snmp.max_oids = atoi(row[13]);
|
|
}
|
|
|
|
static void host_metadata_status(host_t *host, MYSQL_ROW row) {
|
|
if (row[14] != NULL) host->availability.method = atoi(row[14]);
|
|
if (row[15] != NULL) host->availability.ping_method = atoi(row[15]);
|
|
if (row[16] != NULL) host->availability.port = atoi(row[16]);
|
|
if (row[17] != NULL) host->availability.timeout = atoi(row[17]);
|
|
if (row[18] != NULL) host->availability.retries = atoi(row[18]);
|
|
|
|
if (row[19] != NULL) host->state.status = atoi(row[19]);
|
|
if (row[20] != NULL) host->state.status_event_count = atoi(row[20]);
|
|
|
|
if (row[21] != NULL) STRNCOPY(host->state.status_fail_date, row[21]);
|
|
if (row[22] != NULL) STRNCOPY(host->state.status_rec_date, row[22]);
|
|
|
|
if (row[23] != NULL) STRNCOPY(host->state.status_last_error, row[23]);
|
|
}
|
|
|
|
static void host_metadata_statistics(host_t *host, MYSQL_ROW row) {
|
|
if (row[24] != NULL) host->statistics.min_time = atof(row[24]);
|
|
if (row[25] != NULL) host->statistics.max_time = atof(row[25]);
|
|
if (row[26] != NULL) host->statistics.cur_time = atof(row[26]);
|
|
if (row[27] != NULL) host->statistics.avg_time = atof(row[27]);
|
|
if (row[28] != NULL) host->statistics.total_polls = atoi(row[28]);
|
|
if (row[29] != NULL) host->statistics.failed_polls = atoi(row[29]);
|
|
if (row[30] != NULL) host->statistics.availability = atof(row[30]);
|
|
}
|
|
|
|
static void host_metadata_system(host_t *host, MYSQL_ROW row, MYSQL *mysql) {
|
|
if (row[31] != NULL) host->system.snmp_sysUpTimeInstance=atoll(row[31]);
|
|
if (row[32] != NULL) db_escape(mysql, host->system.snmp_sysDescr, sizeof(host->system.snmp_sysDescr), row[32]);
|
|
if (row[33] != NULL) db_escape(mysql, host->system.snmp_sysObjectID, sizeof(host->system.snmp_sysObjectID), row[33]);
|
|
if (row[34] != NULL) db_escape(mysql, host->system.snmp_sysContact, sizeof(host->system.snmp_sysContact), row[34]);
|
|
if (row[35] != NULL) db_escape(mysql, host->system.snmp_sysName, sizeof(host->system.snmp_sysName), row[35]);
|
|
if (row[36] != NULL) db_escape(mysql, host->system.snmp_sysLocation, sizeof(host->system.snmp_sysLocation), row[36]);
|
|
}
|
|
|
|
static void persist_host_status(MYSQL *mysql, const host_t *host, bool include_system_information) {
|
|
char update_sql[BIG_BUFSIZE];
|
|
char escaped_last_error[BUFSIZE];
|
|
db_escape(mysql, escaped_last_error, sizeof(escaped_last_error), host->state.status_last_error);
|
|
if (include_system_information) {
|
|
snprintf(update_sql, BIG_BUFSIZE, "UPDATE host "
|
|
"SET status='%i', status_event_count='%i', status_fail_date=FROM_UNIXTIME(%s),"
|
|
" status_rec_date=FROM_UNIXTIME(%s), status_last_error='%s', min_time='%f',"
|
|
" max_time='%f', cur_time='%f', avg_time='%f', total_polls='%i',"
|
|
" failed_polls='%i', availability='%.4f', snmp_sysDescr='%s', "
|
|
" snmp_sysObjectID='%s', snmp_sysUpTimeInstance='%llu', "
|
|
" snmp_sysContact='%s', snmp_sysName='%s', snmp_sysLocation='%s' "
|
|
"WHERE id='%i'",
|
|
host->state.status,
|
|
host->state.status_event_count,
|
|
host->state.status_fail_date,
|
|
host->state.status_rec_date,
|
|
escaped_last_error,
|
|
host->statistics.min_time,
|
|
host->statistics.max_time,
|
|
host->statistics.cur_time,
|
|
host->statistics.avg_time,
|
|
host->statistics.total_polls,
|
|
host->statistics.failed_polls,
|
|
host->statistics.availability,
|
|
host->system.snmp_sysDescr,
|
|
host->system.snmp_sysObjectID,
|
|
host->system.snmp_sysUpTimeInstance,
|
|
host->system.snmp_sysContact,
|
|
host->system.snmp_sysName,
|
|
host->system.snmp_sysLocation,
|
|
host->id);
|
|
} else {
|
|
snprintf(update_sql, BIG_BUFSIZE, "UPDATE host "
|
|
"SET status='%i', status_event_count='%i', status_fail_date=FROM_UNIXTIME(%s),"
|
|
" status_rec_date=FROM_UNIXTIME(%s), status_last_error='%s', min_time='%f',"
|
|
" max_time='%f', cur_time='%f', avg_time='%f', total_polls='%i',"
|
|
" failed_polls='%i', availability='%.4f' "
|
|
"WHERE id='%i'",
|
|
host->state.status,
|
|
host->state.status_event_count,
|
|
host->state.status_fail_date,
|
|
host->state.status_rec_date,
|
|
escaped_last_error,
|
|
host->statistics.min_time,
|
|
host->statistics.max_time,
|
|
host->statistics.cur_time,
|
|
host->statistics.avg_time,
|
|
host->statistics.total_polls,
|
|
host->statistics.failed_polls,
|
|
host->statistics.availability,
|
|
host->id);
|
|
}
|
|
db_insert(mysql, LOCAL, update_sql);
|
|
}
|
|
|
|
static void load_host_metadata(MYSQL *mysql, MYSQL_ROW row, host_t *host, const poller_thread_t *work) {
|
|
host_metadata_defaults(host);
|
|
host_metadata_connection(host, row);
|
|
host_metadata_status(host, row);
|
|
host_metadata_statistics(host, row);
|
|
host_metadata_system(host, row, mysql);
|
|
|
|
/* correct max_oid bounds issues */
|
|
if ((host->snmp.max_oids == 0) || (host->snmp.max_oids > 100)) {
|
|
SPINE_LOG(("Device[%i] HT[%i] WARNING: Max OIDS is out of range with value of '%i'. Resetting to default of 5", work->host_id, work->host_thread, host->snmp.max_oids));
|
|
host->snmp.max_oids = 5;
|
|
}
|
|
}
|
|
|
|
static void initialize_host_snmp(host_t *host) {
|
|
if (((host->snmp.profile.version >= 1) && (host->snmp.profile.version <= 2) &&
|
|
(strlen(host->snmp.profile.community) > 0)) ||
|
|
(host->snmp.profile.version == 3)) {
|
|
host->snmp.session = snmp_host_init(&(snmp_connection_t){
|
|
.host_id = host->id,
|
|
.hostname = host->hostname,
|
|
.snmp_version = host->snmp.profile.version,
|
|
.snmp_community = host->snmp.profile.community,
|
|
.snmp_username = host->snmp.profile.username,
|
|
.snmp_password = host->snmp.profile.password,
|
|
.snmp_auth_protocol = host->snmp.profile.auth_protocol,
|
|
.snmp_priv_passphrase = host->snmp.profile.priv_passphrase,
|
|
.snmp_priv_protocol = host->snmp.profile.priv_protocol,
|
|
.snmp_context = host->snmp.profile.context,
|
|
.snmp_engine_id = host->snmp.profile.engine_id,
|
|
.snmp_port = host->snmp.profile.port,
|
|
.snmp_timeout = host->snmp.profile.timeout,
|
|
});
|
|
} else {
|
|
host->snmp.session = NULL;
|
|
}
|
|
}
|
|
|
|
static bool refresh_host_availability(MYSQL *mysql, host_t *host, ping_t *ping, int host_thread) {
|
|
if (host->availability.method == AVAIL_SNMP && strlen(host->snmp.profile.community) == 0 && host->snmp.profile.version < 3) {
|
|
host->ignore_host = FALSE;
|
|
update_host_status(HOST_UP, host, ping, host->availability.method);
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] No host availability check possible for '%s'", host->id, host_thread, host->hostname));
|
|
return FALSE;
|
|
}
|
|
if (host->availability.method == AVAIL_STREAM) {
|
|
update_host_status(HOST_UP, host, ping, host->availability.method);
|
|
return FALSE;
|
|
}
|
|
bool alive = ping_host(host, ping) == HOST_UP;
|
|
host->ignore_host = !alive;
|
|
if (host_thread != 1) return FALSE;
|
|
update_host_status(alive ? HOST_UP : HOST_DOWN, host, ping, host->availability.method);
|
|
if (!alive || host->availability.method == AVAIL_PING || host->availability.method == AVAIL_NONE || host->snmp.session == NULL || !set.snmp.mibs) return FALSE;
|
|
get_system_information(host, mysql, 1);
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct {
|
|
MYSQL *local;
|
|
MYSQL *remote;
|
|
const host_t *host;
|
|
const poller_thread_t *work;
|
|
int *errors;
|
|
int *spike_kill;
|
|
} reindex_evaluation_t;
|
|
|
|
static bool reindex_assertion_failed(const reindex_t *reindex, const char *value) {
|
|
if (value == NULL || IS_UNDEFINED(value) || STRIMATCH(value, "No Such Instance")) return FALSE;
|
|
if (STRMATCH(reindex->op, "=")) return strcmp(reindex->assert_value, value) != 0;
|
|
if (STRMATCH(reindex->op, ">")) return atoll(reindex->assert_value) < atoll(value);
|
|
if (STRMATCH(reindex->op, "<")) return !STRMATCH(reindex->assert_value, "0") && atoll(reindex->assert_value) > atoll(value);
|
|
return FALSE;
|
|
}
|
|
|
|
static void log_reindex_assertion(const reindex_evaluation_t *evaluation, const reindex_t *reindex, const char *value, bool failed) {
|
|
bool highlighted = is_debug_device(evaluation->host->id) || set.logging.spine_log_level == 2;
|
|
if (!failed && !highlighted) return;
|
|
if (failed && !highlighted && set.logging.spine_log_level == 1) (*evaluation->errors)++;
|
|
SPINE_LOG(("Device[%i] HT[%i] DQ[%i] RECACHE ASSERT FAILED: '%s%s%s'", evaluation->host->id, evaluation->work->host_thread, reindex->data_query_id, reindex->assert_value, failed ? reindex->op : "=", value != NULL ? value : "(null)"));
|
|
}
|
|
|
|
/* FALSE only when the queue write failed; other partitions never queue. */
|
|
static bool queue_reindex(const reindex_evaluation_t *evaluation, const reindex_t *reindex) {
|
|
if (evaluation->work->host_thread != 1) return TRUE;
|
|
char query[LRG_BUFSIZE];
|
|
snprintf(query, sizeof(query), "REPLACE INTO poller_command (poller_id, time, action, command) VALUES (%i, NOW(), %i, '%i:%i')", set.poller.poller_id, POLLER_COMMAND_REINDEX, evaluation->host->id, reindex->data_query_id);
|
|
if (set.poller.poller_id > 1 && set.poller.mode == REMOTE_ONLINE) return db_insert(evaluation->remote, REMOTE, query);
|
|
return db_insert(evaluation->local, LOCAL, query);
|
|
}
|
|
|
|
static void update_reindex_value(const reindex_evaluation_t *evaluation, const reindex_t *reindex, const char *value) {
|
|
/* An unavailable result cannot replace the stored assertion with uninitialized bytes. */
|
|
if (evaluation->work->host_thread != 1 || value == NULL) return;
|
|
char escaped_value[BUFSIZE];
|
|
char escaped_argument[BUFSIZE];
|
|
char query[LRG_BUFSIZE];
|
|
db_escape(evaluation->local, escaped_value, sizeof(escaped_value), value);
|
|
db_escape(evaluation->local, escaped_argument, sizeof(escaped_argument), reindex->arg1);
|
|
snprintf(query, sizeof(query), "UPDATE poller_reindex SET assert_value='%s' WHERE host_id='%i' AND data_query_id='%i' AND arg1='%s'", escaped_value, evaluation->work->host_id, reindex->data_query_id, escaped_argument);
|
|
db_insert(evaluation->local, LOCAL, query);
|
|
}
|
|
|
|
static void discard_reindex_spike(const reindex_evaluation_t *evaluation) {
|
|
*evaluation->spike_kill = TRUE;
|
|
if (is_debug_device(evaluation->host->id) || set.logging.spine_log_level == 2) {
|
|
SPINE_LOG(("Device[%i] HT[%i] NOTICE: Spike Kill in Effect for '%s'", evaluation->work->host_id, evaluation->work->host_thread, evaluation->host->hostname));
|
|
} else {
|
|
if (set.logging.spine_log_level == 1) (*evaluation->errors)++;
|
|
SPINE_LOG_MEDIUM(("Device[%i] HT[%i] NOTICE: Spike Kill in Effect for '%s'", evaluation->work->host_id, evaluation->work->host_thread, evaluation->host->hostname));
|
|
}
|
|
}
|
|
|
|
static bool evaluate_reindex_assertion(const reindex_evaluation_t *evaluation, const reindex_t *reindex, const char *value) {
|
|
bool failed = reindex_assertion_failed(reindex, value);
|
|
bool unavailable = value == NULL || IS_UNDEFINED(value) || STRIMATCH(value, "No Such Instance");
|
|
if (failed || unavailable) log_reindex_assertion(evaluation, reindex, value, failed);
|
|
/* Advancing the stored value without a queued reindex would lose the
|
|
* reindex for good: the next poll compares against the new value. */
|
|
bool queued = !failed || queue_reindex(evaluation, reindex);
|
|
/* Keep the last good baseline. U compares as 0, so storing it would hide
|
|
* the next reboot under "<" and fire a false reindex under ">". */
|
|
if (queued && !unavailable && (failed || STRMATCH(reindex->op, ">") || STRMATCH(reindex->op, "<"))) {
|
|
update_reindex_value(evaluation, reindex, value);
|
|
}
|
|
/* A failed uptime assertion means the counters reset, so the sample is a spike. */
|
|
if (failed && (STRMATCH(reindex->op, "<") ||
|
|
STRMATCH(reindex->arg1, ".1.3.6.1.2.1.1.3.0") ||
|
|
STRMATCH(reindex->arg1, ".1.3.6.1.6.3.10.2.1.3.0"))) {
|
|
discard_reindex_spike(evaluation);
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
/* An uptime is a non-negative integer; anything else cannot be compared. */
|
|
static bool parse_uptime(const char *text, unsigned long long *ticks) {
|
|
unsigned long long value = 0;
|
|
unsigned int digit;
|
|
|
|
if (text == NULL || ticks == NULL) return FALSE;
|
|
while (isspace((unsigned char)*text)) text++;
|
|
if (*text < '0' || *text > '9') return FALSE;
|
|
while (*text >= '0' && *text <= '9') {
|
|
digit = (unsigned int)(*text++ - '0');
|
|
if (value > (ULLONG_MAX - digit) / 10) return FALSE;
|
|
value = value * 10 + digit;
|
|
}
|
|
while (isspace((unsigned char)*text)) text++;
|
|
if (*text != '\0') return FALSE;
|
|
*ticks = value;
|
|
return TRUE;
|
|
}
|
|
|
|
static char *poll_reindex_snmp(host_t *host, reindex_t *reindex, int host_thread, char *sysUptime, bool *unavailable) {
|
|
if (host->snmp.session == NULL) {
|
|
*unavailable = TRUE;
|
|
SPINE_LOG(("WARNING: Device[%i] HT[%i] DQ[%i] Reindex Check FAILED: No SNMP Session. If not an SNMP host, don't use Uptime Goes Backwards!", host->id, host_thread, reindex->data_query_id));
|
|
return NULL;
|
|
}
|
|
char *poll_result = NULL;
|
|
if ((strstr(reindex->arg1, ".1.3.6.1.2.1.1.3.0") ||
|
|
strstr(reindex->arg1, ".1.3.6.1.6.3.10.2.1.3.0")) && strlen(sysUptime) > 0) {
|
|
|
|
if (!(poll_result = (char *) malloc(BUFSIZE))) {
|
|
die("ERROR: Fatal malloc error: poller.c poll_result");
|
|
}
|
|
|
|
poll_result[0] = '\0';
|
|
|
|
snprintf(poll_result, BUFSIZE, "%s", sysUptime);
|
|
} else if (strstr(reindex->arg1, ".1.3.6.1.2.1.1.3.0") ||
|
|
strstr(reindex->arg1, ".1.3.6.1.6.3.10.2.1.3.0")) {
|
|
unsigned long long ticks;
|
|
bool uptime_use_engine_oid;
|
|
|
|
// Ensure uptime is empty to start with
|
|
sysUptime[0] = '\0';
|
|
|
|
/* Pin the uptime-goes-backward calculation to a single OID for this poll.
|
|
The legacy (centisecond) and modern (second) OIDs are different counters,
|
|
and letting either supply the value on different rows or cycles caused
|
|
false "uptime went backward" detections and constant reindexing. Prefer
|
|
the modern engine OID and fall back to the legacy OID only when the
|
|
engine OID is not present with numeric data. */
|
|
poll_result = snmp_get_base(host, ".1.3.6.1.6.3.10.2.1.3.0", false);
|
|
|
|
uptime_use_engine_oid = parse_uptime(poll_result, &ticks) && ticks <= ULLONG_MAX / 100;
|
|
|
|
if (uptime_use_engine_oid) {
|
|
snprintf(sysUptime, BUFSIZE, "%llu", ticks * 100);
|
|
}
|
|
|
|
SPINE_FREE(poll_result);
|
|
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] Engine Uptime OID Present: %d", host->id, host_thread, reindex->data_query_id, uptime_use_engine_oid));
|
|
|
|
if (!uptime_use_engine_oid) {
|
|
// Engine OID unavailable, fall back to the legacy sysUpTime OID
|
|
poll_result = snmp_get(host, ".1.3.6.1.2.1.1.3.0");
|
|
|
|
if (parse_uptime(poll_result, &ticks)) {
|
|
snprintf(sysUptime, BUFSIZE, "%llu", ticks);
|
|
}
|
|
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] Legacy Uptime Result: %s, Is Numeric: %d", host->id, host_thread, reindex->data_query_id, poll_result != NULL ? poll_result : "U", sysUptime[0] != '\0'));
|
|
|
|
SPINE_FREE(poll_result);
|
|
}
|
|
|
|
// Use the primed uptime to repopulate the poll_result
|
|
// This ensures whichever response was valid gets used
|
|
/* With neither OID readable there is nothing to compare. An empty
|
|
* value reads as 0, which looks like a reboot and queues a reindex. */
|
|
poll_result = strdup(sysUptime[0] != '\0' ? sysUptime : "U");
|
|
if (poll_result == NULL) {
|
|
die("ERROR: Fatal malloc error: poller.c uptime result");
|
|
}
|
|
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] Extended Uptime Result: %s, Is Numeric: %d", host->id, host_thread, reindex->data_query_id, poll_result, sysUptime[0] != '\0'));
|
|
} else {
|
|
poll_result = snmp_get(host, reindex->arg1);
|
|
}
|
|
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE OID: %s, (assert: %s %s output: %s)", host->id, host_thread, reindex->data_query_id, reindex->arg1, reindex->assert_value, reindex->op, poll_result));
|
|
return poll_result;
|
|
}
|
|
|
|
/* trim() can return a pointer past the start of the allocation. Move the
|
|
* trimmed text to the front so the caller still frees what malloc returned. */
|
|
static char *trim_owned(char *owned) {
|
|
char *trimmed;
|
|
|
|
if (owned == NULL) return NULL;
|
|
trimmed = trim(owned);
|
|
if (trimmed != owned) memmove(owned, trimmed, strlen(trimmed) + 1);
|
|
return owned;
|
|
}
|
|
|
|
static char *poll_reindex_action(host_t *host, reindex_t *reindex, int host_thread, char *sysUptime, bool *unavailable) {
|
|
char *poll_result = NULL;
|
|
int php_process;
|
|
char script[SMALL_BUFSIZE];
|
|
switch(reindex->action) {
|
|
case POLLER_ACTION_SNMP:
|
|
poll_result = poll_reindex_snmp(host, reindex, host_thread, sysUptime, unavailable);
|
|
break;
|
|
case POLLER_ACTION_SCRIPT: /* script (popen) */
|
|
/* Reject empty script commands that could cause unexpected behavior */
|
|
if (strlen(reindex->arg1) == 0) {
|
|
SPINE_LOG(("WARNING: Device[%i] HT[%i] DQ[%i] empty script command, skipping",
|
|
host->id, host_thread, reindex->data_query_id));
|
|
break;
|
|
}
|
|
|
|
poll_result = trim_owned(exec_poll(host, reindex->arg1, reindex->data_query_id, "DQ"));
|
|
|
|
php_command_script(reindex->arg1, script, sizeof(script));
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE CMD: %s, output: %s", host->id, host_thread, reindex->data_query_id, script, poll_result));
|
|
|
|
break;
|
|
case POLLER_ACTION_PHP_SCRIPT_SERVER: /* script (php script server) */
|
|
php_process = php_get_process();
|
|
|
|
poll_result = trim_owned(php_cmd(reindex->arg1, php_process));
|
|
|
|
php_command_script(reindex->arg1, script, sizeof(script));
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE SERVER: %s, output: %s", host->id, host_thread, reindex->data_query_id, script, poll_result));
|
|
|
|
break;
|
|
case POLLER_ACTION_SNMP_COUNT: { /* snmp; count items */
|
|
if (!(poll_result = (char *) malloc(BUFSIZE))) {
|
|
die("ERROR: Fatal malloc error: poller.c poll_result");
|
|
}
|
|
poll_result[0] = '\0';
|
|
|
|
int snmp_items = snmp_count(host, reindex->arg1);
|
|
if (snmp_items < 0) {
|
|
SET_UNDEFINED(poll_result);
|
|
} else {
|
|
snprintf(poll_result, BUFSIZE, "%d", snmp_items);
|
|
}
|
|
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE OID COUNT: %s, output: %s", host->id, host_thread, reindex->data_query_id, reindex->arg1, poll_result));
|
|
|
|
break;
|
|
}
|
|
case POLLER_ACTION_SCRIPT_COUNT: { /* script (popen); count line feeds */
|
|
if (!(poll_result = (char *) malloc(BUFSIZE))) {
|
|
die("ERROR: Fatal malloc error: poller.c poll_result");
|
|
}
|
|
poll_result[0] = '\0';
|
|
|
|
char *count_result = exec_poll(host, reindex->arg1, reindex->data_query_id, "DQ");
|
|
snprintf(poll_result, BUFSIZE, "%d", char_count(count_result, '\n'));
|
|
SPINE_FREE(count_result);
|
|
|
|
php_command_script(reindex->arg1, script, sizeof(script));
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE CMD COUNT: %s, output: %s", host->id, host_thread, reindex->data_query_id, script, poll_result));
|
|
|
|
break;
|
|
}
|
|
case POLLER_ACTION_PHP_SCRIPT_SERVER_COUNT: { /* script (php script server); count number of lines */
|
|
if (!(poll_result = (char *) malloc(BUFSIZE))) {
|
|
die("ERROR: Fatal malloc error: poller.c poll_result");
|
|
}
|
|
poll_result[0] = '\0';
|
|
|
|
php_process = php_get_process();
|
|
|
|
char *count_result = php_cmd(reindex->arg1, php_process);
|
|
spine_snprintf(poll_result, BUFSIZE, "%d", char_count(count_result, '\n'));
|
|
SPINE_FREE(count_result);
|
|
|
|
php_command_script(reindex->arg1, script, sizeof(script));
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DQ[%i] RECACHE SERVER COUNT: %s, output: %s", host->id, host_thread, reindex->data_query_id, script, poll_result));
|
|
|
|
break;
|
|
}
|
|
default:
|
|
SPINE_LOG(("Device[%i] HT[%i] ERROR: Unknown Assert Action!", host->id, host_thread));
|
|
}
|
|
return poll_result;
|
|
}
|
|
|
|
static void load_reindex_item(reindex_t *reindex, MYSQL_ROW row) {
|
|
/* initialize the reindex struction */
|
|
reindex->data_query_id = 0;
|
|
reindex->action = -1;
|
|
reindex->op[0] = '\0';
|
|
reindex->assert_value[0] = '\0';
|
|
reindex->arg1[0] = '\0';
|
|
|
|
if (row[0] != NULL) reindex->data_query_id = atoi(row[0]);
|
|
if (row[1] != NULL) reindex->action = atoi(row[1]);
|
|
|
|
if (row[2] != NULL) snprintf(reindex->op, sizeof(reindex->op), "%s", row[2]);
|
|
|
|
if (row[3] != NULL) snprintf(reindex->assert_value, sizeof(reindex->assert_value), "%s", row[3]);
|
|
|
|
if (row[4] != NULL) snprintf(reindex->arg1, sizeof(reindex->arg1), "%s", row[4]);
|
|
}
|
|
|
|
static bool poll_host_reindex(host_t *host, reindex_t *reindex, const char *query,
|
|
const reindex_evaluation_t *evaluation) {
|
|
if (host->ignore_host || evaluation->work->host_id == 0) return TRUE;
|
|
int thread = evaluation->work->host_thread;
|
|
MYSQL_RES *result = db_query(evaluation->local, LOCAL, query);
|
|
if (result == NULL) {
|
|
SPINE_LOG(("Device[%i] HT[%i] ERROR: RECACHE Query Returned Null Result!", host->id, thread));
|
|
} else {
|
|
int count = spine_count_to_int(mysql_num_rows(result));
|
|
if (count == 0) {
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_HIGH, ("Device[%i] HT[%i] Device has no information for recache.", host->id, thread));
|
|
} else {
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_DEBUG, ("Device[%i] HT[%i] DEBUG: RECACHE: Processing %i items in the auto reindex cache for '%s'", host->id, thread, count, host->hostname));
|
|
}
|
|
char sysUptime[BUFSIZE] = "";
|
|
int last_data_query_id = 0;
|
|
bool previous_assert_failure = FALSE;
|
|
MYSQL_ROW row;
|
|
while ((row = mysql_fetch_row(result))) {
|
|
load_reindex_item(reindex, row);
|
|
if (last_data_query_id != reindex->data_query_id) {
|
|
last_data_query_id = reindex->data_query_id;
|
|
previous_assert_failure = FALSE;
|
|
}
|
|
if (previous_assert_failure) continue;
|
|
bool unavailable = FALSE;
|
|
char *value = poll_reindex_action(host, reindex, thread, sysUptime, &unavailable);
|
|
if (unavailable) continue;
|
|
if (evaluate_reindex_assertion(evaluation, reindex, value)) previous_assert_failure = TRUE;
|
|
SPINE_FREE(value);
|
|
}
|
|
db_free_result(result);
|
|
}
|
|
/* Item polling opens a new session after reindex work has finished. */
|
|
if (host->snmp.session != NULL) {
|
|
snmp_host_cleanup(host->snmp.session);
|
|
host->snmp.session = NULL;
|
|
}
|
|
return result != NULL;
|
|
}
|
|
|
|
static void load_poll_item(target_t *item, MYSQL_ROW row) {
|
|
/* initialize monitored object */
|
|
item->target_id = 0;
|
|
item->action = -1;
|
|
item->hostname[0] = '\0';
|
|
item->snmp.community[0] = '\0';
|
|
item->snmp.version = 1;
|
|
item->snmp.username[0] = '\0';
|
|
item->snmp.password[0] = '\0';
|
|
item->snmp.auth_protocol[0] = '\0';
|
|
item->snmp.priv_passphrase[0] = '\0';
|
|
item->snmp.priv_protocol[0] = '\0';
|
|
item->snmp.context[0] = '\0';
|
|
item->snmp.engine_id[0] = '\0';
|
|
item->snmp.port = 161;
|
|
item->snmp.timeout = 500;
|
|
item->rrd_name[0] = '\0';
|
|
item->rrd_path[0] = '\0';
|
|
item->arg1[0] = '\0';
|
|
item->arg2[0] = '\0';
|
|
item->arg3[0] = '\0';
|
|
item->local_data_id = 0;
|
|
item->rrd_num = 0;
|
|
item->output_regex[0] = '\0';
|
|
|
|
if (row[0] != NULL) item->action = atoi(row[0]);
|
|
|
|
if (row[1] != NULL) snprintf(item->hostname, sizeof(item->hostname), "%s", row[1]);
|
|
if (row[2] != NULL) snprintf(item->snmp.community, sizeof(item->snmp.community), "%s", row[2]);
|
|
|
|
if (row[3] != NULL) item->snmp.version = atoi(row[3]);
|
|
|
|
if (row[4] != NULL) snprintf(item->snmp.username, sizeof(item->snmp.username), "%s", row[4]);
|
|
if (row[5] != NULL) snprintf(item->snmp.password, sizeof(item->snmp.password), "%s", row[5]);
|
|
|
|
if (row[6] != NULL) snprintf(item->rrd_name, sizeof(item->rrd_name), "%s", row[6]);
|
|
if (row[7] != NULL) snprintf(item->rrd_path, sizeof(item->rrd_path), "%s", row[7]);
|
|
if (row[8] != NULL) snprintf(item->arg1, sizeof(item->arg1), "%s", row[8]);
|
|
if (row[9] != NULL) snprintf(item->arg2, sizeof(item->arg2), "%s", row[9]);
|
|
if (row[10] != NULL) snprintf(item->arg3, sizeof(item->arg3), "%s", row[10]);
|
|
|
|
if (row[11] != NULL) item->local_data_id = atoi(row[11]);
|
|
|
|
if (row[12] != NULL) item->rrd_num = atoi(row[12]);
|
|
if (row[13] != NULL) item->snmp.port = atoi(row[13]);
|
|
if (row[14] != NULL) item->snmp.timeout = atoi(row[14]);
|
|
|
|
if (row[15] != NULL) snprintf(item->snmp.auth_protocol,
|
|
sizeof(item->snmp.auth_protocol), "%s", row[15]);
|
|
if (row[16] != NULL) snprintf(item->snmp.priv_passphrase,
|
|
sizeof(item->snmp.priv_passphrase), "%s", row[16]);
|
|
if (row[17] != NULL) snprintf(item->snmp.priv_protocol,
|
|
sizeof(item->snmp.priv_protocol), "%s", row[17]);
|
|
if (row[18] != NULL) snprintf(item->snmp.context,
|
|
sizeof(item->snmp.context), "%s", row[18]);
|
|
if (row[19] != NULL) snprintf(item->snmp.engine_id,
|
|
sizeof(item->snmp.engine_id), "%s", row[19]);
|
|
|
|
if (set.hosts.has_output_regex && row[20] != NULL)
|
|
snprintf(item->output_regex, sizeof(item->output_regex), "%s", row[20]);
|
|
|
|
SET_UNDEFINED(item->result);
|
|
}
|
|
|
|
static void poll_script_item(host_t *host, target_t *item,
|
|
const poll_error_context_t *errors, double thread_start, bool spike_kill,
|
|
bool script_server) {
|
|
int php_process = -1;
|
|
char *poll_result;
|
|
/* Reject empty script commands that could cause unexpected behavior */
|
|
if (strlen(item->arg1) == 0) {
|
|
SPINE_LOG(("WARNING: Device[%i] HT[%i] DS[%i] empty script%s command, skipping",
|
|
errors->host_id, errors->thread_id, item->local_data_id, script_server ? " server" : ""));
|
|
SET_UNDEFINED(item->result);
|
|
return;
|
|
}
|
|
if (script_server) {
|
|
php_process = php_get_process();
|
|
poll_result = php_cmd(item->arg1, php_process);
|
|
} else {
|
|
poll_result = exec_poll(host, item->arg1, item->local_data_id, "DS");
|
|
}
|
|
strncopy(item->result, poll_result, sizeof(item->result));
|
|
enum poll_result_status status = normalize_poll_result(item->result, false);
|
|
if (status != POLL_RESULT_VALID) {
|
|
record_result_error(errors, host, item, item->result, false);
|
|
if (status == POLL_RESULT_INVALID) SET_UNDEFINED(item->result);
|
|
}
|
|
SPINE_FREE(poll_result);
|
|
double thread_end = get_time_as_double();
|
|
char script[SMALL_BUFSIZE];
|
|
php_command_script(item->arg1, script, sizeof(script));
|
|
if (script_server) {
|
|
SPINE_LOG_DEVICE(errors->host_id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DS[%i] TT[%.2f] SS[%i] SERVER: %s, output: %s", errors->host_id, errors->thread_id, item->local_data_id, (float) ((thread_end - thread_start) * 1000), php_process, script, item->result));
|
|
} else {
|
|
SPINE_LOG_DEVICE(errors->host_id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] DS[%i] TT[%.2f] SCRIPT: %s, output: %s", errors->host_id, errors->thread_id, item->local_data_id, (float) ((thread_end - thread_start) * 1000), script, item->result));
|
|
}
|
|
/* insert a NaN in place of the actual value if the snmp agent restarts */
|
|
if (!IS_UNDEFINED(item->result) && spike_kill && !strstr(item->result, ":")) SET_UNDEFINED(item->result);
|
|
}
|
|
|
|
typedef struct {
|
|
char *output;
|
|
char *boost;
|
|
bool failed;
|
|
} poll_output_buffers_t;
|
|
|
|
static char *allocate_output_query(const char *prefix, const char *failure_message) {
|
|
char *query = malloc(MAX_MYSQL_BUF_SIZE + RESULTS_BUFFER);
|
|
if (query == NULL) die("%s", failure_message);
|
|
memset(query, 0, MAX_MYSQL_BUF_SIZE + RESULTS_BUFFER);
|
|
strncat(query, prefix, spine_count_to_int(strlen(prefix)));
|
|
return query;
|
|
}
|
|
|
|
/* Attempt each destination independently. Reset only early-flush buffers;
|
|
* a failed write must not suppress the other destination or later batches. */
|
|
static bool flush_output_query(MYSQL *mysql, int mode, char *query,
|
|
const char *suffix, const char *prefix) {
|
|
if (query == NULL) return TRUE;
|
|
strncat(query, suffix, spine_count_to_int(strlen(suffix)));
|
|
bool success = db_insert(mysql, mode, query);
|
|
if (prefix != NULL) {
|
|
memset(query, 0, MAX_MYSQL_BUF_SIZE + RESULTS_BUFFER);
|
|
strncat(query, prefix, spine_count_to_int(strlen(prefix)));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static poll_output_buffers_t write_poll_results(MYSQL *mysql, MYSQL *mysqlr,
|
|
const poller_queries_t *queries, const target_t *poller_items,
|
|
int rows_processed, const char *host_time) {
|
|
char *query3 = NULL;
|
|
char *query12 = NULL;
|
|
bool failed = FALSE;
|
|
/* Escaping can double the result and the data source name. */
|
|
char result_string[RESULTS_BUFFER * 2 + DBL_BUFSIZE + SMALL_BUFSIZE];
|
|
int result_length;
|
|
int new_buffer = TRUE;
|
|
size_t out_buffer;
|
|
MYSQL *mysqlt;
|
|
int i;
|
|
|
|
/* insert the query results into the database */
|
|
query3 = allocate_output_query(queries->output, "ERROR: Fatal malloc error: poller.c query3 output buffer!");
|
|
|
|
out_buffer = strlen(query3);
|
|
|
|
if (set.boost.boost_redirect && set.boost.boost_enabled) {
|
|
query12 = allocate_output_query(queries->boost_output, "ERROR: Fatal malloc error: poller.c query12 boost output buffer!");
|
|
}
|
|
|
|
int mode;
|
|
if (set.poller.poller_id > 1 && set.poller.mode == REMOTE_ONLINE) {
|
|
SPINE_LOG_DEBUG(("DEBUG: Setting up writes to remote database"));
|
|
mysqlt = mysqlr;
|
|
mode = REMOTE;
|
|
} else {
|
|
SPINE_LOG_DEBUG(("DEBUG: Setting up writes to local database"));
|
|
mysqlt = mysql;
|
|
mode = LOCAL;
|
|
}
|
|
|
|
i = 0;
|
|
while (i < rows_processed) {
|
|
char escaped_result[RESULTS_BUFFER * 2 + 1];
|
|
char escaped_rrd_name[DBL_BUFSIZE];
|
|
|
|
db_escape(mysqlt, escaped_result, sizeof(escaped_result), poller_items[i].result);
|
|
db_escape(mysqlt, escaped_rrd_name, sizeof(escaped_rrd_name), poller_items[i].rrd_name);
|
|
|
|
if (!format_poller_output_row(result_string, sizeof(result_string),
|
|
poller_items[i].local_data_id,
|
|
escaped_rrd_name,
|
|
host_time,
|
|
escaped_result)) {
|
|
SPINE_LOG(("ERROR: Poller output for DS[%i] exceeds the configured result buffer and was skipped",
|
|
poller_items[i].local_data_id));
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
result_length = spine_count_to_int(strlen(result_string));
|
|
|
|
/* if the next element to the buffer will overflow it, write to the database */
|
|
if ((out_buffer + result_length) >= MAX_MYSQL_BUF_SIZE) {
|
|
if (!flush_output_query(mysqlt, mode, query3, queries->suffix, queries->output)) failed = TRUE;
|
|
if (!flush_output_query(mysqlt, mode, query12, queries->suffix, queries->boost_output)) failed = TRUE;
|
|
|
|
/* reset the output buffer length */
|
|
out_buffer = strlen(query3);
|
|
|
|
/* set binary, let the system know we are a new buffer */
|
|
new_buffer = TRUE;
|
|
}
|
|
|
|
/* if this is our first pass, or we just outputted to the database, need to change the delimiter */
|
|
result_string[0] = new_buffer ? ' ' : ',';
|
|
|
|
strncat(query3, result_string, result_length);
|
|
|
|
if (query12 != NULL) {
|
|
strncat(query12, result_string, result_length);
|
|
}
|
|
|
|
out_buffer = out_buffer + strlen(result_string);
|
|
new_buffer = FALSE;
|
|
i++;
|
|
}
|
|
|
|
/* perform the last insert if there is data to process */
|
|
if (out_buffer > strlen(queries->output)) {
|
|
if (!flush_output_query(mysqlt, mode, query3, queries->suffix, NULL)) failed = TRUE;
|
|
if (!flush_output_query(mysqlt, mode, query12, queries->suffix, NULL)) failed = TRUE;
|
|
}
|
|
/* MEMORY output tables can retain earlier rows after a rejected write.
|
|
* Confirmed partial output remains; keep due items eligible for recollection. */
|
|
return (poll_output_buffers_t){query3, query12, failed};
|
|
}
|
|
|
|
/* The key holds a whole profile. Narrower copies truncated long communities,
|
|
* which then never matched their own item and forced a session per OID. */
|
|
typedef struct {
|
|
int initialized;
|
|
char hostname[sizeof(((target_t *)0)->hostname)];
|
|
snmp_profile_t profile;
|
|
} snmp_item_key_t;
|
|
|
|
typedef struct {
|
|
target_t *items;
|
|
snmp_oids_t *oids;
|
|
int count;
|
|
snmp_item_key_t key;
|
|
const poll_error_context_t *errors;
|
|
} snmp_poll_batch_t;
|
|
|
|
static void remember_snmp_item(snmp_item_key_t *key, const target_t *item) {
|
|
key->profile = item->snmp;
|
|
STRNCOPY(key->hostname, item->hostname);
|
|
}
|
|
|
|
static void *open_snmp_item(const host_t *host, target_t *item) {
|
|
return snmp_host_init(&(snmp_connection_t){
|
|
.host_id = host->id,
|
|
.hostname = item->hostname,
|
|
.snmp_version = item->snmp.version,
|
|
.snmp_community = item->snmp.community,
|
|
.snmp_username = item->snmp.username,
|
|
.snmp_password = item->snmp.password,
|
|
.snmp_auth_protocol = item->snmp.auth_protocol,
|
|
.snmp_priv_passphrase = item->snmp.priv_passphrase,
|
|
.snmp_priv_protocol = item->snmp.priv_protocol,
|
|
.snmp_context = item->snmp.context,
|
|
.snmp_engine_id = item->snmp.engine_id,
|
|
.snmp_port = item->snmp.port,
|
|
.snmp_timeout = item->snmp.timeout,
|
|
});
|
|
}
|
|
|
|
static bool snmp_item_changed(const snmp_item_key_t *key, const target_t *item) {
|
|
return !STRMATCH(key->hostname, item->hostname) ||
|
|
(key->profile.port != item->snmp.port) ||
|
|
(key->profile.timeout != item->snmp.timeout) ||
|
|
(key->profile.version != item->snmp.version) ||
|
|
(item->snmp.version < 3 &&
|
|
(!STRMATCH(key->profile.community, item->snmp.community))) ||
|
|
(item->snmp.version > 2 &&
|
|
((!STRMATCH(key->profile.username, item->snmp.username)) ||
|
|
(!STRMATCH(key->profile.password, item->snmp.password)) ||
|
|
(!STRMATCH(key->profile.auth_protocol, item->snmp.auth_protocol)) ||
|
|
(!STRMATCH(key->profile.priv_passphrase, item->snmp.priv_passphrase)) ||
|
|
(!STRMATCH(key->profile.priv_protocol, item->snmp.priv_protocol)) ||
|
|
(!STRMATCH(key->profile.context, item->snmp.context)) ||
|
|
(!STRMATCH(key->profile.engine_id, item->snmp.engine_id))));
|
|
}
|
|
|
|
/* The batch-full, credential-change and final flushes all come through here,
|
|
* so every item gets the same regex, validation and spike kill. */
|
|
static void flush_snmp_batch(host_t *host, snmp_poll_batch_t *batch,
|
|
double thread_start, bool spike_kill) {
|
|
if (batch->count <= 0) return;
|
|
snmp_get_multi(host, batch->items, batch->oids, batch->count);
|
|
store_snmp_results(host, batch->items, batch->oids, batch->count, batch->errors, thread_start, spike_kill);
|
|
batch->count = 0;
|
|
memset(batch->oids, 0, sizeof(snmp_oids_t) * host->snmp.max_oids);
|
|
}
|
|
|
|
static void poll_snmp_item(host_t *host, snmp_poll_batch_t *batch, int position,
|
|
double thread_start, bool spike_kill) {
|
|
target_t *item = &batch->items[position];
|
|
if (batch->key.initialized == 0) {
|
|
remember_snmp_item(&batch->key, item);
|
|
host->snmp.session = open_snmp_item(host, item);
|
|
batch->key.initialized++;
|
|
}
|
|
if (host->snmp.session == NULL) {
|
|
host->ignore_host = TRUE;
|
|
return;
|
|
}
|
|
if (snmp_item_changed(&batch->key, item)) {
|
|
flush_snmp_batch(host, batch, thread_start, spike_kill);
|
|
if (host->snmp.session != NULL) {
|
|
snmp_host_cleanup(host->snmp.session);
|
|
host->snmp.session = NULL;
|
|
}
|
|
host->snmp.session = open_snmp_item(host, item);
|
|
remember_snmp_item(&batch->key, item);
|
|
}
|
|
if (batch->count >= host->snmp.max_oids) flush_snmp_batch(host, batch, thread_start, spike_kill);
|
|
snprintf(batch->oids[batch->count].oid, sizeof(batch->oids[batch->count].oid), "%s", item->arg1);
|
|
batch->oids[batch->count].array_position = position;
|
|
batch->count++;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
target_t *items;
|
|
snmp_oids_t *oids;
|
|
} poll_item_storage_t;
|
|
|
|
static MYSQL_RES *select_poll_items(MYSQL *mysql, const poller_queries_t *queries,
|
|
const host_t *host, const poller_thread_t *work, int *num_rows) {
|
|
const char *query = set.poller.poller_interval == 0 ? queries->items : queries->due_items;
|
|
MYSQL_RES *result = db_query(mysql, LOCAL, query);
|
|
*num_rows = 0;
|
|
if (result != NULL) {
|
|
*num_rows = spine_count_to_int(mysql_num_rows(result));
|
|
} else {
|
|
SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to Retrieve Rows due to Null Result!", host->id, work->host_thread));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static poll_item_storage_t load_poll_items(MYSQL_RES *result, host_t *host, int num_rows) {
|
|
poll_item_storage_t storage;
|
|
/* retrieve each hosts polling items from poller cache and load into array */
|
|
storage.items = (target_t *) calloc(num_rows, sizeof(target_t));
|
|
if (storage.items == NULL) die("ERROR: Fatal calloc error: poller.c poller_items");
|
|
|
|
int i = 0;
|
|
MYSQL_ROW row;
|
|
while ((row = mysql_fetch_row(result))) {
|
|
load_poll_item(&storage.items[i], row);
|
|
|
|
i++;
|
|
}
|
|
|
|
/* free the mysql result */
|
|
db_free_result(result);
|
|
|
|
/* create an array for snmp oids */
|
|
if (host->snmp.max_oids <= 0) {
|
|
host->snmp.max_oids = 1;
|
|
}
|
|
storage.oids = (snmp_oids_t *) calloc(host->snmp.max_oids, sizeof(snmp_oids_t));
|
|
if (storage.oids == NULL) {
|
|
die("ERROR: Fatal calloc error: poller.c snmp_oids");
|
|
}
|
|
|
|
return storage;
|
|
}
|
|
|
|
static int collect_poll_items(host_t *host, snmp_poll_batch_t *batch, int num_rows, bool spike_kill) {
|
|
int i = 0;
|
|
int rows_processed = 0;
|
|
double thread_start = 0;
|
|
char script[SMALL_BUFSIZE];
|
|
while ((i < num_rows) && (!host->ignore_host)) {
|
|
thread_start = get_time_as_double();
|
|
|
|
switch(batch->items[i].action) {
|
|
case POLLER_ACTION_SNMP:
|
|
poll_snmp_item(host, batch, i, thread_start, spike_kill);
|
|
break;
|
|
case POLLER_ACTION_SCRIPT:
|
|
poll_script_item(host, &batch->items[i], batch->errors, thread_start, spike_kill, FALSE);
|
|
break;
|
|
case POLLER_ACTION_PHP_SCRIPT_SERVER:
|
|
poll_script_item(host, &batch->items[i], batch->errors, thread_start, spike_kill, TRUE);
|
|
break;
|
|
default: /* unknown action, generate error */
|
|
php_command_script(batch->items[i].arg1, script, sizeof(script));
|
|
SPINE_LOG(("Device[%i] HT[%i] DS[%i] ERROR: Unknown Poller Action: %s", batch->errors->host_id, batch->errors->thread_id, batch->items[i].local_data_id, script));
|
|
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
rows_processed++;
|
|
}
|
|
|
|
/* process last multi-get request if applicable */
|
|
flush_snmp_batch(host, batch, thread_start, spike_kill);
|
|
|
|
return rows_processed;
|
|
}
|
|
|
|
static void release_poll_connections(const poller_thread_t *work,
|
|
const pool_t *local, const pool_t *remote) {
|
|
if (local != NULL) {
|
|
db_release_connection(LOCAL, local->id);
|
|
} else {
|
|
SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized local connection.", work->host_id, work->host_thread));
|
|
}
|
|
if (set.poller.poller_id > 1 && set.poller.mode == REMOTE_ONLINE) {
|
|
if (remote != NULL) {
|
|
db_release_connection(REMOTE, remote->id);
|
|
} else {
|
|
SPINE_LOG(("WARNING: Device[%i] HT[%i] Trying to close uninitialized remote connection.", work->host_id, work->host_thread));
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef enum {
|
|
POLL_HOST_LOADED,
|
|
POLL_HOST_MISSING,
|
|
POLL_HOST_FAILED
|
|
} poll_host_load_t;
|
|
|
|
/* A device deleted since selection returns quietly. A failed device query
|
|
* fails the device: treating it as an ignored host would still advance its
|
|
* schedule and drop the samples it never collected. */
|
|
static poll_host_load_t load_poll_host(MYSQL *mysql, const poller_queries_t *queries,
|
|
host_t *host, ping_t *ping, const poller_thread_t *work) {
|
|
/* host_id=0 denotes a data source without a device. */
|
|
if (!work->host_id) {
|
|
host->id = 0;
|
|
host->snmp.max_oids = 1;
|
|
host->snmp.session = NULL;
|
|
host->ignore_host = FALSE;
|
|
return POLL_HOST_LOADED;
|
|
}
|
|
MYSQL_RES *result = db_query(mysql, LOCAL, queries->host);
|
|
if (result == NULL) {
|
|
SPINE_LOG(("Device[%i] HT[%i] ERROR: Unable to load the device", work->host_id, work->host_thread));
|
|
return POLL_HOST_FAILED;
|
|
}
|
|
if (spine_count_to_int(mysql_num_rows(result)) != 1) {
|
|
db_free_result(result);
|
|
return POLL_HOST_MISSING;
|
|
}
|
|
MYSQL_ROW row = mysql_fetch_row(result);
|
|
if (row == NULL) {
|
|
SPINE_LOG(("Device[%i] HT[%i] ERROR: MySQL Returned a Null Device Result", host->id, work->host_thread));
|
|
host->ignore_host = TRUE;
|
|
return POLL_HOST_LOADED;
|
|
}
|
|
load_host_metadata(mysql, row, host, work);
|
|
db_free_result(result);
|
|
initialize_host_snmp(host);
|
|
bool include_system_information = refresh_host_availability(mysql, host, ping, work->host_thread);
|
|
if (work->host_thread == 1) {
|
|
persist_host_status(mysql, host, include_system_information && host->ignore_host != TRUE);
|
|
}
|
|
return POLL_HOST_LOADED;
|
|
}
|
|
|
|
/* Publish each partition only after its bookkeeping writes finish. The last
|
|
* partition records polling time and advances the schedule after every earlier
|
|
* write succeeded. Database waits never hold LOCK_THDET. */
|
|
static void complete_poll_host(MYSQL *mysql, poller_queries_t *queries,
|
|
const poller_thread_t *work, const poll_error_context_t *error_context,
|
|
bool output_failed, bool poll_failed, double poll_start) {
|
|
extern poller_thread_t **details;
|
|
int host_id = work->host_id;
|
|
int host_thread = work->host_thread;
|
|
double host_time_double = work->host_time_double;
|
|
int errors = *error_context->errors;
|
|
const char *error_string = error_context->buffer;
|
|
poller_thread_t *device = details[work->device_counter];
|
|
double poll_time = get_time_as_double() - poll_start;
|
|
bool last_partition;
|
|
bool failed;
|
|
bool write_failed = FALSE;
|
|
|
|
SPINE_LOG_DEVICE(host_id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] Total Time: %0.2g Seconds", host_id, host_thread, poll_time));
|
|
|
|
if (!poll_failed && errors > 0) {
|
|
int error_query_len = spine_count_to_int(strlen(error_string) + BUFSIZE);
|
|
char *error_query = (char *)malloc(error_query_len);
|
|
if (error_query == NULL) die("ERROR: Fatal malloc error: poller.c error_query!");
|
|
|
|
snprintf(error_query, error_query_len, "INSERT INTO host_errors (host_id, poller_id, errors, local_data_ids)"
|
|
" VALUES(%i, %i, %i, '%s')"
|
|
" ON DUPLICATE KEY UPDATE"
|
|
" errors = errors + VALUES(errors),"
|
|
" local_data_ids = CONCAT(local_data_ids, ', ', VALUES(local_data_ids))",
|
|
host_id, set.poller.poller_id, errors, error_string);
|
|
|
|
if (!db_insert(mysql, LOCAL, error_query)) write_failed = TRUE;
|
|
|
|
free(error_query);
|
|
}
|
|
|
|
|
|
thread_mutex_lock(LOCK_THDET);
|
|
if (output_failed || write_failed) device->output_failed = TRUE;
|
|
if (poll_failed) device->poll_failed = TRUE;
|
|
failed = device->output_failed || device->poll_failed;
|
|
if (failed) {
|
|
device->complete = FALSE;
|
|
set.exit.exit_code = EXIT_FAILURE;
|
|
}
|
|
device->threads_complete++;
|
|
last_partition = device->threads_complete == device->host_threads;
|
|
thread_mutex_unlock(LOCK_THDET);
|
|
|
|
if (output_failed || poll_failed || write_failed) {
|
|
SPINE_LOG(("ERROR: Device[%i] HT[%i] polling or completion write failed; due items stay eligible for recollection", host_id, host_thread));
|
|
}
|
|
if (!last_partition) return;
|
|
|
|
if (!poll_failed) {
|
|
poll_time = get_time_as_double();
|
|
snprintf(queries->items, BUFSIZE, "UPDATE host SET polling_time = %.3f - %.3f WHERE id = %i", poll_time, host_time_double, host_id);
|
|
if (!db_insert(mysql, LOCAL, queries->items)) write_failed = TRUE;
|
|
|
|
/* Advancing the due-item set is the final write, after every partition
|
|
* and the device polling-time write have reported their outcome. */
|
|
if (!failed && !write_failed && set.poller.active_profiles != 1) {
|
|
SPINE_LOG_MEDIUM(("Device[%i] HT[%i] Updating Poller Items for Next Poll", host_id, host_thread));
|
|
if (!db_insert(mysql, LOCAL, queries->schedule)) write_failed = TRUE;
|
|
}
|
|
}
|
|
|
|
thread_mutex_lock(LOCK_THDET);
|
|
if (write_failed) {
|
|
device->output_failed = TRUE;
|
|
set.exit.exit_code = EXIT_FAILURE;
|
|
}
|
|
device->complete = !device->output_failed && !device->poll_failed;
|
|
thread_mutex_unlock(LOCK_THDET);
|
|
if (write_failed) {
|
|
SPINE_LOG(("ERROR: Device[%i] HT[%i] device completion write failed; the device is not counted as polled", host_id, host_thread));
|
|
}
|
|
}
|
|
|
|
void poll_host(const poller_thread_t *work, int *host_errors) {
|
|
assert(work != NULL && host_errors != NULL);
|
|
int host_id = work->host_id;
|
|
int host_thread = work->host_thread;
|
|
int host_data_ids = work->host_data_ids;
|
|
const char *host_time = work->host_time;
|
|
poller_queries_t queries;
|
|
char *query3 = NULL;
|
|
char *query12 = NULL;
|
|
|
|
|
|
int errors = 0;
|
|
int *buf_errors;
|
|
int *buf_size;
|
|
char *error_string;
|
|
|
|
int num_rows;
|
|
int spike_kill = FALSE;
|
|
int rows_processed = 0;
|
|
bool output_failed = FALSE;
|
|
bool poll_failed = FALSE;
|
|
poll_host_load_t loaded;
|
|
|
|
|
|
double poll_time = get_time_as_double();
|
|
|
|
/* reindex shortcuts to speed polling */
|
|
|
|
|
|
pool_t *local_cnn = NULL;
|
|
pool_t *remote_cnn = NULL;
|
|
|
|
reindex_t *reindex = NULL;
|
|
host_t *host = NULL;
|
|
ping_t *ping = NULL;
|
|
target_t *poller_items = NULL;
|
|
snmp_oids_t *snmp_oids = NULL;
|
|
|
|
if (!(error_string = malloc(DBL_BUFSIZE))) {
|
|
die("ERROR: Fatal malloc error: poller.c error_string!");
|
|
}
|
|
if (!(buf_size = malloc(sizeof(int)))) {
|
|
die("ERROR: Fatal malloc error: poller.c buf_size!");
|
|
}
|
|
if (!(buf_errors = malloc(sizeof(int)))) {
|
|
die("ERROR: Fatal malloc error: poller.c buf_errors!");
|
|
}
|
|
|
|
if (error_string == NULL || buf_size == NULL || buf_errors == NULL) {
|
|
die("ERROR: Fatal malloc error: poller error buffer!");
|
|
}
|
|
/* Reindex assertion failures count errors without naming a data source,
|
|
* so the list can reach the host_errors INSERT before anything is added. */
|
|
error_string[0] = '\0';
|
|
*buf_size = 0;
|
|
*buf_errors = 0;
|
|
const poll_error_context_t error_context = {
|
|
error_string, buf_size, buf_errors, &errors, host_id, host_thread
|
|
};
|
|
|
|
MYSQL *mysql;
|
|
MYSQL *mysqlr = NULL;
|
|
MYSQL_RES *result;
|
|
|
|
local_cnn = db_get_connection(LOCAL);
|
|
if (local_cnn != NULL && set.poller.poller_id > 1 && set.poller.mode == REMOTE_ONLINE) {
|
|
remote_cnn = db_get_connection(REMOTE);
|
|
if (remote_cnn == NULL) {
|
|
db_release_connection(LOCAL, local_cnn->id);
|
|
local_cnn = NULL;
|
|
}
|
|
}
|
|
|
|
if (local_cnn == NULL) {
|
|
SPINE_LOG(("ERROR: Device[%i] HT[%i] No database connection available for polling", host_id, host_thread));
|
|
complete_poll_host(NULL, NULL, work, &error_context, FALSE, TRUE, poll_time);
|
|
SPINE_FREE(error_string);
|
|
SPINE_FREE(buf_size);
|
|
SPINE_FREE(buf_errors);
|
|
mysql_thread_end();
|
|
return;
|
|
}
|
|
|
|
mysql = &local_cnn->mysql;
|
|
if (remote_cnn != NULL) mysqlr = &remote_cnn->mysql;
|
|
|
|
/* allocate host and ping structures with appropriate values */
|
|
if (!(host = (host_t *) malloc(sizeof(host_t)))) {
|
|
die("ERROR: Fatal malloc error: poller.c host struct!");
|
|
}
|
|
|
|
/* set zeros */
|
|
memset(host, 0, sizeof(host_t));
|
|
|
|
if (!(ping = (ping_t *) malloc(sizeof(ping_t)))) {
|
|
die("ERROR: Fatal malloc error: poller.c ping struct!");
|
|
}
|
|
|
|
/* set zeros */
|
|
memset(ping, 0, sizeof(ping_t));
|
|
|
|
if (!(reindex = (reindex_t *) malloc(sizeof(reindex_t)))) {
|
|
die("ERROR: Fatal malloc error: poller.c reindex poll!");
|
|
}
|
|
memset(reindex, 0, sizeof(reindex_t));
|
|
|
|
poller_prepare_queries(&queries, host_id, host_thread, host_data_ids);
|
|
|
|
/* initialize the ping structure variables */
|
|
snprintf(ping->ping_status, 50, "down");
|
|
snprintf(ping->ping_response, SMALL_BUFSIZE, "Ping not performed due to setting.");
|
|
snprintf(ping->snmp_status, 50, "down");
|
|
snprintf(ping->snmp_response, SMALL_BUFSIZE, "SNMP not performed due to setting or ping result");
|
|
|
|
loaded = load_poll_host(mysql, &queries, host, ping, work);
|
|
if (loaded == POLL_HOST_FAILED) {
|
|
complete_poll_host(mysql, &queries, work, &error_context, FALSE, TRUE, poll_time);
|
|
}
|
|
if (loaded != POLL_HOST_LOADED) {
|
|
release_poll_connections(work, local_cnn, remote_cnn);
|
|
SPINE_FREE(host);
|
|
SPINE_FREE(reindex);
|
|
SPINE_FREE(ping);
|
|
SPINE_FREE(error_string);
|
|
SPINE_FREE(buf_size);
|
|
SPINE_FREE(buf_errors);
|
|
mysql_thread_end();
|
|
|
|
return;
|
|
}
|
|
|
|
if (set.availability.ping_only) {
|
|
if (host->snmp.session != NULL) {
|
|
snmp_host_cleanup(host->snmp.session);
|
|
host->snmp.session = NULL;
|
|
}
|
|
SPINE_FREE(host);
|
|
SPINE_FREE(reindex);
|
|
SPINE_FREE(ping);
|
|
SPINE_FREE(error_string);
|
|
SPINE_FREE(buf_size);
|
|
SPINE_FREE(buf_errors);
|
|
|
|
release_poll_connections(work, local_cnn, remote_cnn);
|
|
|
|
mysql_thread_end();
|
|
|
|
return;
|
|
}
|
|
|
|
const reindex_evaluation_t evaluation = {mysql, mysqlr, host, work, &errors, &spike_kill};
|
|
poll_failed = !poll_host_reindex(host, reindex, queries.reindex, &evaluation);
|
|
|
|
/* calculate the number of poller items to poll this cycle */
|
|
num_rows = 0;
|
|
result = NULL;
|
|
if (!poll_failed) {
|
|
result = select_poll_items(mysql, &queries, host, work, &num_rows);
|
|
poll_failed = result == NULL;
|
|
}
|
|
|
|
if (num_rows > 0) {
|
|
poll_item_storage_t storage = load_poll_items(result, host, num_rows);
|
|
poller_items = storage.items;
|
|
snmp_oids = storage.oids;
|
|
|
|
/* log an informative message */
|
|
SPINE_LOG_DEVICE(host_id, POLLER_VERBOSITY_MEDIUM, ("Device[%i] HT[%i] NOTE: There are '%i' Polling Items for this Device", host_id, host_thread, num_rows));
|
|
|
|
snmp_poll_batch_t batch = {.items = poller_items, .oids = snmp_oids, .errors = &error_context};
|
|
rows_processed = collect_poll_items(host, &batch, num_rows, spike_kill);
|
|
|
|
poll_output_buffers_t output = write_poll_results(mysql, mysqlr, &queries, poller_items, rows_processed, host_time);
|
|
query3 = output.output;
|
|
query12 = output.boost;
|
|
output_failed = output.failed;
|
|
|
|
/* cleanup memory and prepare for function exit */
|
|
if (host->snmp.session != NULL) {
|
|
snmp_host_cleanup(host->snmp.session);
|
|
host->snmp.session = NULL;
|
|
}
|
|
|
|
SPINE_FREE(query3);
|
|
if (query12 != NULL) {
|
|
SPINE_FREE(query12);
|
|
}
|
|
|
|
SPINE_FREE(poller_items);
|
|
SPINE_FREE(snmp_oids);
|
|
} else {
|
|
/* free the mysql result */
|
|
db_free_result(result);
|
|
}
|
|
|
|
SPINE_FREE(host);
|
|
SPINE_FREE(reindex);
|
|
SPINE_FREE(ping);
|
|
|
|
complete_poll_host(mysql, &queries, work, &error_context, output_failed, poll_failed, poll_time);
|
|
|
|
release_poll_connections(work, local_cnn, remote_cnn);
|
|
|
|
mysql_thread_end();
|
|
|
|
SPINE_LOG_DEVICE(host_id, POLLER_VERBOSITY_DEBUG, ("Device[%i] HT[%i] DEBUG: HOST COMPLETE: About to Exit Device Polling Thread Function", host_id, host_thread));
|
|
|
|
if (set.logging.spine_log_level == 1) {
|
|
buffer_output_errors(error_string, buf_size, buf_errors, host_id, host_thread, 0, true);
|
|
}
|
|
|
|
SPINE_FREE(error_string);
|
|
SPINE_FREE(buf_size);
|
|
SPINE_FREE(buf_errors);
|
|
|
|
*host_errors = errors;
|
|
}
|
|
|
|
/*! \fn void buffer_output_errors(local_data_id) {
|
|
* \brief buffers output errors and pushes those errors to standard
|
|
* output as required.
|
|
* \param char* buffer - pointer to the output buffer
|
|
* \param int device_id - the device id
|
|
* \param int thread id - the device thread
|
|
* \param int local_data_id - the local data id
|
|
* \param boolean flush - flush any part of buffer
|
|
*/
|
|
void buffer_output_errors(char *error_string, int *buf_size, int *buf_errors, int device_id, int thread_id, int local_data_id, bool flush) {
|
|
int error_len;
|
|
char tbuffer[SMALL_BUFSIZE];
|
|
|
|
if (flush && *buf_errors > 0) {
|
|
SPINE_LOG(("WARNING: Invalid Response(s), Errors[%i] Device[%i] Thread[%i] DS[%s]", *buf_errors, device_id, thread_id, error_string));
|
|
} else if (!flush) {
|
|
snprintf(tbuffer, SMALL_BUFSIZE, *buf_errors > 0 ? ", %i" : "%i", local_data_id);
|
|
error_len = spine_count_to_int(strlen(tbuffer));
|
|
if (*buf_size + error_len >= DBL_BUFSIZE) {
|
|
SPINE_LOG(("WARNING: Invalid Response(s), Errors[%i] Device[%i] Thread[%i] DS[%s]", *buf_errors, device_id, thread_id, error_string));
|
|
*buf_errors = 1;
|
|
*buf_size = snprintf(error_string, DBL_BUFSIZE, "%i", local_data_id);
|
|
} else {
|
|
(*buf_errors)++;
|
|
snprintf(error_string + *buf_size, DBL_BUFSIZE - *buf_size, "%s", tbuffer);
|
|
*buf_size += error_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void poll_system_uptime(host_t *host) {
|
|
char *poll_result;
|
|
unsigned long long ticks;
|
|
// Get the legacy system uptime instance first
|
|
SPINE_LOG_DEVDBG(("DEVDBG: Device[%d] poll_result = snmp_get_allow_fail(host, '.1.3.6.1.2.1.1.3.0');", host->id));
|
|
poll_result = snmp_get_allow_fail(host, ".1.3.6.1.2.1.1.3.0");
|
|
SPINE_LOG_DEVDBG(("DEVDGB: Device[%d] poll_result = snmp_get_allow_fail(host, '.1.3.6.1.2.1.1.3.0'); [complete]", host->id));
|
|
|
|
if (parse_uptime(poll_result, &ticks)) {
|
|
host->system.snmp_sysUpTimeInstance = ticks;
|
|
SPINE_FREE(poll_result);
|
|
|
|
// Attempt to get the more modern version
|
|
SPINE_LOG_DEVDBG(("DEVDBG: Device[%d] poll_result = snmp_get_allow_fail(host, '.1.3.6.1.6.3.10.2.1.3.0');", host->id));
|
|
poll_result = snmp_get_allow_fail(host, ".1.3.6.1.6.3.10.2.1.3.0");
|
|
SPINE_LOG_DEVDBG(("DEVDGB: Device[%d] poll_result = snmp_get_allow_fail(host, '.1.3.6.1.6.3.10.2.1.3.0'); [complete]", host->id));
|
|
|
|
if (parse_uptime(poll_result, &ticks) && ticks <= ULLONG_MAX / 100) {
|
|
host->system.snmp_sysUpTimeInstance = ticks * 100;
|
|
}
|
|
}
|
|
SPINE_FREE(poll_result);
|
|
}
|
|
|
|
static void poll_system_field(host_t *host, MYSQL *mysql, char *oid, char *destination, size_t capacity) {
|
|
SPINE_LOG_DEVDBG(("DEVDBG: Device[%d] requesting system OID %s", host->id, oid));
|
|
char *result = snmp_get_allow_fail(host, oid);
|
|
if (result != NULL) db_escape(mysql, destination, (int)capacity, result);
|
|
SPINE_FREE(result);
|
|
}
|
|
|
|
void get_system_information(host_t *host, MYSQL *mysql, int system) {
|
|
SPINE_LOG_MEDIUM(("Device[%d] Checking for System Information Update", host->id));
|
|
bool full = set.snmp.mibs || system;
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_MEDIUM, ("Device[%d] Updating %s System Information Table", host->id, full ? "Full" : "Short"));
|
|
if (full) {
|
|
poll_system_field(host, mysql, ".1.3.6.1.2.1.1.1.0", host->system.snmp_sysDescr, sizeof(host->system.snmp_sysDescr));
|
|
poll_system_field(host, mysql, ".1.3.6.1.2.1.1.2.0", host->system.snmp_sysObjectID, sizeof(host->system.snmp_sysObjectID));
|
|
}
|
|
poll_system_uptime(host);
|
|
if (full) {
|
|
poll_system_field(host, mysql, ".1.3.6.1.2.1.1.4.0", host->system.snmp_sysContact, sizeof(host->system.snmp_sysContact));
|
|
poll_system_field(host, mysql, ".1.3.6.1.2.1.1.5.0", host->system.snmp_sysName, sizeof(host->system.snmp_sysName));
|
|
poll_system_field(host, mysql, ".1.3.6.1.2.1.1.6.0", host->system.snmp_sysLocation, sizeof(host->system.snmp_sysLocation));
|
|
}
|
|
}
|
|
|
|
static int acquire_script_permit(const host_t *host) {
|
|
if (set.php.script_timeout <= 0) return EINVAL;
|
|
/* Preserve the existing retry budget without signed multiplication overflow. */
|
|
uint64_t attempts = (uint64_t)set.php.script_timeout * 15;
|
|
int error = EAGAIN;
|
|
for (uint64_t retry = 1; retry < attempts; retry++) {
|
|
error = spine_permits_try_acquire(&available_scripts);
|
|
if (error == 0) return 0;
|
|
if (error == EAGAIN || error == EWOULDBLOCK) {
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_DEVDBG, ("Device[%i] DEBUG: Pausing as unable to obtain a script execution lock", host->id));
|
|
} else {
|
|
SPINE_LOG_DEVICE(host->id, POLLER_VERBOSITY_DEVDBG, ("Device[%i] DEBUG: Pausing as error %d whilst obtaining a script execution lock", host->id, error));
|
|
}
|
|
spine_sleep_usec(10000);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
typedef struct {
|
|
const host_t *host;
|
|
const char *command;
|
|
int id;
|
|
const char *type;
|
|
} script_result_context_t;
|
|
|
|
/* Return whether the legacy popen backend may close without blocking on a
|
|
* timed-out command; nft_popen owns and reaps its registered child instead. */
|
|
static bool read_script_result(const script_result_context_t *context, int fd, double deadline, char *result) {
|
|
int ready = spine_wait_readable(fd, deadline);
|
|
if (ready < 0) {
|
|
switch (errno) {
|
|
case EBADF:
|
|
SPINE_LOG(("Device[%i] ERROR: One or more of the file descriptor sets specified a file descriptor that is not a valid open file descriptor.", context->host->id));
|
|
break;
|
|
case EINVAL:
|
|
SPINE_LOG(("Device[%i] ERROR: Possible invalid timeout specified in select() statement.", context->host->id));
|
|
break;
|
|
default:
|
|
SPINE_LOG(("Device[%i] ERROR: The script/command select() failed", context->host->id));
|
|
break;
|
|
}
|
|
SET_UNDEFINED(result);
|
|
return FALSE;
|
|
}
|
|
if (ready == 0) {
|
|
SPINE_LOG_MEDIUM(("Device[%i] ERROR: The NIFTY POPEN timed out", context->host->id));
|
|
int pid = nft_pchild(fd);
|
|
if (pid > 1) {
|
|
/* nft_popen() made the script a group leader; take its descendants too. */
|
|
kill(-pid, SIGKILL);
|
|
} else {
|
|
SPINE_LOG(("Device[%i] ERROR: Unable to find the timed-out POPEN child", context->host->id));
|
|
}
|
|
SET_UNDEFINED(result);
|
|
return FALSE;
|
|
}
|
|
/* Use the full capacity to detect truncation without waiting for EOF. */
|
|
ssize_t bytes = read(fd, result, RESULTS_BUFFER);
|
|
if (bytes > 0 && bytes < RESULTS_BUFFER && memchr(result, '\0', (size_t)bytes) == NULL) {
|
|
result[bytes] = '\0';
|
|
} else {
|
|
char script[SMALL_BUFSIZE];
|
|
|
|
php_command_script(context->command, script, sizeof(script));
|
|
if (STRIMATCH(context->type, "DS")) {
|
|
SPINE_LOG(("Device[%i] DS[%i] ERROR: Empty result [%s]: '%s'", context->host->id, context->id, context->host->hostname, script));
|
|
} else {
|
|
SPINE_LOG(("Device[%i] DQ[%i] ERROR: Empty result [%s]: '%s'", context->host->id, context->id, context->host->hostname, script));
|
|
}
|
|
SET_UNDEFINED(result);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*! \fn char *exec_poll(host_t *current_host, char *command, int id, char *type)
|
|
* \brief polls a host using a script
|
|
* \param current_host a pointer to the current host structure
|
|
* \param command the command to be executed
|
|
* \param id either the local_data_id or the data_query_id
|
|
*
|
|
* This function will poll a specific host using the script pointed to by
|
|
* the command variable.
|
|
*
|
|
* \return a pointer to a character buffer containing the result.
|
|
*
|
|
*/
|
|
/* WARNING: command is passed to /bin/sh -c (via nft_popen) without shell escaping.
|
|
* The caller MUST ensure command originates from a trusted source
|
|
* (the Cacti database). Do not pass user-controlled input directly. */
|
|
char *exec_poll(host_t *current_host, char *command, int id, const char *type) {
|
|
int cmd_fd;
|
|
double deadline;
|
|
char *proc_command;
|
|
char *result_string;
|
|
|
|
/* compensate for back slashes in arguments */
|
|
#if defined(__CYGWIN__)
|
|
proc_command = add_slashes(command);
|
|
#else
|
|
proc_command = command;
|
|
#endif
|
|
|
|
if (!(result_string = (char *) malloc(RESULTS_BUFFER))) {
|
|
die("ERROR: Fatal malloc error: poller.c exec_poll!");
|
|
}
|
|
|
|
/* set zeros */
|
|
memset(result_string, 0, RESULTS_BUFFER);
|
|
|
|
|
|
/* don't run too many scripts, operating systems do not like that. */
|
|
int sem_err;
|
|
int needs_cleanup = 0;
|
|
|
|
/* used for checking executable status */
|
|
char executable[BUFSIZE];
|
|
char *saveptr = NULL;
|
|
|
|
pthread_cleanup_push(child_cleanup_script, NULL);
|
|
|
|
sem_err = acquire_script_permit(current_host);
|
|
|
|
if (sem_err) {
|
|
SPINE_LOG(("ERROR: Device[%i]: Failed to obtain a script execution lock (error %d)", current_host->id, sem_err));
|
|
SET_UNDEFINED(result_string);
|
|
#if defined(__CYGWIN__)
|
|
SPINE_FREE(proc_command);
|
|
#endif
|
|
} else {
|
|
/* Mark for cleanup */
|
|
needs_cleanup = 1;
|
|
|
|
/* record start time */
|
|
deadline = spine_monotonic_time() + set.php.script_timeout;
|
|
|
|
/* peel the executable from the command */
|
|
saveptr = proc_command;
|
|
spine_snprintf(executable, sizeof(executable), "%s", proc_command);
|
|
strtok_r(executable, " ", &saveptr);
|
|
|
|
/* cheesy little hack to add /usr/bin/ if its not included */
|
|
if (strstr(executable, "/") == NULL) {
|
|
saveptr = proc_command;
|
|
spine_snprintf(executable, sizeof(executable), "/usr/bin/%s", proc_command);
|
|
strtok_r(executable, " ", &saveptr);
|
|
}
|
|
|
|
/* executable is the first token; the arguments can carry credentials. */
|
|
SPINE_LOG_DEBUG(("The executable is '%s'", executable));
|
|
|
|
if (access(executable, X_OK | F_OK) != -1) {
|
|
cmd_fd = nft_popen(proc_command, "r");
|
|
SPINE_LOG_DEVICE(current_host->id, POLLER_VERBOSITY_DEBUG, ("Device[%i] DEBUG: The NIFTY POPEN returned the following File Descriptor %i", current_host->id, cmd_fd));
|
|
|
|
if (cmd_fd >= 0) {
|
|
const script_result_context_t context = {current_host, command, id, type};
|
|
(void)read_script_result(&context, cmd_fd, deadline, result_string);
|
|
|
|
/* close pipe */
|
|
nft_pclose(cmd_fd);
|
|
} else {
|
|
SPINE_LOG(("Device[%i] ERROR: Problem executing POPEN [%s]: '%s'", current_host->id, current_host->hostname, executable));
|
|
SET_UNDEFINED(result_string);
|
|
}
|
|
} else {
|
|
SPINE_LOG(("Device[%i] ERROR: Problem executing POPEN. File '%s' does not exist or is not executable.", current_host->id, executable));
|
|
SET_UNDEFINED(result_string);
|
|
}
|
|
|
|
#if defined(__CYGWIN__)
|
|
SPINE_FREE(proc_command);
|
|
#endif
|
|
}
|
|
|
|
/* reduce the active script count */
|
|
pthread_cleanup_pop(needs_cleanup);
|
|
|
|
return result_string;
|
|
}
|