From 489fde76802bfbae71c0d2639b8905a343016ca9 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Tue, 27 Jan 2009 18:30:03 +0000 Subject: [PATCH] Add support for copy-on-write storage volumes --- ChangeLog | 19 ++ docs/formatstorage.html | 45 ++++- docs/formatstorage.html.in | 52 +++++- src/libvirt_private.syms | 1 + src/storage_backend.c | 130 +++++++------ src/storage_backend.h | 13 +- src/storage_backend_fs.c | 335 ++++++++++++++++++++++++++++++---- src/storage_backend_iscsi.c | 6 +- src/storage_backend_logical.c | 68 ++++--- src/storage_conf.c | 101 +++++++--- src/storage_conf.h | 1 + 11 files changed, 620 insertions(+), 151 deletions(-) diff --git a/ChangeLog b/ChangeLog index 042d345998..4c1d4e31dd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +Tue Jan 27 18:17:07 GMT 2009 Daniel P. Berrange + + Support Copy-on-Write storage volumes + * docs/formatstorage.html.in: Add notes about backingStore + XML for storage volumes wanting copy-on-write (eg qcow, + LVM snapshots). + * src/libvirt_private.syms: Add virStorageVolFormatFileSystemTypeFromString + * src/storage_backend.c, src/storage_backend.h: Refactor the + virStorageBackendUpdateVolInfo* methods to allow re-use for + backingStore files + * src/storage_backend_fs.c: Extract backing store data out of + Cow, QCow, QCow2, and VMDK file formats. Allow creation of volumes + with a backing store + * src/storage_backend_logical.c: Extract information about master + volume for snapshots, and allow creation of snapshots. + * src/storage_backend_iscsi.c: Adapt to storage_backend.h changes + * src/storage_conf.h, src/storage_conf.c: Support new backingStore + XML element for COW file data + Tue Jan 27 16:27:07 +0100 2009 Jim Meyering * POTFILES.in: update: remove src/lxc_conf.c; Add src/bridge.c. diff --git a/docs/formatstorage.html b/docs/formatstorage.html index e6ed2799b9..bec32d10ba 100644 --- a/docs/formatstorage.html +++ b/docs/formatstorage.html @@ -131,6 +131,8 @@ General metadata
  • Target elements +
  • + Backing store elements
  • Example configuration @@ -328,14 +330,14 @@ ... <target> <path>/var/lib/virt/images/sparse.img</path> + <format>qcow2</format> <permissions> <owner>0744</owner> <group>0744</group> <mode>0744</mode> <label>virt_image_t</label> </permissions> - </target> - </volume> + </target>
    path
    Provides the location at which the volume can be accessed on the local filesystem, as an absolute path. This is a readonly attribute, so shouldn't be specified when creating a volume. @@ -355,6 +357,45 @@ element contains the numeric group ID. The label element contains the MAC (eg SELinux) label string. Since 0.4.1 +
    +

    + Backing store elements +

    +

    + A single backingStore element is contained within the top level + volume element. This tag is used to describe the optional copy + on write, backing store for the storage volume. It can contain the following + child elements: +

    +
    +        ...
    +        <backingStore>
    +          <path>/var/lib/virt/images/master.img</path>
    +          <format>raw</format>
    +          <permissions>
    +            <owner>0744</owner>
    +            <group>0744</group>
    +            <mode>0744</mode>
    +            <label>virt_image_t</label>
    +          </permissions>
    +        </backingStore>
    +      </volume>
    +
    path
    Provides the location at which the backing store can be accessed on + the local filesystem, as an absolute path. If omitted, there is no + backing store for this volume. + Since 0.6.0
    format
    Provides information about the pool specific backing store format. + For disk pools it will provide the partition type. For filesystem + or directory pools it will provide the file format type, eg cow, + qcow, vmdk, raw. Consult the pool-specific docs for the list of valid + values. Most file formats require a backing store of the same format, + however, the qcow2 format allows a different backing store format. + Since 0.6.0
    permissions
    Provides information about the permissions of the backing file. + It contains 4 child elements. The + mode element contains the octal permission set. The + owner element contains the numeric user ID. The group + element contains the numeric group ID. The label element + contains the MAC (eg SELinux) label string. + Since 0.6.0

    Example configuration diff --git a/docs/formatstorage.html.in b/docs/formatstorage.html.in index 366339af1c..2055f04278 100644 --- a/docs/formatstorage.html.in +++ b/docs/formatstorage.html.in @@ -234,14 +234,14 @@ ... <target> <path>/var/lib/virt/images/sparse.img</path> + <format>qcow2</format> <permissions> <owner>0744</owner> <group>0744</group> <mode>0744</mode> <label>virt_image_t</label> </permissions> - </target> - </volume> + </target>
    path
    @@ -271,6 +271,54 @@
    +

    Backing store elements

    + +

    + A single backingStore element is contained within the top level + volume element. This tag is used to describe the optional copy + on write, backing store for the storage volume. It can contain the following + child elements: +

    + +
    +        ...
    +        <backingStore>
    +          <path>/var/lib/virt/images/master.img</path>
    +          <format>raw</format>
    +          <permissions>
    +            <owner>0744</owner>
    +            <group>0744</group>
    +            <mode>0744</mode>
    +            <label>virt_image_t</label>
    +          </permissions>
    +        </backingStore>
    +      </volume>
    + +
    +
    path
    +
    Provides the location at which the backing store can be accessed on + the local filesystem, as an absolute path. If omitted, there is no + backing store for this volume. + Since 0.6.0
    +
    format
    +
    Provides information about the pool specific backing store format. + For disk pools it will provide the partition type. For filesystem + or directory pools it will provide the file format type, eg cow, + qcow, vmdk, raw. Consult the pool-specific docs for the list of valid + values. Most file formats require a backing store of the same format, + however, the qcow2 format allows a different backing store format. + Since 0.6.0
    +
    permissions
    +
    Provides information about the permissions of the backing file. + It contains 4 child elements. The + mode element contains the octal permission set. The + owner element contains the numeric user ID. The group + element contains the numeric group ID. The label element + contains the MAC (eg SELinux) label string. + Since 0.6.0 +
    +
    +

    Example configuration

    diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7b177063a8..a6f4f3ee43 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -259,6 +259,7 @@ virStoragePoolFormatDiskTypeToString; virStoragePoolFormatFileSystemTypeToString; virStoragePoolFormatFileSystemNetTypeToString; virStorageVolFormatFileSystemTypeToString; +virStorageVolFormatFileSystemTypeFromString; virStoragePoolTypeFromString; virStoragePoolObjLock; virStoragePoolObjUnlock; diff --git a/src/storage_backend.c b/src/storage_backend.c index 1bebbafdfe..8408f34669 100644 --- a/src/storage_backend.c +++ b/src/storage_backend.c @@ -99,29 +99,53 @@ virStorageBackendForType(int type) { int -virStorageBackendUpdateVolInfo(virConnectPtr conn, - virStorageVolDefPtr vol, - int withCapacity) +virStorageBackendUpdateVolTargetInfo(virConnectPtr conn, + virStorageVolTargetPtr target, + unsigned long long *allocation, + unsigned long long *capacity) { int ret, fd; - if ((fd = open(vol->target.path, O_RDONLY)) < 0) { + if ((fd = open(target->path, O_RDONLY)) < 0) { virReportSystemError(conn, errno, _("cannot open volume '%s'"), - vol->target.path); + target->path); return -1; } - ret = virStorageBackendUpdateVolInfoFD(conn, - vol, - fd, - withCapacity); + ret = virStorageBackendUpdateVolTargetInfoFD(conn, + target, + fd, + allocation, + capacity); close(fd); return ret; } +int +virStorageBackendUpdateVolInfo(virConnectPtr conn, + virStorageVolDefPtr vol, + int withCapacity) +{ + int ret; + + if ((ret = virStorageBackendUpdateVolTargetInfo(conn, + &vol->target, + &vol->allocation, + withCapacity ? &vol->capacity : NULL)) < 0) + return ret; + + if (vol->backingStore.path && + (ret = virStorageBackendUpdateVolTargetInfo(conn, + &vol->backingStore, + NULL, NULL)) < 0) + return ret; + + return 0; +} + struct diskType { int part_table_type; unsigned short offset; @@ -154,10 +178,11 @@ static struct diskType const disk_types[] = { }; int -virStorageBackendUpdateVolInfoFD(virConnectPtr conn, - virStorageVolDefPtr vol, - int fd, - int withCapacity) +virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn, + virStorageVolTargetPtr target, + int fd, + unsigned long long *allocation, + unsigned long long *capacity) { struct stat sb; #if HAVE_SELINUX @@ -167,7 +192,7 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn, if (fstat(fd, &sb) < 0) { virReportSystemError(conn, errno, _("cannot stat file '%s'"), - vol->target.path); + target->path); return -1; } @@ -176,38 +201,41 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn, !S_ISBLK(sb.st_mode)) return -2; - if (S_ISREG(sb.st_mode)) { + if (allocation) { + if (S_ISREG(sb.st_mode)) { #ifndef __MINGW32__ - vol->allocation = (unsigned long long)sb.st_blocks * - (unsigned long long)sb.st_blksize; + *allocation = (unsigned long long)sb.st_blocks * + (unsigned long long)sb.st_blksize; #else - vol->allocation = sb.st_size; + *allocation = sb.st_size; #endif - /* Regular files may be sparse, so logical size (capacity) is not same - * as actual allocation above - */ - if (withCapacity) - vol->capacity = sb.st_size; - } else { - off_t end; - /* XXX this is POSIX compliant, but doesn't work for for CHAR files, - * only BLOCK. There is a Linux specific ioctl() for getting - * size of both CHAR / BLOCK devices we should check for in - * configure - */ - end = lseek(fd, 0, SEEK_END); - if (end == (off_t)-1) { - virReportSystemError(conn, errno, - _("cannot seek to end of file '%s'"), - vol->target.path); - return -1; + /* Regular files may be sparse, so logical size (capacity) is not same + * as actual allocation above + */ + if (capacity) + *capacity = sb.st_size; + } else { + off_t end; + /* XXX this is POSIX compliant, but doesn't work for for CHAR files, + * only BLOCK. There is a Linux specific ioctl() for getting + * size of both CHAR / BLOCK devices we should check for in + * configure + */ + end = lseek(fd, 0, SEEK_END); + if (end == (off_t)-1) { + virReportSystemError(conn, errno, + _("cannot seek to end of file '%s'"), + target->path); + return -1; + } + *allocation = end; + if (capacity) + *capacity = end; } - vol->allocation = end; - if (withCapacity) vol->capacity = end; } /* make sure to set the target format "unknown" to begin with */ - vol->target.format = VIR_STORAGE_POOL_DISK_UNKNOWN; + target->format = VIR_STORAGE_POOL_DISK_UNKNOWN; if (S_ISBLK(sb.st_mode)) { off_t start; @@ -219,14 +247,14 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn, if (start < 0) { virReportSystemError(conn, errno, _("cannot seek to beginning of file '%s'"), - vol->target.path); + target->path); return -1; } bytes = saferead(fd, buffer, sizeof(buffer)); if (bytes < 0) { virReportSystemError(conn, errno, _("cannot read beginning of file '%s'"), - vol->target.path); + target->path); return -1; } @@ -235,38 +263,38 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn, continue; if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic, disk_types[i].length) == 0) { - vol->target.format = disk_types[i].part_table_type; + target->format = disk_types[i].part_table_type; break; } } } - vol->target.perms.mode = sb.st_mode; - vol->target.perms.uid = sb.st_uid; - vol->target.perms.gid = sb.st_gid; + target->perms.mode = sb.st_mode & S_IRWXUGO; + target->perms.uid = sb.st_uid; + target->perms.gid = sb.st_gid; - VIR_FREE(vol->target.perms.label); + VIR_FREE(target->perms.label); #if HAVE_SELINUX if (fgetfilecon(fd, &filecon) == -1) { if (errno != ENODATA && errno != ENOTSUP) { virReportSystemError(conn, errno, _("cannot get file context of '%s'"), - vol->target.path); + target->path); return -1; } else { - vol->target.perms.label = NULL; + target->perms.label = NULL; } } else { - vol->target.perms.label = strdup(filecon); - if (vol->target.perms.label == NULL) { + target->perms.label = strdup(filecon); + if (target->perms.label == NULL) { virReportOOMError(conn); return -1; } freecon(filecon); } #else - vol->target.perms.label = NULL; + target->perms.label = NULL; #endif return 0; diff --git a/src/storage_backend.h b/src/storage_backend.h index 06f6979ad9..7fab384bd0 100644 --- a/src/storage_backend.h +++ b/src/storage_backend.h @@ -64,10 +64,15 @@ int virStorageBackendUpdateVolInfo(virConnectPtr conn, virStorageVolDefPtr vol, int withCapacity); -int virStorageBackendUpdateVolInfoFD(virConnectPtr conn, - virStorageVolDefPtr vol, - int fd, - int withCapacity); +int virStorageBackendUpdateVolTargetInfo(virConnectPtr conn, + virStorageVolTargetPtr target, + unsigned long long *allocation, + unsigned long long *capacity); +int virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn, + virStorageVolTargetPtr target, + int fd, + unsigned long long *allocation, + unsigned long long *capacity); void virStorageBackendWaitForDevices(virConnectPtr conn); diff --git a/src/storage_backend_fs.c b/src/storage_backend_fs.c index 544f4e2f6b..6fd9f8f81d 100644 --- a/src/storage_backend_fs.c +++ b/src/storage_backend_fs.c @@ -49,6 +49,19 @@ enum lv_endian { LV_BIG_ENDIAN /* 4321 */ }; +enum { + BACKING_STORE_OK, + BACKING_STORE_INVALID, + BACKING_STORE_ERROR, +}; + +static int cowGetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); +static int qcowXGetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); +static int vmdk4GetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); + /* Either 'magic' or 'extension' *must* be provided */ struct FileTypeInfo { int type; /* One of the constants above */ @@ -65,85 +78,228 @@ struct FileTypeInfo { * -1 to use st_size as capacity */ int sizeBytes; /* Number of bytes for size field */ int sizeMultiplier; /* A scaling factor if size is not in bytes */ + /* Store a COW base image path (possibly relative), + * or NULL if there is no COW base image, to RES; + * return BACKING_STORE_* */ + int (*getBackingStore)(virConnectPtr conn, char **res, + const unsigned char *buf, size_t buf_size); }; const struct FileTypeInfo const fileTypeInfo[] = { /* Bochs */ /* XXX Untested { VIR_STORAGE_VOL_BOCHS, "Bochs Virtual HD Image", NULL, LV_LITTLE_ENDIAN, 64, 0x20000, - 32+16+16+4+4+4+4+4, 8, 1 },*/ + 32+16+16+4+4+4+4+4, 8, 1, NULL },*/ /* CLoop */ /* XXX Untested { VIR_STORAGE_VOL_CLOOP, "#!/bin/sh\n#V2.0 Format\nmodprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n", NULL, LV_LITTLE_ENDIAN, -1, 0, - -1, 0, 0 }, */ + -1, 0, 0, NULL }, */ /* Cow */ { VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL, LV_BIG_ENDIAN, 4, 2, - 4+4+1024+4, 8, 1 }, + 4+4+1024+4, 8, 1, cowGetBackingStore }, /* DMG */ /* XXX QEMU says there's no magic for dmg, but we should check... */ { VIR_STORAGE_VOL_FILE_DMG, NULL, ".dmg", 0, -1, 0, - -1, 0, 0 }, + -1, 0, 0, NULL }, /* XXX there's probably some magic for iso we can validate too... */ { VIR_STORAGE_VOL_FILE_ISO, NULL, ".iso", 0, -1, 0, - -1, 0, 0 }, + -1, 0, 0, NULL }, /* Parallels */ /* XXX Untested { VIR_STORAGE_VOL_FILE_PARALLELS, "WithoutFreeSpace", NULL, LV_LITTLE_ENDIAN, 16, 2, - 16+4+4+4+4, 4, 512 }, + 16+4+4+4+4, 4, 512, NULL }, */ /* QCow */ { VIR_STORAGE_VOL_FILE_QCOW, "QFI", NULL, LV_BIG_ENDIAN, 4, 1, - 4+4+8+4+4, 8, 1 }, + 4+4+8+4+4, 8, 1, qcowXGetBackingStore }, /* QCow 2 */ { VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL, LV_BIG_ENDIAN, 4, 2, - 4+4+8+4+4, 8, 1 }, + 4+4+8+4+4, 8, 1, qcowXGetBackingStore }, /* VMDK 3 */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL, LV_LITTLE_ENDIAN, 4, 1, - 4+4+4, 4, 512 }, + 4+4+4, 4, 512, NULL }, */ /* VMDK 4 */ { VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL, LV_LITTLE_ENDIAN, 4, 1, - 4+4+4, 8, 512 }, + 4+4+4, 8, 512, vmdk4GetBackingStore }, /* Connectix / VirtualPC */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL, LV_BIG_ENDIAN, -1, 0, - -1, 0, 0}, + -1, 0, 0, NULL}, */ }; #define VIR_FROM_THIS VIR_FROM_STORAGE +static int +cowGetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ +#define COW_FILENAME_MAXLEN 1024 + *res = NULL; + if (buf_size < 4+4+ COW_FILENAME_MAXLEN) + return BACKING_STORE_INVALID; + if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */ + return BACKING_STORE_OK; + + *res = strndup ((const char*)buf + 4+4, COW_FILENAME_MAXLEN); + if (*res == NULL) { + virReportOOMError(conn); + return BACKING_STORE_ERROR; + } + return BACKING_STORE_OK; +} + +static int +qcowXGetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ + unsigned long long offset; + unsigned long size; + + *res = NULL; + if (buf_size < 4+4+8+4) + return BACKING_STORE_INVALID; + offset = (((unsigned long long)buf[4+4] << 56) + | ((unsigned long long)buf[4+4+1] << 48) + | ((unsigned long long)buf[4+4+2] << 40) + | ((unsigned long long)buf[4+4+3] << 32) + | ((unsigned long long)buf[4+4+4] << 24) + | ((unsigned long long)buf[4+4+5] << 16) + | ((unsigned long long)buf[4+4+6] << 8) + | buf[4+4+7]); /* QCowHeader.backing_file_offset */ + if (offset > buf_size) + return BACKING_STORE_INVALID; + size = ((buf[4+4+8] << 24) + | (buf[4+4+8+1] << 16) + | (buf[4+4+8+2] << 8) + | buf[4+4+8+3]); /* QCowHeader.backing_file_size */ + if (size == 0) + return BACKING_STORE_OK; + if (offset + size > buf_size || offset + size < offset) + return BACKING_STORE_INVALID; + if (size + 1 == 0) + return BACKING_STORE_INVALID; + if (VIR_ALLOC_N(*res, size + 1) < 0) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("backing store path")); + return BACKING_STORE_ERROR; + } + memcpy(*res, buf + offset, size); + (*res)[size] = '\0'; + return BACKING_STORE_OK; +} + + +static int +vmdk4GetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ + static const char prefix[] = "parentFileNameHint=\""; + + char desc[20*512 + 1], *start, *end; + size_t len; + + *res = NULL; + + if (buf_size <= 0x200) + return BACKING_STORE_INVALID; + len = buf_size - 0x200; + if (len > sizeof(desc) - 1) + len = sizeof(desc) - 1; + memcpy(desc, buf + 0x200, len); + desc[len] = '\0'; + start = strstr(desc, prefix); + if (start == NULL) + return BACKING_STORE_OK; + start += strlen(prefix); + end = strchr(start, '"'); + if (end == NULL) + return BACKING_STORE_INVALID; + if (end == start) + return BACKING_STORE_OK; + *end = '\0'; + *res = strdup(start); + if (*res == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("backing store path")); + return BACKING_STORE_ERROR; + } + return BACKING_STORE_OK; +} + +/** + * Return an absolute path corresponding to PATH, which is absolute or relative + * to the directory containing BASE_FILE, or NULL on error + */ +static char *absolutePathFromBaseFile(const char *base_file, const char *path) +{ + size_t base_size, path_size; + char *res, *p; + + if (*path == '/') + return strdup(path); + + base_size = strlen(base_file) + 1; + path_size = strlen(path) + 1; + if (VIR_ALLOC_N(res, base_size - 1 + path_size) < 0) + return NULL; + memcpy(res, base_file, base_size); + p = strrchr(res, '/'); + if (p != NULL) + p++; + else + p = res; + memcpy(p, path, path_size); + if (VIR_REALLOC_N(res, (p + path_size) - res) < 0) { + /* Ignore failure */ + } + return res; +} + /** * Probe the header of a file to determine what type of disk image * it is, and info about its capacity if available. */ -static int virStorageBackendProbeFile(virConnectPtr conn, - virStorageVolDefPtr def) { +static int virStorageBackendProbeTarget(virConnectPtr conn, + virStorageVolTargetPtr target, + char **backingStore, + unsigned long long *allocation, + unsigned long long *capacity) { int fd; - unsigned char head[4096]; + unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */ int len, i, ret; - if ((fd = open(def->target.path, O_RDONLY)) < 0) { + if (backingStore) + *backingStore = NULL; + + if ((fd = open(target->path, O_RDONLY)) < 0) { virReportSystemError(conn, errno, _("cannot open volume '%s'"), - def->target.path); + target->path); return -1; } - if ((ret = virStorageBackendUpdateVolInfoFD(conn, def, fd, 1)) < 0) { + if ((ret = virStorageBackendUpdateVolTargetInfoFD(conn, target, fd, + allocation, + capacity)) < 0) { close(fd); return ret; /* Take care to propagate ret, it is not always -1 */ } @@ -151,7 +307,7 @@ static int virStorageBackendProbeFile(virConnectPtr conn, if ((len = read(fd, head, sizeof(head))) < 0) { virReportSystemError(conn, errno, _("cannot read header '%s'"), - def->target.path); + target->path); close(fd); return -1; } @@ -191,9 +347,9 @@ static int virStorageBackendProbeFile(virConnectPtr conn, } /* Optionally extract capacity from file */ - if (fileTypeInfo[i].sizeOffset != -1) { + if (fileTypeInfo[i].sizeOffset != -1 && capacity) { if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { - def->capacity = + *capacity = ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7] << 56) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) | @@ -203,7 +359,7 @@ static int virStorageBackendProbeFile(virConnectPtr conn, ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 8) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset]); } else { - def->capacity = + *capacity = ((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) | @@ -214,13 +370,37 @@ static int virStorageBackendProbeFile(virConnectPtr conn, ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]); } /* Avoid unlikely, but theoretically possible overflow */ - if (def->capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier)) + if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier)) continue; - def->capacity *= fileTypeInfo[i].sizeMultiplier; + *capacity *= fileTypeInfo[i].sizeMultiplier; } /* Validation passed, we know the file format now */ - def->target.format = fileTypeInfo[i].type; + target->format = fileTypeInfo[i].type; + if (fileTypeInfo[i].getBackingStore != NULL && backingStore) { + char *base; + + switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) { + case BACKING_STORE_OK: + break; + + case BACKING_STORE_INVALID: + continue; + + case BACKING_STORE_ERROR: + return -1; + } + if (base != NULL) { + *backingStore + = absolutePathFromBaseFile(target->path, base); + VIR_FREE(base); + if (*backingStore == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, + _("backing store path")); + return -1; + } + } + } return 0; } @@ -229,15 +409,15 @@ static int virStorageBackendProbeFile(virConnectPtr conn, if (fileTypeInfo[i].extension == NULL) continue; - if (!virFileHasSuffix(def->target.path, fileTypeInfo[i].extension)) + if (!virFileHasSuffix(target->path, fileTypeInfo[i].extension)) continue; - def->target.format = fileTypeInfo[i].type; + target->format = fileTypeInfo[i].type; return 0; } /* All fails, so call it a raw file */ - def->target.format = VIR_STORAGE_VOL_FILE_RAW; + target->format = VIR_STORAGE_VOL_FILE_RAW; return 0; } @@ -636,6 +816,7 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn, while ((ent = readdir(dir)) != NULL) { int ret; + char *backingStore; if (VIR_ALLOC(vol) < 0) goto no_memory; @@ -655,7 +836,11 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn, if ((vol->key = strdup(vol->target.path)) == NULL) goto no_memory; - if ((ret = virStorageBackendProbeFile(conn, vol) < 0)) { + if ((ret = virStorageBackendProbeTarget(conn, + &vol->target, + &backingStore, + &vol->allocation, + &vol->capacity) < 0)) { if (ret == -1) goto no_memory; else { @@ -667,6 +852,48 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn, } } + if (backingStore != NULL) { + if (vol->target.format == VIR_STORAGE_VOL_FILE_QCOW2 && + STRPREFIX("fmt:", backingStore)) { + char *fmtstr = backingStore + 4; + char *path = strchr(fmtstr, ':'); + if (!path) { + VIR_FREE(backingStore); + } else { + *path = '\0'; + if ((vol->backingStore.format = + virStorageVolFormatFileSystemTypeFromString(fmtstr)) < 0) { + VIR_FREE(backingStore); + } else { + memmove(backingStore, path, strlen(path) + 1); + vol->backingStore.path = backingStore; + + if (virStorageBackendUpdateVolTargetInfo(conn, + &vol->backingStore, + NULL, + NULL) < 0) + VIR_FREE(vol->backingStore); + } + } + } else { + vol->backingStore.path = backingStore; + + if ((ret = virStorageBackendProbeTarget(conn, + &vol->backingStore, + NULL, NULL, NULL)) < 0) { + if (ret == -1) + goto no_memory; + else { + /* Silently ignore non-regular files, + * eg '.' '..', 'lost+found' */ + VIR_FREE(vol->backingStore); + } + } + } + } + + + if (VIR_REALLOC_N(pool->volumes.objs, pool->volumes.count+1) < 0) goto no_memory; @@ -836,28 +1063,48 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn, } } else { #if HAVE_QEMU_IMG - const char *type; + const char *type = virStorageVolFormatFileSystemTypeToString(vol->target.format); + const char *backingType = vol->backingStore.path ? + virStorageVolFormatFileSystemTypeToString(vol->backingStore.format) : NULL; char size[100]; - const char *imgargv[7]; + const char **imgargv; + const char *imgargvnormal[] = { + QEMU_IMG, "create", "-f", type, vol->target.path, size, NULL, + }; + /* XXX including "backingType" here too, once QEMU accepts + * the patches to specify it. It'll probably be -F backingType */ + const char *imgargvbacking[] = { + QEMU_IMG, "create", "-f", type, "-b", vol->backingStore.path, vol->target.path, size, NULL, + }; - if ((type = virStorageVolFormatFileSystemTypeToString(vol->target.format)) == NULL) { + if (type == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unknown storage vol type %d"), vol->target.format); return -1; } + if (vol->backingStore.path == NULL) { + imgargv = imgargvnormal; + } else { + if (backingType == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol backing store type %d"), + vol->backingStore.format); + return -1; + } + if (access(vol->backingStore.path, R_OK) != 0) { + virReportSystemError(conn, errno, + _("inaccessible backing store volume %s"), + vol->backingStore.path); + return -1; + } + + imgargv = imgargvbacking; + } /* Size in KB */ snprintf(size, sizeof(size), "%llu", vol->capacity/1024); - imgargv[0] = QEMU_IMG; - imgargv[1] = "create"; - imgargv[2] = "-f"; - imgargv[3] = type; - imgargv[4] = vol->target.path; - imgargv[5] = size; - imgargv[6] = NULL; - if (virRun(conn, imgargv, NULL) < 0) { unlink(vol->target.path); return -1; @@ -884,6 +1131,12 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn, vol->target.format); return -1; } + if (vol->target.backingStore != NULL) { + virStorageReportError(conn, VIR_ERR_NO_SUPPORT, + _("copy-on-write image not supported with " + "qcow-create")); + return -1; + } /* Size in MB - yes different units to qemu-img :-( */ snprintf(size, sizeof(size), "%llu", vol->capacity/1024/1024); @@ -934,7 +1187,9 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn, } /* Refresh allocation / permissions info, but not capacity */ - if (virStorageBackendUpdateVolInfoFD(conn, vol, fd, 0) < 0) { + if (virStorageBackendUpdateVolTargetInfoFD(conn, &vol->target, fd, + &vol->allocation, + NULL) < 0) { unlink(vol->target.path); close(fd); return -1; diff --git a/src/storage_backend_iscsi.c b/src/storage_backend_iscsi.c index 1d909c5c10..33f8507f13 100644 --- a/src/storage_backend_iscsi.c +++ b/src/storage_backend_iscsi.c @@ -228,7 +228,11 @@ virStorageBackendISCSINewLun(virConnectPtr conn, virStoragePoolObjPtr pool, VIR_FREE(devpath); - if (virStorageBackendUpdateVolInfoFD(conn, vol, fd, 1) < 0) + if (virStorageBackendUpdateVolTargetInfoFD(conn, + &vol->target, + fd, + &vol->allocation, + &vol->capacity) < 0) goto cleanup; /* XXX use unique iSCSI id instead */ diff --git a/src/storage_backend_logical.c b/src/storage_backend_logical.c index 474c3d80b4..75a716403e 100644 --- a/src/storage_backend_logical.c +++ b/src/storage_backend_logical.c @@ -106,18 +106,27 @@ virStorageBackendLogicalMakeVol(virConnectPtr conn, } if (vol->target.path == NULL) { - if (VIR_ALLOC_N(vol->target.path, strlen(pool->def->target.path) + - 1 + strlen(vol->name) + 1) < 0) { - virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume")); + if (virAsprintf(&vol->target.path, "%s/%s", + pool->def->target.path, vol->name) < 0) { + virReportOOMError(conn); + virStorageVolDefFree(vol); return -1; } - strcpy(vol->target.path, pool->def->target.path); - strcat(vol->target.path, "/"); - strcat(vol->target.path, vol->name); + } + + if (groups[1] && !STREQ(groups[1], "")) { + if (virAsprintf(&vol->backingStore.path, "%s/%s", + pool->def->target.path, groups[1]) < 0) { + virReportOOMError(conn); + virStorageVolDefFree(vol); + return -1; + } + + vol->backingStore.format = VIR_STORAGE_POOL_LOGICAL_LVM2; } if (vol->key == NULL && - (vol->key = strdup(groups[1])) == NULL) { + (vol->key = strdup(groups[2])) == NULL) { virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume")); return -1; } @@ -134,22 +143,22 @@ virStorageBackendLogicalMakeVol(virConnectPtr conn, } if ((vol->source.extents[vol->source.nextent].path = - strdup(groups[2])) == NULL) { + strdup(groups[3])) == NULL) { virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("extents")); return -1; } - if (virStrToLong_ull(groups[3], NULL, 10, &offset) < 0) { + if (virStrToLong_ull(groups[4], NULL, 10, &offset) < 0) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("malformed volume extent offset value")); return -1; } - if (virStrToLong_ull(groups[4], NULL, 10, &length) < 0) { + if (virStrToLong_ull(groups[5], NULL, 10, &length) < 0) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("malformed volume extent length value")); return -1; } - if (virStrToLong_ull(groups[5], NULL, 10, &size) < 0) { + if (virStrToLong_ull(groups[6], NULL, 10, &size) < 0) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("malformed volume extent size value")); return -1; @@ -168,14 +177,14 @@ virStorageBackendLogicalFindLVs(virConnectPtr conn, virStorageVolDefPtr vol) { /* - * # lvs --separator , --noheadings --units b --unbuffered --nosuffix --options "lv_name,uuid,devices,seg_size,vg_extent_size" VGNAME - * RootLV,06UgP5-2rhb-w3Bo-3mdR-WeoL-pytO-SAa2ky,/dev/hda2(0),5234491392,33554432 - * SwapLV,oHviCK-8Ik0-paqS-V20c-nkhY-Bm1e-zgzU0M,/dev/hda2(156),1040187392,33554432 - * Test2,3pg3he-mQsA-5Sui-h0i6-HNmc-Cz7W-QSndcR,/dev/hda2(219),1073741824,33554432 - * Test3,UB5hFw-kmlm-LSoX-EI1t-ioVd-h7GL-M0W8Ht,/dev/hda2(251),2181038080,33554432 - * Test3,UB5hFw-kmlm-LSoX-EI1t-ioVd-h7GL-M0W8Ht,/dev/hda2(187),1040187392,33554432 + * # lvs --separator , --noheadings --units b --unbuffered --nosuffix --options "lv_name,origin,uuid,devices,seg_size,vg_extent_size" VGNAME + * RootLV,,06UgP5-2rhb-w3Bo-3mdR-WeoL-pytO-SAa2ky,/dev/hda2(0),5234491392,33554432 + * SwapLV,,oHviCK-8Ik0-paqS-V20c-nkhY-Bm1e-zgzU0M,/dev/hda2(156),1040187392,33554432 + * Test2,,3pg3he-mQsA-5Sui-h0i6-HNmc-Cz7W-QSndcR,/dev/hda2(219),1073741824,33554432 + * Test3,,UB5hFw-kmlm-LSoX-EI1t-ioVd-h7GL-M0W8Ht,/dev/hda2(251),2181038080,33554432 + * Test3,Test2,UB5hFw-kmlm-LSoX-EI1t-ioVd-h7GL-M0W8Ht,/dev/hda2(187),1040187392,33554432 * - * Pull out name & uuid, device, device extent start #, segment size, extent size. + * Pull out name, origin, & uuid, device, device extent start #, segment size, extent size. * * NB can be multiple rows per volume if they have many extents * @@ -185,15 +194,15 @@ virStorageBackendLogicalFindLVs(virConnectPtr conn, * not a suitable separator (rhbz 470693). */ const char *regexes[] = { - "^\\s*(\\S+),(\\S+),(\\S+)\\((\\S+)\\),(\\S+),([0-9]+),?\\s*$" + "^\\s*(\\S+),(\\S*),(\\S+),(\\S+)\\((\\S+)\\),(\\S+),([0-9]+),?\\s*$" }; int vars[] = { - 6 + 7 }; const char *prog[] = { LVS, "--separator", ",", "--noheadings", "--units", "b", "--unbuffered", "--nosuffix", "--options", - "lv_name,uuid,devices,seg_size,vg_extent_size", + "lv_name,origin,uuid,devices,seg_size,vg_extent_size", pool->def->source.name, NULL }; @@ -565,10 +574,25 @@ virStorageBackendLogicalCreateVol(virConnectPtr conn, { int fd = -1; char size[100]; - const char *cmdargv[] = { + const char *cmdargvnew[] = { LVCREATE, "--name", vol->name, "-L", size, pool->def->target.path, NULL }; + const char *cmdargvsnap[] = { + LVCREATE, "--name", vol->name, "-L", size, + "-s", vol->backingStore.path, NULL + }; + const char **cmdargv = cmdargvnew; + + if (vol->backingStore.path) { + if (vol->backingStore.format != + VIR_STORAGE_POOL_LOGICAL_LVM2) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", + _("LVM snapshots must be backed by another LVM volume")); + return -1; + } + cmdargv = cmdargvsnap; + } snprintf(size, sizeof(size)-1, "%lluK", vol->capacity/1024); size[sizeof(size)-1] = '\0'; diff --git a/src/storage_conf.c b/src/storage_conf.c index 9d3f5a7a94..2f12e88d89 100644 --- a/src/storage_conf.c +++ b/src/storage_conf.c @@ -249,6 +249,8 @@ virStorageVolDefFree(virStorageVolDefPtr def) { VIR_FREE(def->target.path); VIR_FREE(def->target.perms.label); + VIR_FREE(def->backingStore.path); + VIR_FREE(def->backingStore.perms.label); VIR_FREE(def); } @@ -998,6 +1000,28 @@ virStorageVolDefParseDoc(virConnectPtr conn, if (virStorageVolDefParsePerms(conn, ctxt, &ret->target.perms) < 0) goto cleanup; + + + ret->backingStore.path = virXPathString(conn, "string(/volume/backingStore/path)", ctxt); + if (options->formatFromString) { + char *format = virXPathString(conn, "string(/volume/backingStore/format/@type)", ctxt); + if (format == NULL) + ret->backingStore.format = options->defaultFormat; + else + ret->backingStore.format = (options->formatFromString)(format); + + if (ret->backingStore.format < 0) { + virStorageReportError(conn, VIR_ERR_XML_ERROR, + _("unknown volume format type %s"), format); + VIR_FREE(format); + goto cleanup; + } + VIR_FREE(format); + } + + if (virStorageVolDefParsePerms(conn, ctxt, &ret->backingStore.perms) < 0) + goto cleanup; + return ret; cleanup: @@ -1069,6 +1093,47 @@ virStorageVolDefParse(virConnectPtr conn, } +static int +virStorageVolTargetDefFormat(virConnectPtr conn, + virStorageVolOptionsPtr options, + virBufferPtr buf, + virStorageVolTargetPtr def, + const char *type) { + virBufferVSprintf(buf, " <%s>\n", type); + + if (def->path) + virBufferVSprintf(buf," %s\n", def->path); + + if (options->formatToString) { + const char *format = (options->formatToString)(def->format); + if (!format) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown volume format number %d"), + def->format); + return -1; + } + virBufferVSprintf(buf," \n", format); + } + + virBufferAddLit(buf," \n"); + virBufferVSprintf(buf," 0%o\n", + def->perms.mode); + virBufferVSprintf(buf," %d\n", + def->perms.uid); + virBufferVSprintf(buf," %d\n", + def->perms.gid); + + + if (def->perms.label) + virBufferVSprintf(buf," \n", + def->perms.label); + + virBufferAddLit(buf," \n"); + + virBufferVSprintf(buf, " \n", type); + + return 0; +} char * virStorageVolDefFormat(virConnectPtr conn, @@ -1116,37 +1181,15 @@ virStorageVolDefFormat(virConnectPtr conn, virBufferVSprintf(&buf," %llu\n", def->allocation); - virBufferAddLit(&buf, " \n"); + if (virStorageVolTargetDefFormat(conn, options, &buf, + &def->target, "target") < 0) + goto cleanup; - if (def->target.path) - virBufferVSprintf(&buf," %s\n", def->target.path); + if (def->backingStore.path && + virStorageVolTargetDefFormat(conn, options, &buf, + &def->backingStore, "backingStore") < 0) + goto cleanup; - if (options->formatToString) { - const char *format = (options->formatToString)(def->target.format); - if (!format) { - virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, - _("unknown volume format number %d"), - def->target.format); - goto cleanup; - } - virBufferVSprintf(&buf," \n", format); - } - - virBufferAddLit(&buf," \n"); - virBufferVSprintf(&buf," 0%o\n", - def->target.perms.mode); - virBufferVSprintf(&buf," %d\n", - def->target.perms.uid); - virBufferVSprintf(&buf," %d\n", - def->target.perms.gid); - - - if (def->target.perms.label) - virBufferVSprintf(&buf," \n", - def->target.perms.label); - - virBufferAddLit(&buf," \n"); - virBufferAddLit(&buf, " \n"); virBufferAddLit(&buf,"\n"); if (virBufferError(&buf)) diff --git a/src/storage_conf.h b/src/storage_conf.h index 7171823cf9..fc0fe0e787 100644 --- a/src/storage_conf.h +++ b/src/storage_conf.h @@ -89,6 +89,7 @@ struct _virStorageVolDef { virStorageVolSource source; virStorageVolTarget target; + virStorageVolTarget backingStore; }; typedef struct _virStorageVolDefList virStorageVolDefList;