17c478bdstevel@tonic-gate/*
27c478bdstevel@tonic-gate * CDDL HEADER START
37c478bdstevel@tonic-gate *
47c478bdstevel@tonic-gate * The contents of this file are subject to the terms of the
57c478bdstevel@tonic-gate * Common Development and Distribution License, Version 1.0 only
67c478bdstevel@tonic-gate * (the "License").  You may not use this file except in compliance
77c478bdstevel@tonic-gate * with the License.
87c478bdstevel@tonic-gate *
97c478bdstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
107c478bdstevel@tonic-gate * or http://www.opensolaris.org/os/licensing.
117c478bdstevel@tonic-gate * See the License for the specific language governing permissions
127c478bdstevel@tonic-gate * and limitations under the License.
137c478bdstevel@tonic-gate *
147c478bdstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each
157c478bdstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
167c478bdstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the
177c478bdstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying
187c478bdstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner]
197c478bdstevel@tonic-gate *
207c478bdstevel@tonic-gate * CDDL HEADER END
217c478bdstevel@tonic-gate */
227c478bdstevel@tonic-gate/*
237c478bdstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
247c478bdstevel@tonic-gate * Use is subject to license terms.
257c478bdstevel@tonic-gate */
267c478bdstevel@tonic-gate
277c478bdstevel@tonic-gate#pragma ident	"%Z%%M%	%I%	%E% SMI"
287c478bdstevel@tonic-gate
297c478bdstevel@tonic-gate/*
307c478bdstevel@tonic-gate * Shell Escape I/O Backend
317c478bdstevel@tonic-gate *
327c478bdstevel@tonic-gate * The MDB parser implements two forms of shell escapes: (1) traditional adb(1)
337c478bdstevel@tonic-gate * style shell escapes of the form "!command", which simply allows the user to
347c478bdstevel@tonic-gate * invoke a command (or shell pipeline) as if they had executed sh -c command
357c478bdstevel@tonic-gate * and then return to the debugger, and (2) shell pipes of the form "dcmds !
367c478bdstevel@tonic-gate * command", in which the output of one or more MDB dcmds is sent as standard
377c478bdstevel@tonic-gate * input to a shell command (or shell pipeline).  Form (1) can be handled
387c478bdstevel@tonic-gate * entirely from the parser by calling mdb_shell_exec (below); it simply
397c478bdstevel@tonic-gate * forks the shell, executes the desired command, and waits for completion.
407c478bdstevel@tonic-gate * Form (2) is slightly more complicated: we construct a UNIX pipe, fork
417c478bdstevel@tonic-gate * the shell, and then built an fdio object out of the write end of the pipe.
427c478bdstevel@tonic-gate * We then layer a shellio object (implemented below) and iob on top, and
437c478bdstevel@tonic-gate * set mdb.m_out to point to this new iob.  The shellio is simply a pass-thru
447c478bdstevel@tonic-gate * to the fdio, except that its io_close routine performs a waitpid for the
457c478bdstevel@tonic-gate * forked child process.
467c478bdstevel@tonic-gate */
477c478bdstevel@tonic-gate
487c478bdstevel@tonic-gate#include <sys/types.h>
497c478bdstevel@tonic-gate#include <sys/wait.h>
507c478bdstevel@tonic-gate#include <unistd.h>
517c478bdstevel@tonic-gate#include <errno.h>
527c478bdstevel@tonic-gate#include <stdlib.h>
537c478bdstevel@tonic-gate#include <fcntl.h>
547c478bdstevel@tonic-gate
557c478bdstevel@tonic-gate#include <mdb/mdb_shell.h>
567c478bdstevel@tonic-gate#include <mdb/mdb_lex.h>
577c478bdstevel@tonic-gate#include <mdb/mdb_err.h>
587c478bdstevel@tonic-gate#include <mdb/mdb_debug.h>
597c478bdstevel@tonic-gate#include <mdb/mdb_string.h>
607c478bdstevel@tonic-gate#include <mdb/mdb_frame.h>
617c478bdstevel@tonic-gate#include <mdb/mdb_io_impl.h>
627c478bdstevel@tonic-gate#include <mdb/mdb.h>
637c478bdstevel@tonic-gate
647c478bdstevel@tonic-gate#define	E_BADEXEC	127	/* Exit status for failed exec */
657c478bdstevel@tonic-gate
667c478bdstevel@tonic-gate/*
677c478bdstevel@tonic-gate * We must manually walk the open file descriptors and set FD_CLOEXEC, because
687c478bdstevel@tonic-gate * we need to be able to print an error if execlp() fails.  If mdb.m_err has
697c478bdstevel@tonic-gate * been altered, then using closefrom() could close our output file descriptor,
707c478bdstevel@tonic-gate * preventing us from displaying an error message.  Using FD_CLOEXEC ensures
717c478bdstevel@tonic-gate * that the file descriptors are only closed if execlp() succeeds.
727c478bdstevel@tonic-gate */
737c478bdstevel@tonic-gate/*ARGSUSED*/
747c478bdstevel@tonic-gatestatic int
757c478bdstevel@tonic-gateclosefd_walk(void *unused, int fd)
767c478bdstevel@tonic-gate{
777c478bdstevel@tonic-gate	if (fd > 2)
787c478bdstevel@tonic-gate		(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
797c478bdstevel@tonic-gate	return (0);
807c478bdstevel@tonic-gate}
817c478bdstevel@tonic-gate
827c478bdstevel@tonic-gatevoid
837c478bdstevel@tonic-gatemdb_shell_exec(char *cmd)
847c478bdstevel@tonic-gate{
857c478bdstevel@tonic-gate	int status;
867c478bdstevel@tonic-gate	pid_t pid;
877c478bdstevel@tonic-gate
887c478bdstevel@tonic-gate	if (access(mdb.m_shell, X_OK) == -1)
897c478bdstevel@tonic-gate		yyperror("cannot access %s", mdb.m_shell);
907c478bdstevel@tonic-gate
917c478bdstevel@tonic-gate	if ((pid = vfork()) == -1)
927c478bdstevel@tonic-gate		yyperror("failed to fork");
937c478bdstevel@tonic-gate
947c478bdstevel@tonic-gate	if (pid == 0) {
957c478bdstevel@tonic-gate		(void) fdwalk(closefd_walk, NULL);
967c478bdstevel@tonic-gate		(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
977c478bdstevel@tonic-gate		    "-c", cmd, NULL);
987c478bdstevel@tonic-gate
997c478bdstevel@tonic-gate		warn("failed to exec %s", mdb.m_shell);
1007c478bdstevel@tonic-gate		_exit(E_BADEXEC);
1017c478bdstevel@tonic-gate	}
1027c478bdstevel@tonic-gate
1037c478bdstevel@tonic-gate	do {
1047c478bdstevel@tonic-gate		mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
1057c478bdstevel@tonic-gate	} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
1067c478bdstevel@tonic-gate
1077c478bdstevel@tonic-gate	mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
1087c478bdstevel@tonic-gate	strfree(cmd);
1097c478bdstevel@tonic-gate}
1107c478bdstevel@tonic-gate
1117c478bdstevel@tonic-gate/*
1127c478bdstevel@tonic-gate * This use of the io_unlink entry point is a little strange: we have stacked
1137c478bdstevel@tonic-gate * the shellio on top of the fdio, but before the shellio's close routine can
1147c478bdstevel@tonic-gate * wait for the child process, we need to close the UNIX pipe file descriptor
1157c478bdstevel@tonic-gate * in order to generate an EOF to terminate the child.  Since each io is
1167c478bdstevel@tonic-gate * unlinked from its iob before being popped by mdb_iob_destroy, we use the
1177c478bdstevel@tonic-gate * io_unlink entry point to release the underlying fdio (forcing its io_close
1187c478bdstevel@tonic-gate * routine to be called) and remove it from the iob's i/o stack out of order.
1197c478bdstevel@tonic-gate */
1207c478bdstevel@tonic-gate
1217c478bdstevel@tonic-gate/*ARGSUSED*/
1227c478bdstevel@tonic-gatestatic void
1237c478bdstevel@tonic-gateshellio_unlink(mdb_io_t *io, mdb_iob_t *iob)
1247c478bdstevel@tonic-gate{
1257c478bdstevel@tonic-gate	mdb_io_t *fdio = io->io_next;
1267c478bdstevel@tonic-gate
1277c478bdstevel@tonic-gate	ASSERT(iob->iob_iop == io);
1287c478bdstevel@tonic-gate	ASSERT(fdio != NULL);
1297c478bdstevel@tonic-gate
1307c478bdstevel@tonic-gate	io->io_next = fdio->io_next;
1317c478bdstevel@tonic-gate	fdio->io_next = NULL;
1327c478bdstevel@tonic-gate	mdb_io_rele(fdio);
1337c478bdstevel@tonic-gate}
1347c478bdstevel@tonic-gate
1357c478bdstevel@tonic-gatestatic void
1367c478bdstevel@tonic-gateshellio_close(mdb_io_t *io)
1377c478bdstevel@tonic-gate{
1387c478bdstevel@tonic-gate	pid_t pid = (pid_t)(intptr_t)io->io_data;
1397c478bdstevel@tonic-gate	int status;
1407c478bdstevel@tonic-gate
1417c478bdstevel@tonic-gate	do {
1427c478bdstevel@tonic-gate		mdb_dprintf(MDB_DBG_SHELL, "waiting for PID %d\n", (int)pid);
1437c478bdstevel@tonic-gate	} while (waitpid(pid, &status, 0) == -1 && errno == EINTR);
1447c478bdstevel@tonic-gate
1457c478bdstevel@tonic-gate	mdb_dprintf(MDB_DBG_SHELL, "waitpid %d -> 0x%x\n", (int)pid, status);
1467c478bdstevel@tonic-gate}
1477c478bdstevel@tonic-gate
1487c478bdstevel@tonic-gatestatic const mdb_io_ops_t shellio_ops = {
1497c478bdstevel@tonic-gate	no_io_read,
1507c478bdstevel@tonic-gate	no_io_write,
1517c478bdstevel@tonic-gate	no_io_seek,
1527c478bdstevel@tonic-gate	no_io_ctl,
1537c478bdstevel@tonic-gate	shellio_close,
1547c478bdstevel@tonic-gate	no_io_name,
1557c478bdstevel@tonic-gate	no_io_link,
1567c478bdstevel@tonic-gate	shellio_unlink,
1577c478bdstevel@tonic-gate	no_io_setattr,
1587c478bdstevel@tonic-gate	no_io_suspend,
1597c478bdstevel@tonic-gate	no_io_resume
1607c478bdstevel@tonic-gate};
1617c478bdstevel@tonic-gate
1627c478bdstevel@tonic-gatevoid
1637c478bdstevel@tonic-gatemdb_shell_pipe(char *cmd)
1647c478bdstevel@tonic-gate{
1657c478bdstevel@tonic-gate	uint_t iflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
1667c478bdstevel@tonic-gate	mdb_iob_t *iob;
1677c478bdstevel@tonic-gate	mdb_io_t *io;
1687c478bdstevel@tonic-gate	int pfds[2];
1697c478bdstevel@tonic-gate	pid_t pid;
1707c478bdstevel@tonic-gate
1717c478bdstevel@tonic-gate	if (access(mdb.m_shell, X_OK) == -1)
1727c478bdstevel@tonic-gate		yyperror("cannot access %s", mdb.m_shell);
1737c478bdstevel@tonic-gate
1747c478bdstevel@tonic-gate	if (pipe(pfds) == -1)
1757c478bdstevel@tonic-gate		yyperror("failed to open pipe");
1767c478bdstevel@tonic-gate
1777c478bdstevel@tonic-gate	iob = mdb_iob_create(mdb_fdio_create(pfds[1]), MDB_IOB_WRONLY | iflag);
1787c478bdstevel@tonic-gate	mdb_iob_clrflags(iob, MDB_IOB_AUTOWRAP | MDB_IOB_INDENT);
1797c478bdstevel@tonic-gate	mdb_iob_resize(iob, BUFSIZ, BUFSIZ);
1807c478bdstevel@tonic-gate
1817c478bdstevel@tonic-gate	if ((pid = vfork()) == -1) {
1827c478bdstevel@tonic-gate		(void) close(pfds[0]);
1837c478bdstevel@tonic-gate		(void) close(pfds[1]);
1847c478bdstevel@tonic-gate		mdb_iob_destroy(iob);
1857c478bdstevel@tonic-gate		yyperror("failed to fork");
1867c478bdstevel@tonic-gate	}
1877c478bdstevel@tonic-gate
1887c478bdstevel@tonic-gate	if (pid == 0) {
1897c478bdstevel@tonic-gate		(void) close(pfds[1]);
1907c478bdstevel@tonic-gate		(void) close(STDIN_FILENO);
1917c478bdstevel@tonic-gate		(void) dup2(pfds[0], STDIN_FILENO);
1927c478bdstevel@tonic-gate
1937c478bdstevel@tonic-gate		(void) fdwalk(closefd_walk, NULL);
1947c478bdstevel@tonic-gate		(void) execlp(mdb.m_shell, strbasename(mdb.m_shell),
1957c478bdstevel@tonic-gate		    "-c", cmd, NULL);
1967c478bdstevel@tonic-gate
1977c478bdstevel@tonic-gate		warn("failed to exec %s", mdb.m_shell);
1987c478bdstevel@tonic-gate		_exit(E_BADEXEC);
1997c478bdstevel@tonic-gate	}
2007c478bdstevel@tonic-gate
2017c478bdstevel@tonic-gate	(void) close(pfds[0]);
2027c478bdstevel@tonic-gate	strfree(cmd);
2037c478bdstevel@tonic-gate
2047c478bdstevel@tonic-gate	io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);
2057c478bdstevel@tonic-gate
2067c478bdstevel@tonic-gate	io->io_ops = &shellio_ops;
2077c478bdstevel@tonic-gate	io->io_data = (void *)(intptr_t)pid;
2087c478bdstevel@tonic-gate	io->io_next = NULL;
2097c478bdstevel@tonic-gate	io->io_refcnt = 0;
2107c478bdstevel@tonic-gate
2117c478bdstevel@tonic-gate	mdb_iob_stack_push(&mdb.m_frame->f_ostk, mdb.m_out, yylineno);
2127c478bdstevel@tonic-gate	mdb_iob_push_io(iob, io);
2137c478bdstevel@tonic-gate	mdb.m_out = iob;
2147c478bdstevel@tonic-gate}
215