/* Copyright (c) 2000
W. M. Shandruk <walt@erudition.net>. All rights are reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software
must display the following acknowledgement:
This product includes software developed by W. M. Shandruk.
4. The name of W. M. Shandruk may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY W. M. SHANDRUK ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL W. M. SHANDRUK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
#include "monitord.h"
#include "config.h"
int main (int argc, char *arga[]) {
int i, num, interval;
char *buf; /* All-purpose buffer */
char *file [_MAXLINE];
char *filename; /* config file name */
buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
filename = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
bzero (buf, _BUFSIZE );
bzero (filename, _BUFSIZE );
// Set the HUP variable to zero because no SIGHUPs have been caught yet
HUP = 0;
interval = _INTERVAL; /* Default interval period */
strncpy (filename, _PATH_TO_CONFIG, strlen(_PATH_TO_CONFIG)); /* Default config file */
/* check if there are any arguments */
if (argc > 1) {
/* Loop through all arguments passed in */
for (i = 0; i < argc; i++) {
/* If interval is set, grab it */
if (strstr (arga[i], "-t") && (argc > i)) {
strncpy (buf, arga[i+1], _BUFSIZE);
interval = atoi (buf);
}
/* If a custom config is set, grab it */
if (strstr (arga[i], "-f") && (argc > i)) {
strncpy (buf, arga[i+1], _BUFSIZE);
bzero ( filename, _BUFSIZE );
strncpy (filename, buf, _BUFSIZE);
}
}
}
// init the *file[];
for ( i = 0; i < _MAXLINE; i++ ) {
file[i] = (char *) malloc ( (size_t) sizeof(char) * _BUFSIZE );
bzero ( file[i], sizeof (file[i]) );
}
// file = (char **) calloc (1000, (size_t) sizeof(char) * _BUFSIZE );
// Drop this daemon into the background
switch (fork ()) {
case -1: {
warn ("couldn't fork()");
exit(1);
}
case 0: {
setsid ();
break;
}
default:
exit(1);
}
/* Read the configuration file, saving it in *file[] and return the number of lines
were read in the configuration file so we know how much we have to loop next time to
check for processes we're responsible for. We're going to have a separate look in which
we check for processes we're responsible for inside the Main loop() because we're going
to be calling sleep() so we don't suck up processor time */
num = read_conf ((char ***) & file, filename );
/* Free some used up memory */
free (buf);
free (filename);
loop ((char **) & file, filename, num, interval ); // Start the main loop
exit (0); // exit if loop() breaks
}
/* This is the function that reads our configuration file and returns the number of valid
lines that have been read. This line count is important because it will define how often the
loop inside the main loop() will go around for checking for processes we're responsible for.
There will be one look within the main loop() for each process we're responsible for, which will
simplify checking */
int read_conf ( char ***file, char *filename ) {
FILE *sfile;
int line_count;
char *buf;
buf = (char *) malloc ( (size_t) _BUFSIZE ); // init & zero the buffer
bzero (buf, sizeof (buf) );
line_count = 0;
// Open the configuration file for reading
if ((sfile = fopen (filename, "r" )) == NULL) {
syslog(LOG_ERR, "could not open %s, exiting", filename);
printf ("monitord: couldn't open file %s, exiting\n", filename);
exit(0);
}
/* Start the main reading loop. We're going to only count valid lines with line_count.
First thing we do is grab the first line; this allows us to manage the counter
correctly; in other words, we can put the line grabber in the loop at the end of the
loop so that when the feof() reaches the end of the file so does the loop and the
counter gets incremented the correct number of times */
fgets ( buf, strlen(buf), sfile );
while (!feof(sfile)) {
// If the line isn't a comment, proceed
if (!strstr (buf, "#") && (strlen(buf) > 1)) {
// save the line into *file[]
strncpy ( (char *) file[line_count], buf, _BUFSIZE );
// realloc ( & file[1], (size_t) sizeof(char) * _BUFSIZE * (line_count + 1) );
// zero out the buffer so we don't have it hold old garbage
bzero (buf, sizeof (buf));
line_count++; // Advance the counter
}
fgets ( buf, _MAXLINE, sfile ); // Grab next line from the file
}
fclose (sfile);
return (line_count); // Return the number of valid lines in the configuration file
}
int loop ( char **file, char *filename, int max_proc, int interval ) {
int i, j, current_option, pid, FOUND;
int status; // exit status of a child
// These are variables we will need for finding and executing the processes
char uid[_BUFSIZE / 7]; /* user ID */
// char gid[_BUFSIZE / 7]; /* group ID */
char allopt[_BUFSIZE / 7]; /* entire options line */
char opt[5][_BUFSIZE / 6]; /* options array */
char delaytime[_BUFSIZE / 7]; /* delay time for a particular proc */
char proc[_BUFSIZE / 7]; /* current process we're checking */
char script[_BUFSIZE / 7]; /* start up script or process name */
char script_path[_BUFSIZE / 7]; /* path to start up script or process name */
char param[_BUFSIZE / 7]; /* parameters to the current process */
char var[_BUFSIZE / 6]; /* generic variable from config file */
char value[_BUFSIZE / 6]; /* generic value from config file */
char email[_BUFSIZE / 6]; /* admin's email */
char eserver[_BUFSIZE / 6]; /* admin's email server */
char delay[max_proc]; /* array for tracking delay times for processes */
char cmdline[_BUFSIZE]; /* entire process line from config file */
char tmppath[_BUFSIZE]; /* temporary copy of script_path */
options_t options;
char *buf; // all purpose buffer
char *buf2; // all purpose buffer
char *token; // all purpose buffer
DIR *dirp; // Directory Stream pointer
struct dirent *dp; // Directory struct
FILE *fp; // File Stream pointer
struct passwd *pw; // Passwd DB struct
buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the all purpose buffer
buf2 = (char *) malloc ( (size_t) _BUFSIZE ); // init another all purpose buffer
token = (char *) malloc ( (size_t) _BUFSIZE ); // init the token buffer
bzero (buf, sizeof (buf) );
bzero (buf2, sizeof (buf) );
bzero (token, sizeof (token) );
FOUND = 0;
// Main loop
while(1) {
// Catch HUP signal to reread config file
signal (SIGHUP, (void *) sig_catch);
if (HUP) {
// Reload the configuration file
max_proc = read_conf ((char ***) file, filename);
// Set HUP var back to zero because signal was already handled
HUP = 0;
}
// Reduce all delay times by interval seconds
for ( j = 0; j < max_proc; j++ ) {
if ( delay[j] > 0 )
{ delay[j] -= interval; }
else
{ delay[j] = 0; }
}
// Let's run through the maximum number of proceses that we are responsible for
for ( i = 0; i < max_proc; i++ ) {
/*
Breaks up the *file string to extract each bit we need, and save it into
separate strings
*/
// sscanf ( (char *) file[i], "%s %s %s", var, filler, value );
/*
We're first going to check for the general configuration lines
that set the admin's email, status update time, and so on. If we
pick these up, we "continue" so the later strtok() lines are
skipped until we get past the generation configuration stuff
*/
strncpy (buf, file[i], _BUFSIZE); /* copy temporary copy of line */
strncpy (var, strtok(buf, " =\t"), sizeof(var));
strncpy (value, strtok(NULL, " =\t"), sizeof(value));
/* Each line has a \n at the end which must be removed,
so we set it to NULL */
value[strlen(value) - 1] = '\0';
/* Grab admin's email */
if (strncmp (var, "email", sizeof(var)) == 0) {
strncpy (email, value, sizeof(email));
continue;
}
/* Grab admin's email server */
if (strncmp (var, "smtp-server", sizeof(var)) == 0) {
strncpy (eserver, value, sizeof(eserver));
continue;
}
/* Grab status update interval */
/* if (strncmp (var, "interval", sizeof(var)) == 0) {
interval = atoi (value) / interval;
continue;
}
*/
bzero (buf, _BUFSIZE);
/* grab daemon configuration lines */
strncpy (cmdline, file[i], sizeof(cmdline)); /* copy temporary copy of line */
strncpy (uid, strtok(cmdline, " \t"), sizeof(uid));
// strncpy (gid, strtok(NULL, " \t"), sizeof(gid));
strncpy (allopt, strtok(NULL, " \t"), sizeof(allopt));
strncpy (delaytime, strtok(NULL, " \t"), sizeof(delaytime));
strncpy (proc, strtok(NULL, " \t"), sizeof(proc));
strncpy (script_path, strtok(NULL, " \t"), sizeof(script_path));
/* The param element - the command parameters - must be constructed
because they are composed of multiple tokens, so, they must be
spliced into a single string. This is done with a loop that uses
strncat() to concatenate the individuals tokens. However, we
must, at the very first, zero out the param string because it
being constructed by concatenation, it'll have its previous
contents appended if we don't wipe it, unlike where with strncpy()
the previous contents gets wiped */
bzero (param, sizeof(param));
while ((token = strtok(NULL, " \t"))) {
strncat (param, " ", sizeof(param));
strncat (param, token, sizeof(param));
}
/* Each line has a \n at the end which must be removed
so we set it to NULL */
param[strlen(param) - 1] = '\0';
/* The script is the last part of script_path and is extracted
with a strtok() loop */
strncpy (tmppath, script_path, sizeof(tmppath)); /* tmp path var */
token = strtok (tmppath, "/");
/* Loop and extract the script from the script_path */
do {
strncpy (script, token, sizeof(script));
} while ((token = strtok (NULL, "/")));
/* parse the *opt[] array to grab all of the options */
token = strtok (allopt, " \t,");
current_option = 0;
do {
strncpy (opt[current_option], token, sizeof(opt[current_option]));
current_option++;
} while ((token = strtok (NULL, ",")));
/* Place the options into useful variables */
options.isauto = FALSE;
options.alert = FALSE;
for (j = 0; j < current_option; j++) {
if (strncmp (opt[j], "auto", sizeof(opt[j])) == 0) options.isauto = TRUE;
if (strncmp (opt[j], "alert", sizeof(opt[j])) == 0) options.alert = TRUE;
/* if (strncmp (opt[j], "status", sizeof(opt[j])) == 0) options.status = TRUE;
else { options.status = FALSE; }
*/
}
/* If there are no parameters, then \n will end up on
script_path, so when script is extracted, it needs to be
replaced with \0 */
if (!strlen(param)) script[strlen(script) - 1] = '\0';
// printf ("(%s) (%s) (%s) (%s) (%s) (%s) (%s)\n", uid, gid, allopt, proc, script_path, script, param);
// Open the /proc dir so we can run down the PID dirs
dirp = (DIR *) opendir ("/proc");
/* Loop around until we reach the end of the /proc dir. readdir() will keep
reading a new directory/file in /proc on each cycle and dump its info into
the dp structure */
while ( (dp = (struct dirent *) readdir(dirp)) != NULL ) {
// Create the path of the status file in the PID dir
sprintf ( buf, "/proc/%s/status",dp->d_name );
// Open the status file
if ((fp = fopen (buf, "r")) != NULL) {
// Grab the line of status file
fgets ( buf, _BUFSIZE, fp );
// grab the first token on the line, which is the bin name
strtok ( buf, " " );
// printf ( "%s:%s\n", dp->d_name, buf );
fclose (fp);
}
else {
// printf("Couldn't open %s\n", buf);
}
// Set the FOUND flag if the process we're checking for is found
if (!strncmp (buf, proc, sizeof(buf))) FOUND = TRUE;
}
closedir (dirp); // Close the /proc directory
// If the process wasn't found in the process listing then start it
if (!FOUND && options.isauto && !delay[i]) {
/* Email admin that the service has died, if the "mail"
option has been set in the options */
if (options.alert) {
bzero (buf, sizeof(buf));
bzero (buf2, sizeof(buf2));
sprintf (buf, "[%s] Service \"%s\" has died\n", getdate(), proc);
sprintf (buf2, "(monitord) SYSTEM ALERT, \"%s\" has died\n", proc);
mail (email, eserver, buf2, buf);
}
/* Set a delay for which not check a process to allow it to fully start up */
delay[i] = atoi (delaytime);
if ((pid = fork() ) < 0) {
// printf ("Problem creating child process for system()\n");
}
if (pid > 0) {
// Printf ("Start child process\n");
}
else if (pid == 0) {
pw = getpwnam (uid);
/* Set the UID/GID of calling fork() so it starts with the
proper owner */
// printf ("%s %s %s %s %s\n", uid, gid, allopt, script_path, param);
seteuid (pw->pw_uid);
setegid (pw->pw_gid);
/* Format the execution string to include parameters that
are specified to be passed to the daemon we are responsible
for */
if ((strlen (param) != 1)) {
sprintf (buf, "%s %s", script_path, param);
}
else {
sprintf (buf, "%s", script_path);
}
/* Actually restart the daemon */
if (system (buf) != -1) {
syslog(LOG_NOTICE, "restarted \"%s\" using \"%s %s\"\n", proc, script_path, param);
/* Email the admin that the service has been
restarted if "mail" option is set */
if (options.alert) {
bzero (buf, sizeof(buf));
sprintf (buf, "[%s] restarted \"%s\" using \"%s %s\"\n", getdate(), proc, script_path, param);
sprintf (buf2, "(monitord) \"%s\" restarted\n", proc);
mail (email, eserver, buf2, buf);
}
exit(0);
} else {
syslog(LOG_NOTICE, "unable to restart \"%s\"\n", proc);
/* Email the admin that the service has not
been able to be restarted if "mail" option
is set */
if (options.alert) {
bzero (buf, sizeof(buf));
sprintf (buf, "[%s] unable to restart \"%s\"\n", getdate(), proc);
sprintf (buf2, "(monitord) SYSTEM ALERT: \"%s\" unable to restart\n", proc);
mail (email, eserver, buf2, buf);
}
exit(0);
}
}
} /* End of FOUND loop */
/* Reset the FOUND variable so that it can be used to locate
the next process we are responsible for */
FOUND = 0;
} /* End of max_proc loop */
// Put the process to sleep for a bit so it doesn't suck up CPU cycles
waitpid ( -1, &status, WNOHANG );
sleep (interval);
} /* End of main loop */
return (1);
}
void sig_catch () {
syslog(LOG_NOTICE, "reloaded\n"); // Log the reload event
printf ("[%s] monitord config reloaded\n", getdate());
HUP = 1;
return;
}
char *getdate () {
struct timeval *tp;
struct timezone *tzp;
time_t *time;
char *buf;
buf = (char *) malloc ( (size_t) _BUFSIZE ); // init the time buffer
tp = (struct timeval *) malloc ( (size_t) sizeof (struct timeval) ); // init the time buffer
tzp = (struct timezone *) malloc ( (size_t) sizeof (struct timezone) ); // init the timezone buffer
time = (time_t *) malloc ( (size_t) sizeof (time_t) ); // init the timezone buffer
/* Get time of day in seconds since Epoch */
gettimeofday (tp, tzp);
/* Save time of day in pointer variable */
*time = tp->tv_sec;
/* Convert the Epoch time to text and save in *buf */
strncpy (buf, ctime(time), _BUFSIZE);
/* Chop off '\n' from the end of the *buf */
*(buf + (strlen(buf) - 1)) = '\0';
/* Free everything you can */
free ((struct timeval *) tp);
free ((struct timzone *) tzp);
free ((time_t *) time);
return( buf );
}
syntax highlighted by Code2HTML, v. 0.9.1