home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / vile-src.zip / vile-8.1 / w32pipe.c < prev    next >
C/C++ Source or Header  |  1998-09-22  |  12KB  |  376 lines

  1. /*
  2.  * w32pipe:  win32 clone of npopen.c, utilizes native pipes (not temp files).
  3.  * Written by Clark Morgan for vile (december 1997).
  4.  *
  5.  * Background
  6.  * ==========
  7.  * The techniques used in w32_npclose() and w32_inout_popen() are derived
  8.  * from much trial and error and support "pipe" I/O in both a console and
  9.  * GUI environment.  You may _think_ you have a better way of effecting the
  10.  * functionality provided in this module and that may well be the case.
  11.  * But be sure you test your new code with at least these versions of Win32:
  12.  *
  13.  *      win95 (original version), OSR2, NT 4.0
  14.  *
  15.  * For each HOST, be sure to test read pipes, write pipes, and filters (and
  16.  * test repeatedly within the same vile session).
  17.  *
  18.  *
  19.  * Acknowledgments
  20.  * ===============
  21.  * Until I read Steve Kirkendall's code for the Win32 version of elvis, I
  22.  * did not realize that attempting to redirect stdin to a device is a
  23.  * _not_ a good strategy.
  24.  *
  25.  *
  26.  * Caveats
  27.  * =======
  28.  *
  29.  * -- This code has not been tested with NT 3.51 .
  30.  *
  31.  * -- The MSDN Knowledge Base has example code that uses anonymous pipes
  32.  *    to redirect a spawned process's stdin, stdout, and stderr.  Don't go
  33.  *    there.
  34.  *
  35.  * -- The original Win95 console shell (command.com) accesses the floppy
  36.  *    drive each and every time a process communicates with it via a pipe
  37.  *    and the OS R2 shell abruptly hangs under similar conditions.  By
  38.  *    default, then, on a WinNT host, vile's pipes are implemented using
  39.  *    native pipes (i.e., with the code in this module), while Win95 hosts
  40.  *    fall back to temp file communication.  If the user's replacement
  41.  *    Win95 shell does not exhibit communication problems similar to
  42.  *    those described above (e.g., Thompson Toolkit Shell), vile may be
  43.  *    forced to use native Win32 pipes by setting the global mode
  44.  *    "w32pipes" (e.g., "se w32pipes").  For further details, including
  45.  *    a description of "force-console" mode, refer to the file
  46.  *    doc/w32modes.doc .
  47.  *
  48.  * -- This module's native pipes implementation exhibits various problems
  49.  *    when a 16-bit console app is exec'd.  On a win95 host, the editor
  50.  *    and shell generally hang.  WinNT does better, but winvile creates
  51.  *    "background" shell windows that require manual closure.
  52.  *
  53.  * -- This module configures read pipes so that the exec'd app reads
  54.  *    it's input from an empty file.  That's a necessity, not a bug.
  55.  *    Consequently, if an attempt is made to read data from an app
  56.  *    that itself reads input (why would you do that?), the app will
  57.  *    appear to hang if it reopens stdin on the console (because vile's
  58.  *    stdin is not available to the app--another necessity).  In this
  59.  *    situation, kill the app by typing ^C (and then please apply for a
  60.  *    QA position with a certain Redmond company).
  61.  *
  62.  * $Header: /usr/build/vile/vile/RCS/w32pipe.c,v 1.15 1998/09/23 00:13:34 tom Exp $
  63.  */
  64.  
  65. #include <windows.h>
  66. #include <io.h>
  67. #ifdef __BORLANDC__
  68. #define __MSC
  69. #include <sys/stat.h>
  70. #endif
  71. #include <share.h>
  72. #include <process.h>
  73. #include <assert.h>
  74. #include <ctype.h>
  75.  
  76. #define HAVE_FCNTL_H 1
  77.  
  78. #include "estruct.h"
  79. #include "edef.h"
  80.  
  81. #define BAD_FD          (-1)
  82. #define BAD_PROC_HANDLE (INVALID_HANDLE_VALUE)
  83. #define PIPESIZ         (4096)
  84. #define SHELL_ERR_MSG   \
  85.           "error: shell process \"%s\" failed, check COMSPEC env var\n"
  86.  
  87. static HANDLE proc_handle;
  88. static char   *tmpin_name;
  89.  
  90. /* ------------------------------------------------------------------ */
  91.  
  92. static void
  93. global_cleanup(void)
  94. {
  95.     if (tmpin_name)
  96.     {
  97.         (void) remove(tmpin_name);
  98.         (void) free(tmpin_name);
  99.         tmpin_name = NULL;
  100.     }
  101.     restore_console_title();
  102. #if DISP_NTWIN
  103.     if (global_g_val(GMDFORCE_CONSOLE))
  104.         FreeConsole();
  105. #endif
  106. }
  107.  
  108.  
  109.  
  110. static HANDLE
  111. exec_shell(char *cmd, HANDLE *handles, int hide_child)
  112. {
  113.     char                 *cmdstr;
  114. #if DISP_NTWIN
  115.     HWND                 fgnd;
  116.     int                  force_console = global_g_val(GMDFORCE_CONSOLE);
  117. #endif
  118.     int                  freestr;
  119.     PROCESS_INFORMATION  pi;
  120.     STARTUPINFO          si;
  121.  
  122.     proc_handle = BAD_PROC_HANDLE;  /* in case of failure */
  123.     if ((cmdstr = mk_shell_cmd_str(cmd, &freestr, TRUE)) == NULL)
  124.     {
  125.         /* heap exhausted! */
  126.  
  127.         mlforce("insufficient memory to invoke shell");
  128.  
  129.         /* Give user a chance to read message--more will surely follow. */
  130.         Sleep(3000);
  131.         return (proc_handle);
  132.     }
  133.  
  134.     memset(&si, 0, sizeof(si));
  135.     si.cb          = sizeof(si);
  136.     si.dwFlags     = STARTF_USESTDHANDLES;
  137.     si.hStdInput   = handles[0];
  138.     si.hStdOutput  = handles[1];
  139.     si.hStdError   = handles[2];
  140. #if DISP_NTWIN
  141.     if (force_console)
  142.     {
  143.         if (hide_child)
  144.             fgnd = GetForegroundWindow();
  145.         AllocConsole();
  146.     }
  147.     else if (hide_child)
  148.     {
  149.         si.dwFlags     |= STARTF_USESHOWWINDOW;
  150.         si.wShowWindow  = SW_HIDE;
  151.     }
  152. #endif
  153.     if (CreateProcess(NULL,
  154.                       cmdstr,
  155.                       NULL,
  156.                       NULL,
  157.                       TRUE,       /* Inherit handles */
  158. #if DISP_NTWIN
  159.                       force_console ? 0 : CREATE_NEW_CONSOLE,
  160. #else
  161.                       0,
  162. #endif
  163.                       NULL,
  164.                       NULL,
  165.                       &si,
  166.                       &pi))
  167.     {
  168.         /* Success */
  169.  
  170. #if DISP_NTWIN
  171.         if (force_console && hide_child)
  172.             SetForegroundWindow(fgnd);
  173. #endif
  174.         CloseHandle(pi.hThread);
  175.         proc_handle = pi.hProcess;
  176.     }
  177.     if (freestr)
  178.         free(cmdstr);
  179.     return (proc_handle);
  180. }
  181.  
  182.  
  183.  
  184. int
  185. w32_inout_popen(FILE **fr, FILE **fw, char *cmd)
  186. {
  187.     HANDLE handles[3];
  188.     int    i, rc, rp[2], tmpin_fd, wp[2];
  189.  
  190.     proc_handle  = BAD_PROC_HANDLE;
  191.     rp[0]        = rp[1]      = wp[0]      = wp[1] = BAD_FD;
  192.     handles[0]   = handles[1] = handles[2] = INVALID_HANDLE_VALUE;
  193.     tmpin_fd     = BAD_FD;
  194.     tmpin_name   = NULL;
  195.     set_console_title(cmd);
  196.     if (is_win95())
  197.     {
  198.         char *cmdp;
  199.  
  200.         /*
  201.          * If w32pipes is set on a win95 host, you don't ever want slowreadf()
  202.          * to periodically update the display while an intrinisic, high
  203.          * bandwidth DOS command is in progress.  Reason:  the win95 shell,
  204.          * command.com, will simply hang in the following scenario:
  205.          *
  206.          *    ^X!<high_bandwidth_cmd>
  207.          *
  208.          *         and
  209.          *
  210.          *    PIPESIZ < output of <high_bandwidth_cmd>
  211.          *
  212.          * I'm assuming that what's going on here is that command.com is
  213.          * written in ASM and is using very low level BIOS/DOS calls to
  214.          * effect output and, furthermore, that these low level calls don't
  215.          * block when the input consumer is busy.
  216.          */
  217.  
  218.         for (cmdp = cmd; *cmdp && isspace(*cmdp); cmdp++)
  219.             ;
  220.         nowait_pipe_cmd = (strnicmp(cmdp, "dir", 3) == 0)  ||
  221.                           (strnicmp(cmdp, "type", 4) == 0);
  222.     }
  223.     do
  224.     {
  225.         if (fr)
  226.         {
  227.             *fr = NULL;
  228.  
  229.             /*
  230.              * Open (parent's) input pipe in TEXT mode, which will force
  231.              * translation of the child's CR/LF record delimiters to NL
  232.              * and keep the dreaded ^M chars from temporarily appearing
  233.              * in a vile buffer (ugly).
  234.              */
  235.             if (_pipe(rp, PIPESIZ, O_TEXT|O_NOINHERIT) == -1)
  236.                 break;
  237.             if (! DuplicateHandle(GetCurrentProcess(),
  238.                                   (HANDLE) _get_osfhandle(rp[1]),
  239.                                   GetCurrentProcess(),
  240.                                   handles + 1,
  241.                                   0,
  242.                                   TRUE,
  243.                                   DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE))
  244.             {
  245.                 break;
  246.             }
  247.             handles[2] = handles[1];
  248.             rp[1]      = BAD_FD;   /* closed by DuplicateHandle() */
  249.             if (! fw)
  250.             {
  251.                 /*
  252.                  * This is a read pipe (only).  Connect child's stdin to
  253.                  * an empty file.  Under no circumstances should the
  254.                  * child's stdin be connected to a device (else lots of
  255.                  * screwy things will occur).  In particular, connecting
  256.                  * the child's stdin to the parent's stdin will cause
  257.                  * aborts and hangs on the various Win32 hosts.  You've
  258.                  * been warned.
  259.                  */
  260.  
  261.                 if ((tmpin_name = _tempnam(getenv("TEMP"), "vile")) == NULL)
  262.                     break;
  263.                 if ((tmpin_fd = open(tmpin_name,
  264.                                      O_RDONLY|O_CREAT|O_TRUNC,
  265.                                      _S_IWRITE|_S_IREAD)) == BAD_FD)
  266.                 {
  267.                     break;
  268.                 }
  269.                 handles[0] = (HANDLE) _get_osfhandle(tmpin_fd);
  270.             }
  271.             if (! (*fr = fdopen(rp[0], "r")))
  272.                 break;
  273.         }
  274.         if (fw)
  275.         {
  276.             *fw = NULL;
  277.  
  278.             /*
  279.              * Open (child's) output pipe in binary mode, which will
  280.              * prevent translation of the parent's CR/LF record delimiters
  281.              * to NL.  Apparently, many apps want those delimiters :-) .
  282.              */
  283.             if (_pipe(wp, PIPESIZ, O_BINARY|O_NOINHERIT) == -1)
  284.                 break;
  285.             if (! DuplicateHandle(GetCurrentProcess(),
  286.                                   (HANDLE) _get_osfhandle(wp[0]),
  287.                                   GetCurrentProcess(),
  288.                                   handles + 0,
  289.                                   0,
  290.                                   TRUE,
  291.                                   DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE))
  292.             {
  293.                 break;
  294.             }
  295.             wp[0] = BAD_FD;     /* closed by DuplicateHandle() */
  296.             if (! fr)
  297.                 handles[1] = handles[2] = GetStdHandle(STD_OUTPUT_HANDLE);
  298.             if (! (*fw = fdopen(wp[1], "w")))
  299.                 break;
  300.         }
  301.         rc = (exec_shell(cmd,
  302.                          handles,
  303.                          fr != NULL  /* Child wdw hidden unless write pipe. */
  304.                          ) == BAD_PROC_HANDLE) ? FALSE : TRUE;
  305.         if (fw)
  306.         {
  307.             if (! rc)
  308.             {
  309.                  /* Shell process failed, put complaint in user's face. */
  310.  
  311.                 fputc('\n', stdout);
  312.                 printf(SHELL_ERR_MSG, get_shell());
  313.                 fflush(stdout);
  314.             }
  315.             CloseHandle(handles[0]);
  316.         }
  317.         if (fr)
  318.         {
  319.             if (! rc)
  320.             {
  321.                 char  buf[200];
  322.                 DWORD dummy, len;
  323.  
  324.                 /* Shell process failed, put complaint in user's buffer. */
  325.  
  326.                 len = lsprintf(buf, SHELL_ERR_MSG, get_shell()) - buf;
  327.                 (void) WriteFile(handles[1], buf, len, &dummy, NULL);
  328.                 FlushFileBuffers(handles[1]);
  329.             }
  330.             CloseHandle(handles[1]);
  331.             if (tmpin_fd != BAD_FD)
  332.                 close(tmpin_fd);
  333.         }
  334.         return (rc);
  335.     }
  336.     while (FALSE);
  337.  
  338.     /* If we get here -- some operation has failed.  Clean up. */
  339.  
  340.     if (wp[0] != BAD_FD)
  341.         close(wp[0]);
  342.     if (wp[1] != BAD_FD)
  343.         close(wp[1]);
  344.     if (rp[0] != BAD_FD)
  345.         close(rp[0]);
  346.     if (rp[1] != BAD_FD)
  347.         close(rp[1]);
  348.     if (tmpin_fd != BAD_FD)
  349.         close(tmpin_fd);
  350.     for (i = 0; i < 3; i++)
  351.     {
  352.         if (handles[i] != INVALID_HANDLE_VALUE)
  353.             CloseHandle(handles[i]);
  354.     }
  355.     global_cleanup();
  356.     return (FALSE);
  357. }
  358.  
  359.  
  360.  
  361. void
  362. w32_npclose(FILE *fp)
  363. {
  364.     int term_status;
  365.  
  366.     (void) fflush(fp);
  367.     (void) fclose(fp);
  368.     if (proc_handle != BAD_PROC_HANDLE)
  369.     {
  370.         (void) _cwait(&term_status, (int) proc_handle, 0);
  371.         (void) CloseHandle(proc_handle);
  372.         proc_handle = BAD_PROC_HANDLE;
  373.     }
  374.     global_cleanup();
  375. }
  376.