qemu/contrib/vhost-user-scsi/vhost-user-scsi.c
<<
>>
Prefs
   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#define inline __attribute__((gnu_inline))  /* required for libiscsi v1.9.0 */
  16#include <iscsi/scsi-lowlevel.h>
  17#undef inline
  18#include "libvhost-user-glib.h"
  19#include "standard-headers/linux/virtio_scsi.h"
  20
  21
  22#define VUS_ISCSI_INITIATOR "iqn.2016-11.com.nutanix:vhost-user-scsi"
  23
  24enum {
  25    VHOST_USER_SCSI_MAX_QUEUES = 8,
  26};
  27
  28typedef struct VusIscsiLun {
  29    struct iscsi_context *iscsi_ctx;
  30    int iscsi_lun;
  31} VusIscsiLun;
  32
  33typedef struct VusDev {
  34    VugDev parent;
  35
  36    VusIscsiLun lun;
  37    GMainLoop *loop;
  38} VusDev;
  39
  40/** libiscsi integration **/
  41
  42typedef struct virtio_scsi_cmd_req VirtIOSCSICmdReq;
  43typedef struct virtio_scsi_cmd_resp VirtIOSCSICmdResp;
  44
  45static int vus_iscsi_add_lun(VusIscsiLun *lun, char *iscsi_uri)
  46{
  47    struct iscsi_url *iscsi_url;
  48    struct iscsi_context *iscsi_ctx;
  49    int ret = 0;
  50
  51    assert(lun);
  52    assert(iscsi_uri);
  53    assert(!lun->iscsi_ctx);
  54
  55    iscsi_ctx = iscsi_create_context(VUS_ISCSI_INITIATOR);
  56    if (!iscsi_ctx) {
  57        g_warning("Unable to create iSCSI context");
  58        return -1;
  59    }
  60
  61    iscsi_url = iscsi_parse_full_url(iscsi_ctx, iscsi_uri);
  62    if (!iscsi_url) {
  63        g_warning("Unable to parse iSCSI URL: %s", iscsi_get_error(iscsi_ctx));
  64        goto fail;
  65    }
  66
  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));
  72        goto fail;
  73    }
  74
  75    lun->iscsi_ctx = iscsi_ctx;
  76    lun->iscsi_lun = iscsi_url->lun;
  77
  78    g_debug("Context %p created for lun 0: %s", iscsi_ctx, iscsi_uri);
  79
  80out:
  81    if (iscsi_url) {
  82        iscsi_destroy_url(iscsi_url);
  83    }
  84    return ret;
  85
  86fail:
  87    (void)iscsi_destroy_context(iscsi_ctx);
  88    ret = -1;
  89    goto out;
  90}
  91
  92static struct scsi_task *scsi_task_new(int cdb_len, uint8_t *cdb, int dir,
  93                                       int xfer_len)
  94{
  95    struct scsi_task *task;
  96
  97    assert(cdb_len > 0);
  98    assert(cdb);
  99
 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;
 105
 106    return task;
 107}
 108
 109static int get_cdb_len(uint8_t *cdb)
 110{
 111    assert(cdb);
 112
 113    switch (cdb[0] >> 5) {
 114    case 0: return 6;
 115    case 1: /* fall through */
 116    case 2: return 10;
 117    case 4: return 16;
 118    case 5: return 12;
 119    }
 120    g_warning("Unable to determine cdb len (0x%02hhX)", (uint8_t)(cdb[0] >> 5));
 121    return -1;
 122}
 123
 124static 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)
 129{
 130    struct scsi_task *task;
 131    uint32_t dir;
 132    uint32_t len;
 133    int cdb_len;
 134    int i;
 135
 136    assert(ctx);
 137    assert(req);
 138    assert(rsp);
 139
 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));
 146        rsp->sense_len = 18;
 147        rsp->sense[0] = 0x70;
 148        rsp->sense[2] = SCSI_SENSE_ILLEGAL_REQUEST;
 149        rsp->sense[7] = 10;
 150        rsp->sense[12] = 0x24;
 151
 152        return 0;
 153    }
 154
 155    cdb_len = get_cdb_len(req->cdb);
 156    if (cdb_len == -1) {
 157        return -1;
 158    }
 159
 160    len = 0;
 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;
 167        }
 168    } else {
 169        dir = SCSI_XFER_READ;
 170        for (i = 0; i < in_len; i++) {
 171            len += in[i].iov_len;
 172        }
 173    }
 174
 175    task = scsi_task_new(cdb_len, req->cdb, dir, len);
 176
 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;
 183    }
 184
 185    g_debug("Sending iscsi cmd (cdb_len=%d, dir=%d, task=%p)",
 186         cdb_len, dir, task);
 187    if (!iscsi_scsi_command_sync(ctx, 0, task, NULL)) {
 188        g_warning("Error serving SCSI command");
 189        g_free(task);
 190        return -1;
 191    }
 192
 193    memset(rsp, 0, sizeof(*rsp));
 194
 195    rsp->status = task->status;
 196    rsp->resid  = task->residual;
 197
 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);
 202    }
 203
 204    g_free(task);
 205
 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);
 208
 209    return 0;
 210}
 211
 212/** libvhost-user callbacks **/
 213
 214static void vus_panic_cb(VuDev *vu_dev, const char *buf)
 215{
 216    VugDev *gdev;
 217    VusDev *vdev_scsi;
 218
 219    assert(vu_dev);
 220
 221    gdev = container_of(vu_dev, VugDev, parent);
 222    vdev_scsi = container_of(gdev, VusDev, parent);
 223    if (buf) {
 224        g_warning("vu_panic: %s", buf);
 225    }
 226
 227    g_main_loop_quit(vdev_scsi->loop);
 228}
 229
 230static void vus_proc_req(VuDev *vu_dev, int idx)
 231{
 232    VugDev *gdev;
 233    VusDev *vdev_scsi;
 234    VuVirtq *vq;
 235    VuVirtqElement *elem = NULL;
 236
 237    assert(vu_dev);
 238
 239    gdev = container_of(vu_dev, VugDev, parent);
 240    vdev_scsi = container_of(gdev, VusDev, parent);
 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        VirtIOSCSICmdReq *req;
 253        VirtIOSCSICmdResp *rsp;
 254
 255        elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement));
 256        if (!elem) {
 257            g_debug("No more elements pending on vq[%d]@%p", idx, vq);
 258            break;
 259        }
 260        g_debug("Popped elem@%p", elem);
 261
 262        assert(!(elem->out_num > 1 && elem->in_num > 1));
 263        assert(elem->out_num > 0 && elem->in_num > 0);
 264
 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);
 268            break;
 269        }
 270        req = (VirtIOSCSICmdReq *)elem->out_sg[0].iov_base;
 271
 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);
 275            break;
 276        }
 277        rsp = (VirtIOSCSICmdResp *)elem->in_sg[0].iov_base;
 278
 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);
 283            break;
 284        }
 285
 286        vu_queue_push(vu_dev, vq, elem, 0);
 287        vu_queue_notify(vu_dev, vq);
 288
 289        free(elem);
 290    }
 291    free(elem);
 292}
 293
 294static void vus_queue_set_started(VuDev *vu_dev, int idx, bool started)
 295{
 296    VuVirtq *vq;
 297
 298    assert(vu_dev);
 299
 300    vq = vu_get_queue(vu_dev, idx);
 301
 302    if (idx == 0 || idx == 1) {
 303        g_debug("queue %d unimplemented", idx);
 304    } else {
 305        vu_set_queue_handler(vu_dev, vq, started ? vus_proc_req : NULL);
 306    }
 307}
 308
 309static const VuDevIface vus_iface = {
 310    .queue_set_started = vus_queue_set_started,
 311};
 312
 313/** misc helpers **/
 314
 315static int unix_sock_new(char *unix_fn)
 316{
 317    int sock;
 318    struct sockaddr_un un;
 319    size_t len;
 320
 321    assert(unix_fn);
 322
 323    sock = socket(AF_UNIX, SOCK_STREAM, 0);
 324    if (sock < 0) {
 325        perror("socket");
 326        return -1;
 327    }
 328
 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);
 332
 333    (void)unlink(unix_fn);
 334    if (bind(sock, (struct sockaddr *)&un, len) < 0) {
 335        perror("bind");
 336        goto fail;
 337    }
 338
 339    if (listen(sock, 1) < 0) {
 340        perror("listen");
 341        goto fail;
 342    }
 343
 344    return sock;
 345
 346fail:
 347    (void)close(sock);
 348
 349    return -1;
 350}
 351
 352/** vhost-user-scsi **/
 353
 354static int opt_fdnum = -1;
 355static char *opt_socket_path;
 356static gboolean opt_print_caps;
 357static char *iscsi_uri;
 358
 359static 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" },
 368    { NULL, }
 369};
 370
 371int main(int argc, char **argv)
 372{
 373    VusDev *vdev_scsi = NULL;
 374    int lsock = -1, csock = -1, err = EXIT_SUCCESS;
 375
 376    GError *error = NULL;
 377    GOptionContext *context;
 378
 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);
 383        exit(EXIT_FAILURE);
 384    }
 385
 386    if (opt_print_caps) {
 387        g_print("{\n");
 388        g_print("  \"type\": \"scsi\"\n");
 389        g_print("}\n");
 390        goto out;
 391    }
 392
 393    if (!iscsi_uri) {
 394        goto help;
 395    }
 396
 397    if (opt_socket_path) {
 398        lsock = unix_sock_new(opt_socket_path);
 399        if (lsock < 0) {
 400            exit(EXIT_FAILURE);
 401        }
 402    } else if (opt_fdnum < 0) {
 403        g_print("%s\n", g_option_context_get_help(context, true, NULL));
 404        exit(EXIT_FAILURE);
 405    } else {
 406        lsock = opt_fdnum;
 407    }
 408
 409    csock = accept(lsock, NULL, NULL);
 410    if (csock < 0) {
 411        perror("accept");
 412        goto err;
 413    }
 414
 415    vdev_scsi = g_new0(VusDev, 1);
 416    vdev_scsi->loop = g_main_loop_new(NULL, FALSE);
 417
 418    if (vus_iscsi_add_lun(&vdev_scsi->lun, iscsi_uri) != 0) {
 419        goto err;
 420    }
 421
 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");
 425        goto err;
 426    }
 427
 428    g_main_loop_run(vdev_scsi->loop);
 429
 430    vug_deinit(&vdev_scsi->parent);
 431
 432out:
 433    if (vdev_scsi) {
 434        g_main_loop_unref(vdev_scsi->loop);
 435        g_free(vdev_scsi);
 436    }
 437    if (csock >= 0) {
 438        close(csock);
 439    }
 440    if (lsock >= 0) {
 441        close(lsock);
 442
 443        if (opt_socket_path) {
 444            unlink(opt_socket_path);
 445        }
 446    }
 447    g_free(opt_socket_path);
 448    g_free(iscsi_uri);
 449
 450    return err;
 451
 452err:
 453    err = EXIT_FAILURE;
 454    goto out;
 455
 456help:
 457    fprintf(stderr, "Usage: %s [ -s socket-path -i iscsi-uri -f fd -p print-capabilities ] | [ -h ]\n",
 458            argv[0]);
 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");
 464
 465    goto err;
 466}
 467