X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fexec.c;h=acc6cf6f12aa93ea7fc743e78cc378acaf78c29a;hb=9c98fa31ef50a6ff849d36cac4f5297faa6f7909;hp=44f03e3387a9236260a6388eb3efbd62596dfac7;hpb=4cf9eb921f5994d4515613efa8739ce445cfd9a1;p=collectd.git diff --git a/src/exec.c b/src/exec.c index 44f03e33..acc6cf6f 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,6 +1,8 @@ /** * collectd - src/exec.c - * Copyright (C) 2007,2008 Florian octo Forster + * Copyright (C) 2007-2009 Florian octo Forster + * Copyright (C) 2007-2009 Sebastian Harl + * Copyright (C) 2008 Peter Holik * * 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 @@ -17,8 +19,12 @@ * * Authors: * Florian octo Forster + * Sebastian Harl + * Peter Holik **/ +#define _BSD_SOURCE /* For setgroups */ + #include "collectd.h" #include "common.h" #include "plugin.h" @@ -35,7 +41,6 @@ #define PL_NORMAL 0x01 #define PL_NOTIF_ACTION 0x02 -#define PL_NAGIOS_PLUGIN 0x04 #define PL_RUNNING 0x10 @@ -79,7 +84,7 @@ static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER; /* * Functions */ -static void sigchld_handler (int signal) /* {{{ */ +static void sigchld_handler (int __attribute__((unused)) signal) /* {{{ */ { pid_t pid; int status; @@ -128,11 +133,6 @@ static int exec_config_exec (oconfig_item_t *ci) /* {{{ */ } memset (pl, '\0', sizeof (program_list_t)); -#if 0 - if (strcasecmp ("NagiosExec", ci->key) == 0) - pl->flags |= PL_NAGIOS_PLUGIN; - else -#endif if (strcasecmp ("NotificationExec", ci->key) == 0) pl->flags |= PL_NOTIF_ACTION; else @@ -176,10 +176,9 @@ static int exec_config_exec (oconfig_item_t *ci) /* {{{ */ { char *tmp = strrchr (ci->values[1].value.string, '/'); if (tmp == NULL) - strncpy (buffer, ci->values[1].value.string, sizeof (buffer)); + sstrncpy (buffer, ci->values[1].value.string, sizeof (buffer)); else - strncpy (buffer, tmp + 1, sizeof (buffer)); - buffer[sizeof (buffer) - 1] = '\0'; + sstrncpy (buffer, tmp + 1, sizeof (buffer)); } pl->argv[0] = strdup (buffer); if (pl->argv[0] == NULL) @@ -202,17 +201,16 @@ static int exec_config_exec (oconfig_item_t *ci) /* {{{ */ { if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER) { - snprintf (buffer, sizeof (buffer), "%lf", + ssnprintf (buffer, sizeof (buffer), "%lf", ci->values[i + 1].value.number); } else { if (ci->values[i + 1].value.boolean) - strncpy (buffer, "true", sizeof (buffer)); + sstrncpy (buffer, "true", sizeof (buffer)); else - strncpy (buffer, "false", sizeof (buffer)); + sstrncpy (buffer, "false", sizeof (buffer)); } - buffer[sizeof (buffer) - 1] = '\0'; pl->argv[i] = strdup (buffer); } @@ -256,9 +254,6 @@ static int exec_config (oconfig_item_t *ci) /* {{{ */ { oconfig_item_t *child = ci->children + i; if ((strcasecmp ("Exec", child->key) == 0) -#if 0 - || (strcasecmp ("NagiosExec", child->key) == 0) -#endif || (strcasecmp ("NotificationExec", child->key) == 0)) exec_config_exec (child); else @@ -270,6 +265,17 @@ static int exec_config (oconfig_item_t *ci) /* {{{ */ return (0); } /* int exec_config }}} */ +static void set_environment (void) /* {{{ */ +{ + char buffer[1024]; + + ssnprintf (buffer, sizeof (buffer), "%i", interval_g); + setenv ("COLLECTD_INTERVAL", buffer, /* overwrite = */ 1); + + ssnprintf (buffer, sizeof (buffer), "%s", hostname_g); + setenv ("COLLECTD_HOSTNAME", buffer, /* overwrite = */ 1); +} /* }}} void set_environment */ + static void exec_child (program_list_t *pl) /* {{{ */ { int status; @@ -334,6 +340,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) { @@ -368,15 +393,27 @@ static void exec_child (program_list_t *pl) /* {{{ */ exit (-1); } /* void exec_child }}} */ +static void reset_signal_mask (void) /* {{{ */ +{ + sigset_t ss; + + memset (&ss, 0, sizeof (ss)); + sigemptyset (&ss); + sigprocmask (SIG_SETMASK, &ss, /* old mask = */ NULL); +} /* }}} void reset_signal_mask */ + /* - * Creates two pipes (one for reading, ong for writing), 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 STDERR of the child. Then is calls `exec_child'. + * 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) /* {{{ */ +static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err) /* {{{ */ { int fd_pipe_in[2]; int fd_pipe_out[2]; + int fd_pipe_err[2]; + char errbuf[1024]; int status; int pid; @@ -386,7 +423,6 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ status = pipe (fd_pipe_in); if (status != 0) { - char errbuf[1024]; ERROR ("exec plugin: pipe failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); @@ -395,7 +431,14 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ status = pipe (fd_pipe_out); if (status != 0) { - char errbuf[1024]; + 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); @@ -404,58 +447,59 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ pid = fork (); if (pid < 0) { - char errbuf[1024]; ERROR ("exec plugin: fork failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); } else if (pid == 0) { - close (fd_pipe_in[1]); - close (fd_pipe_out[0]); + int fd_num; + int fd; - /* If the `out' pipe has the filedescriptor STDIN we have to be careful - * with the `dup's below. So, if this is the case we have to handle the - * `out' pipe first. */ - if (fd_pipe_out[1] == STDIN_FILENO) + /* Close all file descriptors but the pipe end we need. */ + fd_num = getdtablesize (); + for (fd = 0; fd < fd_num; fd++) { - int new_fileno = (fd_pipe_in[0] == STDOUT_FILENO) - ? STDERR_FILENO : STDOUT_FILENO; - dup2 (fd_pipe_out[1], new_fileno); - close (fd_pipe_out[1]); - fd_pipe_out[1] = new_fileno; + if ((fd == fd_pipe_in[0]) + || (fd == fd_pipe_out[1]) + || (fd == fd_pipe_err[1])) + continue; + close (fd); } - /* Now `fd_pipe_out[1]' is either `STDOUT' or `STDERR', but definitely not - * `STDIN_FILENO'. */ /* 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]); - fd_pipe_in[0] = STDIN_FILENO; } - /* Now connect the `out' pipe to STDOUT and STDERR */ + /* Now connect the `out' pipe to STDOUT */ if (fd_pipe_out[1] != STDOUT_FILENO) + { dup2 (fd_pipe_out[1], STDOUT_FILENO); - if (fd_pipe_out[1] != STDERR_FILENO) - dup2 (fd_pipe_out[1], STDERR_FILENO); + close (fd_pipe_out[1]); + } - /* If the pipe has some FD that's something completely different, close it - * now. */ - if ((fd_pipe_out[1] != STDOUT_FILENO) && (fd_pipe_out[1] != STDERR_FILENO)) + /* Now connect the `out' pipe to STDOUT */ + if (fd_pipe_err[1] != STDERR_FILENO) { - close (fd_pipe_out[1]); - fd_pipe_out[1] = STDOUT_FILENO; + dup2 (fd_pipe_err[1], STDERR_FILENO); + close (fd_pipe_err[1]); } + set_environment (); + + /* Unblock all signals */ + reset_signal_mask (); + exec_child (pl); /* does not return */ } 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]; @@ -467,110 +511,168 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ 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) /* {{{ */ { - char *fields[256]; - int fields_num; - - fields[0] = "PUTVAL"; - fields_num = strsplit (buffer, fields + 1, STATIC_ARRAY_SIZE(fields) - 1); - - 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)); + if (strncasecmp ("PUTVAL", buffer, strlen ("PUTVAL")) == 0) + return (handle_putval (stdout, buffer)); + else if (strncasecmp ("PUTNOTIF", buffer, strlen ("PUTNOTIF")) == 0) + return (handle_putnotif (stdout, buffer)); + else + { + /* For backwards compatibility */ + char tmp[1220]; + /* Let's annoy the user a bit.. */ + INFO ("exec plugin: Prepending `PUTVAL' to this line: %s", buffer); + ssnprintf (tmp, sizeof (tmp), "PUTVAL %s", buffer); + return (handle_putval (stdout, tmp)); + } } /* 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; - status = fork_child (pl, NULL, &fd); + 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); - pl->pid = 0; - 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'; - if (waitpid (pl->pid, &status, 0) > 0) - pl->status = status; + parse_line (pbuffer); - DEBUG ("exec plugin: Child %i exited with status %i.", - (int) pl->pid, pl->status); + 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; - if (pl->flags & PL_NAGIOS_PLUGIN) - { - notification_t n; + 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) + { + /* We've reached EOF */ + NOTICE ("exec plugin: Program `%s' has closed STDERR.", + pl->exec); + close (fd_err); + FD_CLR (fd_err, &fdset); + highest_fd = fd; + fd_err = -1; + continue; + } - 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; + pbuffer_err[len] = '\0'; - strncpy (n.message, buffer, sizeof (n.message)); - n.message[sizeof (n.message) - 1] = '\0'; + len += pbuffer_err - buffer_err; + pbuffer_err = buffer_err; - n.time = time (NULL); + while ((pnl = strchr(pbuffer_err, '\n'))) + { + *pnl = '\0'; + if (*(pnl-1) == '\r' ) *(pnl-1) = '\0'; - strncpy (n.host, hostname_g, sizeof (n.host)); - n.host[sizeof (n.host) - 1] = '\0'; + ERROR ("exec plugin: exec_read_one: error = %s", pbuffer_err); - plugin_dispatch_notification (&n); + 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; } + DEBUG ("exec plugin: exec_read_one: Waiting for `%s' to exit.", pl->exec); + if (waitpid (pl->pid, &status, 0) > 0) + pl->status = status; + + DEBUG ("exec plugin: Child %i exited with status %i.", + (int) pl->pid, pl->status); + pl->pid = 0; pthread_mutex_lock (&pl_lock); pl->flags &= ~PL_RUNNING; pthread_mutex_unlock (&pl_lock); + close (fd); + if (fd_err >= 0) + close (fd_err); + pthread_exit ((void *) 0); return (NULL); } /* void *exec_read_one }}} */ @@ -578,14 +680,15 @@ static void *exec_read_one (void *arg) /* {{{ */ 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; + notification_t *n = &((program_list_and_notification_t *) arg)->n; + notification_meta_t *meta; int fd; FILE *fh; int pid; int status; const char *severity; - pid = fork_child (pl, &fd, NULL); + pid = fork_child (pl, &fd, NULL, NULL); if (pid < 0) { sfree (arg); pthread_exit ((void *) 1); @@ -627,6 +730,21 @@ static void *exec_notification_one (void *arg) /* {{{ */ if (strlen (n->type_instance) > 0) fprintf (fh, "TypeInstance: %s\n", n->type_instance); + for (meta = n->meta; meta != NULL; meta = meta->next) + { + if (meta->type == NM_TYPE_STRING) + fprintf (fh, "%s: %s\n", meta->name, meta->nm_value.nm_string); + else if (meta->type == NM_TYPE_SIGNED_INT) + fprintf (fh, "%s: %"PRIi64"\n", meta->name, meta->nm_value.nm_signed_int); + else if (meta->type == NM_TYPE_UNSIGNED_INT) + fprintf (fh, "%s: %"PRIu64"\n", meta->name, meta->nm_value.nm_unsigned_int); + else if (meta->type == NM_TYPE_DOUBLE) + fprintf (fh, "%s: %e\n", meta->name, meta->nm_value.nm_double); + else if (meta->type == NM_TYPE_BOOLEAN) + fprintf (fh, "%s: %s\n", meta->name, + meta->nm_value.nm_boolean ? "true" : "false"); + } + fprintf (fh, "\n%s\n", n->message); fflush (fh); @@ -637,6 +755,9 @@ static void *exec_notification_one (void *arg) /* {{{ */ DEBUG ("exec plugin: Child %i exited with status %i.", pid, status); + if (n->meta != NULL) + plugin_notification_meta_free (n->meta); + n->meta = NULL; sfree (arg); pthread_exit ((void *) 0); return (NULL); @@ -662,8 +783,8 @@ static int exec_read (void) /* {{{ */ pthread_t t; pthread_attr_t attr; - /* Only execute `normal' and `nagios' style executables here. */ - if ((pl->flags & (PL_NAGIOS_PLUGIN | PL_NORMAL)) == 0) + /* Only execute `normal' style executables here. */ + if ((pl->flags & PL_NORMAL) == 0) continue; pthread_mutex_lock (&pl_lock); @@ -684,7 +805,8 @@ static int exec_read (void) /* {{{ */ return (0); } /* int exec_read }}} */ -static int exec_notification (const notification_t *n) +static int exec_notification (const notification_t *n, + user_data_t __attribute__((unused)) *user_data) { program_list_t *pl; program_list_and_notification_t *pln; @@ -713,13 +835,18 @@ static int exec_notification (const notification_t *n) pln->pl = pl; memcpy (&pln->n, n, sizeof (notification_t)); + /* Set the `meta' member to NULL, otherwise `plugin_notification_meta_copy' + * will run into an endless loop. */ + pln->n.meta = NULL; + plugin_notification_meta_copy (&pln->n, n); + 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 */ +} /* }}} int exec_notification */ static int exec_shutdown (void) /* {{{ */ { @@ -752,7 +879,8 @@ 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_notification ("exec", exec_notification, + /* user_data = */ NULL); plugin_register_shutdown ("exec", exec_shutdown); } /* void module_register */