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
13static 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
32static const char *sigmsg __P((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 __P((SCR *, EXCMD *));
40 */
41int
42ex_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 __P((SCR *, EXCMD *, char *, const char *, int));
86 */
87int
88ex_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 __P((SCR *, long, const char *, int, int));
151 */
152int
153proc_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 */
216static const char *
217sigmsg(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