blob: 78287df637c881a020bdadbd0866e7de6ebdee20 [file] [log] [blame]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#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 "monit.h"
#include "net.h"
#include "socket.h"
#include "event.h"
#include "system/Time.h"
/**
* Methods for controlling services managed by monit.
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
typedef enum {
Process_Stopped = 0,
Process_Started
} Process_Status;
/* -------------------------------------------------------------- Prototypes */
static void do_start(Service_T, int);
static int do_stop(Service_T, int);
static void do_monitor(Service_T, int);
static void do_unmonitor(Service_T, int);
static void do_depend(Service_T, int, int);
static Process_Status wait_process(Service_T, Process_Status expect);
/* ------------------------------------------------------------------ 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
* @return FALSE for error, otherwise TRUE
*/
int control_service_daemon(const char *S, const char *action) {
int rv = FALSE;
int status, content_length = 0;
Socket_T socket;
char *auth;
char buf[STRLEN];
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 FALSE;
}
socket = socket_create_t(Run.bind_addr ? Run.bind_addr : "localhost", Run.httpdport, SOCKET_TCP,
(Ssl_T){.use_ssl = Run.httpdssl, .clientpemfile = Run.httpsslclientpem}, NET_TIMEOUT);
if (! socket) {
LogError("%s: Cannot connect to the monit daemon. Did you start it with http support?\n", prog);
return FALSE;
}
/* Send request */
auth = Util_getBasicAuthHeaderMonit();
if (socket_print(socket,
"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);
goto err1;
}
/* Process response */
if (! socket_readln(socket, buf, STRLEN)) {
LogError("%s: error receiving data -- %s\n", prog, STRERROR);
goto err1;
}
Str_chomp(buf);
if (! sscanf(buf, "%*s %d", &status)) {
LogError("%s: cannot parse status in response: %s\n", prog, buf);
goto err1;
}
if (status >= 300) {
char *message = NULL;
/* Skip headers */
while (socket_readln(socket, buf, STRLEN)) {
if (! strncmp(buf, "\r\n", sizeof(buf)))
break;
if (Str_startsWith(buf, "Content-Length") && ! sscanf(buf, "%*s%*[: ]%d", &content_length))
goto err1;
}
if (content_length > 0 && content_length < 1024 && socket_readln(socket, buf, STRLEN)) {
char token[] = "</h2>";
char *p = strstr(buf, token);
if (strlen(p) <= strlen(token))
goto err2;
p += strlen(token);
message = CALLOC(1, content_length + 1);
snprintf(message, content_length + 1, "%s", p);
p = strstr(message, "<p>");
if (p)
*p = 0;
}
err2:
LogError("%s: action failed -- %s\n", prog, message ? message : "unable to parse response");
FREE(message);
} else
rv = TRUE;
err1:
FREE(auth);
socket_free(&socket);
return rv;
}
/**
* 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
* @return FALSE for error, otherwise TRUE
*/
int 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 FALSE;
}
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
* @return FALSE for error, otherwise TRUE
*/
int 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 FALSE;
}
switch(A) {
case ACTION_START:
if (s->type == TYPE_PROCESS) {
if (Util_isProcessRunning(s, FALSE)) {
DEBUG("%s: Process already running -- process %s\n", prog, S);
Util_monitorSet(s);
return TRUE;
}
if (!s->start) {
LogError("%s: Start method not defined -- process %s\n", prog, S);
Util_monitorSet(s);
return FALSE;
}
}
do_depend(s, ACTION_STOP, FALSE);
do_start(s, 0);
do_depend(s, ACTION_START, 0);
break;
case ACTION_STOP:
if (s->type == TYPE_PROCESS && !s->stop) {
LogError("%s: Stop method not defined -- process %s\n", prog, S);
Util_monitorUnset(s);
return FALSE;
}
do_depend(s, ACTION_STOP, TRUE);
do_stop(s, TRUE);
break;
case ACTION_RESTART:
if (s->type == TYPE_PROCESS && (!s->start || !s->stop)) {
LogError("%s: Start or stop method not defined -- process %s\n", prog, S);
Util_monitorSet(s);
return FALSE;
}
LogInfo("'%s' trying to restart\n", s->name);
do_depend(s, ACTION_STOP, FALSE);
if (do_stop(s, FALSE)) {
/* Only start if stop succeeded */
do_start(s, 0);
do_depend(s, ACTION_START, 0);
} 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, 0);
break;
case ACTION_UNMONITOR:
/* We disable monitoring of this service and all services which depends on it */
do_depend(s, ACTION_UNMONITOR, 0);
do_unmonitor(s, 0);
break;
default:
LogError("%s: service '%s' -- invalid action %s\n", prog, S, A);
return FALSE;
}
return TRUE;
}
/*
* 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
* @param flag A Custom flag
*/
static void do_start(Service_T s, int flag) {
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, flag);
}
}
if (s->start && (s->type != TYPE_PROCESS || !Util_isProcessRunning(s, FALSE))) {
LogInfo("'%s' start: %s\n", s->name, s->start->arg[0]);
spawn(s, s->start, NULL);
/* We only wait for a process type, other service types does not have a pid file to watch */
if (s->type == TYPE_PROCESS)
wait_process(s, Process_Started);
}
Util_monitorSet(s);
}
/*
* This function simply stops the service p.
* @param s A Service_T object
* @param flag TRUE if the monitoring should be disabled or FALSE if monitoring should continue (when stop is part of restart)
* @return TRUE if the service was stopped otherwise FALSE
*/
static int do_stop(Service_T s, int flag) {
int rv = TRUE;
ASSERT(s);
if (s->depend_visited)
return rv;
s->depend_visited = TRUE;
if (s->stop && (s->type != TYPE_PROCESS || Util_isProcessRunning(s, FALSE))) {
LogInfo("'%s' stop: %s\n", s->name, s->stop->arg[0]);
spawn(s, s->stop, NULL);
if (s->type == TYPE_PROCESS && (wait_process(s, Process_Stopped) != Process_Stopped)) // Only wait for process service types stop
rv = FALSE;
}
if (flag)
Util_monitorUnset(s);
else
Util_resetInfo(s);
return rv;
}
/*
* 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
* @param flag A Custom flag
*/
static void do_monitor(Service_T s, int flag) {
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, flag);
}
}
Util_monitorSet(s);
}
/*
* This is a function for disabling monitoring
* @param s A Service_T object
* @param flag A Custom flag
*/
static void do_unmonitor(Service_T s, int flag) {
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
* @param flag A Custom flag
*/
static void do_depend(Service_T s, int action, int flag) {
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, flag);
else if (action == ACTION_MONITOR)
do_monitor(child, flag);
do_depend(child, action, flag);
if (action == ACTION_STOP)
do_stop(child, flag);
else if (action == ACTION_UNMONITOR)
do_unmonitor(child, flag);
break;
}
}
}
}
}
/*
* This function waits for the process to change state. If the process state doesn't match the expectation,
* a failed event is posted to notify the user. The time is saved on enter so in the case that the time steps
* backwards/forwards, the wait_process will wait for absolute time and not stall or prematurely exit.
* @param service A Service to wait for
* @param expect A expected state (see Process_Status)
* @return Either Process_Started if the process is running or Process_Stopped if it's not running
*/
static Process_Status wait_process(Service_T s, Process_Status expect) {
int debug = Run.debug, isrunning = FALSE;
unsigned long now = time(NULL) * 1000, wait = 50;
unsigned long timeout = now + s->start->timeout * 1000;
ASSERT(s);
do {
Time_usleep(wait * USEC_PER_MSEC);
now += wait ;
wait = wait < 1000 ? wait * 2 : 1000; // double the wait during each cycle until 1s is reached
isrunning = Util_isProcessRunning(s, TRUE);
if ((expect == Process_Stopped && ! isrunning) || (expect == Process_Started && isrunning))
break;
Run.debug = FALSE; // Turn off debug second time through to avoid flooding the log with pid file does not exist. This poll stuff here _will_ be refactored away
} while (now < timeout && ! Run.stopped);
Run.debug = debug; // restore the debug state
if (isrunning) {
if (expect == Process_Started)
Event_post(s, Event_Exec, STATE_SUCCEEDED, s->action_EXEC, "started");
else
Event_post(s, Event_Exec, STATE_FAILED, s->action_EXEC, "failed to stop");
return Process_Started;
} else {
if (expect == Process_Started)
Event_post(s, Event_Exec, STATE_FAILED, s->action_EXEC, "failed to start");
else
Event_post(s, Event_Exec, STATE_SUCCEEDED, s->action_EXEC, "stopped");
return Process_Stopped;
}
}