storage_backend_scsi.c 17.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/*
 * storage_backend_scsi.c: storage backend for SCSI handling
 *
 * Copyright (C) 2007-2008 Red Hat, Inc.
 * 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
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 * Author: Daniel P. Berrange <berrange redhat com>
 */

#include <config.h>

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

#include "virterror_internal.h"
#include "storage_backend_scsi.h"
#include "memory.h"
#include "logging.h"
35
#include "files.h"
36 37 38 39 40 41 42 43

#define VIR_FROM_THIS VIR_FROM_STORAGE

/* 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
44
getDeviceType(uint32_t host,
45 46 47 48 49 50 51 52 53 54 55 56 57
              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",
                    host, bus, target, lun) < 0) {
58
        virReportOOMError();
59 60 61 62 63
        goto out;
    }

    typefile = fopen(type_path, "r");
    if (typefile == NULL) {
64
        virReportSystemError(errno,
65 66 67 68 69 70 71 72
                             _("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);
73
    VIR_FORCE_FCLOSE(typefile);
74 75

    if (gottype == NULL) {
76
        virReportSystemError(errno,
77 78 79 80 81 82 83 84 85 86 87
                             _("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) {
88
        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
89 90 91 92 93 94 95
                              _("Device type '%s' is not an integer"),
                              typestr);
        /* Hm, type wasn't an integer; seems strange */
        retval = -1;
        goto out;
    }

96
    VIR_DEBUG("Device type is %d", *type);
97 98 99 100 101 102

out:
    VIR_FREE(type_path);
    return retval;
}

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
struct diskType {
    int part_table_type;
    unsigned short offset;
    unsigned short length;
    unsigned long long magic;
};

static struct diskType const disk_types[] = {
    { VIR_STORAGE_POOL_DISK_LVM2, 0x218, 8, 0x31303020324D564CULL },
    { VIR_STORAGE_POOL_DISK_GPT,  0x200, 8, 0x5452415020494645ULL },
    { VIR_STORAGE_POOL_DISK_DVH,  0x0,   4, 0x41A9E50BULL },
    { VIR_STORAGE_POOL_DISK_MAC,  0x0,   2, 0x5245ULL },
    { VIR_STORAGE_POOL_DISK_BSD,  0x40,  4, 0x82564557ULL },
    { VIR_STORAGE_POOL_DISK_SUN,  0x1fc, 2, 0xBEDAULL },
    /*
     * NOTE: pc98 is funky; the actual signature is 0x55AA (just like dos), so
     * we can't use that.  At the moment I'm relying on the "dummy" IPL
     * bootloader data that comes from parted.  Luckily, the chances of running
     * into a pc98 machine running libvirt are approximately nil.
     */
    /*{ 0x1fe, 2, 0xAA55UL },*/
    { VIR_STORAGE_POOL_DISK_PC98, 0x0,   8, 0x314C5049000000CBULL },
    /*
     * NOTE: the order is important here; some other disk types (like GPT and
     * and PC98) also have 0x55AA at this offset.  For that reason, the DOS
     * one must be the last one.
     */
    { VIR_STORAGE_POOL_DISK_DOS,  0x1fe, 2, 0xAA55ULL },
    { -1,                         0x0,   0, 0x0ULL },
};

static int
135
virStorageBackendSCSIUpdateVolTargetInfo(virStorageVolTargetPtr target,
136 137 138
                                         unsigned long long *allocation,
                                         unsigned long long *capacity)
{
139 140
    int fdret, fd = -1;
    int ret = -1;
141

142 143 144
    if ((fdret = virStorageBackendVolOpen(target->path)) < 0)
        goto cleanup;
    fd = fdret;
145

146
    if (virStorageBackendUpdateVolTargetInfoFD(target,
147 148 149
                                               fd,
                                               allocation,
                                               capacity) < 0)
150
        goto cleanup;
151

152
    if (virStorageBackendDetectBlockVolFormatFD(target, fd) < 0)
153
        goto cleanup;
154

155 156
    ret = 0;

157
cleanup:
158
    VIR_FORCE_CLOSE(fd);
159 160

    return ret;
161
}
162 163

static int
164
virStorageBackendSCSINewLun(virStoragePoolObjPtr pool,
165
                            uint32_t host ATTRIBUTE_UNUSED,
166 167 168 169 170 171 172 173 174 175
                            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) {
176
        virReportOOMError();
177 178 179 180 181 182
        retval = -1;
        goto out;
    }

    vol->type = VIR_STORAGE_VOL_BLOCK;

183 184 185 186 187 188
    /* '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) {
189
        virReportOOMError();
190 191 192 193 194
        retval = -1;
        goto free_vol;
    }

    if (virAsprintf(&devpath, "/dev/%s", dev) < 0) {
195
        virReportOOMError();
196 197 198 199
        retval = -1;
        goto free_vol;
    }

200
    VIR_DEBUG("Trying to create volume for '%s'", devpath);
201 202 203 204 205 206 207

    /* 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...
     */
208
    if ((vol->target.path = virStorageBackendStablePath(pool,
209 210 211 212 213 214 215 216 217
                                                        devpath)) == NULL) {
        retval = -1;
        goto free_vol;
    }

    if (STREQLEN(devpath, vol->target.path, PATH_MAX) &&
        !(STREQ(pool->def->target.path, "/dev") ||
          STREQ(pool->def->target.path, "/dev/"))) {

218
        VIR_DEBUG("No stable path found for '%s' in '%s'",
219 220 221 222 223 224
                  devpath, pool->def->target.path);

        retval = -1;
        goto free_vol;
    }

225
    if (virStorageBackendSCSIUpdateVolTargetInfo(&vol->target,
226 227
                                                 &vol->allocation,
                                                 &vol->capacity) < 0) {
228

229
        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
230 231 232 233 234 235 236 237 238
                              _("Failed to update volume for '%s'"),
                              devpath);
        retval = -1;
        goto free_vol;
    }

    /* XXX should use logical unit's UUID instead */
    vol->key = strdup(vol->target.path);
    if (vol->key == NULL) {
239
        virReportOOMError();
240 241 242 243 244 245 246 247 248
        retval = -1;
        goto free_vol;
    }

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

    if (VIR_REALLOC_N(pool->volumes.objs,
                      pool->volumes.count + 1) < 0) {
249
        virReportOOMError();
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
        retval = -1;
        goto free_vol;
    }
    pool->volumes.objs[pool->volumes.count++] = vol;

    goto out;

free_vol:
    virStorageVolDefFree(vol);
out:
    VIR_FREE(devpath);
    return retval;
}


static int
266
getNewStyleBlockDevice(const char *lun_path,
267 268 269 270 271 272 273 274 275
                       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;

    if (virAsprintf(&block_path, "%s/block", lun_path) < 0) {
276
        virReportOOMError();
277 278 279
        goto out;
    }

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

    block_dir = opendir(block_path);
    if (block_dir == NULL) {
284
        virReportSystemError(errno,
285 286 287 288 289 290 291 292 293 294 295 296 297
                             _("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;
        }

        *block_device = strdup(block_dirent->d_name);
298 299

        if (*block_device == NULL) {
300
            virReportOOMError();
301 302 303 304 305
            closedir(block_dir);
            retval = -1;
            goto out;
        }

306
        VIR_DEBUG("Block device is '%s'", *block_device);
307 308 309 310 311 312 313 314 315 316 317 318 319

        break;
    }

    closedir(block_dir);

out:
    VIR_FREE(block_path);
    return retval;
}


static int
320
getOldStyleBlockDevice(const char *lun_path ATTRIBUTE_UNUSED,
321 322 323 324 325 326 327 328 329 330
                       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 */
331
        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
332 333 334 335 336 337 338
                              _("Failed to parse block name %s"),
                              block_name);
        retval = -1;
    } else {
        blockp++;
        *block_device = strdup(blockp);

339
        if (*block_device == NULL) {
340
            virReportOOMError();
341 342 343 344
            retval = -1;
            goto out;
        }

345
        VIR_DEBUG("Block device is '%s'", *block_device);
346 347
    }

348
out:
349 350 351 352 353
    return retval;
}


static int
354
getBlockDevice(uint32_t host,
355 356 357 358 359 360 361 362 363 364 365 366
               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",
                    host, bus, target, lun) < 0) {
367
        virReportOOMError();
368 369 370 371 372
        goto out;
    }

    lun_dir = opendir(lun_path);
    if (lun_dir == NULL) {
373
        virReportSystemError(errno,
374 375 376 377 378 379 380 381 382
                             _("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) {
383
                retval = getNewStyleBlockDevice(lun_path,
384 385 386
                                                lun_dirent->d_name,
                                                block_device);
            } else {
387
                retval = getOldStyleBlockDevice(lun_path,
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
                                                lun_dirent->d_name,
                                                block_device);
            }
            break;
        }
    }

    closedir(lun_dir);

out:
    VIR_FREE(lun_path);
    return retval;
}


static int
404
processLU(virStoragePoolObjPtr pool,
405 406 407 408 409 410 411 412 413 414
          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;

415
    VIR_DEBUG("Processing LU %u:%u:%u:%u",
416 417
              host, bus, target, lun);

418 419
    if (getDeviceType(host, bus, target, lun, &device_type) < 0) {
        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
                              _("Failed to determine if %u:%u:%u:%u is a Direct-Access LUN"),
                              host, bus, target, lun);
        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;
    }

436
    VIR_DEBUG("%u:%u:%u:%u is a Direct-Access LUN",
437 438
              host, bus, target, lun);

439
    if (getBlockDevice(host, bus, target, lun, &block_device) < 0) {
440 441 442
        goto out;
    }

443
    if (virStorageBackendSCSINewLun(pool,
444 445
                                    host, bus, target, lun,
                                    block_device) < 0) {
446
        VIR_DEBUG("Failed to create new storage volume for %u:%u:%u:%u",
447 448 449 450 451
                  host, bus, target, lun);
        retval = -1;
        goto out;
    }

452
    VIR_DEBUG("Created new storage volume for %u:%u:%u:%u successfully",
453 454 455 456 457 458 459 460 461 462
              host, bus, target, lun);

    VIR_FREE(type_path);

out:
    return retval;
}


int
463
virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool,
464 465 466 467 468 469 470 471 472
                             uint32_t scanhost)
{
    int retval = 0;
    uint32_t bus, target, lun;
    char *device_path = NULL;
    DIR *devicedir = NULL;
    struct dirent *lun_dirent = NULL;
    char devicepattern[64];

473
    VIR_DEBUG("Discovering LUs on host %u", scanhost);
474

475
    virFileWaitForDevices();
476 477

    if (virAsprintf(&device_path, "/sys/bus/scsi/devices") < 0) {
478
        virReportOOMError();
479 480 481 482 483 484
        goto out;
    }

    devicedir = opendir(device_path);

    if (devicedir == NULL) {
485
        virReportSystemError(errno,
486 487 488 489 490 491 492 493 494 495 496 497 498
                             _("Failed to opendir path '%s'"), device_path);
        retval = -1;
        goto out;
    }

    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;
        }

499
        VIR_DEBUG("Found LU '%s'", lun_dirent->d_name);
500

501
        processLU(pool, scanhost, bus, target, lun);
502 503 504 505 506 507 508 509 510 511 512
    }

    closedir(devicedir);

out:
    VIR_FREE(device_path);
    return retval;
}


int
513
virStorageBackendSCSIGetHostNumber(const char *sysfs_path,
514 515 516 517 518 519
                                   uint32_t *host)
{
    int retval = 0;
    DIR *sysdir = NULL;
    struct dirent *dirent = NULL;

520
    VIR_DEBUG("Finding host number from '%s'", sysfs_path);
521

522
    virFileWaitForDevices();
523 524 525 526

    sysdir = opendir(sysfs_path);

    if (sysdir == NULL) {
527
        virReportSystemError(errno,
528 529 530 531 532 533 534 535 536
                             _("Failed to opendir path '%s'"), sysfs_path);
        retval = -1;
        goto out;
    }

    while ((dirent = readdir(sysdir))) {
        if (STREQLEN(dirent->d_name, "target", strlen("target"))) {
            if (sscanf(dirent->d_name,
                       "target%u:", host) != 1) {
537
                VIR_DEBUG("Failed to parse target '%s'", dirent->d_name);
538 539 540 541 542 543 544 545 546 547 548 549
                retval = -1;
                break;
            }
        }
    }

    closedir(sysdir);
out:
    return retval;
}


550
static int
551
virStorageBackendSCSITriggerRescan(uint32_t host)
552 553 554 555 556
{
    int fd = -1;
    int retval = 0;
    char *path;

557
    VIR_DEBUG("Triggering rescan of host %d", host);
558 559

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

565
    VIR_DEBUG("Scan trigger path is '%s'", path);
566 567 568 569

    fd = open(path, O_WRONLY);

    if (fd < 0) {
570
        virReportSystemError(errno,
571 572 573 574 575 576 577 578 579
                             _("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) {
580
        VIR_FORCE_CLOSE(fd);
581
        virReportSystemError(errno,
582 583 584 585 586
                             _("Write to '%s' to trigger host scan failed"),
                             path);
        retval = -1;
    }

587
    VIR_FORCE_CLOSE(fd);
588 589 590
free_path:
    VIR_FREE(path);
out:
591
    VIR_DEBUG("Rescan of host %d complete", host);
592 593 594
    return retval;
}

595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
static int
virStorageBackendSCSICheckPool(virConnectPtr conn ATTRIBUTE_UNUSED,
                               virStoragePoolObjPtr pool,
                               bool *isActive)
{
    char *path;

    *isActive = false;
    if (virAsprintf(&path, "/sys/class/scsi_host/%s", pool->def->source.adapter) < 0) {
        virReportOOMError();
        return -1;
    }

    if (access(path, F_OK) == 0)
        *isActive = true;

    VIR_FREE(path);

    return 0;
}
615

616
static int
617
virStorageBackendSCSIRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
618 619 620 621 622 623 624 625
                                 virStoragePoolObjPtr pool)
{
    int retval = 0;
    uint32_t host;

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

    if (sscanf(pool->def->source.adapter, "host%u", &host) != 1) {
626
        VIR_DEBUG("Failed to get host number from '%s'",
627
                    pool->def->source.adapter);
628 629 630 631
        retval = -1;
        goto out;
    }

632
    VIR_DEBUG("Scanning host%u", host);
633

634
    if (virStorageBackendSCSITriggerRescan(host) < 0) {
635 636 637 638
        retval = -1;
        goto out;
    }

639
    virStorageBackendSCSIFindLUs(pool, host);
640 641 642 643 644 645 646 647 648

out:
    return retval;
}


virStorageBackend virStorageBackendSCSI = {
    .type = VIR_STORAGE_POOL_SCSI,

649
    .checkPool = virStorageBackendSCSICheckPool,
650 651
    .refreshPool = virStorageBackendSCSIRefreshPool,
};