/*
+-------------------------------------------------------------------------+
| Copyright (C) 2002-2006 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 |
| |
+-------------------------------------------------------------------------+
| cactid: 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, implimentation details) |
+-------------------------------------------------------------------------+
| - Cacti - http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
#include "common.h"
#include "cactid.h"
/*! \fn char *php_cmd(const char *php_command, int php_process)
* \brief calls the script server and executes a script command
* \param php_command the formatted php script server command
* \param php_process the php script server process to call
*
* This function is called directly by the Cactid poller when a script server
* request has been initiated for a host. It will place the PHP Script Server
* command on it's output pipe and then wait the pre-defined timeout period for
* a response on the PHP Script Servers output pipe.
*
* \return pointer to the string results. Must be freed by the parent.
*
*/
char *php_cmd(const char *php_command, int php_process) {
char *result_string;
char command[BUFSIZE];
int write_status;
assert(php_command != 0);
/* pad command with CR-LF */
snprintf(command, sizeof(command)-1, "%s\r\n", php_command);
/* place lock around mutex */
switch (php_process) {
case 0: thread_mutex_lock(LOCK_PHP_PROC_0); break;
case 1: thread_mutex_lock(LOCK_PHP_PROC_1); break;
case 2: thread_mutex_lock(LOCK_PHP_PROC_2); break;
case 3: thread_mutex_lock(LOCK_PHP_PROC_3); break;
case 4: thread_mutex_lock(LOCK_PHP_PROC_4); break;
case 5: thread_mutex_lock(LOCK_PHP_PROC_5); break;
case 6: thread_mutex_lock(LOCK_PHP_PROC_6); break;
case 7: thread_mutex_lock(LOCK_PHP_PROC_7); break;
case 8: thread_mutex_lock(LOCK_PHP_PROC_8); break;
case 9: thread_mutex_lock(LOCK_PHP_PROC_9); break;
}
/* send command to the script server */
write_status = write(php_processes[php_process].php_write_fd, command, strlen(command));
/* if write status is <= 0 then the script server may be hung */
if (write_status <= 0) {
result_string = strdup("U");
CACTID_LOG(("ERROR: SS[%i] PHP Script Server communications lost.\n", php_process));
php_close(php_process);
}else{
/* read the result from the php_command */
result_string = php_readpipe(php_process);
}
/* unlock around php process */
switch (php_process) {
case 0: thread_mutex_unlock(LOCK_PHP_PROC_0); break;
case 1: thread_mutex_unlock(LOCK_PHP_PROC_1); break;
case 2: thread_mutex_unlock(LOCK_PHP_PROC_2); break;
case 3: thread_mutex_unlock(LOCK_PHP_PROC_3); break;
case 4: thread_mutex_unlock(LOCK_PHP_PROC_4); break;
case 5: thread_mutex_unlock(LOCK_PHP_PROC_5); break;
case 6: thread_mutex_unlock(LOCK_PHP_PROC_6); break;
case 7: thread_mutex_unlock(LOCK_PHP_PROC_7); break;
case 8: thread_mutex_unlock(LOCK_PHP_PROC_8); break;
case 9: thread_mutex_unlock(LOCK_PHP_PROC_9); break;
}
return result_string;
}
/*! \fn in php_get_process()
* \brief returns the next php script server process to utilize
*
* This very simple function simply returns the next PHP Script Server
* process id to poll using a round robin algorithm.
*
* \return the integer number of the next script server to use
*
*/
int php_get_process(void) {
int i;
thread_mutex_lock(LOCK_PHP);
if (set.php_current_server >= set.php_servers) {
set.php_current_server = 0;
}
i = set.php_current_server;
set.php_current_server++;
thread_mutex_unlock(LOCK_PHP);
return i;
}
/*! \fn char *php_readpipe(int php_process)
* \brief read a line from a PHP Script Server process
* \param php_process the PHP Script Server process to obtain output from
*
* This function will read the output pipe from the PHP Script Server process
* and return that string to the Cactid thread requesting the output. If for
* some reason the PHP Script Server process does not respond in time, it will
* be closed using the php_close function, then restarted.
*
* \return a string pointer to the PHP Script Server response
*/
char *php_readpipe(int php_process) {
fd_set fds;
int rescode, numfds;
struct timeval timeout;
double begin_time = 0;
double end_time = 0;
char *result_string;
if (!(result_string = (char *)malloc(BUFSIZE))) {
die("ERROR: Fatal malloc error: php.c php_readpipe!\n");
}
memset(result_string, 0, BUFSIZE);
/* record start time */
begin_time = get_time_as_double();
/* initialize file descriptors to review for input/output */
FD_ZERO(&fds);
FD_SET(php_processes[php_process].php_read_fd,&fds);
numfds = php_processes[php_process].php_read_fd + 1;
/* establish timeout value for the PHP script server to respond */
timeout.tv_sec = set.script_timeout;
timeout.tv_usec = 0;
/* check to see which pipe talked and take action
* should only be the READ pipe */
retry:
switch (select(numfds, &fds, NULL, NULL, &timeout)) {
case -1:
switch (errno) {
case EBADF:
CACTID_LOG(("ERROR: SS[%i] An invalid file descriptor was given in one of the sets.\n", php_process));
break;
case EAGAIN:
case EINTR:
#ifndef SOLAR_THREAD
/* take a moment */
usleep(2000);
#endif
/* record end time */
end_time = get_time_as_double();
/* re-establish new timeout value */
timeout.tv_sec = rint(floor(set.script_timeout-(end_time-begin_time)));
timeout.tv_usec = rint((set.script_timeout-(end_time-begin_time)-timeout.tv_sec)*1000000);
if ((end_time - begin_time) < set.script_timeout) {
goto retry;
}else{
CACTID_LOG(("WARNING: SS[%i] The Script Server script timed out while processing EINTR's.\n", php_process));
}
break;
case EINVAL:
CACTID_LOG(("ERROR: SS[%i] N is negative or the value contained within timeout is invalid.\n", php_process));
break;
case ENOMEM:
CACTID_LOG(("ERROR: SS[%i] Select was unable to allocate memory for internal tables.\n", php_process));
break;
default:
CACTID_LOG(("ERROR: SS[%i] Unknown fatal select() error\n", php_process));
break;
}
SET_UNDEFINED(result_string);
/* kill script server because it is misbehaving */
php_close(php_process);
php_init(php_process);
break;
case 0:
CACTID_LOG(("WARNING: SS[%i] The PHP Script Server did not respond in time and will therefore be restarted\n", php_process));
SET_UNDEFINED(result_string);
/* kill script server because it is misbehaving */
php_close(php_process);
php_init(php_process);
break;
default:
rescode = read(php_processes[php_process].php_read_fd, result_string, BUFSIZE);
if (rescode == 0) {
SET_UNDEFINED(result_string);
}
php_processes[php_process].php_state = PHP_READY;
}
return result_string;
}
/*! \fn int php_init(int php_process)
* \brief initialize either a specific PHP Script Server or all of them.
* \param php_process the process number to start or PHP_INIT
*
* This function will either start an individual PHP Script Server process
* or all of them if the input parameter is the PHP_INIT constant. The function
* will check the status of the process to verify that it is ready to process
* scripts as well.
*
* \return TRUE if the PHP Script Server is know running or FALSE otherwise
*/
int php_init(int php_process) {
int cacti2php_pdes[2];
int php2cacti_pdes[2];
pid_t pid;
char poller_id[11];
char *argv[5];
int cancel_state;
char *result_string = 0;
int num_processes;
int i;
/* special code to start all PHP Servers */
if (php_process == PHP_INIT) {
num_processes = set.php_servers;
}else{
num_processes = 1;
}
for (i=0; i < num_processes; i++) {
CACTID_LOG_DEBUG(("DEBUG: SS[%i] PHP Script Server Routine Starting\n", i));
/* create the output pipes from Cactid to php*/
if (pipe(cacti2php_pdes) < 0) {
CACTID_LOG(("ERROR: SS[%i] Could not allocate php server pipes\n", i));
return FALSE;
}
/* create the input pipes from php to Cactid */
if (pipe(php2cacti_pdes) < 0) {
CACTID_LOG(("ERROR: SS[%i] Could not allocate php server pipes\n", i));
return FALSE;
}
/* disable thread cancellation from this point forward. */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
/* establish arguments for script server execution */
argv[0] = set.path_php;
argv[1] = set.path_php_server;
argv[2] = "cactid";
snprintf(poller_id, sizeof(poller_id)-1, "%d", set.poller_id);
argv[3] = poller_id;
argv[4] = NULL;
/* fork a child process */
CACTID_LOG_DEBUG(("DEBUG: SS[%i] PHP Script Server About to FORK Child Process\n", i));
pid = fork();
/* check the pid status and process as required */
switch (pid) {
case -1: /* ERROR: Could not fork() */
close(php2cacti_pdes[0]);
close(php2cacti_pdes[1]);
close(cacti2php_pdes[0]);
close(cacti2php_pdes[1]);
CACTID_LOG(("ERROR: SS[%i] Cound not fork PHP Script Server\n", i));
pthread_setcancelstate(cancel_state, NULL);
return FALSE;
/* NOTREACHED */
case 0: /* SUCCESS: I am now the child */
/* set the standard input/output channels of the new process. */
dup2(cacti2php_pdes[0], STDIN_FILENO);
dup2(php2cacti_pdes[1], STDOUT_FILENO);
/* close unneeded Pipes */
(void)close(php2cacti_pdes[0]);
(void)close(php2cacti_pdes[1]);
(void)close(cacti2php_pdes[0]);
(void)close(cacti2php_pdes[1]);
/* start the php script server process */
execv(argv[0], argv);
_exit(127);
/* NOTREACHED */
default: /* I am the parent process */
CACTID_LOG_DEBUG(("DEBUG: SS[%i] PHP Script Server Child FORK Success\n", i));
}
/* Parent */
/* close unneeded pipes */
close(cacti2php_pdes[0]);
close(php2cacti_pdes[1]);
if (php_process == PHP_INIT) {
php_processes[i].php_pid = pid;
php_processes[i].php_write_fd = cacti2php_pdes[1];
php_processes[i].php_read_fd = php2cacti_pdes[0];
}else{
php_processes[php_process].php_pid = pid;
php_processes[php_process].php_write_fd = cacti2php_pdes[1];
php_processes[php_process].php_read_fd = php2cacti_pdes[0];
}
/* restore caller's cancellation state. */
pthread_setcancelstate(cancel_state, NULL);
/* check pipe to insure startup took place */
if (php_process == PHP_INIT) {
result_string = php_readpipe(i);
}else{
result_string = php_readpipe(php_process);
}
if (strstr(result_string, "Started")) {
if (php_process == PHP_INIT) {
CACTID_LOG_DEBUG(("DEBUG: SS[%i] Confirmed PHP Script Server running\n", i));
php_processes[i].php_state = PHP_READY;
}else{
CACTID_LOG_DEBUG(("DEBUG: SS[%i] Confirmed PHP Script Server running\n", php_process));
php_processes[php_process].php_state = PHP_READY;
}
}else{
CACTID_LOG(("ERROR: SS[%i] Script Server did not start properly return message was: '%s'\n", php_process, result_string));
if (php_process == PHP_INIT) {
php_processes[i].php_state = PHP_BUSY;
}else{
php_processes[php_process].php_state = PHP_BUSY;
}
}
}
free(result_string);
return TRUE;
}
/*! \fn void php_close(int php_process)
* \brief close the php script server process
* \param php_process the process to close or PHP_INIT
*
* This function will take an input parameter of either a specially coded
* PHP_INIT parameter or an integer stating the process number. With that
* information is will close and/or terminate the child PHP Script Server
* process and then return to the calling function.
*
* TODO: Make ending of the child process not be reliant on SIG_TERM in cases
* where the child process is hung for one reason or another.
*
*/
void php_close(int php_process) {
int i;
int num_processes;
if (php_process == PHP_INIT) {
num_processes = set.php_servers;
}else{
num_processes = 1;
}
for(i = 0; i < num_processes; i++) {
php_t *phpp;
CACTID_LOG_DEBUG(("DEBUG: SS[%i] Script Server Shutdown Started\n", i));
/* tell the script server to close */
if (php_process == PHP_INIT) {
phpp = &php_processes[i];
}else{
phpp = &php_processes[php_process];
}
/* If we still have a valid write pipe, tell PHP to close down
* by sending a "quit" message, then closing the input channel
* so it gets an EOF.
*
* Then we wait a moment before actually killing it to allow for
* a clean shutdown.
*/
if (phpp->php_write_fd >= 0) {
static const char quit[] = "quit\r\n";
write(phpp->php_write_fd, quit, strlen(quit));
close(phpp->php_write_fd);
phpp->php_write_fd = -1;
/* wait before killing php */
#ifndef SOLAR_THREAD
usleep(50000); /* 50 msec */
#endif
}
/* only try to kill the process if the PID looks valid.
* Trying to kill a negative number is bad news (it's
* a process group leader), and PID 1 is "init".
*/
if (phpp->php_pid > 1) {
/* end the php script server process */
kill(phpp->php_pid, SIGTERM);
/* reset this PID variable? */
}
/* close file descriptors */
close(phpp->php_read_fd);
phpp->php_read_fd = -1;
}
}
syntax highlighted by Code2HTML, v. 0.9.1