1 /**
2 * collectd - src/mdevents.c
3 *
4 * Copyright(c) 2018 Intel Corporation. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * Authors:
25 * Krzysztof Kazimierczak <krzysztof.kazimierczak@intel.com>
26 * Maciej Fijalkowski <maciej.fijalkowski@intel.com>
27 * Michal Kobylinski <michal.kobylinski@intel.com>
28 **/
29
30 #include "collectd.h"
31 #include "plugin.h"
32 #include "utils/common/common.h"
33 #include "utils/ignorelist/ignorelist.h"
34
35 #include <limits.h>
36 #include <regex.h>
37 #include <stdio.h>
38 #include <string.h>
39
40 #define MD_EVENTS_PLUGIN "mdevents"
41 #define DAEMON_NAME "mdadm"
42
43 #define MD_EVENTS_ERROR(err_msg, ...) \
44 ERROR(MD_EVENTS_PLUGIN ": %s: " err_msg, __FUNCTION__, ##__VA_ARGS__)
45
46 // Syslog can be located under different paths on various linux distros;
47 // The following two cover the debian-based and redhat distros
48 #define SYSLOG_PATH "/var/log/syslog"
49 #define SYSLOG_MSG_PATH "/var/log/messages"
50
51 #define MAX_SYSLOG_MESSAGE_LENGTH 1024
52 #define MAX_ERROR_MSG 100
53 #define MAX_MATCHES 4
54 #define MD_ARRAY_NAME_PREFIX_LEN 7
55
56 static FILE *syslog_file;
57 static regex_t regex;
58 static ignorelist_t *event_ignorelist;
59 static ignorelist_t *array_ignorelist;
60
61 static char regex_pattern[] =
62 "mdadm[\\[0-9]+\\]: ([a-zA-Z]+) event detected on md"
63 " device ([a-z0-9\\/\\.\\-]+)[^\\/\n]*([a-z0-9\\/\\.\\-]+)?";
64
65 static const char *config_keys[] = {"Array", "Event", "IgnoreArray",
66 "IgnoreEvent"};
67 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
68
69 enum md_events_regex_entries {
70 // matches of substrings in whole RE start from index 1
71 EVENT = 1,
72 MD_DEVICE = 2,
73 COMPONENT_DEVICE = 3,
74 };
75
76 typedef struct md_events_event_s {
77 char event_name[DATA_MAX_NAME_LEN];
78 char md_device[DATA_MAX_NAME_LEN];
79 char component_device[DATA_MAX_NAME_LEN];
80 } md_events_event_t;
81
82 static const char *md_events_critical_events[] = {"DeviceDisappeared",
83 "DegradedArray", "Fail"};
84 static const char *md_events_warning_events[] = {
85 "SparesMissing", "FailSpare", "MoveSpare", "RebuildFinished"};
86 static const char *md_events_informative_events[] = {
87 "RebuildStarted", "RebuildNN", "SpareActive", "NewArray", "TestMessage"};
88
md_events_classify_event(const char * event_name)89 static int md_events_classify_event(const char *event_name) {
90 int i;
91
92 for (i = 0; i < STATIC_ARRAY_SIZE(md_events_critical_events); i++) {
93 if (!strcmp(event_name, md_events_critical_events[i]))
94 return NOTIF_FAILURE;
95 }
96 for (i = 0; i < STATIC_ARRAY_SIZE(md_events_warning_events); i++) {
97 if (!strcmp(event_name, md_events_warning_events[i]))
98 return NOTIF_WARNING;
99 }
100 for (i = 0; i < STATIC_ARRAY_SIZE(md_events_informative_events); i++) {
101 if (!strcmp(event_name, md_events_informative_events[i]))
102 return NOTIF_OKAY;
103 }
104 // we do not support that event
105 return 0;
106 }
107
md_events_parse_events(const char * events,size_t len)108 int md_events_parse_events(const char *events, size_t len) {
109 char *event_buf;
110 char *event;
111 char *save_ptr;
112
113 // have an additional byte for nul terminator
114 len++;
115
116 if ((event_buf = calloc(1, len)) == NULL) {
117 MD_EVENTS_ERROR("calloc failed for event_buf\n");
118 return -1;
119 }
120
121 // need a non-const copy so that strtok can work on this
122 strncpy(event_buf, events, len);
123 event_buf[len - 1] = '\0';
124 event = strtok_r(event_buf, " ", &save_ptr);
125 if (event == NULL) {
126 MD_EVENTS_ERROR("Couldn't parse events specified by user\n");
127 free(event_buf);
128 return -1;
129 }
130 // verify that user-defined event from config is the one that
131 // we/mdadm support
132 if (md_events_classify_event(event)) {
133 ignorelist_add(event_ignorelist, event);
134 } else {
135 MD_EVENTS_ERROR("Unclassified event \"%s\"; Ignoring.\n", event);
136 free(event_buf);
137 return -1;
138 }
139
140 while ((event = strtok_r(NULL, " ", &save_ptr)) != NULL) {
141 if (md_events_classify_event(event)) {
142 ignorelist_add(event_ignorelist, event);
143 } else {
144 MD_EVENTS_ERROR("Unclassified event \"%s\"; Ignoring.\n", event);
145 free(event_buf);
146 return -1;
147 }
148 }
149 free(event_buf);
150 return 0;
151 }
152
md_events_parse_boolean(const char * bool_setting,ignorelist_t * list)153 static int md_events_parse_boolean(const char *bool_setting,
154 ignorelist_t *list) {
155 if (IS_TRUE(bool_setting)) {
156 ignorelist_set_invert(list, 0);
157 return 0;
158 } else if (IS_FALSE(bool_setting)) {
159 ignorelist_set_invert(list, 1);
160 return 0;
161 }
162 return 1;
163 }
164
md_events_config(const char * key,const char * value)165 static int md_events_config(const char *key, const char *value) {
166 size_t len = strlen(value);
167
168 if (array_ignorelist == NULL) {
169 array_ignorelist = ignorelist_create(/* invert = */ 1);
170 if (array_ignorelist == NULL)
171 return -1;
172 }
173 if (event_ignorelist == NULL) {
174 event_ignorelist = ignorelist_create(/* invert = */ 1);
175 if (event_ignorelist == NULL)
176 return -1;
177 }
178
179 if (!strcasecmp("Event", key) && len) {
180 if (md_events_parse_events(value, len)) {
181 MD_EVENTS_ERROR(
182 "Failed while parsing events, please check your config file");
183 return -1;
184 }
185 }
186 if (!strcasecmp("Array", key) && len) {
187 if (strncmp("/dev/md", value, MD_ARRAY_NAME_PREFIX_LEN)) {
188 MD_EVENTS_ERROR("The array name/regex must start with '/dev/md';"
189 " Ignoring %s\n",
190 value);
191 return -1;
192 } else {
193 ignorelist_add(array_ignorelist, value);
194 }
195 }
196 if (!strcasecmp("IgnoreArray", key)) {
197 if (md_events_parse_boolean(value, array_ignorelist)) {
198 MD_EVENTS_ERROR("Error while checking 'IgnoreArray' value, "
199 "is it boolean? Check the config file.");
200 return -1;
201 }
202 }
203 if (!strcasecmp("IgnoreEvent", key)) {
204 if (md_events_parse_boolean(value, event_ignorelist)) {
205 MD_EVENTS_ERROR("Error while checking 'IgnoreEvent' value, "
206 "is it boolean? Check the config file.");
207 return -1;
208 }
209 }
210
211 return 0;
212 }
213
md_events_handle_regex_error(int rc,regex_t * regex,const char * func_name)214 static void md_events_handle_regex_error(int rc, regex_t *regex,
215 const char *func_name) {
216 char buf[MAX_ERROR_MSG] = {};
217
218 regerror(rc, regex, buf, MAX_ERROR_MSG);
219 DEBUG("%s() failed with '%s'\n", func_name, buf);
220 }
221
md_events_compile_regex(regex_t * regex,const char * regex_pattern)222 static int md_events_compile_regex(regex_t *regex, const char *regex_pattern) {
223 int status = regcomp(regex, regex_pattern, REG_EXTENDED | REG_NEWLINE);
224
225 if (status) {
226 md_events_handle_regex_error(status, regex, "regcomp");
227 return -1;
228 }
229 return 0;
230 }
231
md_events_dispatch_notification(md_events_event_t * event,notification_t * notif)232 static int md_events_dispatch_notification(md_events_event_t *event,
233 notification_t *notif) {
234 int offset;
235 size_t len;
236
237 if (!notif || !event) {
238 MD_EVENTS_ERROR("Null pointer\n");
239 return -1;
240 }
241
242 len = strlen(hostname_g);
243 // we need to make sure that we don't overflow the notif->host buffer
244 // keep in mind that strlen(hostname_g) does not include the nul terminator
245 if (len > sizeof(notif->host) - 1)
246 len = sizeof(notif->host) - 1;
247 memcpy(notif->host, hostname_g, len);
248 notif->host[len] = '\0';
249
250 // with this string literal we are safe to copy
251 strncpy(notif->type, "gauge", sizeof(notif->type));
252 offset =
253 snprintf(notif->message, NOTIF_MAX_MSG_LEN, "event name %s, md array %s ",
254 event->event_name, event->md_device);
255 // this one is not present in every event;
256 if (event->component_device[0] != '\0') {
257 snprintf(notif->message + offset, NOTIF_MAX_MSG_LEN - offset,
258 "component device %s\n", event->component_device);
259 }
260 plugin_dispatch_notification(notif);
261
262 return 0;
263 }
264
265 // helper function to check whether regex match will fit onto buffer
266 // check whether the difference of indexes is bigger than max allowed length
md_events_get_max_len(regmatch_t match,size_t max_name_len)267 static inline size_t md_events_get_max_len(regmatch_t match,
268 size_t max_name_len) {
269 size_t len;
270
271 if (match.rm_eo - match.rm_so > max_name_len - 1)
272 len = max_name_len - 1;
273 else
274 len = match.rm_eo - match.rm_so;
275 return len;
276 }
277
md_events_copy_match(char * buf,const char * line,regmatch_t match)278 static void md_events_copy_match(char *buf, const char *line,
279 regmatch_t match) {
280 size_t bytes_to_copy = md_events_get_max_len(match, DATA_MAX_NAME_LEN);
281
282 memcpy(buf, &line[match.rm_so], bytes_to_copy);
283 buf[bytes_to_copy] = '\0';
284 }
285
md_events_match_regex(regex_t * regex,const char * to_match)286 static int md_events_match_regex(regex_t *regex, const char *to_match) {
287 regmatch_t matches[MAX_MATCHES] = {};
288 int status, severity;
289 md_events_event_t event = {};
290
291 status = regexec(regex, to_match, MAX_MATCHES, matches, 0);
292 if (status) {
293 md_events_handle_regex_error(status, regex, "regexec");
294 return -1;
295 }
296
297 // each element from matches array contains the indexes (start/end) within
298 // the string that we ran regexp on; use them to retrieve the substrings
299 md_events_copy_match(event.event_name, to_match, matches[EVENT]);
300 md_events_copy_match(event.md_device, to_match, matches[MD_DEVICE]);
301
302 // this one is not present in every event, regex API sets indexes to -1
303 // if the match wasn't found
304 if (matches[COMPONENT_DEVICE].rm_so != -1)
305 md_events_copy_match(event.component_device, to_match,
306 matches[COMPONENT_DEVICE]);
307
308 if (ignorelist_match(event_ignorelist, event.event_name))
309 return -1;
310
311 if (ignorelist_match(array_ignorelist, event.md_device))
312 return -1;
313
314 severity = md_events_classify_event(event.event_name);
315 if (!severity) {
316 MD_EVENTS_ERROR("Unsupported event %s\n", event.event_name);
317 return -1;
318 }
319
320 md_events_dispatch_notification(&event,
321 &(notification_t){.severity = severity,
322 .time = cdtime(),
323 .plugin = MD_EVENTS_PLUGIN,
324 .type_instance = ""});
325 return 0;
326 }
327
md_events_read(void)328 static int md_events_read(void) {
329 char syslog_line[MAX_SYSLOG_MESSAGE_LENGTH];
330 while (fgets(syslog_line, sizeof(syslog_line), syslog_file))
331 // don't check the return code here; exiting from read callback with
332 // nonzero status causes the suspension of next read call;
333 md_events_match_regex(®ex, syslog_line);
334
335 return 0;
336 }
337
md_events_shutdown(void)338 static int md_events_shutdown(void) {
339 if (syslog_file)
340 fclose(syslog_file);
341
342 regfree(®ex);
343 ignorelist_free(event_ignorelist);
344 ignorelist_free(array_ignorelist);
345
346 plugin_unregister_config(MD_EVENTS_PLUGIN);
347 plugin_unregister_read(MD_EVENTS_PLUGIN);
348 plugin_unregister_shutdown(MD_EVENTS_PLUGIN);
349
350 return 0;
351 }
352
md_events_init(void)353 static int md_events_init(void) {
354 syslog_file = fopen(SYSLOG_PATH, "r");
355 if (!syslog_file) {
356 syslog_file = fopen(SYSLOG_MSG_PATH, "r");
357 if (!syslog_file) {
358 MD_EVENTS_ERROR(
359 "/var/log/syslog and /var/log/messages files are not present. Are "
360 "you sure that you have rsyslog utility installed on your system?\n");
361 return -1;
362 }
363 }
364
365 // monitor events only from point of collectd start
366 if (fseek(syslog_file, 0, SEEK_END)) {
367 MD_EVENTS_ERROR("fseek on syslog file failed, error: %s\n",
368 strerror(errno));
369 fclose(syslog_file);
370 return -1;
371 }
372
373 if (md_events_compile_regex(®ex, regex_pattern)) {
374 fclose(syslog_file);
375 return -1;
376 }
377
378 return 0;
379 }
380
module_register(void)381 void module_register(void) {
382 plugin_register_init(MD_EVENTS_PLUGIN, md_events_init);
383 plugin_register_config(MD_EVENTS_PLUGIN, md_events_config, config_keys,
384 config_keys_num);
385 plugin_register_read(MD_EVENTS_PLUGIN, md_events_read);
386 plugin_register_shutdown(MD_EVENTS_PLUGIN, md_events_shutdown);
387 }
388