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