pfopen.c

Go to the documentation of this file.
00001 /*--------------------------------------------------------------------------
00002 
00003    Copyright (c) 2006-2007, Intellectual Reserve, Inc. All rights reserved.
00004 
00005    This program is free software; you can redistribute it and/or modify
00006    it under the terms of the GNU General Public License version 2 as
00007    published by the Free Software Foundation.
00008 
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012    GNU General Public License for more details.
00013 
00014    You should have received a copy of the GNU General Public License
00015    along with this program; if not, write to the Free Software
00016    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00017 
00018   --------------------------------------------------------------------------*/
00019 
00029 #include "pfopen.h"
00030 
00031 #include "dnxDebug.h"
00032 
00033 #include <sys/types.h>
00034 #include <stdio.h>
00035 #include <stdlib.h>
00036 #include <unistd.h>
00037 #include <string.h>
00038 #include <errno.h>
00039 #include <fcntl.h>
00040 #include <signal.h>
00041 #include <sys/wait.h>
00042 #include <assert.h>
00043 
00044 #define elemcount(x) (sizeof(x)/sizeof(*(x)))
00045 
00046 //----------------------------------------------------------------------------
00047 
00093 PFILE * pfopen(const char * cmdstring, const char * type)
00094 {
00095    PFILE * pfile;
00096    int pid, eno, i, pfd[6];
00097 
00098    // only allow "r" or "w"
00099    assert((type[0] == 'r' || type[0] == 'w') &&  type[1] == 0);
00100 
00101    // Allocate a PFILE structure
00102    if ((pfile = (PFILE *)xcalloc(1, sizeof *pfile)) == 0)
00103    {
00104       errno = ENOMEM;
00105       return 0;
00106    }
00107 
00108    // set all descriptors to unallocated state and open pipes
00109    memset(pfd, -1, sizeof pfd);
00110    if (pipe(pfd) < 0 || pipe(pfd+2) < 0
00111          || (*type == 'w' && pipe(pfd+4) < 0))
00112       goto errout;
00113 
00114    // create buffered channels for parent ends of pipes
00115    if ((pfile->fp[0] = fdopen(pfd[0], "r")) == 0
00116          || (pfile->fp[1] = fdopen(pfd[2], "r")) == 0
00117          || (*type == 'w' && (pfile->fp[2] = fdopen(pfd[5], "w")) == 0))
00118       goto errout;
00119 
00120    // fork and check for error
00121    if ((pid = fork()) < 0) 
00122       goto errout;
00123 
00124    // child process?
00125    if (pid == 0)
00126    {
00127       // make child its own process group so parent can signal child subtree
00128       setpgid(0, 0);
00129 
00130       // close parent (read) ends, dup child (write) ends (if needed)
00131       fclose(pfile->fp[0]);
00132       fclose(pfile->fp[1]);
00133       if (pfd[1] != STDOUT_FILENO) 
00134       {
00135          dup2(pfd[1], STDOUT_FILENO);
00136          close(pfd[1]);
00137       }
00138       if (pfd[3] != STDERR_FILENO) 
00139       {
00140          dup2(pfd[3], STDERR_FILENO);
00141          close(pfd[3]);
00142       }
00143 
00144       // if also sending input from parent to child...
00145       if (*type == 'w')
00146       {
00147          // close parent (write) end, dup child (read) end (if needed)
00148          fclose(pfile->fp[2]);
00149          if (pfd[4] != STDIN_FILENO) 
00150          {
00151             dup2(pfd[4], STDIN_FILENO);
00152             close(pfd[4]);
00153          }
00154       } 
00155 
00156       execl("/bin/sh", "/bin/sh", "-c", cmdstring, (char *)0);
00157 
00158       _exit(127); // if we get here, we got an exec error
00159    }
00160 
00161    // parent continues in this process...
00162 
00163    // close child ends of pipes
00164    close(pfd[1]);
00165    close(pfd[3]);
00166    if (*type == 'w')
00167       close(pfd[4]);
00168 
00169    // save child process pid
00170    pfile->pid = pid;
00171 
00172    return pfile;
00173 
00174 errout:
00175    eno = errno;  // save errno (close changes it)
00176 
00177    // close buffered parent FILEs (also closes descriptors)
00178    if (pfile->fp[0] != 0)
00179       fclose(pfile->fp[0]), pfd[0] = -1;
00180    if (pfile->fp[1] != 0)
00181       fclose(pfile->fp[1]), pfd[2] = -1;
00182    if (pfile->fp[2] != 0)
00183       fclose(pfile->fp[2]), pfd[5] = -1;
00184 
00185    // close any remaining unbuffered descriptors
00186    for (i = 0; i < elemcount(pfd); i++)
00187       if (pfd[i] >= 0)
00188          close(pfd[i]);
00189 
00190    xfree(pfile);
00191    errno = eno;
00192    return 0;
00193 }
00194 
00195 //----------------------------------------------------------------------------
00196 
00212 int pfclose(PFILE * pfile)
00213 {
00214    int stat;
00215 
00216    if (pfile == 0)
00217    {
00218       errno = EINVAL;
00219       return -1;     /* popen() has never been called */
00220    }
00221 
00222    if (pfile->fp[0] != 0)
00223       fclose(pfile->fp[0]);
00224    if (pfile->fp[1] != 0)
00225       fclose(pfile->fp[1]);
00226    if (pfile->fp[2] != 0)
00227       fclose(pfile->fp[2]);
00228    
00229    xfree(pfile);
00230 
00231    while (waitpid(pfile->pid, &stat, 0) < 0)
00232       if (errno != EINTR)
00233          return -1;  /* error other than EINTR from waitpid() */
00234    
00235    return stat;      /* return child's termination status */
00236 }
00237 
00238 //----------------------------------------------------------------------------
00239 
00247 int pfkill(PFILE * pfile, int sig)
00248 {
00249    assert(pfile);
00250 
00251    /* send specified signal to child process group */
00252    return kill(-(pfile->pid), sig);
00253 }
00254 
00255 /*--------------------------------------------------------------------------
00256                                  TEST MAIN
00257 
00258    From within dnx/client, compile with GNU tools using this command line:
00259 
00260       gcc -DDEBUG -DPFOPEN_TEST -g -O0 -o pfopenTest -I../common \
00261          ../common/dnxError.c pfopen.c
00262 
00263    Alternatively, a heap check may be done with the following command line:
00264 
00265       gcc -DDEBUG -DDEBUG_HEAP -DPFOPEN_TEST -g -O0 -o pfopenTest \
00266          -I../common ../common/dnxError.c pfopen.c
00267 
00268   --------------------------------------------------------------------------*/
00269 
00270 #ifdef PFOPEN_TEST
00271 
00272 #include "utesthelp.h"
00273 #include "dnxError.h"
00274 
00275 // Implementation Note: m4 echos back on STDOUT whatever it reads on
00276 // STDIN; when killed (SIGTERM), it writes "Terminated\n" to STDERR.
00277 
00278 char * program = "m4";
00279 char * text = "This is a test\n";
00280 char * errtext = "Terminated\n";
00281 int verbose;
00282 
00283 int main(int argc, char ** argv)
00284 {
00285    PFILE * pf;
00286    FILE * fin, * fout, * ferr;
00287    char buf[128];
00288 
00289    verbose = argc > 1 ? 1 : 0;
00290 
00291    CHECK_NONZERO(pf = pfopen(program, "w"));
00292 
00293    fin  = PF_IN(pf);
00294    fout = PF_OUT(pf);
00295    ferr = PF_ERR(pf);
00296 
00297    fprintf(fin, text);
00298    fflush(fin);
00299    close(fileno(fin));
00300 
00301    fgets(buf, sizeof buf, fout);
00302 
00303    CHECK_ZERO(strcmp(text, buf));
00304 
00305    CHECK_ZERO(pfclose(pf));
00306 
00307 #ifdef DEBUG_HEAP
00308    CHECK_ZERO(dnxCheckHeap());
00309 #endif
00310 
00311    return 0;
00312 }
00313 
00314 #endif   /* PFOPEN_TEST */
00315 
00316 /*--------------------------------------------------------------------------*/
00317 

Generated on Tue Apr 13 15:48:07 2010 for DNX by  doxygen 1.5.6