storage_backend_scsi.c 20.1 KB
Newer Older
1 2 3
/*
 * storage_backend_scsi.c: storage backend for SCSI handling
 *
C
Cédric Bosdonnat 已提交
4
 * Copyright (C) 2007-2008, 2013-2014 Red Hat, Inc.
5 6 7 8 9 10 11 12 13 14 15 16 17
 * Copyright (C) 2007-2008 Daniel P. Berrange
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
O
Osier Yang 已提交
19
 * <http://www.gnu.org/licenses/>.
20 21 22 23 24 25 26 27 28 29 30
 *
 * Author: Daniel P. Berrange <berrange redhat com>
 */

#include <config.h>

#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <fcntl.h>

31
#include "virerror.h"
32
#include "storage_backend_scsi.h"
33
#include "viralloc.h"
34
#include "virlog.h"
E
Eric Blake 已提交
35
#include "virfile.h"
36
#include "vircommand.h"
37
#include "virstring.h"
38 39 40

#define VIR_FROM_THIS VIR_FROM_STORAGE

41 42
VIR_LOG_INIT("storage.storage_backend_scsi");

43 44 45 46 47
/* Function to check if the type file in the given sysfs_path is a
 * Direct-Access device (i.e. type 0).  Return -1 on failure, type of
 * the device otherwise.
 */
static int
48
getDeviceType(uint32_t host,
49 50 51 52 53 54 55 56 57 58 59 60
              uint32_t bus,
              uint32_t target,
              uint32_t lun,
              int *type)
{
    char *type_path = NULL;
    char typestr[3];
    char *gottype, *p;
    FILE *typefile;
    int retval = 0;

    if (virAsprintf(&type_path, "/sys/bus/scsi/devices/%u:%u:%u:%u/type",
61
                    host, bus, target, lun) < 0)
62 63 64 65
        goto out;

    typefile = fopen(type_path, "r");
    if (typefile == NULL) {
66
        virReportSystemError(errno,
67 68 69 70 71 72 73 74
                             _("Could not find typefile '%s'"),
                             type_path);
        /* there was no type file; that doesn't seem right */
        retval = -1;
        goto out;
    }

    gottype = fgets(typestr, 3, typefile);
75
    VIR_FORCE_FCLOSE(typefile);
76 77

    if (gottype == NULL) {
78
        virReportSystemError(errno,
79 80 81 82 83 84 85 86 87 88 89
                             _("Could not read typefile '%s'"),
                             type_path);
        /* we couldn't read the type file; have to give up */
        retval = -1;
        goto out;
    }

    /* we don't actually care about p, but if you pass NULL and the last
     * character is not \0, virStrToLong_i complains
     */
    if (virStrToLong_i(typestr, &p, 10, type) < 0) {
90 91 92
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Device type '%s' is not an integer"),
                       typestr);
93 94 95 96 97
        /* Hm, type wasn't an integer; seems strange */
        retval = -1;
        goto out;
    }

98
    VIR_DEBUG("Device type is %d", *type);
99

100
 out:
101 102 103 104
    VIR_FREE(type_path);
    return retval;
}

105
static int
106
virStorageBackendSCSIUpdateVolTargetInfo(virStorageVolTargetPtr target,
107 108 109
                                         unsigned long long *allocation,
                                         unsigned long long *capacity)
{
110 111
    int fdret, fd = -1;
    int ret = -1;
E
Eric Blake 已提交
112
    struct stat sb;
113

E
Eric Blake 已提交
114 115
    if ((fdret = virStorageBackendVolOpenCheckMode(target->path, &sb,
                                                   VIR_STORAGE_VOL_OPEN_DEFAULT)) < 0)
116 117
        goto cleanup;
    fd = fdret;
118

119
    if (virStorageBackendUpdateVolTargetInfoFD(target,
120
                                               fd,
E
Eric Blake 已提交
121
                                               &sb,
122 123
                                               allocation,
                                               capacity) < 0)
124
        goto cleanup;
125

126
    if (virStorageBackendDetectBlockVolFormatFD(target, fd) < 0)
127
        goto cleanup;
128

129 130
    ret = 0;

131
 cleanup:
132
    VIR_FORCE_CLOSE(fd);
133 134

    return ret;
135
}
136

137 138 139 140 141

static char *
virStorageBackendSCSISerial(const char *dev)
{
    char *serial = NULL;
142
#ifdef WITH_UDEV
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    virCommandPtr cmd = virCommandNewArgList(
        "/lib/udev/scsi_id",
        "--replace-whitespace",
        "--whitelisted",
        "--device", dev,
        NULL
        );

    /* Run the program and capture its output */
    virCommandSetOutputBuffer(cmd, &serial);
    if (virCommandRun(cmd, NULL) < 0)
        goto cleanup;
#endif

    if (serial && STRNEQ(serial, "")) {
        char *nl = strchr(serial, '\n');
        if (nl)
            *nl = '\0';
    } else {
        VIR_FREE(serial);
163
        ignore_value(VIR_STRDUP(serial, dev));
164 165
    }

166
#ifdef WITH_UDEV
167
 cleanup:
168 169 170 171 172 173 174
    virCommandFree(cmd);
#endif

    return serial;
}


175
static int
176
virStorageBackendSCSINewLun(virStoragePoolObjPtr pool,
177
                            uint32_t host ATTRIBUTE_UNUSED,
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
                            uint32_t bus,
                            uint32_t target,
                            uint32_t lun,
                            const char *dev)
{
    virStorageVolDefPtr vol;
    char *devpath = NULL;
    int retval = 0;

    if (VIR_ALLOC(vol) < 0) {
        retval = -1;
        goto out;
    }

    vol->type = VIR_STORAGE_VOL_BLOCK;

194 195 196 197 198 199
    /* 'host' is dynamically allocated by the kernel, first come,
     * first served, per HBA. As such it isn't suitable for use
     * in the volume name. We only need uniqueness per-pool, so
     * just leave 'host' out
     */
    if (virAsprintf(&(vol->name), "unit:%u:%u:%u", bus, target, lun) < 0) {
200 201 202 203 204 205 206 207 208
        retval = -1;
        goto free_vol;
    }

    if (virAsprintf(&devpath, "/dev/%s", dev) < 0) {
        retval = -1;
        goto free_vol;
    }

209
    VIR_DEBUG("Trying to create volume for '%s'", devpath);
210 211 212 213 214 215 216

    /* Now figure out the stable path
     *
     * XXX this method is O(N) because it scans the pool target
     * dir every time its run. Should figure out a more efficient
     * way of doing this...
     */
217
    if ((vol->target.path = virStorageBackendStablePath(pool,
218 219
                                                        devpath,
                                                        true)) == NULL) {
220 221 222 223
        retval = -1;
        goto free_vol;
    }

224
    if (STREQ(devpath, vol->target.path) &&
225 226 227
        !(STREQ(pool->def->target.path, "/dev") ||
          STREQ(pool->def->target.path, "/dev/"))) {

228
        VIR_DEBUG("No stable path found for '%s' in '%s'",
229 230 231 232 233 234
                  devpath, pool->def->target.path);

        retval = -1;
        goto free_vol;
    }

235
    if (virStorageBackendSCSIUpdateVolTargetInfo(&vol->target,
236 237
                                                 &vol->allocation,
                                                 &vol->capacity) < 0) {
238

239 240 241
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to update volume for '%s'"),
                       devpath);
242 243 244 245
        retval = -1;
        goto free_vol;
    }

246
    if (!(vol->key = virStorageBackendSCSISerial(vol->target.path))) {
247 248 249 250 251 252 253
        retval = -1;
        goto free_vol;
    }

    pool->def->capacity += vol->capacity;
    pool->def->allocation += vol->allocation;

254
    if (VIR_APPEND_ELEMENT(pool->volumes.objs, pool->volumes.count, vol) < 0) {
255 256 257 258 259 260
        retval = -1;
        goto free_vol;
    }

    goto out;

261
 free_vol:
262
    virStorageVolDefFree(vol);
263
 out:
264 265 266 267 268 269
    VIR_FREE(devpath);
    return retval;
}


static int
270
getNewStyleBlockDevice(const char *lun_path,
271 272 273 274 275 276 277 278
                       const char *block_name ATTRIBUTE_UNUSED,
                       char **block_device)
{
    char *block_path = NULL;
    DIR *block_dir = NULL;
    struct dirent *block_dirent = NULL;
    int retval = 0;

279
    if (virAsprintf(&block_path, "%s/block", lun_path) < 0)
280 281
        goto out;

282
    VIR_DEBUG("Looking for block device in '%s'", block_path);
283 284 285

    block_dir = opendir(block_path);
    if (block_dir == NULL) {
286
        virReportSystemError(errno,
287 288 289 290 291 292 293 294 295 296 297 298
                             _("Failed to opendir sysfs path '%s'"),
                             block_path);
        retval = -1;
        goto out;
    }

    while ((block_dirent = readdir(block_dir))) {

        if (STREQLEN(block_dirent->d_name, ".", 1)) {
            continue;
        }

299
        if (VIR_STRDUP(*block_device, block_dirent->d_name) < 0) {
300 301 302 303 304
            closedir(block_dir);
            retval = -1;
            goto out;
        }

305
        VIR_DEBUG("Block device is '%s'", *block_device);
306 307 308 309 310 311

        break;
    }

    closedir(block_dir);

312
 out:
313 314 315 316 317 318
    VIR_FREE(block_path);
    return retval;
}


static int
319
getOldStyleBlockDevice(const char *lun_path ATTRIBUTE_UNUSED,
320 321 322 323 324 325 326 327 328 329
                       const char *block_name,
                       char **block_device)
{
    char *blockp = NULL;
    int retval = 0;

    /* old-style; just parse out the sd */
    blockp = strrchr(block_name, ':');
    if (blockp == NULL) {
        /* Hm, wasn't what we were expecting; have to give up */
330 331 332
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to parse block name %s"),
                       block_name);
333 334 335
        retval = -1;
    } else {
        blockp++;
336
        if (VIR_STRDUP(*block_device, blockp) < 0) {
337 338 339 340
            retval = -1;
            goto out;
        }

341
        VIR_DEBUG("Block device is '%s'", *block_device);
342 343
    }

344
 out:
345 346 347 348 349
    return retval;
}


static int
350
getBlockDevice(uint32_t host,
351 352 353 354 355 356 357 358 359 360 361
               uint32_t bus,
               uint32_t target,
               uint32_t lun,
               char **block_device)
{
    char *lun_path = NULL;
    DIR *lun_dir = NULL;
    struct dirent *lun_dirent = NULL;
    int retval = 0;

    if (virAsprintf(&lun_path, "/sys/bus/scsi/devices/%u:%u:%u:%u",
362
                    host, bus, target, lun) < 0)
363 364 365 366
        goto out;

    lun_dir = opendir(lun_path);
    if (lun_dir == NULL) {
367
        virReportSystemError(errno,
368 369 370 371 372 373 374 375 376
                             _("Failed to opendir sysfs path '%s'"),
                             lun_path);
        retval = -1;
        goto out;
    }

    while ((lun_dirent = readdir(lun_dir))) {
        if (STREQLEN(lun_dirent->d_name, "block", 5)) {
            if (strlen(lun_dirent->d_name) == 5) {
377
                retval = getNewStyleBlockDevice(lun_path,
378 379 380
                                                lun_dirent->d_name,
                                                block_device);
            } else {
381
                retval = getOldStyleBlockDevice(lun_path,
382 383 384 385 386 387 388 389 390
                                                lun_dirent->d_name,
                                                block_device);
            }
            break;
        }
    }

    closedir(lun_dir);

391
 out:
392 393 394 395 396 397
    VIR_FREE(lun_path);
    return retval;
}


static int
398
processLU(virStoragePoolObjPtr pool,
399 400 401 402 403 404 405 406 407 408
          uint32_t host,
          uint32_t bus,
          uint32_t target,
          uint32_t lun)
{
    char *type_path = NULL;
    int retval = 0;
    int device_type;
    char *block_device = NULL;

409
    VIR_DEBUG("Processing LU %u:%u:%u:%u",
410 411
              host, bus, target, lun);

412
    if (getDeviceType(host, bus, target, lun, &device_type) < 0) {
413 414 415
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to determine if %u:%u:%u:%u is a Direct-Access LUN"),
                       host, bus, target, lun);
416 417 418 419 420 421 422 423 424 425 426 427 428 429
        retval = -1;
        goto out;
    }

    /* We don't create volumes for devices other than disk and cdrom
     * devices, but finding a device that isn't one of those types
     * isn't an error, either. */
    if (!(device_type == VIR_STORAGE_DEVICE_TYPE_DISK ||
          device_type == VIR_STORAGE_DEVICE_TYPE_ROM))
    {
        retval = 0;
        goto out;
    }

430
    VIR_DEBUG("%u:%u:%u:%u is a Direct-Access LUN",
431 432
              host, bus, target, lun);

433
    if (getBlockDevice(host, bus, target, lun, &block_device) < 0) {
434 435 436
        goto out;
    }

437
    if (virStorageBackendSCSINewLun(pool,
438 439
                                    host, bus, target, lun,
                                    block_device) < 0) {
440
        VIR_DEBUG("Failed to create new storage volume for %u:%u:%u:%u",
441 442 443 444 445
                  host, bus, target, lun);
        retval = -1;
        goto out;
    }

446
    VIR_DEBUG("Created new storage volume for %u:%u:%u:%u successfully",
447 448 449 450
              host, bus, target, lun);

    VIR_FREE(type_path);

451
 out:
452
    VIR_FREE(block_device);
453 454 455 456 457
    return retval;
}


int
458
virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool,
459 460 461 462
                             uint32_t scanhost)
{
    int retval = 0;
    uint32_t bus, target, lun;
463
    const char *device_path = "/sys/bus/scsi/devices";
464 465 466
    DIR *devicedir = NULL;
    struct dirent *lun_dirent = NULL;
    char devicepattern[64];
467
    bool found = false;
468

469
    VIR_DEBUG("Discovering LUs on host %u", scanhost);
470

471
    virFileWaitForDevices();
472 473 474 475

    devicedir = opendir(device_path);

    if (devicedir == NULL) {
476
        virReportSystemError(errno,
477
                             _("Failed to opendir path '%s'"), device_path);
478
        return -1;
479 480 481 482 483 484 485 486 487 488
    }

    snprintf(devicepattern, sizeof(devicepattern), "%u:%%u:%%u:%%u\n", scanhost);

    while ((lun_dirent = readdir(devicedir))) {
        if (sscanf(lun_dirent->d_name, devicepattern,
                   &bus, &target, &lun) != 3) {
            continue;
        }

489
        found = true;
490
        VIR_DEBUG("Found LU '%s'", lun_dirent->d_name);
491

492
        processLU(pool, scanhost, bus, target, lun);
493 494
    }

495 496 497
    if (!found)
        VIR_DEBUG("No LU found for pool %s", pool->def->name);

498 499 500 501 502
    closedir(devicedir);

    return retval;
}

503
static int
504
virStorageBackendSCSITriggerRescan(uint32_t host)
505 506 507 508 509
{
    int fd = -1;
    int retval = 0;
    char *path;

510
    VIR_DEBUG("Triggering rescan of host %d", host);
511 512 513 514 515 516

    if (virAsprintf(&path, "/sys/class/scsi_host/host%u/scan", host) < 0) {
        retval = -1;
        goto out;
    }

517
    VIR_DEBUG("Scan trigger path is '%s'", path);
518 519 520 521

    fd = open(path, O_WRONLY);

    if (fd < 0) {
522
        virReportSystemError(errno,
523 524 525 526 527 528 529 530 531
                             _("Could not open '%s' to trigger host scan"),
                             path);
        retval = -1;
        goto free_path;
    }

    if (safewrite(fd,
                  LINUX_SYSFS_SCSI_HOST_SCAN_STRING,
                  sizeof(LINUX_SYSFS_SCSI_HOST_SCAN_STRING)) < 0) {
532
        VIR_FORCE_CLOSE(fd);
533
        virReportSystemError(errno,
534 535 536 537 538
                             _("Write to '%s' to trigger host scan failed"),
                             path);
        retval = -1;
    }

539
    VIR_FORCE_CLOSE(fd);
540
 free_path:
541
    VIR_FREE(path);
542
 out:
543
    VIR_DEBUG("Rescan of host %d complete", host);
544 545 546
    return retval;
}

547 548 549 550 551 552 553 554 555 556 557
static int
getHostNumber(const char *adapter_name,
              unsigned int *result)
{
    char *host = (char *)adapter_name;

    /* Specifying adapter like 'host5' is still supported for
     * back-compat reason.
     */
    if (STRPREFIX(host, "scsi_host")) {
        host += strlen("scsi_host");
558 559
    } else if (STRPREFIX(host, "fc_host")) {
        host += strlen("fc_host");
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    } else if (STRPREFIX(host, "host")) {
        host += strlen("host");
    } else {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid adapter name '%s' for SCSI pool"),
                       adapter_name);
        return -1;
    }

    if (result && virStrToLong_ui(host, NULL, 10, result) == -1) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Invalid adapter name '%s' for SCSI pool"),
                       adapter_name);
        return -1;
    }

    return 0;
}

579 580 581 582 583
static char *
getAdapterName(virStoragePoolSourceAdapter adapter)
{
    char *name = NULL;

584 585 586 587
    if (adapter.type != VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST) {
        ignore_value(VIR_STRDUP(name, adapter.data.name));
        return name;
    }
588 589 590 591 592 593 594 595 596 597 598 599 600

    if (!(name = virGetFCHostNameByWWN(NULL,
                                       adapter.data.fchost.wwnn,
                                       adapter.data.fchost.wwpn))) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("Failed to find SCSI host with wwnn='%s', "
                         "wwpn='%s'"), adapter.data.fchost.wwnn,
                       adapter.data.fchost.wwpn);
    }

    return name;
}

601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
static int
createVport(virStoragePoolSourceAdapter adapter)
{
    unsigned int parent_host;
    char *name = NULL;

    if (adapter.type != VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST)
        return 0;

    /* This filters either HBA or already created vHBA */
    if ((name = virGetFCHostNameByWWN(NULL, adapter.data.fchost.wwnn,
                                      adapter.data.fchost.wwpn))) {
        VIR_FREE(name);
        return 0;
    }

617 618 619 620 621 622
    if (!adapter.data.fchost.parent &&
        !(adapter.data.fchost.parent = virFindFCHostCapableVport(NULL))) {
         virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("'parent' for vHBA not specified, and "
                         "cannot find one on this host"));
         return -1;
623 624 625 626 627
    }

    if (getHostNumber(adapter.data.fchost.parent, &parent_host) < 0)
        return -1;

628 629
    if (virManageVport(parent_host, adapter.data.fchost.wwpn,
                       adapter.data.fchost.wwnn, VPORT_CREATE) < 0)
630 631 632 633 634 635 636 637 638 639
        return -1;

    virFileWaitForDevices();
    return 0;
}

static int
deleteVport(virStoragePoolSourceAdapter adapter)
{
    unsigned int parent_host;
O
Osier Yang 已提交
640 641
    char *name = NULL;
    int ret = -1;
642 643 644 645

    if (adapter.type != VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST)
        return 0;

646 647 648 649 650 651 652 653
    /* It must be a HBA instead of a vHBA as long as "parent"
     * is NULL. "createVport" guaranteed "parent" for a vHBA
     * cannot be NULL, it's either specified in XML, or detected
     * automatically.
     */
    if (!adapter.data.fchost.parent)
        return 0;

O
Osier Yang 已提交
654 655
    if (!(name = virGetFCHostNameByWWN(NULL, adapter.data.fchost.wwnn,
                                       adapter.data.fchost.wwpn)))
656 657 658
        return -1;

    if (getHostNumber(adapter.data.fchost.parent, &parent_host) < 0)
O
Osier Yang 已提交
659
        goto cleanup;
660

661 662
    if (virManageVport(parent_host, adapter.data.fchost.wwpn,
                       adapter.data.fchost.wwnn, VPORT_DELETE) < 0)
O
Osier Yang 已提交
663
        goto cleanup;
664

O
Osier Yang 已提交
665
    ret = 0;
666
 cleanup:
O
Osier Yang 已提交
667 668
    VIR_FREE(name);
    return ret;
669 670 671
}


672 673 674 675 676
static int
virStorageBackendSCSICheckPool(virConnectPtr conn ATTRIBUTE_UNUSED,
                               virStoragePoolObjPtr pool,
                               bool *isActive)
{
677 678
    char *path = NULL;
    char *name = NULL;
679
    unsigned int host;
680
    int ret = -1;
681 682

    *isActive = false;
683

684 685 686 687 688 689 690 691 692 693 694 695 696
    if (!(name = getAdapterName(pool->def->source.adapter))) {
        /* It's normal for the pool with "fc_host" type source
         * adapter fails to get the adapter name, since the vHBA
         * the adapter based on might be not created yet.
         */
        if (pool->def->source.adapter.type ==
            VIR_STORAGE_POOL_SOURCE_ADAPTER_TYPE_FC_HOST) {
            virResetLastError();
            return 0;
        } else {
            return -1;
        }
    }
697

698 699 700
    if (getHostNumber(name, &host) < 0)
        goto cleanup;

701
    if (virAsprintf(&path, "/sys/class/scsi_host/host%d", host) < 0)
702
        goto cleanup;
703

704
    *isActive = virFileExists(path);
705

706
    ret = 0;
707
 cleanup:
708
    VIR_FREE(path);
709 710
    VIR_FREE(name);
    return ret;
711
}
712

713
static int
714
virStorageBackendSCSIRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
715 716
                                 virStoragePoolObjPtr pool)
{
717
    char *name = NULL;
718
    unsigned int host;
719
    int ret = -1;
720 721 722

    pool->def->allocation = pool->def->capacity = pool->def->available = 0;

723 724 725 726
    if (!(name = getAdapterName(pool->def->source.adapter)))
        return -1;

    if (getHostNumber(name, &host) < 0)
727 728
        goto out;

729
    VIR_DEBUG("Scanning host%u", host);
730

731
    if (virStorageBackendSCSITriggerRescan(host) < 0)
732 733
        goto out;

734
    virStorageBackendSCSIFindLUs(pool, host);
735

736
    ret = 0;
737
 out:
738
    VIR_FREE(name);
739
    return ret;
740 741
}

742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
static int
virStorageBackendSCSIStartPool(virConnectPtr conn ATTRIBUTE_UNUSED,
                               virStoragePoolObjPtr pool)
{
    virStoragePoolSourceAdapter adapter = pool->def->source.adapter;
    return createVport(adapter);
}

static int
virStorageBackendSCSIStopPool(virConnectPtr conn ATTRIBUTE_UNUSED,
                              virStoragePoolObjPtr pool)
{
    virStoragePoolSourceAdapter adapter = pool->def->source.adapter;
    return deleteVport(adapter);
}
757 758 759 760

virStorageBackend virStorageBackendSCSI = {
    .type = VIR_STORAGE_POOL_SCSI,

761
    .checkPool = virStorageBackendSCSICheckPool,
762
    .refreshPool = virStorageBackendSCSIRefreshPool,
763 764
    .startPool = virStorageBackendSCSIStartPool,
    .stopPool = virStorageBackendSCSIStopPool,
765
};