aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog19
-rw-r--r--docs/formatstorage.html45
-rw-r--r--docs/formatstorage.html.in52
-rw-r--r--src/libvirt_private.syms1
-rw-r--r--src/storage_backend.c130
-rw-r--r--src/storage_backend.h13
-rw-r--r--src/storage_backend_fs.c337
-rw-r--r--src/storage_backend_iscsi.c6
-rw-r--r--src/storage_backend_logical.c68
-rw-r--r--src/storage_conf.c101
-rw-r--r--src/storage_conf.h1
11 files changed, 621 insertions, 152 deletions
diff --git a/ChangeLog b/ChangeLog
index 042d34599..4c1d4e31d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+Tue Jan 27 18:17:07 GMT 2009 Daniel P. Berrange <berrange@redhat.com>
+
+ 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 <meyering@redhat.com>
* POTFILES.in: update: remove src/lxc_conf.c; Add src/bridge.c.
diff --git a/docs/formatstorage.html b/docs/formatstorage.html
index e6ed2799b..bec32d10b 100644
--- 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 @@
...
&lt;target&gt;
&lt;path&gt;/var/lib/virt/images/sparse.img&lt;/path&gt;
+ &lt;format&gt;qcow2&lt;/format&gt;
&lt;permissions&gt;
&lt;owner&gt;0744&lt;/owner&gt;
&lt;group&gt;0744&lt;/group&gt;
&lt;mode&gt;0744&lt;/mode&gt;
&lt;label&gt;virt_image_t&lt;/label&gt;
&lt;/permissions&gt;
- &lt;/target&gt;
- &lt;/volume&gt;</pre>
+ &lt;/target&gt;</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>
+ ...
+ &lt;backingStore&gt;
+ &lt;path&gt;/var/lib/virt/images/master.img&lt;/path&gt;
+ &lt;format&gt;raw&lt;/format&gt;
+ &lt;permissions&gt;
+ &lt;owner&gt;0744&lt;/owner&gt;
+ &lt;group&gt;0744&lt;/group&gt;
+ &lt;mode&gt;0744&lt;/mode&gt;
+ &lt;label&gt;virt_image_t&lt;/label&gt;
+ &lt;/permissions&gt;
+ &lt;/backingStore&gt;
+ &lt;/volume&gt;</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
index 366339af1..2055f0427 100644
--- a/docs/formatstorage.html.in
+++ b/docs/formatstorage.html.in
@@ -234,14 +234,14 @@
...
&lt;target&gt;
&lt;path&gt;/var/lib/virt/images/sparse.img&lt;/path&gt;
+ &lt;format&gt;qcow2&lt;/format&gt;
&lt;permissions&gt;
&lt;owner&gt;0744&lt;/owner&gt;
&lt;group&gt;0744&lt;/group&gt;
&lt;mode&gt;0744&lt;/mode&gt;
&lt;label&gt;virt_image_t&lt;/label&gt;
&lt;/permissions&gt;
- &lt;/target&gt;
- &lt;/volume&gt;</pre>
+ &lt;/target&gt;</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>
+ ...
+ &lt;backingStore&gt;
+ &lt;path&gt;/var/lib/virt/images/master.img&lt;/path&gt;
+ &lt;format&gt;raw&lt;/format&gt;
+ &lt;permissions&gt;
+ &lt;owner&gt;0744&lt;/owner&gt;
+ &lt;group&gt;0744&lt;/group&gt;
+ &lt;mode&gt;0744&lt;/mode&gt;
+ &lt;label&gt;virt_image_t&lt;/label&gt;
+ &lt;/permissions&gt;
+ &lt;/backingStore&gt;
+ &lt;/volume&gt;</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
index 7b177063a..a6f4f3ee4 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 1bebbafdf..8408f3466 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 06f6979ad..7fab384bd 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 544f4e2f6..6fd9f8f81 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];
-
- if ((type = virStorageVolFormatFileSystemTypeToString(vol->target.format)) == NULL) {
+ 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 == 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 1d909c5c1..33f8507f1 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 474c3d80b..75a716403 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;
+ }
+ }
+
+ 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;
}
- strcpy(vol->target.path, pool->def->target.path);
- strcat(vol->target.path, "/");
- strcat(vol->target.path, vol->name);
+
+ 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 9d3f5a7a9..2f12e88d8 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," <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 conn,
virBufferVSprintf(&buf," <allocation>%llu</allocation>\n",
def->allocation);
- virBufferAddLit(&buf, " <target>\n");
-
- if (def->target.path)
- virBufferVSprintf(&buf," <path>%s</path>\n", def->target.path);
-
- 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 (virStorageVolTargetDefFormat(conn, options, &buf,
+ &def->target, "target") < 0)
+ goto cleanup;
- if (def->target.perms.label)
- virBufferVSprintf(&buf," <label>%s</label>\n",
- def->target.perms.label);
+ if (def->backingStore.path &&
+ virStorageVolTargetDefFormat(conn, options, &buf,
+ &def->backingStore, "backingStore") < 0)
+ goto cleanup;
- 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
index 7171823cf..fc0fe0e78 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;