X-Git-Url: https://git.verplant.org/?a=blobdiff_plain;f=src%2Fexec.c;h=07c35c9bcf36d0e307ebbc48a93fb1e7fafa57e1;hb=a019b6c8144745db63c599680bd693ac02f11666;hp=dfdd11c18ed9b6079458f2f433258cacef4dc283;hpb=9c2dc5d943adc4b8a6081c85896bd87f825a4c23;p=collectd.git diff --git a/src/exec.c b/src/exec.c index dfdd11c1..07c35c9b 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,6 +1,6 @@ /** * collectd - src/exec.c - * Copyright (C) 2007 Florian octo Forster + * Copyright (C) 2007,2008 Florian octo Forster * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -22,7 +22,9 @@ #include "collectd.h" #include "common.h" #include "plugin.h" + #include "utils_cmd_putval.h" +#include "utils_cmd_putnotif.h" #include #include @@ -33,11 +35,20 @@ #define PL_NORMAL 0x01 #define PL_NOTIF_ACTION 0x02 -#define PL_NAGIOS_PLUGIN 0x04 + +#define PL_RUNNING 0x10 /* * Private data types */ +/* + * Access to this structure is serialized using the `pl_lock' lock and the + * `PL_RUNNING' flag. The execution of notifications is *not* serialized, so + * all functions used to handle notifications MUST NOT write to this structure. + * The `pid' and `status' fields are thus unused if the `PL_NOTIF_ACTION' flag + * is set. + * The `PL_RUNNING' flag is set in `exec_read' and unset in `exec_read_one'. + */ struct program_list_s; typedef struct program_list_s program_list_t; struct program_list_s @@ -52,10 +63,17 @@ struct program_list_s program_list_t *next; }; +typedef struct program_list_and_notification_s +{ + program_list_t *pl; + notification_t n; +} program_list_and_notification_t; + /* * Private variables */ static program_list_t *pl_head = NULL; +static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER; /* * Functions @@ -109,9 +127,7 @@ static int exec_config_exec (oconfig_item_t *ci) /* {{{ */ } memset (pl, '\0', sizeof (program_list_t)); - if (strcasecmp ("NagiosExec", ci->key) == 0) - pl->flags |= PL_NAGIOS_PLUGIN; - else if (strcasecmp ("NotificationExec", ci->key) == 0) + if (strcasecmp ("NotificationExec", ci->key) == 0) pl->flags |= PL_NOTIF_ACTION; else pl->flags |= PL_NORMAL; @@ -234,7 +250,6 @@ static int exec_config (oconfig_item_t *ci) /* {{{ */ { oconfig_item_t *child = ci->children + i; if ((strcasecmp ("Exec", child->key) == 0) - || (strcasecmp ("NagiosExec", child->key) == 0) || (strcasecmp ("NotificationExec", child->key) == 0)) exec_config_exec (child); else @@ -310,6 +325,25 @@ static void exec_child (program_list_t *pl) /* {{{ */ } } /* if (pl->group == NULL) */ +#if HAVE_SETGROUPS + if (getuid () == 0) + { + gid_t glist[2]; + size_t glist_len; + + glist[0] = gid; + glist_len = 1; + + if ((gid != egid) && (egid != -1)) + { + glist[1] = egid; + glist_len = 2; + } + + setgroups (glist_len, glist); + } +#endif /* HAVE_SETGROUPS */ + status = setgid (gid); if (status != 0) { @@ -344,49 +378,116 @@ static void exec_child (program_list_t *pl) /* {{{ */ exit (-1); } /* void exec_child }}} */ -static int fork_child (program_list_t *pl) /* {{{ */ +/* + * Creates three pipes (one for reading, one for writing and one for errors), + * forks a child, sets up the pipes so that fd_in is connected to STDIN of + * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR + * of the child. Then is calls `exec_child'. + */ +static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err) /* {{{ */ { - int fd_pipe[2]; + int fd_pipe_in[2]; + int fd_pipe_out[2]; + int fd_pipe_err[2]; + char errbuf[1024]; int status; + int pid; if (pl->pid != 0) return (-1); - status = pipe (fd_pipe); + status = pipe (fd_pipe_in); if (status != 0) { - char errbuf[1024]; ERROR ("exec plugin: pipe failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); } - pl->pid = fork (); - if (pl->pid < 0) + status = pipe (fd_pipe_out); + if (status != 0) + { + ERROR ("exec plugin: pipe failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + status = pipe (fd_pipe_err); + if (status != 0) + { + ERROR ("exec plugin: pipe failed: %s", + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + pid = fork (); + if (pid < 0) { - char errbuf[1024]; ERROR ("exec plugin: fork failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); } - else if (pl->pid == 0) + else if (pid == 0) { - close (fd_pipe[0]); + int fd_num; + int fd; - /* Connect the pipe to STDOUT and STDERR */ - if (fd_pipe[1] != STDOUT_FILENO) - dup2 (fd_pipe[1], STDOUT_FILENO); - if (fd_pipe[1] != STDERR_FILENO) - dup2 (fd_pipe[1], STDERR_FILENO); - if ((fd_pipe[1] != STDOUT_FILENO) && (fd_pipe[1] != STDERR_FILENO)) - close (fd_pipe[1]); + /* Close all file descriptors but the pipe end we need. */ + fd_num = getdtablesize (); + for (fd = 0; fd < fd_num; fd++) + { + if ((fd == fd_pipe_in[0]) + || (fd == fd_pipe_out[1]) + || (fd == fd_pipe_err[1])) + continue; + close (fd); + } + + /* Connect the `in' pipe to STDIN */ + if (fd_pipe_in[0] != STDIN_FILENO) + { + dup2 (fd_pipe_in[0], STDIN_FILENO); + close (fd_pipe_in[0]); + } + + /* Now connect the `out' pipe to STDOUT */ + if (fd_pipe_out[1] != STDOUT_FILENO) + { + dup2 (fd_pipe_out[1], STDOUT_FILENO); + close (fd_pipe_out[1]); + } + + /* Now connect the `out' pipe to STDOUT */ + if (fd_pipe_err[1] != STDERR_FILENO) + { + dup2 (fd_pipe_err[1], STDERR_FILENO); + close (fd_pipe_err[1]); + } exec_child (pl); /* does not return */ } - close (fd_pipe[1]); - return (fd_pipe[0]); + close (fd_pipe_in[0]); + close (fd_pipe_out[1]); + close (fd_pipe_err[1]); + + if (fd_in != NULL) + *fd_in = fd_pipe_in[1]; + else + close (fd_pipe_in[1]); + + if (fd_out != NULL) + *fd_out = fd_pipe_out[0]; + else + close (fd_pipe_out[0]); + + if (fd_err != NULL) + *fd_err = fd_pipe_err[0]; + else + close (fd_pipe_err[0]); + + return (pid); } /* int fork_child }}} */ static int parse_line (char *buffer) /* {{{ */ @@ -395,58 +496,126 @@ static int parse_line (char *buffer) /* {{{ */ int fields_num; fields[0] = "PUTVAL"; - fields_num = strsplit (buffer, &fields[1], STATIC_ARRAY_SIZE(fields) - 1); + fields_num = strsplit (buffer, fields + 1, STATIC_ARRAY_SIZE(fields) - 1); - handle_putval (stdout, fields, fields_num + 1); - return (0); + if (strcasecmp (fields[1], "putval") == 0) + return (handle_putval (stdout, fields + 1, fields_num)); + else if (strcasecmp (fields[1], "putnotif") == 0) + return (handle_putnotif (stdout, fields + 1, fields_num)); + + /* compatibility code */ + return (handle_putval (stdout, fields, fields_num + 1)); } /* int parse_line }}} */ static void *exec_read_one (void *arg) /* {{{ */ { program_list_t *pl = (program_list_t *) arg; - int fd; - FILE *fh; - char buffer[1024]; + int fd, fd_err, highest_fd; + fd_set fdset, copy; int status; + char buffer[1200]; /* if not completely read */ + char buffer_err[1024]; + char *pbuffer = buffer; + char *pbuffer_err = buffer_err; - fd = fork_child (pl); - if (fd < 0) + status = fork_child (pl, NULL, &fd, &fd_err); + if (status < 0) pthread_exit ((void *) 1); + pl->pid = status; assert (pl->pid != 0); - fh = fdopen (fd, "r"); - if (fh == NULL) - { - char errbuf[1024]; - ERROR ("exec plugin: fdopen (%i) failed: %s", fd, - sstrerror (errno, errbuf, sizeof (errbuf))); - kill (pl->pid, SIGTERM); - close (fd); - pthread_exit ((void *) 1); - } + FD_ZERO( &fdset ); + FD_SET(fd, &fdset); + FD_SET(fd_err, &fdset); + + /* Determine the highest file descriptor */ + highest_fd = (fd > fd_err) ? fd : fd_err; - buffer[0] = '\0'; - while (fgets (buffer, sizeof (buffer), fh) != NULL) + /* We use a copy of fdset, as select modifies it */ + copy = fdset; + + while (select(highest_fd + 1, ©, NULL, NULL, NULL ) > 0) { int len; - len = strlen (buffer); + if (FD_ISSET(fd, ©)) + { + char *pnl; - /* Remove newline from end. */ - while ((len > 0) && ((buffer[len - 1] == '\n') - || (buffer[len - 1] == '\r'))) - buffer[--len] = '\0'; + len = read(fd, pbuffer, sizeof(buffer) - 1 - (pbuffer - buffer)); - DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer); + if (len < 0) + { + if (errno == EAGAIN || errno == EINTR) continue; + break; + } + else if (len == 0) break; /* We've reached EOF */ - if (pl->flags & PL_NAGIOS_PLUGIN) - break; + pbuffer[len] = '\0'; - parse_line (buffer); - } /* while (fgets) */ + len += pbuffer - buffer; + pbuffer = buffer; - fclose (fh); + while ((pnl = strchr(pbuffer, '\n'))) + { + *pnl = '\0'; + if (*(pnl-1) == '\r' ) *(pnl-1) = '\0'; + + parse_line (pbuffer); + + pbuffer = ++pnl; + } + /* not completely read ? */ + if (pbuffer - buffer < len) + { + len -= pbuffer - buffer; + memmove(buffer, pbuffer, len); + pbuffer = buffer + len; + } + else + pbuffer = buffer; + } + else if (FD_ISSET(fd_err, ©)) + { + char *pnl; + + len = read(fd_err, pbuffer_err, sizeof(buffer_err) - 1 - (pbuffer_err - buffer_err)); + + if (len < 0) + { + if (errno == EAGAIN || errno == EINTR) continue; + break; + } + else if (len == 0) break; /* We've reached EOF */ + + pbuffer_err[len] = '\0'; + + len += pbuffer_err - buffer_err; + pbuffer_err = buffer_err; + + while ((pnl = strchr(pbuffer_err, '\n'))) + { + *pnl = '\0'; + if (*(pnl-1) == '\r' ) *(pnl-1) = '\0'; + + ERROR ("exec plugin: exec_read_one: error = %s", pbuffer_err); + + pbuffer_err = ++pnl; + } + /* not completely read ? */ + if (pbuffer_err - buffer_err < len) + { + len -= pbuffer_err - buffer_err; + memmove(buffer_err, pbuffer_err, len); + pbuffer_err = buffer_err + len; + } + else + pbuffer_err = buffer_err; + } + /* reset copy */ + copy = fdset; + } if (waitpid (pl->pid, &status, 0) > 0) pl->status = status; @@ -454,33 +623,85 @@ static void *exec_read_one (void *arg) /* {{{ */ DEBUG ("exec plugin: Child %i exited with status %i.", (int) pl->pid, pl->status); - if (pl->flags & PL_NAGIOS_PLUGIN) - { - notification_t n; + pl->pid = 0; - memset (&n, '\0', sizeof (n)); - - n.severity = NOTIF_FAILURE; - if (pl->status == 0) - n.severity = NOTIF_OKAY; - else if (pl->status == 1) - n.severity = NOTIF_WARNING; + pthread_mutex_lock (&pl_lock); + pl->flags &= ~PL_RUNNING; + pthread_mutex_unlock (&pl_lock); - strncpy (n.message, buffer, sizeof (n.message)); - n.message[sizeof (n.message) - 1] = '\0'; + close (fd); + close (fd_err); - n.time = time (NULL); + pthread_exit ((void *) 0); + return (NULL); +} /* void *exec_read_one }}} */ - strncpy (n.host, hostname_g, sizeof (n.host)); - n.host[sizeof (n.host) - 1] = '\0'; +static void *exec_notification_one (void *arg) /* {{{ */ +{ + program_list_t *pl = ((program_list_and_notification_t *) arg)->pl; + const notification_t *n = &((program_list_and_notification_t *) arg)->n; + int fd; + FILE *fh; + int pid; + int status; + const char *severity; - plugin_dispatch_notification (&n); + pid = fork_child (pl, &fd, NULL, NULL); + if (pid < 0) { + sfree (arg); + pthread_exit ((void *) 1); } - pl->pid = 0; + fh = fdopen (fd, "w"); + if (fh == NULL) + { + char errbuf[1024]; + ERROR ("exec plugin: fdopen (%i) failed: %s", fd, + sstrerror (errno, errbuf, sizeof (errbuf))); + kill (pl->pid, SIGTERM); + pl->pid = 0; + close (fd); + sfree (arg); + pthread_exit ((void *) 1); + } + + severity = "FAILURE"; + if (n->severity == NOTIF_WARNING) + severity = "WARNING"; + else if (n->severity == NOTIF_OKAY) + severity = "OKAY"; + + fprintf (fh, + "Severity: %s\n" + "Time: %u\n", + severity, (unsigned int) n->time); + + /* Print the optional fields */ + if (strlen (n->host) > 0) + fprintf (fh, "Host: %s\n", n->host); + if (strlen (n->plugin) > 0) + fprintf (fh, "Plugin: %s\n", n->plugin); + if (strlen (n->plugin_instance) > 0) + fprintf (fh, "PluginInstance: %s\n", n->plugin_instance); + if (strlen (n->type) > 0) + fprintf (fh, "Type: %s\n", n->type); + if (strlen (n->type_instance) > 0) + fprintf (fh, "TypeInstance: %s\n", n->type_instance); + + fprintf (fh, "\n%s\n", n->message); + + fflush (fh); + fclose (fh); + + waitpid (pid, &status, 0); + + DEBUG ("exec plugin: Child %i exited with status %i.", + pid, status); + + sfree (arg); pthread_exit ((void *) 0); return (NULL); -} /* void *exec_read_one }}} */ +} /* void *exec_notification_one }}} */ static int exec_init (void) /* {{{ */ { @@ -502,9 +723,20 @@ static int exec_read (void) /* {{{ */ pthread_t t; pthread_attr_t attr; - if (pl->pid != 0) + /* Only execute `normal' style executables here. */ + if ((pl->flags & PL_NORMAL) == 0) continue; + pthread_mutex_lock (&pl_lock); + /* Skip if a child is already running. */ + if ((pl->flags & PL_RUNNING) != 0) + { + pthread_mutex_unlock (&pl_lock); + continue; + } + pl->flags |= PL_RUNNING; + pthread_mutex_unlock (&pl_lock); + pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_create (&t, &attr, exec_read_one, (void *) pl); @@ -513,6 +745,43 @@ static int exec_read (void) /* {{{ */ return (0); } /* int exec_read }}} */ +static int exec_notification (const notification_t *n) +{ + program_list_t *pl; + program_list_and_notification_t *pln; + + for (pl = pl_head; pl != NULL; pl = pl->next) + { + pthread_t t; + pthread_attr_t attr; + + /* Only execute `notification' style executables here. */ + if ((pl->flags & PL_NOTIF_ACTION) == 0) + continue; + + /* Skip if a child is already running. */ + if (pl->pid != 0) + continue; + + pln = (program_list_and_notification_t *) malloc (sizeof + (program_list_and_notification_t)); + if (pln == NULL) + { + ERROR ("exec plugin: malloc failed."); + continue; + } + + pln->pl = pl; + memcpy (&pln->n, n, sizeof (notification_t)); + + pthread_attr_init (&attr); + pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); + pthread_create (&t, &attr, exec_notification_one, (void *) pln); + } /* for (pl) */ + + return (0); +} /* int exec_notification */ + static int exec_shutdown (void) /* {{{ */ { program_list_t *pl; @@ -544,6 +813,7 @@ void module_register (void) plugin_register_complex_config ("exec", exec_config); plugin_register_init ("exec", exec_init); plugin_register_read ("exec", exec_read); + plugin_register_notification ("exec", exec_notification); plugin_register_shutdown ("exec", exec_shutdown); } /* void module_register */