#!/usr/bin/php
<?php
/*
 +-------------------------------------------------------------------------+
 | Copyright (C) 2004-2025 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/                                                   |
 +-------------------------------------------------------------------------+
*/

/*
# description: Start Flow-Capture
# chkconfig: 2345 95 00
*/

/* modify this line if you are using an init.d operating system.  Ensure 
 * that it matches your apache account.  Some use 'apache' others use
 * www-user or www-data.
 */
$webuser = 'apache';

if (function_exists('pcntl_async_signals')) {
    pcntl_async_signals(true);
} else {
    declare(ticks = 100);
}

ini_set('output_buffering', 'Off');
ini_set('max_runtime', '-1');
ini_set('memory_limit', '-1');

/**
 * determine the cacti base directory
 */
$cacti_base = get_cacti_base();

if ($cacti_base === false) {
	print 'FATAL: Cacti install base directory can not be determined' . PHP_EOL;
	exit(1);
}

include_once($cacti_base . '/include/cli_check.php');
include_once($cacti_base . '/lib/poller.php');
include_once($cacti_base . '/plugins/flowview/setup.php');
include_once($cacti_base . '/plugins/flowview/functions.php');

$shortopts = 'VvHh';
$longopts = array(
	'systemd',
	'version',
	'help',
);

global $options, $reload;

$options = getopt($shortopts, $longopts);
$reload  = false;

if (isset($options['systemd'])) {
	start(true);
} else {
	if (isset($_SERVER['argv'][1])) {
		switch (strtolower($_SERVER['argv'][1])) {
		case 'start':
			start(false);
			break;
		case 'stop':
			stop('initd');
			break;
		case 'restart':
			restart();
			break;
		case 'reload':
			reload();
			break;
		default:
			print 'Usage: /etc/init.d/flow-capture {start|stop|restart}' . PHP_EOL;
			break;
		}
	}
}

/**
 * Types include
 *
 * master  - the main process launched from the Cacti main poller and will launch child processes
 * child   - a child of the master process from the 'master'
 *
 */

/* install signal handlers for UNIX only */
if (function_exists('pcntl_signal')) {
	pcntl_signal(SIGTERM, 'sig_handler');
	pcntl_signal(SIGINT, 'sig_handler');
	pcntl_signal(SIGHUP, 'sig_handler');
}

function get_cacti_base() {
	$cacti_bases = array(
		'/var/www/html/cacti',
		'/usr/local/share/cacti/site',
		'/usr/local/share/cacti',
		'/usr/share/cacti',
		'/var/www/html',
		'/opt/cacti'
	);

	/* Lets check for the poller in the standard locations */
	foreach($cacti_bases as $cacti_base) {
		if ((file_exists($cacti_base) || is_dir($cacti_base)) && file_exists("$cacti_base/poller.php")) {
			return $cacti_base;
		}
	} 

	/* Lets check if our flowview service directory is in the cacti directory */
	$dir = implode('/', array_slice(explode('/', dirname(__FILE__)), 0, -3));
	if (file_exists("$dir/poller.php")) {
		return $dir;
	}

	return false;
}

function start($systemd = true) {
	global $config, $cacti_base, $webuser;;

	if (!register_process_start('flowview', 'master', $config['poller_id'], 315360000)) {
		exit(0);
	}

	if (!$systemd) {
		posix_seteuid(posix_getpwnam($webuser)['uid']);
		posix_setegid(posix_getpwnam($webuser)['gid']);
	}

	print 'NOTE: Starting Flow Collection' . PHP_EOL;

	flowview_connect();

	$devices = flowview_db_fetch_assoc('SELECT * FROM plugin_flowview_devices');

	if (!empty($devices)) {
		foreach ($devices as $device) {
			$php_binary = read_config_option('path_php_binary');
			print "NOTE: Launching cacti-flow-capture as '" . $cacti_base . '/plugins/flowview/flow_collector.php --listener-id=' . $device['id'] . "'" . PHP_EOL;

			exec_background($php_binary, ' -q ' . $cacti_base . '/plugins/flowview/flow_collector.php --listener-id=' . $device['id']);
		}
	} else {
		print 'NOTE: No Flow Capture Listeners configured' . PHP_EOL;
	}

	// Just hang on for a while
	if ($systemd) {
		while (true) {
			sleep(300);

			db_check_reconnect(false, true);

			heartbeat_process('flowview', 'master', $config['poller_id']);

			check_processes();
		}
	}
}

function check_processes() {
	global $cacti_base;

	print 'NOTE: Checking for new or gone listeners' . PHP_EOL;

	flowview_connect();

	$devices    = flowview_db_fetch_assoc('SELECT * FROM plugin_flowview_devices');
	$php_binary = read_config_option('path_php_binary');

	if (!empty($devices)) {
		foreach($devices as $device) {
			$start = false;

			$registered = db_fetch_cell_prepared('SELECT pid 
				FROM processes 
				WHERE tasktype = "flowview" 
				AND taskname = ?',
				array('child_' . $device['id']));

			if ($registered > 0) {
				/* if true, the process is running */
				if (!posix_kill($registered, 0)) {
					$start = true;
				}
			} else {
				$start = true;
			}

			if ($start) {
				cacti_log(sprintf("Detecting a new or crashed flow collector with name '%s' with the id:%s.  Relaunching!", $device['name'], $device['id']), false, 'FLOWVIEW');

				$php_binary = read_config_option('path_php_binary');

				print "NOTE: Launching crashed or new cacti-flow-capture as '" . $cacti_base . '/plugins/flowview/flow_collector.php --listener-id=' . $device['id'] . "'" . PHP_EOL;

				exec_background($php_binary, ' -q ' . $cacti_base . '/plugins/flowview/flow_collector.php --listener-id=' . $device['id']);
			}
		}
	}
}

function stop() {
	global $config;

	print 'NOTE: Stopping Flow Collection' . PHP_EOL;

	flowview_connect();

	$devices    = flowview_db_fetch_assoc('SELECT * FROM plugin_flowview_devices');
	$php_binary = read_config_option('path_php_binary');

	if (cacti_sizeof($devices)) {
		shell_exec('ps ax | grep \'flow_collector.php\' | grep -v \'grep\' | awk \'{ print $1 }\' | xargs kill');
	}

	unregister_process('flowview', 'master', $config['poller_id']);
}

function restart() {
	stop();
	start(false);
}

function reload() {
	global $config, $reload;

	$reload = true;

	$running = db_fetch_assoc_prepared('SELECT * 
		FROM processes 
		WHERE tasktype = "flowview" 
		AND taskname != "master" 
		AND taskid = ?', 
		array($config['poller_id']));

	if (cacti_sizeof($running)) {
		foreach($running as $task) {
			cacti_log(sprintf('Signaling Process %s to reload its configuration!', $task['taskname']), false, 'FLOWVIEW');
			posix_kill($task['pid'], SIGHUP);
		}
	}
}

/**
 * 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($signo) {
	global $config;

	switch ($signo) {
		case SIGHUP:
			cacti_log('NOTE: Flow capture received request to reload!', false, 'FLOWVIEW');

			break;
		case SIGTERM:
		case SIGINT:
			cacti_log("WARNING: Flowview main collector was terminated by user", false, 'FLOWVIEW');

			flowview_kill_running_processes();
			unregister_process('flowview', 'master', $config['poller_id'], getmypid());

			exit(1);
			break;
		default:
			/* ignore all other signals */
	}
}

/**
 * flowview_kill_running_processes - this function is part of an interrupt
 *   handler to kill children processes when the parent is killed
 *
 * @return (void)
 */
function flowview_kill_running_processes() {
	global $config;

	$processes = db_fetch_assoc_prepared('SELECT *
		FROM processes
		WHERE tasktype = "flowview"
		AND taskname LIKE "child_%"
		AND taskid = ?
		AND pid != ?',
		array($config['poller_id'], getmypid()));

	if (cacti_sizeof($processes)) {
		foreach($processes as $p) {
			cacti_log(sprintf('WARNING: Killing FlowView Child: %s PID: %d due to another due to signal or overrun.', ucfirst($p['taskname']), $p['pid']), false, 'FLOWVIEW');

			posix_kill($p['pid'], SIGTERM);

			unregister_process($p['tasktype'], $p['taskname'], $p['taskid'], $p['pid']);
		}
	}
}
