xref: /illumos-gate/usr/src/cmd/mdb/common/mdb/mdb_shell.c (revision 0c1b95be)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Shell Escape I/O Backend
29  *
30  * The MDB parser implements two forms of shell escapes: (1) traditional adb(1)
31  * style shell escapes of the form "!command", which simply allows the user to
32  * invoke a command (or shell pipeline) as if they had executed sh -c command
33  * and then return to the debugger, and (2) shell pipes of the form "dcmds !
34  * command", in which the output of one or more MDB dcmds is sent as standard
35  * input to a shell command (or shell pipeline).  Form (1) can be handled
36  * entirely from the parser by calling mdb_shell_exec (below); it simply
37  * forks the shell, executes the desired command, and waits for completion.
38  * Form (2) is slightly more complicated: we construct a UNIX pipe, fork
39  * the shell, and then built an fdio object out of the write end of the pipe.
40  * We then layer a shellio object (implemented below) and iob on top, and
41  * set mdb.m_out to point to this new iob.  The shellio is simply a pass-thru
42  * to the fdio, except that its io_close routine performs a waitpid for the
43  * forked child process.
44  */
45 
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <unistd.h>
49 #include <errno.h>
50 #include <stdlib.h>
51 #include <fcntl.h>
52 
53 #include <mdb/mdb_shell.h>
54 #include <mdb/mdb_lex.h>
55 #include <mdb/mdb_err.h>
56 #include <mdb/mdb_debug.h>
57 #include <mdb/mdb_string.h>
58 #include <mdb/mdb_frame.h>
59 #include <mdb/mdb_io_impl.h>
60 #include <mdb/mdb.h>
61 
62 #define	E_BADEXEC	127	/* Exit status for failed exec */
63 
64 /*
65  * We must manually walk the open file descriptors and set FD_CLOEXEC, because
66  * we need to be able to print an error if execlp() fails.  If mdb.m_err has
67  * been altered, then using closefrom() could close our output file descriptor,
68  * preventing us from displaying an error message.  Using FD_CLOEXEC ensures
69  * that the file descriptors are only closed if execlp() succeeds.
70  */
71 /*ARGSUSED*/
72 static int
closefd_walk(void * unused,int fd)73 closefd_walk(void *unused, int fd)
74 {
75 	if (fd > 2)
76 		(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
77 	return (0);
78 }
79 
80 void
mdb_shell_exec(char * cmd)81 mdb_shell_exec(char *cmd)
82 {
83 	int status;
84 	pid_t pid;
85 
86 	if (access(mdb.m_shell, X_OK) == -1)
87 		yyperror("cannot access %s", mdb.m_shell);
88 
89 	if ((pid = vfork()) == -1)
90 		yyperror("failed to fork");
91 
92 	if (pid == 0) {
93 		(void) fdwalk(closefd_walk, NULL);
94 		(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
95 		    "-c", cmd, NULL);
96 
97 		warn("failed to exec %s", mdb.m_shell);
98 		_exit(E_BADEXEC);
99 	}
100 
101 	do {
102 		mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
103 	} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
104 
105 	mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
106 	strfree(cmd);
107 }
108 
109 /*
110  * This use of the io_unlink entry point is a little strange: we have stacked
111  * the shellio on top of the fdio, but before the shellio's close routine can
112  * wait for the child process, we need to close the UNIX pipe file descriptor
113  * in order to generate an EOF to terminate the child.  Since each io is
114  * unlinked from its iob before being popped by mdb_iob_destroy, we use the
115  * io_unlink entry point to release the underlying fdio (forcing its io_close
116  * routine to be called) and remove it from the iob's i/o stack out of order.
117  */
118 
119 /*ARGSUSED*/
120 static void
shellio_unlink(mdb_io_t * io,mdb_iob_t * iob)121 shellio_unlink(mdb_io_t *io, mdb_iob_t *iob)
122 {
123 	mdb_io_t *fdio = io->io_next;
124 
125 	ASSERT(iob->iob_iop == io);
126 	ASSERT(fdio != NULL);
127 
128 	io->io_next = fdio->io_next;
129 	fdio->io_next = NULL;
130 	mdb_io_rele(fdio);
131 }
132 
133 static void
shellio_close(mdb_io_t * io)134 shellio_close(mdb_io_t *io)
135 {
136 	pid_t pid = (pid_t)(intptr_t)io->io_data;
137 	int status;
138 
139 	do {
140 		mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
141 	} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
142 
143 	mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
144 }
145 
146 static const mdb_io_ops_t shellio_ops = {
147 	.io_read = no_io_read,
148 	.io_write = no_io_write,
149 	.io_seek = no_io_seek,
150 	.io_ctl = no_io_ctl,
151 	.io_close = shellio_close,
152 	.io_name = no_io_name,
153 	.io_link = no_io_link,
154 	.io_unlink = shellio_unlink,
155 	.io_setattr = no_io_setattr,
156 	.io_suspend = no_io_suspend,
157 	.io_resume = no_io_resume
158 };
159 
160 void
mdb_shell_pipe(char * cmd)161 mdb_shell_pipe(char *cmd)
162 {
163 	uint_t iflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
164 	mdb_iob_t *iob;
165 	mdb_io_t *io;
166 	int pfds[2];
167 	pid_t pid;
168 
169 	if (access(mdb.m_shell, X_OK) == -1)
170 		yyperror("cannot access %s", mdb.m_shell);
171 
172 	if (pipe(pfds) == -1)
173 		yyperror("failed to open pipe");
174 
175 	iob = mdb_iob_create(mdb_fdio_create(pfds[1]), MDB_IOB_WRONLY | iflag);
176 	mdb_iob_clrflags(iob, MDB_IOB_AUTOWRAP | MDB_IOB_INDENT);
177 	mdb_iob_resize(iob, BUFSIZ, BUFSIZ);
178 
179 	if ((pid = vfork()) == -1) {
180 		(void) close(pfds[0]);
181 		(void) close(pfds[1]);
182 		mdb_iob_destroy(iob);
183 		yyperror("failed to fork");
184 	}
185 
186 	if (pid == 0) {
187 		(void) close(pfds[1]);
188 		(void) close(STDIN_FILENO);
189 		(void) dup2(pfds[0], STDIN_FILENO);
190 
191 		(void) fdwalk(closefd_walk, NULL);
192 		(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
193 		    "-c", cmd, NULL);
194 
195 		warn("failed to exec %s", mdb.m_shell);
196 		_exit(E_BADEXEC);
197 	}
198 
199 	(void) close(pfds[0]);
200 	strfree(cmd);
201 
202 	io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);
203 
204 	io->io_ops = &shellio_ops;
205 	io->io_data = (void *)(intptr_t)pid;
206 	io->io_next = NULL;
207 	io->io_refcnt = 0;
208 
209 	mdb_iob_stack_push(&mdb.m_frame->f_ostk, mdb.m_out, yylineno);
210 	mdb_iob_push_io(iob, io);
211 	mdb.m_out = iob;
212 }
213