core-extra/netns/vnode_client.c

509 lines
9.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>
*
* vnode_client.c
*
*
*
*/
#include <stdio.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include "vnode_chnl.h"
#include "vnode_client.h"
#include "vnode_tlv.h"
#include "vnode_io.h"
extern int verbose;
typedef struct {
vnode_client_cmddonecb_t cmddonecb;
void *data;
} vnode_clientcmd_t;
vnode_client_cmdio_t *vnode_open_clientcmdio(vnode_client_cmdiotype_t iotype)
{
int err;
vnode_client_cmdio_t *clientcmdio;
clientcmdio = malloc(sizeof(*clientcmdio));
if (!clientcmdio)
{
WARN("malloc() failed");
return NULL;
}
clientcmdio->iotype = iotype;
switch (clientcmdio->iotype)
{
case VCMD_IO_NONE:
case VCMD_IO_FD:
err = 0;
break;
case VCMD_IO_PIPE:
err = open_stdio_pipe(&clientcmdio->stdiopipe);
break;
case VCMD_IO_PTY:
err = open_stdio_pty(&clientcmdio->stdiopty);
break;
default:
WARNX("unknown i/o type: %u", clientcmdio->iotype);
err = -1;
break;
}
if (err)
{
free(clientcmdio);
clientcmdio = NULL;
}
return clientcmdio;
}
void vnode_close_clientcmdio(vnode_client_cmdio_t *clientcmdio)
{
switch (clientcmdio->iotype)
{
case VCMD_IO_NONE:
case VCMD_IO_FD:
break;
case VCMD_IO_PIPE:
close_stdio_pipe(&clientcmdio->stdiopipe);
break;
case VCMD_IO_PTY:
close_stdio_pty(&clientcmdio->stdiopty);
break;
default:
WARNX("unknown i/o type: %u", clientcmdio->iotype);
break;
}
memset(clientcmdio, 0, sizeof(*clientcmdio));
free(clientcmdio);
return;
}
static void vnode_client_cmddone(vnode_cmdentry_t *cmd)
{
vnode_clientcmd_t *clientcmd = cmd->data;;
if (clientcmd->cmddonecb)
clientcmd->cmddonecb(cmd->cmdid, cmd->pid, cmd->status, clientcmd->data);
memset(clientcmd, 0, sizeof(*clientcmd));
free(clientcmd);
memset(cmd, 0, sizeof(*cmd));
free(cmd);
return;
}
static int tlv_cmdreqack_cmdid(vnode_tlv_t *tlv, void *data)
{
vnode_cmdreqack_t *cmdreqack = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDID);
tmp = tlv_int32(&cmdreqack->cmdid, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDID: %d", cmdreqack->cmdid);
return tmp;
}
static int tlv_cmdreqack_cmdpid(vnode_tlv_t *tlv, void *data)
{
vnode_cmdreqack_t *cmdreqack = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDPID);
tmp = tlv_int32(&cmdreqack->pid, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDPID: %d", cmdreqack->pid);
return tmp;
}
static void vnode_clientrecv_cmdreqack(vnode_msgio_t *msgio)
{
vnode_cmdentry_t *cmd;
vnode_client_t *client = msgio->data;
vnode_cmdreqack_t cmdreqack = CMDREQACK_INIT;
static const vnode_tlvhandler_t tlvhandler[VNODE_TLV_MAX] = {
[VNODE_TLV_CMDID] = tlv_cmdreqack_cmdid,
[VNODE_TLV_CMDPID] = tlv_cmdreqack_cmdpid,
};
#ifdef DEBUG
WARNX("command request ack");
#endif
assert(msgio->msgbuf.msg->hdr.type == VNODE_MSG_CMDREQACK);
if (vnode_parsemsg(msgio->msgbuf.msg, &cmdreqack, tlvhandler))
return;
TAILQ_FOREACH(cmd, &client->cmdlisthead, entries)
if (cmd->cmdid == cmdreqack.cmdid)
break;
if (cmd == NULL)
{
WARNX("cmdid %d not found in command list", cmdreqack.cmdid);
return;
}
#ifdef DEBUG
WARNX("cmdid %d found in cmd list", cmdreqack.cmdid);
#endif
cmd->pid = cmdreqack.pid;
if (cmdreqack.pid == -1)
{
#ifdef DEBUG
WARNX("XXX pid == -1 removing cmd from list");
#endif
TAILQ_REMOVE(&client->cmdlisthead, cmd, entries);
cmd->status = -1;
vnode_client_cmddone(cmd);
return;
}
return;
}
static int tlv_cmdstatus_cmdid(vnode_tlv_t *tlv, void *data)
{
vnode_cmdstatus_t *cmdstatus = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDID);
tmp = tlv_int32(&cmdstatus->cmdid, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDID: %d", cmdstatus->cmdid);
return tmp;
}
static int tlv_cmdstatus_status(vnode_tlv_t *tlv, void *data)
{
vnode_cmdstatus_t *cmdstatus = data;
int tmp;
assert(tlv->type == VNODE_TLV_CMDSTATUS);
tmp = tlv_int32(&cmdstatus->status, tlv);
if (tmp == 0 && verbose)
INFO("VNODE_TLV_CMDSTATUS: %d", cmdstatus->status);
return tmp;
}
static void vnode_clientrecv_cmdstatus(vnode_msgio_t *msgio)
{
vnode_cmdentry_t *cmd;
vnode_client_t *client = msgio->data;
vnode_cmdstatus_t cmdstatus = CMDSTATUS_INIT;
static const vnode_tlvhandler_t tlvhandler[VNODE_TLV_MAX] = {
[VNODE_TLV_CMDID] = tlv_cmdstatus_cmdid,
[VNODE_TLV_CMDSTATUS] = tlv_cmdstatus_status,
};
#ifdef DEBUG
WARNX("command status");
#endif
assert(msgio->msgbuf.msg->hdr.type == VNODE_MSG_CMDSTATUS);
if (vnode_parsemsg(msgio->msgbuf.msg, &cmdstatus, tlvhandler))
return;
TAILQ_FOREACH(cmd, &client->cmdlisthead, entries)
if (cmd->cmdid == cmdstatus.cmdid)
break;
if (cmd == NULL)
{
WARNX("cmdid %d not found in command list", cmdstatus.cmdid);
return;
}
#ifdef DEBUG
WARNX("cmdid %d found in cmd list; removing", cmdstatus.cmdid);
#endif
TAILQ_REMOVE(&client->cmdlisthead, cmd, entries);
cmd->status = cmdstatus.status;
vnode_client_cmddone(cmd);
return;
}
static void server_ioerror(vnode_msgio_t *msgio)
{
vnode_client_t *client = msgio->data;
#ifdef DEBUG
WARNX("i/o error on fd %d; client: %p", msgio->fd, client);
#endif
if (client)
{
assert(msgio == &client->msgio);
if (client->ioerrorcb)
client->ioerrorcb(client);
}
return;
}
vnode_client_t *vnode_client(struct ev_loop *loop, const char *ctrlchnlname,
vnode_clientcb_t ioerrorcb, void *data)
{
int fd = -1;
vnode_client_t *client;
static const vnode_msghandler_t msghandler[VNODE_MSG_MAX] = {
[VNODE_MSG_CMDREQACK] = vnode_clientrecv_cmdreqack,
[VNODE_MSG_CMDSTATUS] = vnode_clientrecv_cmdstatus,
};
if (!ioerrorcb)
{
WARNX("no i/o error callback given");
return NULL;
}
fd = vnode_connect(ctrlchnlname);
if (fd < 0)
{
WARN("vnode_connect() failed for '%s'", ctrlchnlname);
return NULL;
}
if ((client = calloc(1, sizeof(*client))) == NULL)
{
WARN("calloc() failed");
close(fd);
return NULL;
}
TAILQ_INIT(&client->cmdlisthead);
client->loop = loop;
client->serverfd = fd;
client->ioerrorcb = ioerrorcb;
client->data = data;
if (vnode_msgiostart(&client->msgio, client->loop,
client->serverfd, client, server_ioerror, msghandler))
{
WARNX("vnode_msgiostart() failed");
close(fd);
return NULL;
}
#ifdef DEBUG
WARNX("new client connected to %s: %p", ctrlchnlname, client);
#endif
return client;
}
void vnode_delclient(vnode_client_t *client)
{
#ifdef DEBUG
WARNX("deleting client: %p", client);
#endif
vnode_msgiostop(&client->msgio);
if (client->serverfd >= 0)
{
close(client->serverfd);
client->serverfd = -1;
}
while (!TAILQ_EMPTY(&client->cmdlisthead))
{
vnode_cmdentry_t *cmd;
cmd = TAILQ_FIRST(&client->cmdlisthead);
TAILQ_REMOVE(&client->cmdlisthead, cmd, entries);
cmd->status = -1;
vnode_client_cmddone(cmd);
}
/* XXX more stuff ?? */
memset(client, 0, sizeof(*client));
free(client);
return;
}
static int vnode_setcmdio(int *cmdin, int *cmdout, int *cmderr,
vnode_client_cmdio_t *clientcmdio)
{
switch (clientcmdio->iotype)
{
case VCMD_IO_NONE:
*cmdin = -1;
*cmdout = -1;
*cmderr = -1;
break;
case VCMD_IO_FD:
*cmdin = clientcmdio->stdiofd.infd;
*cmdout = clientcmdio->stdiofd.outfd;
*cmderr = clientcmdio->stdiofd.errfd;
break;
case VCMD_IO_PIPE:
*cmdin = clientcmdio->stdiopipe.infd[0];
*cmdout = clientcmdio->stdiopipe.outfd[1];
*cmderr = clientcmdio->stdiopipe.errfd[1];
break;
case VCMD_IO_PTY:
*cmdin = clientcmdio->stdiopty.slavefd;
*cmdout = clientcmdio->stdiopty.slavefd;
*cmderr = clientcmdio->stdiopty.slavefd;
break;
default:
WARNX("unknown i/o type: %u", clientcmdio->iotype);
return -1;
}
return 0;
}
static void vnode_cleanupcmdio(vnode_client_cmdio_t *clientcmdio)
{
#define CLOSE(var) \
do { \
if (var >= 0) \
close(var); \
var = -1; \
} while (0)
switch (clientcmdio->iotype)
{
case VCMD_IO_NONE:
case VCMD_IO_FD:
break;
case VCMD_IO_PIPE:
CLOSE(clientcmdio->stdiopipe.infd[0]);
CLOSE(clientcmdio->stdiopipe.outfd[1]);
CLOSE(clientcmdio->stdiopipe.errfd[1]);
break;
case VCMD_IO_PTY:
CLOSE(clientcmdio->stdiopty.slavefd);
break;
default:
WARNX("unknown i/o type: %u", clientcmdio->iotype);
break;
}
#undef CLOSE
return;
}
int vnode_client_cmdreq(vnode_client_t *client,
vnode_client_cmdio_t *clientcmdio,
vnode_client_cmddonecb_t cmddonecb, void *data,
int argc, char *argv[])
{
int cmdin, cmdout, cmderr;
vnode_clientcmd_t *clientcmd;
vnode_cmdentry_t *cmd;
if (argc >= VNODE_ARGMAX)
{
WARNX("too many command arguments");
return -1;
}
if (argv[argc] != NULL)
{
WARNX("command arguments not null-terminated");
return -1;
}
if (vnode_setcmdio(&cmdin, &cmdout, &cmderr, clientcmdio))
{
WARNX("vnode_setcmdio() failed");
return -1;
}
if ((clientcmd = malloc(sizeof(*clientcmd))) == NULL)
{
WARN("malloc() failed");
return -1;
}
clientcmd->cmddonecb = cmddonecb;
clientcmd->data = data;
if ((cmd = malloc(sizeof(*cmd))) == NULL)
{
WARN("malloc() failed");
free(clientcmd);
return -1;
}
if (client->cmdid < 0)
client->cmdid = 0;
cmd->cmdid = client->cmdid++;
cmd->pid = -1;
cmd->status = -1;
cmd->data = clientcmd;
TAILQ_INSERT_TAIL(&client->cmdlisthead, cmd, entries);
if (vnode_send_cmdreq(client->serverfd, cmd->cmdid,
argv, cmdin, cmdout, cmderr))
{
WARN("vnode_send_cmdreq() failed");
TAILQ_REMOVE(&client->cmdlisthead, cmd, entries);
free(clientcmd);
free(cmd);
return -1;
}
vnode_cleanupcmdio(clientcmdio);
return cmd->cmdid;
}