check fscanf return value in process_check
[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
33 #include <errno.h>
34 #include <pthread.h>
35 #include <regex.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 #define PROCEVENT_EXITED 0
52 #define PROCEVENT_STARTED 1
53 #define PROCEVENT_FIELDS 3 // pid, status, extra
54 #define BUFSIZE 512
55 #define PROCDIR "/proc"
56 #define PROCEVENT_REGEX_MATCHES 1
57
58 /*
59  * Private data types
60  */
61
62 typedef struct {
63   int head;
64   int tail;
65   int maxLen;
66   int **buffer;
67 } circbuf_t;
68
69 struct processlist_s {
70   char *process;
71   char *process_regex;
72
73   regex_t process_regex_obj;
74
75   uint32_t is_regex;
76   uint32_t pid;
77
78   struct processlist_s *next;
79 };
80 typedef struct processlist_s processlist_t;
81
82 /*
83  * Private variables
84  */
85
86 static int procevent_thread_loop = 0;
87 static int procevent_thread_error = 0;
88 static pthread_t procevent_thread_id;
89 static pthread_mutex_t procevent_lock = PTHREAD_MUTEX_INITIALIZER;
90 static pthread_cond_t procevent_cond = PTHREAD_COND_INITIALIZER;
91 static pthread_mutex_t procevent_list_lock = PTHREAD_MUTEX_INITIALIZER;
92 static int nl_sock = -1;
93 static int buffer_length;
94 static circbuf_t ring;
95 static processlist_t *processlist_head = NULL;
96
97 static const char *config_keys[] = {"BufferLength", "Process", "RegexProcess"};
98 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
99
100 /*
101  * Private functions
102  */
103
104 // Does /proc/<pid>/comm contain a process name we are interested in?
105 static processlist_t *process_check(int pid) {
106   int len, is_match, status, retval;
107   char file[BUFSIZE];
108   FILE *fh;
109   char buffer[BUFSIZE];
110   regmatch_t matches[PROCEVENT_REGEX_MATCHES];
111
112   len = snprintf(file, sizeof(file), PROCDIR "/%d/comm", pid);
113
114   if ((len < 0) || (len >= BUFSIZE)) {
115     WARNING("procevent process_check: process name too large");
116     return NULL;
117   }
118
119   if (NULL == (fh = fopen(file, "r"))) {
120     // No /proc/<pid>/comm for this pid, just ignore
121     DEBUG("procevent plugin: no comm file available for pid %d", pid);
122     return NULL;
123   }
124
125   retval = fscanf(fh, "%[^\n]", buffer);
126
127   if (retval < 0) {
128     WARNING("procevent process_check: unable to read comm file for pid %d",
129             pid);
130     return NULL;
131   }
132
133   //
134   // Go through the processlist linked list and look for the process name
135   // in /proc/<pid>/comm.  If found:
136   // 1. If pl->pid is -1, then set pl->pid to <pid>
137   // 2. If pl->pid is not -1, then another <process name> process was already
138   //    found.  If <pid> == pl->pid, this is an old match, so do nothing.
139   //    If the <pid> is different, however,  make a new processlist_t and
140   //    associate <pid> with it (with the same process name as the existing).
141   //
142
143   pthread_mutex_lock(&procevent_list_lock);
144
145   processlist_t *pl;
146   processlist_t *match = NULL;
147
148   for (pl = processlist_head; pl != NULL; pl = pl->next) {
149     if (pl->is_regex != 0) {
150       is_match = (regexec(&pl->process_regex_obj, buffer,
151                           PROCEVENT_REGEX_MATCHES, matches, 0) == 0
152                       ? 1
153                       : 0);
154     } else {
155       is_match = (strcmp(buffer, pl->process) == 0 ? 1 : 0);
156     }
157
158     if (is_match == 1) {
159       DEBUG("procevent plugin: process %d name match (pattern: %s) for %s", pid,
160             (pl->is_regex == 0 ? pl->process : pl->process_regex), buffer);
161
162       if (pl->is_regex == 1) {
163         // If this is a regex name, copy the actual process name into the object
164         // for cleaner log reporting
165
166         if (pl->process != NULL)
167           sfree(pl->process);
168         pl->process = strdup(buffer);
169         if (pl->process == NULL) {
170           char errbuf[1024];
171           ERROR("procevent plugin: strdup failed during process_check: %s",
172                 sstrerror(errno, errbuf, sizeof(errbuf)));
173           pthread_mutex_unlock(&procevent_list_lock);
174           return NULL;
175         }
176       }
177
178       if (pl->pid == pid) {
179         // this is a match, and we've already stored the exact pid/name combo
180         match = pl;
181         break;
182       } else if (pl->pid == -1) {
183         // this is a match, and we've found a candidate processlist_t to store
184         // this new pid/name combo
185         pl->pid = pid;
186         match = pl;
187         break;
188       } else if (pl->pid != -1) {
189         // this is a match, but another instance of this process has already
190         // claimed this pid/name combo,
191         // so keep looking
192         match = pl;
193         continue;
194       }
195     }
196   }
197
198   if (match != NULL && match->pid != -1 && match->pid != pid) {
199     // if there was a match but the associated processlist_t object already
200     // contained a pid/name combo,
201     // then make a new one and add it to the linked list
202
203     DEBUG(
204         "procevent plugin: allocating new processlist_t object for PID %d (%s)",
205         pid, match->process);
206
207     processlist_t *pl2;
208     char *process;
209     char *process_regex;
210
211     pl2 = malloc(sizeof(*pl2));
212     if (pl2 == NULL) {
213       char errbuf[1024];
214       ERROR("procevent plugin: malloc failed during process_check: %s",
215             sstrerror(errno, errbuf, sizeof(errbuf)));
216       pthread_mutex_unlock(&procevent_list_lock);
217       return NULL;
218     }
219
220     process = strdup(match->process);
221     if (process == NULL) {
222       char errbuf[1024];
223       sfree(pl2);
224       ERROR("procevent plugin: strdup failed during process_check: %s",
225             sstrerror(errno, errbuf, sizeof(errbuf)));
226       pthread_mutex_unlock(&procevent_list_lock);
227       return NULL;
228     }
229
230     if (match->is_regex == 1) {
231       pl2->is_regex = 1;
232       status =
233           regcomp(&pl2->process_regex_obj, match->process_regex, REG_EXTENDED);
234
235       if (status != 0) {
236         ERROR("procevent plugin: invalid regular expression: %s",
237               match->process_regex);
238         return NULL;
239       }
240
241       process_regex = strdup(match->process_regex);
242       if (process_regex == NULL) {
243         char errbuf[1024];
244         sfree(pl);
245         ERROR("procevent plugin: strdup failed during process_check: %s",
246               sstrerror(errno, errbuf, sizeof(errbuf)));
247         return NULL;
248       }
249
250       pl2->process_regex = process_regex;
251     }
252
253     pl2->process = process;
254     pl2->pid = pid;
255     pl2->next = processlist_head;
256     processlist_head = pl2;
257
258     match = pl2;
259   }
260
261   pthread_mutex_unlock(&procevent_list_lock);
262
263   if (fh != NULL) {
264     fclose(fh);
265     fh = NULL;
266   }
267
268   return match;
269 }
270
271 // Does our map have this PID or name?
272 static processlist_t *process_map_check(int pid, char *process) {
273   processlist_t *pl;
274
275   pthread_mutex_lock(&procevent_list_lock);
276
277   for (pl = processlist_head; pl != NULL; pl = pl->next) {
278     int match_pid = 0;
279     int match_process = 0;
280     int match = 0;
281
282     if (pid > 0) {
283       if (pl->pid == pid)
284         match_pid = 1;
285     }
286
287     if (process != NULL) {
288       if (strcmp(pl->process, process) == 0)
289         match_process = 1;
290     }
291
292     if (pid > 0 && process == NULL && match_pid == 1)
293       match = 1;
294     else if (pid < 0 && process != NULL && match_process == 1)
295       match = 1;
296     else if (pid > 0 && process != NULL && match_pid == 1 && match_process == 1)
297       match = 1;
298
299     if (match == 1) {
300       pthread_mutex_unlock(&procevent_list_lock);
301       return pl;
302     }
303   }
304
305   pthread_mutex_unlock(&procevent_list_lock);
306
307   return NULL;
308 }
309
310 static int process_map_refresh(void) {
311   DIR *proc;
312
313   errno = 0;
314   proc = opendir(PROCDIR);
315   if (proc == NULL) {
316     char errbuf[1024];
317     ERROR("procevent plugin: fopen (%s): %s", PROCDIR,
318           sstrerror(errno, errbuf, sizeof(errbuf)));
319     return -1;
320   }
321
322   while (42) {
323     struct dirent *dent;
324     int len;
325     char file[BUFSIZE];
326
327     struct stat statbuf;
328
329     int status;
330
331     errno = 0;
332     dent = readdir(proc);
333     if (dent == NULL) {
334       char errbuf[4096];
335
336       if (errno == 0) /* end of directory */
337         break;
338
339       ERROR("procevent plugin: failed to read directory %s: %s", PROCDIR,
340             sstrerror(errno, errbuf, sizeof(errbuf)));
341       closedir(proc);
342       return -1;
343     }
344
345     if (dent->d_name[0] == '.')
346       continue;
347
348     len = snprintf(file, sizeof(file), PROCDIR "/%s", dent->d_name);
349     if ((len < 0) || (len >= BUFSIZE))
350       continue;
351
352     status = stat(file, &statbuf);
353     if (status != 0) {
354       char errbuf[4096];
355       WARNING("procevent plugin: stat (%s) failed: %s", file,
356               sstrerror(errno, errbuf, sizeof(errbuf)));
357       continue;
358     }
359
360     if (!S_ISDIR(statbuf.st_mode))
361       continue;
362
363     len = snprintf(file, sizeof(file), PROCDIR "/%s/comm", dent->d_name);
364     if ((len < 0) || (len >= BUFSIZE))
365       continue;
366
367     int not_number = 0;
368
369     for (int i = 0; i < strlen(dent->d_name); i++) {
370       if (!isdigit(dent->d_name[i])) {
371         not_number = 1;
372         break;
373       }
374     }
375
376     if (not_number != 0)
377       continue;
378
379     // Check if we need to store this pid/name combo in our processlist_t linked
380     // list
381     int this_pid = atoi(dent->d_name);
382     processlist_t *pl = process_check(this_pid);
383
384     if (pl != NULL)
385       DEBUG("procevent plugin: process map refreshed for PID %d and name %s",
386             this_pid, pl->process);
387   }
388
389   closedir(proc);
390
391   return 0;
392 }
393
394 static int nl_connect() {
395   int rc;
396   struct sockaddr_nl sa_nl;
397
398   nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
399   if (nl_sock == -1) {
400     ERROR("procevent plugin: socket open failed.");
401     return -1;
402   }
403
404   sa_nl.nl_family = AF_NETLINK;
405   sa_nl.nl_groups = CN_IDX_PROC;
406   sa_nl.nl_pid = getpid();
407
408   rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
409   if (rc == -1) {
410     ERROR("procevent plugin: socket bind failed.");
411     close(nl_sock);
412     return -1;
413   }
414
415   return 0;
416 }
417
418 static int set_proc_ev_listen(bool enable) {
419   int rc;
420   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
421     struct nlmsghdr nl_hdr;
422     struct __attribute__((__packed__)) {
423       struct cn_msg cn_msg;
424       enum proc_cn_mcast_op cn_mcast;
425     };
426   } nlcn_msg;
427
428   memset(&nlcn_msg, 0, sizeof(nlcn_msg));
429   nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
430   nlcn_msg.nl_hdr.nlmsg_pid = getpid();
431   nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
432
433   nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
434   nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
435   nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
436
437   nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
438
439   rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
440   if (rc == -1) {
441     ERROR("procevent plugin: subscribing to netlink process events failed.");
442     return -1;
443   }
444
445   return 0;
446 }
447
448 static int read_event() {
449   int status;
450   int ret = 0;
451   int proc_id = -1;
452   int proc_status = -1;
453   int proc_extra = -1;
454   struct __attribute__((aligned(NLMSG_ALIGNTO))) {
455     struct nlmsghdr nl_hdr;
456     struct __attribute__((__packed__)) {
457       struct cn_msg cn_msg;
458       struct proc_event proc_ev;
459     };
460   } nlcn_msg;
461
462   if (nl_sock == -1)
463     return ret;
464
465   status = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
466
467   if (status == 0) {
468     return 0;
469   } else if (status == -1) {
470     if (errno != EINTR) {
471       ERROR("procevent plugin: socket receive error: %d", errno);
472       return -1;
473     }
474   }
475
476   switch (nlcn_msg.proc_ev.what) {
477   case PROC_EVENT_NONE:
478     // printf("set mcast listen ok\n");
479     break;
480   case PROC_EVENT_FORK:
481     // printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d\n",
482     //         nlcn_msg.proc_ev.event_data.fork.parent_pid,
483     //         nlcn_msg.proc_ev.event_data.fork.parent_tgid,
484     //         nlcn_msg.proc_ev.event_data.fork.child_pid,
485     //         nlcn_msg.proc_ev.event_data.fork.child_tgid);
486     // proc_status = PROCEVENT_STARTED;
487     // proc_id = nlcn_msg.proc_ev.event_data.fork.child_pid;
488     break;
489   case PROC_EVENT_EXEC:
490     // printf("exec: tid=%d pid=%d\n",
491     //         nlcn_msg.proc_ev.event_data.exec.process_pid,
492     //         nlcn_msg.proc_ev.event_data.exec.process_tgid);
493     proc_status = PROCEVENT_STARTED;
494     proc_id = nlcn_msg.proc_ev.event_data.exec.process_pid;
495     break;
496   case PROC_EVENT_UID:
497     // printf("uid change: tid=%d pid=%d from %d to %d\n",
498     //         nlcn_msg.proc_ev.event_data.id.process_pid,
499     //         nlcn_msg.proc_ev.event_data.id.process_tgid,
500     //         nlcn_msg.proc_ev.event_data.id.r.ruid,
501     //         nlcn_msg.proc_ev.event_data.id.e.euid);
502     break;
503   case PROC_EVENT_GID:
504     // printf("gid change: tid=%d pid=%d from %d to %d\n",
505     //         nlcn_msg.proc_ev.event_data.id.process_pid,
506     //         nlcn_msg.proc_ev.event_data.id.process_tgid,
507     //         nlcn_msg.proc_ev.event_data.id.r.rgid,
508     //         nlcn_msg.proc_ev.event_data.id.e.egid);
509     break;
510   case PROC_EVENT_EXIT:
511     proc_id = nlcn_msg.proc_ev.event_data.exit.process_pid;
512     proc_status = PROCEVENT_EXITED;
513     proc_extra = nlcn_msg.proc_ev.event_data.exit.exit_code;
514     break;
515   default:
516     break;
517   }
518
519   // If we're interested in this process status event, place the event
520   // in the ring buffer for consumption by the main polling thread.
521
522   if (proc_status != -1) {
523     pthread_mutex_unlock(&procevent_lock);
524
525     int next = ring.head + 1;
526     if (next >= ring.maxLen)
527       next = 0;
528
529     if (next == ring.tail) {
530       WARNING("procevent plugin: ring buffer full");
531     } else {
532       DEBUG("procevent plugin: Process %d status is now %s", proc_id,
533             (proc_status == PROCEVENT_EXITED ? "EXITED" : "STARTED"));
534
535       if (proc_status == PROCEVENT_EXITED) {
536         ring.buffer[ring.head][0] = proc_id;
537         ring.buffer[ring.head][1] = proc_status;
538         ring.buffer[ring.head][2] = proc_extra;
539       } else {
540         ring.buffer[ring.head][0] = proc_id;
541         ring.buffer[ring.head][1] = proc_status;
542         ring.buffer[ring.head][2] = 0;
543       }
544
545       ring.head = next;
546     }
547
548     pthread_mutex_unlock(&procevent_lock);
549   }
550
551   return ret;
552 }
553
554 static void *procevent_thread(void *arg) /* {{{ */
555 {
556   pthread_mutex_lock(&procevent_lock);
557
558   while (procevent_thread_loop > 0) {
559     int status;
560
561     pthread_mutex_unlock(&procevent_lock);
562
563     usleep(1000);
564
565     status = read_event();
566
567     pthread_mutex_lock(&procevent_lock);
568
569     if (status < 0) {
570       procevent_thread_error = 1;
571       break;
572     }
573
574     if (procevent_thread_loop <= 0)
575       break;
576   } /* while (procevent_thread_loop > 0) */
577
578   pthread_mutex_unlock(&procevent_lock);
579
580   return ((void *)0);
581 } /* }}} void *procevent_thread */
582
583 static int start_thread(void) /* {{{ */
584 {
585   int status;
586
587   pthread_mutex_lock(&procevent_lock);
588
589   if (procevent_thread_loop != 0) {
590     pthread_mutex_unlock(&procevent_lock);
591     return (0);
592   }
593
594   if (nl_sock == -1) {
595     status = nl_connect();
596
597     if (status != 0)
598       return status;
599
600     status = set_proc_ev_listen(true);
601     if (status == -1)
602       return status;
603   }
604
605   DEBUG("procevent plugin: socket created and bound");
606
607   procevent_thread_loop = 1;
608   procevent_thread_error = 0;
609
610   status = plugin_thread_create(&procevent_thread_id, /* attr = */ NULL,
611                                 procevent_thread,
612                                 /* arg = */ (void *)0, "procevent");
613   if (status != 0) {
614     procevent_thread_loop = 0;
615     ERROR("procevent plugin: Starting thread failed.");
616     pthread_mutex_unlock(&procevent_lock);
617     return (-1);
618   }
619
620   pthread_mutex_unlock(&procevent_lock);
621   return (0);
622 } /* }}} int start_thread */
623
624 static int stop_thread(int shutdown) /* {{{ */
625 {
626   int status;
627
628   if (nl_sock != -1) {
629     status = close(nl_sock);
630     if (status != 0) {
631       ERROR("procevent plugin: failed to close socket %d: %d (%s)", nl_sock,
632             status, strerror(errno));
633       return (-1);
634     } else
635       nl_sock = -1;
636   }
637
638   pthread_mutex_lock(&procevent_lock);
639
640   if (procevent_thread_loop == 0) {
641     pthread_mutex_unlock(&procevent_lock);
642     return (-1);
643   }
644
645   procevent_thread_loop = 0;
646   pthread_cond_broadcast(&procevent_cond);
647   pthread_mutex_unlock(&procevent_lock);
648
649   if (shutdown == 1) {
650     // Calling pthread_cancel here in
651     // the case of a shutdown just assures that the thread is
652     // gone and that the process has been fully terminated.
653
654     DEBUG("procevent plugin: Canceling thread for process shutdown");
655
656     status = pthread_cancel(procevent_thread_id);
657
658     if (status != 0) {
659       ERROR("procevent plugin: Unable to cancel thread: %d", status);
660       status = -1;
661     }
662   } else {
663     status = pthread_join(procevent_thread_id, /* return = */ NULL);
664     if (status != 0) {
665       ERROR("procevent plugin: Stopping thread failed.");
666       status = -1;
667     }
668   }
669
670   pthread_mutex_lock(&procevent_lock);
671   memset(&procevent_thread_id, 0, sizeof(procevent_thread_id));
672   procevent_thread_error = 0;
673   pthread_mutex_unlock(&procevent_lock);
674
675   DEBUG("procevent plugin: Finished requesting stop of thread");
676
677   return (status);
678 } /* }}} int stop_thread */
679
680 static int procevent_init(void) /* {{{ */
681 {
682   int status;
683
684   if (processlist_head == NULL) {
685     NOTICE("procevent plugin: No processes have been configured.");
686     return (-1);
687   }
688
689   ring.head = 0;
690   ring.tail = 0;
691   ring.maxLen = buffer_length;
692   ring.buffer = (int **)malloc(buffer_length * sizeof(int *));
693
694   for (int i = 0; i < buffer_length; i++) {
695     ring.buffer[i] = (int *)malloc(PROCEVENT_FIELDS * sizeof(int));
696   }
697
698   status = process_map_refresh();
699
700   if (status == -1) {
701     ERROR("procevent plugin: Initial process mapping failed.");
702     return (-1);
703   }
704
705   return (start_thread());
706 } /* }}} int procevent_init */
707
708 static int procevent_config(const char *key, const char *value) /* {{{ */
709 {
710   int status;
711
712   if (strcasecmp(key, "BufferLength") == 0) {
713     buffer_length = atoi(value);
714   } else if (strcasecmp(key, "Process") == 0 ||
715              strcasecmp(key, "RegexProcess") == 0) {
716
717     processlist_t *pl;
718     char *process;
719     char *process_regex;
720
721     pl = malloc(sizeof(*pl));
722     if (pl == NULL) {
723       char errbuf[1024];
724       ERROR("procevent plugin: malloc failed during procevent_config: %s",
725             sstrerror(errno, errbuf, sizeof(errbuf)));
726       return (1);
727     }
728
729     process = strdup(value);
730     if (process == NULL) {
731       char errbuf[1024];
732       sfree(pl);
733       ERROR("procevent plugin: strdup failed during procevent_config: %s",
734             sstrerror(errno, errbuf, sizeof(errbuf)));
735       return (1);
736     }
737
738     if (strcasecmp(key, "RegexProcess") == 0) {
739       pl->is_regex = 1;
740       status = regcomp(&pl->process_regex_obj, value, REG_EXTENDED);
741
742       if (status != 0) {
743         ERROR("procevent plugin: invalid regular expression: %s", value);
744         return (1);
745       }
746
747       process_regex = strdup(value);
748       if (process_regex == NULL) {
749         char errbuf[1024];
750         sfree(pl);
751         ERROR("procevent plugin: strdup failed during procevent_config: %s",
752               sstrerror(errno, errbuf, sizeof(errbuf)));
753         return (1);
754       }
755
756       pl->process_regex = process_regex;
757     } else {
758       pl->is_regex = 0;
759     }
760
761     pl->process = process;
762     pl->pid = -1;
763     pl->next = processlist_head;
764     processlist_head = pl;
765   } else {
766     return (-1);
767   }
768
769   return (0);
770 } /* }}} int procevent_config */
771
772 static void submit(int pid, const char *type, /* {{{ */
773                    gauge_t value, const char *process) {
774   value_list_t vl = VALUE_LIST_INIT;
775   char hostname[1024];
776
777   vl.values = &(value_t){.gauge = value};
778   vl.values_len = 1;
779   sstrncpy(vl.plugin, "procevent", sizeof(vl.plugin));
780   sstrncpy(vl.plugin_instance, process, sizeof(vl.plugin_instance));
781   sstrncpy(vl.type, type, sizeof(vl.type));
782
783   DEBUG("procevent plugin: dispatching state %d for PID %d (%s)", (int)value,
784         pid, process);
785
786   // Create metadata to store JSON key-values
787   meta_data_t *meta = meta_data_create();
788
789   vl.meta = meta;
790
791   gethostname(hostname, sizeof(hostname));
792
793   if (value == 1) {
794     meta_data_add_string(meta, "condition", "process_up");
795     meta_data_add_string(meta, "entity", process);
796     meta_data_add_string(meta, "source", hostname);
797     meta_data_add_string(meta, "dest", "process_down");
798   } else {
799     meta_data_add_string(meta, "condition", "process_down");
800     meta_data_add_string(meta, "entity", process);
801     meta_data_add_string(meta, "source", hostname);
802     meta_data_add_string(meta, "dest", "process_up");
803   }
804
805   plugin_dispatch_values(&vl);
806 } /* }}} void interface_submit */
807
808 static int procevent_read(void) /* {{{ */
809 {
810   if (procevent_thread_error != 0) {
811     ERROR(
812         "procevent plugin: The interface thread had a problem. Restarting it.");
813
814     stop_thread(0);
815
816     start_thread();
817
818     return (-1);
819   } /* if (procevent_thread_error != 0) */
820
821   pthread_mutex_lock(&procevent_lock);
822
823   while (ring.head != ring.tail) {
824     int next = ring.tail + 1;
825
826     if (next >= ring.maxLen)
827       next = 0;
828
829     if (ring.buffer[ring.tail][1] == PROCEVENT_EXITED) {
830       processlist_t *pl = process_map_check(ring.buffer[ring.tail][0], NULL);
831
832       if (pl != NULL) {
833         // This process is of interest to us, so publish its EXITED status
834         submit(ring.buffer[ring.tail][0], "gauge", ring.buffer[ring.tail][1],
835                pl->process);
836         DEBUG("procevent plugin: PID %d (%s) EXITED, removing PID from process "
837               "list",
838               pl->pid, pl->process);
839         pl->pid = -1;
840       }
841     } else if (ring.buffer[ring.tail][1] == PROCEVENT_STARTED) {
842       // a new process has started, so check if we should monitor it
843       processlist_t *pl = process_check(ring.buffer[ring.tail][0]);
844
845       if (pl != NULL) {
846         // This process is of interest to us, so publish its STARTED status
847         submit(ring.buffer[ring.tail][0], "gauge", ring.buffer[ring.tail][1],
848                pl->process);
849         DEBUG(
850             "procevent plugin: PID %d (%s) STARTED, adding PID to process list",
851             pl->pid, pl->process);
852       }
853     }
854
855     ring.tail = next;
856   }
857
858   pthread_mutex_unlock(&procevent_lock);
859
860   return (0);
861 } /* }}} int procevent_read */
862
863 static int procevent_shutdown(void) /* {{{ */
864 {
865   // int status = 0;
866   processlist_t *pl;
867
868   DEBUG("procevent plugin: Shutting down thread.");
869
870   if (stop_thread(1) < 0)
871     return (-1);
872
873   for (int i = 0; i < buffer_length; i++) {
874     free(ring.buffer[i]);
875   }
876
877   free(ring.buffer);
878
879   pl = processlist_head;
880   while (pl != NULL) {
881     processlist_t *pl_next;
882
883     pl_next = pl->next;
884
885     if (pl->is_regex == 1) {
886       sfree(pl->process_regex);
887       regfree(&pl->process_regex_obj);
888     }
889
890     sfree(pl->process);
891     sfree(pl);
892
893     pl = pl_next;
894   }
895
896   return (0);
897 } /* }}} int procevent_shutdown */
898
899 void module_register(void) {
900   plugin_register_config("procevent", procevent_config, config_keys,
901                          config_keys_num);
902   plugin_register_init("procevent", procevent_init);
903   plugin_register_read("procevent", procevent_read);
904   plugin_register_shutdown("procevent", procevent_shutdown);
905 } /* void module_register */