#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <spawn.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>
extern char **environ;
static int
shell_spawn(char *cmd, int stdin_fd, int stdout_fd, int close_fd, pid_t *pidp)
{
posix_spawn_file_actions_t fact;
int err;
char *argv[] = {
(char *)strbasename(mdb.m_shell), "-c", cmd, NULL };
if ((err = posix_spawn_file_actions_init(&fact)) != 0)
return (err);
if (close_fd != -1)
err = posix_spawn_file_actions_addclose(&fact, close_fd);
if (err == 0 && stdin_fd != -1) {
err = posix_spawn_file_actions_adddup2(&fact, stdin_fd,
STDIN_FILENO);
}
if (err == 0 && stdout_fd != -1) {
err = posix_spawn_file_actions_adddup2(&fact, stdout_fd,
STDOUT_FILENO);
}
if (err == 0) {
err = posix_spawn_file_actions_addclosefrom_np(&fact,
STDERR_FILENO + 1);
}
if (err == 0) {
err = posix_spawnp(pidp, mdb.m_shell, &fact, NULL,
argv, environ);
}
(void) posix_spawn_file_actions_destroy(&fact);
return (err);
}
void
mdb_shell_exec(char *cmd)
{
int err, status;
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1)
yyperror("cannot access %s", mdb.m_shell);
if ((err = shell_spawn(cmd, -1, -1, -1, &pid)) != 0) {
errno = err;
yyperror("failed to exec %s", mdb.m_shell);
}
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);
}
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 = {
.io_read = no_io_read,
.io_write = no_io_write,
.io_seek = no_io_seek,
.io_ctl = no_io_ctl,
.io_close = shellio_close,
.io_name = no_io_name,
.io_link = no_io_link,
.io_unlink = shellio_unlink,
.io_setattr = no_io_setattr,
.io_suspend = no_io_suspend,
.io_resume = no_io_resume
};
static void
shell_pipe(char *cmd, int stdout_fd)
{
uint_t iflag = mdb_iob_getflags(mdb.m_out) & MDB_IOB_INDENT;
mdb_iob_t *iob;
mdb_io_t *io;
int err, pfds[2];
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1) {
if (stdout_fd != -1)
(void) close(stdout_fd);
yyperror("cannot access %s", mdb.m_shell);
}
if (pipe(pfds) == -1) {
if (stdout_fd != -1)
(void) close(stdout_fd);
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 ((err = shell_spawn(cmd, pfds[0], stdout_fd, pfds[1], &pid)) != 0) {
(void) close(pfds[0]);
(void) close(pfds[1]);
if (stdout_fd != -1)
(void) close(stdout_fd);
mdb_iob_destroy(iob);
errno = err;
yyperror("failed to exec %s", mdb.m_shell);
}
(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;
}
void
mdb_shell_pipe(char *cmd)
{
shell_pipe(cmd, -1);
}
static int
shell_tmpfd(void)
{
char tmpl[] = "/tmp/mdb.XXXXXX";
int fd;
if ((fd = mkstemp(tmpl)) == -1)
return (-1);
(void) unlink(tmpl);
return (fd);
}
static void
shell_source_eval(int fd)
{
mdb_frame_t *fp = mdb.m_frame;
int err;
if (lseek(fd, 0, SEEK_SET) == -1) {
(void) close(fd);
yyperror("failed to rewind temporary file");
}
mdb_iob_stack_push(&fp->f_istk, mdb.m_in, yylineno);
mdb.m_in = mdb_iob_create(mdb_fdio_create(fd), MDB_IOB_RDONLY);
err = mdb_run();
ASSERT(fp == mdb.m_frame);
mdb.m_in = mdb_iob_stack_pop(&fp->f_istk);
yylineno = mdb_iob_lineno(mdb.m_in);
if (err == MDB_ERR_PAGER && mdb.m_fmark != fp)
longjmp(fp->f_pcb, err);
if (err == MDB_ERR_QUIT || err == MDB_ERR_ABORT ||
err == MDB_ERR_SIGINT || err == MDB_ERR_OUTPUT) {
longjmp(fp->f_pcb, err);
}
}
void
mdb_shell_source(char *cmd)
{
int err, fd, status;
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1)
yyperror("cannot access %s", mdb.m_shell);
if ((fd = shell_tmpfd()) == -1)
yyperror("failed to create temporary file");
if ((err = shell_spawn(cmd, -1, fd, -1, &pid)) != 0) {
(void) close(fd);
errno = err;
yyperror("failed to exec %s", mdb.m_shell);
}
strfree(cmd);
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);
shell_source_eval(fd);
}
void
mdb_shell_pipe_source(char *cmd)
{
int fd;
if ((fd = shell_tmpfd()) == -1)
yyperror("failed to create temporary file");
shell_pipe(cmd, fd);
ASSERT(mdb.m_frame->f_shellsrc == -1);
mdb.m_frame->f_shellsrc = fd;
}
void
mdb_shell_source_run(void)
{
int fd = mdb.m_frame->f_shellsrc;
if (fd == -1)
return;
mdb.m_frame->f_shellsrc = -1;
shell_source_eval(fd);
}
void
mdb_shell_source_discard(void)
{
if (mdb.m_frame->f_shellsrc != -1) {
(void) close(mdb.m_frame->f_shellsrc);
mdb.m_frame->f_shellsrc = -1;
}
}
int
mdb_shell_filter(const char *cmd)
{
int fd;
if ((fd = shell_tmpfd()) == -1)
yyperror("failed to create temporary file");
shell_pipe(strdup(cmd), fd);
return (fd);
}
void
mdb_shell_filter_pump(int fd, mdb_iob_t *iob)
{
char buf[BUFSIZ];
ssize_t n;
if (lseek(fd, 0, SEEK_SET) == -1)
yyperror("failed to rewind temporary file");
while ((n = read(fd, buf, sizeof (buf))) > 0) {
if (mdb_iob_write(iob, buf, n) < 0)
break;
}
}
void
mdb_shell_filter_close(int fd)
{
if (fd != -1)
(void) close(fd);
}
void
mdb_shell_producer(mdb_cmd_t *cp)
{
mdb_frame_t *fp = mdb.m_frame;
int err, fd, nullfd, status;
pid_t pid;
if (access(mdb.m_shell, X_OK) == -1)
yyperror("cannot access %s", mdb.m_shell);
if ((fd = shell_tmpfd()) == -1)
yyperror("failed to create temporary file");
if ((nullfd = open("/dev/null", O_RDONLY)) == -1) {
(void) close(fd);
yyperror("failed to open /dev/null");
}
err = shell_spawn(cp->c_shcmd, nullfd, fd, -1, &pid);
(void) close(nullfd);
if (err != 0) {
(void) close(fd);
errno = err;
yyperror("failed to exec %s", mdb.m_shell);
}
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);
if (lseek(fd, 0, SEEK_SET) == -1) {
(void) close(fd);
yyperror("failed to rewind temporary file");
}
mdb_iob_stack_push(&fp->f_istk, mdb.m_in, yylineno);
mdb.m_in = mdb_iob_create(mdb_fdio_create(fd), MDB_IOB_RDONLY);
ASSERT(fp->f_pcmd == NULL);
fp->f_pcmd = cp;
mdb_frame_set_pipe(fp);
err = mdb_run();
mdb_frame_clear_pipe(fp);
fp->f_pcmd = NULL;
ASSERT(fp == mdb.m_frame);
mdb.m_in = mdb_iob_stack_pop(&fp->f_istk);
yylineno = mdb_iob_lineno(mdb.m_in);
if (err == MDB_ERR_PAGER && mdb.m_fmark != fp)
longjmp(fp->f_pcb, err);
if (err == MDB_ERR_QUIT || err == MDB_ERR_ABORT ||
err == MDB_ERR_SIGINT || err == MDB_ERR_OUTPUT) {
longjmp(fp->f_pcb, err);
}
}