qemu/contrib/ivshmem-client/ivshmem-client.c
<<
>>
Prefs
   1/*
   2 * Copyright 6WIND S.A., 2014
   3 *
   4 * This work is licensed under the terms of the GNU GPL, version 2 or
   5 * (at your option) any later version.  See the COPYING file in the
   6 * top-level directory.
   7 */
   8
   9#include "qemu/osdep.h"
  10#include <sys/socket.h>
  11#include <sys/un.h>
  12
  13#include "qemu/queue.h"
  14
  15#include "ivshmem-client.h"
  16
  17/* log a message on stdout if verbose=1 */
  18#define IVSHMEM_CLIENT_DEBUG(client, fmt, ...) do { \
  19        if ((client)->verbose) {         \
  20            printf(fmt, ## __VA_ARGS__); \
  21        }                                \
  22    } while (0)
  23
  24/* read message from the unix socket */
  25static int
  26ivshmem_client_read_one_msg(IvshmemClient *client, int64_t *index, int *fd)
  27{
  28    int ret;
  29    struct msghdr msg;
  30    struct iovec iov[1];
  31    union {
  32        struct cmsghdr cmsg;
  33        char control[CMSG_SPACE(sizeof(int))];
  34    } msg_control;
  35    struct cmsghdr *cmsg;
  36
  37    iov[0].iov_base = index;
  38    iov[0].iov_len = sizeof(*index);
  39
  40    memset(&msg, 0, sizeof(msg));
  41    msg.msg_iov = iov;
  42    msg.msg_iovlen = 1;
  43    msg.msg_control = &msg_control;
  44    msg.msg_controllen = sizeof(msg_control);
  45
  46    ret = recvmsg(client->sock_fd, &msg, 0);
  47    if (ret < sizeof(*index)) {
  48        IVSHMEM_CLIENT_DEBUG(client, "cannot read message: %s\n",
  49                             strerror(errno));
  50        return -1;
  51    }
  52    if (ret == 0) {
  53        IVSHMEM_CLIENT_DEBUG(client, "lost connection to server\n");
  54        return -1;
  55    }
  56
  57    *index = GINT64_FROM_LE(*index);
  58    *fd = -1;
  59
  60    for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
  61
  62        if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)) ||
  63            cmsg->cmsg_level != SOL_SOCKET ||
  64            cmsg->cmsg_type != SCM_RIGHTS) {
  65            continue;
  66        }
  67
  68        memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd));
  69    }
  70
  71    return 0;
  72}
  73
  74/* free a peer when the server advertises a disconnection or when the
  75 * client is freed */
  76static void
  77ivshmem_client_free_peer(IvshmemClient *client, IvshmemClientPeer *peer)
  78{
  79    unsigned vector;
  80
  81    QTAILQ_REMOVE(&client->peer_list, peer, next);
  82    for (vector = 0; vector < peer->vectors_count; vector++) {
  83        close(peer->vectors[vector]);
  84    }
  85
  86    g_free(peer);
  87}
  88
  89/* handle message coming from server (new peer, new vectors) */
  90static int
  91ivshmem_client_handle_server_msg(IvshmemClient *client)
  92{
  93    IvshmemClientPeer *peer;
  94    int64_t peer_id;
  95    int ret, fd;
  96
  97    ret = ivshmem_client_read_one_msg(client, &peer_id, &fd);
  98    if (ret < 0) {
  99        return -1;
 100    }
 101
 102    /* can return a peer or the local client */
 103    peer = ivshmem_client_search_peer(client, peer_id);
 104
 105    /* delete peer */
 106    if (fd == -1) {
 107
 108        if (peer == NULL || peer == &client->local) {
 109            IVSHMEM_CLIENT_DEBUG(client, "receive delete for invalid "
 110                                 "peer %" PRId64 "\n", peer_id);
 111            return -1;
 112        }
 113
 114        IVSHMEM_CLIENT_DEBUG(client, "delete peer id = %" PRId64 "\n", peer_id);
 115        ivshmem_client_free_peer(client, peer);
 116        return 0;
 117    }
 118
 119    /* new peer */
 120    if (peer == NULL) {
 121        peer = g_malloc0(sizeof(*peer));
 122        peer->id = peer_id;
 123        peer->vectors_count = 0;
 124        QTAILQ_INSERT_TAIL(&client->peer_list, peer, next);
 125        IVSHMEM_CLIENT_DEBUG(client, "new peer id = %" PRId64 "\n", peer_id);
 126    }
 127
 128    /* new vector */
 129    IVSHMEM_CLIENT_DEBUG(client, "  new vector %d (fd=%d) for peer id %"
 130                         PRId64 "\n", peer->vectors_count, fd, peer->id);
 131    if (peer->vectors_count >= G_N_ELEMENTS(peer->vectors)) {
 132        IVSHMEM_CLIENT_DEBUG(client, "Too many vectors received, failing");
 133        return -1;
 134    }
 135
 136    peer->vectors[peer->vectors_count] = fd;
 137    peer->vectors_count++;
 138
 139    return 0;
 140}
 141
 142/* init a new ivshmem client */
 143int
 144ivshmem_client_init(IvshmemClient *client, const char *unix_sock_path,
 145                    IvshmemClientNotifCb notif_cb, void *notif_arg,
 146                    bool verbose)
 147{
 148    int ret;
 149    unsigned i;
 150
 151    memset(client, 0, sizeof(*client));
 152
 153    ret = snprintf(client->unix_sock_path, sizeof(client->unix_sock_path),
 154                   "%s", unix_sock_path);
 155
 156    if (ret < 0 || ret >= sizeof(client->unix_sock_path)) {
 157        IVSHMEM_CLIENT_DEBUG(client, "could not copy unix socket path\n");
 158        return -1;
 159    }
 160
 161    for (i = 0; i < IVSHMEM_CLIENT_MAX_VECTORS; i++) {
 162        client->local.vectors[i] = -1;
 163    }
 164
 165    QTAILQ_INIT(&client->peer_list);
 166    client->local.id = -1;
 167
 168    client->notif_cb = notif_cb;
 169    client->notif_arg = notif_arg;
 170    client->verbose = verbose;
 171    client->shm_fd = -1;
 172    client->sock_fd = -1;
 173
 174    return 0;
 175}
 176
 177/* create and connect to the unix socket */
 178int
 179ivshmem_client_connect(IvshmemClient *client)
 180{
 181    struct sockaddr_un s_un;
 182    int fd, ret;
 183    int64_t tmp;
 184
 185    IVSHMEM_CLIENT_DEBUG(client, "connect to client %s\n",
 186                         client->unix_sock_path);
 187
 188    client->sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
 189    if (client->sock_fd < 0) {
 190        IVSHMEM_CLIENT_DEBUG(client, "cannot create socket: %s\n",
 191                             strerror(errno));
 192        return -1;
 193    }
 194
 195    s_un.sun_family = AF_UNIX;
 196    ret = snprintf(s_un.sun_path, sizeof(s_un.sun_path), "%s",
 197                   client->unix_sock_path);
 198    if (ret < 0 || ret >= sizeof(s_un.sun_path)) {
 199        IVSHMEM_CLIENT_DEBUG(client, "could not copy unix socket path\n");
 200        goto err_close;
 201    }
 202
 203    if (connect(client->sock_fd, (struct sockaddr *)&s_un, sizeof(s_un)) < 0) {
 204        IVSHMEM_CLIENT_DEBUG(client, "cannot connect to %s: %s\n", s_un.sun_path,
 205                             strerror(errno));
 206        goto err_close;
 207    }
 208
 209    /* first, we expect a protocol version */
 210    if (ivshmem_client_read_one_msg(client, &tmp, &fd) < 0 ||
 211        (tmp != IVSHMEM_PROTOCOL_VERSION) || fd != -1) {
 212        IVSHMEM_CLIENT_DEBUG(client, "cannot read from server\n");
 213        goto err_close;
 214    }
 215
 216    /* then, we expect our index + a fd == -1 */
 217    if (ivshmem_client_read_one_msg(client, &client->local.id, &fd) < 0 ||
 218        client->local.id < 0 || fd != -1) {
 219        IVSHMEM_CLIENT_DEBUG(client, "cannot read from server (2)\n");
 220        goto err_close;
 221    }
 222    IVSHMEM_CLIENT_DEBUG(client, "our_id=%" PRId64 "\n", client->local.id);
 223
 224    /* now, we expect shared mem fd + a -1 index, note that shm fd
 225     * is not used */
 226    if (ivshmem_client_read_one_msg(client, &tmp, &fd) < 0 ||
 227        tmp != -1 || fd < 0) {
 228        if (fd >= 0) {
 229            close(fd);
 230        }
 231        IVSHMEM_CLIENT_DEBUG(client, "cannot read from server (3)\n");
 232        goto err_close;
 233    }
 234    client->shm_fd = fd;
 235    IVSHMEM_CLIENT_DEBUG(client, "shm_fd=%d\n", fd);
 236
 237    return 0;
 238
 239err_close:
 240    close(client->sock_fd);
 241    client->sock_fd = -1;
 242    return -1;
 243}
 244
 245/* close connection to the server, and free all peer structures */
 246void
 247ivshmem_client_close(IvshmemClient *client)
 248{
 249    IvshmemClientPeer *peer;
 250    unsigned i;
 251
 252    IVSHMEM_CLIENT_DEBUG(client, "close client\n");
 253
 254    while ((peer = QTAILQ_FIRST(&client->peer_list)) != NULL) {
 255        ivshmem_client_free_peer(client, peer);
 256    }
 257
 258    close(client->shm_fd);
 259    client->shm_fd = -1;
 260    close(client->sock_fd);
 261    client->sock_fd = -1;
 262    client->local.id = -1;
 263    for (i = 0; i < IVSHMEM_CLIENT_MAX_VECTORS; i++) {
 264        close(client->local.vectors[i]);
 265        client->local.vectors[i] = -1;
 266    }
 267    client->local.vectors_count = 0;
 268}
 269
 270/* get the fd_set according to the unix socket and peer list */
 271void
 272ivshmem_client_get_fds(const IvshmemClient *client, fd_set *fds, int *maxfd)
 273{
 274    int fd;
 275    unsigned vector;
 276
 277    FD_SET(client->sock_fd, fds);
 278    if (client->sock_fd >= *maxfd) {
 279        *maxfd = client->sock_fd + 1;
 280    }
 281
 282    for (vector = 0; vector < client->local.vectors_count; vector++) {
 283        fd = client->local.vectors[vector];
 284        FD_SET(fd, fds);
 285        if (fd >= *maxfd) {
 286            *maxfd = fd + 1;
 287        }
 288    }
 289}
 290
 291/* handle events from eventfd: just print a message on notification */
 292static int
 293ivshmem_client_handle_event(IvshmemClient *client, const fd_set *cur, int maxfd)
 294{
 295    IvshmemClientPeer *peer;
 296    uint64_t kick;
 297    unsigned i;
 298    int ret;
 299
 300    peer = &client->local;
 301
 302    for (i = 0; i < peer->vectors_count; i++) {
 303        if (peer->vectors[i] >= maxfd || !FD_ISSET(peer->vectors[i], cur)) {
 304            continue;
 305        }
 306
 307        ret = read(peer->vectors[i], &kick, sizeof(kick));
 308        if (ret < 0) {
 309            return ret;
 310        }
 311        if (ret != sizeof(kick)) {
 312            IVSHMEM_CLIENT_DEBUG(client, "invalid read size = %d\n", ret);
 313            errno = EINVAL;
 314            return -1;
 315        }
 316        IVSHMEM_CLIENT_DEBUG(client, "received event on fd %d vector %d: %"
 317                             PRIu64 "\n", peer->vectors[i], i, kick);
 318        if (client->notif_cb != NULL) {
 319            client->notif_cb(client, peer, i, client->notif_arg);
 320        }
 321    }
 322
 323    return 0;
 324}
 325
 326/* read and handle new messages on the given fd_set */
 327int
 328ivshmem_client_handle_fds(IvshmemClient *client, fd_set *fds, int maxfd)
 329{
 330    if (client->sock_fd < maxfd && FD_ISSET(client->sock_fd, fds) &&
 331        ivshmem_client_handle_server_msg(client) < 0 && errno != EINTR) {
 332        IVSHMEM_CLIENT_DEBUG(client, "ivshmem_client_handle_server_msg() "
 333                             "failed\n");
 334        return -1;
 335    } else if (ivshmem_client_handle_event(client, fds, maxfd) < 0 &&
 336               errno != EINTR) {
 337        IVSHMEM_CLIENT_DEBUG(client, "ivshmem_client_handle_event() failed\n");
 338        return -1;
 339    }
 340
 341    return 0;
 342}
 343
 344/* send a notification on a vector of a peer */
 345int
 346ivshmem_client_notify(const IvshmemClient *client,
 347                      const IvshmemClientPeer *peer, unsigned vector)
 348{
 349    uint64_t kick;
 350    int fd;
 351
 352    if (vector >= peer->vectors_count) {
 353        IVSHMEM_CLIENT_DEBUG(client, "invalid vector %u on peer %" PRId64 "\n",
 354                             vector, peer->id);
 355        return -1;
 356    }
 357    fd = peer->vectors[vector];
 358    IVSHMEM_CLIENT_DEBUG(client, "notify peer %" PRId64
 359                         " on vector %d, fd %d\n", peer->id, vector, fd);
 360
 361    kick = 1;
 362    if (write(fd, &kick, sizeof(kick)) != sizeof(kick)) {
 363        fprintf(stderr, "could not write to %d: %s\n", peer->vectors[vector],
 364                strerror(errno));
 365        return -1;
 366    }
 367    return 0;
 368}
 369
 370/* send a notification to all vectors of a peer */
 371int
 372ivshmem_client_notify_all_vects(const IvshmemClient *client,
 373                                const IvshmemClientPeer *peer)
 374{
 375    unsigned vector;
 376    int ret = 0;
 377
 378    for (vector = 0; vector < peer->vectors_count; vector++) {
 379        if (ivshmem_client_notify(client, peer, vector) < 0) {
 380            ret = -1;
 381        }
 382    }
 383
 384    return ret;
 385}
 386
 387/* send a notification to all peers */
 388int
 389ivshmem_client_notify_broadcast(const IvshmemClient *client)
 390{
 391    IvshmemClientPeer *peer;
 392    int ret = 0;
 393
 394    QTAILQ_FOREACH(peer, &client->peer_list, next) {
 395        if (ivshmem_client_notify_all_vects(client, peer) < 0) {
 396            ret = -1;
 397        }
 398    }
 399
 400    return ret;
 401}
 402
 403/* lookup peer from its id */
 404IvshmemClientPeer *
 405ivshmem_client_search_peer(IvshmemClient *client, int64_t peer_id)
 406{
 407    IvshmemClientPeer *peer;
 408
 409    if (peer_id == client->local.id) {
 410        return &client->local;
 411    }
 412
 413    QTAILQ_FOREACH(peer, &client->peer_list, next) {
 414        if (peer->id == peer_id) {
 415            return peer;
 416        }
 417    }
 418    return NULL;
 419}
 420
 421/* dump our info, the list of peers their vectors on stdout */
 422void
 423ivshmem_client_dump(const IvshmemClient *client)
 424{
 425    const IvshmemClientPeer *peer;
 426    unsigned vector;
 427
 428    /* dump local infos */
 429    peer = &client->local;
 430    printf("our_id = %" PRId64 "\n", peer->id);
 431    for (vector = 0; vector < peer->vectors_count; vector++) {
 432        printf("  vector %d is enabled (fd=%d)\n", vector,
 433               peer->vectors[vector]);
 434    }
 435
 436    /* dump peers */
 437    QTAILQ_FOREACH(peer, &client->peer_list, next) {
 438        printf("peer_id = %" PRId64 "\n", peer->id);
 439
 440        for (vector = 0; vector < peer->vectors_count; vector++) {
 441            printf("  vector %d is enabled (fd=%d)\n", vector,
 442                   peer->vectors[vector]);
 443        }
 444    }
 445}
 446