/*
 * 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_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.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

#include "monitor.h"
#include "net.h"
#include "socket.h"
#include "event.h"


/**
 *  Methods for controlling services managed by monit.
 *
 *  @author Jan-Henrik Haukeland, <hauk@tildeslash.com>
 *  @author Rory Toma, <rory@digeo.com>
 *  @author Martin Pala, <martinp@tildeslash.com>
 *
 *  @version \$Id: control.c,v 1.107 2007/07/25 12:54:28 hauk Exp $
 *
 *  @file
 */


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


static void  do_start(Service_T);
static int   do_stop(Service_T);
static void  do_monitor(Service_T);
static void  do_unmonitor(Service_T);
static void *wait_start(void *);
static int   wait_stop(Service_T);
static void  do_depend(Service_T, int);


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


/**
 * Pass on to methods in http/cervlet.c to start/stop services
 * @param S A service name as stated in the config file
 * @param action A string describing the action to execute
 */
void control_service_daemon(const char *S, const char *action) {
  
  Socket_T s;
  
  ASSERT(S);
  ASSERT(action);
  
  if(Util_getAction(action) == ACTION_IGNORE) {
    LogError("%s: Cannot %s service '%s' -- invalid action %s\n",
        prog, action, S, action);
    return;
  }
  
  s= socket_new(Run.bind_addr?Run.bind_addr:"localhost",
                Run.httpdport, SOCKET_TCP, Run.httpdssl, NET_TIMEOUT);
  
  if(!s) {
    
    LogError("%s: Cannot connect to the monit daemon."
        " Did you start it with http support?\n", prog);
    
  } else {

    char *auth = Util_getBasicAuthHeader();

    if(socket_print(s,
         "POST /%s HTTP/1.0\r\n"
         "Content-Type: application/x-www-form-urlencoded\r\n"
         "Content-Length: %d\r\n"
         "%s"
         "\r\n"
         "action=%s",
         S,
         strlen("action=") + strlen(action),
         auth?auth:"",
         action) < 0)
    {
      LogError("%s: Cannot send the command '%s' to the monit daemon -- %s", 
          prog, action?action:"null", STRERROR);
    }
    FREE(auth);
    socket_free(&s);
    
  }
  
}


/**
 * Check to see if we should try to start/stop service
 * @param S A service name as stated in the config file
 * @param A A string describing the action to execute
 */
void control_service_string(const char *S, const char *A) {

  int a;

  ASSERT(S);
  ASSERT(A);

  if((a = Util_getAction(A)) == ACTION_IGNORE) {
    LogError("%s: service '%s' -- invalid action %s\n", prog, S, A);
    return;
  }
  control_service(S, a);
}


/**
 * Check to see if we should try to start/stop service
 * @param S A service name as stated in the config file
 * @param A An action id describing the action to execute
 */
void control_service(const char *S, int A) {

  Service_T s = NULL;

  ASSERT(S);

  if(!(s= Util_getService(S))) {
    LogError("%s: service '%s' -- doesn't exist\n", prog, S);
    return;
  }

  switch(A) {

    case ACTION_START:
      if(s->type==TYPE_PROCESS) {
        if(Util_isProcessRunning(s)) {
          DEBUG("%s: Process already running -- process %s\n", prog, S);
          Util_monitorSet(s);
          return;
        }
        if(!s->start) {
          DEBUG("%s: Start method not defined -- process %s\n", prog, S);
          Util_monitorSet(s);
          return;
        }
      }
      do_depend(s, ACTION_STOP);
      do_start(s);
      do_depend(s, ACTION_START);
      break;

    case ACTION_STOP:
      if(s->type==TYPE_PROCESS && !s->stop) {
        DEBUG("%s: Stop method not defined -- process %s\n", prog, S);
        Util_monitorUnset(s);
        return;
      }
      /* soft unmonitor and stop: */
      do_depend(s, ACTION_STOP);
      do_stop(s);
      /* hard unmonitor - will reset all counters and flags: */
      do_depend(s, ACTION_UNMONITOR);
      do_unmonitor(s);
      break;

    case ACTION_RESTART:
      if(s->type==TYPE_PROCESS && (!s->start || !s->stop)) {
        DEBUG("%s: Start or stop method not defined -- process %s\n", prog, S);
        Util_monitorSet(s);
        return;
      }
      LogInfo("'%s' trying to restart\n", s->name);
      do_depend(s, ACTION_STOP);
      if(do_stop(s)) {
        /* Only start if stop succeeded */
        do_start(s);
        do_depend(s, ACTION_START);
      } else {
        /* enable monitoring of this service again to allow the restart retry
         * in the next cycle up to timeout limit */
        Util_monitorSet(s);
      }
      break;

    case ACTION_MONITOR:
      /* We only enable monitoring of this service and all prerequisite
       * services. Chain of services which depends on this service keep
       * its state */
      do_monitor(s);
      break;

    case ACTION_UNMONITOR:
      /* We disable monitoring of this service and all services which
       * depends on it */
      do_depend(s, ACTION_UNMONITOR);
      do_unmonitor(s);
      break;

    default:
      LogError("%s: service '%s' -- invalid action %s\n", prog, S, A);
      break;
  }
}


/*
 * Reset the visited flags used when handling dependencies
 */
void reset_depend() {

  Service_T s;
  
  for(s= servicelist; s; s= s->next) {
    s->visited= FALSE;
    s->depend_visited= FALSE;
  }

}


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


/*
 * This is a post- fix recursive function for starting every service
 * that s depends on before starting s.
 * @param s A Service_T object
 */
static void do_start(Service_T s) {

  ASSERT(s);

  if(s->visited)
    return;
  
  s->visited= TRUE;
  
  if(s->dependantlist) {
    
    Dependant_T d;
    
    for(d= s->dependantlist; d; d= d->next ) {
      
      Service_T parent= Util_getService(d->dependant);
      ASSERT(parent);
      do_start(parent);
      
    }

  }
  
  if(s->start && (s->type!=TYPE_PROCESS || !Util_isProcessRunning(s))) {
    int status;
    pthread_t thread;

    LogInfo("'%s' start: %s\n", s->name, s->start->arg[0]);
    spawn(s, s->start, "Started");
    if(s->type==TYPE_PROCESS) {
      /* We only wait for a process type, other service types does not
       * have a pid file to watch */
      LOCK(Run.mutex)
        Run.wait_start++;
      END_LOCK;
      status= pthread_create(&thread, NULL, wait_start, s);
      if(status != 0) {
        LOCK(Run.mutex)
          Run.wait_start--;
        END_LOCK;
	LogError("Warning: Failed to create the start controller thread. "
	    "Thread error -- %s.\n", strerror(status));
      }
    }
  }
  Util_monitorSet(s);
}


/*
 * This function simply stops the service p.
 * @param s A Service_T object
 * @return TRUE if the service was stopped otherwise FALSE
 */
static int do_stop(Service_T s) {

  ASSERT(s);

  if(s->depend_visited)
    return TRUE;
 
  s->depend_visited= TRUE;

  /* do soft unmonitor - start counter and error state is kept */
  if(s->monitor != MONITOR_NOT)
  {
    s->monitor= MONITOR_NOT;
    DEBUG("Monitoring disabled -- service %s\n", s->name);
  } 

  if(s->stop && (s->type!=TYPE_PROCESS || Util_isProcessRunning(s))) {
    LogInfo("'%s' stop: %s\n", s->name, s->stop->arg[0]);
    spawn(s, s->stop, "Stopped");

    if(s->type==TYPE_PROCESS) {
      /* Only wait for process service types */
      if(!wait_stop(s))
        return FALSE;
    }
  }

  Util_resetInfo(s);

  return TRUE;
 
}


/*
 * This is a post- fix recursive function for enabling monitoring every service
 * that s depends on before monitor s.
 * @param s A Service_T object
 */
static void do_monitor(Service_T s) {

  ASSERT(s);

  if(s->visited)
    return;
  
  s->visited= TRUE;
  
  if(s->dependantlist) {
    
    Dependant_T d;
    
    for(d= s->dependantlist; d; d= d->next ) {
      
      Service_T parent= Util_getService(d->dependant);
      ASSERT(parent);
      do_monitor(parent);
      
    }
  }
  
  Util_monitorSet(s);
   
}


/*
 * This is a function for disabling monitoring
 * @param s A Service_T object
 */
static void do_unmonitor(Service_T s) {

  ASSERT(s);

  if(s->depend_visited)
    return;
 
  s->depend_visited= TRUE;
  
  Util_monitorUnset(s);
   
}


/*
 * This is an in-fix recursive function called before s is started to
 * stop every service that depends on s, in reverse order *or* after s
 * was started to start again every service that depends on s. The
 * action parametere controls if this function should start or stop
 * the procceses that depends on s.
 * @param s A Service_T object
 * @param action An action to do on the dependant services
 */
static void do_depend(Service_T s, int action) {

  Service_T child;
  
  ASSERT(s);

  for(child= servicelist; child; child= child->next) {
    
    if(child->dependantlist) {

      Dependant_T d;
    
      for(d= child->dependantlist; d; d= d->next) {

	if(IS(d->dependant, s->name)) {
	
	  if(action == ACTION_START)
	    do_start(child);
          else if(action == ACTION_MONITOR)
	    do_monitor(child);
	
	  do_depend(child, action);
	
	  if(action == ACTION_STOP)
	    do_stop(child);
          else if(action == ACTION_UNMONITOR)
	    do_unmonitor(child);

	  break;

	}
      }
    }
  }
}
    

/*
 * This function runs in its own thread and waits for the service to
 * start running. If the service did not start a failed event is
 * posted to notify the user.
 * @param service A Service to wait for
 */
static void *wait_start(void *service) {

  Service_T s= service;
  int max_tries= Run.polltime;
  
  ASSERT(s);

  pthread_detach(pthread_self());

  while(max_tries-- && !Run.stopped) {
    if(Util_isProcessRunning(s))
      break;
    sleep(1);
  }
  
  if(!Util_isProcessRunning(s)) {
    Event_post(s, EVENT_EXEC, STATE_FAILED, s->action_EXEC,
      "'%s' failed to start", s->name);
  } else {
    Event_post(s, EVENT_EXEC, STATE_PASSED, s->action_EXEC,
      "'%s' started", s->name);
  }

  LOCK(Run.mutex)
    Run.wait_start--;
  END_LOCK;

  return NULL;
  
}


/*
 * This function waits for the service to stop running. If the service
 * did not stop a failed event is posted to notify the user. This
 * function does purposefully not run in its own thread because, if we
 * did a restart we need to know if we successfully managed to stop
 * the service first before we can do a start.
 * @param service A Service to wait for
 * @return TRUE if the service was stopped otherwise FALSE
 */
static int wait_stop(Service_T s) {

  int max_tries= Run.polltime;
  
  ASSERT(s);

  while(max_tries-- && !Run.stopped) {
    if(!Util_isProcessRunning(s))
      break;
    sleep(1);
  }

  if(Util_isProcessRunning(s)) {
    Event_post(s, EVENT_EXEC, STATE_FAILED, s->action_EXEC,
      "'%s' failed to stop", s->name);
    return FALSE;
  } else {
    Event_post(s, EVENT_EXEC, STATE_PASSED, s->action_EXEC,
      "'%s' stopped", s->name);
  }

  return TRUE;

}


syntax highlighted by Code2HTML, v. 0.9.1