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 #include "qemu/osdep.h"
9 #include "qemu-common.h"
10 #include "qemu/host-utils.h"
11 #include "qemu/sockets.h"
12 
13 #include <sys/mman.h>
14 #include <sys/socket.h>
15 #include <sys/un.h>
16 
17 #include "ivshmem-server.h"
18 
19 /* log a message on stdout if verbose=1 */
20 #define IVSHMEM_SERVER_DEBUG(server, fmt, ...) do { \
21         if ((server)->verbose) {         \
22             printf(fmt, ## __VA_ARGS__); \
23         }                                \
24     } while (0)
25 
26 /** maximum size of a huge page, used by ivshmem_server_ftruncate() */
27 #define IVSHMEM_SERVER_MAX_HUGEPAGE_SIZE (1024 * 1024 * 1024)
28 
29 /** default listen backlog (number of sockets not accepted) */
30 #define IVSHMEM_SERVER_LISTEN_BACKLOG 10
31 
32 /* send message to a client unix socket */
33 static int
34 ivshmem_server_send_one_msg(int sock_fd, int64_t peer_id, int fd)
35 {
36     int ret;
37     struct msghdr msg;
38     struct iovec iov[1];
39     union {
40         struct cmsghdr cmsg;
41         char control[CMSG_SPACE(sizeof(int))];
42     } msg_control;
43     struct cmsghdr *cmsg;
44 
45     peer_id = GINT64_TO_LE(peer_id);
46     iov[0].iov_base = &peer_id;
47     iov[0].iov_len = sizeof(peer_id);
48 
49     memset(&msg, 0, sizeof(msg));
50     msg.msg_iov = iov;
51     msg.msg_iovlen = 1;
52 
53     /* if fd is specified, add it in a cmsg */
54     if (fd >= 0) {
55         memset(&msg_control, 0, sizeof(msg_control));
56         msg.msg_control = &msg_control;
57         msg.msg_controllen = sizeof(msg_control);
58         cmsg = CMSG_FIRSTHDR(&msg);
59         cmsg->cmsg_level = SOL_SOCKET;
60         cmsg->cmsg_type = SCM_RIGHTS;
61         cmsg->cmsg_len = CMSG_LEN(sizeof(int));
62         memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
63     }
64 
65     ret = sendmsg(sock_fd, &msg, 0);
66     if (ret <= 0) {
67         return -1;
68     }
69 
70     return 0;
71 }
72 
73 /* free a peer when the server advertises a disconnection or when the
74  * server is freed */
75 static void
76 ivshmem_server_free_peer(IvshmemServer *server, IvshmemServerPeer *peer)
77 {
78     unsigned vector;
79     IvshmemServerPeer *other_peer;
80 
81     IVSHMEM_SERVER_DEBUG(server, "free peer %" PRId64 "\n", peer->id);
82     close(peer->sock_fd);
83     QTAILQ_REMOVE(&server->peer_list, peer, next);
84 
85     /* advertise the deletion to other peers */
86     QTAILQ_FOREACH(other_peer, &server->peer_list, next) {
87         ivshmem_server_send_one_msg(other_peer->sock_fd, peer->id, -1);
88     }
89 
90     for (vector = 0; vector < peer->vectors_count; vector++) {
91         event_notifier_cleanup(&peer->vectors[vector]);
92     }
93 
94     g_free(peer);
95 }
96 
97 /* send the peer id and the shm_fd just after a new client connection */
98 static int
99 ivshmem_server_send_initial_info(IvshmemServer *server, IvshmemServerPeer *peer)
100 {
101     int ret;
102 
103     /* send our protocol version first */
104     ret = ivshmem_server_send_one_msg(peer->sock_fd, IVSHMEM_PROTOCOL_VERSION,
105                                       -1);
106     if (ret < 0) {
107         IVSHMEM_SERVER_DEBUG(server, "cannot send version: %s\n",
108                              strerror(errno));
109         return -1;
110     }
111 
112     /* send the peer id to the client */
113     ret = ivshmem_server_send_one_msg(peer->sock_fd, peer->id, -1);
114     if (ret < 0) {
115         IVSHMEM_SERVER_DEBUG(server, "cannot send peer id: %s\n",
116                              strerror(errno));
117         return -1;
118     }
119 
120     /* send the shm_fd */
121     ret = ivshmem_server_send_one_msg(peer->sock_fd, -1, server->shm_fd);
122     if (ret < 0) {
123         IVSHMEM_SERVER_DEBUG(server, "cannot send shm fd: %s\n",
124                              strerror(errno));
125         return -1;
126     }
127 
128     return 0;
129 }
130 
131 /* handle message on listening unix socket (new client connection) */
132 static int
133 ivshmem_server_handle_new_conn(IvshmemServer *server)
134 {
135     IvshmemServerPeer *peer, *other_peer;
136     struct sockaddr_un unaddr;
137     socklen_t unaddr_len;
138     int newfd;
139     unsigned i;
140 
141     /* accept the incoming connection */
142     unaddr_len = sizeof(unaddr);
143     newfd = qemu_accept(server->sock_fd,
144                         (struct sockaddr *)&unaddr, &unaddr_len);
145 
146     if (newfd < 0) {
147         IVSHMEM_SERVER_DEBUG(server, "cannot accept() %s\n", strerror(errno));
148         return -1;
149     }
150 
151     qemu_set_nonblock(newfd);
152     IVSHMEM_SERVER_DEBUG(server, "accept()=%d\n", newfd);
153 
154     /* allocate new structure for this peer */
155     peer = g_malloc0(sizeof(*peer));
156     peer->sock_fd = newfd;
157 
158     /* get an unused peer id */
159     /* XXX: this could use id allocation such as Linux IDA, or simply
160      * a free-list */
161     for (i = 0; i < G_MAXUINT16; i++) {
162         if (ivshmem_server_search_peer(server, server->cur_id) == NULL) {
163             break;
164         }
165         server->cur_id++;
166     }
167     if (i == G_MAXUINT16) {
168         IVSHMEM_SERVER_DEBUG(server, "cannot allocate new client id\n");
169         close(newfd);
170         g_free(peer);
171         return -1;
172     }
173     peer->id = server->cur_id++;
174 
175     /* create eventfd, one per vector */
176     peer->vectors_count = server->n_vectors;
177     for (i = 0; i < peer->vectors_count; i++) {
178         if (event_notifier_init(&peer->vectors[i], FALSE) < 0) {
179             IVSHMEM_SERVER_DEBUG(server, "cannot create eventfd\n");
180             goto fail;
181         }
182     }
183 
184     /* send peer id and shm fd */
185     if (ivshmem_server_send_initial_info(server, peer) < 0) {
186         IVSHMEM_SERVER_DEBUG(server, "cannot send initial info\n");
187         goto fail;
188     }
189 
190     /* advertise the new peer to others */
191     QTAILQ_FOREACH(other_peer, &server->peer_list, next) {
192         for (i = 0; i < peer->vectors_count; i++) {
193             ivshmem_server_send_one_msg(other_peer->sock_fd, peer->id,
194                                         peer->vectors[i].wfd);
195         }
196     }
197 
198     /* advertise the other peers to the new one */
199     QTAILQ_FOREACH(other_peer, &server->peer_list, next) {
200         for (i = 0; i < peer->vectors_count; i++) {
201             ivshmem_server_send_one_msg(peer->sock_fd, other_peer->id,
202                                         other_peer->vectors[i].wfd);
203         }
204     }
205 
206     /* advertise the new peer to itself */
207     for (i = 0; i < peer->vectors_count; i++) {
208         ivshmem_server_send_one_msg(peer->sock_fd, peer->id,
209                                     event_notifier_get_fd(&peer->vectors[i]));
210     }
211 
212     QTAILQ_INSERT_TAIL(&server->peer_list, peer, next);
213     IVSHMEM_SERVER_DEBUG(server, "new peer id = %" PRId64 "\n",
214                          peer->id);
215     return 0;
216 
217 fail:
218     while (i--) {
219         event_notifier_cleanup(&peer->vectors[i]);
220     }
221     close(newfd);
222     g_free(peer);
223     return -1;
224 }
225 
226 /* Try to ftruncate a file to next power of 2 of shmsize.
227  * If it fails; all power of 2 above shmsize are tested until
228  * we reach the maximum huge page size. This is useful
229  * if the shm file is in a hugetlbfs that cannot be truncated to the
230  * shm_size value. */
231 static int
232 ivshmem_server_ftruncate(int fd, unsigned shmsize)
233 {
234     int ret;
235     struct stat mapstat;
236 
237     /* align shmsize to next power of 2 */
238     shmsize = pow2ceil(shmsize);
239 
240     if (fstat(fd, &mapstat) != -1 && mapstat.st_size == shmsize) {
241         return 0;
242     }
243 
244     while (shmsize <= IVSHMEM_SERVER_MAX_HUGEPAGE_SIZE) {
245         ret = ftruncate(fd, shmsize);
246         if (ret == 0) {
247             return ret;
248         }
249         shmsize *= 2;
250     }
251 
252     return -1;
253 }
254 
255 /* Init a new ivshmem server */
256 int
257 ivshmem_server_init(IvshmemServer *server, const char *unix_sock_path,
258                     const char *shm_path, bool use_shm_open,
259                     size_t shm_size, unsigned n_vectors,
260                     bool verbose)
261 {
262     int ret;
263 
264     memset(server, 0, sizeof(*server));
265     server->verbose = verbose;
266 
267     ret = snprintf(server->unix_sock_path, sizeof(server->unix_sock_path),
268                    "%s", unix_sock_path);
269     if (ret < 0 || ret >= sizeof(server->unix_sock_path)) {
270         IVSHMEM_SERVER_DEBUG(server, "could not copy unix socket path\n");
271         return -1;
272     }
273     ret = snprintf(server->shm_path, sizeof(server->shm_path),
274                    "%s", shm_path);
275     if (ret < 0 || ret >= sizeof(server->shm_path)) {
276         IVSHMEM_SERVER_DEBUG(server, "could not copy shm path\n");
277         return -1;
278     }
279 
280     server->use_shm_open = use_shm_open;
281     server->shm_size = shm_size;
282     server->n_vectors = n_vectors;
283 
284     QTAILQ_INIT(&server->peer_list);
285 
286     return 0;
287 }
288 
289 /* open shm, create and bind to the unix socket */
290 int
291 ivshmem_server_start(IvshmemServer *server)
292 {
293     struct sockaddr_un sun;
294     int shm_fd, sock_fd, ret;
295 
296     /* open shm file */
297     if (server->use_shm_open) {
298         IVSHMEM_SERVER_DEBUG(server, "Using POSIX shared memory: %s\n",
299                              server->shm_path);
300         shm_fd = shm_open(server->shm_path, O_CREAT | O_RDWR, S_IRWXU);
301     } else {
302         gchar *filename = g_strdup_printf("%s/ivshmem.XXXXXX", server->shm_path);
303         IVSHMEM_SERVER_DEBUG(server, "Using file-backed shared memory: %s\n",
304                              server->shm_path);
305         shm_fd = mkstemp(filename);
306         unlink(filename);
307         g_free(filename);
308     }
309 
310     if (shm_fd < 0) {
311         fprintf(stderr, "cannot open shm file %s: %s\n", server->shm_path,
312                 strerror(errno));
313         return -1;
314     }
315     if (ivshmem_server_ftruncate(shm_fd, server->shm_size) < 0) {
316         fprintf(stderr, "ftruncate(%s) failed: %s\n", server->shm_path,
317                 strerror(errno));
318         goto err_close_shm;
319     }
320 
321     IVSHMEM_SERVER_DEBUG(server, "create & bind socket %s\n",
322                          server->unix_sock_path);
323 
324     /* create the unix listening socket */
325     sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
326     if (sock_fd < 0) {
327         IVSHMEM_SERVER_DEBUG(server, "cannot create socket: %s\n",
328                              strerror(errno));
329         goto err_close_shm;
330     }
331 
332     sun.sun_family = AF_UNIX;
333     ret = snprintf(sun.sun_path, sizeof(sun.sun_path), "%s",
334                    server->unix_sock_path);
335     if (ret < 0 || ret >= sizeof(sun.sun_path)) {
336         IVSHMEM_SERVER_DEBUG(server, "could not copy unix socket path\n");
337         goto err_close_sock;
338     }
339     if (bind(sock_fd, (struct sockaddr *)&sun, sizeof(sun)) < 0) {
340         IVSHMEM_SERVER_DEBUG(server, "cannot connect to %s: %s\n", sun.sun_path,
341                              strerror(errno));
342         goto err_close_sock;
343     }
344 
345     if (listen(sock_fd, IVSHMEM_SERVER_LISTEN_BACKLOG) < 0) {
346         IVSHMEM_SERVER_DEBUG(server, "listen() failed: %s\n", strerror(errno));
347         goto err_close_sock;
348     }
349 
350     server->sock_fd = sock_fd;
351     server->shm_fd = shm_fd;
352 
353     return 0;
354 
355 err_close_sock:
356     close(sock_fd);
357 err_close_shm:
358     close(shm_fd);
359     return -1;
360 }
361 
362 /* close connections to clients, the unix socket and the shm fd */
363 void
364 ivshmem_server_close(IvshmemServer *server)
365 {
366     IvshmemServerPeer *peer, *npeer;
367 
368     IVSHMEM_SERVER_DEBUG(server, "close server\n");
369 
370     QTAILQ_FOREACH_SAFE(peer, &server->peer_list, next, npeer) {
371         ivshmem_server_free_peer(server, peer);
372     }
373 
374     unlink(server->unix_sock_path);
375     close(server->sock_fd);
376     close(server->shm_fd);
377     server->sock_fd = -1;
378     server->shm_fd = -1;
379 }
380 
381 /* get the fd_set according to the unix socket and the peer list */
382 void
383 ivshmem_server_get_fds(const IvshmemServer *server, fd_set *fds, int *maxfd)
384 {
385     IvshmemServerPeer *peer;
386 
387     if (server->sock_fd == -1) {
388         return;
389     }
390 
391     FD_SET(server->sock_fd, fds);
392     if (server->sock_fd >= *maxfd) {
393         *maxfd = server->sock_fd + 1;
394     }
395 
396     QTAILQ_FOREACH(peer, &server->peer_list, next) {
397         FD_SET(peer->sock_fd, fds);
398         if (peer->sock_fd >= *maxfd) {
399             *maxfd = peer->sock_fd + 1;
400         }
401     }
402 }
403 
404 /* process incoming messages on the sockets in fd_set */
405 int
406 ivshmem_server_handle_fds(IvshmemServer *server, fd_set *fds, int maxfd)
407 {
408     IvshmemServerPeer *peer, *peer_next;
409 
410     if (server->sock_fd < maxfd && FD_ISSET(server->sock_fd, fds) &&
411         ivshmem_server_handle_new_conn(server) < 0 && errno != EINTR) {
412         IVSHMEM_SERVER_DEBUG(server, "ivshmem_server_handle_new_conn() "
413                              "failed\n");
414         return -1;
415     }
416 
417     QTAILQ_FOREACH_SAFE(peer, &server->peer_list, next, peer_next) {
418         /* any message from a peer socket result in a close() */
419         IVSHMEM_SERVER_DEBUG(server, "peer->sock_fd=%d\n", peer->sock_fd);
420         if (peer->sock_fd < maxfd && FD_ISSET(peer->sock_fd, fds)) {
421             ivshmem_server_free_peer(server, peer);
422         }
423     }
424 
425     return 0;
426 }
427 
428 /* lookup peer from its id */
429 IvshmemServerPeer *
430 ivshmem_server_search_peer(IvshmemServer *server, int64_t peer_id)
431 {
432     IvshmemServerPeer *peer;
433 
434     QTAILQ_FOREACH(peer, &server->peer_list, next) {
435         if (peer->id == peer_id) {
436             return peer;
437         }
438     }
439     return NULL;
440 }
441 
442 /* dump our info, the list of peers their vectors on stdout */
443 void
444 ivshmem_server_dump(const IvshmemServer *server)
445 {
446     const IvshmemServerPeer *peer;
447     unsigned vector;
448 
449     /* dump peers */
450     QTAILQ_FOREACH(peer, &server->peer_list, next) {
451         printf("peer_id = %" PRId64 "\n", peer->id);
452 
453         for (vector = 0; vector < peer->vectors_count; vector++) {
454             printf("  vector %d is enabled (fd=%d)\n", vector,
455                    event_notifier_get_fd(&peer->vectors[vector]));
456         }
457     }
458 }
459