From e6460e9558f6bc0c77c2578722c9a83849baa2e1 Mon Sep 17 00:00:00 2001 From: David Smith Date: Mon, 4 Apr 2011 11:30:26 -0500 Subject: [PATCH] Handle logfile paths that end in '|', treat it as a command to read data from. * pcp/src/pmdas/logger/util.c: New file. * pcp/src/pmdas/logger/util.h: Ditto. * pcp/src/pmdas/logger/event.c (event_init): Handle opening pipe "paths". (event_shutdown): New function that stops piped processes and closes all files. * pcp/src/pmdas/logger/event.h: Added event_shutdown() prototype. * pcp/src/pmdas/logger/logger.c (read_config): Moved lstrip/rstrip code to util.c. (main): Calls event_shutdown() after pmdaMain() returns to clean everything up. * pcp/src/pmdas/logger/GNUmakefile.in: Added util.c and util.h. * pcp/src/pmdas/logger/Install: Support entering pipe "paths". --- pcp/src/pmdas/logger/GNUmakefile.in | 5 +- pcp/src/pmdas/logger/Install | 17 +-- pcp/src/pmdas/logger/event.c | 71 +++++++++++-- pcp/src/pmdas/logger/event.h | 1 + pcp/src/pmdas/logger/logger.c | 37 +++---- pcp/src/pmdas/logger/util.c | 157 ++++++++++++++++++++++++++++ pcp/src/pmdas/logger/util.h | 29 +++++ 7 files changed, 274 insertions(+), 43 deletions(-) create mode 100644 pcp/src/pmdas/logger/util.c create mode 100644 pcp/src/pmdas/logger/util.h diff --git a/pcp/src/pmdas/logger/GNUmakefile.in b/pcp/src/pmdas/logger/GNUmakefile.in index 7e192eb64..88404438c 100644 --- a/pcp/src/pmdas/logger/GNUmakefile.in +++ b/pcp/src/pmdas/logger/GNUmakefile.in @@ -28,8 +28,8 @@ LIBTARGET = pmda_logger.$(DSOSUFFIX) TARGETS = $(CMDTARGET) $(LIBTARGET) DFILES = README -CFILES = logger.c percontext.c event.c -HFILES = percontext.h logger.h event.h +CFILES = logger.c percontext.c event.c util.c +HFILES = percontext.h logger.h event.h util.h LLDLIBS = -lpcp -lpcp_pmda LCFLAGS = -I. LSRCFILES = Install Remove pmns help $(DFILES) root GNUmakefile.install @@ -47,6 +47,7 @@ default_pcp default: domain.h $(TARGETS) logger.o: domain.h logger.o percontext.o event.o: percontext.h event.o logger.o: event.h +util.o event.o logger.o: util.h include $(BUILDRULES) diff --git a/pcp/src/pmdas/logger/Install b/pcp/src/pmdas/logger/Install index 9475dad59..833e8bf4c 100644 --- a/pcp/src/pmdas/logger/Install +++ b/pcp/src/pmdas/logger/Install @@ -62,7 +62,6 @@ _parsedefaults() esac done eval configfile='$'$OPTIND - echo "_parsedefaults found $configfile" } # Get logfile(s) to monitor @@ -105,9 +104,11 @@ $1 == "'$iam'" { printf "%s",$6 echo echo \ -'Enter the PMNS name and logfile path. An empty line terminates the -logfile selection process and there must be at least one logfile specified. -' +'Enter the PMNS name and logfile path. If the path ends in "|", the +filename is interpreted as a command which will output data. + +An empty line terminates the logfile selection process and there must +be at least one logfile specified. ' args="" touch $configfile @@ -138,8 +139,12 @@ logfile selection process and there must be at least one logfile specified. echo "Sorry, pathname \"$pathname\" already specified. Please try again." continue fi - if [ ! -e "$pathname" ]; then - echo "Error: pathname $value doesn't exist! Reinstall to change." + if echo $pathname | grep "|[ \t]*$" > /dev/null; then + # Accept pipe "paths" as is + true + elif [ ! -e "$pathname" ]; then + echo "Error: pathname $pathname doesn't exist!." + continue fi echo "$name $pathname" >>$configfile done diff --git a/pcp/src/pmdas/logger/event.c b/pcp/src/pmdas/logger/event.c index 13fd430f7..c223bf266 100644 --- a/pcp/src/pmdas/logger/event.c +++ b/pcp/src/pmdas/logger/event.c @@ -29,6 +29,7 @@ #include #include "event.h" #include "percontext.h" +#include "util.h" #define BUF_SIZE 1024 @@ -43,10 +44,9 @@ TAILQ_HEAD(tailhead, event); struct EventFileData { struct LogfileData *logfile; int fd; + pid_t pid; int numclients; struct tailhead head; - - /* current client list - allocated? */ }; static struct EventFileData *file_data_tab = NULL; @@ -103,7 +103,7 @@ event_init(pmdaInterface *dispatch, struct LogfileData *logfiles, ctx_register_callbacks(ctx_start_callback, ctx_end_callback); /* Allocate our EventFileData table. */ - file_data_tab = malloc(sizeof(struct EventFileData) * numlogfiles); + file_data_tab = calloc(numlogfiles, sizeof(struct EventFileData)); if (file_data_tab == NULL) { fprintf(stderr, "%s: allocation error: %s\n", __FUNCTION__, strerror(errno)); @@ -113,7 +113,6 @@ event_init(pmdaInterface *dispatch, struct LogfileData *logfiles, /* Fill it the table. */ for (i = 0; i < numlogfiles; i++) { file_data_tab[i].logfile = &logfiles[i]; - file_data_tab[i].numclients = 0; TAILQ_INIT(&file_data_tab[i].head); /* initialize queue */ } @@ -128,9 +127,38 @@ event_init(pmdaInterface *dispatch, struct LogfileData *logfiles, /* Try to open all the logfiles to monitor */ for (i = 0; i < numlogfiles; i++) { - file_data_tab[i].fd = open(logfiles[i].pathname, O_RDONLY|O_NONBLOCK); + size_t pathlen = strlen(logfiles[i].pathname); + + /* We support 2 kinds of PATHNAMEs: + * + * (1) Regular paths. These paths are opened normally. + * + * (2) Pipes. If the path ends in '|', the filename is + * interpreted as a command which pipes input to us. + * + * Handle both. + */ + + if (logfiles[i].pathname[pathlen - 1] != '|') { + file_data_tab[i].fd = open(logfiles[i].pathname, + O_RDONLY|O_NONBLOCK); + } + else { + char cmd[MAXPATHLEN]; + + strncpy(cmd, logfiles[i].pathname, sizeof(cmd)); + cmd[pathlen - 1] = '\0'; /* get rid of the '|' */ + /* Remove all trailing whitespace. */ + rstrip(cmd); + + /* Start the command. */ + file_data_tab[i].fd = start_cmd(cmd, &file_data_tab[i].pid); + } + if (file_data_tab[i].fd < 0) { __pmNotifyErr(LOG_ERR, "open failure on %s", logfiles[i].pathname); + /* Cleanup. */ + event_shutdown(); exit(1); } @@ -152,18 +180,21 @@ event_create(int logfile) } /* Read up to BUF_SIZE bytes at a time. */ - if ((c = read(file_data_tab[logfile].fd, e->buffer, - sizeof(e->buffer) - 1)) < 0) { + c = read(file_data_tab[logfile].fd, e->buffer, sizeof(e->buffer) - 1); + + /* If we've got EOF (0 bytes read) or EBADF (fd isn't valid - most + * likely a closed pipe), just ignore the error. */ + if (c == 0 || (c < 0 && errno == EBADF)) { + free(e); + return 0; + } + if (c < 0) { __pmNotifyErr(LOG_ERR, "read failure on %s: %s", file_data_tab[logfile].logfile->pathname, strerror(errno)); free(e); return -1; } - else if (c == 0) { /* EOF */ - free(e); - return 0; - } /* Store event in queue. */ e->clients = file_data_tab[logfile].numclients; @@ -275,3 +306,21 @@ event_cleanup(void) } } } + +void +event_shutdown(void) +{ + int i; + + __pmNotifyErr(LOG_INFO, "%s: Shutting down...", __FUNCTION__); + for (i = 0; i < numlogfiles; i++) { + if (file_data_tab[i].pid != 0) { + (void) stop_cmd(file_data_tab[i].pid); + file_data_tab[i].pid = 0; + } + if (file_data_tab[i].fd > 0) { + close(file_data_tab[i].fd); + file_data_tab[i].fd = 0; + } + } +} diff --git a/pcp/src/pmdas/logger/event.h b/pcp/src/pmdas/logger/event.h index d32045316..6f190273a 100644 --- a/pcp/src/pmdas/logger/event.h +++ b/pcp/src/pmdas/logger/event.h @@ -31,5 +31,6 @@ extern void event_init(pmdaInterface *dispatch, struct LogfileData *logfiles, int numlogfiles); extern int event_fetch(pmValueBlock **vbpp, unsigned int logfile); extern int event_get_clients_per_logfile(unsigned int logfile); +extern void event_shutdown(void); #endif /* _EVENT_H */ diff --git a/pcp/src/pmdas/logger/logger.c b/pcp/src/pmdas/logger/logger.c index 68b463611..2824b3164 100644 --- a/pcp/src/pmdas/logger/logger.c +++ b/pcp/src/pmdas/logger/logger.c @@ -27,6 +27,7 @@ #include "domain.h" #include "percontext.h" #include "event.h" +#include "util.h" /* * Logger PMDA @@ -63,11 +64,11 @@ static struct dynamic_metric_info *dynamic_metric_infotab = NULL; static pmdaMetric dynamic_metrictab[] = { /* perfile.{LOGFILE}.numclients */ - { (void *)0, + { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) }, }, /* perfile.{LOGFILE}.records */ - { (void *)1, + { NULL, /* m_user gets filled in later */ { 0 /* pmid gets filled in later */, PM_TYPE_EVENT, PM_INDOM_NULL, PM_SEM_INSTANT, PMDA_PMUNITS(0,0,0,0,0,0) }, }, }; @@ -241,14 +242,8 @@ read_config(const char *filename) } line[len - 1] = '\0'; /* Remove the '\n'. */ - /* Remove all trailing whitespace. Set ptr to last char of - * string. */ - ptr = line + strlen(line) - 1; - /* While trailing whitespace, move back. */ - while (ptr >= line && isspace(*ptr)) { - --ptr; - } - *(ptr+1) = '\0'; /* Now set '\0' as terminal byte. */ + /* Strip all trailing whitespace. */ + rstrip(line); /* If the string is now empty, just ignore the line. */ len = strlen(line); @@ -256,14 +251,9 @@ read_config(const char *filename) continue; } - /* Skip past all leading whitespace. Note that the string - * can't be completely blank here, our earlier check would - * have caught that, so we don't have to worry about hitting - * the end of the string. */ - name = line; - while (isspace(*name)) { - name++; - } + /* Skip past all leading whitespace to find the start of + * NAME. */ + name = lstrip(line); /* Now we need to split the line into 2 parts: NAME and * PATHNAME. NAME can't have whitespace in it, so look for @@ -300,9 +290,7 @@ read_config(const char *filename) } /* Skip past any extra whitespace between NAME and PATHNAME */ - while (*ptr != '\0' && isspace(*ptr)) { - ptr++; - } + ptr = lstrip(ptr); /* Make sure PATHNAME (the rest of the line) isn't too long. */ if (strlen(ptr) > MAXPATHLEN) { @@ -326,7 +314,7 @@ read_config(const char *filename) strncpy(data->pathname, ptr, sizeof(data->pathname)); /* data->pmid_string gets filled in after pmdaInit() is called. */ - __pmNotifyErr(LOG_INFO, "%s: sa wlogfile %s (%s)\n", __FUNCTION__, + __pmNotifyErr(LOG_INFO, "%s: saw logfile %s (%s)\n", __FUNCTION__, data->pathname, data->pmns_name); } if (rc != 0) { @@ -454,8 +442,8 @@ logger_init(pmdaInterface *dp) dp->version.four.pmid = logger_pmid; dp->version.four.name = logger_name; dp->version.four.children = logger_children; - /* DRS: if we want to generate help text for the dynamic metrics, - * we'll have to override 'four.text'. */ + /* FIXME: if we want to generate help text for the dynamic + * metrics, we'll have to override 'four.text'. */ pmdaSetFetchCallBack(dp, logger_fetchCallBack); pmdaSetEndContextCallBack(dp, logger_end_contextCallBack); @@ -535,5 +523,6 @@ main(int argc, char **argv) pmdaMain(&desc); + event_shutdown(); exit(0); } diff --git a/pcp/src/pmdas/logger/util.c b/pcp/src/pmdas/logger/util.c new file mode 100644 index 000000000..eca1bb0b7 --- /dev/null +++ b/pcp/src/pmdas/logger/util.c @@ -0,0 +1,157 @@ +/* + * Utiltiy functions for the logger PMDA. + * + * Copyright (c) 2011 Red Hat Inc. + * + * 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" + +void +rstrip(char *str) +{ + char *ptr; + + /* Remove all trailing whitespace. Set ptr to last char of + * string. */ + ptr = str + strlen(str) - 1; + /* While trailing whitespace, move back. */ + while (ptr >= str && isspace(*ptr)) { + --ptr; + } + *(ptr+1) = '\0'; /* Now set '\0' as terminal byte. */ +} + +char * +lstrip(char *str) +{ + /* While leading whitespace, move forward. */ + char *ptr = str; + while (*ptr != '\0' && isspace(*ptr)) { + ptr++; + } + return ptr; +} + +int +start_cmd(const char *cmd, pid_t *ppid) +{ + pid_t child_pid; + int rc; + int pipe_fds[2]; +#define PARENT_END 0 /* parent end of the pipe */ +#define CHILD_END 1 /* child end of the pipe */ +#define STDOUT_FD 1 /* stdout fd */ + + /* FIXME items: + * (1) Should we be looking to handle shell metachars + * differently? Perhaps we should just allow isalnum()||isspace() + * chars only. + * (2) Do we need to clean up the environment in the child before + * the exec()? Remove things like IFS, CDPATH, ENV, and BASH_ENV. + */ + + __pmNotifyErr(LOG_INFO, "%s: Trying to run command: %s", __FUNCTION__, + cmd); + + /* Create the pipes. */ + rc = pipe2(pipe_fds, O_CLOEXEC|O_NONBLOCK); + if (rc < 0) { + __pmNotifyErr(LOG_ERR, "%s: pipe2() returned %s", __FUNCTION__, + strerror(-rc)); + return rc; + } + + /* Create the new process. */ + child_pid = fork(); + if (child_pid == 0) { /* child process */ + int i; + + /* Duplicate our pipe fd onto stdout of the child. Note that + * this clears O_CLOEXEC, so the new stdout should stay open + * when we call exec(). */ + if (dup2(pipe_fds[CHILD_END], STDOUT_FD) < 0) { + __pmNotifyErr(LOG_ERR, "%s: dup2() returned %s", __FUNCTION__, + strerror(errno)); + _exit(127); + } + + /* Close all other fds. */ + for (i = 0; i <= pipe_fds[CHILD_END]; i++) { + if (i != STDOUT_FD) { + close(i); + } + } + + /* Actually run the command. */ + execl ("/bin/sh", "sh", "-c", cmd, (char *)NULL); + _exit (127); + + } + else if (child_pid > 0) { /* parent process */ + close (pipe_fds[PARENT_END]); + if (ppid != NULL) { + *ppid = child_pid; + } + } + else if (child_pid < 0) { /* fork error */ + int errno_save = errno; + + __pmNotifyErr(LOG_ERR, "%s: fork() returned %s", __FUNCTION__, + strerror(errno_save)); + close (pipe_fds[PARENT_END]); + close (pipe_fds[CHILD_END]); + + return -errno_save; + } + + return pipe_fds[CHILD_END]; +} + +int +stop_cmd(pid_t pid) +{ + int rc; + pid_t wait_pid; + int wstatus; + + __pmNotifyErr(LOG_INFO, "%s: killing pid %d", __FUNCTION__, (int)pid); + + /* Send the TERM signal. */ + rc = kill(pid, SIGTERM); + __pmNotifyErr(LOG_INFO, "%s: kill returned %d", __FUNCTION__, rc); + + /* Wait for the process to go away. */ + do { + wait_pid = waitpid (pid, &wstatus, 0); + __pmNotifyErr(LOG_INFO, "%s: waitpid returned %d", __FUNCTION__, + (int)wait_pid); + } while (wait_pid == -1 && errno == EINTR); + + /* Return process exit status. */ + return wstatus; +} diff --git a/pcp/src/pmdas/logger/util.h b/pcp/src/pmdas/logger/util.h new file mode 100644 index 000000000..0c49f016d --- /dev/null +++ b/pcp/src/pmdas/logger/util.h @@ -0,0 +1,29 @@ +/* + * Utility routines. + * + * Copyright (c) 2011 Red Hat Inc. + * + * 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 + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _UTIL_H +#define _UTIL_H + +extern char *lstrip(char *str); +extern void rstrip(char *str); +extern int start_cmd(const char *cmd, pid_t *ppid); +extern int stop_cmd(pid_t pid); + +#endif /* _UTIL_H */ -- 2.43.5