f7a199b2b4
The current ivshmem protocol uses 'long' for integers. But the sizeof(long) depends on the host and the endianess is not defined, which may cause portability troubles. Instead, switch to using little-endian int64_t. This breaks the protocol, except on x64 little-endian host where this change should be compatible. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Claudio Fontana <claudio.fontana@huawei.com>
447 lines
12 KiB
C
447 lines
12 KiB
C
/*
|
|
* Copyright 6WIND S.A., 2014
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or
|
|
* (at your option) any later version. See the COPYING file in the
|
|
* top-level directory.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include "qemu-common.h"
|
|
#include "qemu/queue.h"
|
|
|
|
#include "ivshmem-client.h"
|
|
|
|
/* log a message on stdout if verbose=1 */
|
|
#define IVSHMEM_CLIENT_DEBUG(client, fmt, ...) do { \
|
|
if ((client)->verbose) { \
|
|
printf(fmt, ## __VA_ARGS__); \
|
|
} \
|
|
} while (0)
|
|
|
|
/* read message from the unix socket */
|
|
static int
|
|
ivshmem_client_read_one_msg(IvshmemClient *client, int64_t *index, int *fd)
|
|
{
|
|
int ret;
|
|
struct msghdr msg;
|
|
struct iovec iov[1];
|
|
union {
|
|
struct cmsghdr cmsg;
|
|
char control[CMSG_SPACE(sizeof(int))];
|
|
} msg_control;
|
|
struct cmsghdr *cmsg;
|
|
|
|
iov[0].iov_base = index;
|
|
iov[0].iov_len = sizeof(*index);
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = &msg_control;
|
|
msg.msg_controllen = sizeof(msg_control);
|
|
|
|
ret = recvmsg(client->sock_fd, &msg, 0);
|
|
if (ret < sizeof(*index)) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot read message: %s\n",
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
if (ret == 0) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "lost connection to server\n");
|
|
return -1;
|
|
}
|
|
|
|
*index = GINT64_FROM_LE(*index);
|
|
*fd = -1;
|
|
|
|
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
|
|
|
|
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
|
|
cmsg->cmsg_level != SOL_SOCKET ||
|
|
cmsg->cmsg_type != SCM_RIGHTS) {
|
|
continue;
|
|
}
|
|
|
|
memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* free a peer when the server advertises a disconnection or when the
|
|
* client is freed */
|
|
static void
|
|
ivshmem_client_free_peer(IvshmemClient *client, IvshmemClientPeer *peer)
|
|
{
|
|
unsigned vector;
|
|
|
|
QTAILQ_REMOVE(&client->peer_list, peer, next);
|
|
for (vector = 0; vector < peer->vectors_count; vector++) {
|
|
close(peer->vectors[vector]);
|
|
}
|
|
|
|
g_free(peer);
|
|
}
|
|
|
|
/* handle message coming from server (new peer, new vectors) */
|
|
static int
|
|
ivshmem_client_handle_server_msg(IvshmemClient *client)
|
|
{
|
|
IvshmemClientPeer *peer;
|
|
int64_t peer_id;
|
|
int ret, fd;
|
|
|
|
ret = ivshmem_client_read_one_msg(client, &peer_id, &fd);
|
|
if (ret < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* can return a peer or the local client */
|
|
peer = ivshmem_client_search_peer(client, peer_id);
|
|
|
|
/* delete peer */
|
|
if (fd == -1) {
|
|
|
|
if (peer == NULL || peer == &client->local) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "receive delete for invalid "
|
|
"peer %" PRId64 "\n", peer_id);
|
|
return -1;
|
|
}
|
|
|
|
IVSHMEM_CLIENT_DEBUG(client, "delete peer id = %" PRId64 "\n", peer_id);
|
|
ivshmem_client_free_peer(client, peer);
|
|
return 0;
|
|
}
|
|
|
|
/* new peer */
|
|
if (peer == NULL) {
|
|
peer = g_malloc0(sizeof(*peer));
|
|
peer->id = peer_id;
|
|
peer->vectors_count = 0;
|
|
QTAILQ_INSERT_TAIL(&client->peer_list, peer, next);
|
|
IVSHMEM_CLIENT_DEBUG(client, "new peer id = %" PRId64 "\n", peer_id);
|
|
}
|
|
|
|
/* new vector */
|
|
IVSHMEM_CLIENT_DEBUG(client, " new vector %d (fd=%d) for peer id %"
|
|
PRId64 "\n", peer->vectors_count, fd, peer->id);
|
|
if (peer->vectors_count >= G_N_ELEMENTS(peer->vectors)) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "Too many vectors received, failing");
|
|
return -1;
|
|
}
|
|
|
|
peer->vectors[peer->vectors_count] = fd;
|
|
peer->vectors_count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* init a new ivshmem client */
|
|
int
|
|
ivshmem_client_init(IvshmemClient *client, const char *unix_sock_path,
|
|
IvshmemClientNotifCb notif_cb, void *notif_arg,
|
|
bool verbose)
|
|
{
|
|
int ret;
|
|
unsigned i;
|
|
|
|
memset(client, 0, sizeof(*client));
|
|
|
|
ret = snprintf(client->unix_sock_path, sizeof(client->unix_sock_path),
|
|
"%s", unix_sock_path);
|
|
|
|
if (ret < 0 || ret >= sizeof(client->unix_sock_path)) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "could not copy unix socket path\n");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < IVSHMEM_CLIENT_MAX_VECTORS; i++) {
|
|
client->local.vectors[i] = -1;
|
|
}
|
|
|
|
QTAILQ_INIT(&client->peer_list);
|
|
client->local.id = -1;
|
|
|
|
client->notif_cb = notif_cb;
|
|
client->notif_arg = notif_arg;
|
|
client->verbose = verbose;
|
|
client->shm_fd = -1;
|
|
client->sock_fd = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* create and connect to the unix socket */
|
|
int
|
|
ivshmem_client_connect(IvshmemClient *client)
|
|
{
|
|
struct sockaddr_un sun;
|
|
int fd, ret;
|
|
int64_t tmp;
|
|
|
|
IVSHMEM_CLIENT_DEBUG(client, "connect to client %s\n",
|
|
client->unix_sock_path);
|
|
|
|
client->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (client->sock_fd < 0) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot create socket: %s\n",
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
sun.sun_family = AF_UNIX;
|
|
ret = snprintf(sun.sun_path, sizeof(sun.sun_path), "%s",
|
|
client->unix_sock_path);
|
|
if (ret < 0 || ret >= sizeof(sun.sun_path)) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "could not copy unix socket path\n");
|
|
goto err_close;
|
|
}
|
|
|
|
if (connect(client->sock_fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot connect to %s: %s\n", sun.sun_path,
|
|
strerror(errno));
|
|
goto err_close;
|
|
}
|
|
|
|
/* first, we expect a protocol version */
|
|
if (ivshmem_client_read_one_msg(client, &tmp, &fd) < 0 ||
|
|
(tmp != IVSHMEM_PROTOCOL_VERSION) || fd != -1) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot read from server\n");
|
|
goto err_close;
|
|
}
|
|
|
|
/* then, we expect our index + a fd == -1 */
|
|
if (ivshmem_client_read_one_msg(client, &client->local.id, &fd) < 0 ||
|
|
client->local.id < 0 || fd != -1) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot read from server (2)\n");
|
|
goto err_close;
|
|
}
|
|
IVSHMEM_CLIENT_DEBUG(client, "our_id=%" PRId64 "\n", client->local.id);
|
|
|
|
/* now, we expect shared mem fd + a -1 index, note that shm fd
|
|
* is not used */
|
|
if (ivshmem_client_read_one_msg(client, &tmp, &fd) < 0 ||
|
|
tmp != -1 || fd < 0) {
|
|
if (fd >= 0) {
|
|
close(fd);
|
|
}
|
|
IVSHMEM_CLIENT_DEBUG(client, "cannot read from server (3)\n");
|
|
goto err_close;
|
|
}
|
|
client->shm_fd = fd;
|
|
IVSHMEM_CLIENT_DEBUG(client, "shm_fd=%d\n", fd);
|
|
|
|
return 0;
|
|
|
|
err_close:
|
|
close(client->sock_fd);
|
|
client->sock_fd = -1;
|
|
return -1;
|
|
}
|
|
|
|
/* close connection to the server, and free all peer structures */
|
|
void
|
|
ivshmem_client_close(IvshmemClient *client)
|
|
{
|
|
IvshmemClientPeer *peer;
|
|
unsigned i;
|
|
|
|
IVSHMEM_CLIENT_DEBUG(client, "close client\n");
|
|
|
|
while ((peer = QTAILQ_FIRST(&client->peer_list)) != NULL) {
|
|
ivshmem_client_free_peer(client, peer);
|
|
}
|
|
|
|
close(client->shm_fd);
|
|
client->shm_fd = -1;
|
|
close(client->sock_fd);
|
|
client->sock_fd = -1;
|
|
client->local.id = -1;
|
|
for (i = 0; i < IVSHMEM_CLIENT_MAX_VECTORS; i++) {
|
|
close(client->local.vectors[i]);
|
|
client->local.vectors[i] = -1;
|
|
}
|
|
client->local.vectors_count = 0;
|
|
}
|
|
|
|
/* get the fd_set according to the unix socket and peer list */
|
|
void
|
|
ivshmem_client_get_fds(const IvshmemClient *client, fd_set *fds, int *maxfd)
|
|
{
|
|
int fd;
|
|
unsigned vector;
|
|
|
|
FD_SET(client->sock_fd, fds);
|
|
if (client->sock_fd >= *maxfd) {
|
|
*maxfd = client->sock_fd + 1;
|
|
}
|
|
|
|
for (vector = 0; vector < client->local.vectors_count; vector++) {
|
|
fd = client->local.vectors[vector];
|
|
FD_SET(fd, fds);
|
|
if (fd >= *maxfd) {
|
|
*maxfd = fd + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle events from eventfd: just print a message on notification */
|
|
static int
|
|
ivshmem_client_handle_event(IvshmemClient *client, const fd_set *cur, int maxfd)
|
|
{
|
|
IvshmemClientPeer *peer;
|
|
uint64_t kick;
|
|
unsigned i;
|
|
int ret;
|
|
|
|
peer = &client->local;
|
|
|
|
for (i = 0; i < peer->vectors_count; i++) {
|
|
if (peer->vectors[i] >= maxfd || !FD_ISSET(peer->vectors[i], cur)) {
|
|
continue;
|
|
}
|
|
|
|
ret = read(peer->vectors[i], &kick, sizeof(kick));
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
if (ret != sizeof(kick)) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "invalid read size = %d\n", ret);
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
IVSHMEM_CLIENT_DEBUG(client, "received event on fd %d vector %d: %"
|
|
PRIu64 "\n", peer->vectors[i], i, kick);
|
|
if (client->notif_cb != NULL) {
|
|
client->notif_cb(client, peer, i, client->notif_arg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read and handle new messages on the given fd_set */
|
|
int
|
|
ivshmem_client_handle_fds(IvshmemClient *client, fd_set *fds, int maxfd)
|
|
{
|
|
if (client->sock_fd < maxfd && FD_ISSET(client->sock_fd, fds) &&
|
|
ivshmem_client_handle_server_msg(client) < 0 && errno != EINTR) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "ivshmem_client_handle_server_msg() "
|
|
"failed\n");
|
|
return -1;
|
|
} else if (ivshmem_client_handle_event(client, fds, maxfd) < 0 &&
|
|
errno != EINTR) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "ivshmem_client_handle_event() failed\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* send a notification on a vector of a peer */
|
|
int
|
|
ivshmem_client_notify(const IvshmemClient *client,
|
|
const IvshmemClientPeer *peer, unsigned vector)
|
|
{
|
|
uint64_t kick;
|
|
int fd;
|
|
|
|
if (vector >= peer->vectors_count) {
|
|
IVSHMEM_CLIENT_DEBUG(client, "invalid vector %u on peer %" PRId64 "\n",
|
|
vector, peer->id);
|
|
return -1;
|
|
}
|
|
fd = peer->vectors[vector];
|
|
IVSHMEM_CLIENT_DEBUG(client, "notify peer %" PRId64
|
|
" on vector %d, fd %d\n", peer->id, vector, fd);
|
|
|
|
kick = 1;
|
|
if (write(fd, &kick, sizeof(kick)) != sizeof(kick)) {
|
|
fprintf(stderr, "could not write to %d: %s\n", peer->vectors[vector],
|
|
strerror(errno));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* send a notification to all vectors of a peer */
|
|
int
|
|
ivshmem_client_notify_all_vects(const IvshmemClient *client,
|
|
const IvshmemClientPeer *peer)
|
|
{
|
|
unsigned vector;
|
|
int ret = 0;
|
|
|
|
for (vector = 0; vector < peer->vectors_count; vector++) {
|
|
if (ivshmem_client_notify(client, peer, vector) < 0) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* send a notification to all peers */
|
|
int
|
|
ivshmem_client_notify_broadcast(const IvshmemClient *client)
|
|
{
|
|
IvshmemClientPeer *peer;
|
|
int ret = 0;
|
|
|
|
QTAILQ_FOREACH(peer, &client->peer_list, next) {
|
|
if (ivshmem_client_notify_all_vects(client, peer) < 0) {
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* lookup peer from its id */
|
|
IvshmemClientPeer *
|
|
ivshmem_client_search_peer(IvshmemClient *client, int64_t peer_id)
|
|
{
|
|
IvshmemClientPeer *peer;
|
|
|
|
if (peer_id == client->local.id) {
|
|
return &client->local;
|
|
}
|
|
|
|
QTAILQ_FOREACH(peer, &client->peer_list, next) {
|
|
if (peer->id == peer_id) {
|
|
return peer;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* dump our info, the list of peers their vectors on stdout */
|
|
void
|
|
ivshmem_client_dump(const IvshmemClient *client)
|
|
{
|
|
const IvshmemClientPeer *peer;
|
|
unsigned vector;
|
|
|
|
/* dump local infos */
|
|
peer = &client->local;
|
|
printf("our_id = %" PRId64 "\n", peer->id);
|
|
for (vector = 0; vector < peer->vectors_count; vector++) {
|
|
printf(" vector %d is enabled (fd=%d)\n", vector,
|
|
peer->vectors[vector]);
|
|
}
|
|
|
|
/* dump peers */
|
|
QTAILQ_FOREACH(peer, &client->peer_list, next) {
|
|
printf("peer_id = %" PRId64 "\n", peer->id);
|
|
|
|
for (vector = 0; vector < peer->vectors_count; vector++) {
|
|
printf(" vector %d is enabled (fd=%d)\n", vector,
|
|
peer->vectors[vector]);
|
|
}
|
|
}
|
|
}
|