core-extra/netns/vcmd_main.c

439 lines
8.9 KiB
C

/*
* CORE
* Copyright (c)2010-2012 the Boeing Company.
* See the LICENSE file included in this distribution.
*
* author: Tom Goff <thomas.goff@boeing.com>
*
* vcmd_main.c
*
* vcmd utility program for executing programs in an existing namespace
* specified by the given channel.
*
*/
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <getopt.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include "version.h"
#include "vnode_chnl.h"
#include "vnode_cmd.h"
#include "vnode_client.h"
#include "myerr.h"
#define FORWARD_SIGNALS
#define VCMD_DEFAULT_CMD "/bin/bash"
int verbose;
typedef struct {
vnode_client_t *client;
vnode_client_cmdio_t *cmdio;
int argc;
char **argv;
int cmdid;
int cmdstatus;
ev_io stdin_watcher;
int stdin_fwdfd;
ev_io ptymaster_watcher;
int ptymaster_fwdfd;
} vcmd_t;
static vcmd_t vcmd;
static struct termios saveattr;
static int saveattr_set;
struct option longopts[] =
{
{"version", no_argument, NULL, 'V'},
{"help", no_argument, NULL, 'h'},
{ 0 }
};
void usage(int status, char *fmt, ...)
{
extern const char *__progname;
va_list ap;
FILE *output;
va_start(ap, fmt);
output = status ? stderr : stdout;
fprintf(output, "\n");
if (fmt != NULL)
{
vfprintf(output, fmt, ap);
fprintf(output, "\n\n");
}
fprintf(output,
"Usage: %s [-h|-V] [-v] [-q|-i|-I] -c <channel name> -- command args"
"...\n\n"
"Run the specified command in the Linux namespace container "
"specified by the \ncontrol <channel name>, with the specified "
"arguments.\n\nOptions:\n"
" -h, --help show this help message and exit\n"
" -V, --version show version number and exit\n"
" -v enable verbose logging\n"
" -q run the command quietly, without local input or output\n"
" -i run the command interactively (use PTY)\n"
" -I run the command non-interactively (without PTY)\n"
" -c control channel name (e.g. '/tmp/pycore.45647/n3')\n",
__progname);
va_end(ap);
exit(status);
}
static void vcmd_rwcb(struct ev_loop *loop, ev_io *w, int revents)
{
int outfd = *(int *)w->data;
char buf[BUFSIZ];
ssize_t rcount, wcount;
rcount = read(w->fd, buf, sizeof(buf));
if (rcount <= 0)
{
ev_io_stop(loop, w);
}
else
{
wcount = write(outfd, buf, rcount);
if (wcount != rcount)
WARN("write() error: wrote %d of %d bytes", wcount, rcount);
}
return;
}
static void vcmd_cmddonecb(int32_t cmdid, pid_t pid, int status, void *data)
{
vcmd_t *vcmd = data;
if (vcmd->cmdio->iotype == VCMD_IO_PTY)
{
ev_io_stop(vcmd->client->loop, &vcmd->stdin_watcher);
ev_io_stop(vcmd->client->loop, &vcmd->ptymaster_watcher);
/* drain command output */
for (;;)
{
char buf[BUFSIZ];
ssize_t rcount, wcount;
rcount = read(vcmd->ptymaster_watcher.fd, buf, sizeof(buf));
if (rcount <= 0)
break;
wcount = write(STDOUT_FILENO, buf, rcount);
if (wcount != rcount)
WARN("write() error: %d of %d bytes", wcount, rcount);
}
}
vnode_close_clientcmdio(vcmd->cmdio);
#ifdef DEBUG
WARNX("cmdid %u; pid %d; status: 0x%x", cmdid, pid, status);
#endif
if (WIFEXITED(status))
/* normal terminataion */
vcmd->cmdstatus = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
{
if (verbose)
INFO("command %u terminated by signal: %d", cmdid, WTERMSIG(status));
vcmd->cmdstatus = 255;
}
else
{
INFO("unexpected termination status for command %u: 0x%x", cmdid, status);
vcmd->cmdstatus = 255;
}
vcmd->cmdid = -1;
ev_unloop(vcmd->client->loop, EVUNLOOP_ALL);
return;
}
static void vcmd_cmdreqcb(struct ev_loop *loop, ev_timer *w, int revents)
{
vcmd_t *vcmd = w->data;
#ifdef DEBUG
WARNX("sending command request: serverfd %d; vcmd %p",
vcmd->client->serverfd, vcmd);
#endif
if (vcmd->cmdio->iotype == VCMD_IO_PTY)
{
/* setup forwarding i/o */
vcmd->stdin_fwdfd = vcmd->cmdio->stdiopty.masterfd;
vcmd->stdin_watcher.data = &vcmd->stdin_fwdfd;
ev_io_init(&vcmd->stdin_watcher, vcmd_rwcb, STDIN_FILENO, EV_READ);
ev_io_start(loop, &vcmd->stdin_watcher);
vcmd->ptymaster_fwdfd = STDOUT_FILENO;
vcmd->ptymaster_watcher.data = &vcmd->ptymaster_fwdfd;
ev_io_init(&vcmd->ptymaster_watcher, vcmd_rwcb,
vcmd->cmdio->stdiopty.masterfd, EV_READ);
ev_io_start(loop, &vcmd->ptymaster_watcher);
}
vcmd->cmdid = vnode_client_cmdreq(vcmd->client, vcmd->cmdio,
vcmd_cmddonecb, vcmd,
vcmd->argc, vcmd->argv);
if (vcmd->cmdid < 0)
{
WARNX("vnode_client_cmdreq() failed");
vnode_delclient(vcmd->client);
vcmd->client = NULL;
exit(255);
}
return;
}
static void vcmd_ioerrorcb(vnode_client_t *client)
{
vcmd_t *vcmd = client->data;
WARNX("i/o error");
vnode_delclient(client);
vcmd->client = NULL;
exit(1);
return;
}
#ifdef FORWARD_SIGNALS
static void sighandler(int signum)
{
if (!vcmd.client || vcmd.cmdid < 0)
return;
#ifdef DEBUG
WARNX("sending command signal: serverfd %d; cmdid %u; signum: %d",
vcmd.client->serverfd, vcmd.cmdid, signum);
#endif
if (vnode_send_cmdsignal(vcmd.client->serverfd, vcmd.cmdid, signum))
WARN("vnode_send_cmdsignal() failed");
return;
}
#endif /* FORWARD_SIGNALS */
static void sigwinch_handler(int signum)
{
struct winsize wsiz;
if (signum != SIGWINCH)
{
WARNX("unexpected signal number: %d", signum);
return;
}
if (!vcmd.cmdio || vcmd.cmdio->iotype != VCMD_IO_PTY)
return;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsiz))
{
WARN("ioctl() failed");
return;
}
if (ioctl(vcmd.cmdio->stdiopty.masterfd, TIOCSWINSZ, &wsiz))
WARN("ioctl() failed");
return;
}
static int termioraw(int fd, struct termios *saveattr)
{
int err;
struct termios raw = {};
err = tcgetattr(fd, saveattr);
if (err)
{
WARN("tcgetattr() failed");
return err;
}
cfmakeraw(&raw);
err = tcsetattr(fd, TCSADRAIN, &raw);
if (err)
{
WARN("tcsetattr() failed");
return err;
}
return 0;
}
static void cleanup(void)
{
if (saveattr_set)
if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &saveattr))
WARN("tcsetattr() failed");
return;
}
int main(int argc, char *argv[])
{
char *ctrlchnlname = NULL;
vnode_client_cmdiotype_t iotype = VCMD_IO_FD;
ev_timer cmdreq;
extern const char *__progname;
#ifdef FORWARD_SIGNALS
int i;
struct sigaction sig_action = {
.sa_handler = sighandler,
};
#endif /* FORWARD_SIGNALS */
char *def_argv[2] = { VCMD_DEFAULT_CMD, 0 };
if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) &&
isatty(STDERR_FILENO) && getpgrp() == tcgetpgrp(STDOUT_FILENO))
iotype = VCMD_IO_PTY;
/* Parse command line argument list */
for (;;)
{
int opt;
if ((opt = getopt_long(argc, argv, "c:hiIqvV", longopts, NULL)) == -1)
break;
switch (opt)
{
case 'c':
ctrlchnlname = optarg;
break;
case 'i':
iotype = VCMD_IO_PTY;
break;
case 'I':
iotype = VCMD_IO_FD;
break;
case 'q':
iotype = VCMD_IO_NONE;
break;
case 'v':
verbose++;
break;
case 'V':
printf("%s version %s\n", __progname, CORE_VERSION);
exit(0);
case 'h':
/* pass through */
default:
usage(0, NULL);
}
}
argc -= optind;
argv += optind;
if (ctrlchnlname == NULL)
usage(1, "no control channel name given");
if (!argc)
{
argc = 1;
argv = def_argv;
}
if (argc >= VNODE_ARGMAX)
usage(1, "too many command arguments");
if (atexit(cleanup))
ERR(1, "atexit() failed");
#ifdef FORWARD_SIGNALS
for (i = 1; i < _NSIG; i++)
if (sigaction(i, &sig_action, NULL))
if (verbose && i != SIGKILL && i != SIGSTOP)
WARN("sigaction() failed for %d", i);
#endif /* FORWARD_SIGNALS */
vcmd.cmdio = vnode_open_clientcmdio(iotype);
if (!vcmd.cmdio)
ERR(1, "vnode_open_clientcmdio() failed");
vcmd.argc = argc;
vcmd.argv = argv;
vcmd.cmdstatus = 255;
switch (vcmd.cmdio->iotype)
{
case VCMD_IO_NONE:
break;
case VCMD_IO_FD:
SET_STDIOFD(vcmd.cmdio, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO);
break;
case VCMD_IO_PTY:
{
struct sigaction sigwinch_action = {
.sa_handler = sigwinch_handler,
};
if (sigaction(SIGWINCH, &sigwinch_action, NULL))
WARN("sigaction() failed for SIGWINCH");
sigwinch_handler(SIGWINCH);
if (termioraw(STDOUT_FILENO, &saveattr))
WARNX("termioraw() failed");
else
saveattr_set = 1;
}
break;
default:
ERR(1, "unsupported i/o type: %u", vcmd.cmdio->iotype);
break;
}
vcmd.client = vnode_client(ev_default_loop(0), ctrlchnlname,
vcmd_ioerrorcb, &vcmd);
if (!vcmd.client)
ERR(1, "vnode_client() failed");
cmdreq.data = &vcmd;
ev_timer_init(&cmdreq, vcmd_cmdreqcb, 0, 0);
ev_timer_start(vcmd.client->loop, &cmdreq);
ev_loop(vcmd.client->loop, 0);
vnode_delclient(vcmd.client);
exit(vcmd.cmdstatus);
}