xref: /freebsd/contrib/nvi/ex/ex_shell.c (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #ifndef lint
13 static const char sccsid[] = "$Id: ex_shell.c,v 10.44 2012/07/06 06:51:26 zy Exp $";
14 #endif /* not lint */
15 
16 #include <sys/queue.h>
17 #include <sys/time.h>
18 #include <sys/wait.h>
19 
20 #include <bitstring.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "../common/common.h"
31 
32 static const char *sigmsg(int);
33 
34 /*
35  * ex_shell -- :sh[ell]
36  *	Invoke the program named in the SHELL environment variable
37  *	with the argument -i.
38  *
39  * PUBLIC: int ex_shell(SCR *, EXCMD *);
40  */
41 int
42 ex_shell(SCR *sp, EXCMD *cmdp)
43 {
44 	int rval;
45 	char *buf;
46 
47 	/* We'll need a shell. */
48 	if (opts_empty(sp, O_SHELL, 0))
49 		return (1);
50 
51 	/*
52 	 * XXX
53 	 * Assumes all shells use -i.
54 	 */
55 	(void)asprintf(&buf, "%s -i", O_STR(sp, O_SHELL));
56 	if (buf == NULL) {
57 		msgq(sp, M_SYSERR, NULL);
58 		return (1);
59 	}
60 
61 	/* Restore the window name. */
62 	(void)sp->gp->scr_rename(sp, NULL, 0);
63 
64 	/* If we're still in a vi screen, move out explicitly. */
65 	rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE));
66 	free(buf);
67 
68 	/* Set the window name. */
69 	(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
70 
71 	/*
72 	 * !!!
73 	 * Historically, vi didn't require a continue message after the
74 	 * return of the shell.  Match it.
75 	 */
76 	F_SET(sp, SC_EX_WAIT_NO);
77 
78 	return (rval);
79 }
80 
81 /*
82  * ex_exec_proc --
83  *	Run a separate process.
84  *
85  * PUBLIC: int ex_exec_proc(SCR *, EXCMD *, char *, const char *, int);
86  */
87 int
88 ex_exec_proc(SCR *sp, EXCMD *cmdp, char *cmd, const char *msg, int need_newline)
89 {
90 	GS *gp;
91 	const char *name;
92 	pid_t pid;
93 
94 	gp = sp->gp;
95 
96 	/* We'll need a shell. */
97 	if (opts_empty(sp, O_SHELL, 0))
98 		return (1);
99 
100 	/* Enter ex mode. */
101 	if (F_ISSET(sp, SC_VI)) {
102 		if (gp->scr_screen(sp, SC_EX)) {
103 			ex_wemsg(sp, cmdp->cmd->name, EXM_NOCANON);
104 			return (1);
105 		}
106 		(void)gp->scr_attr(sp, SA_ALTERNATE, 0);
107 		F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE);
108 	}
109 
110 	/* Put out additional newline, message. */
111 	if (need_newline)
112 		(void)ex_puts(sp, "\n");
113 	if (msg != NULL) {
114 		(void)ex_puts(sp, msg);
115 		(void)ex_puts(sp, "\n");
116 	}
117 	(void)ex_fflush(sp);
118 
119 	switch (pid = vfork()) {
120 	case -1:			/* Error. */
121 		msgq(sp, M_SYSERR, "vfork");
122 		return (1);
123 	case 0:				/* Utility. */
124 		if (gp->scr_child)
125 			gp->scr_child(sp);
126 		if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
127 			name = O_STR(sp, O_SHELL);
128 		else
129 			++name;
130 		execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL);
131 		msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
132 		_exit(127);
133 		/* NOTREACHED */
134 	default:			/* Parent. */
135 		return (proc_wait(sp, (long)pid, cmd, 0, 0));
136 	}
137 	/* NOTREACHED */
138 }
139 
140 /*
141  * proc_wait --
142  *	Wait for one of the processes.
143  *
144  * !!!
145  * The pid_t type varies in size from a short to a long depending on the
146  * system.  It has to be cast into something or the standard promotion
147  * rules get you.  I'm using a long based on the belief that nobody is
148  * going to make it unsigned and it's unlikely to be a quad.
149  *
150  * PUBLIC: int proc_wait(SCR *, long, const char *, int, int);
151  */
152 int
153 proc_wait(SCR *sp, long int pid, const char *cmd, int silent, int okpipe)
154 {
155 	size_t len;
156 	int nf, pstat;
157 	char *p;
158 
159 	/* Wait for the utility, ignoring interruptions. */
160 	for (;;) {
161 		errno = 0;
162 		if (waitpid((pid_t)pid, &pstat, 0) != -1)
163 			break;
164 		if (errno != EINTR) {
165 			msgq(sp, M_SYSERR, "waitpid");
166 			return (1);
167 		}
168 	}
169 
170 	/*
171 	 * Display the utility's exit status.  Ignore SIGPIPE from the
172 	 * parent-writer, as that only means that the utility chose to
173 	 * exit before reading all of its input.
174 	 */
175 	if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) {
176 		for (; cmdskip(*cmd); ++cmd);
177 		p = msg_print(sp, cmd, &nf);
178 		len = strlen(p);
179 		msgq(sp, M_ERR, "%.*s%s: received signal: %s%s",
180 		    (int)MIN(len, 20), p, len > 20 ? " ..." : "",
181 		    sigmsg(WTERMSIG(pstat)),
182 		    WCOREDUMP(pstat) ? "; core dumped" : "");
183 		if (nf)
184 			FREE_SPACE(sp, p, 0);
185 		return (1);
186 	}
187 
188 	if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) {
189 		/*
190 		 * Remain silent for "normal" errors when doing shell file
191 		 * name expansions, they almost certainly indicate nothing
192 		 * more than a failure to match.
193 		 *
194 		 * Remain silent for vi read filter errors.  It's historic
195 		 * practice.
196 		 */
197 		if (!silent) {
198 			for (; cmdskip(*cmd); ++cmd);
199 			p = msg_print(sp, cmd, &nf);
200 			len = strlen(p);
201 			msgq(sp, M_ERR, "%.*s%s: exited with status %d",
202 			    (int)MIN(len, 20), p, len > 20 ? " ..." : "",
203 			    WEXITSTATUS(pstat));
204 			if (nf)
205 				FREE_SPACE(sp, p, 0);
206 		}
207 		return (1);
208 	}
209 	return (0);
210 }
211 
212 /*
213  * sigmsg --
214  * 	Return a pointer to a message describing a signal.
215  */
216 static const char *
217 sigmsg(int signo)
218 {
219 	static char buf[40];
220 	char *message;
221 
222 	/* POSIX.1-2008 leaves strsignal(3)'s return value unspecified. */
223 	if ((message = strsignal(signo)) != NULL)
224 		return message;
225 	(void)snprintf(buf, sizeof(buf), "Unknown signal: %d", signo);
226 	return (buf);
227 }
228