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