2 * vhost-user-scsi sample application
4 * Copyright (c) 2016 Nutanix Inc. All rights reserved.
7 * Felipe Franciosi <felipe@nutanix.com>
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.
13 #include "qemu/osdep.h"
14 #include <iscsi/iscsi.h>
15 #define inline __attribute__((gnu_inline)) /* required for libiscsi v1.9.0 */
16 #include <iscsi/scsi-lowlevel.h>
18 #include "libvhost-user-glib.h"
19 #include "standard-headers/linux/virtio_scsi.h"
22 #define VUS_ISCSI_INITIATOR "iqn.2016-11.com.nutanix:vhost-user-scsi"
25 VHOST_USER_SCSI_MAX_QUEUES
= 8,
28 typedef struct VusIscsiLun
{
29 struct iscsi_context
*iscsi_ctx
;
33 typedef struct VusDev
{
40 /** libiscsi integration **/
42 typedef struct virtio_scsi_cmd_req VirtIOSCSICmdReq
;
43 typedef struct virtio_scsi_cmd_resp VirtIOSCSICmdResp
;
45 static int vus_iscsi_add_lun(VusIscsiLun
*lun
, char *iscsi_uri
)
47 struct iscsi_url
*iscsi_url
;
48 struct iscsi_context
*iscsi_ctx
;
53 assert(!lun
->iscsi_ctx
);
55 iscsi_ctx
= iscsi_create_context(VUS_ISCSI_INITIATOR
);
57 g_warning("Unable to create iSCSI context");
61 iscsi_url
= iscsi_parse_full_url(iscsi_ctx
, iscsi_uri
);
63 g_warning("Unable to parse iSCSI URL: %s", iscsi_get_error(iscsi_ctx
));
67 iscsi_set_session_type(iscsi_ctx
, ISCSI_SESSION_NORMAL
);
68 iscsi_set_header_digest(iscsi_ctx
, ISCSI_HEADER_DIGEST_NONE_CRC32C
);
69 if (iscsi_full_connect_sync(iscsi_ctx
, iscsi_url
->portal
, iscsi_url
->lun
)) {
70 g_warning("Unable to login to iSCSI portal: %s",
71 iscsi_get_error(iscsi_ctx
));
75 lun
->iscsi_ctx
= iscsi_ctx
;
76 lun
->iscsi_lun
= iscsi_url
->lun
;
78 g_debug("Context %p created for lun 0: %s", iscsi_ctx
, iscsi_uri
);
82 iscsi_destroy_url(iscsi_url
);
87 (void)iscsi_destroy_context(iscsi_ctx
);
92 static struct scsi_task
*scsi_task_new(int cdb_len
, uint8_t *cdb
, int dir
,
95 struct scsi_task
*task
;
100 task
= g_new0(struct scsi_task
, 1);
101 memcpy(task
->cdb
, cdb
, cdb_len
);
102 task
->cdb_size
= cdb_len
;
103 task
->xfer_dir
= dir
;
104 task
->expxferlen
= xfer_len
;
109 static int get_cdb_len(uint8_t *cdb
)
113 switch (cdb
[0] >> 5) {
115 case 1: /* fall through */
120 g_warning("Unable to determine cdb len (0x%02hhX)", (uint8_t)(cdb
[0] >> 5));
124 static int handle_cmd_sync(struct iscsi_context
*ctx
,
125 VirtIOSCSICmdReq
*req
,
126 struct iovec
*out
, unsigned int out_len
,
127 VirtIOSCSICmdResp
*rsp
,
128 struct iovec
*in
, unsigned int in_len
)
130 struct scsi_task
*task
;
140 if (!(!req
->lun
[1] && req
->lun
[2] == 0x40 && !req
->lun
[3])) {
141 /* Ignore anything different than target=0, lun=0 */
142 g_debug("Ignoring unconnected lun (0x%hhX, 0x%hhX)",
143 req
->lun
[1], req
->lun
[3]);
144 rsp
->status
= SCSI_STATUS_CHECK_CONDITION
;
145 memset(rsp
->sense
, 0, sizeof(rsp
->sense
));
147 rsp
->sense
[0] = 0x70;
148 rsp
->sense
[2] = SCSI_SENSE_ILLEGAL_REQUEST
;
150 rsp
->sense
[12] = 0x24;
155 cdb_len
= get_cdb_len(req
->cdb
);
161 if (!out_len
&& !in_len
) {
162 dir
= SCSI_XFER_NONE
;
163 } else if (out_len
) {
164 dir
= SCSI_XFER_WRITE
;
165 for (i
= 0; i
< out_len
; i
++) {
166 len
+= out
[i
].iov_len
;
169 dir
= SCSI_XFER_READ
;
170 for (i
= 0; i
< in_len
; i
++) {
171 len
+= in
[i
].iov_len
;
175 task
= scsi_task_new(cdb_len
, req
->cdb
, dir
, len
);
177 if (dir
== SCSI_XFER_WRITE
) {
178 task
->iovector_out
.iov
= (struct scsi_iovec
*)out
;
179 task
->iovector_out
.niov
= out_len
;
180 } else if (dir
== SCSI_XFER_READ
) {
181 task
->iovector_in
.iov
= (struct scsi_iovec
*)in
;
182 task
->iovector_in
.niov
= in_len
;
185 g_debug("Sending iscsi cmd (cdb_len=%d, dir=%d, task=%p)",
187 if (!iscsi_scsi_command_sync(ctx
, 0, task
, NULL
)) {
188 g_warning("Error serving SCSI command");
193 memset(rsp
, 0, sizeof(*rsp
));
195 rsp
->status
= task
->status
;
196 rsp
->resid
= task
->residual
;
198 if (task
->status
== SCSI_STATUS_CHECK_CONDITION
) {
199 rsp
->response
= VIRTIO_SCSI_S_FAILURE
;
200 rsp
->sense_len
= task
->datain
.size
- 2;
201 memcpy(rsp
->sense
, &task
->datain
.data
[2], rsp
->sense_len
);
206 g_debug("Filled in rsp: status=%hhX, resid=%u, response=%hhX, sense_len=%u",
207 rsp
->status
, rsp
->resid
, rsp
->response
, rsp
->sense_len
);
212 /** libvhost-user callbacks **/
214 static void vus_panic_cb(VuDev
*vu_dev
, const char *buf
)
221 gdev
= container_of(vu_dev
, VugDev
, parent
);
222 vdev_scsi
= container_of(gdev
, VusDev
, parent
);
224 g_warning("vu_panic: %s", buf
);
227 g_main_loop_quit(vdev_scsi
->loop
);
230 static void vus_proc_req(VuDev
*vu_dev
, int idx
)
235 VuVirtqElement
*elem
= NULL
;
239 gdev
= container_of(vu_dev
, VugDev
, parent
);
240 vdev_scsi
= container_of(gdev
, VusDev
, parent
);
242 vq
= vu_get_queue(vu_dev
, idx
);
244 g_warning("Error fetching VQ (dev=%p, idx=%d)", vu_dev
, idx
);
245 vus_panic_cb(vu_dev
, NULL
);
249 g_debug("Got kicked on vq[%d]@%p", idx
, vq
);
252 VirtIOSCSICmdReq
*req
;
253 VirtIOSCSICmdResp
*rsp
;
255 elem
= vu_queue_pop(vu_dev
, vq
, sizeof(VuVirtqElement
));
257 g_debug("No more elements pending on vq[%d]@%p", idx
, vq
);
260 g_debug("Popped elem@%p", elem
);
262 assert(!(elem
->out_num
> 1 && elem
->in_num
> 1));
263 assert(elem
->out_num
> 0 && elem
->in_num
> 0);
265 if (elem
->out_sg
[0].iov_len
< sizeof(VirtIOSCSICmdReq
)) {
266 g_warning("Invalid virtio-scsi req header");
267 vus_panic_cb(vu_dev
, NULL
);
270 req
= (VirtIOSCSICmdReq
*)elem
->out_sg
[0].iov_base
;
272 if (elem
->in_sg
[0].iov_len
< sizeof(VirtIOSCSICmdResp
)) {
273 g_warning("Invalid virtio-scsi rsp header");
274 vus_panic_cb(vu_dev
, NULL
);
277 rsp
= (VirtIOSCSICmdResp
*)elem
->in_sg
[0].iov_base
;
279 if (handle_cmd_sync(vdev_scsi
->lun
.iscsi_ctx
,
280 req
, &elem
->out_sg
[1], elem
->out_num
- 1,
281 rsp
, &elem
->in_sg
[1], elem
->in_num
- 1) != 0) {
282 vus_panic_cb(vu_dev
, NULL
);
286 vu_queue_push(vu_dev
, vq
, elem
, 0);
287 vu_queue_notify(vu_dev
, vq
);
294 static void vus_queue_set_started(VuDev
*vu_dev
, int idx
, bool started
)
300 vq
= vu_get_queue(vu_dev
, idx
);
302 if (idx
== 0 || idx
== 1) {
303 g_debug("queue %d unimplemented", idx
);
305 vu_set_queue_handler(vu_dev
, vq
, started
? vus_proc_req
: NULL
);
309 static const VuDevIface vus_iface
= {
310 .queue_set_started
= vus_queue_set_started
,
315 static int unix_sock_new(char *unix_fn
)
318 struct sockaddr_un un
;
323 sock
= socket(AF_UNIX
, SOCK_STREAM
, 0);
329 un
.sun_family
= AF_UNIX
;
330 (void)snprintf(un
.sun_path
, sizeof(un
.sun_path
), "%s", unix_fn
);
331 len
= sizeof(un
.sun_family
) + strlen(un
.sun_path
);
333 (void)unlink(unix_fn
);
334 if (bind(sock
, (struct sockaddr
*)&un
, len
) < 0) {
339 if (listen(sock
, 1) < 0) {
352 /** vhost-user-scsi **/
354 static int opt_fdnum
= -1;
355 static char *opt_socket_path
;
356 static gboolean opt_print_caps
;
357 static char *iscsi_uri
;
359 static GOptionEntry entries
[] = {
360 { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE
, &opt_print_caps
,
361 "Print capabilities", NULL
},
362 { "fd", 'f', 0, G_OPTION_ARG_INT
, &opt_fdnum
,
363 "Use inherited fd socket", "FDNUM" },
364 { "iscsi-uri", 'i', 0, G_OPTION_ARG_FILENAME
, &iscsi_uri
,
365 "iSCSI URI to connect to", "FDNUM" },
366 { "socket-path", 's', 0, G_OPTION_ARG_FILENAME
, &opt_socket_path
,
367 "Use UNIX socket path", "PATH" },
371 int main(int argc
, char **argv
)
373 VusDev
*vdev_scsi
= NULL
;
374 int lsock
= -1, csock
= -1, err
= EXIT_SUCCESS
;
376 GError
*error
= NULL
;
377 GOptionContext
*context
;
379 context
= g_option_context_new(NULL
);
380 g_option_context_add_main_entries(context
, entries
, NULL
);
381 if (!g_option_context_parse(context
, &argc
, &argv
, &error
)) {
382 g_printerr("Option parsing failed: %s\n", error
->message
);
386 if (opt_print_caps
) {
388 g_print(" \"type\": \"scsi\"\n");
397 if (opt_socket_path
) {
398 lsock
= unix_sock_new(opt_socket_path
);
402 } else if (opt_fdnum
< 0) {
403 g_print("%s\n", g_option_context_get_help(context
, true, NULL
));
409 csock
= accept(lsock
, NULL
, NULL
);
415 vdev_scsi
= g_new0(VusDev
, 1);
416 vdev_scsi
->loop
= g_main_loop_new(NULL
, FALSE
);
418 if (vus_iscsi_add_lun(&vdev_scsi
->lun
, iscsi_uri
) != 0) {
422 if (!vug_init(&vdev_scsi
->parent
, VHOST_USER_SCSI_MAX_QUEUES
, csock
,
423 vus_panic_cb
, &vus_iface
)) {
424 g_printerr("Failed to initialize libvhost-user-glib\n");
428 g_main_loop_run(vdev_scsi
->loop
);
430 vug_deinit(&vdev_scsi
->parent
);
434 g_main_loop_unref(vdev_scsi
->loop
);
443 if (opt_socket_path
) {
444 unlink(opt_socket_path
);
447 g_free(opt_socket_path
);
457 fprintf(stderr
, "Usage: %s [ -s socket-path -i iscsi-uri -f fd -p print-capabilities ] | [ -h ]\n",
459 fprintf(stderr
, " -s, --socket-path=SOCKET_PATH path to unix socket\n");
460 fprintf(stderr
, " -i, --iscsi-uri=ISCSI_URI iscsi uri for lun 0\n");
461 fprintf(stderr
, " -f, --fd=FILE_DESCRIPTOR file-descriptor\n");
462 fprintf(stderr
, " -p, --print-capabilities=PRINT_CAPABILITIES denotes print-capabilities\n");
463 fprintf(stderr
, " -h print help and quit\n");