/* ***************************************************************************** * * $RCSfile: bidipipe.c,v $ * $Date: 1998/07/15 18:50:18 $ * $Source: /home/richard/Xml/RCS/bidipipe.c,v $ * $Revision: 1.2 $ * $Author: richard $ * ***************************************************************************** * * Copyright 1998, Brown University and Richard Goerwitz * ***************************************************************************** * * Utilities for reading from and writing to the same pipe. * * Both main entry points below, bidi_popen and bidi_spopen, work a * bit like popen, in that they return a pointer to a FILE stream. * They differ, though, in the following significant ways: 1) the * FILE stream is always opened mode "r", and 2) before returning, * both routines may also arrange to have data sent to the command * that is being read from: * * FILE *bidi_popen (f, cmd) - send data from FILE *f to cmd, then * return cmd's stdout as a stream * FILE *bidi_spopen (s, cmd) - send data from char *s to cmd, then * return cmd's stdout as a stream * * Both routines return NULL on various forms of failure (failed fork, * failed fdopen, and various other internal failures). A NULL f or * s argument makes these routines work like popen(cmd, "r"). * * NOTE WELL: Just as with popen(), you must close the streams * returned by these routines. The routine used to do the closing * is: * * int bidi_pclose (f) * * The return value of bidi_pclose is -1 on failure (e.g., if f/s is * already closed). Otherwise, it is the exit status of the process * opened as cmd (arg 2 to bidi_popen and bidi_spopen) above. * ***************************************************************************** */ #include "bidipipe.h" #include "sigstuff.h" #include #include struct file_and_process { FILE *f; pid_t pid; }; static int flist_len = 0; static int flist_buflen = 0; static struct file_and_process **flist = NULL; static int add_file_to_flist (FILE *, pid_t); static FILE *common_popen (void *, char *, int); #define FILE_SOURCE 1 #define STRING_SOURCE 2 /* * bidi_popen * * A bit like popen() except that it's bidirectional. It reads from * FILE f (arg 1) and writes f's data to external command cmd (arg 2). * Returns a file descriptor from which cmd's data may be read. To * close the pipeline, use bidi_pclose (f). If f is NULL below, this * routine works like popen(cmd, "r"). */ FILE * bidi_popen (FILE *f, char *cmd) { /* common_popen will write f to cmd and return a FILE pointer */ return common_popen (f, cmd, FILE_SOURCE); } /* * bidi_spopen * * A bit like popen() except that it's bidirectional. It reads from * string s (arg 1) and writes s out to external command cmd (arg 2). * Returns a file descriptor from which cmd's data may be read. To * close the pipeline, use bidi_pclose (f). If f is NULL below, this * routine works like popen(cmd, "r"). */ FILE * bidi_spopen (char *s, char *cmd) { /* common_popen will write s to cmd and return a FILE pointer */ return common_popen (s, cmd, STRING_SOURCE); } /* * common_popen * * See bidi_popen and bidi_spopen above. Somewhat like popen(). */ static FILE * common_popen (void *data, char *cmd, int data_type) { size_t i; pid_t pid; FILE *out_f; char buf[BUFSIZ]; int in_fd[2], out_fd[2]; int tmpfd, count, saved_errno, exitcode, status; out_f = NULL; /* fork to avoid deadlock */ if (pipe (out_fd) >= 0) { sig_hold (SIGCHLD); switch (pid = fork ()) { case -1: return NULL; case 0: sig_default (SIGALRM); sig_default (SIGCHLD); #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "In first child.\n"); #endif /* close all streams opened by previous popen calls */ for (i = 0; i < flist_len; i++) fclose (flist[i]->f); if (data == NULL) { /* nothing to write to cmd */ #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "Exec'ing %s.\n", cmd); #endif close (out_fd[0]); if ((tmpfd = open ("/dev/null", O_RDONLY)) != -1) { close (0); dup2 (tmpfd, 0); } close (1); dup2 (out_fd[1], 1); close (2); dup2 (out_fd[1], 2); execl ("/bin/sh", "sh", "-c", cmd, NULL); exit (1); } else { if (pipe (in_fd) < 0) exit (1); else { switch (fork ()) { case -1: exit (1); case 0: #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "In second child.\n"); #endif close (out_fd[0]); close (out_fd[1]); close (in_fd[0]); exitcode = 0; saved_errno = errno; if (data_type == STRING_SOURCE) { close (0); close (1); close (2); count = strlen (data); if (write (in_fd[1], data, count) < count) exitcode = 1; } else { close (1); close (2); if ((FILE *)data != stdin) close (0); while ((count = fread (buf, 1, BUFSIZ, data)) > 0) if (write (in_fd[1], buf, count) < count) break; if (errno || ferror ((FILE *)data)) exitcode = 1; } close (in_fd[1]); errno = saved_errno; #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "Second child exit status = %d.\n", exitcode); #endif exit (exitcode); break; default: /* still in first child (itself a parent now) */ #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "Exececuting %s.\n", cmd); #endif close (in_fd[1]); close (out_fd[0]); close (0); dup2 (in_fd[0], 0); close (1); dup2 (out_fd[1], 1); close (2); dup2 (out_fd[1], 2); execl ("/bin/sh", "sh", "-c", cmd, NULL); exit (1); } } } break; default: /* still in parent */ close (out_fd[1]); if ((out_f = fdopen (out_fd[0], "r")) == NULL || add_file_to_flist (out_f, pid) == -1) { kill (pid, SIGTERM); close (out_fd[0]); if (waitpid (pid, &status, WNOHANG) == 0) { kill (pid, SIGKILL); waitpid (pid, &status, 0); } out_f = NULL; } #ifdef STANDALONE_BIDIPIPE_TEST fprintf (stderr, "Parent returning new stream to caller.\n"); #endif break; } sig_release (SIGCHLD); } return out_f; } static int add_file_to_flist (FILE *f, pid_t pid) { size_t i; struct file_and_process *new_fap; if ((new_fap = malloc (sizeof (struct file_and_process))) == NULL) return -1; new_fap->f = f; new_fap->pid = pid; if (flist == NULL) { flist_buflen = flist_len = 1; if ((flist = malloc (sizeof (struct file_and_process *))) == NULL) return -1; } else { if (++flist_len > flist_buflen) { flist_buflen *= 2; if ((flist = realloc (flist, flist_buflen * (sizeof (struct file_and_process *)))) == NULL) return -1; } } for (i = 0; i < (flist_len - 1); i++) if (flist[i]->f >= f) break; if (i < (flist_len - 1)) memmove (&flist[i + 1], &flist[i], (flist_len - i - 1) * sizeof (struct file_and_process *)); flist[i] = new_fap; return flist_len; } int bidi_pclose (FILE *f) { size_t i; int errno, status = -1; for (i = 0; i < flist_len; i++) { if (flist[i]->f > f) break; else if (flist[i]->f == f) { fclose (flist[i]->f); errno = 0; while (waitpid (flist[i]->pid, &status, 0) < 0) if (errno != EAGAIN) { status = -1; break; } free (flist[i]); if (i < --flist_len) memmove (&flist[i], &flist[i + 1], (flist_len - i) * sizeof (struct file_and_process *)); break; } } return status; } #ifdef STANDALONE_BIDIPIPE_TEST xmlparse_environment xmlparse_env; int main (void) { FILE *f, *f2, *f3; char buf[BUFSIZ]; f = bidi_popen (stdin, "/bin/cat"); memset (buf, 0, BUFSIZ); fread (buf, 1, BUFSIZ, f); puts (buf); f2 = bidi_spopen ("This is string data.", "/bin/cat"); memset (buf, 0, BUFSIZ); fread (buf, 1, BUFSIZ, f2); puts (buf); f3 = bidi_spopen (NULL, "/bin/echo 'hello world'"); memset (buf, 0, BUFSIZ); fread (buf, 1, BUFSIZ, f3); puts (buf); fprintf (stderr, "Subprocess 3 exit status (important) = %d\n", bidi_pclose (f3)); fprintf (stderr, "Subprocess 3 exit status (should be -1 now) = %d\n", bidi_pclose (f3)); fprintf (stderr, "flist_len = %d\n", flist_len); fprintf (stderr, "Subprocess 2 exit status (important) = %d\n", bidi_pclose (f2)); fprintf (stderr, "Subprocess 2 exit status (should be -1 now) = %d\n", bidi_pclose (f2)); fprintf (stderr, "flist_len = %d\n", flist_len); fprintf (stderr, "Subprocess 1 exit status (important) = %d\n", bidi_pclose (f)); fprintf (stderr, "Subprocess 1 exit status (should be -1 now) = %d\n", bidi_pclose (f)); fprintf (stderr, "flist_len = %d\n", flist_len); return 0; } #endif /* STANDALONE_BIDIPIPE_TEST */