/* * Copyright (C) 2013 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * * Author: Eric Blake */ #include #include #include "testutils.h" #include "vircommand.h" #include "virerror.h" #include "virfile.h" #include "virlog.h" #include "virstoragefile.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NONE #define datadir abs_builddir "/virstoragedata" /* This test creates the following files, all in datadir: * raw: 1024-byte raw file * qcow2: qcow2 file with 'raw' as backing * wrap: qcow2 file with 'qcow2' as backing * qed: qed file with 'raw' as backing * sub/link1: symlink to qcow2 * sub/link2: symlink to wrap * * Relative names to these files are known at compile time, but absolute * and canonical names depend on where the test is run; for convenience, * we pre-populate the computation of these names for use during the test. */ static char *qemuimg; static char *absraw; static char *canonraw; static char *absqcow2; static char *canonqcow2; static char *abswrap; static char *absqed; static char *abslink2; static void testCleanupImages(void) { virCommandPtr cmd; VIR_FREE(qemuimg); VIR_FREE(absraw); VIR_FREE(canonraw); VIR_FREE(absqcow2); VIR_FREE(canonqcow2); VIR_FREE(abswrap); VIR_FREE(absqed); VIR_FREE(abslink2); if (chdir(abs_builddir) < 0) { fprintf(stderr, "unable to return to correct directory, refusing to " "clean up %s\n", datadir); return; } cmd = virCommandNewArgList("rm", "-rf", datadir, NULL); ignore_value(virCommandRun(cmd, NULL)); virCommandFree(cmd); } static int testPrepImages(void) { int ret = EXIT_FAILURE; virCommandPtr cmd = NULL; qemuimg = virFindFileInPath("kvm-img"); if (!qemuimg) qemuimg = virFindFileInPath("qemu-img"); if (!qemuimg) goto skip; if (virAsprintf(&absraw, "%s/raw", datadir) < 0 || virAsprintf(&absqcow2, "%s/qcow2", datadir) < 0 || virAsprintf(&abswrap, "%s/wrap", datadir) < 0 || virAsprintf(&absqed, "%s/qed", datadir) < 0 || virAsprintf(&abslink2, "%s/sub/link2", datadir) < 0) { virReportOOMError(); goto cleanup; } if (virFileMakePath(datadir "/sub") < 0) { fprintf(stderr, "unable to create directory %s\n", datadir "/sub"); goto cleanup; } if (chdir(datadir) < 0) { fprintf(stderr, "unable to test relative backing chains\n"); goto cleanup; } /* I'm lazy enough to use a shell one-liner instead of open/write/close */ virCommandFree(cmd); cmd = virCommandNewArgList("sh", "-c", "printf %1024d 0 > raw", NULL); if (virCommandRun(cmd, NULL) < 0) { fprintf(stderr, "unable to create raw file\n"); goto cleanup; } if (!(canonraw = canonicalize_file_name(absraw))) { virReportOOMError(); goto cleanup; } /* Create a qcow2 wrapping relative raw; later on, we modify its * metadata to test other configurations */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "create", "-f", "qcow2", "-obacking_file=raw,backing_fmt=raw", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) goto skip; /* Make sure our later uses of 'qemu-img rebase' will work */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "raw", "-b", "raw", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) goto skip; if (!(canonqcow2 = canonicalize_file_name(absqcow2))) { virReportOOMError(); goto cleanup; } /* Create a second qcow2 wrapping the first, to be sure that we * can correctly avoid insecure probing. */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "create", "-f", "qcow2", NULL); virCommandAddArgFormat(cmd, "-obacking_file=%s,backing_fmt=qcow2", absqcow2); virCommandAddArg(cmd, "wrap"); if (virCommandRun(cmd, NULL) < 0) goto skip; /* Create a qed file. */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "create", "-f", "qed", NULL); virCommandAddArgFormat(cmd, "-obacking_file=%s,backing_fmt=raw", absraw); virCommandAddArg(cmd, "qed"); if (virCommandRun(cmd, NULL) < 0) goto skip; #ifdef HAVE_SYMLINK /* Create some symlinks in a sub-directory. */ if (symlink("../qcow2", datadir "/sub/link1") < 0 || symlink("../wrap", datadir "/sub/link2") < 0) { fprintf(stderr, "unable to create symlink"); goto cleanup; } #endif ret = 0; cleanup: virCommandFree(cmd); if (ret) testCleanupImages(); return ret; skip: fputs("qemu-img is too old; skipping this test\n", stderr); ret = EXIT_AM_SKIP; goto cleanup; } typedef struct _testFileData testFileData; struct _testFileData { const char *expBackingStore; const char *expBackingStoreRaw; const char *expDirectory; enum virStorageFileFormat expFormat; bool expIsFile; unsigned long long expCapacity; bool expEncrypted; }; enum { EXP_PASS = 0, EXP_FAIL = 1, EXP_WARN = 2, ALLOW_PROBE = 4, }; struct testChainData { const char *start; enum virStorageFileFormat format; const testFileData *files; int nfiles; unsigned int flags; }; static int testStorageChain(const void *args) { const struct testChainData *data = args; int ret = -1; virStorageFileMetadataPtr meta; virStorageFileMetadataPtr elt; int i = 0; meta = virStorageFileGetMetadata(data->start, data->format, -1, -1, (data->flags & ALLOW_PROBE) != 0); if (!meta) { if (data->flags & EXP_FAIL) { virResetLastError(); ret = 0; } goto cleanup; } else if (data->flags & EXP_FAIL) { fprintf(stderr, "call should have failed\n"); goto cleanup; } if (data->flags & EXP_WARN) { if (!virGetLastError()) { fprintf(stderr, "call should have warned\n"); goto cleanup; } virResetLastError(); } else if (virGetLastError()) { fprintf(stderr, "call should not have warned\n"); goto cleanup; } elt = meta; while (elt) { char *expect = NULL; char *actual = NULL; if (i == data->nfiles) { fprintf(stderr, "probed chain was too long\n"); goto cleanup; } if (virAsprintf(&expect, "store:%s\nraw:%s\ndirectory:%s\nother:%d %d %lld %d", NULLSTR(data->files[i].expBackingStore), NULLSTR(data->files[i].expBackingStoreRaw), NULLSTR(data->files[i].expDirectory), data->files[i].expFormat, data->files[i].expIsFile, data->files[i].expCapacity, data->files[i].expEncrypted) < 0 || virAsprintf(&actual, "store:%s\nraw:%s\ndirectory:%s\nother:%d %d %lld %d", NULLSTR(elt->backingStore), NULLSTR(elt->backingStoreRaw), NULLSTR(elt->directory), elt->backingStoreFormat, elt->backingStoreIsFile, elt->capacity, elt->encrypted) < 0) { virReportOOMError(); VIR_FREE(expect); VIR_FREE(actual); goto cleanup; } if (STRNEQ(expect, actual)) { virtTestDifference(stderr, expect, actual); VIR_FREE(expect); VIR_FREE(actual); goto cleanup; } VIR_FREE(expect); VIR_FREE(actual); elt = elt->backingMeta; i++; } if (i != data->nfiles) { fprintf(stderr, "probed chain was too short\n"); goto cleanup; } ret = 0; cleanup: virStorageFileFreeMetadata(meta); return ret; } static int mymain(void) { int ret; virCommandPtr cmd = NULL; /* Prep some files with qemu-img; if that is not found on PATH, or * if it lacks support for qcow2 and qed, skip this test. */ if ((ret = testPrepImages()) != 0) return ret; #define TEST_ONE_CHAIN(id, start, format, chain, flags) \ do { \ struct testChainData data = { \ start, format, chain, ARRAY_CARDINALITY(chain), flags, \ }; \ if (virtTestRun("Storage backing chain " id, 1, \ testStorageChain, &data) < 0) \ ret = -1; \ } while (0) #define TEST_CHAIN(id, relstart, absstart, format, chain1, flags1, \ chain2, flags2, chain3, flags3, chain4, flags4) \ do { \ TEST_ONE_CHAIN(#id "a", relstart, format, chain1, flags1); \ TEST_ONE_CHAIN(#id "b", relstart, format, chain2, flags2); \ TEST_ONE_CHAIN(#id "c", absstart, format, chain3, flags3); \ TEST_ONE_CHAIN(#id "d", absstart, format, chain4, flags4); \ } while (0) /* Expected details about files in chains */ const testFileData raw = { NULL, NULL, NULL, VIR_STORAGE_FILE_NONE, false, 0, false, }; const testFileData qcow2_relback_relstart = { canonraw, "raw", ".", VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData qcow2_relback_absstart = { canonraw, "raw", datadir, VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData qcow2_absback = { canonraw, absraw, datadir, VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData qcow2_as_probe = { canonraw, absraw, datadir, VIR_STORAGE_FILE_AUTO, true, 1024, false, }; const testFileData qcow2_bogus = { NULL, datadir "/bogus", datadir, VIR_STORAGE_FILE_NONE, false, 1024, false, }; const testFileData qcow2_protocol = { "nbd:example.org:6000", NULL, NULL, VIR_STORAGE_FILE_RAW, false, 1024, false, }; const testFileData wrap = { canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_QCOW2, true, 1024, false, }; const testFileData wrap_as_raw = { canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData wrap_as_probe = { canonqcow2, absqcow2, datadir, VIR_STORAGE_FILE_AUTO, true, 1024, false, }; const testFileData qed = { canonraw, absraw, datadir, VIR_STORAGE_FILE_RAW, true, 1024, false, }; #if HAVE_SYMLINK const testFileData link1_rel = { canonraw, "../raw", "sub/../sub/..", VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData link1_abs = { canonraw, "../raw", datadir "/sub/../sub/..", VIR_STORAGE_FILE_RAW, true, 1024, false, }; const testFileData link2_rel = { canonqcow2, "../sub/link1", "sub/../sub", VIR_STORAGE_FILE_QCOW2, true, 1024, false, }; const testFileData link2_abs = { canonqcow2, "../sub/link1", datadir "/sub/../sub", VIR_STORAGE_FILE_QCOW2, true, 1024, false, }; #endif /* The actual tests, in several groups. */ /* Missing file */ const testFileData chain0[] = { }; TEST_ONE_CHAIN("0", "bogus", VIR_STORAGE_FILE_RAW, chain0, EXP_FAIL); /* Raw image, whether with right format or no specified format */ const testFileData chain1[] = { raw }; TEST_CHAIN(1, "raw", absraw, VIR_STORAGE_FILE_RAW, chain1, EXP_PASS, chain1, ALLOW_PROBE | EXP_PASS, chain1, EXP_PASS, chain1, ALLOW_PROBE | EXP_PASS); TEST_CHAIN(2, "raw", absraw, VIR_STORAGE_FILE_AUTO, chain1, EXP_PASS, chain1, ALLOW_PROBE | EXP_PASS, chain1, EXP_PASS, chain1, ALLOW_PROBE | EXP_PASS); /* Qcow2 file with relative raw backing, format provided */ const testFileData chain3a[] = { qcow2_relback_relstart, raw }; const testFileData chain3c[] = { qcow2_relback_absstart, raw }; const testFileData chain4a[] = { raw }; TEST_CHAIN(3, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2, chain3a, EXP_PASS, chain3a, ALLOW_PROBE | EXP_PASS, chain3c, EXP_PASS, chain3c, ALLOW_PROBE | EXP_PASS); TEST_CHAIN(4, "qcow2", absqcow2, VIR_STORAGE_FILE_AUTO, chain4a, EXP_PASS, chain3a, ALLOW_PROBE | EXP_PASS, chain4a, EXP_PASS, chain3c, ALLOW_PROBE | EXP_PASS); /* Rewrite qcow2 file to use absolute backing name */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "raw", "-b", absraw, "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Qcow2 file with raw as absolute backing, backing format provided */ const testFileData chain5[] = { qcow2_absback, raw }; const testFileData chain6[] = { raw }; TEST_CHAIN(5, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2, chain5, EXP_PASS, chain5, ALLOW_PROBE | EXP_PASS, chain5, EXP_PASS, chain5, ALLOW_PROBE | EXP_PASS); TEST_CHAIN(6, "qcow2", absqcow2, VIR_STORAGE_FILE_AUTO, chain6, EXP_PASS, chain5, ALLOW_PROBE | EXP_PASS, chain6, EXP_PASS, chain5, ALLOW_PROBE | EXP_PASS); /* Wrapped file access */ const testFileData chain7[] = { wrap, qcow2_absback, raw }; TEST_CHAIN(7, "wrap", abswrap, VIR_STORAGE_FILE_QCOW2, chain7, EXP_PASS, chain7, ALLOW_PROBE | EXP_PASS, chain7, EXP_PASS, chain7, ALLOW_PROBE | EXP_PASS); /* Rewrite qcow2 and wrap file to omit backing file type */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-b", absraw, "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-b", absqcow2, "wrap", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Qcow2 file with raw as absolute backing, backing format omitted */ const testFileData chain8a[] = { wrap_as_raw, raw }; const testFileData chain8b[] = { wrap_as_probe, qcow2_as_probe, raw }; TEST_CHAIN(8, "wrap", abswrap, VIR_STORAGE_FILE_QCOW2, chain8a, EXP_PASS, chain8b, ALLOW_PROBE | EXP_PASS, chain8a, EXP_PASS, chain8b, ALLOW_PROBE | EXP_PASS); /* Rewrite qcow2 to a missing backing file, with backing type */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "qcow2", "-b", datadir "/bogus", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Qcow2 file with missing backing file but specified type */ const testFileData chain9[] = { qcow2_bogus }; TEST_CHAIN(9, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2, chain9, EXP_WARN, chain9, ALLOW_PROBE | EXP_WARN, chain9, EXP_WARN, chain9, ALLOW_PROBE | EXP_WARN); /* Rewrite qcow2 to a missing backing file, without backing type */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-b", datadir "/bogus", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Qcow2 file with missing backing file and no specified type */ const testFileData chain10[] = { qcow2_bogus }; TEST_CHAIN(10, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2, chain10, EXP_WARN, chain10, ALLOW_PROBE | EXP_WARN, chain10, EXP_WARN, chain10, ALLOW_PROBE | EXP_WARN); /* Rewrite qcow2 to use an nbd: protocol as backend */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "raw", "-b", "nbd:example.org:6000", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Qcow2 file with backing protocol instead of file */ const testFileData chain11[] = { qcow2_protocol }; TEST_CHAIN(11, "qcow2", absqcow2, VIR_STORAGE_FILE_QCOW2, chain11, EXP_PASS, chain11, ALLOW_PROBE | EXP_PASS, chain11, EXP_PASS, chain11, ALLOW_PROBE | EXP_PASS); /* qed file */ const testFileData chain12a[] = { raw }; const testFileData chain12b[] = { qed, raw }; TEST_CHAIN(12, "qed", absqed, VIR_STORAGE_FILE_AUTO, chain12a, EXP_PASS, chain12b, ALLOW_PROBE | EXP_PASS, chain12a, EXP_PASS, chain12b, ALLOW_PROBE | EXP_PASS); #ifdef HAVE_SYMLINK /* Rewrite qcow2 and wrap file to use backing names relative to a * symlink from a different directory */ virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "raw", "-b", "../raw", "qcow2", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; virCommandFree(cmd); cmd = virCommandNewArgList(qemuimg, "rebase", "-u", "-f", "qcow2", "-F", "qcow2", "-b", "../sub/link1", "wrap", NULL); if (virCommandRun(cmd, NULL) < 0) ret = -1; /* Behavior of symlinks to qcow2 with relative backing files */ const testFileData chain13a[] = { link2_rel, link1_rel, raw }; const testFileData chain13c[] = { link2_abs, link1_abs, raw }; TEST_CHAIN(13, "sub/link2", abslink2, VIR_STORAGE_FILE_QCOW2, chain13a, EXP_PASS, chain13a, ALLOW_PROBE | EXP_PASS, chain13c, EXP_PASS, chain13c, ALLOW_PROBE | EXP_PASS); #endif /* Final cleanup */ testCleanupImages(); virCommandFree(cmd); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } VIRT_TEST_MAIN(mymain)