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