Hello, the attached patch adds support for copy-on-write images: creating them, deleting them, identifying them. Mirek
Index: ChangeLog =================================================================== RCS file: /data/cvs/libvirt/ChangeLog,v retrieving revision 1.1750 diff -u -p -r1.1750 ChangeLog --- ChangeLog 8 Dec 2008 13:22:44 -0000 1.1750 +++ ChangeLog 9 Dec 2008 17:25:18 -0000 @@ -1,3 +1,10 @@ +2008-12-09 Miloslav Trmač <mitr@xxxxxxxxxx> + + * src/storage_conf.h, src/storage_conf.c: New volume target option + cow-base. + * src/storage_backend_fs.c: Implement /volume/target/cow-base. + * docs/formatstorage.html.in: Document it. + Mon Dec 8 13:22:06 +0100 2008 Jim Meyering <meyering@xxxxxxxxxx> virsh.c: tweak options to produce more accurate help Index: docs/formatstorage.html.in =================================================================== RCS file: /data/cvs/libvirt/docs/formatstorage.html.in,v retrieving revision 1.5 diff -u -p -r1.5 formatstorage.html.in --- docs/formatstorage.html.in 4 Dec 2008 14:51:57 -0000 1.5 +++ docs/formatstorage.html.in 9 Dec 2008 17:25:18 -0000 @@ -269,6 +269,13 @@ contains the MAC (eg SELinux) label string. <span class="since">Since 0.4.1</span> </dd> + <dt><code>cow-base</code></dt> + <dd>Provides the key of a base volume. This element is optional, and + supported only in some volume types and formats. If it is present, + this volume is a copy-on-write volume that stores only changes to the + base volume. Changes to the base volume may make this volume + inconsistent. + </dd> </dl> <h2><a name="examples">Example configuration</a></h2> Index: src/storage_backend_fs.c =================================================================== RCS file: /data/cvs/libvirt/src/storage_backend_fs.c,v retrieving revision 1.22 diff -u -p -r1.22 storage_backend_fs.c --- src/storage_backend_fs.c 17 Nov 2008 11:19:33 -0000 1.22 +++ src/storage_backend_fs.c 9 Dec 2008 17:25:18 -0000 @@ -48,6 +48,18 @@ #include "xml.h" +enum { + GET_COW_BASE_OK, + GET_COW_BASE_INVALID, + GET_COW_BASE_ERROR, +}; + +static int cowGetCowBase(virConnectPtr, char **, const unsigned char *, size_t); +static int qcowXGetCowBase(virConnectPtr, char **, const unsigned char *, + size_t); +static int vmdk4GetCowBase(virConnectPtr, char **, const unsigned char *, + size_t); + /* Either 'magic' or 'extension' *must* be provided */ struct FileTypeInfo { int type; /* One of the constants above */ @@ -64,66 +76,225 @@ 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 GET_COW_BASE_* */ + int (*getCowBase)(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, __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, __LITTLE_ENDIAN, -1, 0, - -1, 0, 0 }, */ + -1, 0, 0, NULL }, */ /* Cow */ { VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL, __BIG_ENDIAN, 4, 2, - 4+4+1024+4, 8, 1 }, + 4+4+1024+4, 8, 1, cowGetCowBase }, /* 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, __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, __BIG_ENDIAN, 4, 1, - 4+4+8+4+4, 8, 1 }, + 4+4+8+4+4, 8, 1, qcowXGetCowBase }, /* QCow 2 */ { VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL, __BIG_ENDIAN, 4, 2, - 4+4+8+4+4, 8, 1 }, + 4+4+8+4+4, 8, 1, qcowXGetCowBase }, /* VMDK 3 */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL, __LITTLE_ENDIAN, 4, 1, - 4+4+4, 4, 512 }, + 4+4+4, 4, 512, NULL }, */ /* VMDK 4 */ { VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL, __LITTLE_ENDIAN, 4, 1, - 4+4+4, 8, 512 }, + 4+4+4, 8, 512, vmdk4GetCowBase }, /* Connectix / VirtualPC */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL, __BIG_ENDIAN, -1, 0, - -1, 0, 0}, + -1, 0, 0, NULL}, */ }; +static const struct FileTypeInfo * +virStorageBackendFileSystemVolFormatToInfo(virConnectPtr conn, int format) +{ + size_t i; + + for (i = 0; i < sizeof(fileTypeInfo) / sizeof(fileTypeInfo[0]); i++) { + if (fileTypeInfo[i].type == format) + return &fileTypeInfo[i]; + } + + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unsupported volume format %d"), format); + return NULL; +} + +static int +cowGetCowBase(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ + size_t len; + + *res = NULL; + if (buf_size < 4+4+1024) + return GET_COW_BASE_INVALID; + if (buf[4+4] == '\0') + return GET_COW_BASE_OK; + + len = 1024; + if (VIR_ALLOC_N(*res, len + 1) < 0) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, + _("copy-on-write base path")); + return GET_COW_BASE_ERROR; + } + memcpy(*res, buf + 4+4, len); + (*res)[len] = '\0'; + if (VIR_REALLOC_N(*res, strlen(*res) + 1) < 0) { + /* Ignore failure */ + } + return GET_COW_BASE_OK; +} + +static int +qcowXGetCowBase(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 GET_COW_BASE_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]); + if (offset > buf_size) + return GET_COW_BASE_INVALID; + size = ((buf[4+4+8] << 24) + | (buf[4+4+8+1] << 16) + | (buf[4+4+8+2] << 8) + | buf[4+4+8+3]); + if (size == 0) + return GET_COW_BASE_OK; + if (offset + size > buf_size || offset + size < offset) + return GET_COW_BASE_INVALID; + if (size + 1 == 0) + return GET_COW_BASE_INVALID; + if (VIR_ALLOC_N(*res, size + 1) < 0) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, + _("copy-on-write base path")); + return GET_COW_BASE_ERROR; + } + memcpy(*res, buf + offset, size); + (*res)[size] = '\0'; + return GET_COW_BASE_OK; +} + +static int +vmdk4GetCowBase(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 GET_COW_BASE_INVALID; + /* FIXME? the default buf_size is not large enough to contain the complete + descriptor. */ + 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 GET_COW_BASE_OK; + start += strlen(prefix); + end = strchr(start, '"'); + if (end == NULL) + return GET_COW_BASE_INVALID; + if (end == start) + return GET_COW_BASE_OK; + *end = '\0'; + *res = strdup(start); + if (*res == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, + _("copy-on-write base path")); + return GET_COW_BASE_ERROR; + } + return GET_COW_BASE_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. @@ -219,6 +390,32 @@ static int virStorageBackendProbeFile(vi /* Validation passed, we know the file format now */ def->target.format = fileTypeInfo[i].type; + if (fileTypeInfo[i].getCowBase != NULL) { + char *base; + + switch (fileTypeInfo[i].getCowBase(conn, &base, head, len)) { + case GET_COW_BASE_OK: + break; + + case GET_COW_BASE_INVALID: + continue; + + case GET_COW_BASE_ERROR: + return -1; + } + if (base == NULL) + def->target.cowBase = NULL; + else { + def->target.cowBase = absolutePathFromBaseFile(def->target.path, + base); + free(base); + if (def->target.cowBase == NULL) { + virStorageReportError(conn, VIR_ERR_NO_MEMORY, + _("copy-on-write base path")); + return -1; + } + } + } return 0; } @@ -832,7 +1029,9 @@ virStorageBackendFileSystemVolCreate(vir #if HAVE_QEMU_IMG const char *type; char size[100]; - const char *imgargv[7]; + const char *imgargv[9]; + size_t i; + virStorageVolDefPtr cowBase; if ((type = virStorageVolFormatFileSystemTypeToString(vol->target.format)) == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, @@ -840,17 +1039,55 @@ virStorageBackendFileSystemVolCreate(vir vol->target.format); return -1; } + if (vol->target.cowBase == NULL) + cowBase = NULL; + else { + const struct FileTypeInfo *info; + + info = virStorageBackendFileSystemVolFormatToInfo(conn, + vol->target.format); + if (info == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + vol->target.format); + return -1; + } + if (info->getCowBase == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("copy-on-write not supported with " + "format %s"), type); + return -1; + } + cowBase = virStorageVolDefFindByKey(pool, vol->target.cowBase); + if (cowBase == NULL) { + virStorageReportError(conn, VIR_ERR_NO_STORAGE_VOL, + _("unknown copy-on-write base volume " + "key %s"), vol->target.cowBase); + return -1; + } + if (cowBase->target.format != vol->target.format) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("copy-on-write with mixed file formats " + "not supported")); + return -1; + } + } /* Size in KB */ snprintf(size, sizeof(size), "%llu", vol->capacity/1024); - imgargv[0] = QEMU_IMG; - imgargv[1] = "create"; - imgargv[2] = "-f"; - imgargv[3] = type; - imgargv[4] = vol->target.path; - imgargv[5] = size; - imgargv[6] = NULL; + i = 0; + imgargv[i++] = QEMU_IMG; + imgargv[i++] = "create"; + imgargv[i++] = "-f"; + imgargv[i++] = type; + if (cowBase != NULL) { + imgargv[i++] = "-b"; + imgargv[i++] = cowBase->target.path; + } + imgargv[i++] = vol->target.path; + imgargv[i++] = size; + imgargv[i++] = NULL; if (virRun(conn, imgargv, NULL) < 0) { unlink(vol->target.path); @@ -878,6 +1115,12 @@ virStorageBackendFileSystemVolCreate(vir vol->target.format); return -1; } + if (vol->target.cowBase != 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); @@ -951,10 +1194,23 @@ virStorageBackendFileSystemVolCreate(vir */ static int virStorageBackendFileSystemVolDelete(virConnectPtr conn, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool, virStorageVolDefPtr vol, unsigned int flags ATTRIBUTE_UNUSED) { + size_t i; + + for (i = 0; i < pool->volumes.count; i++) { + const char *cowBase; + + cowBase = pool->volumes.objs[i]->target.cowBase; + if (cowBase != NULL && STREQ(cowBase, vol->key)) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("volume %s is used by volume %s"), + vol->key, pool->volumes.objs[i]->key); + return -1; + } + } if (unlink(vol->target.path) < 0) { /* Silently ignore failures where the vol has already gone away */ if (errno != ENOENT) { Index: src/storage_conf.c =================================================================== RCS file: /data/cvs/libvirt/src/storage_conf.c,v retrieving revision 1.33 diff -u -p -r1.33 storage_conf.c --- src/storage_conf.c 8 Dec 2008 11:28:37 -0000 1.33 +++ src/storage_conf.c 9 Dec 2008 17:25:18 -0000 @@ -247,6 +247,7 @@ virStorageVolDefFree(virStorageVolDefPtr VIR_FREE(def->target.path); VIR_FREE(def->target.perms.label); + VIR_FREE(def->target.cowBase); VIR_FREE(def); } @@ -993,6 +994,10 @@ virStorageVolDefParseDoc(virConnectPtr c if (virStorageVolDefParsePerms(conn, ctxt, &ret->target.perms) < 0) goto cleanup; + ret->target.cowBase = virXPathString(conn, + "string(/volume/target/cow-base)", + ctxt); + return ret; cleanup: @@ -1141,6 +1146,10 @@ virStorageVolDefFormat(virConnectPtr con def->target.perms.label); virBufferAddLit(&buf," </permissions>\n"); + + if (def->target.cowBase) + virBufferVSprintf(&buf, " <cow-base>%s</cow-base>\n", + def->target.cowBase); virBufferAddLit(&buf, " </target>\n"); virBufferAddLit(&buf,"</volume>\n"); Index: src/storage_conf.h =================================================================== RCS file: /data/cvs/libvirt/src/storage_conf.h,v retrieving revision 1.13 diff -u -p -r1.13 storage_conf.h --- src/storage_conf.h 4 Dec 2008 22:00:14 -0000 1.13 +++ src/storage_conf.h 9 Dec 2008 17:25:18 -0000 @@ -73,6 +73,7 @@ struct _virStorageVolTarget { char *path; int format; virStoragePerms perms; + char *cowBase; };
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list