ossp-pkg/l2/l2_ch_pipe.c
/*
** OSSP l2 - Flexible Logging
** Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
** Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
**
** This file is part of OSSP l2, a flexible logging library which
** can be found at http://www.ossp.org/pkg/lib/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 <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#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(*)(int))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
};