This is a follow up on Miloslav's proposal to add copy on write support to the storage APIs, changing the XML to that described here: http://www.redhat.com/archives/libvir-list/2009-January/msg00231.html In addition to the original QCOW/VMDK support, I have done an impl which can extract the backing store for LVM volumes docs/formatstorage.html | 45 +++++ docs/formatstorage.html.in | 52 ++++++ src/libvirt_private.syms | 1 src/storage_backend.c | 140 ++++++++++------- src/storage_backend.h | 13 + src/storage_backend_fs.c | 336 +++++++++++++++++++++++++++++++++++++----- src/storage_backend_iscsi.c | 6 src/storage_backend_logical.c | 61 +++++-- src/storage_conf.c | 101 +++++++++--- src/storage_conf.h | 1 10 files changed, 609 insertions(+), 147 deletions(-) Daniel diff --git a/docs/formatstorage.html b/docs/formatstorage.html --- a/docs/formatstorage.html +++ b/docs/formatstorage.html @@ -131,6 +131,8 @@ <a href="#StorageVolFirst">General metadata</a> </li><li> <a href="#StorageVolTarget">Target elements</a> + </li><li> + <a href="#StorageVolBacking">Backing store elements</a> </li></ul> </li><li> <a href="#examples">Example configuration</a> @@ -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></pre> + </target></pre> <dl><dt><code>path</code></dt><dd>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. @@ -356,6 +358,45 @@ contains the MAC (eg SELinux) label string. <span class="since">Since 0.4.1</span> </dd></dl> + <h3> + <a name="StorageVolBacking" id="StorageVolBacking">Backing store elements</a> + </h3> + <p> + A single <code>backingStore</code> element is contained within the top level + <code>volume</code> 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: + </p> + <pre> + ... + <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></pre> + <dl><dt><code>path</code></dt><dd>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. + <span class="since">Since 0.6.0</span></dd><dt><code>format</code></dt><dd>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. + <span class="since">Since 0.6.0</span></dd><dt><code>permissions</code></dt><dd>Provides information about the permissions of the backing file. + It contains 4 child elements. The + <code>mode</code> element contains the octal permission set. The + <code>owner</code> element contains the numeric user ID. The <code>group</code> + element contains the numeric group ID. The <code>label</code> element + contains the MAC (eg SELinux) label string. + <span class="since">Since 0.6.0</span> + </dd></dl> <h2> <a name="examples" id="examples">Example configuration</a> </h2> diff --git a/docs/formatstorage.html.in b/docs/formatstorage.html.in --- 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></pre> + </target></pre> <dl> <dt><code>path</code></dt> @@ -271,6 +271,54 @@ </dd> </dl> + <h3><a name="StorageVolBacking">Backing store elements</a></h3> + + <p> + A single <code>backingStore</code> element is contained within the top level + <code>volume</code> 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: + </p> + + <pre> + ... + <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></pre> + + <dl> + <dt><code>path</code></dt> + <dd>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. + <span class="since">Since 0.6.0</span></dd> + <dt><code>format</code></dt> + <dd>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. + <span class="since">Since 0.6.0</span></dd> + <dt><code>permissions</code></dt> + <dd>Provides information about the permissions of the backing file. + It contains 4 child elements. The + <code>mode</code> element contains the octal permission set. The + <code>owner</code> element contains the numeric user ID. The <code>group</code> + element contains the numeric group ID. The <code>label</code> element + contains the MAC (eg SELinux) label string. + <span class="since">Since 0.6.0</span> + </dd> + </dl> + <h2><a name="examples">Example configuration</a></h2> <p> diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -258,6 +258,7 @@ virStoragePoolFormatDiskTypeToString; virStoragePoolFormatFileSystemTypeToString; virStoragePoolFormatFileSystemNetTypeToString; virStorageVolFormatFileSystemTypeToString; +virStorageVolFormatFileSystemTypeFromString; virStoragePoolTypeFromString; virStoragePoolObjLock; virStoragePoolObjUnlock; diff --git a/src/storage_backend.c b/src/storage_backend.c --- a/src/storage_backend.c +++ b/src/storage_backend.c @@ -99,27 +99,51 @@ virStorageBackendForType(int type) { int +virStorageBackendUpdateVolTargetInfo(virConnectPtr conn, + virStorageVolTargetPtr target, + unsigned long long *allocation, + unsigned long long *capacity) +{ + int ret, fd; + + if ((fd = open(target->path, O_RDONLY)) < 0) { + virReportSystemError(conn, errno, + _("cannot open volume '%s'"), + target->path); + return -1; + } + + ret = virStorageBackendUpdateVolTargetInfoFD(conn, + target, + fd, + allocation, + capacity); + + close(fd); + + return ret; +} + +int virStorageBackendUpdateVolInfo(virConnectPtr conn, virStorageVolDefPtr vol, int withCapacity) { - int ret, fd; + int ret; - if ((fd = open(vol->target.path, O_RDONLY)) < 0) { - virReportSystemError(conn, errno, - _("cannot open volume '%s'"), - vol->target.path); - return -1; - } + if ((ret = virStorageBackendUpdateVolTargetInfo(conn, + &vol->target, + &vol->allocation, + withCapacity ? &vol->capacity : NULL)) < 0) + return ret; - ret = virStorageBackendUpdateVolInfoFD(conn, - vol, - fd, - withCapacity); + if (vol->backingStore.path && + (ret = virStorageBackendUpdateVolTargetInfo(conn, + &vol->backingStore, + NULL, NULL)) < 0) + return ret; - close(fd); - - return ret; + return 0; } struct diskType { @@ -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(virConn if (fstat(fd, &sb) < 0) { virReportSystemError(conn, errno, _("cannot stat file '%s'"), - vol->target.path); + target->path); return -1; } @@ -176,38 +201,41 @@ virStorageBackendUpdateVolInfoFD(virConn !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(virConn 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(virConn 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 & (0x200-1); + 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 --- a/src/storage_backend.h +++ b/src/storage_backend.h @@ -64,10 +64,15 @@ int virStorageBackendUpdateVolInfo(virCo 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 --- 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,234 @@ 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) +{ + size_t len; + + *res = NULL; + if (buf_size < 4+4+1024) + return BACKING_STORE_INVALID; + if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */ + return BACKING_STORE_OK; + + len = 1024; + if (VIR_ALLOC_N(*res, len + 1) < 0) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, _("backing store path")); + return BACKING_STORE_ERROR; + } + memcpy(*res, buf + 4+4, len); /* cow_header_v2.backing_file */ + (*res)[len] = '\0'; + if (VIR_REALLOC_N(*res, strlen(*res) + 1) < 0) { + /* Ignore failure */ + } + 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 +313,7 @@ static int virStorageBackendProbeFile(vi 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 +353,9 @@ static int virStorageBackendProbeFile(vi } /* 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 +365,7 @@ static int virStorageBackendProbeFile(vi ((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 +376,37 @@ static int virStorageBackendProbeFile(vi ((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 +415,15 @@ static int virStorageBackendProbeFile(vi 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 +822,7 @@ virStorageBackendFileSystemRefresh(virCo while ((ent = readdir(dir)) != NULL) { int ret; + char *backingStore; if (VIR_ALLOC(vol) < 0) goto no_memory; @@ -655,7 +842,11 @@ virStorageBackendFileSystemRefresh(virCo 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 +858,48 @@ virStorageBackendFileSystemRefresh(virCo } } + 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; @@ -837,8 +1070,10 @@ virStorageBackendFileSystemVolCreate(vir } else { #if HAVE_QEMU_IMG const char *type; + const char *backingType = NULL; char size[100]; - const char *imgargv[7]; + const char *imgargv[9]; + size_t i; if ((type = virStorageVolFormatFileSystemTypeToString(vol->target.format)) == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, @@ -846,17 +1081,36 @@ virStorageBackendFileSystemVolCreate(vir vol->target.format); return -1; } + if (vol->backingStore.path) { + if ((backingType = virStorageVolFormatFileSystemTypeToString(vol->backingStore.format)) == 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; + } + } /* 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; + i = 0; + imgargv[i++] = QEMU_IMG; + imgargv[i++] = "create"; + imgargv[i++] = "-f"; + imgargv[i++] = type; + if (vol->backingStore.path != NULL) { + imgargv[i++] = "-b"; + imgargv[i++] = vol->backingStore.path; + } + imgargv[i++] = vol->target.path; + imgargv[i++] = size; + imgargv[i++] = NULL; if (virRun(conn, imgargv, NULL) < 0) { unlink(vol->target.path); @@ -884,6 +1138,12 @@ virStorageBackendFileSystemVolCreate(vir 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 +1194,9 @@ virStorageBackendFileSystemVolCreate(vir } /* 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 --- a/src/storage_backend_iscsi.c +++ b/src/storage_backend_iscsi.c @@ -226,7 +226,11 @@ virStorageBackendISCSINewLun(virConnectP 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 --- a/src/storage_backend_logical.c +++ b/src/storage_backend_logical.c @@ -116,8 +116,22 @@ virStorageBackendLogicalMakeVol(virConne strcat(vol->target.path, vol->name); } + if (groups[1] && !STREQ(groups[1], "")) { + if (VIR_ALLOC_N(vol->backingStore.path, strlen(pool->def->target.path) + + 1 + strlen(groups[1]) + 1) < 0) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume")); + return -1; + } + strcpy(vol->backingStore.path, pool->def->target.path); + strcat(vol->backingStore.path, "/"); + strcat(vol->backingStore.path, groups[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 +148,22 @@ virStorageBackendLogicalMakeVol(virConne } 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 +182,14 @@ virStorageBackendLogicalFindLVs(virConne 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 +199,15 @@ virStorageBackendLogicalFindLVs(virConne * 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 +579,25 @@ virStorageBackendLogicalCreateVol(virCon { 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 --- a/src/storage_conf.c +++ b/src/storage_conf.c @@ -249,6 +249,8 @@ virStorageVolDefFree(virStorageVolDefPtr 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 c 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," <path>%s</path>\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," <format type='%s'/>\n", format); + } + + virBufferAddLit(buf," <permissions>\n"); + virBufferVSprintf(buf," <mode>0%o</mode>\n", + def->perms.mode); + virBufferVSprintf(buf," <owner>%d</owner>\n", + def->perms.uid); + virBufferVSprintf(buf," <group>%d</group>\n", + def->perms.gid); + + + if (def->perms.label) + virBufferVSprintf(buf," <label>%s</label>\n", + def->perms.label); + + virBufferAddLit(buf," </permissions>\n"); + + virBufferVSprintf(buf, " </%s>\n", type); + + return 0; +} char * virStorageVolDefFormat(virConnectPtr conn, @@ -1116,37 +1181,15 @@ virStorageVolDefFormat(virConnectPtr con virBufferVSprintf(&buf," <allocation>%llu</allocation>\n", def->allocation); - virBufferAddLit(&buf, " <target>\n"); + if (virStorageVolTargetDefFormat(conn, options, &buf, + &def->target, "target") < 0) + goto cleanup; - if (def->target.path) - virBufferVSprintf(&buf," <path>%s</path>\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," <format type='%s'/>\n", format); - } - - virBufferAddLit(&buf," <permissions>\n"); - virBufferVSprintf(&buf," <mode>0%o</mode>\n", - def->target.perms.mode); - virBufferVSprintf(&buf," <owner>%d</owner>\n", - def->target.perms.uid); - virBufferVSprintf(&buf," <group>%d</group>\n", - def->target.perms.gid); - - - if (def->target.perms.label) - virBufferVSprintf(&buf," <label>%s</label>\n", - def->target.perms.label); - - virBufferAddLit(&buf," </permissions>\n"); - virBufferAddLit(&buf, " </target>\n"); virBufferAddLit(&buf,"</volume>\n"); if (virBufferError(&buf)) diff --git a/src/storage_conf.h b/src/storage_conf.h --- 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; -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :| -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list