File:  [ELWIX - Embedded LightWeight unIX -] / libaitio / src / exec.c
Revision 1.1.2.5: download - view: text, annotated - select for diffs - revision graph
Thu Dec 5 14:16:33 2013 UTC (10 years, 7 months ago) by misho
Branches: io6_7
fix ret code

#include "global.h"


extern char **environ;
extern int __isthreaded;

struct pid {
	SLIST_ENTRY(pid) next;
	FILE *fp;
	pid_t pid;
};
static SLIST_HEAD(, pid) pidlist = SLIST_HEAD_INITIALIZER(pidlist);
static pthread_mutex_t pidlist_mutex = PTHREAD_MUTEX_INITIALIZER;

#define	THREAD_LOCK()	if (__isthreaded) pthread_mutex_lock(&pidlist_mutex)
#define	THREAD_UNLOCK()	if (__isthreaded) pthread_mutex_unlock(&pidlist_mutex)


/*
 * io_progInit() - Init program pool
 *
 * @progName = program name for execution
 * @initNum = initial started programs
 * @maxNum = maximum started programs
 * return: NULL error or !=NULL allocated pool (must destroied with io_progDestroy())
 */
prog_t *
io_progInit(const char *progName, u_int initNum, u_int maxNum)
{
	prog_t *prg = NULL;

	if (initNum > maxNum)
		return NULL;

	prg = e_malloc(sizeof(prog_t));
	if (!prg) {
		io_SetErr(elwix_GetErrno(), "%s", elwix_GetError());
		return NULL;
	} else
		memset(prg, 0, sizeof(prog_t));

	prg->prog_inin = initNum;
	prg->prog_maxn = maxNum;
	strlcpy(prg->prog_name, progName, sizeof prg->prog_name);

	prg->prog_fds = array_Init(prg->prog_maxn);
	if (!prg->prog_fds) {
		io_SetErr(elwix_GetErrno(), "%s", elwix_GetError());
		e_free(prg);
		return NULL;
	}

	pthread_mutex_init(&prg->prog_mtx, NULL);

	if (io_progOpen(prg, prg->prog_inin) < 0)
		io_progDestroy(&prg);
	return prg;
}

/*
 * io_progDestroy() - Destroy entire program pool
 *
 * @pprg = program pool
 * return: none
 */
void
io_progDestroy(prog_t ** __restrict pprg)
{
	if (!pprg || !*pprg)
		return;

	io_progClose(*pprg, 0);

	array_Destroy(&(*pprg)->prog_fds);
	pthread_mutex_destroy(&(*pprg)->prog_mtx);

	e_free(*pprg);
	*pprg = NULL;
}

/*
 * io_progClose() - Close all programs in pool
 *
 * @prg = program pool
 * @closeNum = close program(s) (0 all)
 * return: 0 error, >0 closed programs
 */
int
io_progClose(prog_t * __restrict prg, u_int closeNum)
{
	register int i;
	int ret = 0;

	if (!prg)
		return 0;
	if (closeNum > prg->prog_maxn) {
		io_SetErr(EINVAL, "Requested number for close program is over pool's limit");
		return 0;
	}

	pthread_mutex_lock(&prg->prog_mtx);
	for (i = array_Size(prg->prog_fds) - 1; 
			(closeNum ? ret < closeNum : 42) && i > -1; i--)
		if (array_Get(prg->prog_fds, i)) {
			io_pclose(array(prg->prog_fds, i, FILE*));
			array_Del(prg->prog_fds, i, 0);
			prg->prog_cnum--;
			ret++;
		}
	pthread_mutex_unlock(&prg->prog_mtx);

	return ret;
}

/*
 * io_progOpen() - Execute number of program(s)
 *
 * @prg = program pool
 * @execNum = execute program(s) (0 max)
 * return: 0 error, >0 executed programs and abs(<0) executed programs with logged error
 */
int
io_progOpen(prog_t * __restrict prg, u_int execNum)
{
	FILE *f;
	int stat, ret = 0;
	register int i;
	pid_t pid;

	if (!prg)
		return 0;
	if (execNum > prg->prog_maxn) {
		io_SetErr(EINVAL, "Requested number for program execution is over pool's limit");
		return 0;
	}

	pthread_mutex_lock(&prg->prog_mtx);
	for (i = 0; (execNum ? ret < execNum : 42) && i < array_Size(prg->prog_fds); i++)
		if (!array_Get(prg->prog_fds, i)) {
			f = io_popen(prg->prog_name, "r+", &pid);
			if (!f) {
				LOGERR;
				ret = -1;
				break;
			} else if (waitpid(pid, &stat, WNOHANG) > 0) {
				io_SetErr(ECHILD, "Program exit with status %d", 
						WIFEXITED(stat) ? WEXITSTATUS(stat) : -1);
				ret = -1;
				break;
			} else
				array_Set(prg->prog_fds, i, f);
			prg->prog_cnum++;
			ret++;
		}
	pthread_mutex_unlock(&prg->prog_mtx);

	return ret;
}

/*
 * io_progVacuum() - Vacuum pool to running number of programs
 *
 * @prg = program pool
 * @toNum = vacuum to number of programs (0 to init number)
 * return: 0 error or >0 closed programs
 */
int
io_progVacuum(prog_t * __restrict prg, u_int toNum)
{
	register int i;
	int ret = 0;

	if (!prg)
		return 0;
	if (toNum > prg->prog_maxn) {
		io_SetErr(EINVAL, "Requested number for close program is over pool's limit");
		return 0;
	}
	if (!toNum)
		toNum = prg->prog_inin;

	pthread_mutex_lock(&prg->prog_mtx);
	for (i = array_Size(prg->prog_fds) - 1; prg->prog_cnum > toNum && i > -1; i--)
		if (array_Get(prg->prog_fds, i)) {
			io_pclose(array(prg->prog_fds, i, FILE*));
			array_Del(prg->prog_fds, i, 0);
			prg->prog_cnum--;
			ret++;
		}
	pthread_mutex_unlock(&prg->prog_mtx);

	return ret;
}


/*
 * io_popen() - ELWIX replacement of standard popen
 *
 * @command = command
 * @type = type
 * @ppid = return pid of child program
 * return: NULL error or !=NULL open program
 */
FILE *
io_popen(const char *command, const char *type, pid_t *ppid)
{
	struct pid *cur;
	FILE *iop;
	int pdes[2], pid, twoway, cloexec;
	char *argv[4];
	struct pid *p;

	cloexec = strchr(type, 'e') != NULL;
	/*
	 * Lite2 introduced two-way popen() pipes using _socketpair().
	 * FreeBSD's pipe() is bidirectional, so we use that.
	 */
	if (strchr(type, '+')) {
		twoway = 1;
		type = "r+";
	} else  {
		twoway = 0;
		if ((*type != 'r' && *type != 'w') ||
		    (type[1] && (type[1] != 'e' || type[2])))
			return (NULL);
	}
	if ((cloexec ? pipe2(pdes, O_CLOEXEC) : pipe(pdes)) < 0)
		return (NULL);

	if ((cur = e_malloc(sizeof(struct pid))) == NULL) {
		close(pdes[0]);
		close(pdes[1]);
		return (NULL);
	}

	argv[0] = "sh";
	argv[1] = "-c";
	argv[2] = (char *)command;
	argv[3] = NULL;

	THREAD_LOCK();
	switch (pid = vfork()) {
	case -1:			/* Error. */
		THREAD_UNLOCK();
		close(pdes[0]);
		close(pdes[1]);
		e_free(cur);
		return (NULL);
		/* NOTREACHED */
	case 0:				/* Child. */
		if (*type == 'r') {
			/*
			 * The _dup2() to STDIN_FILENO is repeated to avoid
			 * writing to pdes[1], which might corrupt the
			 * parent's copy.  This isn't good enough in
			 * general, since the _exit() is no return, so
			 * the compiler is free to corrupt all the local
			 * variables.
			 */
			if (!cloexec)
				close(pdes[0]);
			if (pdes[1] != STDOUT_FILENO) {
				dup2(pdes[1], STDOUT_FILENO);
				if (!cloexec)
					close(pdes[1]);
				if (twoway)
					dup2(STDOUT_FILENO, STDIN_FILENO);
			} else if (twoway && (pdes[1] != STDIN_FILENO)) {
				dup2(pdes[1], STDIN_FILENO);
				if (cloexec)
					fcntl(pdes[1], F_SETFD, 0);
			} else if (cloexec)
				fcntl(pdes[1], F_SETFD, 0);
		} else {
			if (pdes[0] != STDIN_FILENO) {
				dup2(pdes[0], STDIN_FILENO);
				if (!cloexec)
					close(pdes[0]);
			} else if (cloexec)
				fcntl(pdes[0], F_SETFD, 0);
			if (!cloexec)
				close(pdes[1]);
		}
		SLIST_FOREACH(p, &pidlist, next)
			close(fileno(p->fp));
		execve(_PATH_BSHELL, argv, environ);
		_exit(127);
		/* NOTREACHED */
	default:
		if (ppid)
			*ppid = pid;
	}
	THREAD_UNLOCK();

	/* Parent; assume fdopen can't fail. */
	if (*type == 'r') {
		iop = fdopen(pdes[0], type);
		close(pdes[1]);
	} else {
		iop = fdopen(pdes[1], type);
		close(pdes[0]);
	}

	/* Link into list of file descriptors. */
	cur->fp = iop;
	cur->pid = pid;
	THREAD_LOCK();
	SLIST_INSERT_HEAD(&pidlist, cur, next);
	THREAD_UNLOCK();

	return (iop);
}

/*
 * io_pclose() - ELWIX replacement of standard pclose
 *
 * @iop = popen handle
 * return: -1 error or !=-1 pid status
 */
int
io_pclose(FILE *iop)
{
	struct pid *cur, *last = NULL;
	int pstat;
	pid_t pid;

	/*
	 * Find the appropriate file pointer and remove it from the list.
	 */
	THREAD_LOCK();
	SLIST_FOREACH(cur, &pidlist, next) {
		if (cur->fp == iop)
			break;
		last = cur;
	}
	if (cur == NULL) {
		THREAD_UNLOCK();
		return (-1);
	}
	if (last == NULL)
		SLIST_REMOVE_HEAD(&pidlist, next);
	else
		SLIST_REMOVE_AFTER(last, next);
	THREAD_UNLOCK();

	fclose(iop);

	do {
		pid = wait4(cur->pid, &pstat, 0, (struct rusage *)0);
	} while (pid == -1 && errno == EINTR);

	e_free(cur);

	return (pid == -1 ? -1 : pstat);
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>