Use ignorelist_t and fix leaks
[collectd.git] / src / procevent.c
1 /**
2  * collectd - src/procevent.c
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  *
22  * Authors:
23  *   Red Hat NFVPE
24  *     Andrew Bays <abays at redhat.com>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31 #include "utils_complain.h"
32 #include "utils_ignorelist.h"
33
34 #include <errno.h>
35 #include <pthread.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <sys/socket.h>
39 #include <unistd.h>
40
41 #include <dirent.h>
42 #include <linux/cn_proc.h>
43 #include <linux/connector.h>
44 #include <linux/netlink.h>
45 #include <linux/rtnetlink.h>
46 #include <signal.h>
47 #include <stdbool.h>
48 #include <stdlib.h>
49 #include <string.h>
50
51 #include <yajl/yajl_common.h>
52 #include <yajl/yajl_gen.h>
53 #if HAVE_YAJL_YAJL_VERSION_H
54 #include <yajl/yajl_version.h>
55 #endif
56 #if defined(YAJL_MAJOR) && (YAJL_MAJOR > 1)
57 #define HAVE_YAJL_V2 1
58 #endif
59
60 #define PROCEVENT_EXITED 0
61 #define PROCEVENT_STARTED 1
62 #define PROCEVENT_FIELDS 4 // pid, status, extra, timestamp
63 #define BUFSIZE 512
64 #define PROCDIR "/proc"
65
66 #define PROCEVENT_DOMAIN_FIELD "domain"
67 #define PROCEVENT_DOMAIN_VALUE "fault"
68 #define PROCEVENT_EVENT_ID_FIELD "eventId"
69 #define PROCEVENT_EVENT_NAME_FIELD "eventName"
70 #define PROCEVENT_EVENT_NAME_DOWN_VALUE "down"
71 #define PROCEVENT_EVENT_NAME_UP_VALUE "up"
72 #define PROCEVENT_LAST_EPOCH_MICROSEC_FIELD "lastEpochMicrosec"
73 #define PROCEVENT_PRIORITY_FIELD "priority"
74 #define PROCEVENT_PRIORITY_VALUE "high"
75 #define PROCEVENT_REPORTING_ENTITY_NAME_FIELD "reportingEntityName"
76 #define PROCEVENT_REPORTING_ENTITY_NAME_VALUE "collectd procevent plugin"
77 #define PROCEVENT_SEQUENCE_FIELD "sequence"
78 #define PROCEVENT_SEQUENCE_VALUE "0"
79 #define PROCEVENT_SOURCE_NAME_FIELD "sourceName"
80 #define PROCEVENT_START_EPOCH_MICROSEC_FIELD "startEpochMicrosec"
81 #define PROCEVENT_VERSION_FIELD "version"
82 #define PROCEVENT_VERSION_VALUE "1.0"
83
84 #define PROCEVENT_ALARM_CONDITION_FIELD "alarmCondition"
85 #define PROCEVENT_ALARM_INTERFACE_A_FIELD "alarmInterfaceA"
86 #define PROCEVENT_EVENT_SEVERITY_FIELD "eventSeverity"
87 #define PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE "CRITICAL"
88 #define PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE "NORMAL"
89 #define PROCEVENT_EVENT_SOURCE_TYPE_FIELD "eventSourceType"
90 #define PROCEVENT_EVENT_SOURCE_TYPE_VALUE "process"
91 #define PROCEVENT_FAULT_FIELDS_FIELD "faultFields"
92 #define PROCEVENT_FAULT_FIELDS_VERSION_FIELD "faultFieldsVersion"
93 #define PROCEVENT_FAULT_FIELDS_VERSION_VALUE "1.0"
94 #define PROCEVENT_SPECIFIC_PROBLEM_FIELD "specificProblem"
95 #define PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE "down"
96 #define PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE "up"
97 #define PROCEVENT_VF_STATUS_FIELD "vfStatus"
98 #define PROCEVENT_VF_STATUS_CRITICAL_VALUE "Ready to terminate"
99 #define PROCEVENT_VF_STATUS_NORMAL_VALUE "Active"
100
101 /*
102  * Private data types
103  */
104
105 typedef struct {
106   int head;
107   int tail;
108   int maxLen;
109   long long unsigned int **buffer;
110 } circbuf_t;
111
112 struct processlist_s {
113   char *process;
114
115   uint32_t pid;
116
117   struct processlist_s *next;
118 };
119 typedef struct processlist_s processlist_t;
120
121 /*
122  * Private variables
123  */
124 static ignorelist_t *ignorelist = NULL;
125
126 static int procevent_thread_loop = 0;
127 static int procevent_thread_error = 0;
128 static pthread_t procevent_thread_id;
129 static pthread_mutex_t procevent_lock = PTHREAD_MUTEX_INITIALIZER;
130 static pthread_cond_t procevent_cond = PTHREAD_COND_INITIALIZER;
131 static pthread_mutex_t procevent_list_lock = PTHREAD_MUTEX_INITIALIZER;
132 static int nl_sock = -1;
133 static int buffer_length;
134 static circbuf_t ring;
135 static processlist_t *processlist_head = NULL;
136 static int event_id = 0;
137
138 static const char *config_keys[] = {"BufferLength", "Process", "RegexProcess"};
139 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
140
141 /*
142  * Private functions
143  */
144
145 static int gen_message_payload(int state, int pid, char *process,
146                                long long unsigned int timestamp, char **buf) {
147   const unsigned char *buf2;
148   yajl_gen g;
149   char json_str[DATA_MAX_NAME_LEN];
150
151 #if !defined(HAVE_YAJL_V2)
152   yajl_gen_config conf = {};
153
154   conf.beautify = 0;
155 #endif
156
157 #if HAVE_YAJL_V2
158   size_t len;
159   g = yajl_gen_alloc(NULL);
160   yajl_gen_config(g, yajl_gen_beautify, 0);
161 #else
162   unsigned int len;
163   g = yajl_gen_alloc(&conf, NULL);
164 #endif
165
166   yajl_gen_clear(g);
167
168   // *** BEGIN common event header ***
169
170   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
171     goto err;
172
173   // domain
174   if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_FIELD,
175                       strlen(PROCEVENT_DOMAIN_FIELD)) != yajl_gen_status_ok)
176     goto err;
177
178   if (yajl_gen_string(g, (u_char *)PROCEVENT_DOMAIN_VALUE,
179                       strlen(PROCEVENT_DOMAIN_VALUE)) != yajl_gen_status_ok)
180     goto err;
181
182   // eventId
183   if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_ID_FIELD,
184                       strlen(PROCEVENT_EVENT_ID_FIELD)) != yajl_gen_status_ok)
185     goto err;
186
187   event_id = event_id + 1;
188   int event_id_len = sizeof(char) * sizeof(int) * 4 + 1;
189   memset(json_str, '\0', DATA_MAX_NAME_LEN);
190   snprintf(json_str, event_id_len, "%d", event_id);
191
192   if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
193     goto err;
194   }
195
196   // eventName
197   if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_NAME_FIELD,
198                       strlen(PROCEVENT_EVENT_NAME_FIELD)) != yajl_gen_status_ok)
199     goto err;
200
201   int event_name_len = 0;
202   event_name_len = event_name_len + (sizeof(char) * sizeof(int) * 4); // pid
203   event_name_len = event_name_len + strlen(process);      // process name
204   event_name_len = event_name_len + (state == 0 ? 4 : 2); // "down" or "up"
205   event_name_len = event_name_len +
206                    13; // "process", 3 spaces, 2 parentheses and null-terminator
207   memset(json_str, '\0', DATA_MAX_NAME_LEN);
208   snprintf(json_str, event_name_len, "process %s (%d) %s", process, pid,
209            (state == 0 ? PROCEVENT_EVENT_NAME_DOWN_VALUE
210                        : PROCEVENT_EVENT_NAME_UP_VALUE));
211
212   if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
213       yajl_gen_status_ok) {
214     goto err;
215   }
216
217   // lastEpochMicrosec
218   if (yajl_gen_string(g, (u_char *)PROCEVENT_LAST_EPOCH_MICROSEC_FIELD,
219                       strlen(PROCEVENT_LAST_EPOCH_MICROSEC_FIELD)) !=
220       yajl_gen_status_ok)
221     goto err;
222
223   int last_epoch_microsec_len =
224       sizeof(char) * sizeof(long long unsigned int) * 4 + 1;
225   memset(json_str, '\0', DATA_MAX_NAME_LEN);
226   snprintf(json_str, last_epoch_microsec_len, "%llu",
227            (long long unsigned int)CDTIME_T_TO_US(cdtime()));
228
229   if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
230     goto err;
231   }
232
233   // priority
234   if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_FIELD,
235                       strlen(PROCEVENT_PRIORITY_FIELD)) != yajl_gen_status_ok)
236     goto err;
237
238   if (yajl_gen_string(g, (u_char *)PROCEVENT_PRIORITY_VALUE,
239                       strlen(PROCEVENT_PRIORITY_VALUE)) != yajl_gen_status_ok)
240     goto err;
241
242   // reportingEntityName
243   if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_FIELD,
244                       strlen(PROCEVENT_REPORTING_ENTITY_NAME_FIELD)) !=
245       yajl_gen_status_ok)
246     goto err;
247
248   if (yajl_gen_string(g, (u_char *)PROCEVENT_REPORTING_ENTITY_NAME_VALUE,
249                       strlen(PROCEVENT_REPORTING_ENTITY_NAME_VALUE)) !=
250       yajl_gen_status_ok)
251     goto err;
252
253   // sequence
254   if (yajl_gen_string(g, (u_char *)PROCEVENT_SEQUENCE_FIELD,
255                       strlen(PROCEVENT_SEQUENCE_FIELD)) != yajl_gen_status_ok)
256     goto err;
257
258   if (yajl_gen_number(g, PROCEVENT_SEQUENCE_VALUE,
259                       strlen(PROCEVENT_SEQUENCE_VALUE)) != yajl_gen_status_ok)
260     goto err;
261
262   // sourceName
263   if (yajl_gen_string(g, (u_char *)PROCEVENT_SOURCE_NAME_FIELD,
264                       strlen(PROCEVENT_SOURCE_NAME_FIELD)) !=
265       yajl_gen_status_ok)
266     goto err;
267
268   if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
269       yajl_gen_status_ok)
270     goto err;
271
272   // startEpochMicrosec
273   if (yajl_gen_string(g, (u_char *)PROCEVENT_START_EPOCH_MICROSEC_FIELD,
274                       strlen(PROCEVENT_START_EPOCH_MICROSEC_FIELD)) !=
275       yajl_gen_status_ok)
276     goto err;
277
278   int start_epoch_microsec_len =
279       sizeof(char) * sizeof(long long unsigned int) * 4 + 1;
280   memset(json_str, '\0', DATA_MAX_NAME_LEN);
281   snprintf(json_str, start_epoch_microsec_len, "%llu",
282            (long long unsigned int)timestamp);
283
284   if (yajl_gen_number(g, json_str, strlen(json_str)) != yajl_gen_status_ok) {
285     goto err;
286   }
287
288   // version
289   if (yajl_gen_string(g, (u_char *)PROCEVENT_VERSION_FIELD,
290                       strlen(PROCEVENT_VERSION_FIELD)) != yajl_gen_status_ok)
291     goto err;
292
293   if (yajl_gen_number(g, PROCEVENT_VERSION_VALUE,
294                       strlen(PROCEVENT_VERSION_VALUE)) != yajl_gen_status_ok)
295     goto err;
296
297   // *** END common event header ***
298
299   // *** BEGIN fault fields ***
300
301   if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_FIELD,
302                       strlen(PROCEVENT_FAULT_FIELDS_FIELD)) !=
303       yajl_gen_status_ok)
304     goto err;
305
306   if (yajl_gen_map_open(g) != yajl_gen_status_ok)
307     goto err;
308
309   // alarmCondition
310   if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_CONDITION_FIELD,
311                       strlen(PROCEVENT_ALARM_CONDITION_FIELD)) !=
312       yajl_gen_status_ok)
313     goto err;
314
315   int alarm_condition_len = 0;
316   alarm_condition_len =
317       alarm_condition_len + (sizeof(char) * sizeof(int) * 4);  // pid
318   alarm_condition_len = alarm_condition_len + strlen(process); // process name
319   alarm_condition_len =
320       alarm_condition_len + 25; // "process", "state", "change", 4 spaces, 2
321                                 // parentheses and null-terminator
322   memset(json_str, '\0', DATA_MAX_NAME_LEN);
323   snprintf(json_str, alarm_condition_len, "process %s (%d) state change",
324            process, pid);
325
326   if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
327       yajl_gen_status_ok) {
328     goto err;
329   }
330
331   // alarmInterfaceA
332   if (yajl_gen_string(g, (u_char *)PROCEVENT_ALARM_INTERFACE_A_FIELD,
333                       strlen(PROCEVENT_ALARM_INTERFACE_A_FIELD)) !=
334       yajl_gen_status_ok)
335     goto err;
336
337   if (yajl_gen_string(g, (u_char *)process, strlen(process)) !=
338       yajl_gen_status_ok)
339     goto err;
340
341   // eventSeverity
342   if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SEVERITY_FIELD,
343                       strlen(PROCEVENT_EVENT_SEVERITY_FIELD)) !=
344       yajl_gen_status_ok)
345     goto err;
346
347   if (yajl_gen_string(
348           g, (u_char *)(state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
349                                    : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE),
350           strlen((state == 0 ? PROCEVENT_EVENT_SEVERITY_CRITICAL_VALUE
351                              : PROCEVENT_EVENT_SEVERITY_NORMAL_VALUE))) !=
352       yajl_gen_status_ok)
353     goto err;
354
355   // eventSourceType
356   if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_FIELD,
357                       strlen(PROCEVENT_EVENT_SOURCE_TYPE_FIELD)) !=
358       yajl_gen_status_ok)
359     goto err;
360
361   if (yajl_gen_string(g, (u_char *)PROCEVENT_EVENT_SOURCE_TYPE_VALUE,
362                       strlen(PROCEVENT_EVENT_SOURCE_TYPE_VALUE)) !=
363       yajl_gen_status_ok)
364     goto err;
365
366   // faultFieldsVersion
367   if (yajl_gen_string(g, (u_char *)PROCEVENT_FAULT_FIELDS_VERSION_FIELD,
368                       strlen(PROCEVENT_FAULT_FIELDS_VERSION_FIELD)) !=
369       yajl_gen_status_ok)
370     goto err;
371
372   if (yajl_gen_number(g, PROCEVENT_FAULT_FIELDS_VERSION_VALUE,
373                       strlen(PROCEVENT_FAULT_FIELDS_VERSION_VALUE)) !=
374       yajl_gen_status_ok)
375     goto err;
376
377   // specificProblem
378   if (yajl_gen_string(g, (u_char *)PROCEVENT_SPECIFIC_PROBLEM_FIELD,
379                       strlen(PROCEVENT_SPECIFIC_PROBLEM_FIELD)) !=
380       yajl_gen_status_ok)
381     goto err;
382
383   int specific_problem_len = 0;
384   specific_problem_len =
385       specific_problem_len + (sizeof(char) * sizeof(int) * 4);   // pid
386   specific_problem_len = specific_problem_len + strlen(process); // process name
387   specific_problem_len =
388       specific_problem_len + (state == 0 ? 4 : 2); // "down" or "up"
389   specific_problem_len =
390       specific_problem_len +
391       13; // "process", 3 spaces, 2 parentheses and null-terminator
392   memset(json_str, '\0', DATA_MAX_NAME_LEN);
393   snprintf(json_str, specific_problem_len, "process %s (%d) %s", process, pid,
394            (state == 0 ? PROCEVENT_SPECIFIC_PROBLEM_DOWN_VALUE
395                        : PROCEVENT_SPECIFIC_PROBLEM_UP_VALUE));
396
397   if (yajl_gen_string(g, (u_char *)json_str, strlen(json_str)) !=
398       yajl_gen_status_ok) {
399     goto err;
400   }
401
402   // vfStatus
403   if (yajl_gen_string(g, (u_char *)PROCEVENT_VF_STATUS_FIELD,
404                       strlen(PROCEVENT_VF_STATUS_FIELD)) != yajl_gen_status_ok)
405     goto err;
406
407   if (yajl_gen_string(
408           g, (u_char *)(state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
409                                    : PROCEVENT_VF_STATUS_NORMAL_VALUE),
410           strlen((state == 0 ? PROCEVENT_VF_STATUS_CRITICAL_VALUE
411                              : PROCEVENT_VF_STATUS_NORMAL_VALUE))) !=
412       yajl_gen_status_ok)
413     goto err;
414
415   if (yajl_gen_map_close(g) != yajl_gen_status_ok)
416     goto err;
417
418   // *** END fault fields ***
419
420   if (yajl_gen_map_close(g) != yajl_gen_status_ok)
421     goto err;
422
423   if (yajl_gen_get_buf(g, &buf2, &len) != yajl_gen_status_ok)
424     goto err;
425
426   *buf = malloc(strlen((char *)buf2) + 1);
427
428   if (*buf == NULL) {
429     char errbuf[1024];
430     ERROR("procevent plugin: malloc failed during gen_message_payload: %s",
431           sstrerror(errno, errbuf, sizeof(errbuf)));
432     goto err;
433   }
434
435   sstrncpy(*buf, (char *)buf2, strlen((char *)buf2) + 1);
436
437   yajl_gen_free(g);
438
439   return 0;
440
441 err:
442   yajl_gen_free(g);
443   ERROR("procevent plugin: gen_message_payload failed to generate JSON");
444   return -1;
445 }
446
447 // Does /proc/<pid>/comm contain a process name we are interested in?
448 static processlist_t *process_check(int pid) {
449   int len, is_match, retval;
450   char file[BUFSIZE];
451   FILE *fh;
452   char buffer[BUFSIZE];
453
454   len = snprintf(file, sizeof(file), PROCDIR "/%d/comm", pid);
455
456   if ((len < 0) || (len >= BUFSIZE)) {
457     WARNING("procevent process_check: process name too large");
458     return NULL;
459   }
460
461   if (NULL == (fh = fopen(file, "r"))) {
462     // No /proc/<pid>/comm for this pid, just ignore
463     DEBUG("procevent plugin: no comm file available for pid %d", pid);
464     return NULL;
465   }
466
467   retval = fscanf(fh, "%[^\n]", buffer);
468
469   if (retval < 0) {
470     WARNING("procevent process_check: unable to read comm file for pid %d",
471             pid);
472     fclose(fh);
473     return NULL;
474   }
475
476   // Now that we have the process name in the buffer, check if we are
477   // even interested in it
478   if (ignorelist_match(ignorelist, buffer) != 0) {
479     DEBUG("procevent process_check: ignoring process %s (%d)", buffer, pid);
480     fclose(fh);
481     return NULL;
482   }
483
484   if (fh != NULL) {
485     fclose(fh);
486     fh = NULL;
487   }
488
489   //
490   // Go through the processlist linked list and look for the process name
491   // in /proc/<pid>/comm.  If found:
492   // 1. If pl->pid is -1, then set pl->pid to <pid> (and return that object)
493   // 2. If pl->pid is not -1, then another <process name> process was already
494   //    found.  If <pid> == pl->pid, this is an old match, so do nothing.
495   //    If the <pid> is different, however, make a new processlist_t and
496   //    associate <pid> with it (with the same process name as the existing).
497   //
498
499   pthread_mutex_lock(&procevent_list_lock);
500
501   processlist_t *pl;
502   processlist_t *match = NULL;
503
504   for (pl = processlist_head; pl != NULL; pl = pl->next) {
505
506     is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
507
508     if (is_match == 1) {
509       DEBUG("procevent plugin: process %d name match for %s", pid, buffer);
510
511       if (pl->pid == pid) {
512         // this is a match, and we've already stored the exact pid/name combo
513         match = pl;
514         break;
515       } else if (pl->pid == -1) {
516         // this is a match, and we've found a candidate processlist_t to store
517         // this new pid/name combo
518         pl->pid = pid;
519         match = pl;
520         break;
521       } else if (pl->pid != -1) {
522         // this is a match, but another instance of this process has already
523         // claimed this pid/name combo,
524         // so keep looking
525         match = pl;
526         continue;
527       }
528     }
529   }
530
531   if (match == NULL ||
532       (match != NULL && match->pid != -1 && match->pid != pid)) {
533     // if there wasn't an existing match, OR
534     // if there was a match but the associated processlist_t object already
535     // contained a pid/name combo,
536     // then make a new one and add it to the linked list
537
538     DEBUG(
539         "procevent plugin: allocating new processlist_t object for PID %d (%s)",
540         pid, buffer);
541
542     processlist_t *pl2;
543     char *process;
544
545     pl2 = malloc(sizeof(*pl2));
546     if (pl2 == NULL) {
547       char errbuf[1024];
548       ERROR("procevent plugin: malloc failed during process_check: %s",
549             sstrerror(errno, errbuf, sizeof(errbuf)));
550       pthread_mutex_unlock(&procevent_list_lock);
551       return NULL;
552     }
553
554     process = strdup(buffer);
555     if (process == NULL) {
556       char errbuf[1024];
557       sfree(pl2);
558       ERROR("procevent plugin: strdup failed during process_check: %s",
559             sstrerror(errno, errbuf, sizeof(errbuf)));
560       pthread_mutex_unlock(&procevent_list_lock);
561       return NULL;
562     }
563
564     pl2->process = process;
565     pl2->pid = pid;
566     pl2->next = processlist_head;
567     processlist_head = pl2;
568
569     match = pl2;
570   }
571
572   pthread_mutex_unlock(&procevent_list_lock);
573
574   return match;
575 }
576
577 // Does our map have this PID or name?
578 static processlist_t *process_map_check(int pid, char *process) {
579   processlist_t *pl;
580
581   pthread_mutex_lock(&procevent_list_lock);
582
583   for (pl = processlist_head; pl != NULL; pl = pl->next) {
584     int match_pid = 0;
585     int match_process = 0;
586     int match = 0;
587
588     if (pid > 0) {
589       if (pl->pid == pid)
590         match_pid = 1;
591     }
592
593     if (process != NULL) {
594       if (strcmp(pl->process, process) == 0)
595         match_process = 1;
596     }
597
598     if (pid > 0 && process == NULL && match_pid == 1)
599       match = 1;
600     else if (pid < 0 && process != NULL && match_process == 1)
601       match = 1;
602     else if (pid > 0 && process != NULL && match_pid == 1 && match_process == 1)
603       match = 1;
604
605     if (match == 1) {
606       pthread_mutex_unlock(&procevent_list_lock);
607       return pl;
608     }
609   }
610
611   pthread_mutex_unlock(&procevent_list_lock);
612
613   return NULL;
614 }
615
616 static int process_map_refresh(void) {
617   DIR *proc;
618
619   errno = 0;
620   proc = opendir(PROCDIR);
621   if (proc == NULL) {
622     char errbuf[1024];
623     ERROR("procevent plugin: fopen (%s): %s", PROCDIR,
624           sstrerror(errno, errbuf, sizeof(errbuf)));
625     return -1;
626   }
627
628   while (42) {
629     struct dirent *dent;
630     int len;
631     char file[BUFSIZE];
632
633     struct stat statbuf;
634
635     int status;
636
637     errno = 0;
638     dent = readdir(proc);
639     if (dent == NULL) {
640       char errbuf[4096];
641
642       if (errno == 0) /* end of directory */
643         break;
644
645       ERROR("procevent plugin: failed to read directory %s: %s", PROCDIR,
646             sstrerror(errno, errbuf, sizeof(errbuf)));
647       closedir(proc);
648       return -1;
649     }
650
651     if (dent->d_name[0] == '.')
652       continue;
653
654     len = snprintf(file, sizeof(file), PROCDIR "/%s", dent->d_name);
655     if ((len < 0) || (len >= BUFSIZE))
656       continue;
657
658     status = stat(file, &statbuf);
659     if (status != 0) {
660       char errbuf[4096];
661       WARNING("procevent plugin: stat (%s) failed: %s", file,
662               sstrerror(errno, errbuf, sizeof(errbuf)));
663       continue;
664     }
665
666     if (!S_ISDIR(statbuf.st_mode))
667       continue;
668
669     len = snprintf(file, sizeof(file), PROCDIR "/%s/comm", dent->d_name);
670     if ((len < 0) || (len >= BUFSIZE))
671       continue;
672
673     int not_number = 0;
674
675     for (int i = 0; i < strlen(dent->d_name); i++) {
676       if (!isdigit(dent->d_name[i])) {
677         not_number = 1;
678         break;
679       }
680     }
681
682     if (not_number != 0)
683       continue;
684
685     // Check if we need to store this pid/name combo in our processlist_t linked
686     // list
687     int this_pid = atoi(dent->d_name);
688     processlist_t *pl = process_check(this_pid);
689
690     if (pl != NULL)
691       DEBUG("procevent plugin: process map refreshed for PID %d and name %s",
692             this_pid, pl->process);
693   }
694
695   closedir(proc);
696
697   return 0;
698 }
699
700 static int nl_connect() {
701   int rc;
702   struct sockaddr_nl sa_nl;
703
704   nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
705   if (nl_sock == -1) {
706     ERROR("procevent plugin: socket open failed.");
707     return -1;
708   }
709
710   sa_nl.nl_family = AF_NETLINK;
711   sa_nl.nl_groups = CN_IDX_PROC;
712   sa_nl.nl_pid = getpid();
713
714   rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
715   if (rc == -1) {
716     ERROR("procevent plugin: socket bind failed.");
717     close(nl_sock);
718     return -1;
719   }
720
721   return 0;
722 }
723
724 static int set_proc_ev_listen(bool enable) {
725   int rc;
726   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
727     struct nlmsghdr nl_hdr;
728     struct __attribute__((__packed__)) {
729       struct cn_msg cn_msg;
730       enum proc_cn_mcast_op cn_mcast;
731     };
732   } nlcn_msg;
733
734   memset(&nlcn_msg, 0, sizeof(nlcn_msg));
735   nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
736   nlcn_msg.nl_hdr.nlmsg_pid = getpid();
737   nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
738
739   nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
740   nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
741   nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
742
743   nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
744
745   rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
746   if (rc == -1) {
747     ERROR("procevent plugin: subscribing to netlink process events failed.");
748     return -1;
749   }
750
751   return 0;
752 }
753
754 static int read_event() {
755   int status;
756   int ret = 0;
757   int proc_id = -1;
758   int proc_status = -1;
759   int proc_extra = -1;
760   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
761     struct nlmsghdr nl_hdr;
762     struct __attribute__((__packed__)) {
763       struct cn_msg cn_msg;
764       struct proc_event proc_ev;
765     };
766   } nlcn_msg;
767
768   if (nl_sock == -1)
769     return ret;
770
771   status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
772
773   if (status == 0) {
774     return 0;
775   } else if (status == -1) {
776     if (errno != EINTR) {
777       ERROR("procevent plugin: socket receive error: %d", errno);
778       return -1;
779     }
780   }
781
782   switch (nlcn_msg.proc_ev.what) {
783   case PROC_EVENT_NONE:
784   case PROC_EVENT_FORK:
785   case PROC_EVENT_UID:
786   case PROC_EVENT_GID:
787     // Not of interest in current version
788     break;
789   case PROC_EVENT_EXEC:
790     proc_status = PROCEVENT_STARTED;
791     proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
792     break;
793   case PROC_EVENT_EXIT:
794     proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
795     proc_status = PROCEVENT_EXITED;
796     proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
797     break;
798   default:
799     break;
800   }
801
802   // If we're interested in this process status event, place the event
803   // in the ring buffer for consumption by the main polling thread.
804
805   if (proc_status != -1) {
806     pthread_mutex_unlock(&procevent_lock);
807
808     int next = ring.head + 1;
809     if (next >= ring.maxLen)
810       next = 0;
811
812     if (next == ring.tail) {
813       WARNING("procevent plugin: ring buffer full");
814     } else {
815       DEBUG("procevent plugin: Process %d status is now %s at %llu", proc_id,
816             (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"),
817             (long long unsigned int)CDTIME_T_TO_US(cdtime()));
818
819       if (proc_status == PROCEVENT_EXITED) {
820         ring.buffer[ring.head][0] = proc_id;
821         ring.buffer[ring.head][1] = proc_status;
822         ring.buffer[ring.head][2] = proc_extra;
823         ring.buffer[ring.head][3] =
824             (long long unsigned int)CDTIME_T_TO_US(cdtime());
825       } else {
826         ring.buffer[ring.head][0] = proc_id;
827         ring.buffer[ring.head][1] = proc_status;
828         ring.buffer[ring.head][2] = 0;
829         ring.buffer[ring.head][3] =
830             (long long unsigned int)CDTIME_T_TO_US(cdtime());
831       }
832
833       ring.head = next;
834     }
835
836     pthread_mutex_unlock(&procevent_lock);
837   }
838
839   return ret;
840 }
841
842 static void *procevent_thread(void *arg) /* {{{ */
843 {
844   pthread_mutex_lock(&procevent_lock);
845
846   while (procevent_thread_loop > 0) {
847     int status;
848
849     pthread_mutex_unlock(&procevent_lock);
850
851     usleep(1000);
852
853     status = read_event();
854
855     pthread_mutex_lock(&procevent_lock);
856
857     if (status < 0) {
858       procevent_thread_error = 1;
859       break;
860     }
861
862     if (procevent_thread_loop <= 0)
863       break;
864   } /* while (procevent_thread_loop > 0) */
865
866   pthread_mutex_unlock(&procevent_lock);
867
868   return ((void *)0);
869 } /* }}} void *procevent_thread */
870
871 static int start_thread(void) /* {{{ */
872 {
873   int status;
874
875   pthread_mutex_lock(&procevent_lock);
876
877   if (procevent_thread_loop != 0) {
878     pthread_mutex_unlock(&procevent_lock);
879     return (0);
880   }
881
882   if (nl_sock == -1) {
883     status = nl_connect();
884
885     if (status != 0)
886       return status;
887
888     status = set_proc_ev_listen(true);
889     if (status == -1)
890       return status;
891   }
892
893   DEBUG("procevent plugin: socket created and bound");
894
895   procevent_thread_loop = 1;
896   procevent_thread_error = 0;
897
898   status = plugin_thread_create(&procevent_thread_id, /* attr = */ NULL,
899                                 procevent_thread,
900                                 /* arg = */ (void *)0, "procevent");
901   if (status != 0) {
902     procevent_thread_loop = 0;
903     ERROR("procevent plugin: Starting thread failed.");
904     pthread_mutex_unlock(&procevent_lock);
905     return (-1);
906   }
907
908   pthread_mutex_unlock(&procevent_lock);
909   return (0);
910 } /* }}} int start_thread */
911
912 static int stop_thread(int shutdown) /* {{{ */
913 {
914   int status;
915
916   if (nl_sock != -1) {
917     status = close(nl_sock);
918     if (status != 0) {
919       ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
920             status, strerror(errno));
921       return (-1);
922     } else
923       nl_sock = -1;
924   }
925
926   pthread_mutex_lock(&procevent_lock);
927
928   if (procevent_thread_loop == 0) {
929     pthread_mutex_unlock(&procevent_lock);
930     return (-1);
931   }
932
933   procevent_thread_loop = 0;
934   pthread_cond_broadcast(&procevent_cond);
935   pthread_mutex_unlock(&procevent_lock);
936
937   if (shutdown == 1) {
938     // Calling pthread_cancel here in
939     // the case of a shutdown just assures that the thread is
940     // gone and that the process has been fully terminated.
941
942     DEBUG("procevent plugin: Canceling thread for process shutdown");
943
944     status = pthread_cancel(procevent_thread_id);
945
946     if (status != 0) {
947       ERROR("procevent plugin: Unable to cancel thread: %d", status);
948       status = -1;
949     }
950   } else {
951     status = pthread_join(procevent_thread_id, /* return = */ NULL);
952     if (status != 0) {
953       ERROR("procevent plugin: Stopping thread failed.");
954       status = -1;
955     }
956   }
957
958   pthread_mutex_lock(&procevent_lock);
959   memset(&procevent_thread_id, 0, sizeof(procevent_thread_id));
960   procevent_thread_error = 0;
961   pthread_mutex_unlock(&procevent_lock);
962
963   DEBUG("procevent plugin: Finished requesting stop of thread");
964
965   return (status);
966 } /* }}} int stop_thread */
967
968 static int procevent_init(void) /* {{{ */
969 {
970   int status;
971
972   ring.head = 0;
973   ring.tail = 0;
974   ring.maxLen = buffer_length;
975   ring.buffer = (long long unsigned int **)malloc(
976       buffer_length * sizeof(long long unsigned int *));
977
978   for (int i = 0; i < buffer_length; i++) {
979     ring.buffer[i] = (long long unsigned int *)malloc(
980         PROCEVENT_FIELDS * sizeof(long long unsigned int));
981   }
982
983   status = process_map_refresh();
984
985   if (status == -1) {
986     ERROR("procevent plugin: Initial process mapping failed.");
987     return (-1);
988   }
989
990   if (processlist_head == NULL) {
991     NOTICE("procevent plugin: No processes have been configured.");
992     return (-1);
993   }
994
995   return (start_thread());
996 } /* }}} int procevent_init */
997
998 static int procevent_config(const char *key, const char *value) /* {{{ */
999 {
1000   int status;
1001
1002   if (ignorelist == NULL)
1003     ignorelist = ignorelist_create(/* invert = */ 1);
1004
1005   if (strcasecmp(key, "BufferLength") == 0) {
1006     buffer_length = atoi(value);
1007   } else if (strcasecmp(key, "Process") == 0) {
1008     ignorelist_add(ignorelist, value);
1009   } else if (strcasecmp(key, "RegexProcess") == 0) {
1010 #if HAVE_REGEX_H
1011     status = ignorelist_add(ignorelist, value);
1012
1013     if (status != 0) {
1014       ERROR("procevent plugin: invalid regular expression: %s", value);
1015       return (1);
1016     }
1017 #else
1018     WARNING("procevent plugin: The plugin has been compiled without support "
1019             "for the \"RegexProcess\" option.");
1020 #endif
1021   } else {
1022     return (-1);
1023   }
1024
1025   return (0);
1026 } /* }}} int procevent_config */
1027
1028 static void procevent_dispatch_notification(int pid, const char *type, /* {{{ */
1029                                             gauge_t value, char *process,
1030                                             long long unsigned int timestamp) {
1031   char *buf = NULL;
1032   notification_t n = {NOTIF_FAILURE, cdtime(), "", "", "procevent", "", "", "",
1033                       NULL};
1034
1035   if (value == 1)
1036     n.severity = NOTIF_OKAY;
1037
1038   sstrncpy(n.host, hostname_g, sizeof(n.host));
1039   sstrncpy(n.plugin_instance, process, sizeof(n.plugin_instance));
1040   sstrncpy(n.type, "gauge", sizeof(n.type));
1041   sstrncpy(n.type_instance, "process_status", sizeof(n.type_instance));
1042
1043   gen_message_payload(value, pid, process, timestamp, &buf);
1044
1045   notification_meta_t *m = calloc(1, sizeof(*m));
1046
1047   if (m == NULL) {
1048     char errbuf[1024];
1049     sfree(buf);
1050     ERROR("procevent plugin: unable to allocate metadata: %s",
1051           sstrerror(errno, errbuf, sizeof(errbuf)));
1052     return;
1053   }
1054
1055   sstrncpy(m->name, "ves", sizeof(m->name));
1056   m->nm_value.nm_string = sstrdup(buf);
1057   m->type = NM_TYPE_STRING;
1058   n.meta = m;
1059
1060   DEBUG("procevent plugin: notification message: %s",
1061         n.meta->nm_value.nm_string);
1062
1063   DEBUG("procevent plugin: dispatching state %d for PID %d (%s)", (int)value,
1064         pid, process);
1065
1066   plugin_dispatch_notification(&n);
1067   plugin_notification_meta_free(n.meta);
1068
1069   // malloc'd in gen_message_payload
1070   if (buf != NULL)
1071     sfree(buf);
1072 }
1073
1074 static int procevent_read(void) /* {{{ */
1075 {
1076   if (procevent_thread_error != 0) {
1077     ERROR(
1078         "procevent plugin: The interface thread had a problem. Restarting it.");
1079
1080     stop_thread(0);
1081
1082     start_thread();
1083
1084     return (-1);
1085   } /* if (procevent_thread_error != 0) */
1086
1087   pthread_mutex_lock(&procevent_lock);
1088
1089   while (ring.head != ring.tail) {
1090     int next = ring.tail + 1;
1091
1092     if (next >= ring.maxLen)
1093       next = 0;
1094
1095     if (ring.buffer[ring.tail][1] == PROCEVENT_EXITED) {
1096       processlist_t *pl = process_map_check(ring.buffer[ring.tail][0], NULL);
1097
1098       if (pl != NULL) {
1099         // This process is of interest to us, so publish its EXITED status
1100         procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
1101                                         ring.buffer[ring.tail][1], pl->process,
1102                                         ring.buffer[ring.tail][3]);
1103         DEBUG("procevent plugin: PID %d (%s) EXITED, removing PID from process "
1104               "list",
1105               pl->pid, pl->process);
1106         pl->pid = -1;
1107       }
1108     } else if (ring.buffer[ring.tail][1] == PROCEVENT_STARTED) {
1109       // a new process has started, so check if we should monitor it
1110       processlist_t *pl = process_check(ring.buffer[ring.tail][0]);
1111
1112       if (pl != NULL) {
1113         // This process is of interest to us, so publish its STARTED status
1114         procevent_dispatch_notification(ring.buffer[ring.tail][0], "gauge",
1115                                         ring.buffer[ring.tail][1], pl->process,
1116                                         ring.buffer[ring.tail][3]);
1117         DEBUG(
1118             "procevent plugin: PID %d (%s) STARTED, adding PID to process list",
1119             pl->pid, pl->process);
1120       }
1121     }
1122
1123     ring.tail = next;
1124   }
1125
1126   pthread_mutex_unlock(&procevent_lock);
1127
1128   return (0);
1129 } /* }}} int procevent_read */
1130
1131 static int procevent_shutdown(void) /* {{{ */
1132 {
1133   processlist_t *pl;
1134
1135   DEBUG("procevent plugin: Shutting down thread.");
1136
1137   if (stop_thread(1) < 0)
1138     return (-1);
1139
1140   for (int i = 0; i < buffer_length; i++) {
1141     free(ring.buffer[i]);
1142   }
1143
1144   free(ring.buffer);
1145
1146   pl = processlist_head;
1147   while (pl != NULL) {
1148     processlist_t *pl_next;
1149
1150     pl_next = pl->next;
1151
1152     sfree(pl->process);
1153     sfree(pl);
1154
1155     pl = pl_next;
1156   }
1157
1158   return (0);
1159 } /* }}} int procevent_shutdown */
1160
1161 void module_register(void) {
1162   plugin_register_config("procevent", procevent_config, config_keys,
1163                          config_keys_num);
1164   plugin_register_init("procevent", procevent_init);
1165   plugin_register_read("procevent", procevent_read);
1166   plugin_register_shutdown("procevent", procevent_shutdown);
1167 } /* void module_register */