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 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 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 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 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 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