core-extra/netns/vnode_cmd.c

483 lines
10 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>
*
* vnode_cmd.c
*
*/
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include "myerr.h"
#include "vnode_msg.h"
#include "vnode_server.h"
#include "vnode_tlv.h"
#include "vnode_io.h"
#include "vnode_cmd.h"
extern int verbose;
static void vnode_process_cmdreq(vnode_cliententry_t *client,
vnode_cmdreq_t *cmdreq);
static int tlv_cmdreq_cmdid(vnode_tlv_t *tlv, void *data)
{
vnode_cmdreq_t *cmdreq = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDID);
tmp = tlv_int32(&cmdreq->cmdid, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDID: %u", cmdreq->cmdid);
return tmp;
}
static int tlv_cmdreq_cmdarg(vnode_tlv_t *tlv, void *data)
{
vnode_cmdreq_t *cmdreq = data;
int i, tmp;
assert(tlv->type == VNODE_TLV_CMDARG);
#define CMDARGMAX (sizeof(cmdreq->cmdarg) / sizeof(cmdreq->cmdarg[0]))
for (i = 0; i < CMDARGMAX; i++)
if (cmdreq->cmdarg[i] == NULL)
break;
if (i == CMDARGMAX)
{
WARNX("too many command arguments");
return -1;
}
#undef CMDARGMAX
tmp = tlv_string(&cmdreq->cmdarg[i], tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDARG: '%s'", cmdreq->cmdarg[i]);
return tmp;
}
void vnode_recv_cmdreq(vnode_msgio_t *msgio)
{
vnode_cliententry_t *client = msgio->data;
vnode_cmdreq_t cmdreq = CMDREQ_INIT;
static vnode_tlvhandler_t cmdreq_tlvhandler[VNODE_TLV_MAX] = {
[VNODE_TLV_CMDID] = tlv_cmdreq_cmdid,
[VNODE_TLV_CMDARG] = tlv_cmdreq_cmdarg,
};
#ifdef DEBUG
WARNX("command request");
#endif
assert(msgio->msgbuf.msg->hdr.type == VNODE_MSG_CMDREQ);
if (vnode_parsemsg(msgio->msgbuf.msg, &cmdreq, cmdreq_tlvhandler))
return;
cmdreq.cmdio.infd = msgio->msgbuf.infd;
cmdreq.cmdio.outfd = msgio->msgbuf.outfd;
cmdreq.cmdio.errfd = msgio->msgbuf.errfd;
vnode_process_cmdreq(client, &cmdreq);
return;
}
int vnode_send_cmdreq(int fd, int32_t cmdid, char *argv[],
int infd, int outfd, int errfd)
{
size_t offset = 0;
vnode_msgbuf_t msgbuf;
char **cmdarg;
int tmp;
if (vnode_initmsgbuf(&msgbuf))
return -1;
#define ADDTLV(t, l, vp) \
do { \
ssize_t tlvlen; \
tlvlen = vnode_addtlv(&msgbuf, offset, t, l, vp); \
if (tlvlen < 0) \
{ \
WARNX("vnode_addtlv() failed"); \
FREE_MSGBUF(&msgbuf); \
return -1; \
} \
offset += tlvlen; \
} while (0)
ADDTLV(VNODE_TLV_CMDID, sizeof(cmdid), &cmdid);
for (cmdarg = argv; *cmdarg; cmdarg++)
ADDTLV(VNODE_TLV_CMDARG, strlen(*cmdarg) + 1, *cmdarg);
#undef ADDTLV
msgbuf.infd = infd;
msgbuf.outfd = outfd;
msgbuf.errfd = errfd;
#ifdef DEBUG
WARNX("sending cmd req on fd %d: cmd '%s'", fd, argv[0]);
#endif
msgbuf.msg->hdr.type = VNODE_MSG_CMDREQ;
msgbuf.msg->hdr.datalen = offset;
if (vnode_sendmsg(fd, &msgbuf) == vnode_msglen(&msgbuf))
tmp = 0;
else
tmp = -1;
FREE_MSGBUF(&msgbuf);
return tmp;
}
int vnode_send_cmdreqack(int fd, int32_t cmdid, int32_t pid)
{
ssize_t tmp = -1;
size_t offset = 0;
vnode_msgbuf_t msgbuf;
if (vnode_initmsgbuf(&msgbuf))
return -1;
#define ADDTLV(t, l, vp) \
do { \
ssize_t tlvlen; \
tlvlen = vnode_addtlv(&msgbuf, offset, t, l, vp); \
if (tlvlen < 0) \
{ \
WARNX("vnode_addtlv() failed"); \
FREE_MSGBUF(&msgbuf); \
return -1; \
} \
offset += tlvlen; \
} while (0)
ADDTLV(VNODE_TLV_CMDID, sizeof(cmdid), &cmdid);
ADDTLV(VNODE_TLV_CMDPID, sizeof(pid), &pid);
#undef ADDTLV
#ifdef DEBUG
WARNX("sending cmd req ack on fd %d: cmdid %d; pid %d", fd, cmdid, pid);
#endif
msgbuf.msg->hdr.type = VNODE_MSG_CMDREQACK;
msgbuf.msg->hdr.datalen = offset;
if (vnode_sendmsg(fd, &msgbuf) == vnode_msglen(&msgbuf))
tmp = 0;
FREE_MSGBUF(&msgbuf);
return tmp;
}
int vnode_send_cmdstatus(int fd, int32_t cmdid, int32_t status)
{
int tmp;
size_t offset = 0;
vnode_msgbuf_t msgbuf;
if (vnode_initmsgbuf(&msgbuf))
return -1;
#define ADDTLV(t, l, vp) \
do { \
ssize_t tlvlen; \
tlvlen = vnode_addtlv(&msgbuf, offset, t, l, vp); \
if (tlvlen < 0) \
{ \
WARNX("vnode_addtlv() failed"); \
FREE_MSGBUF(&msgbuf); \
return -1; \
} \
offset += tlvlen; \
} while (0)
ADDTLV(VNODE_TLV_CMDID, sizeof(cmdid), &cmdid);
ADDTLV(VNODE_TLV_CMDSTATUS, sizeof(status), &status);
#undef ADDTLV
#ifdef DEBUG
WARNX("sending cmd status on fd %d: cmdid %d; status %d",
fd, cmdid, status);
#endif
msgbuf.msg->hdr.type = VNODE_MSG_CMDSTATUS;
msgbuf.msg->hdr.datalen = offset;
if (vnode_sendmsg(fd, &msgbuf) == vnode_msglen(&msgbuf))
tmp = 0;
else
tmp = -1;
FREE_MSGBUF(&msgbuf);
return tmp;
}
int vnode_send_cmdsignal(int fd, int32_t cmdid, int32_t signum)
{
ssize_t tmp;
size_t offset = 0;
vnode_msgbuf_t msgbuf;
if (vnode_initmsgbuf(&msgbuf))
return -1;
#define ADDTLV(t, l, vp) \
do { \
ssize_t tlvlen; \
tlvlen = vnode_addtlv(&msgbuf, offset, t, l, vp); \
if (tlvlen < 0) \
{ \
WARNX("vnode_addtlv() failed"); \
FREE_MSGBUF(&msgbuf); \
return -1; \
} \
offset += tlvlen; \
} while (0)
ADDTLV(VNODE_TLV_CMDID, sizeof(cmdid), &cmdid);
ADDTLV(VNODE_TLV_SIGNUM, sizeof(signum), &signum);
#undef ADDTLV
#ifdef DEBUG
WARNX("sending cmd signal on fd %d: cmdid %d; signum %d",
fd, cmdid, signum);
#endif
msgbuf.msg->hdr.type = VNODE_MSG_CMDSIGNAL;
msgbuf.msg->hdr.datalen = offset;
if (vnode_sendmsg(fd, &msgbuf) == vnode_msglen(&msgbuf))
tmp = 0;
else
tmp = -1;
FREE_MSGBUF(&msgbuf);
return tmp;
}
static int tlv_cmdsignal_cmdid(vnode_tlv_t *tlv, void *data)
{
vnode_cmdsignal_t *cmdsignal = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDID);
tmp = tlv_int32(&cmdsignal->cmdid, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDID: %d", cmdsignal->cmdid);
return tmp;
}
static int tlv_cmdsignal_signum(vnode_tlv_t *tlv, void *data)
{
vnode_cmdsignal_t *cmdsignal = data;
int tmp;
assert(tlv->type == VNODE_TLV_SIGNUM);
tmp = tlv_int32(&cmdsignal->signum, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_SIGNUM: %d", cmdsignal->signum);
return tmp;
}
void vnode_recv_cmdsignal(vnode_msgio_t *msgio)
{
vnode_cliententry_t *client = msgio->data;
vnode_cmdsignal_t cmdsignal = CMDSIGNAL_INIT;
static vnode_tlvhandler_t cmdsignal_tlvhandler[VNODE_TLV_MAX] = {
[VNODE_TLV_CMDID] = tlv_cmdsignal_cmdid,
[VNODE_TLV_SIGNUM] = tlv_cmdsignal_signum,
};
vnode_cmdentry_t *cmd;
#ifdef DEBUG
WARNX("command signal");
#endif
assert(msgio->msgbuf.msg->hdr.type == VNODE_MSG_CMDSIGNAL);
if (vnode_parsemsg(msgio->msgbuf.msg, &cmdsignal, cmdsignal_tlvhandler))
return;
TAILQ_FOREACH(cmd, &client->server->cmdlisthead, entries)
{
if (cmd->cmdid == cmdsignal.cmdid && cmd->data == client)
{
if (verbose)
INFO("sending pid %u signal %u", cmd->pid, cmdsignal.signum);
if (kill(cmd->pid, cmdsignal.signum))
WARN("kill() failed");
break;
}
}
if (cmd == NULL)
WARNX("cmdid %d not found for client %p", cmdsignal.cmdid, client);
return;
}
static pid_t forkexec(vnode_cmdreq_t *cmdreq)
{
pid_t pid;
if (verbose)
INFO("spawning '%s'", cmdreq->cmdarg[0]);
pid = fork();
switch (pid)
{
case -1:
WARN("fork() failed");
break;
case 0:
/* child */
if (setsid() == -1)
WARN("setsid() failed");
#define DUP2(oldfd, newfd) \
do { \
if (oldfd >= 0) \
if (dup2(oldfd, newfd) < 0) \
{ \
WARN("dup2() failed for " #newfd \
": oldfd: %d; newfd: %d", \
oldfd, newfd); \
_exit(1); \
} \
} while (0)
DUP2(cmdreq->cmdio.infd, STDIN_FILENO);
DUP2(cmdreq->cmdio.outfd, STDOUT_FILENO);
DUP2(cmdreq->cmdio.errfd, STDERR_FILENO);
#undef DUP2
#define CLOSE_IF_NOT(fd, notfd) \
do { \
if (fd >= 0 && fd != notfd) \
close(fd); \
} while (0)
CLOSE_IF_NOT(cmdreq->cmdio.infd, STDIN_FILENO);
CLOSE_IF_NOT(cmdreq->cmdio.outfd, STDOUT_FILENO);
CLOSE_IF_NOT(cmdreq->cmdio.errfd, STDERR_FILENO);
#undef CLOSE_IF_NOT
if (clear_nonblock(STDIN_FILENO))
WARN("clear_nonblock() failed");
if (clear_nonblock(STDOUT_FILENO))
WARN("clear_nonblock() failed");
if (clear_nonblock(STDERR_FILENO))
WARN("clear_nonblock() failed");
/* try to get a controlling terminal (don't steal a terminal and
ignore errors) */
if (isatty(STDIN_FILENO))
ioctl(STDIN_FILENO, TIOCSCTTY, 0);
else if (isatty(STDOUT_FILENO))
ioctl(STDOUT_FILENO, TIOCSCTTY, 0);
execvp(cmdreq->cmdarg[0], cmdreq->cmdarg);
WARN("execvp() failed for '%s'", cmdreq->cmdarg[0]);
_exit(1);
break;
default:
/* parent */
break;
}
#define CLOSE(fd) \
do { \
if (fd >= 0) \
close(fd); \
} while (0)
CLOSE(cmdreq->cmdio.infd);
CLOSE(cmdreq->cmdio.outfd);
CLOSE(cmdreq->cmdio.errfd);
#undef CLOSE
return pid;
}
static void vnode_process_cmdreq(vnode_cliententry_t *client,
vnode_cmdreq_t *cmdreq)
{
vnode_cmdentry_t *cmd = NULL;
if ((cmd = malloc(sizeof(*cmd))) == NULL)
{
WARN("malloc() failed");
return;
}
cmd->cmdid = cmdreq->cmdid;
cmd->pid = -1;
cmd->status = -1;
cmd->data = client;
cmd->pid = forkexec(cmdreq);
if (verbose)
INFO("cmd: '%s'; pid: %d; cmdid: %d; "
"infd: %d; outfd: %d; errfd: %d",
cmdreq->cmdarg[0], cmd->pid, cmd->cmdid,
cmdreq->cmdio.infd, cmdreq->cmdio.outfd, cmdreq->cmdio.errfd);
if (vnode_send_cmdreqack(client->clientfd, cmd->cmdid, cmd->pid))
{
WARNX("vnode_send_cmdreqack() failed");
// XXX if (cmd->pid != -1) kill(cmd->pid, SIGKILL); ?
free(cmd);
return;
}
if (cmd->pid == -1)
free(cmd);
else
{
#ifdef DEBUG
WARNX("adding pid %d to cmd list", cmd->pid);
#endif
TAILQ_INSERT_TAIL(&client->server->cmdlisthead, cmd, entries);
}
return;
}