| /* |
| * 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; |
| } |
| } |
| |