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