blob: a9521bd1af181adb89da0c8087f948c5701bc051 [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_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include "monit.h"
#include "state.h"
// libmonit
#include "exceptions/IOException.h"
/**
* The list of persistent properties:
*
* 1.) service name + service type
* Monit configuration may change, so the state restore needs to ignore
* the removed services or services which type doesn't match (the
* service name was reused for different check). The current service
* runtime is thus paired with the saved service state by name and type.
*
* 2.) monitoring state
* Keep the monitoring enabled or disabled on Monit restart. Useful for
* example when Monit is running in active/passive cluster, so the
* service monitoring mode doesn't reset when Monit needs to be reloaded
* and the service won't enter unwanted passive/passive or active/active
* state on multiple hosts. Another example is service which timed out
* due to excessive errors or the monitoring was intentionally disabled
* by admin for maintenance - do not re-enable monitoring on Monit reload.
*
* 3.) service restart counters
*
* 4.) inode number and read position for the file check
* Allows to skip the content match test for the content which was checked
* already to suppress duplicate events.
*
* The data are stored in binary form in the statefile using the following format:
* <MAGIC><VERSION>{<SERVICE_STATE>}+
*
* When the persistent field needs to be added, update the State_Version along
* with State_update() and State_save(). The version allows to recognize the
* service state structure and file format.
*
* The backward compatibility of monitoring state restore is very important if
* Monit runs in cluster => keep previous formats compatibility.
*
* @file
*/
/* ------------------------------------------------------------- Definitions */
/* Extended format version */
typedef enum {
StateVersion0 = 0,
StateVersion1
} State_Version;
/* Format version 0 (Monit <= 5.3) */
typedef struct mystate0 {
char name[STRLEN];
int mode; // obsolete since Monit 5.1
int nstart;
int ncycle;
int monitor;
unsigned long long error; // obsolete since Monit 5.0
} State0_T;
/* Extended format version 1 */
typedef struct mystate1 {
char name[STRLEN];
int type;
int monitor;
int nstart;
int ncycle;
union {
struct {
unsigned long long st_ino;
unsigned long long readpos;
} file;
} priv;
} State1_T;
static int file = -1;
/* ----------------------------------------------------------------- Private */
static void update_v0(int services) {
for (int i = 0; i < services; i++) {
State0_T state;
if (read(file, &state, sizeof(state)) != sizeof(state))
THROW(IOException, "Unable to read service state");
Service_T service;
if ((service = Util_getService(state.name))) {
service->nstart = state.nstart;
service->ncycle = state.ncycle;
if (state.monitor == MONITOR_NOT)
service->monitor = state.monitor;
else if (service->monitor == MONITOR_NOT)
service->monitor = MONITOR_INIT;
}
}
}
static void update_v1() {
State1_T state;
while (read(file, &state, sizeof(state)) == sizeof(state)) {
Service_T service;
if ((service = Util_getService(state.name)) && service->type == state.type) {
service->nstart = state.nstart;
service->ncycle = state.ncycle;
if (state.monitor == MONITOR_NOT)
service->monitor = state.monitor;
else if (service->monitor == MONITOR_NOT)
service->monitor = MONITOR_INIT;
if (service->type == TYPE_FILE) {
service->inf->priv.file.st_ino = state.priv.file.st_ino;
service->inf->priv.file.readpos = state.priv.file.readpos;
}
}
}
}
/* ------------------------------------------------------------------ Public */
int State_open() {
State_close();
if ((file = open(Run.statefile, O_RDWR | O_CREAT, 0600)) == -1) {
LogError("Cannot open for write -- %s\n", STRERROR);
return FALSE;
}
atexit(State_close);
return TRUE;
}
void State_close() {
if (file != -1) {
if (close(file) == -1)
LogError("State file '%s': close error -- %s\n", Run.statefile, STRERROR);
else
file = -1;
}
}
void State_save() {
TRY
{
if (ftruncate(file, 0L) == -1)
THROW(IOException, "Unable to truncate");
if (lseek(file, 0L, SEEK_SET) == -1)
THROW(IOException, "Unable to seek");
int magic = 0;
if (write(file, &magic, sizeof(magic)) != sizeof(magic))
THROW(IOException, "Unable to write magic");
// Save always using the latest format version
int version = StateVersion1;
if (write(file, &version, sizeof(version)) != sizeof(version))
THROW(IOException, "Unable to write format version");
for (Service_T service = servicelist; service; service = service->next) {
State1_T state;
memset(&state, 0, sizeof(state));
snprintf(state.name, sizeof(state.name), "%s", service->name);
state.type = service->type;
state.monitor = service->monitor & ~MONITOR_WAITING;
state.nstart = service->nstart;
state.ncycle = service->ncycle;
if (service->type == TYPE_FILE) {
state.priv.file.st_ino = service->inf->priv.file.st_ino;
state.priv.file.readpos = service->inf->priv.file.readpos;
}
if (write(file, &state, sizeof(state)) != sizeof(state))
THROW(IOException, "Unable to write service state");
}
}
ELSE
{
LogError("State file '%s': %s\n", Run.statefile, Exception_frame.message);
}
END_TRY;
}
void State_update() {
TRY
{
if (lseek(file, 0L, SEEK_SET) == -1)
THROW(IOException, "Unable to seek");
int magic;
if (read(file, &magic, sizeof(magic)) != sizeof(magic))
THROW(IOException, "Unable to read magic");
if (magic > 0) {
// The statefile format of Monit <= 5.3, the magic is number of services, followed by State0_T structures
update_v0(magic);
} else {
// The extended statefile format (Monit >= 5.4)
int version;
if (read(file, &version, sizeof(version)) != sizeof(version))
THROW(IOException, "Unable to read version");
// Currently the extended format has only one version, additional versions can be added here
if (version == StateVersion1)
update_v1();
else
LogWarning("State file '%s': incompatible version %d\n", Run.statefile, version);
}
}
ELSE
{
LogError("State file '%s': %s\n", Run.statefile, Exception_frame.message);
}
END_TRY;
}