1 /*
2 * w32pipe: win32 clone of npopen.c--utilizes native pipes for hosts
3 * and shells that can handle same, else falls back on temp files
4 * (but uses a much different algorithm than that of npopen.c).
5 *
6 * Background
7 * ==========
8 * The techniques used in native_npclose() and native_inout_popen() are
9 * derived from much trial and error and support "pipe" I/O in both a
10 * console and GUI environment. You may _think_ you have a better way of
11 * effecting the functionality provided in this module and that may well be
12 * the case. But be sure you test your new code with at least these
13 * versions of Win32:
14 *
15 * win95, win98, OSR2, NT 4.0, win2K
16 *
17 * For each HOST, be sure to test read pipes, write pipes, and filters (and
18 * test repeatedly within the same vile session).
19 *
20 *
21 * Acknowledgments
22 * ===============
23 * Until I read Steve Kirkendall's code for the Win32 version of elvis, I
24 * did not realize that attempting to redirect stdin to a device is a
25 * _not_ a good strategy.
26 *
27 *
28 * Caveats
29 * =======
30 * -- The MSDN Knowledge Base has example code that uses anonymous pipes
31 * to redirect a spawned process's stdin, stdout, and stderr. Don't go
32 * there.
33 *
34 * -- On a Win2K host, DuplicateHandle() failed.
35 *
36 * -- The original Win95 console shell (command.com) accesses the floppy
37 * drive each and every time a process communicates with it via a pipe
38 * and the OSR2 shell abruptly hangs under similar conditions. By
39 * default, then, on a WinNT host, vile's pipes are implemented using
40 * native pipes (i.e., with the code in this module), while Win95 hosts
41 * fall back to temp file communication. If the user's replacement
42 * Win95 shell does not exhibit communication problems similar to
43 * those described above (e.g., any 32-bit shell), vile may be
44 * forced to use native Win32 pipes by setting the global mode
45 * "w32pipes" (e.g., "se w32pipes"). For further details, including
46 * a description of "force-console" mode, refer to the file
47 * doc/w32modes.doc .
48 *
49 * -- This module's native pipes implementation exhibits various problems
50 * when a 16-bit console app is exec'd. On a win95 host, the editor
51 * and shell generally hang. WinNT does better, but winvile creates
52 * "background" shell windows that require manual closure.
53 *
54 * -- This module configures read pipes so that the exec'd app reads
55 * it's input from an empty file. That's a necessity, not a bug.
56 * Consequently, if an attempt is made to read data from an app
57 * that itself reads input (why would you do that?), the app will
58 * appear to hang if it reopens stdin on the console (because vile's
59 * stdin is not available to the app--another necessity). In this
60 * situation, kill the app by typing ^C (and then please apply for a
61 * QA position with a certain Redmond company).
62 *
63 * $Id: w32pipe.c,v 1.41 2018/11/05 00:50:01 tom Exp $
64 */
65
66 #define HAVE_FCNTL_H 1
67
68 #include "estruct.h"
69 #include "edef.h"
70
71 #include <io.h>
72 #ifdef __BORLANDC__
73 #define __MSC
74 #include <sys/stat.h>
75 #endif
76 #include <share.h>
77 #include <process.h>
78 #include <ctype.h>
79
80 #define BAD_FD (-1)
81 #define BAD_PROC_HANDLE (INVALID_HANDLE_VALUE)
82 #define PIPESIZ (4096)
83 #define SHELL_ERR_MSG \
84 "[shell process \"%s\" failed, check $shell state var]"
85
86 static int native_inout_popen(FILE **fr, FILE **fw, char *cmd);
87 static void native_npclose(FILE *fp);
88 static int tmp_inout_popen(FILE **fr, FILE **fw, char *cmd);
89 static void tmp_npclose(FILE *fp);
90
91 static HANDLE proc_handle;
92 static char *tmpin_name;
93
94 /* ---------------- Cloned from npopen.c ---------------------------- */
95
96 FILE *
npopen(char * cmd,const char * type)97 npopen(char *cmd, const char *type)
98 {
99 FILE *ff = 0;
100
101 if (*type == 'r')
102 (void) inout_popen(&ff, (FILE **) 0, cmd);
103 else if (*type == 'w')
104 (void) inout_popen((FILE **) 0, &ff, cmd);
105 return ff;
106 }
107
108 int
softfork(void)109 softfork(void) /* dummy function to make filter-region work */
110 {
111 return 0;
112 }
113
114 int
inout_popen(FILE ** fr,FILE ** fw,char * cmd)115 inout_popen(FILE **fr, FILE **fw, char *cmd)
116 {
117 TRACE(("inout_popen(fr=%p, fw=%p, cmd='%s')\n", fr, fw, cmd));
118
119 ffstatus = file_is_pipe;
120 fileeof = FALSE;
121 append_libdir_to_path();
122 #ifdef GMDW32PIPES
123 if (global_g_val(GMDW32PIPES))
124 return (native_inout_popen(fr, fw, cmd));
125 #endif
126 return (tmp_inout_popen(fr, fw, cmd));
127 }
128
129 void
npclose(FILE * fp)130 npclose(FILE *fp)
131 {
132 #ifdef GMDW32PIPES
133 if (global_g_val(GMDW32PIPES)) {
134 native_npclose(fp);
135 return;
136 }
137 #endif
138 tmp_npclose(fp);
139 }
140
141 /* ------------------- native pipe routines first ------------------- */
142
143 /*
144 * when desperate to communicate an error, enable popup messages and
145 * use mlforce().
146 */
147 static void
lastditch_msg(char * msg)148 lastditch_msg(char *msg)
149 {
150 #if OPT_POPUP_MSGS
151 int save = global_g_val(GMDPOPUP_MSGS);
152
153 set_global_g_val(GMDPOPUP_MSGS, TRUE);
154 #endif
155 mlforce(msg);
156 #if OPT_POPUP_MSGS
157 update(FALSE);
158 popup_msgs();
159 update(FALSE);
160 set_global_g_val(GMDPOPUP_MSGS, save);
161 #endif
162 }
163
164 static void
common_cleanup(void)165 common_cleanup(void)
166 {
167 if (tmpin_name) {
168 (void) remove(tmpin_name);
169 (void) free(tmpin_name);
170 tmpin_name = NULL;
171 }
172 restore_console_title();
173 }
174
175 #define close_fd(fd) if (fd != BAD_FD) { close(fd); fd = BAD_FD; }
176
177 static void
close_proc_handle(void)178 close_proc_handle(void)
179 {
180 TRACE(("close_proc_handle %p\n", proc_handle));
181 (void) w32_close_handle(proc_handle);
182 proc_handle = BAD_PROC_HANDLE;
183 }
184
185 static HANDLE
exec_shell(char * cmd,HANDLE * handles,int hide_child GCC_UNUSED)186 exec_shell(char *cmd, HANDLE * handles, int hide_child GCC_UNUSED)
187 {
188 W32_CHAR *w32_cmdstr = 0;
189 char *cmdstr;
190 int freestr;
191 PROCESS_INFORMATION pi;
192 STARTUPINFO si;
193
194 proc_handle = BAD_PROC_HANDLE; /* in case of failure */
195 TRACE((T_CALLED "exec_shell %s\n", cmd));
196 if ((cmdstr = mk_shell_cmd_str(cmd, &freestr, TRUE)) == NULL) {
197 /* heap exhausted! */
198
199 no_memory("exec_shell");
200
201 /* Give user a chance to read message--more will surely follow. */
202 Sleep(3000);
203 } else if ((w32_cmdstr = w32_charstring(cmdstr)) == 0) {
204 no_memory("exec_shell");
205 } else {
206
207 memset(&si, 0, sizeof(si));
208 /* *INDENT-EQLS* */
209 si.cb = sizeof(si);
210 si.dwFlags = STARTF_USESTDHANDLES;
211 si.hStdInput = handles[0];
212 si.hStdOutput = handles[1];
213 si.hStdError = handles[2];
214 #if DISP_NTWIN
215 if (hide_child) {
216 si.dwFlags |= STARTF_USESHOWWINDOW;
217 si.wShowWindow = SW_HIDE;
218 }
219 #endif
220 TRACE(("CreateProcess %s (pipe)\n", cmdstr));
221 if (CreateProcess(NULL,
222 w32_cmdstr,
223 NULL,
224 NULL,
225 TRUE, /* Inherit handles */
226 0,
227 NULL,
228 NULL,
229 &si,
230 &pi)) {
231 /* Success */
232
233 w32_close_handle(pi.hThread);
234 proc_handle = pi.hProcess;
235 TRACE(("...created proc_handle %p\n", proc_handle));
236 }
237 }
238 FreeIfNeeded(cmdstr);
239 FreeIfNeeded(w32_cmdstr);
240 returnPtr(proc_handle);
241 }
242
243 static HANDLE
get_handle(int fd)244 get_handle(int fd)
245 {
246 HANDLE result;
247 result = (HANDLE) _get_osfhandle(fd);
248 return result;
249 }
250
251 static int
native_inout_popen(FILE ** fr,FILE ** fw,char * cmd)252 native_inout_popen(FILE **fr, FILE **fw, char *cmd)
253 {
254 char buf[NFILEN + 128];
255 HANDLE handles[3];
256 int i, rc, rp[3], tmpin_fd, wp[3];
257
258 TRACE((T_CALLED "native_inout_popen cmd=%s\n", cmd));
259
260 /* *INDENT-EQLS* */
261 proc_handle = BAD_PROC_HANDLE;
262 rp[0] = rp[1] = rp[2] = wp[0] = wp[1] = wp[2] = BAD_FD;
263 handles[0] = handles[1] = handles[2] = INVALID_HANDLE_VALUE;
264 tmpin_fd = BAD_FD;
265 tmpin_name = NULL;
266 set_console_title(cmd);
267
268 if (is_win95()) {
269 char *cmdp;
270
271 /*
272 * If w32pipes is set on a win95 host, you don't ever want slowreadf()
273 * to periodically update the display while an intrinisic, high
274 * bandwidth DOS command is in progress. Reason: the win95 shell,
275 * command.com, will simply hang in the following scenario:
276 *
277 * ^X!<high_bandwidth_cmd>
278 *
279 * and
280 *
281 * PIPESIZ < output of <high_bandwidth_cmd>
282 *
283 * I'm assuming that what's going on here is that command.com is
284 * written in ASM and is using very low level BIOS/DOS calls to
285 * effect output and, furthermore, that these low level calls don't
286 * block when the input consumer is busy.
287 */
288
289 cmdp = skip_blanks(cmd);
290 nowait_pipe_cmd = (strnicmp(cmdp, "dir", 3) == 0) ||
291 (strnicmp(cmdp, "type", 4) == 0);
292 }
293 do {
294 if (fr) {
295 *fr = NULL;
296
297 /*
298 * Open (parent's) input pipe in TEXT mode, which will force
299 * translation of the child's CR/LF record delimiters to NL
300 * and keep the dreaded ^M chars from temporarily appearing
301 * in a vile buffer (ugly).
302 */
303 if (_pipe(rp, PIPESIZ, O_TEXT | O_NOINHERIT) == -1)
304 break;
305 if ((rp[2] = _dup(rp[1])) == -1)
306 break;
307 handles[2] = handles[1] = get_handle(rp[2]);
308 (void) close(rp[1]);
309 rp[1] = BAD_FD;
310 if (!fw) {
311 /*
312 * This is a read pipe (only). Connect child's stdin to
313 * an empty file. Under no circumstances should the
314 * child's stdin be connected to a device (else lots of
315 * screwy things will occur). In particular, connecting
316 * the child's stdin to the parent's stdin will cause
317 * aborts and hangs on the various Win32 hosts. You've
318 * been warned.
319 */
320
321 if ((tmpin_name = _tempnam(getenv("TEMP"), "vile")) == NULL)
322 break;
323 if ((tmpin_fd = open(tmpin_name,
324 O_RDONLY | O_CREAT | O_TRUNC,
325 _S_IWRITE | _S_IREAD)) == BAD_FD) {
326 break;
327 }
328 handles[0] = get_handle(tmpin_fd);
329 }
330 if ((*fr = fdopen(rp[0], "r")) == 0)
331 break;
332 }
333 if (fw) {
334 *fw = NULL;
335
336 /*
337 * Open (child's) output pipe in binary mode, which will
338 * prevent translation of the parent's CR/LF record delimiters
339 * to NL. Apparently, many apps want those delimiters :-) .
340 */
341 if (_pipe(wp, PIPESIZ, O_BINARY | O_NOINHERIT) == -1)
342 break;
343 if ((wp[2] = _dup(wp[0])) == -1)
344 break;
345 handles[0] = get_handle(wp[2]);
346 (void) close(wp[0]);
347 wp[0] = BAD_FD;
348 if (!fr)
349 handles[1] = handles[2] = GetStdHandle(STD_OUTPUT_HANDLE);
350 if ((*fw = fdopen(wp[1], "w")) == 0)
351 break;
352 }
353 rc = (exec_shell(cmd,
354 handles,
355 fr != NULL /* Child wdw hidden unless write pipe. */
356 ) == BAD_PROC_HANDLE) ? FALSE : TRUE;
357 if (fw) {
358 if (!rc) {
359 /* Shell process failed, put complaint in user's face. */
360
361 sprintf(buf, SHELL_ERR_MSG, get_shell());
362 lastditch_msg(buf);
363 }
364 w32_close_handle(handles[0]);
365 }
366 if (fr) {
367 if (!rc) {
368 unsigned len;
369
370 /*
371 * Shell process failed, put complaint in user's buffer.
372 * Can't write to handles[1] on a win2k host because the
373 * previously failed CreateProcess() call damaged the
374 * handle.
375 */
376 len = (unsigned) (lsprintf(buf,
377 SHELL_ERR_MSG,
378 get_shell()) - buf);
379 (void) write(rp[2], buf, len);
380 (void) close(rp[2]); /* in weird state; why not? */
381 }
382 w32_close_handle(handles[1]);
383 close_fd(tmpin_fd);
384 }
385 returnCode(rc);
386 }
387 while (FALSE);
388
389 /* If we get here -- some operation has failed. Clean up. */
390
391 close_fd(wp[0]);
392 close_fd(wp[1]);
393 close_fd(wp[2]);
394 close_fd(rp[0]);
395 close_fd(rp[1]);
396 close_fd(rp[2]);
397 close_fd(tmpin_fd);
398 for (i = 0; i < 3; i++) {
399 if (handles[i] != INVALID_HANDLE_VALUE)
400 w32_close_handle(handles[i]);
401 }
402 common_cleanup();
403 returnCode(FALSE);
404 }
405
406 static void
native_npclose(FILE * fp)407 native_npclose(FILE *fp)
408 {
409 int term_status;
410
411 (void) fflush(fp);
412 (void) fclose(fp);
413 if (proc_handle != BAD_PROC_HANDLE) {
414 (void) cwait(&term_status, (CWAIT_PARAM_TYPE) proc_handle, 0);
415 TRACE(("...CreateProcess finished waiting in native_npclose\n"));
416 close_proc_handle();
417 }
418 common_cleanup();
419 }
420
421 /* ------------------- tmp file pipe routines last ------------------
422 * -- --
423 * -- The key to making these routines work is recognizing that --
424 * -- under windows it's necessary to wait for the writer process --
425 * -- to finish and close tmp file A before the reader process --
426 * -- reads from A. --
427 * ------------------------------------------------------------------
428 */
429
430 static HANDLE handles[3];
431 static int stdin_fd, stdout_fd;
432 static char *stdin_name, *stdout_name, *shcmd;
433
434 static void
tmp_cleanup(void)435 tmp_cleanup(void)
436 {
437 close_fd(stdin_fd);
438 close_fd(stdout_fd);
439
440 if (stdin_name) {
441 (void) remove(stdin_name);
442 (void) free(stdin_name);
443 stdin_name = NULL;
444 }
445 if (stdout_name) {
446 (void) remove(stdout_name);
447 (void) free(stdout_name);
448 stdout_name = NULL;
449 }
450 common_cleanup();
451 }
452
453 static int
tmp_inout_popen(FILE ** fr,FILE ** fw,char * cmd)454 tmp_inout_popen(FILE **fr, FILE **fw, char *cmd)
455 {
456 char buf[NFILEN + 128];
457 DWORD dummy, len;
458 int rc, term_status, tmpin_fd;
459
460 TRACE(("tmp_inout_popen cmd=%s\n", cmd));
461
462 /* *INDENT-EQLS* */
463 proc_handle = BAD_PROC_HANDLE;
464 handles[0] = handles[1] = handles[2] = INVALID_HANDLE_VALUE;
465 tmpin_fd = stdin_fd = stdout_fd = BAD_FD;
466 tmpin_name = stdin_name = stdout_name = NULL;
467 set_console_title(cmd);
468
469 do {
470 if (fr) {
471 *fr = NULL;
472 if ((stdin_name = _tempnam(getenv("TEMP"), "vile")) == NULL)
473 break;
474 if ((stdin_fd = open(stdin_name,
475 O_RDWR | O_CREAT | O_TRUNC | O_TEXT,
476 _S_IWRITE | _S_IREAD)) == BAD_FD) {
477 break;
478 }
479 handles[2] = handles[1] = get_handle(stdin_fd);
480 if (!fw) {
481 /*
482 * This is a read pipe (only). Connect child's stdin to
483 * an empty file. Under no circumstances should the
484 * child's stdin be connected to a device (else lots of
485 * screwy things will occur). In particular, connecting
486 * the child's stdin to the parent's stdin will cause
487 * aborts and hangs on the various Win32 hosts. You've
488 * been warned.
489 */
490
491 if ((tmpin_name = _tempnam(getenv("TEMP"), "vile")) == NULL)
492 break;
493 if ((tmpin_fd = open(tmpin_name,
494 O_RDONLY | O_CREAT | O_TRUNC,
495 _S_IWRITE | _S_IREAD)) == BAD_FD) {
496 break;
497 }
498 handles[0] = get_handle(tmpin_fd);
499 } else {
500 /*
501 * Set up descriptor for filter operation. Note the
502 * subtleties here: exec'd shell is passed a descriptor
503 * to the temp file that's opened "w". The editor
504 * receives a descriptor to the file that's opened "r".
505 */
506
507 if ((*fr = fopen(stdin_name, "r")) == NULL)
508 break;
509 }
510 }
511 if (fw) {
512 *fw = NULL;
513
514 /* create a temp file to receive data from the editor */
515 if ((stdout_name = _tempnam(getenv("TEMP"), "vile")) == NULL)
516 break;
517 if ((stdout_fd = open(stdout_name,
518 O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
519 _S_IWRITE | _S_IREAD)) == BAD_FD) {
520 break;
521 }
522 if ((*fw = fdopen(stdout_fd, "w")) == 0)
523 break;
524
525 /*
526 * we're all set up, but can't exec "cmd" until the editor
527 * writes data to the temp file connected to stdout.
528 */
529 shcmd = cmd; /* remember this */
530 return (TRUE);
531 }
532
533 /* This must be a read (only) pipe. Appropriate to exec "cmd". */
534 rc = (exec_shell(cmd,
535 handles,
536 TRUE /* hide child wdw */
537 ) == BAD_PROC_HANDLE) ? FALSE : TRUE;
538
539 if (!rc) {
540 /*
541 * Shell process failed, put complaint in user's buffer, which
542 * is currently proxied by a temp file that the editor will
543 * suck in shortly.
544 */
545 len = (DWORD) (lsprintf(buf, SHELL_ERR_MSG, get_shell()) - buf);
546 (void) WriteFile(handles[1], buf, len, &dummy, NULL);
547 FlushFileBuffers(handles[1]);
548 } else {
549 /* wait for exec'd process to exit */
550
551 (void) cwait(&term_status, (CWAIT_PARAM_TYPE) proc_handle, 0);
552 TRACE(("...CreateProcess finished waiting in tmp_inout_popen\n"));
553 close_proc_handle();
554 }
555
556 if (fr) {
557 /*
558 * When closing descriptors shared between parent and child, order
559 * is quite important when $shell == command.com . In this
560 * situation, the descriptors can't be closed until the exec'd
561 * process exits (I kid you not).
562 */
563 close_fd(stdin_fd);
564 (void) close(tmpin_fd);
565
566 /* let the editor consume the output of the read pipe */
567 if ((*fr = fopen(stdin_name, "r")) == NULL) {
568 /*
569 * impossible to put error in user's buffer since that file
570 * descriptor is closed.
571 */
572 sprintf(buf,
573 "[error opening temp file \"%s\": %s]",
574 stdin_name,
575 strerror(errno));
576 lastditch_msg(buf);
577 break;
578 }
579 }
580 return (rc);
581 }
582 while (FALSE);
583
584 /* If we get here -- some operation has failed. Clean up. */
585 tmp_cleanup();
586 return (FALSE);
587 }
588
589 /* npflush is called for filter ops effected with temp files */
590 void
npflush(void)591 npflush(void)
592 {
593 char buf[NFILEN + 128];
594 int rc, term_status;
595
596 /*
597 * caller has filled and closed the write pipe data stream. time to
598 * exec a process.
599 */
600
601 if ((stdout_fd = open(stdout_name, O_RDONLY | O_BINARY)) == BAD_FD) {
602 /* oh my, put complaint in user's face. */
603
604 sprintf(buf, "[unable to open temp file \"%s\": %s]",
605 stdout_name,
606 strerror(errno));
607 lastditch_msg(buf);
608 } else {
609 /* handles[1-2] were initialized by tmp_npopen_open() */
610
611 handles[0] = get_handle(stdout_fd);
612 rc = (exec_shell(shcmd,
613 handles,
614 TRUE /* do hide child window */
615 ) == BAD_PROC_HANDLE) ? FALSE : TRUE;
616 if (!rc) {
617 /* Shell process failed, put complaint in user's face. */
618
619 sprintf(buf, SHELL_ERR_MSG, get_shell());
620 lastditch_msg(buf);
621 } else {
622 /* now wait for app to exit */
623
624 (void) cwait(&term_status, (CWAIT_PARAM_TYPE) proc_handle, 0);
625 TRACE(("...CreateProcess finished waiting in npflush\n"));
626 close_proc_handle();
627 }
628
629 /*
630 * When closing descriptors shared between parent and child, order
631 * is quite important when $shell == command.com . In this
632 * situation, the descriptors can't be closed until the exec'd
633 * process exits.
634 */
635 close_fd(stdout_fd);
636 }
637 }
638
639 static void
tmp_npclose(FILE * fp)640 tmp_npclose(FILE *fp)
641 {
642 char buf[NFILEN + 128];
643 int rc, term_status;
644
645 (void) fflush(fp);
646 (void) fclose(fp);
647
648 if (stdout_fd != BAD_FD) {
649 /*
650 * write pipe, but not a filter. Editor has written data to temp
651 * file, time now to exec "cmd" and hook its stdin to the file.
652 *
653 * It should be noted that exec'ing a process in the npclose()
654 * phase of a write pipe is not exactly keeping in spirit with
655 * the control flow in file.c :-) . However, the strategy used
656 * here ensures that the launched process reads a temp file that
657 * is completey flushed to disk. The only direct drawback with
658 * this approach is that when the exec'd process exits, the user
659 * does not receive a "[press return to continue]" prompt from
660 * file.c . But, cough, we can work around that problem :-) .
661 */
662
663 if ((stdout_fd = open(stdout_name, O_RDONLY | O_BINARY)) == BAD_FD) {
664 /* oh my, put complaint in user's face. */
665
666 sprintf(buf, "[unable to open temp file \"%s\": %s]",
667 stdout_name,
668 strerror(errno));
669 lastditch_msg(buf);
670 } else {
671 handles[0] = get_handle(stdout_fd);
672 handles[1] = handles[2] = GetStdHandle(STD_OUTPUT_HANDLE);
673 rc = (exec_shell(shcmd,
674 handles,
675 FALSE /* don't hide child window */
676 ) == BAD_PROC_HANDLE) ? FALSE : TRUE;
677 if (!rc) {
678 /* Shell process failed, put complaint in user's face. */
679
680 sprintf(buf, SHELL_ERR_MSG, get_shell());
681 lastditch_msg(buf);
682 } else {
683 /* now wait for app to exit */
684
685 (void) cwait(&term_status, (CWAIT_PARAM_TYPE) proc_handle, 0);
686 TRACE(("...CreateProcess finished waiting in tmp_npclose\n"));
687 close_proc_handle();
688 }
689
690 /*
691 * When closing descriptors shared between parent and child,
692 * order is quite important when $shell == command.com . In
693 * this situation, the descriptors can't be closed until the
694 * exec'd process exits.
695 */
696 close_fd(stdout_fd);
697 }
698 pressreturn(); /* cough */
699 sgarbf = TRUE;
700 }
701 tmp_cleanup();
702 }
703