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.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 VuDev vu_dev; 30 int server_sock; 31 GMainLoop *loop; 32 GHashTable *fdmap; /* fd -> gsource */ 33 VusIscsiLun lun; 34 } VusDev; 35 36 /** glib event loop integration for libvhost-user and misc callbacks **/ 37 38 QEMU_BUILD_BUG_ON((int)G_IO_IN != (int)VU_WATCH_IN); 39 QEMU_BUILD_BUG_ON((int)G_IO_OUT != (int)VU_WATCH_OUT); 40 QEMU_BUILD_BUG_ON((int)G_IO_PRI != (int)VU_WATCH_PRI); 41 QEMU_BUILD_BUG_ON((int)G_IO_ERR != (int)VU_WATCH_ERR); 42 QEMU_BUILD_BUG_ON((int)G_IO_HUP != (int)VU_WATCH_HUP); 43 44 typedef struct vus_gsrc { 45 GSource parent; 46 VusDev *vdev_scsi; 47 GPollFD gfd; 48 } vus_gsrc_t; 49 50 static gboolean vus_gsrc_prepare(GSource *src, gint *timeout) 51 { 52 assert(timeout); 53 54 *timeout = -1; 55 return FALSE; 56 } 57 58 static gboolean vus_gsrc_check(GSource *src) 59 { 60 vus_gsrc_t *vus_src = (vus_gsrc_t *)src; 61 62 assert(vus_src); 63 64 return vus_src->gfd.revents & vus_src->gfd.events; 65 } 66 67 static gboolean vus_gsrc_dispatch(GSource *src, GSourceFunc cb, gpointer data) 68 { 69 VusDev *vdev_scsi; 70 vus_gsrc_t *vus_src = (vus_gsrc_t *)src; 71 72 assert(vus_src); 73 74 vdev_scsi = vus_src->vdev_scsi; 75 76 assert(vdev_scsi); 77 78 ((vu_watch_cb)cb)(&vdev_scsi->vu_dev, vus_src->gfd.revents, data); 79 80 return G_SOURCE_CONTINUE; 81 } 82 83 static GSourceFuncs vus_gsrc_funcs = { 84 vus_gsrc_prepare, 85 vus_gsrc_check, 86 vus_gsrc_dispatch, 87 NULL 88 }; 89 90 static GSource *vus_gsrc_new(VusDev *vdev_scsi, int fd, GIOCondition cond, 91 vu_watch_cb vu_cb, GSourceFunc gsrc_cb, gpointer data) 92 { 93 GSource *vus_gsrc; 94 vus_gsrc_t *vus_src; 95 guint id; 96 97 assert(vdev_scsi); 98 assert(fd >= 0); 99 assert(vu_cb || gsrc_cb); 100 assert(!(vu_cb && gsrc_cb)); 101 102 vus_gsrc = g_source_new(&vus_gsrc_funcs, sizeof(vus_gsrc_t)); 103 g_source_set_callback(vus_gsrc, (GSourceFunc) vu_cb, data, NULL); 104 vus_src = (vus_gsrc_t *)vus_gsrc; 105 vus_src->vdev_scsi = vdev_scsi; 106 vus_src->gfd.fd = fd; 107 vus_src->gfd.events = cond; 108 109 g_source_add_poll(vus_gsrc, &vus_src->gfd); 110 g_source_set_callback(vus_gsrc, gsrc_cb, data, NULL); 111 id = g_source_attach(vus_gsrc, NULL); 112 assert(id); 113 g_source_unref(vus_gsrc); 114 115 return vus_gsrc; 116 } 117 118 /** libiscsi integration **/ 119 120 typedef struct virtio_scsi_cmd_req VirtIOSCSICmdReq; 121 typedef struct virtio_scsi_cmd_resp VirtIOSCSICmdResp; 122 123 static int vus_iscsi_add_lun(VusIscsiLun *lun, char *iscsi_uri) 124 { 125 struct iscsi_url *iscsi_url; 126 struct iscsi_context *iscsi_ctx; 127 int ret = 0; 128 129 assert(lun); 130 assert(iscsi_uri); 131 assert(!lun->iscsi_ctx); 132 133 iscsi_ctx = iscsi_create_context(VUS_ISCSI_INITIATOR); 134 if (!iscsi_ctx) { 135 g_warning("Unable to create iSCSI context"); 136 return -1; 137 } 138 139 iscsi_url = iscsi_parse_full_url(iscsi_ctx, iscsi_uri); 140 if (!iscsi_url) { 141 g_warning("Unable to parse iSCSI URL: %s", iscsi_get_error(iscsi_ctx)); 142 goto fail; 143 } 144 145 iscsi_set_session_type(iscsi_ctx, ISCSI_SESSION_NORMAL); 146 iscsi_set_header_digest(iscsi_ctx, ISCSI_HEADER_DIGEST_NONE_CRC32C); 147 if (iscsi_full_connect_sync(iscsi_ctx, iscsi_url->portal, iscsi_url->lun)) { 148 g_warning("Unable to login to iSCSI portal: %s", 149 iscsi_get_error(iscsi_ctx)); 150 goto fail; 151 } 152 153 lun->iscsi_ctx = iscsi_ctx; 154 lun->iscsi_lun = iscsi_url->lun; 155 156 g_debug("Context %p created for lun 0: %s", iscsi_ctx, iscsi_uri); 157 158 out: 159 if (iscsi_url) { 160 iscsi_destroy_url(iscsi_url); 161 } 162 return ret; 163 164 fail: 165 (void)iscsi_destroy_context(iscsi_ctx); 166 ret = -1; 167 goto out; 168 } 169 170 static struct scsi_task *scsi_task_new(int cdb_len, uint8_t *cdb, int dir, 171 int xfer_len) 172 { 173 struct scsi_task *task; 174 175 assert(cdb_len > 0); 176 assert(cdb); 177 178 task = g_new0(struct scsi_task, 1); 179 memcpy(task->cdb, cdb, cdb_len); 180 task->cdb_size = cdb_len; 181 task->xfer_dir = dir; 182 task->expxferlen = xfer_len; 183 184 return task; 185 } 186 187 static int get_cdb_len(uint8_t *cdb) 188 { 189 assert(cdb); 190 191 switch (cdb[0] >> 5) { 192 case 0: return 6; 193 case 1: /* fall through */ 194 case 2: return 10; 195 case 4: return 16; 196 case 5: return 12; 197 } 198 g_warning("Unable to determine cdb len (0x%02hhX)", cdb[0] >> 5); 199 return -1; 200 } 201 202 static int handle_cmd_sync(struct iscsi_context *ctx, 203 VirtIOSCSICmdReq *req, 204 struct iovec *out, unsigned int out_len, 205 VirtIOSCSICmdResp *rsp, 206 struct iovec *in, unsigned int in_len) 207 { 208 struct scsi_task *task; 209 uint32_t dir; 210 uint32_t len; 211 int cdb_len; 212 int i; 213 214 assert(ctx); 215 assert(req); 216 assert(rsp); 217 218 if (!(!req->lun[1] && req->lun[2] == 0x40 && !req->lun[3])) { 219 /* Ignore anything different than target=0, lun=0 */ 220 g_debug("Ignoring unconnected lun (0x%hhX, 0x%hhX)", 221 req->lun[1], req->lun[3]); 222 rsp->status = SCSI_STATUS_CHECK_CONDITION; 223 memset(rsp->sense, 0, sizeof(rsp->sense)); 224 rsp->sense_len = 18; 225 rsp->sense[0] = 0x70; 226 rsp->sense[2] = SCSI_SENSE_ILLEGAL_REQUEST; 227 rsp->sense[7] = 10; 228 rsp->sense[12] = 0x24; 229 230 return 0; 231 } 232 233 cdb_len = get_cdb_len(req->cdb); 234 if (cdb_len == -1) { 235 return -1; 236 } 237 238 len = 0; 239 if (!out_len && !in_len) { 240 dir = SCSI_XFER_NONE; 241 } else if (out_len) { 242 dir = SCSI_XFER_WRITE; 243 for (i = 0; i < out_len; i++) { 244 len += out[i].iov_len; 245 } 246 } else { 247 dir = SCSI_XFER_READ; 248 for (i = 0; i < in_len; i++) { 249 len += in[i].iov_len; 250 } 251 } 252 253 task = scsi_task_new(cdb_len, req->cdb, dir, len); 254 255 if (dir == SCSI_XFER_WRITE) { 256 task->iovector_out.iov = (struct scsi_iovec *)out; 257 task->iovector_out.niov = out_len; 258 } else if (dir == SCSI_XFER_READ) { 259 task->iovector_in.iov = (struct scsi_iovec *)in; 260 task->iovector_in.niov = in_len; 261 } 262 263 g_debug("Sending iscsi cmd (cdb_len=%d, dir=%d, task=%p)", 264 cdb_len, dir, task); 265 if (!iscsi_scsi_command_sync(ctx, 0, task, NULL)) { 266 g_warning("Error serving SCSI command"); 267 g_free(task); 268 return -1; 269 } 270 271 memset(rsp, 0, sizeof(*rsp)); 272 273 rsp->status = task->status; 274 rsp->resid = task->residual; 275 276 if (task->status == SCSI_STATUS_CHECK_CONDITION) { 277 rsp->response = VIRTIO_SCSI_S_FAILURE; 278 rsp->sense_len = task->datain.size - 2; 279 memcpy(rsp->sense, &task->datain.data[2], rsp->sense_len); 280 } 281 282 g_free(task); 283 284 g_debug("Filled in rsp: status=%hhX, resid=%u, response=%hhX, sense_len=%u", 285 rsp->status, rsp->resid, rsp->response, rsp->sense_len); 286 287 return 0; 288 } 289 290 /** libvhost-user callbacks **/ 291 292 static void vus_panic_cb(VuDev *vu_dev, const char *buf) 293 { 294 VusDev *vdev_scsi; 295 296 assert(vu_dev); 297 298 vdev_scsi = container_of(vu_dev, VusDev, vu_dev); 299 if (buf) { 300 g_warning("vu_panic: %s", buf); 301 } 302 303 g_main_loop_quit(vdev_scsi->loop); 304 } 305 306 static void vus_add_watch_cb(VuDev *vu_dev, int fd, int vu_evt, vu_watch_cb cb, 307 void *pvt) 308 { 309 GSource *src; 310 VusDev *vdev_scsi; 311 312 assert(vu_dev); 313 assert(fd >= 0); 314 assert(cb); 315 316 vdev_scsi = container_of(vu_dev, VusDev, vu_dev); 317 src = vus_gsrc_new(vdev_scsi, fd, vu_evt, cb, NULL, pvt); 318 g_hash_table_replace(vdev_scsi->fdmap, GINT_TO_POINTER(fd), src); 319 } 320 321 static void vus_del_watch_cb(VuDev *vu_dev, int fd) 322 { 323 VusDev *vdev_scsi; 324 325 assert(vu_dev); 326 assert(fd >= 0); 327 328 vdev_scsi = container_of(vu_dev, VusDev, vu_dev); 329 g_hash_table_remove(vdev_scsi->fdmap, GINT_TO_POINTER(fd)); 330 } 331 332 static void vus_proc_req(VuDev *vu_dev, int idx) 333 { 334 VusDev *vdev_scsi; 335 VuVirtq *vq; 336 337 assert(vu_dev); 338 339 vdev_scsi = container_of(vu_dev, VusDev, vu_dev); 340 if (idx < 0 || idx >= VHOST_MAX_NR_VIRTQUEUE) { 341 g_warning("VQ Index out of range: %d", idx); 342 vus_panic_cb(vu_dev, NULL); 343 return; 344 } 345 346 vq = vu_get_queue(vu_dev, idx); 347 if (!vq) { 348 g_warning("Error fetching VQ (dev=%p, idx=%d)", vu_dev, idx); 349 vus_panic_cb(vu_dev, NULL); 350 return; 351 } 352 353 g_debug("Got kicked on vq[%d]@%p", idx, vq); 354 355 while (1) { 356 VuVirtqElement *elem; 357 VirtIOSCSICmdReq *req; 358 VirtIOSCSICmdResp *rsp; 359 360 elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement)); 361 if (!elem) { 362 g_debug("No more elements pending on vq[%d]@%p", idx, vq); 363 break; 364 } 365 g_debug("Popped elem@%p", elem); 366 367 assert(!(elem->out_num > 1 && elem->in_num > 1)); 368 assert(elem->out_num > 0 && elem->in_num > 0); 369 370 if (elem->out_sg[0].iov_len < sizeof(VirtIOSCSICmdReq)) { 371 g_warning("Invalid virtio-scsi req header"); 372 vus_panic_cb(vu_dev, NULL); 373 break; 374 } 375 req = (VirtIOSCSICmdReq *)elem->out_sg[0].iov_base; 376 377 if (elem->in_sg[0].iov_len < sizeof(VirtIOSCSICmdResp)) { 378 g_warning("Invalid virtio-scsi rsp header"); 379 vus_panic_cb(vu_dev, NULL); 380 break; 381 } 382 rsp = (VirtIOSCSICmdResp *)elem->in_sg[0].iov_base; 383 384 if (handle_cmd_sync(vdev_scsi->lun.iscsi_ctx, 385 req, &elem->out_sg[1], elem->out_num - 1, 386 rsp, &elem->in_sg[1], elem->in_num - 1) != 0) { 387 vus_panic_cb(vu_dev, NULL); 388 break; 389 } 390 391 vu_queue_push(vu_dev, vq, elem, 0); 392 vu_queue_notify(vu_dev, vq); 393 394 free(elem); 395 } 396 } 397 398 static void vus_queue_set_started(VuDev *vu_dev, int idx, bool started) 399 { 400 VuVirtq *vq; 401 402 assert(vu_dev); 403 404 if (idx < 0 || idx >= VHOST_MAX_NR_VIRTQUEUE) { 405 g_warning("VQ Index out of range: %d", idx); 406 vus_panic_cb(vu_dev, NULL); 407 return; 408 } 409 410 vq = vu_get_queue(vu_dev, idx); 411 412 if (idx == 0 || idx == 1) { 413 g_debug("queue %d unimplemented", idx); 414 } else { 415 vu_set_queue_handler(vu_dev, vq, started ? vus_proc_req : NULL); 416 } 417 } 418 419 static const VuDevIface vus_iface = { 420 .queue_set_started = vus_queue_set_started, 421 }; 422 423 static gboolean vus_vhost_cb(gpointer data) 424 { 425 VuDev *vu_dev = (VuDev *)data; 426 427 assert(vu_dev); 428 429 if (!vu_dispatch(vu_dev) != 0) { 430 g_warning("Error processing vhost message"); 431 vus_panic_cb(vu_dev, NULL); 432 return G_SOURCE_REMOVE; 433 } 434 435 return G_SOURCE_CONTINUE; 436 } 437 438 /** misc helpers **/ 439 440 static int unix_sock_new(char *unix_fn) 441 { 442 int sock; 443 struct sockaddr_un un; 444 size_t len; 445 446 assert(unix_fn); 447 448 sock = socket(AF_UNIX, SOCK_STREAM, 0); 449 if (sock <= 0) { 450 perror("socket"); 451 return -1; 452 } 453 454 un.sun_family = AF_UNIX; 455 (void)snprintf(un.sun_path, sizeof(un.sun_path), "%s", unix_fn); 456 len = sizeof(un.sun_family) + strlen(un.sun_path); 457 458 (void)unlink(unix_fn); 459 if (bind(sock, (struct sockaddr *)&un, len) < 0) { 460 perror("bind"); 461 goto fail; 462 } 463 464 if (listen(sock, 1) < 0) { 465 perror("listen"); 466 goto fail; 467 } 468 469 return sock; 470 471 fail: 472 (void)close(sock); 473 474 return -1; 475 } 476 477 /** vhost-user-scsi **/ 478 479 static void vdev_scsi_free(VusDev *vdev_scsi) 480 { 481 if (vdev_scsi->server_sock >= 0) { 482 close(vdev_scsi->server_sock); 483 } 484 g_main_loop_unref(vdev_scsi->loop); 485 g_hash_table_unref(vdev_scsi->fdmap); 486 g_free(vdev_scsi); 487 } 488 489 static VusDev *vdev_scsi_new(int server_sock) 490 { 491 VusDev *vdev_scsi; 492 493 vdev_scsi = g_new0(VusDev, 1); 494 vdev_scsi->server_sock = server_sock; 495 vdev_scsi->loop = g_main_loop_new(NULL, FALSE); 496 vdev_scsi->fdmap = 497 g_hash_table_new_full(NULL, NULL, NULL, 498 (GDestroyNotify) g_source_destroy); 499 500 return vdev_scsi; 501 } 502 503 static int vdev_scsi_run(VusDev *vdev_scsi) 504 { 505 int cli_sock; 506 int ret = 0; 507 508 assert(vdev_scsi); 509 assert(vdev_scsi->server_sock >= 0); 510 assert(vdev_scsi->loop); 511 512 cli_sock = accept(vdev_scsi->server_sock, NULL, NULL); 513 if (cli_sock < 0) { 514 perror("accept"); 515 return -1; 516 } 517 518 vu_init(&vdev_scsi->vu_dev, 519 cli_sock, 520 vus_panic_cb, 521 vus_add_watch_cb, 522 vus_del_watch_cb, 523 &vus_iface); 524 525 vus_gsrc_new(vdev_scsi, cli_sock, G_IO_IN, NULL, vus_vhost_cb, 526 &vdev_scsi->vu_dev); 527 528 g_main_loop_run(vdev_scsi->loop); 529 530 vu_deinit(&vdev_scsi->vu_dev); 531 532 return ret; 533 } 534 535 int main(int argc, char **argv) 536 { 537 VusDev *vdev_scsi = NULL; 538 char *unix_fn = NULL; 539 char *iscsi_uri = NULL; 540 int sock, opt, err = EXIT_SUCCESS; 541 542 while ((opt = getopt(argc, argv, "u:i:")) != -1) { 543 switch (opt) { 544 case 'h': 545 goto help; 546 case 'u': 547 unix_fn = g_strdup(optarg); 548 break; 549 case 'i': 550 iscsi_uri = g_strdup(optarg); 551 break; 552 default: 553 goto help; 554 } 555 } 556 if (!unix_fn || !iscsi_uri) { 557 goto help; 558 } 559 560 sock = unix_sock_new(unix_fn); 561 if (sock < 0) { 562 goto err; 563 } 564 vdev_scsi = vdev_scsi_new(sock); 565 566 if (vus_iscsi_add_lun(&vdev_scsi->lun, iscsi_uri) != 0) { 567 goto err; 568 } 569 570 if (vdev_scsi_run(vdev_scsi) != 0) { 571 goto err; 572 } 573 574 out: 575 if (vdev_scsi) { 576 vdev_scsi_free(vdev_scsi); 577 unlink(unix_fn); 578 } 579 g_free(unix_fn); 580 g_free(iscsi_uri); 581 582 return err; 583 584 err: 585 err = EXIT_FAILURE; 586 goto out; 587 588 help: 589 fprintf(stderr, "Usage: %s [ -u unix_sock_path -i iscsi_uri ] | [ -h ]\n", 590 argv[0]); 591 fprintf(stderr, " -u path to unix socket\n"); 592 fprintf(stderr, " -i iscsi uri for lun 0\n"); 593 fprintf(stderr, " -h print help and quit\n"); 594 595 goto err; 596 } 597