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(&regex, 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(&regex);
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(&regex, 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