/* ** L2 - OSSP Logging Library ** Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/) ** Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/) ** ** This file is part of OSSP L2, a flexible logging library which ** can be found at http://www.ossp.org/pkg/l2/. ** ** Permission to use, copy, modify, and distribute this software for ** any purpose with or without fee is hereby granted, provided that ** the above copyright notice and this permission notice appear in all ** copies. ** ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. ** ** l2_ch_pipe.c: pipe channel implementation */ #include "l2.h" #include "l2_p.h" /* for TRACE() */ #include #include #include #include #define L2_PIPE_EXECMODE_DIRECT 1 /* direct command execution */ #define L2_PIPE_EXECMODE_SHELL 2 /* shell command execution */ #define L2_PIPE_RUNTIME_CONTINU 3 /* continuous pipe command processing */ #define L2_PIPE_RUNTIME_ONESHOT 4 /* oneshot pipe command processing */ #define L2_PIPE_WRITEFAIL 6 /* how long before failure occurs */ #define L2_PIPE_MAXARGS 256 /* how many args can piped command have */ /* declare private channel configuration */ typedef struct { pid_t Pid; /* process id of child command */ int iWritefail; /* counter to failed write() operations */ int piFd[2]; /* pipe file descriptor */ int iNulldev; /* null device file descriptor */ int iMode; /* execution mode direct or shell */ int iRtme; /* runtime mode continuous or oneshot */ char *szCmdpath; /* path to command and arguments */ struct sigaction sigchld; /* initial state of chld signal handler */ struct sigaction sigpipe; /* initial state of pipe signal handler */ } l2_ch_pipe_t; static void catchsignal(int sig, ...) { pid_t Pid; /* for wait() */ int iStatus = 0; /* for wait() */ if (sig == SIGCHLD) { TRACE("SIGCHLD caught"); Pid = waitpid((pid_t)-1, &iStatus, WUNTRACED | WNOHANG); if (WIFEXITED(iStatus)) TRACE("EXITED child"); /* child finished and returned */ else if (WIFSIGNALED(iStatus)) TRACE("SIGNALED child"); /* child finished due to a signal */ else if (WIFSTOPPED(iStatus)) TRACE("STOPPED child"); /* child stopped due to a signal */ else TRACE("SIGNAL Unknown"); /* child stopped due to a signal */ } else if (sig == SIGPIPE); /* noop for now */ } /* * The lifecycle of the command executed by the pipe channel handler * depends on the runtime option selected during configuration. The * option 'continuous' describes subsequent behavior which rebuilds an * (un)intentionally severed child-controlled pipe. In this case, when * the pipe handler encounters a failed write operation, it will attempt * the reconnection a user-defined number of times until failure is assumed. * * The option 'oneshot' describes subsequent behavior which also rebuilds * such a severed pipe. The difference is that the pipe handler will not * spawn the child process until writing actually occurs. The channel will * also block until the child process finishes and terminates on its own. * * The lifecycle of a pipe command process is illustrated in the * following figure: * * -----BEGIN EMBEDDED OBJECT----- * Content-editor: xfig %s * Content-encoding: gzip:9 base64 * Content-type: application/fig * Content-viewer: xfig %s * Description: L2 Pipe Channel Command Lifecycle * Filename: l2_ch_pipe.fig * Last-modified: 2000-09-28/14:40 * Name: l2_ch_pipe * Version: eo/1.0 * H4sIAIW5uTsCA61ZUW/cNgx+3v0KAX3YCjSBJEuy/VxsQ4EMG9bubS+Oo+SM+uyb * 7WuWfz+Ssi352iTSrW3jir7jJ0qk+FHMm18+/Mqya7m7qbq7sa6OdvfedpMddr/Z * aWjq3Y2dQGJsJzi/5nz3sekeWru7kjshOWdyx1km2Zua/uwMk0ZqprjmTPAi10yD * vBMsY/ACfjjT+JDwADR6h//DmxweWpWcGYn/vCxzAyPN8xgYmJS7b884wQsYEpJU * MUiZ2Rrk5cwkGKTVFsbLWiXAKL2F8bLSCTC53MJ4OZcJMIXYwni5EAkwJa1MqAXG * yyU3ONJxMOUZzCqXZQKMybYwgZwtMJIgFPzkDuZqVmeLgNBy90MQvz5wQqct2KrU * 4Qg/Be0wSlYrch2OZq/xcISfgnZulm/SaHYMD0f0aaH1ut9eu1Tr22AjF+35PKGZ * VwbOe1nCIRcKDr2QGQwlYO6U32fUWIOYiQy+V9A55TjUoPFXdwcJpuuHQ9Wyuhnq * 02Gcqq6247u/ORcvg0nhwRC3YjeS/dEcLXu/r7rOtuyxmfZssG1T3TZtMz3FYGYL * puSwsOk0dPaO9R1gtS1YOrHOwoupZ1P12bKqew2UA5Is9QIq4Tt1Pwy2npovoA// * ATjAHaqmm+CHNdPI6n3T3sVYm6/ACoZ1fzhALmfHoYctHK/Zz/9Wh2Nr2bjvH7tr * AvSeUxK1pcFMDZH2iufyfNkYpeBD57nxaOvm/7lOGXh8F9fxYtkMhcn+zHWDtWDf * bduMe9riI8wWAZrpBVQj/t1pABIEe+/b6hHQH4dmsqw/2qEiR96dLDgzAlcXywbo * TGDs1m0/AiB5njXd8TSRic/7kEIpk3j6BCUcPH0uSQlWYJISzyUpzFFyyQ4zjopQ * Fk4ZB+Ia0yZ+CiFE1UGIOVuEmQJB8S9UCh5VPmsSpRynTjnJLdVQjqFgx8JCFrDU * 2MKCHkFhMT8MPkRCYYHf3hQW9MIVFjLPVDSVBwZ5mag81iCiJwijDV05GelKZCae * ygNrvExUHmsNkVwA42WqlmJhiO2CRXmZqqXYRREZBtZ4mcqcWGuIFQNrvEz1Saw1 * RJ4hzCpTfUIwUYWFEEthMe+sD5zNaI6CzQg/Be3Qvd6KzYg+XaKbz6fPpSlT4MEE * itegrvRr+d0s6c0g1XzaW3YagTAO1RNk4n9OzQBZs7NXkNSm2FQMs86Y5NNbu6++ * NP1wzT4Age4bYM1qtO9gaCGLEov8OEbASr7CYilTjWMPnDZhLp7pdKX+8Vg9dhGQ * rlgiSExXt21ffx7ZqZuadjYPk/zM0gzuWIemgxmjrDWLteTE+6E/EJ0BN7C2f2jq * bxLGKxynVnOxZnR2IrMh1d3ah6aDUuKT31ZcQ3dGRFg4urQt81wn8pCL6xnm+9CQ * i2SxHJhUGqIj4dRxOC8Ua6wZGCsvWcrNORBmu7eQDhT8SI2PAgi/Hiy4+dwj52p4 * bCHUMyqpSa3v7puH0/CqJsyTgabKzKwJhUkXqURlCClRTROpZeSiRfVL5NIoJ1y0 * tLy8YGlFecnSynyZKnZpGqsUbhZv30HBOfRPwUHBQNAaq2lBd+VcRMePNvkl8ePU * Lokf0kyNH1JK2WRyTbIWucYtLdk1pBa6JjLIk42kIE/WogA/03IsTMaXBZX7eJWH * oojwihBPXOucFwEe0ZvBEdx+Aiue16Kajec4MHzhwG/rbazHykAInA5NY8MJWLk5 * fL3u4mvnoCYur8jATaDYAfG8pocHA3mL9ARsCBB0ylzknvO5Cq8nA72Cu9yvS+oI * wWTLdfV3qGE+Yg1z09zb+qluzz2m+OIwzAmvTJJht4AmAXJf5/iTbsFA6Osc7CfX * Mnkb4c41CPDMvBADz/iSMtklvpS5usiX0ohkX4qsfN6XL0Q4VrqbCPd+y0S5OA5P * Y6z1dGkJrY9wTIbrSD6dWZFfdjopuVziUSWKlBOTcTUHM11WXormweIV4G2KMUSj * oTGeYSmLOIYtNI9mWMpa6Qzr1C5hWNJMZVhSSq7QnJGpFdpFSyMCS10alQHJS6My * IGlpvgwgta8rNKz9DV+vDhhKiXeZQhZ8wcm+02WGMGeLYJh6mTFlNt/vcbS01GBv * 3J2eF1hOZHl8S43sCVpqbs3YUitkmdBSw29vWmr0wrXUikzE/64uMMjL1H2KNYia * JwGMl6kzFwtD+T+A8TI1sWJhqMERwHiZmlixMNS0CmC8TE2saJjyDGaVqYkVC0MN * iwDGy9RujIWhlhaFdND8dDI2P02pYltq6+/q5nDxHvdO24zcLPl8pECbxvOG8nDk * jpzS4cid4jnOCzqH/wEp6AKCgx8AAA== * -----END EMBEDDED OBJECT----- */ /* create channel */ static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch) { l2_ch_pipe_t *cfg; /* allocate private channel configuration */ if ((cfg = (l2_ch_pipe_t *)malloc(sizeof(l2_ch_pipe_t))) == NULL) return L2_ERR_ARG; /* initialize configuration with reasonable defaults */ cfg->Pid = -1; cfg->iWritefail = 0; cfg->piFd[0] = -1; cfg->piFd[1] = -1; cfg->iNulldev = -1; cfg->iMode = -1; cfg->iRtme = -1; cfg->szCmdpath = NULL; memset(&cfg->sigchld, 0, sizeof(cfg->sigchld)); memset(&cfg->sigpipe, 0, sizeof(cfg->sigpipe)); /* link private channel configuration into channel context */ ctx->vp = cfg; return L2_OK; } /* configure channel */ static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list ap) { l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp; l2_param_t pa[4]; l2_result_t rv; char *szMode = NULL; char *szRel = NULL; l2_env_t *env; /* feed and call generic parameter parsing engine */ L2_PARAM_SET(pa[0], execmode, STR, &szMode); /* mode direct or shell */ L2_PARAM_SET(pa[1], runtime, STR, &szRel); /* continuous or oneshot */ L2_PARAM_SET(pa[2], path, STR, &cfg->szCmdpath); /* path of cmd */ L2_PARAM_END(pa[3]); l2_channel_env(ch, &env); if ((rv = l2_util_setparams(env, pa, fmt, ap)) != L2_OK) return rv; if (szMode != NULL) { if (strcmp(szMode, "direct") == 0) cfg->iMode = L2_PIPE_EXECMODE_DIRECT; else if (strcmp(szMode, "shell") == 0) cfg->iMode = L2_PIPE_EXECMODE_SHELL; else return L2_ERR_ARG; free(szMode); } if (szRel != NULL) { if (strncmp(szRel, "continuous", strlen("cont")) == 0) cfg->iRtme = L2_PIPE_RUNTIME_CONTINU; else if (strncmp(szMode, "oneshot", strlen("one")) == 0) cfg->iRtme = L2_PIPE_RUNTIME_ONESHOT; else return L2_ERR_ARG; free(szRel); } return L2_OK; } /********************************************************** * parse_cmdpath: Helper method to spawn_command * * Parses szBuf into an argv-style string vector szArgs * **********************************************************/ static l2_result_t parse_cmdpath(char *szBuf, char *szArgs[]) { int iCnt = 0; if (szBuf == NULL) /* check for bad input before we */ return L2_ERR_ARG; /* dereference and throw a SIGSEV */ while ((iCnt++ < L2_PIPE_MAXARGS) && (*szBuf != '\0')) { while ((*szBuf == ' ') || (*szBuf == '\t')) *szBuf++ = '\0'; /* overwrite whitespace with EOL */ *szArgs++ = szBuf; /* found the start of a new token */ while ((*szBuf != '\0') && (*szBuf != ' ') && (*szBuf != '\t')) szBuf++; } *szArgs = '\0'; /* add a NULL to mark the end of the chain */ if (iCnt <= L2_PIPE_MAXARGS) return L2_OK; else return L2_ERR_ARG; } /************************************************************ * spawn_command: Helper method to hook_open and hook_write * * Forks a new process, and copies the command executable * ************************************************************/ static l2_result_t spawn_command(l2_ch_pipe_t *cfg) { char *pVec[L2_PIPE_MAXARGS]; char *sz = NULL; l2_result_t rv; /* initialize auto vars before using them */ memset(pVec, 0, sizeof(pVec)); /* spawn a child process to be later overwritten by the user command */ if ((cfg->Pid = fork()) > 0) { /* parent process */ free(sz); /* no exec() in parent */ close(cfg->piFd[0]); /* half-duplex (no reading) */ cfg->piFd[0] = -1; return L2_OK; } else if (cfg->Pid == 0) { /* child process */ close(cfg->piFd[1]); /* close the writing end, */ cfg->piFd[1] = -1; /* because we don't use it */ dup2(cfg->piFd[0], fileno(stdin)); /* copy the reading end */ /* redirection of child's stdout and stdin */ cfg->iNulldev = open("/dev/null", O_RDWR); dup2(cfg->iNulldev, fileno(stdout)); /* redirect stdout to null */ dup2(cfg->iNulldev, fileno(stderr)); /* redirect stderr to null */ /* the distinction between modes is necessary, because only executing */ /* commands in a shell environment allows usage of variables and such */ if (cfg->iMode == L2_PIPE_EXECMODE_SHELL) { pVec[0] = "/bin/sh"; pVec[1] = "-c"; pVec[2] = cfg->szCmdpath; pVec[3] = NULL; /* add a NULL to mark the end of the chain */ } else { /* plain direct command execution */ sz = strdup(cfg->szCmdpath); if ((rv = parse_cmdpath(sz, pVec)) != L2_OK) { free(sz); return rv; } } if (execvp(*pVec, pVec) == -1) { /* launch */ TRACE("execvp in child returned -1"); free(sz); /* cleanup in case we fail */ close(cfg->piFd[0]); cfg->piFd[0] = -1; /* if execvp() doesn't swap our context or */ return L2_ERR_SYS; /* if child returns, we have an error */ } else return L2_OK; /* NOTREACHED */ } else /* fork failed */ return L2_ERR_SYS; } /* open channel */ static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch) { l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp; struct sigaction locact; /* consistency check */ if (cfg->szCmdpath == NULL) return L2_ERR_USE; /* initialize auto vars before using them */ memset(&locact, 0, sizeof(locact)); locact.sa_handler = (void(*)())catchsignal; sigemptyset(&locact.sa_mask); locact.sa_flags = 0; /* save old signal context before replacing with our own */ if (sigaction(SIGCHLD, &locact, &cfg->sigchld) < 0) return L2_ERR_SYS; if (sigaction(SIGPIPE, &locact, &cfg->sigpipe) < 0) return L2_ERR_SYS; if (pipe(cfg->piFd) == -1) /* open the pipe */ return L2_ERR_SYS; /* short circuit hack, if in oneshot mode and not yet opened then return */ if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (ch->state != L2_CHSTATE_OPENED)) return L2_OK; else return spawn_command(cfg); /* spawn the command process */ } /* write to channel, possibly recursively */ static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch, l2_level_t level, const char *buf, size_t buf_size) { l2_result_t rv = L2_OK; l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp; /* spawn the child command process if we are in oneshot mode */ if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid == -1)) if (spawn_command(cfg) != L2_OK) return L2_ERR_SYS; /* immediate return if we can't spawn command */ /* write message to channel pipe */ if (write(cfg->piFd[1], buf, buf_size) == -1) { if ((errno == EPIPE) && (cfg->iWritefail++ < L2_PIPE_WRITEFAIL)) { if ((rv = l2_channel_close(ch)) != L2_OK) return rv; if ((rv = l2_channel_open(ch)) != L2_OK) return rv; return hook_write(ctx, ch, level, buf, buf_size); } else { /* not broken pipe problem or over the fail limit, so panic */ cfg->iWritefail = 0; /* reset pipe failure counter */ rv = L2_ERR_SYS; } } else /* write() to pipe succeeded */ cfg->iWritefail = 0; /* reset pipe failure counter */ /* block until child terminates if in oneshot execmode */ if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid != -1)) cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG); return rv; } /* close channel */ static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch) { l2_result_t rv = L2_OK; l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp; /* close null device */ if (cfg->iNulldev != -1) { close(cfg->iNulldev); cfg->iNulldev = -1; } /* close output pipe for parent */ if (cfg->piFd[1] != -1) { close(cfg->piFd[1]); cfg->piFd[1] = -1; } /* restore previous signal context, but only if it was saved and replaced */ if (&cfg->sigchld.sa_handler) { if (sigaction(SIGCHLD, &cfg->sigchld, 0) < 0) rv = L2_ERR_SYS; if (sigaction(SIGPIPE, &cfg->sigpipe, 0) < 0) rv = L2_ERR_SYS; } /* kill child process if still running */ if (cfg->Pid != -1) { kill(cfg->Pid, SIGTERM); cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG); cfg->Pid = -1; } return rv; } /* destroy channel */ static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch) { l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp; /* destroy channel configuration */ free(cfg->szCmdpath); cfg->szCmdpath = NULL; free(cfg); return L2_OK; } /* exported channel handler structure */ l2_handler_t l2_handler_pipe = { "pipe", L2_CHANNEL_OUTPUT, hook_create, hook_configure, hook_open, hook_write, NULL, hook_close, hook_destroy };