/*
 * Copyright (C), 2000-2007 by the monit project group.
 * All Rights Reserved.
 *
 * 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 3 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif


#include "monitor.h"
#include "net.h"
#include "ssl.h"
#include "process.h"
#include "md5.h"
#include "sha.h"
#include "state.h"
#include "event.h"


/**
 *  DESCRIPTION
 *    monit - system for monitoring services on a Unix system
 *
 *  SYNOPSIS
 *    monit [options] {arguments}
 *
 *  @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
 *  @author Martin Pala <martinp@tildeslash.com>
 *  @author Christian Hopp, <chopp@iei.tu-clausthal.de>
 *
 *  @version \$Id: monitor.c,v 1.136 2007/07/25 12:54:29 hauk Exp $
 *
 *  @file
 */


/* -------------------------------------------------------------- Prototypes */


static void  do_init();                       /* Initialize this application */
static void  do_reinit();           /* Re-initialize the runtime application */
static void  do_action(char **);         /* Dispatch to the submitted action */
static void  do_exit();                                    /* Finalize monit */
static void  do_default();                              /* Do default action */
static void  handle_options(int, char **);         /* Handle program options */
static void  help();                 /* Print program help message to stdout */
static void  version();                         /* Print version information */
static RETSIGTYPE do_reload(int);       /* Signalhandler for a daemon reload */
static RETSIGTYPE do_destroy(int);   /* Signalhandler for monit finalization */
static RETSIGTYPE do_wakeup(int);  /* Signalhandler for a daemon wakeup call */


/* ------------------------------------------------------------------ Public */


/**
 * The Prime mover
 */
int main(int argc, char **argv) {

  prog= Util_basename(argv[0]);
  init_env();
  handle_options(argc, argv);
 
  do_init();
  do_action(argv); 
  do_exit();

  return 0;

}


/**
 * Wakeup a sleeping monit daemon.
 * Returns TRUE on success otherwise FALSE
 */
int do_wakeupcall() {

  pid_t pid;
  
  if((pid= exist_daemon()) > 0) {
    
    kill(pid, SIGUSR1);
    LogInfo("%s daemon at %d awakened\n", prog, pid);
    
    return TRUE;
    
  }
  
  return FALSE;
  
}


/* ----------------------------------------------------------------- Private */


/**
 * Initialize this application - Register signal handlers,
 * Parse the control file and initialize the program's
 * datastructures and the log system.
 */
static void do_init() {

  int status;

  /*
   * Register interest for the SIGTERM signal,
   * in case we run in daemon mode this signal
   * will terminate a running daemon.
   */
  signal(SIGTERM, do_destroy);

  /*
   * Register interest for the SIGUSER1 signal,
   * in case we run in daemon mode this signal
   * will wakeup a sleeping daemon.
   */
  signal(SIGUSR1, do_wakeup);

  /*
   * Register interest for the SIGINT signal,
   * in case we run as a server but not as a daemon
   * we need to catch this signal if the user pressed
   * CTRL^C in the terminal
   */
  signal(SIGINT, do_destroy);

  /*
   * Register interest for the SIGHUP signal,
   * in case we run in daemon mode this signal
   * will reload the configuration.
   */
  signal(SIGHUP, do_reload);

  /*
   * Register no interest for the SIGPIPE signal,
   */
  signal(SIGPIPE, SIG_IGN);

  /*
   * Initialize the Runtime mutex. This mutex
   * is used to synchronize handling of global
   * service data
   */
  status= pthread_mutex_init(&Run.mutex, NULL);
  if(status != 0) {
    LogError("%s: Cannot initialize mutex -- %s\n",
      prog, strerror(status));
    exit(1);
  }

  /* 
   * Get the position of the control file 
   */
  if(! Run.controlfile) {
    
    Run.controlfile= File_findControlFile();
    
  }
  
  /*
   * Initialize the process information gathering interface
   */
  Run.doprocess= init_process_info();

  /*
   * Start the Parser and create the service list. This will also set
   * any Runtime constants defined in the controlfile.
   */
  if(! parse(Run.controlfile)) {
    
    exit(1);
    
  }

  /*
   * Stop and report success if we are just validating the Control
   * file syntax. The previous parse statement exits the program with
   * an error message if a syntax error is present in the control
   * file.
   */
  if(Run.testing) {

    LogInfo("Control file syntax OK\n");
    exit(0);

  }

  /*
   * Initialize the log system 
   */
  if(! log_init()) {
    
    exit(1);
    
  }

  /* 
   * Did we find any service ?  
   */
  if(! servicelist) {
    
    LogError("%s: No services has been specified\n", prog);
    exit(0);
    
  }
  
  /* 
   * Initialize Runtime file variables 
   */
  File_init();

  /* 
   * Should we print debug information ? 
   */
  if(Run.debug) {
    
    Util_printRunList();
    Util_printServiceList();
    
  }

}


/**
 * Re-Initialize the application - called if a
 * monit daemon receives the SIGHUP signal.
 */
static void do_reinit() {

  Run.doreload= FALSE;
  
  LogInfo("Awakened by the SIGHUP signal\n");
  LogInfo("Reinitializing %s - Control file '%s'\n",
    prog, Run.controlfile);
  
  /* Stop http interface */
  if(Run.dohttpd)
    monit_http(STOP_HTTP);

  /* Save the current state (no changes are possible now
     since the http thread is stopped) */
  State_save();

  /* wait for all wait_start threads to finish */
  while(Run.wait_start)
    sleep(1);

  /* Run the garbage collector */
  gc();

  if(! parse(Run.controlfile)) {
    LogError("%s daemon died\n", prog);
    exit(1);
  }

  /* Close the current log */
  log_close();

  /* Reinstall the log system */
  if(! log_init())
    exit(1);

  /* Did we find any services ?  */
  if(! servicelist) {
    LogError("%s: No services has been specified\n", prog);
    exit(0);
  }
  
  /* Reinitialize Runtime file variables */
  File_init();

  if(! File_createPidFile(Run.pidfile)) {
    LogError("%s daemon died\n", prog);
    exit(1);
  }

  /* Update service data from the state repository */
  State_update();
  
  /* Start http interface */
  if(can_http())
    monit_http(START_HTTP);

  /* send the monit startup notification */
  Event_post(Run.system, EVENT_INSTANCE, STATE_FAILED,
    Run.system->action_MONIT_RELOAD, "Monit reloaded");

}


/**
 * Dispatch to the submitted action - actions are program arguments
 */
static void do_action(char **args) {
  
  char *action= args[optind];
  char *service= args[++optind];

  Run.once= TRUE;

  if(! action) {
    do_default();
  } else if(IS(action, "start")     ||
            IS(action, "stop")      ||
            IS(action, "monitor")   ||
            IS(action, "unmonitor") ||
            IS(action, "restart")      ) {
    if(service) {

      void (*_control_service)(const char *, const char *)=
        exist_daemon()?control_service_daemon:control_service_string;

      if(IS(service, "all")) {

        Service_T s= NULL;

        for(s= servicelist; s; s= s->next) {
          if(s->visited)
            continue;
          if( !Run.mygroup || IS(s->group, Run.mygroup) ) {
            _control_service(s->name, action);
          }
        }

      } else {

        _control_service(service, action);

      }
    } else {
      LogError("%s: please specify the configured service "
        "name or 'all' after %s\n",
        prog, action);
      exit(1);
    }
  } else if(IS(action, "reload")) {
    LogInfo("Reinitializing monit daemon\n", prog);
    kill_daemon(SIGHUP);
  } else if(IS(action, "status")) {
    status(LEVEL_NAME_FULL);
  } else if(IS(action, "summary")) {
    status(LEVEL_NAME_SUMMARY);
  } else if(IS(action, "quit")) {
    kill_daemon(SIGTERM);
  } else if(IS(action, "validate")) {
    validate();
  } else {
    LogError("%s: invalid argument -- %s  (-h will show "
        "valid arguments)\n",
        prog, action);
    exit(1);
  }
  
}


/**
 * Finalize monit
 */
static void do_exit() {
  
  sigset_t ns;

  set_signal_block(&ns, NULL);
  
  Run.stopped= TRUE;

  if(Run.isdaemon && !Run.once) {

    if(can_http())
      monit_http(STOP_HTTP);

    LogInfo("%s daemon with pid [%d] killed\n", prog, (int)getpid());

    /* send the monit stop notification */
    Event_post(Run.system, EVENT_INSTANCE, STATE_FAILED,
      Run.system->action_MONIT_STOP, "Monit stopped");

  }

  /* wait for all wait_start threads to finish */
  while(Run.wait_start)
    sleep(1);

  gc();

  exit(0);
 
}


/**
 * Default action - become a daemon if defined in the Run object and
 * run validate() between sleeps. If not, just run validate() once.
 * Also, if specified, start the monit http server if in deamon mode.
 */
static void do_default() {

  if(Run.isdaemon) {
    
    if(do_wakeupcall())
      exit(0);
  
    Run.once= FALSE;

    if(can_http())
      LogInfo("Starting %s daemon with http interface at [%s:%d]\n",
          prog, Run.bind_addr?Run.bind_addr:"*", Run.httpdport);
    else
      LogInfo("Starting %s daemon\n", prog);
    
    if(Run.init != TRUE)
      daemonize(); 
    
    if(! File_createPidFile(Run.pidfile)) {
      LogError("%s daemon died\n", prog);
      exit(1);
    }

    if(State_shouldUpdate())
      State_update();

    atexit(File_finalize);
  
    if(can_http())
      monit_http(START_HTTP);
    
    /* send the monit startup notification */
    Event_post(Run.system, EVENT_INSTANCE, STATE_FAILED,
      Run.system->action_MONIT_START, "Monit started");

    while(TRUE) {

      validate();
      State_save();

      /* In the case that there is no pending action then sleep */
      if(!Run.doaction)
        sleep(Run.polltime);

      if(Run.dowakeup) {
        Run.dowakeup = FALSE;
        LogInfo("Awakened by User defined signal 1\n");
      }
      
      if(Run.stopped) {
        do_exit();
      } else if(Run.doreload) {
        do_reinit();
      } else {
        Event_post(Run.system, EVENT_INSTANCE, STATE_PASSED,
          Run.system->action_MONIT_START, "Monit has not changed");
        Event_post(Run.system, EVENT_INSTANCE, STATE_PASSED,
          Run.system->action_MONIT_RELOAD, "Monit has not changed");
      }
      
    }
    
  } else {
    
    validate();
    
  }

}


/**
 * Handle program options - Options set from the commandline
 * takes precedence over those found in the control file
 */
static void handle_options(int argc, char **argv) {
  
  int opt;
  opterr= 0;
  Run.mygroup= NULL;

  while((opt= getopt(argc,argv,"c:d:g:l:p:s:iItvVhH")) != -1) {

    switch(opt) {

    case 'c':
        Run.controlfile= xstrdup(optarg);
        break;
	
    case 'd':
	Run.isdaemon= TRUE;
 	sscanf(optarg, "%d", &Run.polltime);
	if(Run.polltime<1) {
	  LogError("%s: option -%c requires a natural number\n", prog, opt);
	  exit(1);
	}
	break;

    case 'g':
        Run.mygroup= xstrdup(optarg);
        break;
	
    case 'l':
        Run.logfile= xstrdup(optarg);
	if(IS(Run.logfile, "syslog"))
	    Run.use_syslog= TRUE;
	Run.dolog= TRUE;
        break;
   
    case 'p':
        Run.pidfile= xstrdup(optarg);
        break;

    case 's':
        Run.statefile= xstrdup(optarg);
        break;

    case 'I':
	Run.init= TRUE;
	break;
      
    case 't':
        Run.testing= TRUE;
        break;
	
    case 'v':
        Run.debug= TRUE;
        break;

    case 'H':
        if (argc > optind) {
          Util_printHash(argv[optind]);
        } else {
          Util_printHash(NULL);
        }
          
        exit(0);
	break;
	
    case 'V':
        version();
        exit(0);
	break;
	
    case 'h':
        help();
        exit(0);
	break;
	
    case '?':
	switch(optopt) {
	  
	case 'c':
	case 'd':
	case 'g':
	case 'l':
	case 'p':
	case 's':
	    LogError("%s: option -- %c requires an argument\n", prog, optopt);
	    break;
	default:
	    LogError("%s: invalid option -- %c  (-h will show valid options)\n",
		  prog, optopt);
	    
	}
	
	exit(1);
	
    }
    
  }
  
}


/**
 * Print the program's help message
 */
static void help() {
  
  printf("Usage: %s [options] {arguments}\n", prog);
  printf("Options are as follows:\n");
  printf(" -c file       Use this control file\n");
  printf(" -d n          Run as a daemon once per n seconds\n");
  printf(" -g name       Set group name for start, stop, restart and status\n");
  printf(" -l logfile    Print log information to this file\n");
  printf(" -p pidfile    Use this lock file in daemon mode\n");
  printf(" -s statefile  Set the file monit should write state information to\n");
  printf(" -I            Do not run in background (needed for run from init)\n");
  printf(" -t            Run syntax check for the control file\n");
  printf(" -v            Verbose mode, work noisy (diagnostic output)\n");
  printf(" -H [filename] Print SHA1 and MD5 hashes of the file or of stdin if the\n");
  printf("               filename is omited; monit will exit afterwards\n");
  printf(" -V            Print version number and patchlevel\n");
  printf(" -h            Print this text\n");
  printf("Optional action arguments for non-daemon mode are as follows:\n");
  printf(" start all      - Start all services\n");
  printf(" start name     - Only start the named service\n");
  printf(" stop all       - Stop all services\n");
  printf(" stop name      - Only stop the named service\n");
  printf(" restart all    - Stop and start all services\n");
  printf(" restart name   - Only restart the named service\n");
  printf(" monitor all    - Enable monitoring of all services\n");
  printf(" monitor name   - Only enable monitoring of the named service\n");
  printf(" unmonitor all  - Disable monitoring of all services\n");
  printf(" unmonitor name - Only disable monitoring of the named service\n");
  printf(" reload         - Reinitialize monit\n");
  printf(" status         - Print full status information for each service\n");
  printf(" summary        - Print short status information for each service\n");
  printf(" quit           - Kill monit daemon process\n");
  printf(" validate       - Check all services and start if not running\n");
  printf("\n");
  printf("(Action arguments operate on services defined in the control file)\n");

}

/**
 * Print version information
 */
static void version() {
  
  printf("This is monit version %s\n", VERSION);
  printf("Copyright (C) 2000-2007 by the monit project group.");
  printf(" All Rights Reserved.\n");

}


/**
 * Signalhandler for a daemon reload call
 */
static RETSIGTYPE do_reload(int sig) {

  Run.doreload= TRUE;
  
}


/**
 * Signalhandler for monit finalization
 */
static RETSIGTYPE do_destroy(int sig) {
  
  Run.stopped= TRUE;
  
}


/**
 * Signalhandler for a daemon wakeup call
 */
static RETSIGTYPE do_wakeup(int sig) {

  Run.dowakeup= TRUE;

}
