home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / vc98 / crt / src / popen.c < prev    next >
C/C++ Source or Header  |  1998-06-17  |  18KB  |  530 lines

  1. /***
  2. *popen.c - initiate a pipe and a child command
  3. *
  4. *       Copyright (c) 1989-1997, Microsoft Corporation. All rights reserved.
  5. *
  6. *Purpose:
  7. *       Defines _popen() and _pclose().
  8. *
  9. *******************************************************************************/
  10.  
  11.  
  12. #include <cruntime.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <malloc.h>
  16. #include <process.h>
  17. #include <io.h>
  18. #include <fcntl.h>
  19. #include <internal.h>
  20. #include <errno.h>
  21. #include <msdos.h>
  22. #include <mtdll.h>
  23. #include <oscalls.h>
  24. #include <tchar.h>
  25. #include <dbgint.h>
  26.  
  27. /* size for pipe buffer
  28.  */
  29. #define PSIZE     1024
  30.  
  31. #define STDIN     0
  32. #define STDOUT    1
  33.  
  34.  
  35. /* definitions for table of stream pointer - process handle pairs. the table
  36.  * is created, maintained and accessed by the idtab function. _popen and
  37.  * _pclose gain access to table entries only by calling idtab. Note that the
  38.  * table is expanded as necessary (by idtab) and free table entries are reused
  39.  * (an entry is free if its stream field is NULL), but the table is never
  40.  * contracted.
  41.  */
  42.  
  43. typedef struct {
  44.         FILE *stream;
  45.         int prochnd;
  46. } IDpair;
  47.  
  48. /* number of entries in idpairs table
  49.  */
  50. #ifndef _UNICODE
  51. unsigned __idtabsiz = 0;
  52. #else  /* _UNICODE */
  53. extern unsigned __idtabsiz;
  54. #endif  /* _UNICODE */
  55.  
  56. /* pointer to first table entry
  57.  */
  58. #ifndef _UNICODE
  59. IDpair *__idpairs = NULL;
  60. #else  /* _UNICODE */
  61. extern IDpair *__idpairs;
  62. #endif  /* _UNICODE */
  63.  
  64. /* function to find specified table entries. also, creates and maintains
  65.  * the table.
  66.  */
  67. static IDpair * __cdecl idtab(FILE *);
  68.  
  69.  
  70. /***
  71. *FILE *_popen(cmdstring,type) - initiate a pipe and a child command
  72. *
  73. *Purpose:
  74. *       Creates a pipe and asynchronously executes a child copy of the command
  75. *       processor with cmdstring (see system()). If the type string contains
  76. *       an 'r', the calling process can read child command's standard output
  77. *       via the returned stream. If the type string contains a 'w', the calling
  78. *       process can write to the child command's standard input via the
  79. *       returned stream.
  80. *
  81. *Entry:
  82. *       _TSCHAR *cmdstring - command to be executed
  83. *       _TSCHAR *type   - string of the form "r|w[b|t]", determines the mode
  84. *                         of the returned stream (i.e., read-only vs write-only,
  85. *                         binary vs text mode)
  86. *
  87. *Exit:
  88. *       If successful, returns a stream associated with one end of the created
  89. *       pipe (the other end of the pipe is associated with either the child
  90. *       command's standard input or standard output).
  91. *
  92. *       If an error occurs, NULL is returned.
  93. *
  94. *Exceptions:
  95. *
  96. *******************************************************************************/
  97.  
  98. FILE * __cdecl _tpopen (
  99.         const _TSCHAR *cmdstring,
  100.         const _TSCHAR *type
  101.         )
  102. {
  103.  
  104.         int phdls[2];             /* I/O handles for pipe */
  105.         int ph_open[2];           /* flags, set if correspond phdls is open */
  106.         int i1;                   /* index into phdls[] */
  107.         int i2;                   /* index into phdls[] */
  108.  
  109.         int tm = 0;               /* flag indicating text or binary mode */
  110.  
  111.         int stdhdl;               /* either STDIN or STDOUT */
  112.         HANDLE osfhndsv1;         /* used to save _osfhnd(stdhdl) */
  113.         long osfhndsv2;           /* used to save _osfhnd(phdls[i2]) */
  114.         char osfilesv1;           /* used to save _osfile(stdhdl) */
  115.         char osfilesv2;           /* used to save _osfile(phdls[i2]) */
  116.  
  117.         HANDLE oldhnd;            /* used to hold OS file handle values... */
  118.         HANDLE newhnd;            /* ...in calls to DuplicateHandle API */
  119.  
  120.         FILE *pstream;            /* stream to be associated with pipe */
  121.  
  122.         HANDLE prochnd;           /* handle for current process */
  123.  
  124.         _TSCHAR *cmdexe;                  /* pathname for the command processor */
  125.         int childhnd;             /* handle for child process (cmd.exe) */
  126.  
  127.         IDpair *locidpair;        /* pointer to IDpair table entry */
  128.  
  129.  
  130.         /* first check for errors in the arguments
  131.          */
  132.         if ( (cmdstring == NULL) || (type == NULL) || ((*type != 'w') &&
  133.              (*type != _T('r'))) )
  134.                 goto error1;
  135.  
  136.         /* do the _pipe(). note that neither of the resulting handles will
  137.          * be inheritable.
  138.          */
  139.  
  140.         if ( *(type + 1) == _T('t') )
  141.                 tm = _O_TEXT;
  142.         else if ( *(type + 1) == _T('b') )
  143.                 tm = _O_BINARY;
  144.  
  145.         tm |= _O_NOINHERIT;
  146.  
  147.         if ( _pipe( phdls, PSIZE, tm ) == -1 )
  148.                 goto error1;
  149.  
  150.         /* test *type and set stdhdl, i1 and i2 accordingly.
  151.          */
  152.         if ( *type == _T('w') ) {
  153.                 stdhdl = STDIN;
  154.                 i1 = 0;
  155.                 i2 = 1;
  156.         }
  157.         else {
  158.                 stdhdl = STDOUT;
  159.                 i1 = 1;
  160.                 i2 = 0;
  161.         }
  162.  
  163.         /* the pipe now exists. the following steps must be carried out before
  164.          * the child process (cmd.exe) may be spawned:
  165.          *
  166.          *      1. save a non-inheritable dup of stdhdl
  167.          *
  168.          *      2. force stdhdl to be a dup of ph[i1]. close both ph[i1] and
  169.          *         the original OS handle underlying stdhdl
  170.          *
  171.          *      3. associate a stdio-level stream with ph[i2].
  172.          */
  173.  
  174.         /* set flags to indicate pipe handles are open. note, these are only
  175.          * used for error recovery.
  176.          */
  177.         ph_open[ 0 ] = ph_open[ 1 ] = 1;
  178.  
  179.  
  180.         /* get the process handle, it will be needed in some API calls
  181.          */
  182.         prochnd = GetCurrentProcess();
  183.  
  184.         /* MULTI-THREAD: ASSERT LOCK ON STDHDL HERE!!!!
  185.          */
  186.         _lock_fh( stdhdl );
  187.  
  188.         /* save a non-inheritable copy of stdhdl for later restoration.
  189.          */
  190.  
  191.         oldhnd = (HANDLE)_osfhnd( stdhdl );
  192.  
  193.         if ( (oldhnd == INVALID_HANDLE_VALUE) ||
  194.              !DuplicateHandle( prochnd,
  195.                                oldhnd,
  196.                                prochnd,
  197.                                &osfhndsv1,
  198.                                0L,
  199.                                FALSE,                   /* non-inheritable */
  200.                                DUPLICATE_SAME_ACCESS )
  201.         ) {
  202.                 goto error2;
  203.         }
  204.  
  205.         osfilesv1 = _osfile( stdhdl );
  206.  
  207.         /* force stdhdl to an inheritable dup of phdls[i1] (i.e., force
  208.          * STDIN to the pipe read handle or STDOUT to the pipe write handle)
  209.          * and close phdls[i1] (so there won't be a stray open handle on the
  210.          * pipe after a _pclose). also, clear ph_open[i1] flag so that error
  211.          * recovery won't try to close it again.
  212.          */
  213.  
  214.         if ( !DuplicateHandle( prochnd,
  215.                                (HANDLE)_osfhnd( phdls[i1] ),
  216.                                prochnd,
  217.                                &newhnd,
  218.                                0L,
  219.                                TRUE,                    /* inheritable */
  220.                                DUPLICATE_SAME_ACCESS )
  221.         ) {
  222.                 goto error3;
  223.         }
  224.  
  225.         (void)CloseHandle( (HANDLE)_osfhnd(stdhdl) );
  226.         _free_osfhnd( stdhdl );
  227.  
  228.         _set_osfhnd( stdhdl, (long)newhnd );
  229.         _osfile( stdhdl ) = _osfile( phdls[i1] );
  230.  
  231.         (void)_close( phdls[i1] );
  232.         ph_open[ i1 ] = 0;
  233.  
  234.  
  235.         /* associate a stream with phdls[i2]. note that if there are no
  236.          * errors, pstream is the return value to the caller.
  237.          */
  238.         if ( (pstream = _tfdopen( phdls[i2], type )) == NULL )
  239.                 goto error4;
  240.  
  241.         /* MULTI-THREAD: ASSERT LOCK ON IDPAIRS HERE!!!!
  242.          */
  243.         _mlock( _POPEN_LOCK );
  244.  
  245.         /* next, set locidpair to a free entry in the idpairs table.
  246.          */
  247.         if ( (locidpair = idtab( NULL )) == NULL )
  248.                 goto error5;
  249.  
  250.  
  251.         /* temporarily change the osfhnd and osfile entries so that
  252.          * the child doesn't get any entries for phdls[i2].
  253.          */
  254.         osfhndsv2 = _osfhnd( phdls[i2] );
  255.         _osfhnd( phdls[i2] ) = (long)INVALID_HANDLE_VALUE;
  256.         osfilesv2 = _osfile( phdls[i2] );
  257.         _osfile( phdls[i2] ) = 0;
  258.  
  259.         /* spawn the child copy of cmd.exe. the algorithm is adapted from
  260.          * SYSTEM.C, and behaves the same way.
  261.          */
  262.         if ( ((cmdexe = _tgetenv(_T("COMSPEC"))) == NULL) ||
  263.              (((childhnd = _tspawnl( _P_NOWAIT,
  264.                                     cmdexe,
  265.                                     cmdexe,
  266.                                     _T("/c"),
  267.                                     cmdstring,
  268.                                     NULL ))
  269.              == -1) && ((errno == ENOENT) || (errno == EACCES))) ) {
  270.                 /*
  271.                  * either COMSPEC wasn't defined, or the spawn failed because
  272.                  * cmdexe wasn't found or was inaccessible. in either case, try to
  273.                  * spawn "cmd.exe" (Windows NT) or "command.com" (Windows 95) instead
  274.                  * Note that spawnlp is used here so that the path is searched.
  275.                  */
  276.                 cmdexe = ( _osver & 0x8000 ) ? _T("command.com") : _T("cmd.exe");
  277.                 childhnd = _tspawnlp( _P_NOWAIT,
  278.                                      cmdexe,
  279.                                      cmdexe,
  280.                                      _T("/c"),
  281.                                      cmdstring,
  282.                                      NULL);
  283.         }
  284.  
  285.         _osfhnd( phdls[i2] ) = osfhndsv2;
  286.         _osfile( phdls[i2] ) = osfilesv2;
  287.  
  288.         /* check if the last (perhaps only) spawn attempt was successful
  289.          */
  290.         if ( childhnd == -1 )
  291.                 goto error6;
  292.  
  293.         /* restore stdhdl for the current process, set value of *locidpair,
  294.          * close osfhndsv1 (note that CloseHandle must be used instead of close)
  295.          * and return pstream to the caller
  296.          */
  297.  
  298.         (void)DuplicateHandle( prochnd,
  299.                                osfhndsv1,
  300.                                prochnd,
  301.                                &newhnd,
  302.                                0L,
  303.                                TRUE,                    /* inheritable */
  304.                                DUPLICATE_CLOSE_SOURCE | /* close osfhndsv1 */
  305.                                DUPLICATE_SAME_ACCESS );
  306.  
  307.         (void)CloseHandle( (HANDLE)_osfhnd(stdhdl) );
  308.         _free_osfhnd( stdhdl );
  309.  
  310.         _set_osfhnd( stdhdl, (long)newhnd );
  311.         _osfile(stdhdl) = osfilesv1;
  312.  
  313.         /* MULTI-THREAD: RELEASE LOCK ON STDHDL HERE!!!!
  314.          */
  315.         _unlock_fh( stdhdl );
  316.  
  317.         locidpair->prochnd = childhnd;
  318.         locidpair->stream = pstream;
  319.  
  320.         /* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
  321.          */
  322.         _munlock( _POPEN_LOCK );
  323.  
  324.         /* all successful calls to _popen return to the caller via this return
  325.          * statement!
  326.          */
  327.         return( pstream );
  328.  
  329.         /**
  330.          * error handling code. all detected errors end up here, entering
  331.          * via a goto one of the labels. note that the logic is currently
  332.          * a straight fall-thru scheme (e.g., if entered at error5, the
  333.          * code for error5, error4,...,error1 is all executed).
  334.          **********************************************************************/
  335.  
  336.         error6: /* make sure locidpair is reusable
  337.                  */
  338.                 locidpair->stream = NULL;
  339.  
  340.         error5: /* close pstream (also, clear ph_open[i2] since the stream
  341.                  * close will also close the pipe handle)
  342.                  */
  343.                 (void)fclose( pstream );
  344.                 ph_open[ i2 ] = 0;
  345.  
  346.                 /* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
  347.                  */
  348.                 _munlock(_POPEN_LOCK);
  349.  
  350.         error4: /* restore stdhdl
  351.                  */
  352.  
  353.                 (void)DuplicateHandle( prochnd,
  354.                                        osfhndsv1,
  355.                                        prochnd,
  356.                                        &newhnd,
  357.                                        0L,
  358.                                        TRUE,
  359.                                        DUPLICATE_SAME_ACCESS );
  360.  
  361.                 (void)CloseHandle( (HANDLE)_osfhnd(stdhdl) );
  362.                 _free_osfhnd( stdhdl );
  363.  
  364.                 _set_osfhnd( stdhdl, (long)newhnd );
  365.                 _osfile( stdhdl ) = osfilesv1;
  366.  
  367.                 /* MULTI-THREAD: RELEASE LOCK ON STDHDL HERE!!!!
  368.                  */
  369.                 _unlock_fh( stdhdl );
  370.  
  371.         error3: /* close osfhndsv1
  372.                  */
  373.  
  374.                 CloseHandle( osfhndsv1 );
  375.  
  376.         error2: /* close handles on pipe (if they are still open)
  377.                  */
  378.                 if ( ph_open[i1] )
  379.                         _close( phdls[i1] );
  380.                 if ( ph_open[i2] )
  381.                         _close( phdls[i2] );
  382.  
  383.         error1: /* return NULL to the caller indicating failure
  384.                  */
  385.                 return( NULL );
  386. }
  387.  
  388. #ifndef _UNICODE
  389.  
  390. /***
  391. *int _pclose(pstream) - wait on a child command and close the stream on the
  392. *   associated pipe
  393. *
  394. *Purpose:
  395. *       Closes pstream then waits on the associated child command. The
  396. *       argument, pstream, must be the return value from a previous call to
  397. *       _popen. _pclose first looks up the process handle of child command
  398. *       started by that _popen and does a cwait on it. Then, it closes pstream
  399. *       and returns the exit status of the child command to the caller.
  400. *
  401. *Entry:
  402. *       FILE *pstream - file stream returned by a previous call to _popen
  403. *
  404. *Exit:
  405. *       If successful, _pclose returns the exit status of the child command.
  406. *       The format of the return value is that same as for cwait, except that
  407. *       the low order and high order bytes are swapped.
  408. *
  409. *       If an error occurs, -1 is returned.
  410. *
  411. *Exceptions:
  412. *
  413. *******************************************************************************/
  414.  
  415. int __cdecl _pclose (
  416.         FILE *pstream
  417.         )
  418. {
  419.         IDpair *locidpair;        /* pointer to entry in idpairs table */
  420.         int termstat;             /* termination status word */
  421.         int retval = -1;          /* return value (to caller) */
  422.  
  423.         /* MULTI-THREAD: LOCK IDPAIRS HERE!!!!
  424.          */
  425.         _mlock(_POPEN_LOCK);
  426.  
  427.         if ( (pstream == NULL) || ((locidpair = idtab(pstream)) == NULL) )
  428.                 /* invalid pstream, exit with retval == -1
  429.                  */
  430.                 goto done;
  431.  
  432.         /* close pstream
  433.          */
  434.         (void)fclose(pstream);
  435.  
  436.         /* wait on the child (copy of the command processor) and all of its
  437.          * children.
  438.          */
  439.         if ( (_cwait(&termstat, locidpair->prochnd, _WAIT_GRANDCHILD) != -1) ||
  440.              (errno == EINTR) )
  441.                 retval = termstat;
  442.  
  443.         /* Mark the IDpairtable entry as free (note: prochnd was closed by the
  444.          * preceding call to _cwait).
  445.          */
  446.         locidpair->stream = NULL;
  447.         locidpair->prochnd = 0;
  448.  
  449.         /* only return path!
  450.          */
  451.         done:
  452.                 /* MULTI-THREAD: RELEASE LOCK ON IDPAIRS HERE!!!!
  453.                  */
  454.                 _munlock(_POPEN_LOCK);
  455.                 return(retval);
  456. }
  457.  
  458. #endif  /* _UNICODE */
  459.  
  460. /***
  461. * static IDpair * idtab(FILE *pstream) - find an idpairs table entry
  462. *
  463. *Purpose:
  464. *   Find an entry in the idpairs table.  This function finds the entry the
  465. *   idpairs table entry corresponding to pstream. In the case where pstream
  466. *   is NULL, the entry being searched for is any free entry. In this case,
  467. *   idtab will create the idpairs table if it doesn't exist, or expand it (by
  468. *   exactly one entry) if there are no free entries.
  469. *
  470. *   [MTHREAD NOTE:  This routine assumes that the caller has acquired the
  471. *   idpairs table lock.]
  472. *
  473. *Entry:
  474. *   FILE *pstream - stream corresponding to table entry to be found (if NULL
  475. *                   then find any free table entry)
  476. *
  477. *Exit:
  478. *   if successful, returns a pointer to the idpairs table entry. otherwise,
  479. *   returns NULL.
  480. *
  481. *Exceptions:
  482. *
  483. *******************************************************************************/
  484.  
  485. static IDpair * __cdecl idtab (
  486.         FILE *pstream
  487.         )
  488. {
  489.  
  490.         IDpair * pairptr;       /* ptr to entry */
  491.         IDpair * newptr;        /* ptr to newly malloc'd memory */
  492.  
  493.  
  494.         /* search the table. if table is empty, appropriate action should
  495.          * fall out automatically.
  496.          */
  497.         for ( pairptr = __idpairs ; pairptr < (__idpairs+__idtabsiz) ; pairptr++ )
  498.                 if ( pairptr->stream == pstream )
  499.                         break;
  500.  
  501.         /* if we found an entry, return it.
  502.          */
  503.         if ( pairptr < (__idpairs + __idtabsiz) )
  504.                 return(pairptr);
  505.  
  506.         /* did not find an entry in the table.  if pstream was NULL, then try
  507.          * creating/expanding the table. otherwise, return NULL. note that
  508.          * when the table is created or expanded, exactly one new entry is
  509.          * produced. this must not be changed unless code is added to mark
  510.          * the extra entries as being free (i.e., set their stream fields to
  511.          * to NULL).
  512.          */
  513.         if ( (pstream != NULL) || ((newptr = (IDpair *)_realloc_crt((void *)__idpairs,
  514.              (__idtabsiz + 1)*sizeof(IDpair))) == NULL) )
  515.                 /* either pstream was non-NULL or the attempt to create/expand
  516.                  * the table failed. in either case, return a NULL to indicate
  517.                  * failure.
  518.                  */
  519.                 return( NULL );
  520.  
  521.         __idpairs = newptr;             /* new table ptr */
  522.         pairptr = newptr + __idtabsiz;  /* first new entry */
  523.         __idtabsiz++;                   /* new table size */
  524.  
  525.         return( pairptr );
  526.  
  527. }
  528.  
  529.  
  530.