/*******************************************************************************
* (C) Xenadyne Inc. 2002. All Rights Reserved
*
* Permission to use, copy, modify and distribute this software for
* any purpose and without fee is hereby granted, provided that the
* above copyright notice appears in all copies. Also note the
* University of California copyright below.
*
* XENADYNE INC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
* IN NO EVENT SHALL XENADYNE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM THE
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* File: nft_popen.c
*
* Description: A thread-safe replacement for popen()/pclose().
*
* This is a thread-safe variant of popen that does unbuffered IO, to
* avoid running afoul of Solaris's inability to fdopen when fd > 255.
*
*******************************************************************************
*/
/*
* Copyright (c) 1988, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software written by Ken Arnold and
* published in UNIX Review, Vol. 6, No. 8.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS 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 REGENTS OR 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.
*/
#include "common.h"
#include "cactid.h"
/* An instance of this struct is created for each popen() fd. */
static struct pid
{
struct pid *next;
int fd;
pid_t pid;
} * PidList;
/* Serialize access to PidList. */
static pthread_mutex_t ListMutex = PTHREAD_MUTEX_INITIALIZER;
static void close_cleanup(void *);
/*! ------------------------------------------------------------------------------
*
* nft_popen
*
* The nft_popen() function forks a command in a child process, and returns
* a pipe that is connected to the child's standard input and output. It is
* like the standard popen() call, except that it does not dfopen() the pipe
* file descriptor in order to return a stdio FILE *. This is useful if you
* wish to use select()- or poll()-driven IO.
*
* The mode argument is defined as in standard popen().
*
* On success, returns a file descriptor, or -1 on error.
* On failure, returns -1, with errno set to one of:
* EINVAL The mode argument is incorrect.
* EMFILE pipe() failed.
* ENFILE pipe() failed.
* ENOMEM malloc() failed.
* EAGAIN fork() failed.
*
*------------------------------------------------------------------------------
*/
int nft_popen(const char * command, const char * type) {
struct pid *cur;
struct pid *p;
int pdes[2];
int fd, pid, twoway;
char *argv[4];
int cancel_state;
extern char **environ;
/* On platforms where pipe() is bidirectional,
* "r+" gives two-way communication.
*/
if (strchr(type, '+')) {
twoway = 1;
type = "r+";
}else {
twoway = 0;
if ((*type != 'r' && *type != 'w') || type[1]) {
errno = EINVAL;
return -1;
}
}
if (pipe(pdes) < 0)
return -1;
/* Disable thread cancellation from this point forward. */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
if ((cur = malloc(sizeof(struct pid))) == NULL) {
(void)close(pdes[0]);
(void)close(pdes[1]);
pthread_setcancelstate(cancel_state, NULL);
return -1;
}
argv[0] = "sh";
argv[1] = "-c";
argv[2] = (char *)command;
argv[3] = NULL;
/* Lock the list mutex prior to forking, to ensure that
* the child process sees PidList in a consistent list state.
*/
pthread_mutex_lock(&ListMutex);
/* Fork. */
switch (pid = fork()) {
case -1: /* Error. */
(void)close(pdes[0]);
(void)close(pdes[1]);
pthread_mutex_unlock(&ListMutex);
pthread_setcancelstate(cancel_state, NULL);
return -1;
/* 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.
*/
(void)close(pdes[0]);
if (pdes[1] != STDOUT_FILENO) {
(void)dup2(pdes[1], STDOUT_FILENO);
(void)close(pdes[1]);
if (twoway)
(void)dup2(STDOUT_FILENO, STDIN_FILENO);
}else if (twoway && (pdes[1] != STDIN_FILENO))
(void)dup2(pdes[1], STDIN_FILENO);
}else {
if (pdes[0] != STDIN_FILENO) {
(void)dup2(pdes[0], STDIN_FILENO);
(void)close(pdes[0]);
}
(void)close(pdes[1]);
}
/* Close all the other pipes in the child process.
* Posix.2 requires this, tho I don't know why.
*/
for (p = PidList; p; p = p->next)
(void)close(p->fd);
/* Execute the command. */
execve("/bin/sh", argv, environ);
_exit(127);
/* NOTREACHED */
}
/* Parent. */
if (*type == 'r') {
fd = pdes[0];
(void)close(pdes[1]);
}else {
fd = pdes[1];
(void)close(pdes[0]);
}
/* Link into list of file descriptors. */
cur->fd = fd;
cur->pid = pid;
cur->next = PidList;
PidList = cur;
/* Unlock the mutex, and restore caller's cancellation state. */
pthread_mutex_unlock(&ListMutex);
pthread_setcancelstate(cancel_state, NULL);
return fd;
}
/*! ------------------------------------------------------------------------------
*
* nft_pchild
*
* Get the pid of the child process for an fd created by ntf_popen().
*
* On success, the pid of the child process is returned.
* On failure, nft_pchild() returns -1, with errno set to:
*
* EBADF The fd is not an active nft_popen() file descriptor.
*
*------------------------------------------------------------------------------
*/
int nft_pchild(int fd) {
struct pid *cur;
pid_t pid = 0;
/* Find the appropriate file descriptor. */
pthread_mutex_lock(&ListMutex);
for (cur = PidList; cur; cur = cur->next)
if (cur->fd == fd) {
pid = cur->pid;
break;
}
pthread_mutex_unlock(&ListMutex);
if (cur == NULL) {
errno = EBADF;
return -1;
}
return pid;
}
/*! ------------------------------------------------------------------------------
*
* nft_pclose
*
* Close the pipe and wait for the status of the child process.
*
* On success, the exit status of the child process is returned.
* On failure, nft_pclose() returns -1, with errno set to:
*
* EBADF The fd is not an active popen() file descriptor.
* ECHILD The waitpid() call failed.
*
* This call is cancellable.
*
*------------------------------------------------------------------------------
*/
int
nft_pclose(int fd)
{
struct pid *cur;
int pstat;
pid_t pid;
/* Find the appropriate file descriptor. */
pthread_mutex_lock(&ListMutex);
for (cur = PidList; cur; cur = cur->next)
if (cur->fd == fd) break;
pthread_mutex_unlock(&ListMutex);
if (cur == NULL) {
errno = EBADF;
return -1;
}
/* The close and waitpid calls below are cancellation points.
* We want to ensure that the fd is closed and the PidList
* entry freed despite cancellation, so push a cleanup handler.
*/
pthread_cleanup_push(close_cleanup, cur);
/* end the process nicely and then forcefully */
(void)close(fd);
/* don't be nice to processes that have hung */
kill(cur->pid, SIGTERM);
cur->fd = -1; /* Prevent the fd being closed twice. */
do { pid = waitpid(cur->pid, &pstat, WNOHANG); }
while (pid == -1 && errno == EINTR);
pthread_cleanup_pop(1); /* Execute the cleanup handler. */
return (pid == -1 ? -1 : pstat);
}
/*! ------------------------------------------------------------------------------
* close_cleanup - close the pipe and free the pidlist entry.
*------------------------------------------------------------------------------
*/
static void
close_cleanup(void * arg)
{
struct pid * cur = arg;
struct pid * prev;
/* Close the pipe fd if necessary. */
if (cur->fd >= 0) {
(void)close(cur->fd);
}
/* Remove the entry from the linked list. */
pthread_mutex_lock(&ListMutex);
if (PidList == cur) {
PidList = cur->next;
}else{
for (prev = PidList; prev; prev = prev->next)
if (prev->next == cur) {
prev->next = cur->next;
break;
}
assert(prev != NULL); /* Search should not fail */
}
pthread_mutex_unlock(&ListMutex);
free(cur);
}
syntax highlighted by Code2HTML, v. 0.9.1