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 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 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 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 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 no_io_read, 148 no_io_write, 149 no_io_seek, 150 no_io_ctl, 151 shellio_close, 152 no_io_name, 153 no_io_link, 154 shellio_unlink, 155 no_io_setattr, 156 no_io_suspend, 157 no_io_resume 158 }; 159 160 void 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