1 /*
2 ** OSSP l2 - Flexible Logging
3 ** Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
4 ** Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
5 ** Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
6 **
7 ** This file is part of OSSP l2, a flexible logging library which
8 ** can be found at http://www.ossp.org/pkg/lib/l2/.
9 **
10 ** Permission to use, copy, modify, and distribute this software for
11 ** any purpose with or without fee is hereby granted, provided that
12 ** the above copyright notice and this permission notice appear in all
13 ** copies.
14 **
15 ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
16 ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
19 ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
22 ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 ** SUCH DAMAGE.
27 **
28 ** l2_ch_pipe.c: pipe channel implementation
29 */
30
31 #include "l2.h"
32 #include "l2_p.h" /* for TRACE() */
33
34 #include <unistd.h>
35 #include <signal.h>
36 #include <sys/wait.h>
37 #include <fcntl.h>
38
39 #define L2_PIPE_EXECMODE_DIRECT 1 /* direct command execution */
40 #define L2_PIPE_EXECMODE_SHELL 2 /* shell command execution */
41 #define L2_PIPE_RUNTIME_CONTINU 3 /* continuous pipe command processing */
42 #define L2_PIPE_RUNTIME_ONESHOT 4 /* oneshot pipe command processing */
43 #define L2_PIPE_WRITEFAIL 6 /* how long before failure occurs */
44 #define L2_PIPE_MAXARGS 256 /* how many args can piped command have */
45
46 /* declare private channel configuration */
47 typedef struct {
48 pid_t Pid; /* process id of child command */
49 int iWritefail; /* counter to failed write() operations */
50 int piFd[2]; /* pipe file descriptor */
51 int iNulldev; /* null device file descriptor */
52 int iMode; /* execution mode direct or shell */
53 int iRtme; /* runtime mode continuous or oneshot */
54 char *szCmdpath; /* path to command and arguments */
55 struct sigaction sigchld; /* initial state of chld signal handler */
56 struct sigaction sigpipe; /* initial state of pipe signal handler */
57 } l2_ch_pipe_t;
58
catchsignal(int sig,...)59 static void catchsignal(int sig, ...)
60 {
61 pid_t Pid; /* for wait() */
62 int iStatus = 0; /* for wait() */
63
64 if (sig == SIGCHLD) {
65 TRACE("SIGCHLD caught");
66 Pid = waitpid((pid_t)-1, &iStatus, WUNTRACED | WNOHANG);
67 if (WIFEXITED(iStatus))
68 TRACE("EXITED child"); /* child finished and returned */
69 else if (WIFSIGNALED(iStatus))
70 TRACE("SIGNALED child"); /* child finished due to a signal */
71 else if (WIFSTOPPED(iStatus))
72 TRACE("STOPPED child"); /* child stopped due to a signal */
73 else
74 TRACE("SIGNAL Unknown"); /* child stopped due to a signal */
75 }
76 else if (sig == SIGPIPE); /* noop for now */
77 }
78
79 /*
80 * The lifecycle of the command executed by the pipe channel handler
81 * depends on the runtime option selected during configuration. The
82 * option 'continuous' describes subsequent behavior which rebuilds an
83 * (un)intentionally severed child-controlled pipe. In this case, when
84 * the pipe handler encounters a failed write operation, it will attempt
85 * the reconnection a user-defined number of times until failure is assumed.
86 *
87 * The option 'oneshot' describes subsequent behavior which also rebuilds
88 * such a severed pipe. The difference is that the pipe handler will not
89 * spawn the child process until writing actually occurs. The channel will
90 * also block until the child process finishes and terminates on its own.
91 *
92 * The lifecycle of a pipe command process is illustrated in the
93 * following figure:
94 *
95 * -----BEGIN EMBEDDED OBJECT-----
96 * Content-editor: xfig %s
97 * Content-encoding: gzip:9 base64
98 * Content-type: application/fig
99 * Content-viewer: xfig %s
100 * Description: L2 Pipe Channel Command Lifecycle
101 * Filename: l2_ch_pipe.fig
102 * Last-modified: 2000-09-28/14:40
103 * Name: l2_ch_pipe
104 * Version: eo/1.0
105 * H4sIAIW5uTsCA61ZUW/cNgx+3v0KAX3YCjSBJEuy/VxsQ4EMG9bubS+Oo+SM+uyb
106 * 7WuWfz+Ssi352iTSrW3jir7jJ0qk+FHMm18+/Mqya7m7qbq7sa6OdvfedpMddr/Z
107 * aWjq3Y2dQGJsJzi/5nz3sekeWru7kjshOWdyx1km2Zua/uwMk0ZqprjmTPAi10yD
108 * vBMsY/ACfjjT+JDwADR6h//DmxweWpWcGYn/vCxzAyPN8xgYmJS7b884wQsYEpJU
109 * MUiZ2Rrk5cwkGKTVFsbLWiXAKL2F8bLSCTC53MJ4OZcJMIXYwni5EAkwJa1MqAXG
110 * yyU3ONJxMOUZzCqXZQKMybYwgZwtMJIgFPzkDuZqVmeLgNBy90MQvz5wQqct2KrU
111 * 4Qg/Be0wSlYrch2OZq/xcISfgnZulm/SaHYMD0f0aaH1ut9eu1Tr22AjF+35PKGZ
112 * VwbOe1nCIRcKDr2QGQwlYO6U32fUWIOYiQy+V9A55TjUoPFXdwcJpuuHQ9Wyuhnq
113 * 02Gcqq6247u/ORcvg0nhwRC3YjeS/dEcLXu/r7rOtuyxmfZssG1T3TZtMz3FYGYL
114 * puSwsOk0dPaO9R1gtS1YOrHOwoupZ1P12bKqew2UA5Is9QIq4Tt1Pwy2npovoA//
115 * ATjAHaqmm+CHNdPI6n3T3sVYm6/ACoZ1fzhALmfHoYctHK/Zz/9Wh2Nr2bjvH7tr
116 * AvSeUxK1pcFMDZH2iufyfNkYpeBD57nxaOvm/7lOGXh8F9fxYtkMhcn+zHWDtWDf
117 * bduMe9riI8wWAZrpBVQj/t1pABIEe+/b6hHQH4dmsqw/2qEiR96dLDgzAlcXywbo
118 * TGDs1m0/AiB5njXd8TSRic/7kEIpk3j6BCUcPH0uSQlWYJISzyUpzFFyyQ4zjopQ
119 * Fk4ZB+Ia0yZ+CiFE1UGIOVuEmQJB8S9UCh5VPmsSpRynTjnJLdVQjqFgx8JCFrDU
120 * 2MKCHkFhMT8MPkRCYYHf3hQW9MIVFjLPVDSVBwZ5mag81iCiJwijDV05GelKZCae
121 * ygNrvExUHmsNkVwA42WqlmJhiO2CRXmZqqXYRREZBtZ4mcqcWGuIFQNrvEz1Saw1
122 * RJ4hzCpTfUIwUYWFEEthMe+sD5zNaI6CzQg/Be3Qvd6KzYg+XaKbz6fPpSlT4MEE
123 * itegrvRr+d0s6c0g1XzaW3YagTAO1RNk4n9OzQBZs7NXkNSm2FQMs86Y5NNbu6++
124 * NP1wzT4Age4bYM1qtO9gaCGLEov8OEbASr7CYilTjWMPnDZhLp7pdKX+8Vg9dhGQ
125 * rlgiSExXt21ffx7ZqZuadjYPk/zM0gzuWIemgxmjrDWLteTE+6E/EJ0BN7C2f2jq
126 * bxLGKxynVnOxZnR2IrMh1d3ah6aDUuKT31ZcQ3dGRFg4urQt81wn8pCL6xnm+9CQ
127 * i2SxHJhUGqIj4dRxOC8Ua6wZGCsvWcrNORBmu7eQDhT8SI2PAgi/Hiy4+dwj52p4
128 * bCHUMyqpSa3v7puH0/CqJsyTgabKzKwJhUkXqURlCClRTROpZeSiRfVL5NIoJ1y0
129 * tLy8YGlFecnSynyZKnZpGqsUbhZv30HBOfRPwUHBQNAaq2lBd+VcRMePNvkl8ePU
130 * Lokf0kyNH1JK2WRyTbIWucYtLdk1pBa6JjLIk42kIE/WogA/03IsTMaXBZX7eJWH
131 * oojwihBPXOucFwEe0ZvBEdx+Aiue16Kajec4MHzhwG/rbazHykAInA5NY8MJWLk5
132 * fL3u4mvnoCYur8jATaDYAfG8pocHA3mL9ARsCBB0ylzknvO5Cq8nA72Cu9yvS+oI
133 * wWTLdfV3qGE+Yg1z09zb+qluzz2m+OIwzAmvTJJht4AmAXJf5/iTbsFA6Osc7CfX
134 * Mnkb4c41CPDMvBADz/iSMtklvpS5usiX0ohkX4qsfN6XL0Q4VrqbCPd+y0S5OA5P
135 * Y6z1dGkJrY9wTIbrSD6dWZFfdjopuVziUSWKlBOTcTUHM11WXormweIV4G2KMUSj
136 * oTGeYSmLOIYtNI9mWMpa6Qzr1C5hWNJMZVhSSq7QnJGpFdpFSyMCS10alQHJS6My
137 * IGlpvgwgta8rNKz9DV+vDhhKiXeZQhZ8wcm+02WGMGeLYJh6mTFlNt/vcbS01GBv
138 * 3J2eF1hOZHl8S43sCVpqbs3YUitkmdBSw29vWmr0wrXUikzE/64uMMjL1H2KNYia
139 * JwGMl6kzFwtD+T+A8TI1sWJhqMERwHiZmlixMNS0CmC8TE2saJjyDGaVqYkVC0MN
140 * iwDGy9RujIWhlhaFdND8dDI2P02pYltq6+/q5nDxHvdO24zcLPl8pECbxvOG8nDk
141 * jpzS4cid4jnOCzqH/wEp6AKCgx8AAA==
142 * -----END EMBEDDED OBJECT-----
143 */
144
145 /* create channel */
hook_create(l2_context_t * ctx,l2_channel_t * ch)146 static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch)
147 {
148 l2_ch_pipe_t *cfg;
149
150 /* allocate private channel configuration */
151 if ((cfg = (l2_ch_pipe_t *)malloc(sizeof(l2_ch_pipe_t))) == NULL)
152 return L2_ERR_ARG;
153
154 /* initialize configuration with reasonable defaults */
155 cfg->Pid = -1;
156 cfg->iWritefail = 0;
157 cfg->piFd[0] = -1;
158 cfg->piFd[1] = -1;
159 cfg->iNulldev = -1;
160 cfg->iMode = -1;
161 cfg->iRtme = -1;
162 cfg->szCmdpath = NULL;
163 memset(&cfg->sigchld, 0, sizeof(cfg->sigchld));
164 memset(&cfg->sigpipe, 0, sizeof(cfg->sigpipe));
165
166 /* link private channel configuration into channel context */
167 ctx->vp = cfg;
168
169 return L2_OK;
170 }
171
172 /* configure channel */
hook_configure(l2_context_t * ctx,l2_channel_t * ch,const char * fmt,va_list * ap)173 static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list *ap)
174 {
175 l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
176 l2_param_t pa[4];
177 l2_result_t rv;
178 char *szMode = NULL;
179 char *szRel = NULL;
180 l2_env_t *env;
181
182 /* feed and call generic parameter parsing engine */
183 L2_PARAM_SET(pa[0], execmode, STR, &szMode); /* mode direct or shell */
184 L2_PARAM_SET(pa[1], runtime, STR, &szRel); /* continuous or oneshot */
185 L2_PARAM_SET(pa[2], path, STR, &cfg->szCmdpath); /* path of cmd */
186 L2_PARAM_END(pa[3]);
187 l2_channel_env(ch, &env);
188 if ((rv = l2_util_setparams(env, pa, fmt, ap)) != L2_OK)
189 return rv;
190
191 if (szMode != NULL) {
192 if (strcmp(szMode, "direct") == 0)
193 cfg->iMode = L2_PIPE_EXECMODE_DIRECT;
194 else if (strcmp(szMode, "shell") == 0)
195 cfg->iMode = L2_PIPE_EXECMODE_SHELL;
196 else
197 return L2_ERR_ARG;
198 free(szMode);
199 }
200
201 if (szRel != NULL) {
202 if (strncmp(szRel, "continuous", strlen("cont")) == 0)
203 cfg->iRtme = L2_PIPE_RUNTIME_CONTINU;
204 else if (strncmp(szMode, "oneshot", strlen("one")) == 0)
205 cfg->iRtme = L2_PIPE_RUNTIME_ONESHOT;
206 else
207 return L2_ERR_ARG;
208 free(szRel);
209 }
210
211 return L2_OK;
212 }
213
214 /**********************************************************
215 * parse_cmdpath: Helper method to spawn_command *
216 * Parses szBuf into an argv-style string vector szArgs *
217 **********************************************************/
parse_cmdpath(char * szBuf,char * szArgs[])218 static l2_result_t parse_cmdpath(char *szBuf, char *szArgs[]) {
219 int iCnt = 0;
220
221 if (szBuf == NULL) /* check for bad input before we */
222 return L2_ERR_ARG; /* dereference and throw a SIGSEV */
223
224 while ((iCnt++ < L2_PIPE_MAXARGS) && (*szBuf != '\0')) {
225 while ((*szBuf == ' ') || (*szBuf == '\t'))
226 *szBuf++ = '\0'; /* overwrite whitespace with EOL */
227 *szArgs++ = szBuf; /* found the start of a new token */
228 while ((*szBuf != '\0') && (*szBuf != ' ') && (*szBuf != '\t'))
229 szBuf++;
230 }
231 *szArgs = '\0'; /* add a NULL to mark the end of the chain */
232
233 if (iCnt <= L2_PIPE_MAXARGS)
234 return L2_OK;
235 else
236 return L2_ERR_ARG;
237 }
238
239 /************************************************************
240 * spawn_command: Helper method to hook_open and hook_write *
241 * Forks a new process, and copies the command executable *
242 ************************************************************/
spawn_command(l2_ch_pipe_t * cfg)243 static l2_result_t spawn_command(l2_ch_pipe_t *cfg)
244 {
245 char *pVec[L2_PIPE_MAXARGS];
246 char *sz = NULL;
247 l2_result_t rv;
248
249 /* initialize auto vars before using them */
250 memset(pVec, 0, sizeof(pVec));
251
252 /* spawn a child process to be later overwritten by the user command */
253 if ((cfg->Pid = fork()) > 0) { /* parent process */
254 free(sz); /* no exec() in parent */
255 close(cfg->piFd[0]); /* half-duplex (no reading) */
256 cfg->piFd[0] = -1;
257 return L2_OK;
258 }
259 else if (cfg->Pid == 0) { /* child process */
260 close(cfg->piFd[1]); /* close the writing end, */
261 cfg->piFd[1] = -1; /* because we don't use it */
262 dup2(cfg->piFd[0], fileno(stdin)); /* copy the reading end */
263
264 /* redirection of child's stdout and stdin */
265 cfg->iNulldev = open("/dev/null", O_RDWR);
266 dup2(cfg->iNulldev, fileno(stdout)); /* redirect stdout to null */
267 dup2(cfg->iNulldev, fileno(stderr)); /* redirect stderr to null */
268
269 /* the distinction between modes is necessary, because only executing */
270 /* commands in a shell environment allows usage of variables and such */
271 if (cfg->iMode == L2_PIPE_EXECMODE_SHELL) {
272 pVec[0] = "/bin/sh";
273 pVec[1] = "-c";
274 pVec[2] = cfg->szCmdpath;
275 pVec[3] = NULL; /* add a NULL to mark the end of the chain */
276 }
277 else { /* plain direct command execution */
278 sz = strdup(cfg->szCmdpath);
279 if ((rv = parse_cmdpath(sz, pVec)) != L2_OK) {
280 free(sz);
281 return rv;
282 }
283 }
284
285 if (execvp(*pVec, pVec) == -1) { /* launch */
286 TRACE("execvp in child returned -1");
287 free(sz); /* cleanup in case we fail */
288 close(cfg->piFd[0]);
289 cfg->piFd[0] = -1; /* if execvp() doesn't swap our context or */
290 return L2_ERR_SYS; /* if child returns, we have an error */
291 }
292 else
293 return L2_OK; /* NOTREACHED */
294 }
295 else /* fork failed */
296 return L2_ERR_SYS;
297 }
298
299 /* open channel */
hook_open(l2_context_t * ctx,l2_channel_t * ch)300 static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch)
301 {
302 l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
303 struct sigaction locact;
304
305 /* consistency check */
306 if (cfg->szCmdpath == NULL)
307 return L2_ERR_USE;
308
309 /* initialize auto vars before using them */
310 memset(&locact, 0, sizeof(locact));
311
312 locact.sa_handler = (void(*)(int))catchsignal;
313 sigemptyset(&locact.sa_mask);
314 locact.sa_flags = 0;
315
316 /* save old signal context before replacing with our own */
317 if (sigaction(SIGCHLD, &locact, &cfg->sigchld) < 0)
318 return L2_ERR_SYS;
319 if (sigaction(SIGPIPE, &locact, &cfg->sigpipe) < 0)
320 return L2_ERR_SYS;
321
322 if (pipe(cfg->piFd) == -1) /* open the pipe */
323 return L2_ERR_SYS;
324
325 /* short circuit hack, if in oneshot mode and not yet opened then return */
326 if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (ch->state != L2_CHSTATE_OPENED))
327 return L2_OK;
328 else
329 return spawn_command(cfg); /* spawn the command process */
330 }
331
332 /* write to channel, possibly recursively */
hook_write(l2_context_t * ctx,l2_channel_t * ch,l2_level_t level,const char * buf,size_t buf_size)333 static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch,
334 l2_level_t level, const char *buf, size_t buf_size)
335 {
336 l2_result_t rv = L2_OK;
337 l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
338
339 /* spawn the child command process if we are in oneshot mode */
340 if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid == -1))
341 if (spawn_command(cfg) != L2_OK)
342 return L2_ERR_SYS; /* immediate return if we can't spawn command */
343
344 /* write message to channel pipe */
345 if (write(cfg->piFd[1], buf, buf_size) == -1) {
346 if ((errno == EPIPE) && (cfg->iWritefail++ < L2_PIPE_WRITEFAIL)) {
347 if ((rv = l2_channel_close(ch)) != L2_OK)
348 return rv;
349 if ((rv = l2_channel_open(ch)) != L2_OK)
350 return rv;
351 return hook_write(ctx, ch, level, buf, buf_size);
352 }
353 else { /* not broken pipe problem or over the fail limit, so panic */
354 cfg->iWritefail = 0; /* reset pipe failure counter */
355 rv = L2_ERR_SYS;
356 }
357 }
358 else /* write() to pipe succeeded */
359 cfg->iWritefail = 0; /* reset pipe failure counter */
360
361 /* block until child terminates if in oneshot execmode */
362 if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid != -1))
363 cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG);
364
365 return rv;
366 }
367
368 /* close channel */
hook_close(l2_context_t * ctx,l2_channel_t * ch)369 static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch)
370 {
371 l2_result_t rv = L2_OK;
372 l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
373
374 /* close null device */
375 if (cfg->iNulldev != -1) {
376 close(cfg->iNulldev);
377 cfg->iNulldev = -1;
378 }
379
380 /* close output pipe for parent */
381 if (cfg->piFd[1] != -1) {
382 close(cfg->piFd[1]);
383 cfg->piFd[1] = -1;
384 }
385
386 /* restore previous signal context, but only if it was saved and replaced */
387 if (&cfg->sigchld.sa_handler) {
388 if (sigaction(SIGCHLD, &cfg->sigchld, 0) < 0)
389 rv = L2_ERR_SYS;
390 if (sigaction(SIGPIPE, &cfg->sigpipe, 0) < 0)
391 rv = L2_ERR_SYS;
392 }
393
394 /* kill child process if still running */
395 if (cfg->Pid != -1) {
396 kill(cfg->Pid, SIGTERM);
397 cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG);
398 cfg->Pid = -1;
399 }
400
401 return rv;
402 }
403
404 /* destroy channel */
hook_destroy(l2_context_t * ctx,l2_channel_t * ch)405 static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch)
406 {
407 l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
408
409 /* destroy channel configuration */
410 free(cfg->szCmdpath);
411 cfg->szCmdpath = NULL;
412 free(cfg);
413
414 return L2_OK;
415 }
416
417 /* exported channel handler structure */
418 l2_handler_t l2_handler_pipe = {
419 "pipe",
420 L2_CHANNEL_OUTPUT,
421 hook_create,
422 hook_configure,
423 hook_open,
424 hook_write,
425 NULL,
426 hook_close,
427 hook_destroy
428 };
429
430