/*
* SUBPROCS - Support for managing subprocesses and communicating with them
*
* Author:
* Emile van Bergen, emile@evbergen.xs4all.nl
*
* Permission to redistribute an original or modified version of this program
* in source, intermediate or object code form is hereby granted exclusively
* under the terms of the GNU General Public License, version 2. Please see the
* file COPYING for details, or refer to http://www.gnu.org/copyleft/gpl.html.
*
* History:
* 2001/07/18 - EvB - Created all over based on test/newproctest.c
* 2002/03/19 - EvB - Added support for custom working directory
* 2002/03/20 - EvB - Removed bug that caused watchdog timeouts on subprocesses
* in state XFERING not to be handled gracefully
*/
char subprocs_id[] = "SUBPROCS - Copyright (C) 2001 Emile van Bergen.";
/*
* INCLUDES & DEFINES
*/
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <metaops.h> /* for AVMSG_ASCII */
#include <subprocs.h>
#define DEBUGLEVEL 3
#include <debug.h>
/*
* FUNCTIONS
*/
PROC *proc_new(char *cmdline, int cmdlinelen, int flags, int xfertimeout,
struct chan *chan, char *basepath, char *progcwd)
{
PROC *ret;
char *c;
int len, argc;
/*
* Check args, allocate object and init it enough for cleanup with
* proc_del.
*/
if (!cmdline || !cmdline[0]) return 0;
if (!cmdlinelen) cmdlinelen = strlen(cmdline);
ret = (PROC *)malloc(sizeof(PROC)); if (!ret) return 0;
memset(ret, 0, sizeof(PROC)); ret->rfd = ret->wfd = -1;
/*
* Duplicate the command line into the cmd member, adding the
* specified base path if it doesn't start with a '/'.
*/
if (cmdline[0] != '/' && basepath && basepath[0]) {
len = strlen(basepath);
ret->cmd = (char *)malloc(len + 1 + cmdlinelen + 1);
if (!ret->cmd) { proc_del(ret); return 0; }
memcpy(ret->cmd, basepath, len); ret->cmd[len++] = '/';
}
else {
len = 0;
ret->cmd = (char *)malloc(cmdlinelen + 1);
if (!ret->cmd) { proc_del(ret); return 0; }
}
memcpy(ret->cmd + len, cmdline, cmdlinelen);
ret->cmd[cmdlinelen + len] = 0;
/*
* Split the copied command line in parts
*/
/* Count number of words in command line */
for(argc = 0, c = ret->cmd; *c; argc++) {
c += strspn(c, " \t\n\r");
c += strcspn(c, " \t\n\r");
}
/* Allocate argv array with words + 1 elements */
ret->argv = (char **)malloc((argc + 1) * sizeof(char *));
if (!ret->argv) { proc_del(ret); return 0; }
/* Split command line into argument array and zero-terminate it */
for(argc = 0, c = ret->cmd; ; ) {
c += strspn(c, " \t\n\r"); ret->argv[argc++] = c;
c += strcspn(c, " \t\n\r"); if (!*c) break; *c++ = 0;
}
ret->argv[argc] = 0;
/* Test if argv[0] is at least executable, to prevent simple errors */
if (access(ret->argv[0], X_OK) == -1) {
msg(F_PROC, L_ERR, "proc_new: ERROR: Program '%s' is not "
"executable: %s!\n",
ret->argv[0], strerror(errno));
proc_del(ret); return 0;
}
/*
* Create environment containing interface version and flags. In
* the future we could perhaps use a META_AV list instead.
*/
/* Allocate array with PR_ENV_VARS elements */
ret->envp = (char **)malloc((PR_ENV_VARS + 1) * sizeof(char *));
if (!ret->envp) { proc_del(ret); return 0; }
memset(ret->envp, 0, PR_ENV_VARS * sizeof(char *));
/* Set first element; the binary interface has been bumped to major
* version 2 because of the different request magic. */
c = (char *)malloc((len = strlen(PR_ENV_IFACEVER)) + 1 + 1);
if (!c) { proc_del(ret); return 0; }
memcpy(c, PR_ENV_IFACEVER, len);
c[len] = flags & AVMSG_ASCII ? '1' : '2';
c[len + 1] = 0; ret->envp[0] = c;
/* Set second element */
c = (char *)malloc((len = strlen(PR_ENV_IFACEFLAGS)) + 8 + 1);
if (!c) { proc_del(ret); return 0; }
memcpy(c, PR_ENV_IFACEFLAGS, len);
meta_ordtoa(c + len, 8, 8, 16, flags);
c[len + 8] = 0; ret->envp[1] = c;
/* Set third element */
ret->envp[2] = 0;
/*
* Initialise the other members
*/
if (progcwd) { c = (char *)malloc(len = (strlen(progcwd) + 1));
if (!c) { proc_del(ret); return 0; }
memcpy(c, progcwd, len); ret->cwd = c; }
ret->chan = chan;
ret->r = ring_new(PR_RING_SIZE); if (!ret->r) {proc_del(ret); return 0;}
ret->w = ring_new(PR_RING_SIZE); if (!ret->w) {proc_del(ret); return 0;}
ret->rfd = ret->wfd = -1;
ret->flags = flags;
ret->xfertimeout = xfertimeout;
ret->expectedlen = -1;
ret->timer = 0;
ret->state = PRS_WAITING;
return ret;
}
void proc_del(PROC *p)
{
char **s;
if (p) {
/* Kill subprocess if it wasn't dead yet */
if (p->pid) kill(p->pid, SIGKILL);
/* Close FDs if they aren't yet */
if (p->rfd != -1) close(p->rfd);
if (p->wfd != -1) close(p->wfd);
/* Free rings */
if (p->r) ring_del(p->r);
if (p->w) ring_del(p->w);
/* Free environment */
if (p->envp) {
for(s = p->envp; *s; s++) free(*s);
free(p->envp);
}
/* Free arguments */
if (p->argv) free(p->argv);
if (p->cmd) free(p->cmd);
/* Free working directory */
if (p->cwd) free(p->cwd);
/* Free object itself */
free(p);
}
}
/* Start the process. If it doesn't work out, we go to the STARTING state
which will cause handle_timeout to try again each PRT_RETRYSTART seconds. */
int proc_start(PROC *p, time_t t)
{
int p2c[2] = {-1, -1}, c2p[2] = {-1, -1};
/* If we were already running, exit now. */
if (p->pid) {
msg(F_PROC, L_NOTICE, "proc_run: WARNING: Already running, as "
"pid %d!\n", p->pid);
return p->pid;
}
/* Create pipes; set close on exec all ends and non-blocking on ours */
if (pipe(p2c) == -1 ||
fcntl(p2c[PIPE_R], F_SETFD, 1) == -1 ||
fcntl(p2c[PIPE_W], F_SETFD, 1) == -1 ||
fcntl(p2c[PIPE_W], F_SETFL, O_NONBLOCK) == -1 ||
pipe(c2p) == -1 ||
fcntl(c2p[PIPE_R], F_SETFD, 1) == -1 ||
fcntl(c2p[PIPE_W], F_SETFD, 1) == -1 ||
fcntl(c2p[PIPE_R], F_SETFL, O_NONBLOCK) == -1) {
msg(F_PROC, L_ERR, "proc_run: ERROR: Could not create pipes: "
"%s!\n", strerror(errno));
goto pr_err;
}
/* Save our ends of the pipes for use by ring_read and ring_write */
p->rfd = c2p[PIPE_R]; p->wfd = p2c[PIPE_W];
/* Fork and run */
p->pid = fork();
switch(p->pid) {
case -1:
msg(F_PROC, L_ERR, "proc_run: ERROR: Could not fork: %s!\n",
strerror(errno));
goto pr_err;
case 0:
/* Set standard input and standard output to the pipes */
if (dup2(p2c[PIPE_R], 0) == -1 ||
dup2(c2p[PIPE_W], 1) == -1) {
msg(F_PROC, L_ERR, "proc_run: ERROR: Could not dup2: "
"%s!\n", strerror(errno));
_exit(125);
}
/* Set the working directory */
if (p->cwd && chdir(p->cwd) == -1) {
msg(F_PROC, L_ERR, "proc_run: ERROR: Could not chdir "
"to '%s': %s!\n",
p->cwd, strerror(errno));
_exit(126);
}
/* Run the child */
execve(p->argv[0], p->argv, p->envp);
/* Apparently it failed */
msg(F_PROC, L_ERR, "proc_run: ERROR: Could not run %s: %s!\n",
p->argv[0], strerror(errno));
_exit(127);
}
msg(F_PROC, L_DEBUG, "proc_start: started pid %d to run %s\n", p->pid, p->argv[0]);
/* Close the other ends of the pipe in the parent */
close(c2p[PIPE_W]); close(p2c[PIPE_R]);
/* Go to next state and return */
p->state = PRS_IDLE;
p->timer = 0;
return p->pid;
pr_err:
if (p2c[PIPE_R] != -1) close(p2c[PIPE_R]);
if (p2c[PIPE_W] != -1) close(p2c[PIPE_W]);
if (c2p[PIPE_R] != -1) close(c2p[PIPE_R]);
if (c2p[PIPE_W] != -1) close(c2p[PIPE_W]);
p->state = PRS_STARTING;
p->timer = t + PRT_RETRYSTART;
p->pid = 0;
return -1;
}
/* End the process. If you don't want it to get restarted, stop calling
proc_handle_end, basically. Continue calling proc_handle_timeout though,
to send the SIGKILL if it doesn't respond to the SIGTERM. */
void proc_stop(PROC *p, time_t t)
{
/* is it safer to empty rings here in addition to proc_handle_end? */
if (p->pid) {
msg(F_PROC, L_DEBUG, "proc_stop: killing %d\n", p->pid);
kill(p->pid, SIGTERM);
p->state = PRS_KILLING;
p->timer = t + PRT_END;
}
}
/* Test if at least one full binary message is available in the ring; returns
* message size if yes, -1 if no, and -2 if a framing error is detected. */
ssize_t have_binmsg_inring(RING *r, U_INT32_T magic)
{
char binhdr[8];
ssize_t inl, expl;
inl = ring_maxget(r);
if (inl < sizeof(binhdr)) return -1;
ring_peekdata(r, binhdr, sizeof(binhdr));
if (getord(binhdr, 4) != magic) return -2;
expl = getord(binhdr + 4, 4);
if (expl < 8 || expl > C_MAX_MSGSIZE) return -2;
if (inl < expl) return -1;
return expl;
}
/* Test if at least one ASCII line is available in the ring; returns line
* length (without terminating LF) if yes, -1 if no. */
ssize_t have_ascline_inring(RING *r)
{
ssize_t inl, n;
inl = ring_maxget(r);
n = ring_strcspn(r, "\n", 1);
if (n < inl) return n;
return -1;
}
/*
* Event handlers
*/
/* To be called when select indicates our read pipe is ready for reading. */
void proc_handle_read(PROC *p, time_t t)
{
ssize_t n;
/* See if we got any space left at all. If not, then apparently the
child is sending us a bigger message than the ring can hold, which
is enough to give it the death penalty. There's no alternative;
as we're out of sync at this point. */
if (!ring_maxput(p->r)) {
msg(F_PROC, L_ERR, "proc_handle_read: ERROR: Message from %d "
"does not fit - restarting subprocess!\n",
p->argv[0]);
proc_stop(p, t);
return;
}
/* Read as much as we can. */
ring_read(p->r, p->rfd, &n);
if (!n) {
msg(F_PROC, L_ERR, "proc_handle_read: Warning: EOF from %d - "
"killing it to be sure\n", p->pid);
proc_stop(p, t);
return;
}
msg(F_PROC, L_DEBUG, "proc_handle_read: Got %ld from pid %d, %ld now "
"in ring.\n", n, p->pid, ring_maxget(p->r));
}
void proc_handle_write(PROC *p, time_t t)
{
ssize_t xfered, left;
/* Write as much as we can. */
if (ring_write(p->w, p->wfd, &xfered, 0) == RING_IOERR) {
msg(F_PROC, L_ERR, "proc_handle_write: Warning: write error from %d - killing it to be sure\n", p->pid);
proc_stop(p, t);
return;
}
/* If we transfered anything at all, add RECEIVING to our state */
p->state |= PRS_RECEIVING;
/* See if we got anything left to send, if not, remove SENDING */
left = ring_maxget(p->w);
if (!left) p->state &= ~PRS_SENDING;
msg(F_PROC, L_DEBUG, "proc_handle_write: Sent %ld to pid %d, %ld still "
"in ring.\n", xfered, p->pid, left);
}
/* Handles this process' timer expiry */
void proc_handle_timeout(PROC *p, time_t t)
{
msg(F_PROC, L_NOTICE, "proc_handle_timeout: Pid %d timed out in state "
"%d.\n", p->pid, p->state);
switch(p->state) {
case PRS_STARTING: /* retrying to start, */
proc_start(p, t); /* or restarting */
break;
case PRS_SENDING: /* started sending */
case PRS_RECEIVING: /* started receiving */
case PRS_XFERING: /* still sending, also receiving */
proc_stop(p, t);
break;
case PRS_KILLING: /* gave SIGTERM */
kill(p->pid, SIGKILL);
p->timer = t + PRT_END;
break;
default:
msg(F_PROC, L_ERR, "proc_handle_timeout: BUG: spurious timer "
"expiry for proc '%s' in state %d!\n",
p->argv[0], p->state);
}
}
/* Handles a SIGCHLD from this process */
void proc_handle_end(PROC *p, time_t t, int exitcode)
{
msg(F_PROC, L_NOTICE, "proc_handle_end: Child %d exited, return code "
"%d (%d).\n", p->pid, exitcode>>8, exitcode&0xff);
close(p->rfd); close(p->wfd); p->rfd = p->wfd = -1;
ring_discard(p->r, 0); ring_discard(p->w, 0);
p->pid = 0;
p->state = PRS_STARTING;
p->timer = t + PRT_RESTART;
}
syntax highlighted by Code2HTML, v. 0.9.1