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