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 */ 25 static int 26 ivshmem_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 */ 76 static void 77 ivshmem_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) */ 90 static int 91 ivshmem_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 */ 143 int 144 ivshmem_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 */ 178 int 179 ivshmem_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 239 err_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 */ 246 void 247 ivshmem_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 */ 271 void 272 ivshmem_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 */ 292 static int 293 ivshmem_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 */ 327 int 328 ivshmem_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 */ 345 int 346 ivshmem_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 */ 371 int 372 ivshmem_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 */ 388 int 389 ivshmem_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 */ 404 IvshmemClientPeer * 405 ivshmem_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 */ 422 void 423 ivshmem_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