Merge pull request #3117 from elfiesmelfie/virt_fix_option_parsing
[collectd.git] / src / exec.c
1 /**
2  * collectd - src/exec.c
3  * Copyright (C) 2007-2010  Florian octo Forster
4  * Copyright (C) 2007-2009  Sebastian Harl
5  * Copyright (C) 2008       Peter Holik
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; only version 2 of the License is applicable.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * Authors:
21  *   Florian octo Forster <octo at collectd.org>
22  *   Sebastian Harl <sh at tokkee.org>
23  *   Peter Holik <peter at holik.at>
24  **/
25
26 #define _DEFAULT_SOURCE
27 #define _BSD_SOURCE /* For setgroups */
28
29 #include "collectd.h"
30
31 #include "plugin.h"
32 #include "utils/common/common.h"
33
34 #include "utils/cmds/putnotif.h"
35 #include "utils/cmds/putval.h"
36
37 #include <grp.h>
38 #include <pwd.h>
39 #include <signal.h>
40 #include <sys/types.h>
41
42 #ifdef HAVE_SYS_CAPABILITY_H
43 #include <sys/capability.h>
44 #endif
45
46 #define PL_NORMAL 0x01
47 #define PL_NOTIF_ACTION 0x02
48
49 #define PL_RUNNING 0x10
50
51 /*
52  * Private data types
53  */
54 /*
55  * Access to this structure is serialized using the `pl_lock' lock and the
56  * `PL_RUNNING' flag. The execution of notifications is *not* serialized, so
57  * all functions used to handle notifications MUST NOT write to this structure.
58  * The `pid' and `status' fields are thus unused if the `PL_NOTIF_ACTION' flag
59  * is set.
60  * The `PL_RUNNING' flag is set in `exec_read' and unset in `exec_read_one'.
61  */
62 struct program_list_s;
63 typedef struct program_list_s program_list_t;
64 struct program_list_s {
65   char *user;
66   char *group;
67   char *exec;
68   char **argv;
69   int pid;
70   int status;
71   int flags;
72   program_list_t *next;
73 };
74
75 typedef struct program_list_and_notification_s {
76   program_list_t *pl;
77   notification_t n;
78 } program_list_and_notification_t;
79
80 /*
81  * constants
82  */
83 const long int MAX_GRBUF_SIZE = 65536;
84
85 /*
86  * Private variables
87  */
88 static program_list_t *pl_head;
89 static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
90
91 /*
92  * Functions
93  */
94 static void sigchld_handler(int __attribute__((unused)) signal) /* {{{ */
95 {
96   pid_t pid;
97   int status;
98   while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
99     program_list_t *pl;
100     for (pl = pl_head; pl != NULL; pl = pl->next)
101       if (pl->pid == pid)
102         break;
103     if (pl != NULL)
104       pl->status = status;
105   } /* while (waitpid) */
106 } /* void sigchld_handler }}} */
107
108 static int exec_config_exec(oconfig_item_t *ci) /* {{{ */
109 {
110   program_list_t *pl;
111   char buffer[128];
112   int i;
113
114   if (ci->children_num != 0) {
115     WARNING("exec plugin: The config option `%s' may not be a block.", ci->key);
116     return -1;
117   }
118   if (ci->values_num < 2) {
119     WARNING("exec plugin: The config option `%s' needs at least two "
120             "arguments.",
121             ci->key);
122     return -1;
123   }
124   if ((ci->values[0].type != OCONFIG_TYPE_STRING) ||
125       (ci->values[1].type != OCONFIG_TYPE_STRING)) {
126     WARNING("exec plugin: The first two arguments to the `%s' option must "
127             "be string arguments.",
128             ci->key);
129     return -1;
130   }
131
132   pl = calloc(1, sizeof(*pl));
133   if (pl == NULL) {
134     ERROR("exec plugin: calloc failed.");
135     return -1;
136   }
137
138   if (strcasecmp("NotificationExec", ci->key) == 0)
139     pl->flags |= PL_NOTIF_ACTION;
140   else
141     pl->flags |= PL_NORMAL;
142
143   pl->user = strdup(ci->values[0].value.string);
144   if (pl->user == NULL) {
145     ERROR("exec plugin: strdup failed.");
146     sfree(pl);
147     return -1;
148   }
149
150   pl->group = strchr(pl->user, ':');
151   if (pl->group != NULL) {
152     *pl->group = '\0';
153     pl->group++;
154   }
155
156   pl->exec = strdup(ci->values[1].value.string);
157   if (pl->exec == NULL) {
158     ERROR("exec plugin: strdup failed.");
159     sfree(pl->user);
160     sfree(pl);
161     return -1;
162   }
163
164   pl->argv = calloc(ci->values_num, sizeof(*pl->argv));
165   if (pl->argv == NULL) {
166     ERROR("exec plugin: calloc failed.");
167     sfree(pl->exec);
168     sfree(pl->user);
169     sfree(pl);
170     return -1;
171   }
172
173   {
174     char *tmp = strrchr(ci->values[1].value.string, '/');
175     if (tmp == NULL)
176       sstrncpy(buffer, ci->values[1].value.string, sizeof(buffer));
177     else
178       sstrncpy(buffer, tmp + 1, sizeof(buffer));
179   }
180   pl->argv[0] = strdup(buffer);
181   if (pl->argv[0] == NULL) {
182     ERROR("exec plugin: strdup failed.");
183     sfree(pl->argv);
184     sfree(pl->exec);
185     sfree(pl->user);
186     sfree(pl);
187     return -1;
188   }
189
190   for (i = 1; i < (ci->values_num - 1); i++) {
191     if (ci->values[i + 1].type == OCONFIG_TYPE_STRING) {
192       pl->argv[i] = strdup(ci->values[i + 1].value.string);
193     } else {
194       if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER) {
195         snprintf(buffer, sizeof(buffer), "%lf", ci->values[i + 1].value.number);
196       } else {
197         if (ci->values[i + 1].value.boolean)
198           sstrncpy(buffer, "true", sizeof(buffer));
199         else
200           sstrncpy(buffer, "false", sizeof(buffer));
201       }
202
203       pl->argv[i] = strdup(buffer);
204     }
205
206     if (pl->argv[i] == NULL) {
207       ERROR("exec plugin: strdup failed.");
208       break;
209     }
210   } /* for (i) */
211
212   if (i < (ci->values_num - 1)) {
213     while ((--i) >= 0) {
214       sfree(pl->argv[i]);
215     }
216     sfree(pl->argv);
217     sfree(pl->exec);
218     sfree(pl->user);
219     sfree(pl);
220     return -1;
221   }
222
223   for (i = 0; pl->argv[i] != NULL; i++) {
224     DEBUG("exec plugin: argv[%i] = %s", i, pl->argv[i]);
225   }
226
227   pl->next = pl_head;
228   pl_head = pl;
229
230   return 0;
231 } /* int exec_config_exec }}} */
232
233 static int exec_config(oconfig_item_t *ci) /* {{{ */
234 {
235   for (int i = 0; i < ci->children_num; i++) {
236     oconfig_item_t *child = ci->children + i;
237     if ((strcasecmp("Exec", child->key) == 0) ||
238         (strcasecmp("NotificationExec", child->key) == 0))
239       exec_config_exec(child);
240     else {
241       WARNING("exec plugin: Unknown config option `%s'.", child->key);
242     }
243   } /* for (i) */
244
245   return 0;
246 } /* int exec_config }}} */
247
248 #if !defined(HAVE_SETENV)
249 static char env_interval[64];
250 // max hostname len is 255, so this should be enough
251 static char env_hostname[300];
252 #endif
253
254 static void set_environment(void) /* {{{ */
255 {
256 #ifdef HAVE_SETENV
257   char buffer[1024];
258
259   snprintf(buffer, sizeof(buffer), "%.3f",
260            CDTIME_T_TO_DOUBLE(plugin_get_interval()));
261   setenv("COLLECTD_INTERVAL", buffer, /* overwrite = */ 1);
262
263   sstrncpy(buffer, hostname_g, sizeof(buffer));
264   setenv("COLLECTD_HOSTNAME", buffer, /* overwrite = */ 1);
265 #else
266   snprintf(env_interval, sizeof(env_interval), "COLLECTD_INTERVAL=%.3f",
267            CDTIME_T_TO_DOUBLE(plugin_get_interval()));
268   putenv(env_interval);
269
270   snprintf(env_hostname, sizeof(env_hostname), "COLLECTD_HOSTNAME=%s",
271            hostname_g);
272   putenv(env_hostname);
273 #endif
274 } /* }}} void set_environment */
275
276 static void unset_environment(void) /* {{{ */
277 {
278 #ifdef HAVE_SETENV
279   unsetenv("COLLECTD_INTERVAL");
280   unsetenv("COLLECTD_HOSTNAME");
281 #else
282   snprintf(env_interval, sizeof(env_interval), "COLLECTD_INTERVAL");
283   putenv(env_interval);
284   snprintf(env_hostname, sizeof(env_hostname), "COLLECTD_HOSTNAME");
285   putenv(env_hostname);
286 #endif
287 } /* }}} void unset_environment */
288
289 __attribute__((noreturn)) static void exec_child(program_list_t *pl, int uid,
290                                                  int gid, int egid) /* {{{ */
291 {
292   int status;
293
294 #if HAVE_SETGROUPS
295   if (getuid() == 0) {
296     gid_t glist[2];
297     size_t glist_len;
298
299     glist[0] = gid;
300     glist_len = 1;
301
302     if ((gid != egid) && (egid != -1)) {
303       glist[1] = egid;
304       glist_len = 2;
305     }
306
307     setgroups(glist_len, glist);
308   }
309 #endif /* HAVE_SETGROUPS */
310
311   status = setgid(gid);
312   if (status != 0) {
313     ERROR("exec plugin: setgid (%i) failed: %s", gid, STRERRNO);
314     exit(-1);
315   }
316
317   if (egid != -1) {
318     status = setegid(egid);
319     if (status != 0) {
320       ERROR("exec plugin: setegid (%i) failed: %s", egid, STRERRNO);
321       exit(-1);
322     }
323   }
324
325   status = setuid(uid);
326   if (status != 0) {
327     ERROR("exec plugin: setuid (%i) failed: %s", uid, STRERRNO);
328     exit(-1);
329   }
330
331   execvp(pl->exec, pl->argv);
332
333   ERROR("exec plugin: Failed to execute ``%s'': %s", pl->exec, STRERRNO);
334   exit(-1);
335 } /* void exec_child }}} */
336
337 static void reset_signal_mask(void) /* {{{ */
338 {
339   sigset_t ss;
340
341   sigemptyset(&ss);
342   sigprocmask(SIG_SETMASK, &ss, /* old mask = */ NULL);
343 } /* }}} void reset_signal_mask */
344
345 static int create_pipe(int fd_pipe[2]) /* {{{ */
346 {
347   int status;
348
349   status = pipe(fd_pipe);
350   if (status != 0) {
351     ERROR("exec plugin: pipe failed: %s", STRERRNO);
352     return -1;
353   }
354
355   return 0;
356 } /* }}} int create_pipe */
357
358 static void close_pipe(int fd_pipe[2]) /* {{{ */
359 {
360   if (fd_pipe[0] != -1)
361     close(fd_pipe[0]);
362
363   if (fd_pipe[1] != -1)
364     close(fd_pipe[1]);
365 } /* }}} void close_pipe */
366
367 /*
368  * Get effective group ID from group name.
369  * Input arguments:
370  *       pl  :program list struct with group name
371  *       gid :group id to fallback in case egid cannot be determined.
372  * Returns:
373  *       egid effective group id if successfull,
374  *            -1 if group is not defined/not found.
375  *            -2 for any buffer allocation error.
376  */
377 static int getegr_id(program_list_t *pl, int gid) /* {{{ */
378 {
379   if (pl->group == NULL) {
380     return -1;
381   }
382   if (strcmp(pl->group, "") == 0) {
383     return gid;
384   }
385   struct group *gr_ptr = NULL;
386   struct group gr;
387
388   long int grbuf_size = sysconf(_SC_GETGR_R_SIZE_MAX);
389   if (grbuf_size <= 0)
390     grbuf_size = sysconf(_SC_PAGESIZE);
391   if (grbuf_size <= 0)
392     grbuf_size = 4096;
393
394   char *temp = NULL;
395   char *grbuf = NULL;
396
397   do {
398     temp = realloc(grbuf, grbuf_size);
399     if (temp == NULL) {
400       ERROR("exec plugin: getegr_id for %s: realloc buffer[%ld] failed ",
401             pl->group, grbuf_size);
402       sfree(grbuf);
403       return -2;
404     }
405     grbuf = temp;
406     if (getgrnam_r(pl->group, &gr, grbuf, grbuf_size, &gr_ptr) == 0) {
407       sfree(grbuf);
408       if (gr_ptr == NULL) {
409         ERROR("exec plugin: No such group: `%s'", pl->group);
410         return -1;
411       }
412       return gr.gr_gid;
413     } else if (errno == ERANGE) {
414       grbuf_size += grbuf_size; // increment buffer size and try again
415     } else {
416       ERROR("exec plugin: getegr_id failed %s", STRERRNO);
417       sfree(grbuf);
418       return -2;
419     }
420   } while (grbuf_size <= MAX_GRBUF_SIZE);
421   ERROR("exec plugin: getegr_id Max grbuf size reached  for %s", pl->group);
422   sfree(grbuf);
423   return -2;
424 }
425
426 /*
427  * Creates three pipes (one for reading, one for writing and one for errors),
428  * forks a child, sets up the pipes so that fd_in is connected to STDIN of
429  * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
430  * of the child. Then is calls `exec_child'.
431  */
432 static int fork_child(program_list_t *pl, int *fd_in, int *fd_out,
433                       int *fd_err) /* {{{ */
434 {
435   int fd_pipe_in[2] = {-1, -1};
436   int fd_pipe_out[2] = {-1, -1};
437   int fd_pipe_err[2] = {-1, -1};
438   int status;
439   int pid;
440
441   int uid;
442   int gid;
443   int egid;
444
445   struct passwd *sp_ptr;
446   struct passwd sp;
447
448   if (pl->pid != 0)
449     return -1;
450
451   long int nambuf_size = sysconf(_SC_GETPW_R_SIZE_MAX);
452   if (nambuf_size <= 0)
453     nambuf_size = sysconf(_SC_PAGESIZE);
454   if (nambuf_size <= 0)
455     nambuf_size = 4096;
456   char nambuf[nambuf_size];
457
458   if ((create_pipe(fd_pipe_in) == -1) || (create_pipe(fd_pipe_out) == -1) ||
459       (create_pipe(fd_pipe_err) == -1))
460     goto failed;
461
462   sp_ptr = NULL;
463   status = getpwnam_r(pl->user, &sp, nambuf, sizeof(nambuf), &sp_ptr);
464   if (status != 0) {
465     ERROR("exec plugin: Failed to get user information for user ``%s'': %s",
466           pl->user, STRERROR(status));
467     goto failed;
468   }
469
470   if (sp_ptr == NULL) {
471     ERROR("exec plugin: No such user: `%s'", pl->user);
472     goto failed;
473   }
474
475   uid = sp.pw_uid;
476   gid = sp.pw_gid;
477   if (uid == 0) {
478     ERROR("exec plugin: Cowardly refusing to exec program as root.");
479     goto failed;
480   }
481
482   /* The group configured in the configfile is set as effective group, because
483    * this way the forked process can (re-)gain the user's primary group. */
484   egid = getegr_id(pl, gid);
485   if (egid == -2) {
486     goto failed;
487   }
488
489   set_environment();
490
491   pid = fork();
492   if (pid < 0) {
493     ERROR("exec plugin: fork failed: %s", STRERRNO);
494     goto failed;
495   } else if (pid == 0) {
496     int fd_num;
497
498     /* Close all file descriptors but the pipe end we need. */
499     fd_num = getdtablesize();
500     for (int fd = 0; fd < fd_num; fd++) {
501       if ((fd == fd_pipe_in[0]) || (fd == fd_pipe_out[1]) ||
502           (fd == fd_pipe_err[1]))
503         continue;
504       close(fd);
505     }
506
507     /* Connect the `in' pipe to STDIN */
508     if (fd_pipe_in[0] != STDIN_FILENO) {
509       dup2(fd_pipe_in[0], STDIN_FILENO);
510       close(fd_pipe_in[0]);
511     }
512
513     /* Now connect the `out' pipe to STDOUT */
514     if (fd_pipe_out[1] != STDOUT_FILENO) {
515       dup2(fd_pipe_out[1], STDOUT_FILENO);
516       close(fd_pipe_out[1]);
517     }
518
519     /* Now connect the `err' pipe to STDERR */
520     if (fd_pipe_err[1] != STDERR_FILENO) {
521       dup2(fd_pipe_err[1], STDERR_FILENO);
522       close(fd_pipe_err[1]);
523     }
524
525     /* Unblock all signals */
526     reset_signal_mask();
527
528     exec_child(pl, uid, gid, egid);
529     /* does not return */
530   }
531
532   unset_environment();
533
534   close(fd_pipe_in[0]);
535   close(fd_pipe_out[1]);
536   close(fd_pipe_err[1]);
537
538   if (fd_in != NULL)
539     *fd_in = fd_pipe_in[1];
540   else
541     close(fd_pipe_in[1]);
542
543   if (fd_out != NULL)
544     *fd_out = fd_pipe_out[0];
545   else
546     close(fd_pipe_out[0]);
547
548   if (fd_err != NULL)
549     *fd_err = fd_pipe_err[0];
550   else
551     close(fd_pipe_err[0]);
552
553   return pid;
554
555 failed:
556   unset_environment();
557
558   close_pipe(fd_pipe_in);
559   close_pipe(fd_pipe_out);
560   close_pipe(fd_pipe_err);
561
562   return -1;
563 } /* int fork_child }}} */
564
565 static int parse_line(char *buffer) /* {{{ */
566 {
567   if (strncasecmp("PUTVAL", buffer, strlen("PUTVAL")) == 0)
568     return cmd_handle_putval(stdout, buffer);
569   else if (strncasecmp("PUTNOTIF", buffer, strlen("PUTNOTIF")) == 0)
570     return handle_putnotif(stdout, buffer);
571   else {
572     ERROR("exec plugin: Unable to parse command, ignoring line: \"%s\"",
573           buffer);
574     return -1;
575   }
576 } /* int parse_line }}} */
577
578 static void *exec_read_one(void *arg) /* {{{ */
579 {
580   program_list_t *pl = (program_list_t *)arg;
581   int fd, fd_err, highest_fd;
582   fd_set fdset, copy;
583   int status;
584   char buffer[1200]; /* if not completely read */
585   char buffer_err[1024];
586   char *pbuffer = buffer;
587   char *pbuffer_err = buffer_err;
588
589   status = fork_child(pl, NULL, &fd, &fd_err);
590   if (status < 0) {
591     /* Reset the "running" flag */
592     pthread_mutex_lock(&pl_lock);
593     pl->flags &= ~PL_RUNNING;
594     pthread_mutex_unlock(&pl_lock);
595     pthread_exit((void *)1);
596   }
597   pl->pid = status;
598
599   assert(pl->pid != 0);
600
601   FD_ZERO(&fdset);
602   FD_SET(fd, &fdset);
603   FD_SET(fd_err, &fdset);
604
605   /* Determine the highest file descriptor */
606   highest_fd = (fd > fd_err) ? fd : fd_err;
607
608   /* We use a copy of fdset, as select modifies it */
609   copy = fdset;
610
611   while (1) {
612     int len;
613
614     status = select(highest_fd + 1, &copy, NULL, NULL, NULL);
615     if (status < 0) {
616       if (errno == EINTR)
617         continue;
618       break;
619     }
620
621     if (FD_ISSET(fd, &copy)) {
622       char *pnl;
623
624       len = read(fd, pbuffer, sizeof(buffer) - 1 - (pbuffer - buffer));
625
626       if (len < 0) {
627         if (errno == EAGAIN || errno == EINTR)
628           continue;
629         break;
630       } else if (len == 0)
631         break; /* We've reached EOF */
632
633       pbuffer[len] = '\0';
634
635       len += pbuffer - buffer;
636       pbuffer = buffer;
637
638       while ((pnl = strchr(pbuffer, '\n'))) {
639         *pnl = '\0';
640         if (*(pnl - 1) == '\r')
641           *(pnl - 1) = '\0';
642
643         parse_line(pbuffer);
644
645         pbuffer = ++pnl;
646       }
647       /* not completely read ? */
648       if (pbuffer - buffer < len) {
649         len -= pbuffer - buffer;
650         memmove(buffer, pbuffer, len);
651         pbuffer = buffer + len;
652       } else
653         pbuffer = buffer;
654     } else if (FD_ISSET(fd_err, &copy)) {
655       char *pnl;
656
657       len = read(fd_err, pbuffer_err,
658                  sizeof(buffer_err) - 1 - (pbuffer_err - buffer_err));
659
660       if (len < 0) {
661         if (errno == EAGAIN || errno == EINTR)
662           continue;
663         break;
664       } else if (len == 0) {
665         /* We've reached EOF */
666         NOTICE("exec plugin: Program `%s' has closed STDERR.", pl->exec);
667
668         /* Remove file descriptor form select() set. */
669         FD_CLR(fd_err, &fdset);
670         copy = fdset;
671         highest_fd = fd;
672
673         /* Clean up file descriptor */
674         close(fd_err);
675         fd_err = -1;
676         continue;
677       }
678
679       pbuffer_err[len] = '\0';
680
681       len += pbuffer_err - buffer_err;
682       pbuffer_err = buffer_err;
683
684       while ((pnl = strchr(pbuffer_err, '\n'))) {
685         *pnl = '\0';
686         if (*(pnl - 1) == '\r')
687           *(pnl - 1) = '\0';
688
689         ERROR("exec plugin: exec_read_one: error = %s", pbuffer_err);
690
691         pbuffer_err = ++pnl;
692       }
693       /* not completely read ? */
694       if (pbuffer_err - buffer_err < len) {
695         len -= pbuffer_err - buffer_err;
696         memmove(buffer_err, pbuffer_err, len);
697         pbuffer_err = buffer_err + len;
698       } else
699         pbuffer_err = buffer_err;
700     }
701     /* reset copy */
702     copy = fdset;
703   }
704
705   DEBUG("exec plugin: exec_read_one: Waiting for `%s' to exit.", pl->exec);
706   if (waitpid(pl->pid, &status, 0) > 0)
707     pl->status = status;
708
709   DEBUG("exec plugin: Child %i exited with status %i.", (int)pl->pid,
710         pl->status);
711
712   pl->pid = 0;
713
714   pthread_mutex_lock(&pl_lock);
715   pl->flags &= ~PL_RUNNING;
716   pthread_mutex_unlock(&pl_lock);
717
718   close(fd);
719   if (fd_err >= 0)
720     close(fd_err);
721
722   pthread_exit((void *)0);
723   return NULL;
724 } /* void *exec_read_one }}} */
725
726 static void *exec_notification_one(void *arg) /* {{{ */
727 {
728   program_list_t *pl = ((program_list_and_notification_t *)arg)->pl;
729   notification_t *n = &((program_list_and_notification_t *)arg)->n;
730   int fd;
731   FILE *fh;
732   int pid;
733   int status;
734   const char *severity;
735
736   pid = fork_child(pl, &fd, NULL, NULL);
737   if (pid < 0) {
738     sfree(arg);
739     pthread_exit((void *)1);
740   }
741
742   fh = fdopen(fd, "w");
743   if (fh == NULL) {
744     ERROR("exec plugin: fdopen (%i) failed: %s", fd, STRERRNO);
745     kill(pid, SIGTERM);
746     close(fd);
747     sfree(arg);
748     pthread_exit((void *)1);
749   }
750
751   severity = "FAILURE";
752   if (n->severity == NOTIF_WARNING)
753     severity = "WARNING";
754   else if (n->severity == NOTIF_OKAY)
755     severity = "OKAY";
756
757   fprintf(fh, "Severity: %s\n"
758               "Time: %.3f\n",
759           severity, CDTIME_T_TO_DOUBLE(n->time));
760
761   /* Print the optional fields */
762   if (strlen(n->host) > 0)
763     fprintf(fh, "Host: %s\n", n->host);
764   if (strlen(n->plugin) > 0)
765     fprintf(fh, "Plugin: %s\n", n->plugin);
766   if (strlen(n->plugin_instance) > 0)
767     fprintf(fh, "PluginInstance: %s\n", n->plugin_instance);
768   if (strlen(n->type) > 0)
769     fprintf(fh, "Type: %s\n", n->type);
770   if (strlen(n->type_instance) > 0)
771     fprintf(fh, "TypeInstance: %s\n", n->type_instance);
772
773   for (notification_meta_t *meta = n->meta; meta != NULL; meta = meta->next) {
774     if (meta->type == NM_TYPE_STRING)
775       fprintf(fh, "%s: %s\n", meta->name, meta->nm_value.nm_string);
776     else if (meta->type == NM_TYPE_SIGNED_INT)
777       fprintf(fh, "%s: %" PRIi64 "\n", meta->name,
778               meta->nm_value.nm_signed_int);
779     else if (meta->type == NM_TYPE_UNSIGNED_INT)
780       fprintf(fh, "%s: %" PRIu64 "\n", meta->name,
781               meta->nm_value.nm_unsigned_int);
782     else if (meta->type == NM_TYPE_DOUBLE)
783       fprintf(fh, "%s: %e\n", meta->name, meta->nm_value.nm_double);
784     else if (meta->type == NM_TYPE_BOOLEAN)
785       fprintf(fh, "%s: %s\n", meta->name,
786               meta->nm_value.nm_boolean ? "true" : "false");
787   }
788
789   fprintf(fh, "\n%s\n", n->message);
790
791   fflush(fh);
792   fclose(fh);
793
794   waitpid(pid, &status, 0);
795
796   DEBUG("exec plugin: Child %i exited with status %i.", pid, status);
797
798   if (n->meta != NULL)
799     plugin_notification_meta_free(n->meta);
800   n->meta = NULL;
801   sfree(arg);
802   pthread_exit((void *)0);
803   return NULL;
804 } /* void *exec_notification_one }}} */
805
806 static int exec_init(void) /* {{{ */
807 {
808   struct sigaction sa = {.sa_handler = sigchld_handler};
809
810   sigaction(SIGCHLD, &sa, NULL);
811
812 #if defined(HAVE_SYS_CAPABILITY_H) && defined(CAP_SETUID) && defined(CAP_SETGID)
813   if ((check_capability(CAP_SETUID) != 0) ||
814       (check_capability(CAP_SETGID) != 0)) {
815     if (getuid() == 0)
816       WARNING(
817           "exec plugin: Running collectd as root, but the CAP_SETUID "
818           "or CAP_SETGID capabilities are missing. The plugin's read function "
819           "will probably fail. Is your init system dropping capabilities?");
820     else
821       WARNING(
822           "exec plugin: collectd doesn't have the CAP_SETUID or "
823           "CAP_SETGID capabilities. If you don't want to run collectd as root, "
824           "try running \"setcap 'cap_setuid=ep cap_setgid=ep'\" on the "
825           "collectd binary.");
826   }
827 #endif
828
829   return 0;
830 } /* int exec_init }}} */
831
832 static int exec_read(void) /* {{{ */
833 {
834   for (program_list_t *pl = pl_head; pl != NULL; pl = pl->next) {
835     pthread_t t;
836     pthread_attr_t attr;
837
838     /* Only execute `normal' style executables here. */
839     if ((pl->flags & PL_NORMAL) == 0)
840       continue;
841
842     pthread_mutex_lock(&pl_lock);
843     /* Skip if a child is already running. */
844     if ((pl->flags & PL_RUNNING) != 0) {
845       pthread_mutex_unlock(&pl_lock);
846       continue;
847     }
848     pl->flags |= PL_RUNNING;
849     pthread_mutex_unlock(&pl_lock);
850
851     pthread_attr_init(&attr);
852     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
853     int status =
854         plugin_thread_create(&t, &attr, exec_read_one, (void *)pl, "exec read");
855     if (status != 0) {
856       ERROR("exec plugin: plugin_thread_create failed.");
857     }
858     pthread_attr_destroy(&attr);
859   } /* for (pl) */
860
861   return 0;
862 } /* int exec_read }}} */
863
864 static int exec_notification(const notification_t *n, /* {{{ */
865                              user_data_t __attribute__((unused)) * user_data) {
866   program_list_and_notification_t *pln;
867
868   for (program_list_t *pl = pl_head; pl != NULL; pl = pl->next) {
869     pthread_t t;
870     pthread_attr_t attr;
871
872     /* Only execute `notification' style executables here. */
873     if ((pl->flags & PL_NOTIF_ACTION) == 0)
874       continue;
875
876     /* Skip if a child is already running. */
877     if (pl->pid != 0)
878       continue;
879
880     pln = malloc(sizeof(*pln));
881     if (pln == NULL) {
882       ERROR("exec plugin: malloc failed.");
883       continue;
884     }
885
886     pln->pl = pl;
887     memcpy(&pln->n, n, sizeof(notification_t));
888
889     /* Set the `meta' member to NULL, otherwise `plugin_notification_meta_copy'
890      * will run into an endless loop. */
891     pln->n.meta = NULL;
892     plugin_notification_meta_copy(&pln->n, n);
893
894     pthread_attr_init(&attr);
895     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
896     int status = plugin_thread_create(&t, &attr, exec_notification_one,
897                                       (void *)pln, "exec notify");
898     if (status != 0) {
899       ERROR("exec plugin: plugin_thread_create failed.");
900     }
901     pthread_attr_destroy(&attr);
902   } /* for (pl) */
903
904   return 0;
905 } /* }}} int exec_notification */
906
907 static int exec_shutdown(void) /* {{{ */
908 {
909   program_list_t *pl;
910   program_list_t *next;
911
912   pl = pl_head;
913   while (pl != NULL) {
914     next = pl->next;
915
916     if (pl->pid > 0) {
917       kill(pl->pid, SIGTERM);
918       INFO("exec plugin: Sent SIGTERM to %hu", (unsigned short int)pl->pid);
919     }
920
921     sfree(pl->user);
922     sfree(pl);
923
924     pl = next;
925   } /* while (pl) */
926   pl_head = NULL;
927
928   return 0;
929 } /* int exec_shutdown }}} */
930
931 void module_register(void) {
932   plugin_register_complex_config("exec", exec_config);
933   plugin_register_init("exec", exec_init);
934   plugin_register_read("exec", exec_read);
935   plugin_register_notification("exec", exec_notification,
936                                /* user_data = */ NULL);
937   plugin_register_shutdown("exec", exec_shutdown);
938 } /* void module_register */