/******************************************************************************* * (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 <assert.h> #include <errno.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <nft_popen.h> extern char **environ; /* 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; /* On platforms where pipe() is bidirectional, * "r+" gives two-way communication. */ if (strchr(type, '+')) { twoway = 1; type = strdup("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] = strdup("sh"); argv[1] = strdup("-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]); free(cur); 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); (void)close(fd); cur->fd = -1; /* Prevent the fd being closed twice. */ do { pid = waitpid(cur->pid, &pstat, 0); } 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); }