Logo Search packages:      
Sourcecode: cacti-spine version File versions  Download package

nft_popen.c

/*******************************************************************************
 ex: set tabstop=4 shiftwidth=4 autoindent:
 * (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 "spine.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;
      int    retry_count = 0;

      /* 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. */
      retry:
      switch (pid = vfork()) {
      case -1:          /* Error. */
            switch (errno) {
            case EAGAIN:
                  if (retry_count < 3) {
                        retry_count++;
                        #ifndef SOLAR_THREAD
                        /* take a moment */
                        usleep(50000);
                        #endif
                        goto retry;
                  }else{
                        SPINE_LOG(("ERROR: SCRIPT: Cound not fork. Out of Resources nft_popen.c\n"));
                  }
            case ENOMEM:
                  if (retry_count < 3) {
                        retry_count++;
                        #ifndef SOLAR_THREAD
                        /* take a moment */
                        usleep(50000);
                        #endif
                        goto retry;
                  }else{
                        SPINE_LOG(("ERROR: SCRIPT Cound not fork. Out of Memory nft_popen.c\n"));
                  }
            default:
                  SPINE_LOG(("ERROR: SCRIPT Cound not fork. Unknown Reason nft_popen.c\n"));
            }

            (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);

      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);
}


Generated by  Doxygen 1.6.0   Back to index