xref: /illumos-gate/usr/src/cmd/vi/port/ex_unix.c (revision fe0e7ec4)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 /* Copyright (c) 1979 Regents of the University of California */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include "ex.h"
36 #include "ex_temp.h"
37 #include "ex_tty.h"
38 #include "ex_vis.h"
39 
40 extern int	getchar();
41 /*
42  * Unix escapes, filtering
43  */
44 
45 /*
46  * First part of a shell escape,
47  * parse the line, expanding # and % and ! and printing if implied.
48  */
49 void
50 unix0(bool warn, int contcmd)
51 {
52 	unsigned char *up, *fp;
53 	short c;
54 	char	multic[MB_LEN_MAX + 1];
55 	int	len;
56 	int	contread = 0;
57 	wchar_t	wc;
58 	unsigned char printub, puxb[UXBSIZE + sizeof (int)];
59 	const char	*specialchars = (contcmd ? "%#!\n" : "%#!");
60 
61 	printub = 0;
62 	CP(puxb, uxb);
63 	c = peekchar();
64 	if (c == '\n' || c == EOF) {
65 		(void) getchar();
66 		error(value(vi_TERSE) ?
67 gettext("Incomplete shell escape command") :
68 gettext("Incomplete shell escape command - use 'shell' to get a shell"));
69 	}
70 	up = (unsigned char *)uxb;
71 
72 	for (;;) {
73 		if (!isascii(c)) {
74 			if (c == EOF)
75 				break;
76 			if ((len = _mbftowc(multic, &wc, getchar, &peekc)) > 0) {
77 				if ((up + len) >= (unsigned char *)&uxb[UXBSIZE]) {
78 					uxb[0] = 0;
79 					error(gettext("Command too long"));
80 				}
81 				strncpy(up, multic, len);
82 				up += len;
83 				goto loop_check;
84 			}
85 		}
86 
87 		(void) getchar();
88 		switch (c) {
89 
90 		case '\\':
91 			if (any(peekchar(), specialchars)) {
92 				c = getchar();
93 				/*
94 				 * If we encountered a backslash-escaped
95 				 * newline, and we're processing a continuation
96 				 * command, then continue processing until
97 				 * non-backslash-escaped newline is reached.
98 				 */
99 				if (contcmd && (c == '\n')) {
100 					contread = 1;
101 				}
102 			}
103 		default:
104 			if (up >= (unsigned char *)&uxb[UXBSIZE]) {
105 tunix:
106 				uxb[0] = 0;
107 				error(gettext("Command too long"));
108 			}
109 			*up++ = c;
110 			break;
111 
112 		case '!':
113 			if (up != (unsigned char *)uxb && *puxb != 0) {
114 				fp = puxb;
115 				if (*fp == 0) {
116 					uxb[0] = 0;
117 					error(value(vi_TERSE) ?
118 gettext("No previous command") :
119 gettext("No previous command to substitute for !"));
120 				}
121 				printub++;
122 				while (*fp) {
123 					if (up >= (unsigned char *)&uxb[UXBSIZE])
124 						goto tunix;
125 					*up++ = *fp++;
126 				}
127 			} else if (up == (unsigned char *)uxb) {
128 				/* If up = uxb it means we are on the first
129 				 * character inside the shell command.
130 				 * (i.e., after the ":!")
131 				 *
132 				 * The user has just entered ":!!" which
133 				 * means that though there is only technically
134 				 * one '!' we know he really meant ":!!!". So
135 				 * substitute the last command for him.
136 				 */
137 				fp = puxb;
138 				if (*fp == 0) {
139 					uxb[0] = 0;
140 					error(value(vi_TERSE) ?
141 gettext("No previous command") :
142 gettext("No previous command to substitute for !"));
143 				}
144 				printub++;
145 				while (*fp) {
146 					if (up >= (unsigned char *)&uxb[UXBSIZE])
147 						goto tunix;
148 					*up++ = *fp++;
149 				}
150 			} else {
151 				/*
152 				 * Treat a lone "!" as just a regular character
153 				 * so commands like "mail machine!login" will
154 				 * work as usual (i.e., the user doesn't need
155 				 * to dereference the "!" with "\!").
156 				 */
157 				if (up >= (unsigned char *)&uxb[UXBSIZE]) {
158 					uxb[0] = 0;
159 					error(gettext("Command too long"));
160 				}
161 				*up++ = c;
162 			}
163 			break;
164 
165 		case '#':
166 			fp = (unsigned char *)altfile;
167 			if (*fp == 0) {
168 				uxb[0] = 0;
169 				error(value(vi_TERSE) ?
170 gettext("No alternate filename") :
171 gettext("No alternate filename to substitute for #"));
172 			}
173 			goto uexp;
174 
175 		case '%':
176 			fp = savedfile;
177 			if (*fp == 0) {
178 				uxb[0] = 0;
179 				error(value(vi_TERSE) ?
180 gettext("No filename") :
181 gettext("No filename to substitute for %%"));
182 			}
183 uexp:
184 			printub++;
185 			while (*fp) {
186 				if (up >= (unsigned char *)&uxb[UXBSIZE])
187 					goto tunix;
188 				*up++ = *fp++;
189 			}
190 			break;
191 		}
192 
193 loop_check:
194 		c = peekchar();
195 		if (c == '"' || c == '|' || (contread > 0) || !endcmd(c)) {
196 			/*
197 			 * If contread was set, then the newline just
198 			 * processed was preceeded by a backslash, and
199 			 * not considered the end of the command. Reset
200 			 * it here in case another backslash-escaped
201 			 * newline is processed.
202 			 */
203 			contread = 0;
204 			continue;
205 		} else {
206 			(void) getchar();
207 			break;
208 		}
209 	}
210 	if (c == EOF)
211 		ungetchar(c);
212 	*up = 0;
213 	if (!inopen)
214 		resetflav();
215 	if (warn)
216 		ckaw();
217 	if (warn && hush == 0 && chng && xchng != chng && value(vi_WARN) && dol > zero) {
218 		xchng = chng;
219 		vnfl();
220 		viprintf(mesg(value(vi_TERSE) ? gettext("[No write]") :
221 gettext("[No write since last change]")));
222 		noonl();
223 		flush();
224 	} else
225 		warn = 0;
226 	if (printub) {
227 		if (uxb[0] == 0)
228 			error(value(vi_TERSE) ? gettext("No previous command") :
229 gettext("No previous command to repeat"));
230 		if (inopen) {
231 			splitw++;
232 			vclean();
233 			vgoto(WECHO, 0);
234 		}
235 		if (warn)
236 			vnfl();
237 		if (hush == 0)
238 			lprintf("!%s", uxb);
239 		if (inopen && Outchar != termchar) {
240 			vclreol();
241 			vgoto(WECHO, 0);
242 		} else
243 			putnl();
244 		flush();
245 	}
246 }
247 
248 /*
249  * Do the real work for execution of a shell escape.
250  * Mode is like the number passed to open system calls
251  * and indicates filtering.  If input is implied, newstdin
252  * must have been setup already.
253  */
254 ttymode
255 unixex(opt, up, newstdin, mode)
256 	unsigned char *opt, *up;
257 	int newstdin, mode;
258 {
259 	int pvec[2];
260 	ttymode f;
261 
262 	signal(SIGINT, SIG_IGN);
263 #ifdef SIGTSTP
264 	if (dosusp)
265 		signal(SIGTSTP, SIG_DFL);
266 #endif
267 	if (inopen)
268 		f = setty(normf);
269 	if ((mode & 1) && pipe(pvec) < 0) {
270 		/* Newstdin should be io so it will be closed */
271 		if (inopen)
272 			setty(f);
273 		error(gettext("Can't make pipe for filter"));
274 	}
275 #ifndef VFORK
276 	pid = fork();
277 #else
278 	pid = vfork();
279 #endif
280 	if (pid < 0) {
281 		if (mode & 1) {
282 			close(pvec[0]);
283 			close(pvec[1]);
284 		}
285 		setrupt();
286 		if (inopen)
287 			setty(f);
288 		error(gettext("No more processes"));
289 	}
290 	if (pid == 0) {
291 		if (mode & 2) {
292 			close(0);
293 			dup(newstdin);
294 			close(newstdin);
295 		}
296 		if (mode & 1) {
297 			close(pvec[0]);
298 			close(1);
299 			dup(pvec[1]);
300 			if (inopen) {
301 				close(2);
302 				dup(1);
303 			}
304 			close(pvec[1]);
305 		}
306 		if (io)
307 			close(io);
308 		if (tfile)
309 			close(tfile);
310 		signal(SIGHUP, oldhup);
311 		signal(SIGQUIT, oldquit);
312 		if (ruptible)
313 			signal(SIGINT, SIG_DFL);
314 		execlp((char *)svalue(vi_SHELL), (char *)svalue(vi_SHELL),
315 		    opt, up, (char *)0);
316 		viprintf(gettext("Invalid SHELL value: %s\n"),
317 		    svalue(vi_SHELL));
318 		flush();
319 		error(NOSTR);
320 	}
321 	if (mode & 1) {
322 		io = pvec[0];
323 		close(pvec[1]);
324 	}
325 	if (newstdin)
326 		close(newstdin);
327 	return (f);
328 }
329 
330 /*
331  * Wait for the command to complete.
332  * F is for restoration of tty mode if from open/visual.
333  * C flags suppression of printing.
334  */
335 void
336 unixwt(c, f)
337 	bool c;
338 	ttymode f;
339 {
340 
341 	waitfor();
342 #ifdef SIGTSTP
343 	if (dosusp)
344 		signal(SIGTSTP, onsusp);
345 #endif
346 	if (inopen)
347 		setty(f);
348 	setrupt();
349 	if (!inopen && c && hush == 0) {
350 		viprintf("!\n");
351 		flush();
352 		termreset();
353 		gettmode();
354 	}
355 }
356 
357 /*
358  * Setup a pipeline for the filtration implied by mode
359  * which is like a open number.  If input is required to
360  * the filter, then a child editor is created to write it.
361  * If output is catch it from io which is created by unixex.
362  */
363 int
364 vi_filter(int mode)
365 {
366 	static int pvec[2];
367 	ttymode f;	/* was register */
368 	int nlines = lineDOL();
369 	int status2;
370 	pid_t pid2 = 0;
371 
372 	mode++;
373 	if (mode & 2) {
374 		signal(SIGINT, SIG_IGN);
375 		signal(SIGPIPE, SIG_IGN);
376 		if (pipe(pvec) < 0)
377 			error(gettext("Can't make pipe"));
378 		pid2 = fork();
379 		io = pvec[0];
380 		if (pid < 0) {
381 			setrupt();
382 			close(pvec[1]);
383 			error(gettext("No more processes"));
384 		}
385 		if (pid2 == 0) {
386 			extern unsigned char tfname[];
387 			setrupt();
388 			io = pvec[1];
389 			close(pvec[0]);
390 
391 			/* To prevent seeking in this process and the
392 				 parent, we must reopen tfile here */
393 			close(tfile);
394 			tfile = open(tfname, 2);
395 
396 			putfile(1);
397 			exit(errcnt);
398 		}
399 		close(pvec[1]);
400 		io = pvec[0];
401 		setrupt();
402 	}
403 	f = unixex("-c", uxb, (mode & 2) ? pvec[0] : 0, mode);
404 	if (mode == 3) {
405 		(void) delete(0);
406 		addr2 = addr1 - 1;
407 	}
408 	if (mode == 1)
409 		deletenone();
410 	if (mode & 1) {
411 		if(FIXUNDO)
412 			undap1 = undap2 = addr2+1;
413 		(void)append(getfile, addr2);
414 #ifdef UNDOTRACE
415 		if (trace)
416 			vudump(gettext("after append in filter"));
417 #endif
418 	}
419 	close(io);
420 	io = -1;
421 	unixwt(!inopen, f);
422 	if (pid2) {
423 		(void)kill(pid2, 9);
424 		do
425 			rpid = waitpid(pid2, &status2, 0);
426 		while (rpid == (pid_t)-1 && errno == EINTR);
427 	}
428 	netchHAD(nlines);
429 	return (0);
430 }
431 
432 /*
433  * Set up to do a recover, getting io to be a pipe from
434  * the recover process.
435  */
436 void
437 recover(void)
438 {
439 	static int pvec[2];
440 
441 	if (pipe(pvec) < 0)
442 		error(gettext(" Can't make pipe for recovery"));
443 	pid = fork();
444 	io = pvec[0];
445 	if (pid < 0) {
446 		close(pvec[1]);
447 		error(gettext(" Can't fork to execute recovery"));
448 	}
449 	if (pid == 0) {
450 		unsigned char cryptkey[19];
451 		close(2);
452 		dup(1);
453 		close(1);
454 		dup(pvec[1]);
455 	        close(pvec[1]);
456 		if(xflag) {
457 			strcpy(cryptkey, "CrYpTkEy=XXXXXXXXX");
458 			strcpy(cryptkey + 9, key);
459 			if(putenv((char *)cryptkey) != 0)
460 				smerror(gettext(" Cannot copy key to environment"));
461 			execlp(EXRECOVER, "exrecover", "-x", svalue(vi_DIRECTORY), file, (char *) 0);
462 		} else
463 			execlp(EXRECOVER, "exrecover", svalue(vi_DIRECTORY), file, (char *) 0);
464 		close(1);
465 		dup(2);
466 		error(gettext(" No recovery routine"));
467 	}
468 	close(pvec[1]);
469 }
470 
471 /*
472  * Wait for the process (pid an external) to complete.
473  */
474 void
475 waitfor(void)
476 {
477 
478 	do
479 		rpid = waitpid(pid, &status, 0);
480 	while (rpid == (pid_t)-1 && errno != ECHILD);
481 	if ((status & 0377) == 0)
482 		status = (status >> 8) & 0377;
483 	else {
484 		/*
485 		 * TRANSLATION_NOTE
486 		 *	Reference order of arguments must not
487 		 *	be changed using '%digit$', since vi's
488 		 *	viprintf() does not support it.
489 		 */
490 		viprintf(gettext("%d: terminated with signal %d"), pid,
491 		    status & 0177);
492 		if (status & 0200)
493 			viprintf(gettext(" -- core dumped"));
494 		putchar('\n');
495 	}
496 }
497 
498 /*
499  * The end of a recover operation.  If the process
500  * exits non-zero, force not edited; otherwise force
501  * a write.
502  */
503 void
504 revocer(void)
505 {
506 
507 	waitfor();
508 	if (pid == rpid && status != 0)
509 		edited = 0;
510 	else
511 		change();
512 }
513