mdb_shell.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Shell Escape I/O Backend
*
* The MDB parser implements two forms of shell escapes: (1) traditional adb(1)
* style shell escapes of the form "!command", which simply allows the user to
* invoke a command (or shell pipeline) as if they had executed sh -c command
* and then return to the debugger, and (2) shell pipes of the form "dcmds !
* command", in which the output of one or more MDB dcmds is sent as standard
* input to a shell command (or shell pipeline). Form (1) can be handled
* entirely from the parser by calling mdb_shell_exec (below); it simply
* forks the shell, executes the desired command, and waits for completion.
* Form (2) is slightly more complicated: we construct a UNIX pipe, fork
* the shell, and then built an fdio object out of the write end of the pipe.
* We then layer a shellio object (implemented below) and iob on top, and
* set mdb.m_out to point to this new iob. The shellio is simply a pass-thru
* to the fdio, except that its io_close routine performs a waitpid for the
* forked child process.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <mdb/mdb_shell.h>
#include <mdb/mdb_lex.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb.h>
#define E_BADEXEC 127 /* Exit status for failed exec */
/*
* We must manually walk the open file descriptors and set FD_CLOEXEC, because
* we need to be able to print an error if execlp() fails. If mdb.m_err has
* been altered, then using closefrom() could close our output file descriptor,
* preventing us from displaying an error message. Using FD_CLOEXEC ensures
* that the file descriptors are only closed if execlp() succeeds.
*/
/*ARGSUSED*/
static int
closefd_walk(void *unused, int fd)
{
if (fd > 2)
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
return (0);
}
void
mdb_shell_exec(char *cmd)
{
int status;
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1)
yyperror("cannot access %s", mdb.m_shell);
if ((pid = vfork()) == -1)
yyperror("failed to fork");
if (pid == 0) {
(void) fdwalk(closefd_walk, NULL);
(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
"-c", cmd, NULL);
warn("failed to exec %s", mdb.m_shell);
_exit(E_BADEXEC);
}
do {
mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
strfree(cmd);
}
/*
* This use of the io_unlink entry point is a little strange: we have stacked
* the shellio on top of the fdio, but before the shellio's close routine can
* wait for the child process, we need to close the UNIX pipe file descriptor
* in order to generate an EOF to terminate the child. Since each io is
* unlinked from its iob before being popped by mdb_iob_destroy, we use the
* io_unlink entry point to release the underlying fdio (forcing its io_close
* routine to be called) and remove it from the iob's i/o stack out of order.
*/
/*ARGSUSED*/
static void
shellio_unlink(mdb_io_t *io, mdb_iob_t *iob)
{
mdb_io_t *fdio = io->io_next;
ASSERT(iob->iob_iop == io);
ASSERT(fdio != NULL);
io->io_next = fdio->io_next;
fdio->io_next = NULL;
mdb_io_rele(fdio);
}
static void
shellio_close(mdb_io_t *io)
{
pid_t pid = (pid_t)(intptr_t)io->io_data;
int status;
do {
mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
}
static const mdb_io_ops_t shellio_ops = {
no_io_read,
no_io_write,
no_io_seek,
no_io_ctl,
shellio_close,
no_io_name,
no_io_link,
shellio_unlink,
no_io_setattr,
no_io_suspend,
no_io_resume
};
void
mdb_shell_pipe(char *cmd)
{
uint_t iflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
mdb_iob_t *iob;
mdb_io_t *io;
int pfds[2];
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1)
yyperror("cannot access %s", mdb.m_shell);
if (pipe(pfds) == -1)
yyperror("failed to open pipe");
iob = mdb_iob_create(mdb_fdio_create(pfds[1]), MDB_IOB_WRONLY | iflag);
mdb_iob_clrflags(iob, MDB_IOB_AUTOWRAP | MDB_IOB_INDENT);
mdb_iob_resize(iob, BUFSIZ, BUFSIZ);
if ((pid = vfork()) == -1) {
(void) close(pfds[0]);
(void) close(pfds[1]);
mdb_iob_destroy(iob);
yyperror("failed to fork");
}
if (pid == 0) {
(void) close(pfds[1]);
(void) close(STDIN_FILENO);
(void) dup2(pfds[0], STDIN_FILENO);
(void) fdwalk(closefd_walk, NULL);
(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
"-c", cmd, NULL);
warn("failed to exec %s", mdb.m_shell);
_exit(E_BADEXEC);
}
(void) close(pfds[0]);
strfree(cmd);
io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);
io->io_ops = &shellio_ops;
io->io_data = (void *)(intptr_t)pid;
io->io_next = NULL;
io->io_refcnt = 0;
mdb_iob_stack_push(&mdb.m_frame->f_ostk, mdb.m_out, yylineno);
mdb_iob_push_io(iob, io);
mdb.m_out = iob;
}