1 /* Spawn: various DOS access commands
2 * for MicroEMACS
3 *
4 * $Id: spawn.c,v 1.218 2018/10/25 22:50:17 tom Exp $
5 */
6
7 #ifdef _WIN32
8 # include <process.h>
9 #endif
10
11 #include "estruct.h"
12 #include "edef.h"
13 #include "nefunc.h"
14
15 #if SYS_UNIX && defined(SIGTSTP) && !DISP_X11
16 #define USE_UNIX_JOB_CTL 1
17 #else
18 #define USE_UNIX_JOB_CTL 0
19 #endif
20
21 #if SYS_VMS
22 #include <starlet.h>
23 #include <lib$routines.h>
24 extern int vms_system(char *); /*FIXME: not the same as 'system()'? */
25 #endif
26
27 #if OPT_FINDPATH
28
29 typedef struct struct_findinfo {
30 FINDCFG cfg; /* findcfg mode info */
31 const char *dir_list; /* findpath directory list */
32 int nonrecursive; /* this find cmd is nonrecursive */
33 } FINDINFO;
34
35 static char *ck_find_cmd(char *cmd, int *allocd_storage, int prepend_bang);
36 static char *find_all_files(char *cmd, FINDINFO * pinfo, int prepend_bang);
37 static char *find_dirs_only(char *cmd, FINDINFO * pinfo, int prepend_bang);
38
39 static char *prev_findcmd; /* last shell command created by the builtin
40 * find feature. debug aid -- may go away
41 */
42 #endif
43
44 static int spawn1(int rerun, int pressret);
45
46 #if SYS_VMS
47 #define EFN 0 /* Event flag. */
48
49 #include <ssdef.h> /* Random headers. */
50 #include <stsdef.h>
51 #include <descrip.h>
52 #include <iodef.h>
53
54 #if DISP_X11
55 #define VMSVT_DATA /* nothing */
56 #else
57 #define VMSVT_DATA extern
58 #endif
59
60 VMSVT_DATA int oldmode[3];
61 VMSVT_DATA int newmode[3];
62 VMSVT_DATA short iochan;
63 #endif
64
65 #if CC_NEWDOSCC && !CC_DJGPP /* Typo, was NEWSDOSCC */
66 #include <process.h>
67 #endif
68
69 /*
70 * Check all modification-times after executing a shell command
71 */
72 #ifdef MDCHK_MODTIME
73 #define AfterShell() check_visible_files_changed()
74 #else
75 #define AfterShell() TRUE
76 #endif
77
78 #if DISP_X11 && !SMALLER
79 static void
x_window_SHELL(const char * cmd)80 x_window_SHELL(const char *cmd)
81 {
82 TBUFF *tmp = 0;
83
84 #ifdef HAVE_WAITPID
85 int pid;
86
87 if ((pid = fork()) > 0) {
88 waitpid(pid, 0, 0);
89 } else if (pid == 0) {
90 if (fork() == 0) {
91 #endif
92 /*
93 * We would use the -display option of xterm, but that
94 * would get in the way of the user's ability to
95 * customize $xshell.
96 */
97 #ifdef HAVE_PUTENV
98 static char *display_env;
99 char *env = get_xdisplay();
100 if (display_env != 0)
101 free(display_env);
102 if ((display_env = typeallocn(char, 20 + strlen(env))) != 0) {
103 lsprintf(display_env, "DISPLAY=%s", env);
104 putenv(display_env);
105 }
106 #endif
107
108 tmp = tb_scopy(&tmp, get_xshell());
109 if (cmd != 0) {
110 tb_unput(tmp);
111 tmp = tb_sappend(&tmp, " ");
112 tmp = tb_sappend(&tmp, get_xshellflags());
113 tmp = tb_sappend(&tmp, " ");
114 tmp = tb_sappend(&tmp, cmd);
115 tmp = tb_append(&tmp, EOS);
116 }
117 if (tmp != 0) {
118 char *result = tb_values(tmp);
119 TRACE(("executing '%s'\n", result));
120 IGNORE_RC(system(result));
121 tb_free(&tmp);
122 }
123 #ifdef HAVE_WAITPID
124 }
125 free_all_leaks();
126 _exit(0);
127 }
128 #endif
129 }
130 #endif
131
132 /*
133 * Create a subjob with a copy of the command interpreter in it. When the
134 * command interpreter exits, mark the screen as garbage so that you do a full
135 * repaint. The message at the start in VMS puts out a newline.
136 * Under some (unknown) condition, you don't get one free when DCL starts up.
137 */
138 /* ARGSUSED */
139 int
spawncli(int f GCC_UNUSED,int n GCC_UNUSED)140 spawncli(int f GCC_UNUSED, int n GCC_UNUSED)
141 {
142 #undef OK_SPAWN
143
144 #if SYS_UNIX
145 #define OK_SPAWN
146 term.clean(TRUE);
147 (void) file_stat(0, 0);
148 term.openup();
149 #if DISP_X11 && !SMALLER
150 (void) x_window_SHELL((char *) 0);
151 #else
152 (void) system_SHELL((char *) 0);
153 #endif
154 term.openup();
155 term.unclean();
156
157 term.open();
158 term.kopen();
159 sgarbf = TRUE;
160 return AfterShell();
161 #endif /* SYS_UNIX */
162
163 #if SYS_VMS
164 #define OK_SPAWN
165 mlforce("[Starting DCL]\r\n");
166 kbd_flush(); /* Ignore "ttcol". */
167 sgarbf = TRUE;
168 return vms_system(NULL); /* NULL => DCL. */
169 #endif
170
171 #if SYS_MSDOS || SYS_OS2 || SYS_WINNT
172 #define OK_SPAWN
173 bottomleft();
174 kbd_flush();
175 term.kclose();
176 {
177 char *shell = get_shell();
178 #if SYS_OS2
179 /*
180 * spawn it if we know it. Some 3rd party command processors fail
181 * if they system themselves (eg 4OS2). CCM 24-MAR-94
182 */
183 spawnl(P_WAIT, shell, shell, NULL);
184 #else
185 #if SYS_WINNT
186 # if DISP_NTCONS
187 w32_CreateProcess(shell, FALSE);
188 # else
189 /*
190 * This is winvile, in which case the editor _might_ have
191 * been launched from the command line (which implies
192 * direct access to an existing Win32 console environment)
193 * or else was launched as a true Win32 app (has no console
194 * env). The latter case requires that CreateProcess() is
195 * called in such a way that a console is guaranteed to be
196 * allocated in the editor's behalf.
197 *
198 * The net result of the following call to w32_CreateProcess()
199 * is that the spawned shell runs in its own process, while
200 * winvile regains control immediately (i.e., there is no
201 * wait for the spawned shell to exit).
202 */
203 w32_CreateProcess(shell, TRUE);
204 # endif
205 #else
206 system(shell);
207 #endif
208 #endif
209 }
210 term.kopen();
211 sgarbf = TRUE;
212 return AfterShell();
213 #endif
214
215 #ifndef OK_SPAWN
216 mlforce("[This version of vile cannot spawn an interactive shell]");
217 return FALSE;
218 #endif
219 }
220
221 #if USE_UNIX_JOB_CTL
222 int
bktoshell(int f,int n)223 bktoshell(int f, int n) /* suspend and wait to wake up */
224 {
225 int forced = (f && n == SPECIAL_BANG_ARG); /* then it was :stop! */
226
227 /* take care of autowrite */
228 if (!forced && writeall(f, n, FALSE, TRUE, TRUE, FALSE) != TRUE)
229 return FALSE;
230
231 beginDisplay();
232 term.clean(TRUE);
233 (void) file_stat(0, 0);
234
235 /* #define simulate_job_control_for_debug */
236 # ifdef simulate_job_control_for_debug
237 rtfrmshell(SIGCONT);
238 return TRUE;
239 # else
240 (void) signal_pg(SIGTSTP);
241 return TRUE;
242 # endif
243 }
244 #else
245 /* ARGSUSED */
246 int
bktoshell(int f GCC_UNUSED,int n GCC_UNUSED)247 bktoshell(int f GCC_UNUSED, int n GCC_UNUSED)
248 {
249 mlforce("[Job control unavailable]");
250 return FALSE;
251 }
252 #endif /* SIGTSTP */
253
254 /*ARGSUSED*/
255 SIGT
rtfrmshell(int ACTUAL_SIG_ARGS GCC_UNUSED)256 rtfrmshell(int ACTUAL_SIG_ARGS GCC_UNUSED)
257 {
258 #if USE_UNIX_JOB_CTL
259 TRACE(("entering rtfrmshell...\n"));
260 endofDisplay();
261 term.openup();
262 term.open();
263 term.kopen();
264 term.unclean();
265 sgarbf = TRUE;
266 setup_handler(SIGCONT, rtfrmshell); /* suspend & restart */
267 (void) update(TRUE);
268 #endif
269 #if defined(MDCHK_MODTIME)
270 (void) check_visible_files_changed();
271 #endif
272 SIGRET;
273 }
274
275 void
pressreturn(void)276 pressreturn(void)
277 {
278 int c;
279 int osgarbf;
280 int old_reading;
281
282 if (!(quiet || clhide)) {
283 osgarbf = sgarbf;
284 sgarbf = FALSE;
285 mlforce("[Press return to continue]");
286 sgarbf = osgarbf;
287 /* loop for a CR, a space, or a : to do another named command */
288 old_reading = read_msgline(TRUE);
289 while ((c = keystroke()) != '\r' &&
290 c != '\n' &&
291 c != ' ' &&
292 !ABORTED(c)) {
293 if (DefaultKeyBinding(c) == &f_namedcmd) {
294 unkeystroke(c);
295 break;
296 }
297 }
298 kbd_erase_to_end(0);
299 read_msgline(old_reading);
300 }
301 }
302
303 /* ARGSUSED */
304 int
respawn(int f,int n GCC_UNUSED)305 respawn(int f, int n GCC_UNUSED)
306 {
307 return spawn1(TRUE, !f);
308 }
309
310 /* ARGSUSED */
311 int
vl_spawn(int f,int n GCC_UNUSED)312 vl_spawn(int f, int n GCC_UNUSED)
313 {
314 return spawn1(FALSE, !f);
315 }
316
317 #define COMMON_SH_PROMPT (SYS_UNIX || SYS_VMS || SYS_MSDOS || SYS_OS2 || SYS_WINNT)
318
319 #if COMMON_SH_PROMPT
320 /*
321 * Common function for prompting for shell/pipe command, and for recording the
322 * last shell/pipe command so that we can support "!!" convention.
323 *
324 * Note that for 'capturecmd()', we must retain a leading "!".
325 */
326 static int
ShellPrompt(TBUFF ** holds,char * result,int rerun)327 ShellPrompt(TBUFF **holds,
328 char *result,
329 int rerun) /* TRUE/FALSE: spawn, -TRUE: capturecmd */
330 {
331 int s;
332 size_t len;
333 static const char bang[] = SHPIPE_LEFT;
334 BUFFER *bp;
335 int cb = any_changed_buf(&bp), fix = (rerun != -TRUE);
336 char save[NLINE], temp[NLINE], line[NLINE + 1];
337
338 if ((len = tb_length(*holds)) != 0) {
339 (void) strncpy(save, tb_values(*holds), len);
340 }
341 save[len] = EOS;
342
343 /* if it doesn't start with '!', or if that's all it is */
344 if (!isShellOrPipe(save) || save[1] == EOS)
345 (void) strcpy(save, bang);
346
347 (void) strcpy(line, save);
348 if (rerun != TRUE) {
349 if (cb != 0) {
350 if (cb > 1) {
351 (void) lsprintf(temp,
352 "Warning: %d modified buffers: %s",
353 cb, bang);
354 } else {
355 (void) lsprintf(temp,
356 "Warning: buffer \"%s\" is modified: %s",
357 bp->b_bname, bang);
358 }
359 } else {
360 (void) lsprintf(temp, "%s%s",
361 rerun == -TRUE ? "" : ": ", bang);
362 }
363
364 if ((s = mlreply_no_bs(temp, line + 1, NLINE - 1)) != TRUE)
365 return s;
366 }
367 if (line[1] == EOS)
368 return FALSE;
369
370 *holds = tb_scopy(holds, line);
371 (void) strcpy(result, line + fix);
372 return TRUE;
373 }
374 #endif
375
376 /*
377 * Run a one-liner in a subjob. When the command returns, wait for a single
378 * character to be typed, then mark the screen as garbage so a full repaint is
379 * done.
380 */
381 /* the #ifdefs have been totally separated, for readability */
382 static int
spawn1(int rerun,int pressret)383 spawn1(int rerun, int pressret)
384 {
385 #if COMMON_SH_PROMPT
386 int s;
387 char line[NLINE]; /* command line send to shell */
388
389 if ((s = ShellPrompt(&tb_save_shell[0], line, rerun)) != TRUE)
390 return s;
391 #endif /* COMMON_SH_PROMPT */
392
393 /* take care of autowrite */
394 if (writeall(FALSE, 1, FALSE, TRUE, TRUE, FALSE) != TRUE)
395 return FALSE;
396
397 #if SYS_UNIX
398 #if DISP_X11
399 (void) pressret;
400 #if defined(HAVE_WAITPID) && !SMALLER
401 (void) x_window_SHELL(line);
402 #else
403 (void) system_SHELL(line);
404 #endif
405 #else
406 term.clean(TRUE);
407 (void) file_stat(0, 0);
408
409 (void) system_SHELL(line);
410
411 term.unclean();
412 if (pressret)
413 pressreturn();
414 term.open();
415 term.kopen();
416 term.flush();
417 sgarbf = TRUE;
418 #endif /* DISP_X11 */
419 return AfterShell();
420 #endif /* SYS_UNIX */
421
422 #if SYS_VMS
423 kbd_flush();
424 s = vms_system(line); /* Run the command. */
425 if (pressret) {
426 term.putch('\r');
427 term.putch('\n');
428 term.flush();
429 pressreturn();
430 }
431 sgarbf = TRUE;
432 return (s);
433 #endif
434 #if SYS_MSDOS || SYS_OS2 || SYS_WINNT
435 kbd_erase_to_end(0);
436 kbd_flush();
437 term.kclose();
438 #if SYS_WINNT
439 # if DISP_NTWIN
440 w32_system_winvile(line, &pressret);
441 # else
442 if (W32_SKIP_SHELL(line))
443 pressret = FALSE;
444 w32_system(line);
445 # endif
446 w32_keybrd_reopen(pressret);
447 #else
448 system(line);
449 term.open();
450 term.kopen();
451 /* wait for return here if we are interactive */
452 if (pressret) {
453 pressreturn();
454 }
455 #endif
456 #if DISP_NTCONS
457 ntcons_reopen();
458 #endif
459 sgarbf = TRUE;
460 return AfterShell();
461 #endif
462 }
463
464 /*
465 * Pipe a one line command into a window
466 */
467 /* ARGSUSED */
468 int
capturecmd(int f,int n)469 capturecmd(int f, int n)
470 {
471 int s;
472 #if SYS_UNIX || SYS_MSDOS || SYS_VMS || SYS_OS2 || SYS_WINNT
473 int allocd_storage;
474 BUFFER *bp; /* pointer to buffer to zot */
475 char line[NLINE]; /* command line send to shell */
476 char *final_cmd; /* possibly edited command line */
477
478 /* get the command to pipe in */
479 hst_init('!');
480 s = ShellPrompt(&tb_save_shell[!global_g_val(GMDSAMEBANGS)], line, -TRUE);
481 hst_flush();
482
483 /* prompt ok? */
484 if (s != TRUE)
485 return s;
486
487 #if OPT_FINDPATH
488 if ((final_cmd = ck_find_cmd(line, &allocd_storage, TRUE)) == NULL)
489 return (FALSE);
490 #else
491 allocd_storage = FALSE;
492 final_cmd = line;
493 #endif
494
495 if ((s = writeall(f, n, FALSE, FALSE, TRUE, FALSE)) == TRUE) {
496 if ((s = ((bp = bfind(OUTPUT_BufName, 0)) != NULL)) == TRUE) {
497 W_VALUES *save_wvals = save_window_modes(bp);
498
499 if ((s = popupbuff(bp)) == TRUE) {
500 ch_fname(bp, final_cmd);
501 bp->b_active = FALSE; /* force a re-read */
502 if ((s = swbuffer_lfl(bp, FALSE, FALSE)) == TRUE)
503 set_rdonly(bp, line, MDVIEW);
504 }
505 restore_window_modes(bp, save_wvals);
506 }
507 }
508 #if OPT_FINDPATH
509 if (allocd_storage)
510 (void) free(final_cmd);
511 #endif
512
513 #else /* ! SYS_UNIX */
514
515 WINDOW *wp; /* pointer to new window */
516 BUFFER *bp; /* pointer to buffer to zot */
517 static char oline[NLINE]; /* command line send to shell */
518 char line[NLINE]; /* command line send to shell */
519 WINDOW *ocurwp; /* save the current window during delete */
520
521 static char filnam[NSTRING] = "command";
522
523 /* get the command to pipe in */
524 if ((s = mlreply("cmd: <", oline, NLINE)) != TRUE)
525 return (s);
526
527 (void) strcpy(line, oline);
528
529 /* get rid of the command output buffer if it exists */
530 if ((bp = find_b_name(OUTPUT_BufName)) != NULL) {
531 /* try to make sure we are off screen */
532 ocurwp = NULL;
533 for_each_window(wp) {
534 if (wp->w_bufp == bp) {
535 if (curwp != wp) {
536 ocurwp = curwp;
537 curwp = wp;
538 }
539 delwind(FALSE, 1);
540 if (ocurwp != NULL)
541 curwp = ocurwp;
542 break;
543 }
544 }
545 if (zotbuf(bp) != TRUE)
546 return (FALSE);
547 }
548
549 if (s != TRUE)
550 return (s);
551
552 /* split the current window to make room for the command output */
553 if ((s = splitwind(FALSE, 1)) != TRUE)
554 return (s);
555
556 /* and read the stuff in */
557 if ((s = getfile(filnam, FALSE)) != TRUE)
558 return (s);
559
560 /* overwrite its buffer name for consistency */
561 set_bname(curbp, OUTPUT_BufName);
562
563 /* make this window in VIEW mode, update buffer's mode lines */
564 set_local_b_val(curwp->w_bufp, MDVIEW, TRUE);
565 curwp->w_flag |= WFMODE;
566
567 #if OPT_FINDERR
568 set_febuff(OUTPUT_BufName);
569 #endif
570
571 /* and get rid of the temporary file */
572 unlink(filnam);
573 s = AfterShell();
574 #endif /* SYS_UNIX */
575 return (s);
576 }
577
578 #if SYS_UNIX || SYS_MSDOS || (SYS_OS2 && CC_CSETPP) || SYS_WINNT
579 /*
580 * write_kreg_to_pipe() exists to facilitate execution of a Win32 thread.
581 * All other host operating systems are simply victims.
582 */
583 static void
write_kreg_to_pipe(void * writefp)584 write_kreg_to_pipe(void *writefp)
585 {
586 FILE *fw;
587 KILL *kp; /* pointer into kill register */
588
589 fw = (FILE *) writefp;
590 kregcirculate(FALSE);
591 kp = kbs[ukb].kbufh;
592 while (kp != NULL) {
593 IGNORE_RC(fwrite((char *) kp->d_chunk,
594 (size_t) 1,
595 (size_t) KbSize(ukb, kp),
596 fw));
597 kp = kp->d_next;
598 }
599 #if SYS_UNIX && ! TEST_DOS_PIPES
600 (void) fflush(fw);
601 (void) fclose(fw);
602 ExitProgram(GOODEXIT);
603 /* NOTREACHED */
604 #else
605 # if SYS_WINNT
606 /*
607 * If this function is invoked by a thread, then that thread (not
608 * the parent process) must close write pipe. We generalize this
609 * function so that all Win32 execution environments (threaded or
610 * not) use the same code.
611 */
612 (void) fflush(fw);
613 (void) fclose(fw);
614 if (!global_g_val(GMDW32PIPES))
615 npflush();
616 # else
617 npflush(); /* fake multi-processing */
618 # endif
619 #endif
620 }
621
622 #if OPT_SELECTIONS
623 /*
624 * A corresponding function to write the current region from DOT to MK to a
625 * pipe.
626 */
627 static void
write_region_to_pipe(void * writefp)628 write_region_to_pipe(void *writefp)
629 {
630 FILE *fw = (FILE *) writefp;
631 LINE *last = setup_region();
632 LINE *lp = DOT.l;
633
634 while (lp != last) {
635 IGNORE_RC(fwrite((char *) lvalue(lp),
636 sizeof(char),
637 (size_t) llength(lp),
638 fw));
639 vl_putc('\n', fw);
640 lp = lforw(lp);
641 }
642 #if SYS_UNIX && ! TEST_DOS_PIPES
643 (void) fflush(fw);
644 (void) fclose(fw);
645 ExitProgram(GOODEXIT);
646 /* NOTREACHED */
647 #else
648 # if SYS_WINNT
649 /*
650 * If this function is invoked by a thread, then that thread (not
651 * the parent process) must close write pipe. We generalize this
652 * function so that all Win32 execution environments (threaded or
653 * not) use the same code.
654 */
655 (void) fflush(fw);
656 (void) fclose(fw);
657 if (!global_g_val(GMDW32PIPES))
658 npflush();
659 # else
660 npflush(); /* fake multi-processing */
661 # endif
662 #endif
663 }
664 #endif
665 #endif /* OPT_SELECTIONS */
666
667 /*
668 * FUNCTION
669 * filterregion(void)
670 *
671 * DESCRIPTION
672 * Run a region through an external filter, replace region with the
673 * filter's output.
674 *
675 * Architecturally, filterregion() is designed like so:
676 *
677 * ------------ write pipe ------------
678 * | vile |--------------->| a filter |
679 * | | | |
680 * | |<---------------| ex: fmt |
681 * ------------ read pipe ------------
682 *
683 * The idea here is to exec a filter (say, fmt) and then SIMULTANEOUSLY
684 * pump a potentially big wad of data down the write pipe while, AT THE
685 * SAME TIME, reading the filter's output.
686 *
687 * The words in caps are the key, as they illustrate a need for separate
688 * vile processes: a writer and a reader. This is accomplished on a
689 * Unix host via the function softfork(), which calls fork().
690 *
691 * For all other OSes, softfork() is a stub, which means that the
692 * entire filter operation runs single threaded. This is a problem.
693 * Consider the following scenario on a host that doesn't support fork():
694 *
695 * 1) vile's !<region> command is used to exec vile-c-filt
696 * 2) vile begins pushing a large <region> (say, 100 KB) down the
697 * write pipe
698 * 3) the filter responds by pushing back an even larger response
699 * 4) since vile hasn't finished the write pipe operation, the editor
700 * does not read any data from the read pipe.
701 * 5) eventually, vile's read pipe buffer limit is reached and the
702 * filter blocks.
703 * 6) when the filter blocks, it can't read data from vile and so the
704 * editor's write pipe buffer limit will trip as well, blocking vile.
705 *
706 * That's process deadlock.
707 *
708 * One workaround on a non-Unix host is the use of temp files, which
709 * uses this algorithm:
710 *
711 * vile writes region to temp file,
712 * vile execs filter with stdin connected to temp file,
713 * vile reads filter's response.
714 *
715 * This mechanism can be effected without error by a single process.
716 *
717 * An alternative workaround simply creates two threads that simulate the
718 * effects of fork(). The first thread fills the write pipe, while the
719 * second thread consumes the read pipe.
720 *
721 * RETURNS
722 * Boolean, T -> all is well, F -> failure
723 */
724
725 int
filterregion(void)726 filterregion(void)
727 {
728 int s = FALSE;
729 /* FIXME work on this for OS2, need inout_popen support, or named pipe? */
730 #if SYS_UNIX || SYS_MSDOS || (SYS_OS2 && CC_CSETPP) || SYS_WINNT
731 static char oline[NLINE]; /* command line send to shell */
732 char line[NLINE]; /* command line send to shell */
733 FILE *fr, *fw;
734
735 TRACE((T_CALLED "filterregion\n"));
736
737 /* get the filter name and its args */
738 if ((s = mlreply_no_bs("!", oline, NLINE)) != TRUE)
739 returnCode(s);
740
741 (void) strcpy(line, oline);
742
743 if ((s = inout_popen(&fr, &fw, line)) != TRUE) {
744 mlforce("[Couldn't open pipe or command]");
745 } else {
746 if ((s = begin_kill()) == TRUE) {
747 if (!softfork()) {
748 #if !(SYS_WINNT && defined(GMDW32PIPES))
749 write_kreg_to_pipe(fw);
750 #else
751 /* This is a Win32 environment with compiled Win32 pipe
752 * support.
753 */
754 if (global_g_val(GMDW32PIPES)) {
755 long rc = (long) _beginthread(write_kreg_to_pipe, 0, fw);
756
757 /*
758 * w32pipes mode enabled -- create child thread to blast
759 * region to write pipe.
760 */
761 if (rc == -1) {
762 mlforce("[Can't create Win32 write pipe]");
763 (void) fclose(fw);
764 (void) npclose(fr);
765 returnCode(FALSE);
766 }
767 } else {
768 /*
769 * Single-threaded parent process writes region to pseudo
770 * write pipe (temp file).
771 */
772 write_kreg_to_pipe(fw);
773 }
774 #endif
775 }
776 #if ! ((SYS_OS2 && CC_CSETPP) || SYS_WINNT)
777 if (fw != 0)
778 (void) fclose(fw);
779 #endif
780 DOT.l = lback(DOT.l);
781 s = ifile((char *) 0, TRUE, fr);
782 npclose(fr);
783 (void) firstnonwhite(FALSE, 1);
784 (void) setmark();
785 end_kill();
786 } else {
787 fclose(fw);
788 npclose(fr);
789 }
790 }
791 #else
792 TRACE((T_CALLED "filterregion (stub)\n"));
793 mlforce("[Region filtering not available -- try buffer filtering]");
794 #endif
795 returnCode(s);
796 }
797
798 #if OPT_SELECTIONS
799 /*
800 * Like filterregion, but opens a stream reading from a filter's output when
801 * we will not modify the current buffer (e.g., syntax highlighting).
802 */
803 int
open_region_filter(void)804 open_region_filter(void)
805 {
806 /* FIXME work on this for OS2, need inout_popen support, or named pipe? */
807 #if SYS_UNIX || SYS_MSDOS || (SYS_OS2 && CC_CSETPP) || SYS_WINNT
808 static char oline[NLINE]; /* command line send to shell */
809 char line[NLINE]; /* command line send to shell */
810 FILE *fr, *fw;
811 #endif
812 int s;
813
814 TRACE((T_CALLED "open_region_filter\n"));
815
816 #if SYS_UNIX || SYS_MSDOS || (SYS_OS2 && CC_CSETPP) || SYS_WINNT
817 /* get the filter name and its args */
818 if ((s = mlreply_no_bs("!", oline, NLINE)) == TRUE) {
819 (void) strcpy(line, oline);
820 if ((s = inout_popen(&fr, &fw, line)) == TRUE) {
821 if (!softfork()) {
822 #if !(SYS_WINNT && defined(GMDW32PIPES))
823 write_region_to_pipe(fw);
824 #else
825 /* This is a Win32 environment with compiled Win32 pipe
826 * support.
827 */
828 if (global_g_val(GMDW32PIPES)) {
829 ULONG code = (ULONG) _beginthread(write_region_to_pipe,
830 0,
831 fw);
832
833 /*
834 * w32pipes mode enabled -- create child thread to blast
835 * region to write pipe.
836 */
837 if (code == (ULONG) (-1)) {
838 mlforce("[Can't create Win32 write pipe]");
839 (void) fclose(fw);
840 (void) npclose(fr);
841 s = FALSE;
842 }
843 } else {
844 /*
845 * Single-threaded parent process writes region to pseudo
846 * write pipe (temp file).
847 */
848 write_region_to_pipe(fw);
849 }
850 #endif
851 }
852 if (s == TRUE) {
853 #if ! ((SYS_OS2 && CC_CSETPP) || SYS_WINNT)
854 (void) fclose(fw);
855 #endif
856 ffp = fr;
857 count_fline = 0;
858 }
859 } else {
860 mlforce("[Couldn't open pipe or command]");
861 }
862 }
863 #else
864 mlforce("[Region filtering not available -- try buffer filtering]");
865 s = FALSE;
866 #endif
867 returnCode(s);
868 }
869 #endif /* OPT_SELECTIONS */
870
871 /*
872 * filter a buffer through an external DOS program
873 * this is obsolete, the filterregion code is better.
874 */
875 /* ARGSUSED */
876 int
vile_filter(int f GCC_UNUSED,int n GCC_UNUSED)877 vile_filter(int f GCC_UNUSED, int n GCC_UNUSED)
878 {
879 #if !(SYS_UNIX||SYS_MSDOS || (SYS_OS2 && CC_CSETPP)) /* filterregion up above is better */
880 int s; /* return status from CLI */
881 BUFFER *bp; /* pointer to buffer to zot */
882 static char oline[NLINE]; /* command line send to shell */
883 char line[NLINE]; /* command line send to shell */
884 char tnam[NFILEN]; /* place to store real file name */
885 static char bname1[] = "fltinp";
886 #if SYS_UNIX
887 char *t;
888 #endif
889
890 static char filnam1[] = "fltinp";
891 static char filnam2[] = "fltout";
892
893 #if SYS_VMS
894 mlforce("[Not available under VMS]");
895 return (FALSE);
896 #endif
897 /* get the filter name and its args */
898 if ((s = mlreply("cmd: |", oline, NLINE)) != TRUE)
899 return (s);
900 (void) strcpy(line, oline);
901
902 /* setup the proper file names */
903 bp = curbp;
904 (void) strcpy(tnam, bp->b_fname); /* save the original name */
905 ch_fname(bp, bname1); /* set it to our new one */
906
907 /* write it out, checking for errors */
908 if (writeout(filnam1, curbp, TRUE, TRUE) != TRUE) {
909 mlforce("[Cannot write filter file]");
910 ch_fname(bp, tnam);
911 return (FALSE);
912 }
913 #if SYS_MSDOS || SYS_OS2 || SYS_WINNT
914 (void) strcat(line, " <fltinp >fltout");
915 bottomleft();
916 term.kclose();
917 system(line);
918 term.kopen();
919 sgarbf = TRUE;
920 s = TRUE;
921 #endif
922 #if SYS_UNIX
923 bottomleft();
924 term.clean(TRUE);
925 (void) file_stat(0, 0);
926 if ((t = strchr(line, '|')) != 0) {
927 char temp[NLINE];
928 (void) strcpy(temp, t);
929 (void) strcat(strcpy(t, " <fltinp"), temp);
930 } else {
931 (void) strcat(line, " <fltinp");
932 }
933 (void) strcat(line, " >fltout");
934 system(line);
935 term.unclean();
936 term.flush();
937 sgarbf = TRUE;
938 s = TRUE;
939 #endif
940
941 /* on failure, escape gracefully */
942 if (s != TRUE || ((s = readin(filnam2, FALSE, curbp, TRUE)) != TRUE)) {
943 mlforce("[Execution failed]");
944 ch_fname(bp, tnam);
945 unlink(filnam1);
946 unlink(filnam2);
947 return (s);
948 }
949
950 ch_fname(bp, tnam); /* restore name */
951
952 b_set_changed(bp); /* flag it as changed */
953 nounmodifiable(bp); /* and it can never be "un-changed" */
954
955 /* and get rid of the temporary file */
956 unlink(filnam1);
957 unlink(filnam2);
958 return AfterShell();
959 #else
960 mlforce("[Buffer filtering not available -- use filter operator]");
961 return FALSE;
962 #endif
963 }
964
965 #if SYS_VMS
966 /*
967 * Run a command. The "cmd" is a pointer to a command string, or NULL if you
968 * want to run a copy of DCL in the subjob (this is how the standard routine
969 * lib$spawn works. You have to do weird stuff with the terminal on the way in
970 * and the way out, because DCL does not want the channel to be in raw mode.
971 */
972 int
vms_system(char * cmd)973 vms_system(char *cmd)
974 {
975 struct dsc$descriptor cdsc;
976 struct dsc$descriptor *cdscp;
977 long status;
978 long substatus;
979 long iosb[2];
980
981 status = sys$qiow(EFN, iochan, IO$_SETMODE, iosb, 0, 0,
982 oldmode, sizeof(oldmode), 0, 0, 0, 0);
983 if (status != SS$_NORMAL || (iosb[0] & 0xFFFF) != SS$_NORMAL)
984 return (FALSE);
985 cdscp = NULL; /* Assume DCL. */
986 if (cmd != NULL) { /* Build descriptor. */
987 cdsc.dsc$a_pointer = cmd;
988 cdsc.dsc$w_length = strlen(cmd);
989 cdsc.dsc$b_dtype = DSC$K_DTYPE_T;
990 cdsc.dsc$b_class = DSC$K_CLASS_S;
991 cdscp = &cdsc;
992 }
993 status = lib$spawn(cdscp, 0, 0, 0, 0, 0, &substatus, 0, 0, 0);
994 if (status != SS$_NORMAL)
995 substatus = status;
996 status = sys$qiow(EFN, iochan, IO$_SETMODE, iosb, 0, 0,
997 newmode, sizeof(newmode), 0, 0, 0, 0);
998 if (status != SS$_NORMAL || (iosb[0] & 0xFFFF) != SS$_NORMAL)
999 return (FALSE);
1000 if ((substatus & STS$M_SUCCESS) == 0) /* Command failed. */
1001 return (FALSE);
1002 return AfterShell();
1003 }
1004 #endif
1005
1006 #ifdef HAVE_PUTENV
1007 int
set_envvar(int f GCC_UNUSED,int n GCC_UNUSED)1008 set_envvar(int f GCC_UNUSED, int n GCC_UNUSED)
1009 {
1010 static TBUFF *var, *val;
1011
1012 char *both;
1013 int rc;
1014
1015 if ((rc = mlreply2("Environment variable: ", &var)) != ABORT) {
1016 if ((rc = mlreply2("Value: ", &val)) != ABORT) {
1017 size_t len_var = tb_length(var);
1018 size_t len_val = tb_length(val);
1019
1020 if (len_var != 0
1021 && len_val != 0
1022 && (both = typeallocn(char, len_var + len_val + 1)) != 0) {
1023 lsprintf(both, "%s=%s", tb_values(var), tb_values(val));
1024 rc = (putenv(both) == 0); /* this will leak. i think it has to. */
1025 } else {
1026 rc = no_memory("set_envvar");
1027 }
1028 }
1029 }
1030
1031 return rc;
1032 }
1033 #endif
1034
1035 #if OPT_FINDPATH
1036
1037 /*
1038 * FUNCTION
1039 * parse_findcfg_mode(FINDCFG *pcfg, char *inputstr)
1040 *
1041 * pcfg - returned by reference -- what was found in inputstr
1042 *
1043 * inputstr - what to parse
1044 *
1045 * DESCRIPTION
1046 * find-cfg mode is a string that supports this syntax:
1047 *
1048 * "[<recursive_token>][,<nonrecursive_token>[,<option>...]]"
1049 *
1050 * where:
1051 * <recursive_token> := an ascii char that triggers a recursive find,
1052 * may not be taken from the character set defined
1053 * by isalpha(). To use ',' as a token, escape it
1054 * with '\'.
1055 * <nonrecursive_token> := an ascii char that triggers a nonrecursive find,
1056 * may not be taken from the character set defined
1057 * by isalpha(). To use ',' as a token, escape it
1058 * with '\'.
1059 *
1060 * <option> := <dirs_only>|<follow>
1061 *
1062 * <dirs_only> := d
1063 *
1064 * <follow> := f
1065 *
1066 * Example usage:
1067 * se find-cfg="$,@" ; '$' -> recursive find, '@' -> nonrecursive find
1068 * se find-cfg="$,,d" ; '$' -> recursive find, the find operation should
1069 * ; only look for directories.
1070 *
1071 * Note that an empty string disables find-cfg mode.
1072 *
1073 * RETURNS
1074 * Boolean, T -> all is well
1075 */
1076 int
parse_findcfg_mode(FINDCFG * pcfg,char * inputstr)1077 parse_findcfg_mode(FINDCFG * pcfg, char *inputstr)
1078 {
1079 char *cp;
1080 int i, rc = TRUE;
1081
1082 memset(pcfg, 0, sizeof(*pcfg));
1083 pcfg->disabled = TRUE; /* an assumption */
1084 cp = mktrimmed(inputstr);
1085 if (*cp == EOS)
1086 return (rc);
1087
1088 /* handle first 2 tokens */
1089 for (i = 0; i < 2 && *cp; i++) {
1090 if (*cp != ',') {
1091 if (isAlpha(*cp)) {
1092 mlforce("[alphanumeric tokens not allowed]");
1093 return (FALSE);
1094 }
1095 if (*cp == '\\' && cp[1] == ',')
1096 cp++; /* skip escape char */
1097 if (i == 0)
1098 pcfg->recur_token = *cp++;
1099 else
1100 pcfg->nonrecur_token = *cp++;
1101 cp = skip_blanks(cp);
1102 if (*cp) {
1103 if (*cp != ',') {
1104 mlforce("[invalid find-cfg syntax]");
1105 return (FALSE);
1106 }
1107 } else {
1108 pcfg->disabled = (!rc);
1109 return (rc); /* end of string, all done */
1110 }
1111 }
1112 cp++; /* skip token delimiter */
1113 cp = skip_blanks(cp);
1114 if (*cp == EOS) {
1115 mlforce("[invalid find-cfg syntax]");
1116 return (FALSE);
1117 }
1118 }
1119
1120 /* options? */
1121 if (*cp) {
1122 while (*cp) {
1123 if (*cp == 'd')
1124 pcfg->dirs_only = TRUE;
1125 else if (*cp == 'f')
1126 pcfg->follow = TRUE;
1127 else {
1128 mlforce("[invalid find-cfg syntax]");
1129 rc = FALSE;
1130 break;
1131 }
1132 cp++;
1133 }
1134 }
1135 pcfg->disabled = (!rc);
1136 return (rc);
1137 }
1138
1139 static void
free_vector(char *** vec,size_t vec_elements)1140 free_vector(char ***vec, size_t vec_elements)
1141 {
1142 char **base;
1143 ULONG i;
1144
1145 base = *vec;
1146 for (i = 0; i < vec_elements; i++, base++)
1147 (void) free(*base);
1148 (void) free(*vec);
1149 }
1150
1151 /*
1152 * Cruise through the user's shell command, stripping out unquoted tokens
1153 * that include wildcard characters. Save each token in a vector. Return
1154 * the remains of the shell command to the caller (or NULL if an error occurs).
1155 */
1156 static char *
extract_wildcards(char * cmd,char *** vec,size_t * vecidx,const char * fnname)1157 extract_wildcards(char *cmd, char ***vec, size_t *vecidx, const char *fnname)
1158 {
1159 char **temp;
1160 char **base;
1161 char *cp;
1162 char *anchor;
1163 char buf[NFILEN * 2];
1164 int delim;
1165 size_t idx, len;
1166
1167 idx = 0;
1168 len = 32;
1169 cp = cmd;
1170 base = castalloc(char *, len * sizeof(char *));
1171 if (base == NULL) {
1172 (void) no_memory(fnname);
1173 return (NULL);
1174 }
1175 while (*cp) {
1176 cp = anchor = skip_blanks(cp);
1177 if (*cp == '\'' || *cp == '"') {
1178 delim = *cp++;
1179 while (*cp) {
1180 if (*cp == '\\' && cp[1] == delim) {
1181 cp += 2;
1182 } else if (*cp == delim) {
1183 cp++;
1184 break;
1185 } else {
1186 cp++;
1187 }
1188 }
1189 } else {
1190 cp++;
1191 while (*cp && (!isSpace(*cp)))
1192 cp++;
1193 len = (size_t) (cp - anchor);
1194 strncpy(buf, anchor, len);
1195 buf[len] = EOS;
1196 if (string_has_wildcards(buf)) {
1197 memset(anchor, ' ', len); /* blank out wildcard in cmd */
1198 if (idx >= len) {
1199 len *= 2;
1200 temp = castrealloc(char *, base, sizeof(*base));
1201 if (temp == NULL) {
1202 free_vector(&base, idx);
1203 (void) no_memory(fnname);
1204 return (NULL);
1205 } else {
1206 base = temp;
1207 }
1208 }
1209 base[idx] = castalloc(char, len + 1);
1210 if (base[idx] == NULL) {
1211 free_vector(&base, idx);
1212 (void) no_memory(fnname);
1213 return (NULL);
1214 }
1215 strcpy(base[idx++], buf);
1216 }
1217 }
1218 }
1219
1220 /* all done */
1221 *vec = base;
1222 *vecidx = idx;
1223 return (cmd);
1224 }
1225
1226 static const char *
determine_quoted_delimiter(void)1227 determine_quoted_delimiter(void)
1228 {
1229 const char *qdelim;
1230
1231 #if SYS_UNIX
1232 qdelim = "'";
1233 #else
1234 {
1235 char *cp, buf[NFILEN];
1236 int unix_shell, shell_len;
1237
1238 strcpy(buf, get_shell());
1239 cp = strrchr(buf, '.');
1240 if (cp)
1241 *cp = EOS; /* trim file suffix */
1242 shell_len = (int) strlen(buf);
1243 unix_shell = (shell_len >= 2 &&
1244 toLower(buf[shell_len - 2]) == 's' &&
1245 toLower(buf[shell_len - 1]) == 'h');
1246 if (unix_shell)
1247 qdelim = "'";
1248 else {
1249 /*
1250 * Assume a DOS-based shell, which generally honors double
1251 * quotes as argument delimiters (not single quotes).
1252 */
1253
1254 qdelim = "\"";
1255 }
1256 }
1257 #endif
1258 return (qdelim);
1259 }
1260
1261 char *
last_findcmd(void)1262 last_findcmd(void)
1263 {
1264 return (prev_findcmd);
1265 }
1266
1267 /*
1268 * Add a string to the end of a cmd string, lengthening same if necessary.
1269 * Does not terminate cmd string.
1270 */
1271 static int
add_token_to_cmd(char ** cmd,size_t * cmdidx,size_t * cmdlen,const char * token,const char * funcname)1272 add_token_to_cmd(char **cmd,
1273 size_t *cmdidx,
1274 size_t *cmdlen,
1275 const char *token,
1276 const char *funcname)
1277 {
1278 int rc = TRUE;
1279 char *tmp;
1280 size_t toklen = strlen(token);
1281
1282 if ((tmp = *cmd) != 0) {
1283 if (*cmdidx + toklen + 2 > *cmdlen) {
1284 *cmdlen *= 2;
1285 tmp = castrealloc(char, tmp, *cmdlen);
1286 if (tmp == NULL) {
1287 (void) free(*cmd);
1288 *cmd = 0;
1289 } else {
1290 *cmd = tmp;
1291 }
1292 }
1293 }
1294 if (*cmd != 0) {
1295 strcpy(*cmd + *cmdidx, token);
1296 *cmdidx += toklen;
1297 (*cmd)[*cmdidx] = ' ';
1298 (*cmdidx)++;
1299 rc = TRUE;
1300 } else {
1301 rc = no_memory(funcname);
1302 }
1303 return (rc);
1304 }
1305
1306 /*
1307 * FUNCTION
1308 * ck_find_cmd(char *cmd, int *allocd_storage, int prepend_bang)
1309 *
1310 * cmd - user's original shell command.
1311 *
1312 * allocd_storage - by ref, T -> the returned cmd string was allocated
1313 * on the heap. Caller must free.
1314 *
1315 * prepend_bang - Boolean, T -> that if a modified shell command is
1316 * created, prepend it with '!'.
1317 *
1318 * DESCRIPTION
1319 * If the user has enabled find-cfg mode and if the user's shell
1320 * command contains a token that indicates that a find operation should
1321 * be initiated, modify the user's shell command appropriately.
1322 *
1323 * Example:
1324 * :setv $findpath="."
1325 * :se find-cfg="$"
1326 * ^X-!$egrep -n FIXME *.[ch]
1327 *
1328 * Resultant shell command on a Unix host (somewhat simplified):
1329 * !find . -name '*.[ch]' -print | xargs egrep -n FIXME
1330 *
1331 * Note that this example does not include the syntax used to filter out
1332 * CVS/RCS directories and tags files.
1333 *
1334 * RETURNS
1335 * Pointer to original user cmd if no find operation required, a newly
1336 * synthesized cmd string, or NULL (failure case).
1337 */
1338 static char *
ck_find_cmd(char * cmd,int * allocd_storage,int prepend_bang)1339 ck_find_cmd(char *cmd, int *allocd_storage, int prepend_bang)
1340 {
1341 char *cp, *cmdcopy;
1342 FINDINFO info;
1343
1344 *allocd_storage = FALSE;
1345 if (!parse_findcfg_mode(&info.cfg, global_g_val_ptr(GVAL_FINDCFG))) {
1346 return (NULL); /* bogus find-cfg value noted */
1347 }
1348 if (info.cfg.disabled)
1349 return (cmd); /* find-cfg mode disabled */
1350
1351 /*
1352 * don't munge vile's copy of the user's command line -- scrogs the
1353 * [Output] buffer name.
1354 */
1355 if ((cmdcopy = strmalloc(cmd)) == NULL) {
1356 (void) no_memory("ck_find_cmd");
1357 return (NULL);
1358 }
1359 cp = skip_blanks(cmdcopy);
1360 if (*cp == SHPIPE_LEFT[0])
1361 cp++;
1362 cp = skip_blanks(cp);
1363 if (*cp) {
1364 info.nonrecursive = (*cp == info.cfg.nonrecur_token);
1365
1366 /* if specifying recursive or nonrecursive find syntax ... */
1367 if (info.nonrecursive || *cp == info.cfg.recur_token) {
1368 /*
1369 * user wants [non]recursive find syntax added to shell
1370 * command.
1371 */
1372
1373 info.dir_list = get_findpath();
1374 if (info.dir_list[0] == EOS)
1375 info.dir_list = ".";
1376 if (info.cfg.dirs_only)
1377 cmd = find_dirs_only(cp + 1, &info, prepend_bang);
1378 else
1379 cmd = find_all_files(cp + 1, &info, prepend_bang);
1380 if (cmd) {
1381 /* keep a record of the cmd that's about to be spawned */
1382
1383 if (prev_findcmd)
1384 (void) free(prev_findcmd);
1385 if ((prev_findcmd = strmalloc(cmd)) == NULL) {
1386 (void) free(cmd);
1387 cmd = NULL;
1388 no_memory("ck_find_cmd");
1389 } else {
1390 *allocd_storage = TRUE;
1391 }
1392 }
1393 }
1394 }
1395 (void) free(cmdcopy);
1396 return (cmd);
1397 }
1398
1399 #if SYS_UNIX
1400 #define EGREP_OPT_CASELESS ""
1401 #define EGREP_TAG_CASELESS "[Tt][Aa][Gg][Ss]"
1402 #else
1403 #define EGREP_OPT_CASELESS "i"
1404 #define EGREP_TAG_CASELESS "tags"
1405 #endif
1406
1407 /*
1408 * FUNCTION
1409 * find_dirs_only(char *cmd, FINDINFO *pinfo, int prepend_bang)
1410 *
1411 * cmd - user's input command, stripped of leading '!' and
1412 * find-cfg tokens.
1413 *
1414 * pinfo - contains info about the user's various "find" parameters.
1415 *
1416 * prepend_bang - Boolean, T -> prepend '!' to beginning of shell cmd
1417 * synthesized by this routine.
1418 *
1419 * DESCRIPTION
1420 * The task:
1421 *
1422 * - construct a shell command that looks like so:
1423 *
1424 * [!]find <$findpath_dir_list> -type d -print | \
1425 * egrep -v <RE that elides CVS & RCS dirs> | xargs cmdstr
1426 *
1427 * - if executing on a host that supports case insensitive file names,
1428 * modify the above shell command to include option modifiers that as
1429 * required.
1430 *
1431 * - if a nonrecursive find is requested, add an appropriate option
1432 * (i.e., -maxdepth 1) to the find cmdline (again, this syntax used may
1433 * only be supported by the GNU version of find).
1434 *
1435 * RETURNS
1436 * Pointer to newly formulated shell command (allocated on the heap) or
1437 * NULL (failure).
1438 */
1439 static char *
find_dirs_only(char * cmd,FINDINFO * pinfo,int prepend_bang)1440 find_dirs_only(char *cmd, FINDINFO * pinfo, int prepend_bang)
1441 {
1442 size_t i, outidx, outlen;
1443 const char *path, *fnname;
1444 char *rslt, buf[NFILEN * 2];
1445 const char *qdelim;
1446 int first = TRUE;
1447
1448 fnname = "find_dirs_only";
1449 outlen = sizeof(buf);
1450 outidx = 0;
1451 rslt = castalloc(char, outlen);
1452 if (!rslt) {
1453 (void) no_memory(fnname);
1454 return (NULL);
1455 }
1456 if (prepend_bang) {
1457 *rslt = SHPIPE_LEFT[0];
1458 rslt[1] = EOS;
1459 outidx = 1;
1460 } else
1461 *rslt = '\0';
1462 strcat(rslt, "find ");
1463 outidx += sizeof("find ") - 1;
1464 path = pinfo->dir_list;
1465
1466 /* add directory list to find command */
1467 while ((path = parse_pathlist(path, buf, &first)) != NULL) {
1468 if (!add_token_to_cmd(&rslt, &outidx, &outlen, buf, fnname))
1469 return (NULL);
1470 }
1471
1472 /* worry about nonrecursive find */
1473 if (pinfo->nonrecursive) {
1474 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-maxdepth 1", fnname))
1475 return (NULL);
1476 }
1477
1478 /* follow symbolic links? */
1479 if (pinfo->cfg.follow) {
1480 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-follow", fnname))
1481 return (NULL);
1482 }
1483
1484 /* terminate find string with "-type d -print" */
1485 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-type d -print", fnname))
1486 return (NULL);
1487
1488 qdelim = determine_quoted_delimiter();
1489
1490 /*
1491 * filter out RCS/CVS directories and tags files. we make the
1492 * assumption that the user's "find" creates filenames with
1493 * '/' as a path delimiter.
1494 *
1495 * ========================== FIXME ==================================
1496 * Note that this regular expression is simplistic and could be better
1497 * if it ensured that "RCS/CVS" are preceded by '/' or nothing.
1498 * ========================== FIXME ==================================
1499 */
1500 sprintf(buf,
1501 "| egrep -v" EGREP_OPT_CASELESS " %s(RCS|CVS)/%s",
1502 qdelim,
1503 qdelim);
1504 if (!add_token_to_cmd(&rslt, &outidx, &outlen, buf, fnname)) {
1505 rslt = NULL;
1506 } else if (!add_token_to_cmd(&rslt, &outidx, &outlen, "| xargs", fnname)) {
1507 rslt = NULL;
1508 } else if (!add_token_to_cmd(&rslt, &outidx, &outlen, cmd, fnname)) {
1509 rslt = NULL;
1510 } else if (rslt != 0) {
1511 rslt[outidx] = EOS; /* terminate cmd string */
1512 if (outidx != 0) {
1513 char *cp;
1514
1515 i = --outidx;
1516 cp = rslt + outidx;
1517 while (isSpace(*cp) && i != 0) {
1518 cp--;
1519 i--;
1520 }
1521 if (cp != rslt + outidx) {
1522 /* white space found at end of string, trim it. */
1523
1524 cp[1] = EOS;
1525 }
1526 }
1527 }
1528 return (rslt);
1529 }
1530
1531 /*
1532 * FUNCTION
1533 * find_all_files(char *cmd, FINDINFO *pinfo, int prepend_bang)
1534 *
1535 * cmd - user's input command, stripped of leading '!' and
1536 * find-cfg tokens.
1537 *
1538 * pinfo - contains info about the user's various "find" parameters.
1539 *
1540 * prepend_bang - Boolean, T -> prepend '!' to beginning of shell cmd
1541 * synthesized by this routine.
1542 *
1543 * DESCRIPTION
1544 * The task:
1545 *
1546 * - scan all unquoted tokens in the user's command line.
1547 *
1548 * - remove each unqoted token that includes shell wildcard chars. Call the
1549 * remainder of the user's input the "cmdstr". Call the removed tokens
1550 * the "wildvec".
1551 *
1552 * - construct a shell command that looks like so:
1553 *
1554 * [!]find <$findpath_dir_list> '(' -name wildvec[0] -o \
1555 * -name wildvec[1] ... -o -name wildvec[n-1] ')' -print | \
1556 * egrep -v <RE that elides CVS & RCS dirs and tags files> | \
1557 * xargs cmdstr
1558 *
1559 * - if executing on a host that supports case insensitive file names,
1560 * modify the above shell command to include option modifiers that as
1561 * required. Note that the find options used in this case (i.e., -iname)
1562 * may only be supported by the GNU version of find.
1563 *
1564 * - if a nonrecursive find is requested, add an appropriate option
1565 * (i.e., -maxdepth 1) to the find cmdline (again, this syntax used may
1566 * only be supported by the GNU version of find).
1567 *
1568 * RETURNS
1569 * Pointer to newly formulated shell command (allocated on the heap) or
1570 * NULL (failure).
1571 */
1572 static char *
find_all_files(char * cmd,FINDINFO * pinfo,int prepend_bang)1573 find_all_files(char *cmd, FINDINFO * pinfo, int prepend_bang)
1574 {
1575 size_t i, outidx, outlen, vecidx;
1576 const char *path, *fnname;
1577 char *xargstr, **vec, *rslt, buf[NFILEN * 2];
1578 const char *qdelim;
1579 int first = TRUE;
1580
1581 fnname = "find_all_files";
1582 if ((xargstr = extract_wildcards(cmd, &vec, &vecidx, fnname)) == NULL)
1583 return (NULL);
1584 if (vecidx == 0) {
1585 /* No wild cards were found on the command line. No sense
1586 * continuing. Why? With no wild cards, the find command
1587 * will search for all files by default, this may or may not
1588 * be what the user wants. It's certainly not what the user
1589 * wants if s/he types this by mistake:
1590 *
1591 * !<token>egrep -n FIXME 8.c
1592 *
1593 * which is an obvious slip of the fingers on the keyboard.
1594 * If the user really wants to examine all files, s/he can type:
1595 *
1596 * !<token>egrep -n FIXME *
1597 */
1598
1599 free_vector(&vec, vecidx);
1600 mlforce("[unless \"d\" option is set, shell command must include at least one wildcard]");
1601 return (NULL);
1602 }
1603 outlen = sizeof(buf);
1604 outidx = 0;
1605 rslt = castalloc(char, outlen);
1606 if (!rslt) {
1607 free_vector(&vec, vecidx);
1608 (void) no_memory(fnname);
1609 return (NULL);
1610 }
1611 if (prepend_bang) {
1612 *rslt = SHPIPE_LEFT[0];
1613 rslt[1] = EOS;
1614 outidx = 1;
1615 } else
1616 *rslt = '\0';
1617 strcat(rslt, "find ");
1618 outidx += sizeof("find ") - 1;
1619 path = pinfo->dir_list;
1620
1621 /* add directory list to find command */
1622 while ((path = parse_pathlist(path, buf, &first)) != NULL) {
1623 if (!add_token_to_cmd(&rslt, &outidx, &outlen, buf, fnname)) {
1624 free_vector(&vec, vecidx);
1625 return (NULL);
1626 }
1627 }
1628
1629 /* worry about nonrecursive find */
1630 if (pinfo->nonrecursive) {
1631 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-maxdepth 1", fnname)) {
1632 free_vector(&vec, vecidx);
1633 return (NULL);
1634 }
1635 }
1636
1637 /* follow symbolic links? */
1638 if (pinfo->cfg.follow) {
1639 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-follow", fnname)) {
1640 free_vector(&vec, vecidx);
1641 return (NULL);
1642 }
1643 }
1644
1645 if (vecidx > 1) {
1646 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "'('", fnname)) {
1647 free_vector(&vec, vecidx);
1648 return (NULL);
1649 }
1650 }
1651
1652 qdelim = determine_quoted_delimiter();
1653
1654 /* add wildcards (if any) to find command */
1655 for (i = 0; i < vecidx; i++) {
1656 sprintf(buf,
1657 "%s-" EGREP_OPT_CASELESS "name %s%s%s",
1658 (i != 0) ? "-o " : "",
1659 qdelim,
1660 vec[i],
1661 qdelim);
1662 if (!add_token_to_cmd(&rslt, &outidx, &outlen, buf, fnname)) {
1663 free_vector(&vec, vecidx);
1664 return (NULL);
1665 }
1666 }
1667 free_vector(&vec, vecidx); /* don't need this anymore */
1668
1669 if (vecidx > 1) {
1670 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "')'", fnname))
1671 return (NULL);
1672 }
1673
1674 /* terminate find string with "-print" (not needed for GNU find) */
1675 if (!add_token_to_cmd(&rslt, &outidx, &outlen, "-print", fnname))
1676 return (NULL);
1677
1678 /*
1679 * filter out RCS/CVS directories and tags files. we make the
1680 * assumption that the user's "find" creates filenames with
1681 * '/' as a path delimiter.
1682 *
1683 * ========================== FIXME ==================================
1684 * Note that this regular expression is simplistic and could be better
1685 * if it ensured that "RCS/CVS" are preceded by '/' or nothing.
1686 * ========================== FIXME ==================================
1687 */
1688 sprintf(buf,
1689 "| egrep -v" EGREP_OPT_CASELESS
1690 " %s((RCS|CVS)/|/" EGREP_TAG_CASELESS "$)%s",
1691 qdelim,
1692 qdelim);
1693 if (!add_token_to_cmd(&rslt, &outidx, &outlen, buf, fnname)) {
1694 rslt = NULL;
1695 } else if (!add_token_to_cmd(&rslt, &outidx, &outlen, "| xargs", fnname)) {
1696 rslt = NULL;
1697 } else if (!add_token_to_cmd(&rslt, &outidx, &outlen, xargstr, fnname)) {
1698 rslt = NULL;
1699 } else if (rslt != 0) {
1700 rslt[outidx] = EOS; /* terminate cmd string */
1701 if (outidx != 0) {
1702 char *cp;
1703
1704 i = --outidx;
1705 cp = rslt + outidx;
1706 while (isSpace(*cp) && i != 0) {
1707 cp--;
1708 i--;
1709 }
1710 if (cp != rslt + outidx) {
1711 /* white space found at end of string, trim it. */
1712
1713 cp[1] = EOS;
1714 }
1715 }
1716 }
1717 return (rslt);
1718 }
1719
1720 #endif /* OPT_FINDPATH */
1721