Files
cacti/cactid.php
Thomas Vincent ac0b2c9515 Fix confirmed poller, database, automation, and UI defects (#7861)
* issue#7024: do not delete still-running poller_time rows on cycle start

The overrun path deleted every poller_time row for the poller, including the
in-flight ones it had just counted, so overrunning processes lost their
tracking. Restrict the delete to completed rows.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7028: restore identifier and connection safety in the db layer

_db_replace interpolated caller-supplied column keys as backtick identifiers
without escaping, and db_commit_transaction checked @@in_transaction on the
default connection rather than the one passed in. Both were correct on 1.2.x
and regressed on develop.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7341: size the CSV export maxrows to the requested span

The export hardcoded --maxrows=10000, truncating wide date ranges regardless
of the data source profile step.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7421: offset automation_get_next_host through the 32-bit integer

The hand-rolled octet walk subtracted the wrong magnitude per octet and
emitted out-of-range octets for ranges wider than a /16.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7030: skip the poller_output ENGINE=MEMORY conversion when already applied

The output-table refresh ran ALTER TABLE poller_output ENGINE=MEMORY every
cycle even though the swapped-in copy already inherits the engine, adding a
metadata-locking DDL to the hot path. Probe the current engine and convert
only when needed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7342: prefer snmpEngineTime only when it covers system uptime

update_system_mibs preferred snmpEngineTime (x100) over sysUpTime whenever it
was present, so an SNMP engine that restarts independently of the system (for
example OpenBSD snmpd) made the stored uptime drop, which reads as a reboot
and forces a perpetual reindex with mass 'U' results. Capture both values and
use snmpEngineTime only when it is at least the system uptime, which still
covers the 32-bit sysUpTime wrap.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7403: scale lmsensors readings in the get path like the query path

The get path returned the raw milli-unit SNMP counter while the query path
divided voltage and temperature by 1000 (and corrected negative voltages), so
a get gave values 1000x too large. Apply the same scaling before returning.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7029: propagate the reconnected handle back to the caller

db_check_reconnect() closed the dead connection, opened a new one, and
returned true without giving the new handle back, so the db_execute_prepared
retry ran its query against the connection that was just closed. Take $db_conn
by reference and assign the fresh handle; the one caller that passed a literal
false now passes a variable.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: refresh sink inventory baseline

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* test: refresh architectural hotspots baseline

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7474: return valid after correcting the theme to selected_theme

is_valid_theme() corrected an invalid theme to selected_theme but left $valid
false, so callers saw the theme as invalid even though it had been fixed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7462: allow SELECT aliases as sortable automation columns

site_name and host_template_name are aliases (s.name AS site_name), valid in
ORDER BY but not real columns, so api_automation_column_exists() rejected them
and the site-name sort silently failed.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7456: bind the OS value in the automation OS filter

The OS filter built 'os = ?' but bound the network value, so filtering by OS
matched on the wrong input.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7491: filter host_template_id against its own column

html_make_device_where() compared h.location to the host_template_id value
instead of h.host_template_id.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7479: apply the hostname and description filters in get_hosts

The REST get_hosts() honored host_id/poller/site/template/location but ignored
the hostname and description filters its callers pass; add them as bound LIKE.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* issue#7475: authorize a direct client-IP poller match before the hostname guard

A poller configured by IP with no hostname-based pollers was rejected because
the empty-hostname guard ran before the direct-match check, even though the
client address exactly matched a poller's configured address.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* Guard two divisors that PHP 8 makes fatal

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: consolidate functional fix changelog

* test: cover consolidated functional fixes

* docs: identify consolidated functional fixes

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-01 21:01:28 -07:00

325 lines
7.5 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
/*
+-------------------------------------------------------------------------+
| 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 General Public License |
| as published by the Free Software Foundation; either version 2 |
| 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 General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(ticks = 100);
}
ini_set('output_buffering', 'Off');
// let's report all errors
error_reporting(E_ALL);
// allow the script to hang around waiting for connections.
set_time_limit(0);
// we do not need so much memory
ini_set('memory_limit', '-1');
ini_set('max_execution_time', '0');
// default values
$hostname = gethostname();
$debug = false;
$foreground = false;
$logrecon = false;
chdir(__DIR__);
require_once('./include/cli_check.php');
// install signal handlers for Linux/UNIX only
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');
}
global $hostname, $debug;
// process calling arguments
$options = get_options();
if (isset($options['debug'])) {
$debug = true;
}
if (isset($options['foreground'])) {
$foreground = true;
}
// Set the frequency of data collection dynamically
// before losing our database connection after fork.
// We would not have to do this is PDO supported
// connection cloning as does Perl.
$frequency = read_config_option('cron_interval', true);
// redirect standard error to dev/null
if (DIRECTORY_SEPARATOR != '\\') {
fclose(STDERR);
$STDERR = fopen('/dev/null', 'wb');
// check if cactid daemon is already running
exec('pgrep -a php | grep cactid.php', $output);
if (sizeof($output) >= 2) {
print 'The Cacti Daemon is still running' . PHP_EOL;
return;
}
} else {
fclose(STDERR);
$STDERR = fopen('null', 'wb');
}
print 'Starting Cacti Daemon ... ';
if (!$foreground) {
if (function_exists('pcntl_fork')) {
// Close the database connection
db_close();
// Fork the current process to daemonize
$pid = pcntl_fork();
if ($pid == -1) {
// Something went wrong
print '[FAILED]' . PHP_EOL;
exit(1);
}
if ($pid == 0) {
// We are the child
} else {
cacti_log('NOTE: Cacti Daemon PID[' . getmypid() . '] Started on Device[' . gethostname() . ']');
admin_email(__('Cacti System Notice'), __('Notice: Cacti Daemon PID[' . getmypid() . '] Started on Device[' . gethostname() . ']', true, 'CACTID'));
print '[OK]' . PHP_EOL;
exit(0);
}
} else {
// On Windows we run in foreground mode
print '[OK]' . PHP_EOL . '[NOTE] This system does not support forking.' . PHP_EOL;
}
} else {
print '[OK]' . PHP_EOL . '[NOTE] The Cacti Daemon is running in foreground mode.' . PHP_EOL;
}
sleep(2);
while (true) {
wait_for_start($frequency);
$reconnect_conn = false;
db_check_reconnect($reconnect_conn, $logrecon);
run_poller();
// Force Cacti to check the service start frequency dynamically
$frequency = -1;
$logrecon = true;
}
/**
* sig_handler - provides a generic means to catch exceptions to the Cacti log.
*
* @param int $signo The signal that was thrown by the interface.
*
* @return void
*/
function sig_handler(int $signo) : void {
global $hostname;
switch ($signo) {
case SIGTERM:
case SIGINT:
cacti_log('WARNING: Cacti Daemon PID[' . getmypid() . '] Terminated on Device[' . gethostname() . ']', true, 'CACTID');
admin_email(__('Cacti System Warning'), __('WARNING: Cacti Daemon PID[' . getmypid() . '] Terminated on Device[' . gethostname() . ']', true, 'CACTID'));
exit(1);
default:
// ignore all other signals
}
}
/**
* wait_for_start
*
* @param int $frequency
*
* @return int
*/
function wait_for_start(int $frequency = -1) : int {
$prev_time = -1;
$i = 0;
while (true) {
if ($frequency <= 0) {
$frequency = read_config_option('cron_interval', true);
if (empty($frequency)) {
$frequency = 300;
}
}
$now = time();
$offset = $now % $frequency;
if ($i % 5 == 0) {
debug('PrevOS: ' . $prev_time . ', CurrOS: ' . $offset . ', Freq: ' . $frequency);
}
if ($prev_time > 0) {
if ($offset < $prev_time) {
debug('Time to Run Poller');
break;
}
}
$prev_time = $offset;
$i++;
sleep(1);
}
return $frequency;
}
/**
* run_poller
*
* @return void
*/
function run_poller() : void {
global $debug;
debug('Cacti Data Collector');
$php_binary = poller_php_binary(read_config_option('path_php_binary'));
$command = poller_cactid_arguments(CACTI_PATH_BASE, $debug);
debug('Command Line is: ' . implode(' ', array_merge([$php_binary], $command)));
poller_enable_child_reaping();
exec_background_process($php_binary, $command);
}
/**
* get_options
*
* @return array
*/
function get_options() : array {
$parms = $_SERVER['argv'];
array_shift($parms);
$options = [];
if (sizeof($parms)) {
$shortopts = 'VvHh';
$longopts = [
'foreground',
'debug',
'version',
'help'
];
$options = getopt($shortopts, $longopts);
foreach ($options as $arg => $value) {
switch($arg) {
case 'foreground':
case 'debug':
break;
case 'version':
case 'V':
case 'v':
display_version();
exit(0);
case 'help':
case 'H':
case 'h':
display_help();
exit(0);
default:
print "ERROR: Invalid Argument: ($arg)" . PHP_EOL . PHP_EOL;
display_help();
exit(1);
}
}
}
return $options;
}
/**
* debug
*
* @param string $string
*
* @return void
*/
function debug(string $string) : void {
global $debug;
if ($debug) {
$output = 'DEBUG: ' . trim($string);
print $output . PHP_EOL;
}
}
/**
* display_version
*
* @return void
*/
function display_version() : void {
$version = get_cacti_cli_version();
print 'The Cacti Daemon (cactid), Version ' . $version . ', ' . COPYRIGHT_YEARS . PHP_EOL;
}
/**
* display_help - displays the usage of the function
*
* @return void
*/
function display_help() : void {
display_version();
print PHP_EOL . 'usage: cactid.php [ --foreground ] [ --debug ]' . PHP_EOL . PHP_EOL;
print 'Daemon for Cacti data collection, otherwise known as cactid.' . PHP_EOL;
print 'optional:' . PHP_EOL;
print ' --foreground Run cactid in foreground mode, otherwise this is a forking daemon.' . PHP_EOL;
print ' --debug Used for debugging in --foreground mode.' . PHP_EOL;
}