libvirt/tests/testutilsqemu.c
Peter Krempa a0b8b54348 qemu: Retire QEMU_CAPS_BLOCKDEV
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2022-08-11 15:25:36 +02:00

1142 lines
31 KiB
C

#include <config.h>
#ifdef WITH_QEMU
# include "testutilsqemu.h"
# include "testutilshostcpus.h"
# include "testutils.h"
# include "viralloc.h"
# include "cpu_conf.h"
# include "qemu/qemu_domain.h"
# define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW
# include "qemu/qemu_capspriv.h"
# include "virstring.h"
# include "virfilecache.h"
# include "virtpm.h"
# include <sys/types.h>
# include <fcntl.h>
# define VIR_FROM_THIS VIR_FROM_QEMU
static virCPUDef *cpuDefault;
static virCPUDef *cpuHaswell;
static virCPUDef *cpuPower8;
static virCPUDef *cpuPower9;
static virCPUDef *cpuPower10;
static const char *qemu_emulators[VIR_ARCH_LAST] = {
[VIR_ARCH_I686] = "/usr/bin/qemu-system-i386",
[VIR_ARCH_X86_64] = "/usr/bin/qemu-system-x86_64",
[VIR_ARCH_AARCH64] = "/usr/bin/qemu-system-aarch64",
[VIR_ARCH_ARMV7L] = "/usr/bin/qemu-system-arm",
[VIR_ARCH_PPC64] = "/usr/bin/qemu-system-ppc64",
[VIR_ARCH_PPC] = "/usr/bin/qemu-system-ppc",
[VIR_ARCH_RISCV32] = "/usr/bin/qemu-system-riscv32",
[VIR_ARCH_RISCV64] = "/usr/bin/qemu-system-riscv64",
[VIR_ARCH_S390X] = "/usr/bin/qemu-system-s390x",
[VIR_ARCH_SPARC] = "/usr/bin/qemu-system-sparc",
};
static const virArch arch_alias[VIR_ARCH_LAST] = {
[VIR_ARCH_PPC64LE] = VIR_ARCH_PPC64,
[VIR_ARCH_ARMV6L] = VIR_ARCH_ARMV7L,
};
static const char *const i386_machines[] = {
"pc", NULL
};
static const char *const x86_64_machines[] = {
"pc", "q35", NULL
};
static const char *const aarch64_machines[] = {
"virt", "virt-2.6", "versatilepb", NULL
};
static const char *const arm_machines[] = {
"vexpress-a9", "virt", NULL
};
static const char *const ppc64_machines[] = {
"pseries", NULL
};
static const char *const ppc_machines[] = {
"ppce500", NULL
};
static const char *const riscv32_machines[] = {
"virt", NULL
};
static const char *const riscv64_machines[] = {
"virt", NULL
};
static const char *const s390x_machines[] = {
"s390-ccw-virtio", NULL
};
static const char *const sparc_machines[] = {
"SS-5", NULL
};
static const char *const *qemu_machines[VIR_ARCH_LAST] = {
[VIR_ARCH_I686] = i386_machines,
[VIR_ARCH_X86_64] = x86_64_machines,
[VIR_ARCH_AARCH64] = aarch64_machines,
[VIR_ARCH_ARMV7L] = arm_machines,
[VIR_ARCH_PPC64] = ppc64_machines,
[VIR_ARCH_PPC] = ppc_machines,
[VIR_ARCH_RISCV32] = riscv32_machines,
[VIR_ARCH_RISCV64] = riscv64_machines,
[VIR_ARCH_S390X] = s390x_machines,
[VIR_ARCH_SPARC] = sparc_machines,
};
static const char *const *kvm_machines[VIR_ARCH_LAST] = {
[VIR_ARCH_I686] = i386_machines,
[VIR_ARCH_X86_64] = x86_64_machines,
[VIR_ARCH_AARCH64] = aarch64_machines,
[VIR_ARCH_ARMV7L] = arm_machines,
[VIR_ARCH_PPC64] = ppc64_machines,
[VIR_ARCH_PPC] = ppc_machines,
[VIR_ARCH_RISCV32] = riscv32_machines,
[VIR_ARCH_RISCV64] = riscv64_machines,
[VIR_ARCH_S390X] = s390x_machines,
};
static const char *const *hvf_machines[VIR_ARCH_LAST] = {
[VIR_ARCH_I686] = NULL,
[VIR_ARCH_X86_64] = x86_64_machines,
[VIR_ARCH_AARCH64] = aarch64_machines,
[VIR_ARCH_ARMV7L] = NULL,
[VIR_ARCH_PPC64] = NULL,
[VIR_ARCH_PPC] = NULL,
[VIR_ARCH_RISCV32] = NULL,
[VIR_ARCH_RISCV64] = NULL,
[VIR_ARCH_S390X] = NULL,
};
static const char *qemu_default_ram_id[VIR_ARCH_LAST] = {
[VIR_ARCH_I686] = "pc.ram",
[VIR_ARCH_X86_64] = "pc.ram",
[VIR_ARCH_AARCH64] = "mach-virt.ram",
[VIR_ARCH_ARMV7L] = "vexpress.highmem",
[VIR_ARCH_PPC64] = "ppc_spapr.ram",
[VIR_ARCH_PPC] = "ppc_spapr.ram",
[VIR_ARCH_S390X] = "s390.ram",
[VIR_ARCH_SPARC] = "sun4m.ram",
};
char *
virFindFileInPath(const char *file)
{
if (g_str_has_prefix(file, "qemu-system") ||
g_str_equal(file, "qemu-kvm")) {
return g_strdup_printf("/usr/bin/%s", file);
}
/* Nothing in tests should be relying on real files
* in host OS, so we return NULL to try to force
* an error in such a case
*/
return NULL;
}
/* Enough to tell capabilities code that swtpm is usable */
bool virTPMHasSwtpm(void)
{
return true;
}
bool
virTPMSwtpmSetupCapsGet(virTPMSwtpmSetupFeature cap)
{
const char *tpmver = getenv(TEST_TPM_ENV_VAR);
switch (cap) {
case VIR_TPM_SWTPM_SETUP_FEATURE_TPM_1_2:
if (!tpmver || (tpmver && strstr(tpmver, TPM_VER_1_2)))
return true;
break;
case VIR_TPM_SWTPM_SETUP_FEATURE_TPM_2_0:
if (!tpmver || (tpmver && strstr(tpmver, TPM_VER_2_0)))
return true;
break;
case VIR_TPM_SWTPM_SETUP_FEATURE_CMDARG_PWDFILE_FD:
case VIR_TPM_SWTPM_SETUP_FEATURE_CMDARG_CREATE_CONFIG_FILES:
case VIR_TPM_SWTPM_SETUP_FEATURE_TPM12_NOT_NEED_ROOT:
case VIR_TPM_SWTPM_SETUP_FEATURE_CMDARG_RECONFIGURE_PCR_BANKS:
case VIR_TPM_SWTPM_SETUP_FEATURE_LAST:
break;
}
return false;
}
virCapsHostNUMA *
virCapabilitiesHostNUMANewHost(void)
{
/*
* Build a NUMA topology with cell_id (NUMA node id
* being 3(0 + 3),4(1 + 3), 5 and 6
*/
return virTestCapsBuildNUMATopology(3);
}
void
virHostCPUX86GetCPUID(uint32_t leaf,
uint32_t extended,
uint32_t *eax,
uint32_t *ebx,
uint32_t *ecx,
uint32_t *edx)
{
if (eax)
*eax = 0;
if (ebx)
*ebx = 0;
if (ecx)
*ecx = 0;
if (edx)
*edx = 0;
if (leaf == 0x8000001F && extended == 0) {
if (ecx)
*ecx = 509;
if (edx)
*edx = 451;
}
}
static int
testQemuAddGuest(virCaps *caps,
virArch arch,
testQemuHostOS hostOS)
{
size_t nmachines;
virCapsGuestMachine **machines = NULL;
virCapsGuest *guest;
virArch emu_arch = arch;
if (arch_alias[arch] != VIR_ARCH_NONE)
emu_arch = arch_alias[arch];
if (qemu_emulators[emu_arch] == NULL)
return 0;
nmachines = g_strv_length((gchar **)qemu_machines[emu_arch]);
machines = virCapabilitiesAllocMachines(qemu_machines[emu_arch],
nmachines);
if (machines == NULL)
goto error;
guest = virCapabilitiesAddGuest(caps, VIR_DOMAIN_OSTYPE_HVM,
arch, qemu_emulators[emu_arch],
NULL, nmachines, machines);
machines = NULL;
nmachines = 0;
if (arch == VIR_ARCH_I686 ||
arch == VIR_ARCH_X86_64)
virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_CPUSELECTION);
virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_QEMU,
NULL, NULL, 0, NULL);
if (hostOS == HOST_OS_LINUX) {
if (kvm_machines[emu_arch] != NULL) {
nmachines = g_strv_length((char **)kvm_machines[emu_arch]);
machines = virCapabilitiesAllocMachines(kvm_machines[emu_arch],
nmachines);
if (machines == NULL)
goto error;
virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_KVM,
qemu_emulators[emu_arch],
NULL, nmachines, machines);
}
}
if (hostOS == HOST_OS_MACOS) {
if (hvf_machines[emu_arch] != NULL) {
nmachines = g_strv_length((char **)hvf_machines[emu_arch]);
machines = virCapabilitiesAllocMachines(hvf_machines[emu_arch],
nmachines);
if (machines == NULL)
goto error;
virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_HVF,
qemu_emulators[emu_arch],
NULL, nmachines, machines);
}
}
return 0;
error:
virCapabilitiesFreeMachines(machines, nmachines);
return -1;
}
static virCaps*
testQemuCapsInitImpl(testQemuHostOS hostOS)
{
virCaps *caps;
size_t i;
if (!(caps = virCapabilitiesNew(VIR_ARCH_X86_64, false, false)))
return NULL;
/* Add dummy 'none' security_driver. This is equal to setting
* security_driver = "none" in qemu.conf. */
caps->host.secModels = g_new0(virCapsHostSecModel, 1);
caps->host.nsecModels = 1;
caps->host.secModels[0].model = g_strdup("none");
caps->host.secModels[0].doi = g_strdup("0");
if (!(caps->host.numa = virCapabilitiesHostNUMANewHost()))
goto cleanup;
for (i = 0; i < VIR_ARCH_LAST; i++) {
if (testQemuAddGuest(caps, i, hostOS) < 0)
goto cleanup;
}
if (virTestGetDebug()) {
g_autofree char *caps_str = NULL;
caps_str = virCapabilitiesFormatXML(caps);
if (!caps_str)
goto cleanup;
VIR_TEST_DEBUG("QEMU driver capabilities:\n%s", caps_str);
}
return caps;
cleanup:
caps->host.cpu = NULL;
virObjectUnref(caps);
return NULL;
}
virCaps*
testQemuCapsInit(void)
{
return testQemuCapsInitImpl(HOST_OS_LINUX);
}
virCaps*
testQemuCapsInitMacOS(void)
{
return testQemuCapsInitImpl(HOST_OS_MACOS);
}
virCPUDef *
qemuTestGetCPUDef(qemuTestCPUDef d)
{
switch (d) {
case QEMU_CPU_DEF_DEFAULT: return cpuDefault;
case QEMU_CPU_DEF_HASWELL: return cpuHaswell;
case QEMU_CPU_DEF_POWER8: return cpuPower8;
case QEMU_CPU_DEF_POWER9: return cpuPower9;
case QEMU_CPU_DEF_POWER10: return cpuPower10;
}
return NULL;
}
void
qemuTestSetHostArch(virQEMUDriver *driver,
virArch arch)
{
if (arch == VIR_ARCH_NONE)
arch = VIR_ARCH_X86_64;
virTestSetHostArch(arch);
driver->hostarch = virArchFromHost();
driver->caps->host.arch = virArchFromHost();
qemuTestSetHostCPU(driver, arch, NULL);
}
void
qemuTestSetHostCPU(virQEMUDriver *driver,
virArch arch,
virCPUDef *cpu)
{
if (!cpu) {
if (ARCH_IS_X86(arch))
cpu = cpuDefault;
else if (ARCH_IS_PPC64(arch))
cpu = cpuPower8;
}
g_unsetenv("VIR_TEST_MOCK_FAKE_HOST_CPU");
if (cpu) {
if (cpu->model)
g_setenv("VIR_TEST_MOCK_FAKE_HOST_CPU", cpu->model, TRUE);
}
if (driver) {
if (cpu)
driver->caps->host.arch = cpu->arch;
driver->caps->host.cpu = cpu;
virCPUDefFree(driver->hostcpu);
if (cpu)
virCPUDefRef(cpu);
driver->hostcpu = cpu;
}
}
virQEMUCaps *
qemuTestParseCapabilitiesArch(virArch arch,
const char *capsFile)
{
g_autoptr(virQEMUCaps) qemuCaps = NULL;
g_autofree char *binary = g_strdup_printf("/usr/bin/qemu-system-%s",
virArchToString(arch));
if (!(qemuCaps = virQEMUCapsNewBinary(binary)) ||
virQEMUCapsLoadCache(arch, qemuCaps, capsFile, true) < 0)
return NULL;
return g_steal_pointer(&qemuCaps);
}
void qemuTestDriverFree(virQEMUDriver *driver)
{
virMutexDestroy(&driver->lock);
if (driver->config) {
virFileDeleteTree(driver->config->stateDir);
virFileDeleteTree(driver->config->configDir);
}
virObjectUnref(driver->qemuCapsCache);
virObjectUnref(driver->xmlopt);
virObjectUnref(driver->caps);
virObjectUnref(driver->config);
virObjectUnref(driver->securityManager);
virCPUDefFree(cpuDefault);
virCPUDefFree(cpuHaswell);
virCPUDefFree(cpuPower8);
virCPUDefFree(cpuPower9);
virCPUDefFree(cpuPower10);
}
static void
qemuTestCapsPopulateFakeMachines(virQEMUCaps *caps,
virArch arch,
testQemuHostOS hostOS)
{
size_t i;
const char *defaultRAMid = NULL;
/* default-ram-id appeared in QEMU 5.2.0. Reflect
* this in our capabilities, i.e. set it for new
* enough versions only. */
if (virQEMUCapsGetVersion(caps) >= 5002000)
defaultRAMid = qemu_default_ram_id[arch];
virQEMUCapsSetArch(caps, arch);
for (i = 0; qemu_machines[arch][i] != NULL; i++) {
virQEMUCapsAddMachine(caps,
VIR_DOMAIN_VIRT_QEMU,
qemu_machines[arch][i],
NULL,
NULL,
0,
false,
false,
true,
defaultRAMid,
false);
virQEMUCapsSet(caps, QEMU_CAPS_TCG);
}
if (hostOS == HOST_OS_LINUX) {
if (kvm_machines[arch] != NULL) {
for (i = 0; kvm_machines[arch][i] != NULL; i++) {
virQEMUCapsAddMachine(caps,
VIR_DOMAIN_VIRT_KVM,
kvm_machines[arch][i],
NULL,
NULL,
0,
false,
false,
true,
defaultRAMid,
false);
virQEMUCapsSet(caps, QEMU_CAPS_KVM);
}
}
}
if (hostOS == HOST_OS_MACOS) {
if (hvf_machines[arch] != NULL) {
for (i = 0; hvf_machines[arch][i] != NULL; i++) {
virQEMUCapsAddMachine(caps,
VIR_DOMAIN_VIRT_HVF,
hvf_machines[arch][i],
NULL,
NULL,
0,
false,
false,
true,
defaultRAMid,
false);
virQEMUCapsSet(caps, QEMU_CAPS_HVF);
}
}
}
}
static int
qemuTestCapsCacheInsertData(virFileCache *cache,
const char *binary,
virQEMUCaps *caps)
{
if (virFileCacheInsertData(cache, binary, virObjectRef(caps)) < 0) {
virObjectUnref(caps);
return -1;
}
return 0;
}
static int
qemuTestCapsCacheInsertImpl(virFileCache *cache,
virQEMUCaps *caps,
testQemuHostOS hostOS)
{
size_t i;
if (caps && virQEMUCapsGetArch(caps) != VIR_ARCH_NONE) {
/* for capabilities which have architecture set we populate only the
* given architecture and poison all other so that the test doesn't
* accidentally test a weird combination */
virArch arch = virQEMUCapsGetArch(caps);
g_autoptr(virQEMUCaps) emptyCaps = virQEMUCapsNew();
g_autoptr(virQEMUCaps) copyCaps = NULL;
virQEMUCaps *effCaps = caps;
if (!emptyCaps)
return -1;
if (arch_alias[arch] != VIR_ARCH_NONE)
arch = arch_alias[arch];
if (qemu_emulators[arch]) {
/* if we are dealing with fake caps we need to populate machine types */
if (!virQEMUCapsHasMachines(caps)) {
if (!(copyCaps = effCaps = virQEMUCapsNewCopy(caps)))
return -1;
qemuTestCapsPopulateFakeMachines(copyCaps, arch, hostOS);
}
if (qemuTestCapsCacheInsertData(cache, qemu_emulators[arch], effCaps) < 0)
return -1;
}
for (i = 0; i < G_N_ELEMENTS(qemu_emulators); i++) {
if (!qemu_emulators[i])
continue;
if (i == arch)
continue;
if (qemuTestCapsCacheInsertData(cache, qemu_emulators[i], emptyCaps) < 0)
return -1;
}
} else {
/* in case when caps are missing or are missing architecture, we populate
* everything */
for (i = 0; i < G_N_ELEMENTS(qemu_emulators); i++) {
g_autoptr(virQEMUCaps) tmp = NULL;
if (qemu_emulators[i] == NULL)
continue;
if (caps)
tmp = virQEMUCapsNewCopy(caps);
else
tmp = virQEMUCapsNew();
if (!tmp)
return -1;
qemuTestCapsPopulateFakeMachines(tmp, i, hostOS);
if (qemuTestCapsCacheInsertData(cache, qemu_emulators[i], tmp) < 0)
return -1;
}
}
return 0;
}
int
qemuTestCapsCacheInsert(virFileCache *cache,
virQEMUCaps *caps)
{
return qemuTestCapsCacheInsertImpl(cache, caps, HOST_OS_LINUX);
}
int
qemuTestCapsCacheInsertMacOS(virFileCache *cache,
virQEMUCaps *caps)
{
return qemuTestCapsCacheInsertImpl(cache, caps, HOST_OS_MACOS);
}
# define STATEDIRTEMPLATE abs_builddir "/qemustatedir-XXXXXX"
# define CONFIGDIRTEMPLATE abs_builddir "/qemuconfigdir-XXXXXX"
int qemuTestDriverInit(virQEMUDriver *driver)
{
virSecurityManager *mgr = NULL;
char statedir[] = STATEDIRTEMPLATE;
char configdir[] = CONFIGDIRTEMPLATE;
memset(driver, 0, sizeof(*driver));
if (!(cpuDefault = virCPUDefCopy(&cpuDefaultData)) ||
!(cpuHaswell = virCPUDefCopy(&cpuHaswellData)) ||
!(cpuPower8 = virCPUDefCopy(&cpuPower8Data)) ||
!(cpuPower9 = virCPUDefCopy(&cpuPower9Data)) ||
!(cpuPower10 = virCPUDefCopy(&cpuPower10Data)))
return -1;
if (virMutexInit(&driver->lock) < 0)
return -1;
driver->hostarch = virArchFromHost();
driver->config = virQEMUDriverConfigNew(false, NULL);
if (!driver->config)
goto error;
/* Do this early so that qemuTestDriverFree() doesn't see (unlink) the real
* dirs. */
VIR_FREE(driver->config->stateDir);
VIR_FREE(driver->config->configDir);
/* Overwrite some default paths so it's consistent for tests. */
VIR_FREE(driver->config->libDir);
VIR_FREE(driver->config->channelTargetDir);
driver->config->libDir = g_strdup("/tmp/lib");
driver->config->channelTargetDir = g_strdup("/tmp/channel");
if (!g_mkdtemp(statedir)) {
fprintf(stderr, "Cannot create fake stateDir");
goto error;
}
driver->config->stateDir = g_strdup(statedir);
if (!g_mkdtemp(configdir)) {
fprintf(stderr, "Cannot create fake configDir");
goto error;
}
driver->config->configDir = g_strdup(configdir);
driver->caps = testQemuCapsInit();
if (!driver->caps)
goto error;
/* Using /dev/null for libDir and cacheDir automatically produces errors
* upon attempt to use any of them */
driver->qemuCapsCache = virQEMUCapsCacheNew("/dev/null", "/dev/null", 0, 0);
if (!driver->qemuCapsCache)
goto error;
driver->xmlopt = virQEMUDriverCreateXMLConf(driver, "none");
if (!driver->xmlopt)
goto error;
if (qemuTestCapsCacheInsert(driver->qemuCapsCache, NULL) < 0)
goto error;
if (!(mgr = virSecurityManagerNew("none", "qemu",
VIR_SECURITY_MANAGER_PRIVILEGED)))
goto error;
if (!(driver->securityManager = virSecurityManagerNewStack(mgr)))
goto error;
qemuTestSetHostCPU(driver, driver->hostarch, NULL);
return 0;
error:
virObjectUnref(mgr);
qemuTestDriverFree(driver);
return -1;
}
int
testQemuCapsSetGIC(virQEMUCaps *qemuCaps,
int gic)
{
virGICCapability *gicCapabilities = NULL;
size_t ngicCapabilities = 0;
gicCapabilities = g_new0(virGICCapability, 2);
# define IMPL_BOTH \
VIR_GIC_IMPLEMENTATION_KERNEL|VIR_GIC_IMPLEMENTATION_EMULATED
if (gic & GIC_V2) {
gicCapabilities[ngicCapabilities].version = VIR_GIC_VERSION_2;
gicCapabilities[ngicCapabilities].implementation = IMPL_BOTH;
ngicCapabilities++;
}
if (gic & GIC_V3) {
gicCapabilities[ngicCapabilities].version = VIR_GIC_VERSION_3;
gicCapabilities[ngicCapabilities].implementation = IMPL_BOTH;
ngicCapabilities++;
}
# undef IMPL_BOTH
virQEMUCapsSetGICCapabilities(qemuCaps,
gicCapabilities, ngicCapabilities);
return 0;
}
#endif
char *
testQemuGetLatestCapsForArch(const char *arch,
const char *suffix)
{
struct dirent *ent;
g_autoptr(DIR) dir = NULL;
int rc;
g_autofree char *fullsuffix = NULL;
unsigned long maxver = 0;
unsigned long ver;
g_autofree char *maxname = NULL;
fullsuffix = g_strdup_printf("%s.%s", arch, suffix);
if (virDirOpen(&dir, TEST_QEMU_CAPS_PATH) < 0)
return NULL;
while ((rc = virDirRead(dir, &ent, TEST_QEMU_CAPS_PATH)) > 0) {
g_autofree char *tmp = NULL;
tmp = g_strdup(STRSKIP(ent->d_name, "caps_"));
if (!tmp)
continue;
if (!virStringStripSuffix(tmp, fullsuffix))
continue;
if (virStringParseVersion(&ver, tmp, false) < 0) {
VIR_TEST_DEBUG("skipping caps file '%s'", ent->d_name);
continue;
}
if (ver > maxver) {
g_free(maxname);
maxname = g_strdup(ent->d_name);
maxver = ver;
}
}
if (rc < 0)
return NULL;
if (!maxname) {
VIR_TEST_VERBOSE("failed to find capabilities for '%s' in '%s'",
arch, TEST_QEMU_CAPS_PATH);
return NULL;
}
return g_strdup_printf("%s/%s", TEST_QEMU_CAPS_PATH, maxname);
}
GHashTable *
testQemuGetLatestCaps(void)
{
const char *archs[] = {
"aarch64",
"ppc64",
"riscv64",
"s390x",
"x86_64",
};
g_autoptr(GHashTable) capslatest = virHashNew(g_free);
size_t i;
VIR_TEST_VERBOSE("");
for (i = 0; i < G_N_ELEMENTS(archs); ++i) {
char *cap = testQemuGetLatestCapsForArch(archs[i], "xml");
if (!cap || virHashAddEntry(capslatest, archs[i], cap) < 0)
return NULL;
VIR_TEST_VERBOSE("latest caps for %s: %s", archs[i], cap);
}
VIR_TEST_VERBOSE("");
return g_steal_pointer(&capslatest);
}
int
testQemuCapsIterate(const char *suffix,
testQemuCapsIterateCallback callback,
void *opaque)
{
struct dirent *ent;
g_autoptr(DIR) dir = NULL;
int rc;
bool fail = false;
if (!callback)
return 0;
/* Validate suffix */
if (!STRPREFIX(suffix, ".")) {
VIR_TEST_VERBOSE("malformed suffix '%s'", suffix);
return -1;
}
if (virDirOpen(&dir, TEST_QEMU_CAPS_PATH) < 0)
return -1;
while ((rc = virDirRead(dir, &ent, TEST_QEMU_CAPS_PATH)) > 0) {
g_autofree char *tmp = g_strdup(ent->d_name);
char *version = NULL;
char *archName = NULL;
/* Strip the trailing suffix, moving on if it's not present */
if (!virStringStripSuffix(tmp, suffix))
continue;
/* Strip the leading prefix */
if (!(version = STRSKIP(tmp, "caps_"))) {
VIR_TEST_VERBOSE("malformed file name '%s'", ent->d_name);
return -1;
}
/* Find the last dot */
if (!(archName = strrchr(tmp, '.'))) {
VIR_TEST_VERBOSE("malformed file name '%s'", ent->d_name);
return -1;
}
/* The version number and the architecture name are separated by
* a dot: overwriting that dot with \0 results in both being usable
* as independent, null-terminated strings */
archName[0] = '\0';
archName++;
/* Run the user-provided callback.
*
* We skip the dot that, as verified earlier, starts the suffix
* to make it nicer to rebuild the original file name from inside
* the callback.
*/
if (callback(TEST_QEMU_CAPS_PATH, "caps", version,
archName, suffix + 1, opaque) < 0)
fail = true;
}
if (rc < 0 || fail)
return -1;
return 0;
}
void
testQemuInfoSetArgs(struct testQemuInfo *info,
struct testQemuConf *conf, ...)
{
va_list argptr;
testQemuInfoArgName argname;
int flag;
if (!(info->args.fakeCaps = virQEMUCapsNew()))
abort();
info->conf = conf;
info->args.newargs = true;
va_start(argptr, conf);
while ((argname = va_arg(argptr, testQemuInfoArgName)) != ARG_END) {
switch (argname) {
case ARG_QEMU_CAPS:
info->args.fakeCapsUsed = true;
while ((flag = va_arg(argptr, int)) < QEMU_CAPS_LAST)
virQEMUCapsSet(info->args.fakeCaps, flag);
break;
case ARG_GIC:
info->args.gic = va_arg(argptr, int);
break;
case ARG_MIGRATE_FROM:
info->migrateFrom = va_arg(argptr, char *);
break;
case ARG_MIGRATE_FD:
info->migrateFd = va_arg(argptr, int);
break;
case ARG_FLAGS:
info->flags = va_arg(argptr, int);
break;
case ARG_PARSEFLAGS:
info->parseFlags = va_arg(argptr, int);
break;
case ARG_CAPS_ARCH:
info->args.capsarch = va_arg(argptr, char *);
break;
case ARG_CAPS_VER:
info->args.capsver = va_arg(argptr, char *);
break;
case ARG_CAPS_HOST_CPU_MODEL:
info->args.capsHostCPUModel = va_arg(argptr, int);
break;
case ARG_HOST_OS:
info->args.hostOS = va_arg(argptr, int);
break;
case ARG_END:
default:
info->args.invalidarg = true;
break;
}
if (info->args.invalidarg)
break;
}
va_end(argptr);
}
int
testQemuInfoInitArgs(struct testQemuInfo *info)
{
g_autofree char *capsfile = NULL;
if (!info->args.newargs)
return 0;
info->args.newargs = false;
if (info->args.invalidarg) {
fprintf(stderr, "Invalid argument encountered by 'testQemuInfoSetArgs'\n");
return -1;
}
if (!!info->args.capsarch ^ !!info->args.capsver) {
fprintf(stderr, "ARG_CAPS_ARCH and ARG_CAPS_VER must be specified together.\n");
return -1;
}
if (info->args.capsarch && info->args.capsver) {
bool stripmachinealiases = false;
virQEMUCaps *cachedcaps = NULL;
info->arch = virArchFromString(info->args.capsarch);
if (STREQ(info->args.capsver, "latest")) {
capsfile = g_strdup(virHashLookup(info->conf->capslatest, info->args.capsarch));
if (!capsfile) {
fprintf(stderr, "'latest' caps for '%s' were not found\n", info->args.capsarch);
return -1;
}
stripmachinealiases = true;
} else {
capsfile = g_strdup_printf("%s/caps_%s.%s.xml",
TEST_QEMU_CAPS_PATH,
info->args.capsver,
info->args.capsarch);
}
if (!g_hash_table_lookup_extended(info->conf->capscache, capsfile, NULL, (void **) &cachedcaps)) {
if (!(cachedcaps = qemuTestParseCapabilitiesArch(info->arch, capsfile)))
return -1;
g_hash_table_insert(info->conf->capscache, g_strdup(capsfile), cachedcaps);
}
if (!(info->qemuCaps = virQEMUCapsNewCopy(cachedcaps)))
return -1;
if (info->args.fakeCapsUsed) {
size_t i;
for (i = 0; i < QEMU_CAPS_LAST; i++) {
if (virQEMUCapsGet(info->args.fakeCaps, i)) {
virQEMUCapsSet(info->qemuCaps, i);
}
}
}
if (stripmachinealiases)
virQEMUCapsStripMachineAliases(info->qemuCaps);
info->flags |= FLAG_REAL_CAPS;
/* provide path to the replies file for schema testing */
capsfile[strlen(capsfile) - 3] = '\0';
info->schemafile = g_strdup_printf("%sreplies", capsfile);
} else {
info->qemuCaps = g_steal_pointer(&info->args.fakeCaps);
}
if (info->args.gic != GIC_NONE &&
testQemuCapsSetGIC(info->qemuCaps, info->args.gic) < 0)
return -1;
return 0;
}
void
testQemuInfoClear(struct testQemuInfo *info)
{
VIR_FREE(info->infile);
VIR_FREE(info->outfile);
VIR_FREE(info->schemafile);
VIR_FREE(info->errfile);
virObjectUnref(info->qemuCaps);
g_clear_pointer(&info->args.fakeCaps, virObjectUnref);
}
/**
* testQemuPrepareHostBackendChardevOne:
* @dev: device definition object
* @chardev: chardev source object
* @opaque: Caller is expected to pass pointer to virDomainObj or NULL
*
* This helper sets up a chardev source backend for FD passing with fake
* file descriptros. It's expected to be used as callback for
* 'qemuDomainDeviceBackendChardevForeach', thus the VM object is passed via
* @opaque. Callers may pass NULL if the test scope is limited.
*/
int
testQemuPrepareHostBackendChardevOne(virDomainDeviceDef *dev,
virDomainChrSourceDef *chardev,
void *opaque)
{
virDomainObj *vm = opaque;
qemuDomainObjPrivate *priv = NULL;
qemuDomainChrSourcePrivate *charpriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(chardev);
int fakesourcefd = -1;
const char *devalias = NULL;
if (vm)
priv = vm->privateData;
if (dev) {
virDomainDeviceInfo *info = virDomainDeviceGetInfo(dev);
devalias = info->alias;
/* vhost-user disk doesn't use FD passing */
if (dev->type == VIR_DOMAIN_DEVICE_DISK)
return 0;
if (dev->type == VIR_DOMAIN_DEVICE_NET) {
/* due to a historical bug in qemu we don't use FD passtrhough for
* vhost-sockets for network devices */
return 0;
}
/* TPMs FD passing setup is special and handled separately */
if (dev->type == VIR_DOMAIN_DEVICE_TPM)
return 0;
} else {
devalias = "monitor";
}
switch ((virDomainChrType) chardev->type) {
case VIR_DOMAIN_CHR_TYPE_NULL:
case VIR_DOMAIN_CHR_TYPE_VC:
case VIR_DOMAIN_CHR_TYPE_PTY:
case VIR_DOMAIN_CHR_TYPE_DEV:
case VIR_DOMAIN_CHR_TYPE_PIPE:
case VIR_DOMAIN_CHR_TYPE_STDIO:
case VIR_DOMAIN_CHR_TYPE_UDP:
case VIR_DOMAIN_CHR_TYPE_TCP:
case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
case VIR_DOMAIN_CHR_TYPE_DBUS:
break;
case VIR_DOMAIN_CHR_TYPE_FILE:
fakesourcefd = 1750;
if (fcntl(fakesourcefd, F_GETFD) != -1)
abort();
charpriv->sourcefd = qemuFDPassNew(devalias, priv);
qemuFDPassAddFD(charpriv->sourcefd, &fakesourcefd, "-source");
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
if (chardev->data.nix.listen) {
g_autofree char *name = g_strdup_printf("%s-source", devalias);
fakesourcefd = 1729;
charpriv->directfd = qemuFDPassDirectNew(name, &fakesourcefd);
}
break;
case VIR_DOMAIN_CHR_TYPE_NMDM:
case VIR_DOMAIN_CHR_TYPE_LAST:
break;
}
if (chardev->logfile) {
int fd = 1751;
if (fcntl(fd, F_GETFD) != -1)
abort();
charpriv->logfd = qemuFDPassNew(devalias, priv);
qemuFDPassAddFD(charpriv->logfd, &fd, "-log");
}
return 0;
}