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