xref: /illumos-gate/usr/src/cmd/vi/port/ex_unix.c (revision 503609a9497e27f206d815a06ce90a747d2ce573)
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  * Copyright (c) 2016 by Delphix. All rights reserved.
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 			/*
110 			 * If this is a tag command (-t or :tag),
111 			 * then don't save any command that follows
112 			 * '!' in the invalid tags file, ie:
113 			 * '!!' should not repeat the invalid command
114 			 * later on when tagflg has been cleared.
115 			 */
116 			if (!tagflg)
117 				*up++ = c;
118 			break;
119 
120 		case '!':
121 			if (up != (unsigned char *)uxb && *puxb != 0) {
122 				fp = puxb;
123 				if (*fp == 0) {
124 					uxb[0] = 0;
125 					error(value(vi_TERSE) ?
126 gettext("No previous command") :
127 gettext("No previous command to substitute for !"));
128 				}
129 				printub++;
130 				while (*fp) {
131 					if (up >= (unsigned char *)&uxb[UXBSIZE])
132 						goto tunix;
133 					*up++ = *fp++;
134 				}
135 			} else if (up == (unsigned char *)uxb) {
136 				/* If up = uxb it means we are on the first
137 				 * character inside the shell command.
138 				 * (i.e., after the ":!")
139 				 *
140 				 * The user has just entered ":!!" which
141 				 * means that though there is only technically
142 				 * one '!' we know they really meant ":!!!". So
143 				 * substitute the last command for them.
144 				 */
145 				fp = puxb;
146 				if (*fp == 0) {
147 					uxb[0] = 0;
148 					error(value(vi_TERSE) ?
149 gettext("No previous command") :
150 gettext("No previous command to substitute for !"));
151 				}
152 				printub++;
153 				while (*fp) {
154 					if (up >= (unsigned char *)&uxb[UXBSIZE])
155 						goto tunix;
156 					*up++ = *fp++;
157 				}
158 			} else {
159 				/*
160 				 * Treat a lone "!" as just a regular character
161 				 * so commands like "mail machine!login" will
162 				 * work as usual (i.e., the user doesn't need
163 				 * to dereference the "!" with "\!").
164 				 */
165 				if (up >= (unsigned char *)&uxb[UXBSIZE]) {
166 					uxb[0] = 0;
167 					error(gettext("Command too long"));
168 				}
169 				*up++ = c;
170 			}
171 			break;
172 
173 		case '#':
174 			fp = (unsigned char *)altfile;
175 			if (*fp == 0) {
176 				uxb[0] = 0;
177 				error(value(vi_TERSE) ?
178 gettext("No alternate filename") :
179 gettext("No alternate filename to substitute for #"));
180 			}
181 			goto uexp;
182 
183 		case '%':
184 			fp = savedfile;
185 			if (*fp == 0) {
186 				uxb[0] = 0;
187 				error(value(vi_TERSE) ?
188 gettext("No filename") :
189 gettext("No filename to substitute for %%"));
190 			}
191 uexp:
192 			printub++;
193 			while (*fp) {
194 				if (up >= (unsigned char *)&uxb[UXBSIZE])
195 					goto tunix;
196 				*up++ = *fp++;
197 			}
198 			break;
199 		}
200 
201 loop_check:
202 		c = peekchar();
203 		if (c == '"' || c == '|' || (contread > 0) || !endcmd(c)) {
204 			/*
205 			 * If contread was set, then the newline just
206 			 * processed was preceeded by a backslash, and
207 			 * not considered the end of the command. Reset
208 			 * it here in case another backslash-escaped
209 			 * newline is processed.
210 			 */
211 			contread = 0;
212 			continue;
213 		} else {
214 			(void) getchar();
215 			break;
216 		}
217 	}
218 	if (c == EOF)
219 		ungetchar(c);
220 	*up = 0;
221 	if (!inopen)
222 		resetflav();
223 	if (warn)
224 		ckaw();
225 	if (warn && hush == 0 && chng && xchng != chng && value(vi_WARN) && dol > zero) {
226 		xchng = chng;
227 		vnfl();
228 		viprintf(mesg(value(vi_TERSE) ? gettext("[No write]") :
229 gettext("[No write since last change]")));
230 		noonl();
231 		flush();
232 	} else
233 		warn = 0;
234 	if (printub) {
235 		if (uxb[0] == 0)
236 			error(value(vi_TERSE) ? gettext("No previous command") :
237 gettext("No previous command to repeat"));
238 		if (inopen) {
239 			splitw++;
240 			vclean();
241 			vgoto(WECHO, 0);
242 		}
243 		if (warn)
244 			vnfl();
245 		if (hush == 0)
246 			lprintf("!%s", uxb);
247 		if (inopen && Outchar != termchar) {
248 			vclreol();
249 			vgoto(WECHO, 0);
250 		} else
251 			putnl();
252 		flush();
253 	}
254 }
255 
256 /*
257  * Do the real work for execution of a shell escape.
258  * Mode is like the number passed to open system calls
259  * and indicates filtering.  If input is implied, newstdin
260  * must have been setup already.
261  */
262 ttymode
263 unixex(opt, up, newstdin, mode)
264 	unsigned char *opt, *up;
265 	int newstdin, mode;
266 {
267 	int pvec[2];
268 	ttymode f;
269 
270 	signal(SIGINT, SIG_IGN);
271 #ifdef SIGTSTP
272 	if (dosusp)
273 		signal(SIGTSTP, SIG_DFL);
274 #endif
275 	if (inopen)
276 		f = setty(normf);
277 	if ((mode & 1) && pipe(pvec) < 0) {
278 		/* Newstdin should be io so it will be closed */
279 		if (inopen)
280 			setty(f);
281 		error(gettext("Can't make pipe for filter"));
282 	}
283 #ifndef VFORK
284 	pid = fork();
285 #else
286 	pid = vfork();
287 #endif
288 	if (pid < 0) {
289 		if (mode & 1) {
290 			close(pvec[0]);
291 			close(pvec[1]);
292 		}
293 		setrupt();
294 		if (inopen)
295 			setty(f);
296 		error(gettext("No more processes"));
297 	}
298 	if (pid == 0) {
299 		if (mode & 2) {
300 			close(0);
301 			dup(newstdin);
302 			close(newstdin);
303 		}
304 		if (mode & 1) {
305 			close(pvec[0]);
306 			close(1);
307 			dup(pvec[1]);
308 			if (inopen) {
309 				close(2);
310 				dup(1);
311 			}
312 			close(pvec[1]);
313 		}
314 		if (io)
315 			close(io);
316 		if (tfile)
317 			close(tfile);
318 		signal(SIGHUP, oldhup);
319 		signal(SIGQUIT, oldquit);
320 		if (ruptible)
321 			signal(SIGINT, SIG_DFL);
322 		execlp((char *)svalue(vi_SHELL), (char *)svalue(vi_SHELL),
323 		    opt, up, (char *)0);
324 		viprintf(gettext("Invalid SHELL value: %s\n"),
325 		    svalue(vi_SHELL));
326 		flush();
327 		error(NOSTR);
328 	}
329 	if (mode & 1) {
330 		io = pvec[0];
331 		close(pvec[1]);
332 	}
333 	if (newstdin)
334 		close(newstdin);
335 	return (f);
336 }
337 
338 /*
339  * Wait for the command to complete.
340  * F is for restoration of tty mode if from open/visual.
341  * C flags suppression of printing.
342  */
343 void
344 unixwt(c, f)
345 	bool c;
346 	ttymode f;
347 {
348 
349 	waitfor();
350 #ifdef SIGTSTP
351 	if (dosusp)
352 		signal(SIGTSTP, onsusp);
353 #endif
354 	if (inopen)
355 		setty(f);
356 	setrupt();
357 	if (!inopen && c && hush == 0) {
358 		viprintf("!\n");
359 		flush();
360 		termreset();
361 		gettmode();
362 	}
363 }
364 
365 /*
366  * Setup a pipeline for the filtration implied by mode
367  * which is like a open number.  If input is required to
368  * the filter, then a child editor is created to write it.
369  * If output is catch it from io which is created by unixex.
370  */
371 int
372 vi_filter(int mode)
373 {
374 	static int pvec[2];
375 	ttymode f;	/* was register */
376 	int nlines = lineDOL();
377 	int status2;
378 	pid_t pid2 = 0;
379 
380 	mode++;
381 	if (mode & 2) {
382 		signal(SIGINT, SIG_IGN);
383 		signal(SIGPIPE, SIG_IGN);
384 		if (pipe(pvec) < 0)
385 			error(gettext("Can't make pipe"));
386 		pid2 = fork();
387 		io = pvec[0];
388 		if (pid < 0) {
389 			setrupt();
390 			close(pvec[1]);
391 			error(gettext("No more processes"));
392 		}
393 		if (pid2 == 0) {
394 			extern unsigned char tfname[];
395 			setrupt();
396 			io = pvec[1];
397 			close(pvec[0]);
398 
399 			/* To prevent seeking in this process and the
400 				 parent, we must reopen tfile here */
401 			close(tfile);
402 			tfile = open(tfname, 2);
403 
404 			putfile(1);
405 			exit(errcnt);
406 		}
407 		close(pvec[1]);
408 		io = pvec[0];
409 		setrupt();
410 	}
411 	f = unixex("-c", uxb, (mode & 2) ? pvec[0] : 0, mode);
412 	if (mode == 3) {
413 		(void) delete(0);
414 		addr2 = addr1 - 1;
415 	}
416 	if (mode == 1)
417 		deletenone();
418 	if (mode & 1) {
419 		if(FIXUNDO)
420 			undap1 = undap2 = addr2+1;
421 		(void)append(getfile, addr2);
422 #ifdef UNDOTRACE
423 		if (trace)
424 			vudump(gettext("after append in filter"));
425 #endif
426 	}
427 	close(io);
428 	io = -1;
429 	unixwt(!inopen, f);
430 	if (pid2) {
431 		(void)kill(pid2, 9);
432 		do
433 			rpid = waitpid(pid2, &status2, 0);
434 		while (rpid == (pid_t)-1 && errno == EINTR);
435 	}
436 	netchHAD(nlines);
437 	return (0);
438 }
439 
440 /*
441  * Set up to do a recover, getting io to be a pipe from
442  * the recover process.
443  */
444 void
445 recover(void)
446 {
447 	static int pvec[2];
448 
449 	if (pipe(pvec) < 0)
450 		error(gettext(" Can't make pipe for recovery"));
451 	pid = fork();
452 	io = pvec[0];
453 	if (pid < 0) {
454 		close(pvec[1]);
455 		error(gettext(" Can't fork to execute recovery"));
456 	}
457 	if (pid == 0) {
458 		unsigned char cryptkey[19];
459 		close(2);
460 		dup(1);
461 		close(1);
462 		dup(pvec[1]);
463 	        close(pvec[1]);
464 		if(xflag) {
465 			strcpy(cryptkey, "CrYpTkEy=XXXXXXXXX");
466 			strcpy(cryptkey + 9, key);
467 			if(putenv((char *)cryptkey) != 0)
468 				smerror(gettext(" Cannot copy key to environment"));
469 			execlp(EXRECOVER, "exrecover", "-x", svalue(vi_DIRECTORY), file, (char *) 0);
470 		} else
471 			execlp(EXRECOVER, "exrecover", svalue(vi_DIRECTORY), file, (char *) 0);
472 		close(1);
473 		dup(2);
474 		error(gettext(" No recovery routine"));
475 	}
476 	close(pvec[1]);
477 }
478 
479 /*
480  * Wait for the process (pid an external) to complete.
481  */
482 void
483 waitfor(void)
484 {
485 
486 	do
487 		rpid = waitpid(pid, &status, 0);
488 	while (rpid == (pid_t)-1 && errno != ECHILD);
489 	if ((status & 0377) == 0)
490 		status = (status >> 8) & 0377;
491 	else {
492 		/*
493 		 * TRANSLATION_NOTE
494 		 *	Reference order of arguments must not
495 		 *	be changed using '%digit$', since vi's
496 		 *	viprintf() does not support it.
497 		 */
498 		viprintf(gettext("%d: terminated with signal %d"), pid,
499 		    status & 0177);
500 		if (status & 0200)
501 			viprintf(gettext(" -- core dumped"));
502 		putchar('\n');
503 	}
504 }
505 
506 /*
507  * The end of a recover operation.  If the process
508  * exits non-zero, force not edited; otherwise force
509  * a write.
510  */
511 void
512 revocer(void)
513 {
514 
515 	waitfor();
516 	if (pid == rpid && status != 0)
517 		edited = 0;
518 	else
519 		change();
520 }
521