mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-08-26 18:41:17 +00:00
Add support for copy-on-write storage volumes
This commit is contained in:
parent
765bca14d1
commit
489fde7680
19
ChangeLog
19
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>
|
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.
|
* POTFILES.in: update: remove src/lxc_conf.c; Add src/bridge.c.
|
||||||
|
@ -131,6 +131,8 @@
|
|||||||
<a href="#StorageVolFirst">General metadata</a>
|
<a href="#StorageVolFirst">General metadata</a>
|
||||||
</li><li>
|
</li><li>
|
||||||
<a href="#StorageVolTarget">Target elements</a>
|
<a href="#StorageVolTarget">Target elements</a>
|
||||||
|
</li><li>
|
||||||
|
<a href="#StorageVolBacking">Backing store elements</a>
|
||||||
</li></ul>
|
</li></ul>
|
||||||
</li><li>
|
</li><li>
|
||||||
<a href="#examples">Example configuration</a>
|
<a href="#examples">Example configuration</a>
|
||||||
@ -328,14 +330,14 @@
|
|||||||
...
|
...
|
||||||
<target>
|
<target>
|
||||||
<path>/var/lib/virt/images/sparse.img</path>
|
<path>/var/lib/virt/images/sparse.img</path>
|
||||||
|
<format>qcow2</format>
|
||||||
<permissions>
|
<permissions>
|
||||||
<owner>0744</owner>
|
<owner>0744</owner>
|
||||||
<group>0744</group>
|
<group>0744</group>
|
||||||
<mode>0744</mode>
|
<mode>0744</mode>
|
||||||
<label>virt_image_t</label>
|
<label>virt_image_t</label>
|
||||||
</permissions>
|
</permissions>
|
||||||
</target>
|
</target></pre>
|
||||||
</volume></pre>
|
|
||||||
<dl><dt><code>path</code></dt><dd>Provides the location at which the volume can be accessed on
|
<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
|
the local filesystem, as an absolute path. This is a readonly
|
||||||
attribute, so shouldn't be specified when creating a volume.
|
attribute, so shouldn't be specified when creating a volume.
|
||||||
@ -355,6 +357,45 @@
|
|||||||
element contains the numeric group ID. The <code>label</code> element
|
element contains the numeric group ID. The <code>label</code> element
|
||||||
contains the MAC (eg SELinux) label string.
|
contains the MAC (eg SELinux) label string.
|
||||||
<span class="since">Since 0.4.1</span>
|
<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>
|
</dd></dl>
|
||||||
<h2>
|
<h2>
|
||||||
<a name="examples" id="examples">Example configuration</a>
|
<a name="examples" id="examples">Example configuration</a>
|
||||||
|
@ -234,14 +234,14 @@
|
|||||||
...
|
...
|
||||||
<target>
|
<target>
|
||||||
<path>/var/lib/virt/images/sparse.img</path>
|
<path>/var/lib/virt/images/sparse.img</path>
|
||||||
|
<format>qcow2</format>
|
||||||
<permissions>
|
<permissions>
|
||||||
<owner>0744</owner>
|
<owner>0744</owner>
|
||||||
<group>0744</group>
|
<group>0744</group>
|
||||||
<mode>0744</mode>
|
<mode>0744</mode>
|
||||||
<label>virt_image_t</label>
|
<label>virt_image_t</label>
|
||||||
</permissions>
|
</permissions>
|
||||||
</target>
|
</target></pre>
|
||||||
</volume></pre>
|
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt><code>path</code></dt>
|
<dt><code>path</code></dt>
|
||||||
@ -271,6 +271,54 @@
|
|||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</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>
|
<h2><a name="examples">Example configuration</a></h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -259,6 +259,7 @@ virStoragePoolFormatDiskTypeToString;
|
|||||||
virStoragePoolFormatFileSystemTypeToString;
|
virStoragePoolFormatFileSystemTypeToString;
|
||||||
virStoragePoolFormatFileSystemNetTypeToString;
|
virStoragePoolFormatFileSystemNetTypeToString;
|
||||||
virStorageVolFormatFileSystemTypeToString;
|
virStorageVolFormatFileSystemTypeToString;
|
||||||
|
virStorageVolFormatFileSystemTypeFromString;
|
||||||
virStoragePoolTypeFromString;
|
virStoragePoolTypeFromString;
|
||||||
virStoragePoolObjLock;
|
virStoragePoolObjLock;
|
||||||
virStoragePoolObjUnlock;
|
virStoragePoolObjUnlock;
|
||||||
|
@ -99,29 +99,53 @@ virStorageBackendForType(int type) {
|
|||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
virStorageBackendUpdateVolInfo(virConnectPtr conn,
|
virStorageBackendUpdateVolTargetInfo(virConnectPtr conn,
|
||||||
virStorageVolDefPtr vol,
|
virStorageVolTargetPtr target,
|
||||||
int withCapacity)
|
unsigned long long *allocation,
|
||||||
|
unsigned long long *capacity)
|
||||||
{
|
{
|
||||||
int ret, fd;
|
int ret, fd;
|
||||||
|
|
||||||
if ((fd = open(vol->target.path, O_RDONLY)) < 0) {
|
if ((fd = open(target->path, O_RDONLY)) < 0) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot open volume '%s'"),
|
_("cannot open volume '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = virStorageBackendUpdateVolInfoFD(conn,
|
ret = virStorageBackendUpdateVolTargetInfoFD(conn,
|
||||||
vol,
|
target,
|
||||||
fd,
|
fd,
|
||||||
withCapacity);
|
allocation,
|
||||||
|
capacity);
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
return ret;
|
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 {
|
struct diskType {
|
||||||
int part_table_type;
|
int part_table_type;
|
||||||
unsigned short offset;
|
unsigned short offset;
|
||||||
@ -154,10 +178,11 @@ static struct diskType const disk_types[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn,
|
||||||
virStorageVolDefPtr vol,
|
virStorageVolTargetPtr target,
|
||||||
int fd,
|
int fd,
|
||||||
int withCapacity)
|
unsigned long long *allocation,
|
||||||
|
unsigned long long *capacity)
|
||||||
{
|
{
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
#if HAVE_SELINUX
|
#if HAVE_SELINUX
|
||||||
@ -167,7 +192,7 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
|||||||
if (fstat(fd, &sb) < 0) {
|
if (fstat(fd, &sb) < 0) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot stat file '%s'"),
|
_("cannot stat file '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,38 +201,41 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
|||||||
!S_ISBLK(sb.st_mode))
|
!S_ISBLK(sb.st_mode))
|
||||||
return -2;
|
return -2;
|
||||||
|
|
||||||
if (S_ISREG(sb.st_mode)) {
|
if (allocation) {
|
||||||
|
if (S_ISREG(sb.st_mode)) {
|
||||||
#ifndef __MINGW32__
|
#ifndef __MINGW32__
|
||||||
vol->allocation = (unsigned long long)sb.st_blocks *
|
*allocation = (unsigned long long)sb.st_blocks *
|
||||||
(unsigned long long)sb.st_blksize;
|
(unsigned long long)sb.st_blksize;
|
||||||
#else
|
#else
|
||||||
vol->allocation = sb.st_size;
|
*allocation = sb.st_size;
|
||||||
#endif
|
#endif
|
||||||
/* Regular files may be sparse, so logical size (capacity) is not same
|
/* Regular files may be sparse, so logical size (capacity) is not same
|
||||||
* as actual allocation above
|
* as actual allocation above
|
||||||
*/
|
*/
|
||||||
if (withCapacity)
|
if (capacity)
|
||||||
vol->capacity = sb.st_size;
|
*capacity = sb.st_size;
|
||||||
} else {
|
} else {
|
||||||
off_t end;
|
off_t end;
|
||||||
/* XXX this is POSIX compliant, but doesn't work for for CHAR files,
|
/* XXX this is POSIX compliant, but doesn't work for for CHAR files,
|
||||||
* only BLOCK. There is a Linux specific ioctl() for getting
|
* only BLOCK. There is a Linux specific ioctl() for getting
|
||||||
* size of both CHAR / BLOCK devices we should check for in
|
* size of both CHAR / BLOCK devices we should check for in
|
||||||
* configure
|
* configure
|
||||||
*/
|
*/
|
||||||
end = lseek(fd, 0, SEEK_END);
|
end = lseek(fd, 0, SEEK_END);
|
||||||
if (end == (off_t)-1) {
|
if (end == (off_t)-1) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot seek to end of file '%s'"),
|
_("cannot seek to end of file '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
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 */
|
/* 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)) {
|
if (S_ISBLK(sb.st_mode)) {
|
||||||
off_t start;
|
off_t start;
|
||||||
@ -219,14 +247,14 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
|||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot seek to beginning of file '%s'"),
|
_("cannot seek to beginning of file '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
bytes = saferead(fd, buffer, sizeof(buffer));
|
bytes = saferead(fd, buffer, sizeof(buffer));
|
||||||
if (bytes < 0) {
|
if (bytes < 0) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot read beginning of file '%s'"),
|
_("cannot read beginning of file '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,38 +263,38 @@ virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
|||||||
continue;
|
continue;
|
||||||
if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic,
|
if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic,
|
||||||
disk_types[i].length) == 0) {
|
disk_types[i].length) == 0) {
|
||||||
vol->target.format = disk_types[i].part_table_type;
|
target->format = disk_types[i].part_table_type;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vol->target.perms.mode = sb.st_mode;
|
target->perms.mode = sb.st_mode & S_IRWXUGO;
|
||||||
vol->target.perms.uid = sb.st_uid;
|
target->perms.uid = sb.st_uid;
|
||||||
vol->target.perms.gid = sb.st_gid;
|
target->perms.gid = sb.st_gid;
|
||||||
|
|
||||||
VIR_FREE(vol->target.perms.label);
|
VIR_FREE(target->perms.label);
|
||||||
|
|
||||||
#if HAVE_SELINUX
|
#if HAVE_SELINUX
|
||||||
if (fgetfilecon(fd, &filecon) == -1) {
|
if (fgetfilecon(fd, &filecon) == -1) {
|
||||||
if (errno != ENODATA && errno != ENOTSUP) {
|
if (errno != ENODATA && errno != ENOTSUP) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot get file context of '%s'"),
|
_("cannot get file context of '%s'"),
|
||||||
vol->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
vol->target.perms.label = NULL;
|
target->perms.label = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vol->target.perms.label = strdup(filecon);
|
target->perms.label = strdup(filecon);
|
||||||
if (vol->target.perms.label == NULL) {
|
if (target->perms.label == NULL) {
|
||||||
virReportOOMError(conn);
|
virReportOOMError(conn);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
freecon(filecon);
|
freecon(filecon);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
vol->target.perms.label = NULL;
|
target->perms.label = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -64,10 +64,15 @@ int virStorageBackendUpdateVolInfo(virConnectPtr conn,
|
|||||||
virStorageVolDefPtr vol,
|
virStorageVolDefPtr vol,
|
||||||
int withCapacity);
|
int withCapacity);
|
||||||
|
|
||||||
int virStorageBackendUpdateVolInfoFD(virConnectPtr conn,
|
int virStorageBackendUpdateVolTargetInfo(virConnectPtr conn,
|
||||||
virStorageVolDefPtr vol,
|
virStorageVolTargetPtr target,
|
||||||
int fd,
|
unsigned long long *allocation,
|
||||||
int withCapacity);
|
unsigned long long *capacity);
|
||||||
|
int virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn,
|
||||||
|
virStorageVolTargetPtr target,
|
||||||
|
int fd,
|
||||||
|
unsigned long long *allocation,
|
||||||
|
unsigned long long *capacity);
|
||||||
|
|
||||||
void virStorageBackendWaitForDevices(virConnectPtr conn);
|
void virStorageBackendWaitForDevices(virConnectPtr conn);
|
||||||
|
|
||||||
|
@ -49,6 +49,19 @@ enum lv_endian {
|
|||||||
LV_BIG_ENDIAN /* 4321 */
|
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 */
|
/* Either 'magic' or 'extension' *must* be provided */
|
||||||
struct FileTypeInfo {
|
struct FileTypeInfo {
|
||||||
int type; /* One of the constants above */
|
int type; /* One of the constants above */
|
||||||
@ -65,85 +78,228 @@ struct FileTypeInfo {
|
|||||||
* -1 to use st_size as capacity */
|
* -1 to use st_size as capacity */
|
||||||
int sizeBytes; /* Number of bytes for size field */
|
int sizeBytes; /* Number of bytes for size field */
|
||||||
int sizeMultiplier; /* A scaling factor if size is not in bytes */
|
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[] = {
|
const struct FileTypeInfo const fileTypeInfo[] = {
|
||||||
/* Bochs */
|
/* Bochs */
|
||||||
/* XXX Untested
|
/* XXX Untested
|
||||||
{ VIR_STORAGE_VOL_BOCHS, "Bochs Virtual HD Image", NULL,
|
{ VIR_STORAGE_VOL_BOCHS, "Bochs Virtual HD Image", NULL,
|
||||||
LV_LITTLE_ENDIAN, 64, 0x20000,
|
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 */
|
/* CLoop */
|
||||||
/* XXX Untested
|
/* 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,
|
{ 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,
|
LV_LITTLE_ENDIAN, -1, 0,
|
||||||
-1, 0, 0 }, */
|
-1, 0, 0, NULL }, */
|
||||||
/* Cow */
|
/* Cow */
|
||||||
{ VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL,
|
{ VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL,
|
||||||
LV_BIG_ENDIAN, 4, 2,
|
LV_BIG_ENDIAN, 4, 2,
|
||||||
4+4+1024+4, 8, 1 },
|
4+4+1024+4, 8, 1, cowGetBackingStore },
|
||||||
/* DMG */
|
/* DMG */
|
||||||
/* XXX QEMU says there's no magic for dmg, but we should check... */
|
/* XXX QEMU says there's no magic for dmg, but we should check... */
|
||||||
{ VIR_STORAGE_VOL_FILE_DMG, NULL, ".dmg",
|
{ VIR_STORAGE_VOL_FILE_DMG, NULL, ".dmg",
|
||||||
0, -1, 0,
|
0, -1, 0,
|
||||||
-1, 0, 0 },
|
-1, 0, 0, NULL },
|
||||||
/* XXX there's probably some magic for iso we can validate too... */
|
/* XXX there's probably some magic for iso we can validate too... */
|
||||||
{ VIR_STORAGE_VOL_FILE_ISO, NULL, ".iso",
|
{ VIR_STORAGE_VOL_FILE_ISO, NULL, ".iso",
|
||||||
0, -1, 0,
|
0, -1, 0,
|
||||||
-1, 0, 0 },
|
-1, 0, 0, NULL },
|
||||||
/* Parallels */
|
/* Parallels */
|
||||||
/* XXX Untested
|
/* XXX Untested
|
||||||
{ VIR_STORAGE_VOL_FILE_PARALLELS, "WithoutFreeSpace", NULL,
|
{ VIR_STORAGE_VOL_FILE_PARALLELS, "WithoutFreeSpace", NULL,
|
||||||
LV_LITTLE_ENDIAN, 16, 2,
|
LV_LITTLE_ENDIAN, 16, 2,
|
||||||
16+4+4+4+4, 4, 512 },
|
16+4+4+4+4, 4, 512, NULL },
|
||||||
*/
|
*/
|
||||||
/* QCow */
|
/* QCow */
|
||||||
{ VIR_STORAGE_VOL_FILE_QCOW, "QFI", NULL,
|
{ VIR_STORAGE_VOL_FILE_QCOW, "QFI", NULL,
|
||||||
LV_BIG_ENDIAN, 4, 1,
|
LV_BIG_ENDIAN, 4, 1,
|
||||||
4+4+8+4+4, 8, 1 },
|
4+4+8+4+4, 8, 1, qcowXGetBackingStore },
|
||||||
/* QCow 2 */
|
/* QCow 2 */
|
||||||
{ VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL,
|
{ VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL,
|
||||||
LV_BIG_ENDIAN, 4, 2,
|
LV_BIG_ENDIAN, 4, 2,
|
||||||
4+4+8+4+4, 8, 1 },
|
4+4+8+4+4, 8, 1, qcowXGetBackingStore },
|
||||||
/* VMDK 3 */
|
/* VMDK 3 */
|
||||||
/* XXX Untested
|
/* XXX Untested
|
||||||
{ VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL,
|
{ VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL,
|
||||||
LV_LITTLE_ENDIAN, 4, 1,
|
LV_LITTLE_ENDIAN, 4, 1,
|
||||||
4+4+4, 4, 512 },
|
4+4+4, 4, 512, NULL },
|
||||||
*/
|
*/
|
||||||
/* VMDK 4 */
|
/* VMDK 4 */
|
||||||
{ VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL,
|
{ VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL,
|
||||||
LV_LITTLE_ENDIAN, 4, 1,
|
LV_LITTLE_ENDIAN, 4, 1,
|
||||||
4+4+4, 8, 512 },
|
4+4+4, 8, 512, vmdk4GetBackingStore },
|
||||||
/* Connectix / VirtualPC */
|
/* Connectix / VirtualPC */
|
||||||
/* XXX Untested
|
/* XXX Untested
|
||||||
{ VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL,
|
{ VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL,
|
||||||
LV_BIG_ENDIAN, -1, 0,
|
LV_BIG_ENDIAN, -1, 0,
|
||||||
-1, 0, 0},
|
-1, 0, 0, NULL},
|
||||||
*/
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
#define VIR_FROM_THIS VIR_FROM_STORAGE
|
#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
|
* Probe the header of a file to determine what type of disk image
|
||||||
* it is, and info about its capacity if available.
|
* it is, and info about its capacity if available.
|
||||||
*/
|
*/
|
||||||
static int virStorageBackendProbeFile(virConnectPtr conn,
|
static int virStorageBackendProbeTarget(virConnectPtr conn,
|
||||||
virStorageVolDefPtr def) {
|
virStorageVolTargetPtr target,
|
||||||
|
char **backingStore,
|
||||||
|
unsigned long long *allocation,
|
||||||
|
unsigned long long *capacity) {
|
||||||
int fd;
|
int fd;
|
||||||
unsigned char head[4096];
|
unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */
|
||||||
int len, i, ret;
|
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,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot open volume '%s'"),
|
_("cannot open volume '%s'"),
|
||||||
def->target.path);
|
target->path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((ret = virStorageBackendUpdateVolInfoFD(conn, def, fd, 1)) < 0) {
|
if ((ret = virStorageBackendUpdateVolTargetInfoFD(conn, target, fd,
|
||||||
|
allocation,
|
||||||
|
capacity)) < 0) {
|
||||||
close(fd);
|
close(fd);
|
||||||
return ret; /* Take care to propagate ret, it is not always -1 */
|
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) {
|
if ((len = read(fd, head, sizeof(head))) < 0) {
|
||||||
virReportSystemError(conn, errno,
|
virReportSystemError(conn, errno,
|
||||||
_("cannot read header '%s'"),
|
_("cannot read header '%s'"),
|
||||||
def->target.path);
|
target->path);
|
||||||
close(fd);
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -191,9 +347,9 @@ static int virStorageBackendProbeFile(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Optionally extract capacity from file */
|
/* Optionally extract capacity from file */
|
||||||
if (fileTypeInfo[i].sizeOffset != -1) {
|
if (fileTypeInfo[i].sizeOffset != -1 && capacity) {
|
||||||
if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) {
|
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+7] << 56) |
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) |
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) |
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) |
|
((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+1] << 8) |
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset]);
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset]);
|
||||||
} else {
|
} else {
|
||||||
def->capacity =
|
*capacity =
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) |
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) |
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) |
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) |
|
||||||
((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) |
|
((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]);
|
((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]);
|
||||||
}
|
}
|
||||||
/* Avoid unlikely, but theoretically possible overflow */
|
/* Avoid unlikely, but theoretically possible overflow */
|
||||||
if (def->capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier))
|
if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier))
|
||||||
continue;
|
continue;
|
||||||
def->capacity *= fileTypeInfo[i].sizeMultiplier;
|
*capacity *= fileTypeInfo[i].sizeMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validation passed, we know the file format now */
|
/* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,15 +409,15 @@ static int virStorageBackendProbeFile(virConnectPtr conn,
|
|||||||
if (fileTypeInfo[i].extension == NULL)
|
if (fileTypeInfo[i].extension == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!virFileHasSuffix(def->target.path, fileTypeInfo[i].extension))
|
if (!virFileHasSuffix(target->path, fileTypeInfo[i].extension))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
def->target.format = fileTypeInfo[i].type;
|
target->format = fileTypeInfo[i].type;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* All fails, so call it a raw file */
|
/* 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,6 +816,7 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn,
|
|||||||
|
|
||||||
while ((ent = readdir(dir)) != NULL) {
|
while ((ent = readdir(dir)) != NULL) {
|
||||||
int ret;
|
int ret;
|
||||||
|
char *backingStore;
|
||||||
|
|
||||||
if (VIR_ALLOC(vol) < 0)
|
if (VIR_ALLOC(vol) < 0)
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
@ -655,7 +836,11 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn,
|
|||||||
if ((vol->key = strdup(vol->target.path)) == NULL)
|
if ((vol->key = strdup(vol->target.path)) == NULL)
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
|
|
||||||
if ((ret = virStorageBackendProbeFile(conn, vol) < 0)) {
|
if ((ret = virStorageBackendProbeTarget(conn,
|
||||||
|
&vol->target,
|
||||||
|
&backingStore,
|
||||||
|
&vol->allocation,
|
||||||
|
&vol->capacity) < 0)) {
|
||||||
if (ret == -1)
|
if (ret == -1)
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
else {
|
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,
|
if (VIR_REALLOC_N(pool->volumes.objs,
|
||||||
pool->volumes.count+1) < 0)
|
pool->volumes.count+1) < 0)
|
||||||
goto no_memory;
|
goto no_memory;
|
||||||
@ -836,28 +1063,48 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if HAVE_QEMU_IMG
|
#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];
|
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,
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
_("unknown storage vol type %d"),
|
_("unknown storage vol type %d"),
|
||||||
vol->target.format);
|
vol->target.format);
|
||||||
return -1;
|
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 */
|
/* Size in KB */
|
||||||
snprintf(size, sizeof(size), "%llu", vol->capacity/1024);
|
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) {
|
if (virRun(conn, imgargv, NULL) < 0) {
|
||||||
unlink(vol->target.path);
|
unlink(vol->target.path);
|
||||||
return -1;
|
return -1;
|
||||||
@ -884,6 +1131,12 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn,
|
|||||||
vol->target.format);
|
vol->target.format);
|
||||||
return -1;
|
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 :-( */
|
/* Size in MB - yes different units to qemu-img :-( */
|
||||||
snprintf(size, sizeof(size), "%llu", vol->capacity/1024/1024);
|
snprintf(size, sizeof(size), "%llu", vol->capacity/1024/1024);
|
||||||
@ -934,7 +1187,9 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Refresh allocation / permissions info, but not capacity */
|
/* 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);
|
unlink(vol->target.path);
|
||||||
close(fd);
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -228,7 +228,11 @@ virStorageBackendISCSINewLun(virConnectPtr conn, virStoragePoolObjPtr pool,
|
|||||||
|
|
||||||
VIR_FREE(devpath);
|
VIR_FREE(devpath);
|
||||||
|
|
||||||
if (virStorageBackendUpdateVolInfoFD(conn, vol, fd, 1) < 0)
|
if (virStorageBackendUpdateVolTargetInfoFD(conn,
|
||||||
|
&vol->target,
|
||||||
|
fd,
|
||||||
|
&vol->allocation,
|
||||||
|
&vol->capacity) < 0)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
|
||||||
/* XXX use unique iSCSI id instead */
|
/* XXX use unique iSCSI id instead */
|
||||||
|
@ -106,18 +106,27 @@ virStorageBackendLogicalMakeVol(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (vol->target.path == NULL) {
|
if (vol->target.path == NULL) {
|
||||||
if (VIR_ALLOC_N(vol->target.path, strlen(pool->def->target.path) +
|
if (virAsprintf(&vol->target.path, "%s/%s",
|
||||||
1 + strlen(vol->name) + 1) < 0) {
|
pool->def->target.path, vol->name) < 0) {
|
||||||
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume"));
|
virReportOOMError(conn);
|
||||||
|
virStorageVolDefFree(vol);
|
||||||
return -1;
|
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 &&
|
if (vol->key == NULL &&
|
||||||
(vol->key = strdup(groups[1])) == NULL) {
|
(vol->key = strdup(groups[2])) == NULL) {
|
||||||
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume"));
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("volume"));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -134,22 +143,22 @@ virStorageBackendLogicalMakeVol(virConnectPtr conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((vol->source.extents[vol->source.nextent].path =
|
if ((vol->source.extents[vol->source.nextent].path =
|
||||||
strdup(groups[2])) == NULL) {
|
strdup(groups[3])) == NULL) {
|
||||||
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("extents"));
|
virStorageReportError(conn, VIR_ERR_NO_MEMORY, "%s", _("extents"));
|
||||||
return -1;
|
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,
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
"%s", _("malformed volume extent offset value"));
|
"%s", _("malformed volume extent offset value"));
|
||||||
return -1;
|
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,
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
"%s", _("malformed volume extent length value"));
|
"%s", _("malformed volume extent length value"));
|
||||||
return -1;
|
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,
|
virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR,
|
||||||
"%s", _("malformed volume extent size value"));
|
"%s", _("malformed volume extent size value"));
|
||||||
return -1;
|
return -1;
|
||||||
@ -168,14 +177,14 @@ virStorageBackendLogicalFindLVs(virConnectPtr conn,
|
|||||||
virStorageVolDefPtr vol)
|
virStorageVolDefPtr vol)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* # lvs --separator , --noheadings --units b --unbuffered --nosuffix --options "lv_name,uuid,devices,seg_size,vg_extent_size" VGNAME
|
* # 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
|
* 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
|
* 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
|
* 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(251),2181038080,33554432
|
||||||
* Test3,UB5hFw-kmlm-LSoX-EI1t-ioVd-h7GL-M0W8Ht,/dev/hda2(187),1040187392,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
|
* 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).
|
* not a suitable separator (rhbz 470693).
|
||||||
*/
|
*/
|
||||||
const char *regexes[] = {
|
const char *regexes[] = {
|
||||||
"^\\s*(\\S+),(\\S+),(\\S+)\\((\\S+)\\),(\\S+),([0-9]+),?\\s*$"
|
"^\\s*(\\S+),(\\S*),(\\S+),(\\S+)\\((\\S+)\\),(\\S+),([0-9]+),?\\s*$"
|
||||||
};
|
};
|
||||||
int vars[] = {
|
int vars[] = {
|
||||||
6
|
7
|
||||||
};
|
};
|
||||||
const char *prog[] = {
|
const char *prog[] = {
|
||||||
LVS, "--separator", ",", "--noheadings", "--units", "b",
|
LVS, "--separator", ",", "--noheadings", "--units", "b",
|
||||||
"--unbuffered", "--nosuffix", "--options",
|
"--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
|
pool->def->source.name, NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -565,10 +574,25 @@ virStorageBackendLogicalCreateVol(virConnectPtr conn,
|
|||||||
{
|
{
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
char size[100];
|
char size[100];
|
||||||
const char *cmdargv[] = {
|
const char *cmdargvnew[] = {
|
||||||
LVCREATE, "--name", vol->name, "-L", size,
|
LVCREATE, "--name", vol->name, "-L", size,
|
||||||
pool->def->target.path, NULL
|
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);
|
snprintf(size, sizeof(size)-1, "%lluK", vol->capacity/1024);
|
||||||
size[sizeof(size)-1] = '\0';
|
size[sizeof(size)-1] = '\0';
|
||||||
|
@ -249,6 +249,8 @@ virStorageVolDefFree(virStorageVolDefPtr def) {
|
|||||||
|
|
||||||
VIR_FREE(def->target.path);
|
VIR_FREE(def->target.path);
|
||||||
VIR_FREE(def->target.perms.label);
|
VIR_FREE(def->target.perms.label);
|
||||||
|
VIR_FREE(def->backingStore.path);
|
||||||
|
VIR_FREE(def->backingStore.perms.label);
|
||||||
VIR_FREE(def);
|
VIR_FREE(def);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -998,6 +1000,28 @@ virStorageVolDefParseDoc(virConnectPtr conn,
|
|||||||
if (virStorageVolDefParsePerms(conn, ctxt, &ret->target.perms) < 0)
|
if (virStorageVolDefParsePerms(conn, ctxt, &ret->target.perms) < 0)
|
||||||
goto cleanup;
|
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;
|
return ret;
|
||||||
|
|
||||||
cleanup:
|
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 *
|
char *
|
||||||
virStorageVolDefFormat(virConnectPtr conn,
|
virStorageVolDefFormat(virConnectPtr conn,
|
||||||
@ -1116,37 +1181,15 @@ virStorageVolDefFormat(virConnectPtr conn,
|
|||||||
virBufferVSprintf(&buf," <allocation>%llu</allocation>\n",
|
virBufferVSprintf(&buf," <allocation>%llu</allocation>\n",
|
||||||
def->allocation);
|
def->allocation);
|
||||||
|
|
||||||
virBufferAddLit(&buf, " <target>\n");
|
if (virStorageVolTargetDefFormat(conn, options, &buf,
|
||||||
|
&def->target, "target") < 0)
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
if (def->target.path)
|
if (def->backingStore.path &&
|
||||||
virBufferVSprintf(&buf," <path>%s</path>\n", def->target.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");
|
virBufferAddLit(&buf,"</volume>\n");
|
||||||
|
|
||||||
if (virBufferError(&buf))
|
if (virBufferError(&buf))
|
||||||
|
@ -89,6 +89,7 @@ struct _virStorageVolDef {
|
|||||||
|
|
||||||
virStorageVolSource source;
|
virStorageVolSource source;
|
||||||
virStorageVolTarget target;
|
virStorageVolTarget target;
|
||||||
|
virStorageVolTarget backingStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct _virStorageVolDefList virStorageVolDefList;
|
typedef struct _virStorageVolDefList virStorageVolDefList;
|
||||||
|
Loading…
Reference in New Issue
Block a user