xref: /qemu/contrib/vhost-user-scsi/vhost-user-scsi.c (revision 62ddfba03452beff05dffe40abc44e8b10429d58)
1 /*
2  * vhost-user-scsi sample application
3  *
4  * Copyright (c) 2016 Nutanix Inc. All rights reserved.
5  *
6  * Author:
7  *  Felipe Franciosi <felipe@nutanix.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 only.
10  * See the COPYING file in the top-level directory.
11  */
12 
13 #include "qemu/osdep.h"
14 #include "contrib/libvhost-user/libvhost-user-glib.h"
15 #include "standard-headers/linux/virtio_scsi.h"
16 #include "iscsi/iscsi.h"
17 #include "iscsi/scsi-lowlevel.h"
18 
19 #include <glib.h>
20 
21 #define VUS_ISCSI_INITIATOR "iqn.2016-11.com.nutanix:vhost-user-scsi"
22 
23 typedef struct VusIscsiLun {
24     struct iscsi_context *iscsi_ctx;
25     int iscsi_lun;
26 } VusIscsiLun;
27 
28 typedef struct VusDev {
29     VugDev parent;
30 
31     int server_sock;
32     VusIscsiLun lun;
33     GMainLoop *loop;
34 } VusDev;
35 
36 /** libiscsi integration **/
37 
38 typedef struct virtio_scsi_cmd_req VirtIOSCSICmdReq;
39 typedef struct virtio_scsi_cmd_resp VirtIOSCSICmdResp;
40 
41 static int vus_iscsi_add_lun(VusIscsiLun *lun, char *iscsi_uri)
42 {
43     struct iscsi_url *iscsi_url;
44     struct iscsi_context *iscsi_ctx;
45     int ret = 0;
46 
47     assert(lun);
48     assert(iscsi_uri);
49     assert(!lun->iscsi_ctx);
50 
51     iscsi_ctx = iscsi_create_context(VUS_ISCSI_INITIATOR);
52     if (!iscsi_ctx) {
53         g_warning("Unable to create iSCSI context");
54         return -1;
55     }
56 
57     iscsi_url = iscsi_parse_full_url(iscsi_ctx, iscsi_uri);
58     if (!iscsi_url) {
59         g_warning("Unable to parse iSCSI URL: %s", iscsi_get_error(iscsi_ctx));
60         goto fail;
61     }
62 
63     iscsi_set_session_type(iscsi_ctx, ISCSI_SESSION_NORMAL);
64     iscsi_set_header_digest(iscsi_ctx, ISCSI_HEADER_DIGEST_NONE_CRC32C);
65     if (iscsi_full_connect_sync(iscsi_ctx, iscsi_url->portal, iscsi_url->lun)) {
66         g_warning("Unable to login to iSCSI portal: %s",
67                   iscsi_get_error(iscsi_ctx));
68         goto fail;
69     }
70 
71     lun->iscsi_ctx = iscsi_ctx;
72     lun->iscsi_lun = iscsi_url->lun;
73 
74     g_debug("Context %p created for lun 0: %s", iscsi_ctx, iscsi_uri);
75 
76 out:
77     if (iscsi_url) {
78         iscsi_destroy_url(iscsi_url);
79     }
80     return ret;
81 
82 fail:
83     (void)iscsi_destroy_context(iscsi_ctx);
84     ret = -1;
85     goto out;
86 }
87 
88 static struct scsi_task *scsi_task_new(int cdb_len, uint8_t *cdb, int dir,
89                                        int xfer_len)
90 {
91     struct scsi_task *task;
92 
93     assert(cdb_len > 0);
94     assert(cdb);
95 
96     task = g_new0(struct scsi_task, 1);
97     memcpy(task->cdb, cdb, cdb_len);
98     task->cdb_size = cdb_len;
99     task->xfer_dir = dir;
100     task->expxferlen = xfer_len;
101 
102     return task;
103 }
104 
105 static int get_cdb_len(uint8_t *cdb)
106 {
107     assert(cdb);
108 
109     switch (cdb[0] >> 5) {
110     case 0: return 6;
111     case 1: /* fall through */
112     case 2: return 10;
113     case 4: return 16;
114     case 5: return 12;
115     }
116     g_warning("Unable to determine cdb len (0x%02hhX)", cdb[0] >> 5);
117     return -1;
118 }
119 
120 static int handle_cmd_sync(struct iscsi_context *ctx,
121                            VirtIOSCSICmdReq *req,
122                            struct iovec *out, unsigned int out_len,
123                            VirtIOSCSICmdResp *rsp,
124                            struct iovec *in, unsigned int in_len)
125 {
126     struct scsi_task *task;
127     uint32_t dir;
128     uint32_t len;
129     int cdb_len;
130     int i;
131 
132     assert(ctx);
133     assert(req);
134     assert(rsp);
135 
136     if (!(!req->lun[1] && req->lun[2] == 0x40 && !req->lun[3])) {
137         /* Ignore anything different than target=0, lun=0 */
138         g_debug("Ignoring unconnected lun (0x%hhX, 0x%hhX)",
139              req->lun[1], req->lun[3]);
140         rsp->status = SCSI_STATUS_CHECK_CONDITION;
141         memset(rsp->sense, 0, sizeof(rsp->sense));
142         rsp->sense_len = 18;
143         rsp->sense[0] = 0x70;
144         rsp->sense[2] = SCSI_SENSE_ILLEGAL_REQUEST;
145         rsp->sense[7] = 10;
146         rsp->sense[12] = 0x24;
147 
148         return 0;
149     }
150 
151     cdb_len = get_cdb_len(req->cdb);
152     if (cdb_len == -1) {
153         return -1;
154     }
155 
156     len = 0;
157     if (!out_len && !in_len) {
158         dir = SCSI_XFER_NONE;
159     } else if (out_len) {
160         dir = SCSI_XFER_WRITE;
161         for (i = 0; i < out_len; i++) {
162             len += out[i].iov_len;
163         }
164     } else {
165         dir = SCSI_XFER_READ;
166         for (i = 0; i < in_len; i++) {
167             len += in[i].iov_len;
168         }
169     }
170 
171     task = scsi_task_new(cdb_len, req->cdb, dir, len);
172 
173     if (dir == SCSI_XFER_WRITE) {
174         task->iovector_out.iov = (struct scsi_iovec *)out;
175         task->iovector_out.niov = out_len;
176     } else if (dir == SCSI_XFER_READ) {
177         task->iovector_in.iov = (struct scsi_iovec *)in;
178         task->iovector_in.niov = in_len;
179     }
180 
181     g_debug("Sending iscsi cmd (cdb_len=%d, dir=%d, task=%p)",
182          cdb_len, dir, task);
183     if (!iscsi_scsi_command_sync(ctx, 0, task, NULL)) {
184         g_warning("Error serving SCSI command");
185         g_free(task);
186         return -1;
187     }
188 
189     memset(rsp, 0, sizeof(*rsp));
190 
191     rsp->status = task->status;
192     rsp->resid  = task->residual;
193 
194     if (task->status == SCSI_STATUS_CHECK_CONDITION) {
195         rsp->response = VIRTIO_SCSI_S_FAILURE;
196         rsp->sense_len = task->datain.size - 2;
197         memcpy(rsp->sense, &task->datain.data[2], rsp->sense_len);
198     }
199 
200     g_free(task);
201 
202     g_debug("Filled in rsp: status=%hhX, resid=%u, response=%hhX, sense_len=%u",
203          rsp->status, rsp->resid, rsp->response, rsp->sense_len);
204 
205     return 0;
206 }
207 
208 /** libvhost-user callbacks **/
209 
210 static void vus_panic_cb(VuDev *vu_dev, const char *buf)
211 {
212     VugDev *gdev;
213     VusDev *vdev_scsi;
214 
215     assert(vu_dev);
216 
217     gdev = container_of(vu_dev, VugDev, parent);
218     vdev_scsi = container_of(gdev, VusDev, parent);
219     if (buf) {
220         g_warning("vu_panic: %s", buf);
221     }
222 
223     g_main_loop_quit(vdev_scsi->loop);
224 }
225 
226 static void vus_proc_req(VuDev *vu_dev, int idx)
227 {
228     VugDev *gdev;
229     VusDev *vdev_scsi;
230     VuVirtq *vq;
231 
232     assert(vu_dev);
233 
234     gdev = container_of(vu_dev, VugDev, parent);
235     vdev_scsi = container_of(gdev, VusDev, parent);
236     if (idx < 0 || idx >= VHOST_MAX_NR_VIRTQUEUE) {
237         g_warning("VQ Index out of range: %d", idx);
238         vus_panic_cb(vu_dev, NULL);
239         return;
240     }
241 
242     vq = vu_get_queue(vu_dev, idx);
243     if (!vq) {
244         g_warning("Error fetching VQ (dev=%p, idx=%d)", vu_dev, idx);
245         vus_panic_cb(vu_dev, NULL);
246         return;
247     }
248 
249     g_debug("Got kicked on vq[%d]@%p", idx, vq);
250 
251     while (1) {
252         VuVirtqElement *elem;
253         VirtIOSCSICmdReq *req;
254         VirtIOSCSICmdResp *rsp;
255 
256         elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement));
257         if (!elem) {
258             g_debug("No more elements pending on vq[%d]@%p", idx, vq);
259             break;
260         }
261         g_debug("Popped elem@%p", elem);
262 
263         assert(!(elem->out_num > 1 && elem->in_num > 1));
264         assert(elem->out_num > 0 && elem->in_num > 0);
265 
266         if (elem->out_sg[0].iov_len < sizeof(VirtIOSCSICmdReq)) {
267             g_warning("Invalid virtio-scsi req header");
268             vus_panic_cb(vu_dev, NULL);
269             break;
270         }
271         req = (VirtIOSCSICmdReq *)elem->out_sg[0].iov_base;
272 
273         if (elem->in_sg[0].iov_len < sizeof(VirtIOSCSICmdResp)) {
274             g_warning("Invalid virtio-scsi rsp header");
275             vus_panic_cb(vu_dev, NULL);
276             break;
277         }
278         rsp = (VirtIOSCSICmdResp *)elem->in_sg[0].iov_base;
279 
280         if (handle_cmd_sync(vdev_scsi->lun.iscsi_ctx,
281                             req, &elem->out_sg[1], elem->out_num - 1,
282                             rsp, &elem->in_sg[1], elem->in_num - 1) != 0) {
283             vus_panic_cb(vu_dev, NULL);
284             break;
285         }
286 
287         vu_queue_push(vu_dev, vq, elem, 0);
288         vu_queue_notify(vu_dev, vq);
289 
290         free(elem);
291     }
292 }
293 
294 static void vus_queue_set_started(VuDev *vu_dev, int idx, bool started)
295 {
296     VuVirtq *vq;
297 
298     assert(vu_dev);
299 
300     if (idx < 0 || idx >= VHOST_MAX_NR_VIRTQUEUE) {
301         g_warning("VQ Index out of range: %d", idx);
302         vus_panic_cb(vu_dev, NULL);
303         return;
304     }
305 
306     vq = vu_get_queue(vu_dev, idx);
307 
308     if (idx == 0 || idx == 1) {
309         g_debug("queue %d unimplemented", idx);
310     } else {
311         vu_set_queue_handler(vu_dev, vq, started ? vus_proc_req : NULL);
312     }
313 }
314 
315 static const VuDevIface vus_iface = {
316     .queue_set_started = vus_queue_set_started,
317 };
318 
319 /** misc helpers **/
320 
321 static int unix_sock_new(char *unix_fn)
322 {
323     int sock;
324     struct sockaddr_un un;
325     size_t len;
326 
327     assert(unix_fn);
328 
329     sock = socket(AF_UNIX, SOCK_STREAM, 0);
330     if (sock <= 0) {
331         perror("socket");
332         return -1;
333     }
334 
335     un.sun_family = AF_UNIX;
336     (void)snprintf(un.sun_path, sizeof(un.sun_path), "%s", unix_fn);
337     len = sizeof(un.sun_family) + strlen(un.sun_path);
338 
339     (void)unlink(unix_fn);
340     if (bind(sock, (struct sockaddr *)&un, len) < 0) {
341         perror("bind");
342         goto fail;
343     }
344 
345     if (listen(sock, 1) < 0) {
346         perror("listen");
347         goto fail;
348     }
349 
350     return sock;
351 
352 fail:
353     (void)close(sock);
354 
355     return -1;
356 }
357 
358 /** vhost-user-scsi **/
359 
360 static void vdev_scsi_free(VusDev *vdev_scsi)
361 {
362     if (vdev_scsi->server_sock >= 0) {
363         close(vdev_scsi->server_sock);
364     }
365     g_main_loop_unref(vdev_scsi->loop);
366     g_free(vdev_scsi);
367 }
368 
369 static VusDev *vdev_scsi_new(int server_sock)
370 {
371     VusDev *vdev_scsi;
372 
373     vdev_scsi = g_new0(VusDev, 1);
374     vdev_scsi->server_sock = server_sock;
375     vdev_scsi->loop = g_main_loop_new(NULL, FALSE);
376 
377     return vdev_scsi;
378 }
379 
380 static int vdev_scsi_run(VusDev *vdev_scsi)
381 {
382     int cli_sock;
383 
384     assert(vdev_scsi);
385     assert(vdev_scsi->server_sock >= 0);
386 
387     cli_sock = accept(vdev_scsi->server_sock, NULL, NULL);
388     if (cli_sock < 0) {
389         perror("accept");
390         return -1;
391     }
392 
393     vug_init(&vdev_scsi->parent,
394              cli_sock,
395              vus_panic_cb,
396              &vus_iface);
397 
398     g_main_loop_run(vdev_scsi->loop);
399 
400     vug_deinit(&vdev_scsi->parent);
401 
402     return 0;
403 }
404 
405 int main(int argc, char **argv)
406 {
407     VusDev *vdev_scsi = NULL;
408     char *unix_fn = NULL;
409     char *iscsi_uri = NULL;
410     int sock, opt, err = EXIT_SUCCESS;
411 
412     while ((opt = getopt(argc, argv, "u:i:")) != -1) {
413         switch (opt) {
414         case 'h':
415             goto help;
416         case 'u':
417             unix_fn = g_strdup(optarg);
418             break;
419         case 'i':
420             iscsi_uri = g_strdup(optarg);
421             break;
422         default:
423             goto help;
424         }
425     }
426     if (!unix_fn || !iscsi_uri) {
427         goto help;
428     }
429 
430     sock = unix_sock_new(unix_fn);
431     if (sock < 0) {
432         goto err;
433     }
434     vdev_scsi = vdev_scsi_new(sock);
435 
436     if (vus_iscsi_add_lun(&vdev_scsi->lun, iscsi_uri) != 0) {
437         goto err;
438     }
439 
440     if (vdev_scsi_run(vdev_scsi) != 0) {
441         goto err;
442     }
443 
444 out:
445     if (vdev_scsi) {
446         vdev_scsi_free(vdev_scsi);
447         unlink(unix_fn);
448     }
449     g_free(unix_fn);
450     g_free(iscsi_uri);
451 
452     return err;
453 
454 err:
455     err = EXIT_FAILURE;
456     goto out;
457 
458 help:
459     fprintf(stderr, "Usage: %s [ -u unix_sock_path -i iscsi_uri ] | [ -h ]\n",
460             argv[0]);
461     fprintf(stderr, "          -u path to unix socket\n");
462     fprintf(stderr, "          -i iscsi uri for lun 0\n");
463     fprintf(stderr, "          -h print help and quit\n");
464 
465     goto err;
466 }
467