2008-05-22 16:18:40 +00:00
|
|
|
#include <config.h>
|
2008-06-26 09:37:51 +00:00
|
|
|
#ifdef WITH_QEMU
|
2008-05-16 16:51:30 +00:00
|
|
|
|
2010-03-09 18:22:22 +00:00
|
|
|
# include "testutilsqemu.h"
|
2023-03-09 14:15:40 +00:00
|
|
|
# include "testutilsqemuschema.h"
|
2017-07-21 12:24:51 +00:00
|
|
|
# include "testutilshostcpus.h"
|
2010-03-09 18:22:22 +00:00
|
|
|
# include "testutils.h"
|
2012-12-12 18:06:53 +00:00
|
|
|
# include "viralloc.h"
|
2010-04-16 06:01:59 +00:00
|
|
|
# include "cpu_conf.h"
|
2010-12-17 16:41:51 +00:00
|
|
|
# include "qemu/qemu_domain.h"
|
2018-12-13 14:53:50 +00:00
|
|
|
# define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW
|
2015-09-09 14:03:14 +00:00
|
|
|
# include "qemu/qemu_capspriv.h"
|
2013-05-03 12:52:21 +00:00
|
|
|
# include "virstring.h"
|
2017-07-19 15:01:56 +00:00
|
|
|
# include "virfilecache.h"
|
2022-06-18 17:21:57 +00:00
|
|
|
# include "virtpm.h"
|
2013-05-03 12:52:21 +00:00
|
|
|
|
2022-02-09 11:30:25 +00:00
|
|
|
# include <sys/types.h>
|
|
|
|
# include <fcntl.h>
|
|
|
|
|
2013-05-03 12:52:21 +00:00
|
|
|
# define VIR_FROM_THIS VIR_FROM_QEMU
|
2008-05-16 16:51:30 +00:00
|
|
|
|
2022-05-05 14:27:55 +00:00
|
|
|
static virCPUDef *cpuDefault;
|
|
|
|
static virCPUDef *cpuHaswell;
|
|
|
|
static virCPUDef *cpuPower8;
|
|
|
|
static virCPUDef *cpuPower9;
|
2022-05-03 20:15:05 +00:00
|
|
|
static virCPUDef *cpuPower10;
|
2015-03-23 16:19:28 +00:00
|
|
|
|
2019-11-26 16:53:53 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-07-08 19:30:30 +00:00
|
|
|
if (g_str_equal(file, "nbdkit")) {
|
|
|
|
return g_strdup(TEST_NBDKIT_PATH);
|
|
|
|
}
|
|
|
|
|
2019-11-26 16:53:53 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2019-11-29 10:40:39 +00:00
|
|
|
|
2022-06-18 17:21:57 +00:00
|
|
|
/* Enough to tell capabilities code that swtpm is usable */
|
|
|
|
bool virTPMHasSwtpm(void)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-20 07:51:55 +00:00
|
|
|
|
2022-07-20 07:11:38 +00:00
|
|
|
bool
|
|
|
|
virTPMSwtpmSetupCapsGet(virTPMSwtpmSetupFeature cap)
|
|
|
|
{
|
2022-07-20 07:51:55 +00:00
|
|
|
const char *tpmver = getenv(TEST_TPM_ENV_VAR);
|
|
|
|
|
2022-07-20 07:11:38 +00:00
|
|
|
switch (cap) {
|
2022-07-20 07:51:55 +00:00
|
|
|
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;
|
2022-07-20 07:11:38 +00:00
|
|
|
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:
|
2024-10-22 14:15:21 +00:00
|
|
|
case VIR_TPM_SWTPM_SETUP_FEATURE_TPMSTATE_OPT_LOCK:
|
2022-07-20 07:11:38 +00:00
|
|
|
case VIR_TPM_SWTPM_SETUP_FEATURE_LAST:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
virCapsHostNUMA *
|
2019-11-29 10:40:39 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-12-08 19:53:45 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2019-11-29 10:40:39 +00:00
|
|
|
|
2023-08-31 07:45:38 +00:00
|
|
|
static virCaps *
|
2023-03-08 16:49:28 +00:00
|
|
|
testQemuCapsInit(void)
|
2014-03-18 08:13:43 +00:00
|
|
|
{
|
2021-03-11 07:16:13 +00:00
|
|
|
virCaps *caps;
|
2008-05-16 16:51:30 +00:00
|
|
|
|
2015-03-23 16:19:28 +00:00
|
|
|
if (!(caps = virCapabilitiesNew(VIR_ARCH_X86_64, false, false)))
|
2008-05-16 16:51:30 +00:00
|
|
|
return NULL;
|
|
|
|
|
2014-09-03 17:06:55 +00:00
|
|
|
/* Add dummy 'none' security_driver. This is equal to setting
|
|
|
|
* security_driver = "none" in qemu.conf. */
|
2020-09-22 23:04:17 +00:00
|
|
|
caps->host.secModels = g_new0(virCapsHostSecModel, 1);
|
2014-09-03 17:06:55 +00:00
|
|
|
caps->host.nsecModels = 1;
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
caps->host.secModels[0].model = g_strdup("none");
|
|
|
|
caps->host.secModels[0].doi = g_strdup("0");
|
2014-09-03 17:06:55 +00:00
|
|
|
|
2019-11-29 10:40:39 +00:00
|
|
|
if (!(caps->host.numa = virCapabilitiesHostNUMANewHost()))
|
tests: Create full host NUMA topology in more cases
vircapstest has code to add a full host NUMA topology, that
is, one that includes all information about nodes and CPUs
including IDs; testQemuCapsInit(), which is used to create a
mock virCapsPtr for QEMU tests, however, just fakes it by
setting nnumaCell_max to some number.
While the latter approach has served us well so far, we're
going to need all the information to be filled in soon. In
order to do that, we can just move the existing code from
vircapstest to testutils and, with some renaming and
trivial tweaking, use it as-is.
Interestingly, the NUMA topology generated by the function
is rigged up so that the NUMA nodes aren't (necessarily)
numbered starting from 0, which is a nice way to spot
mistaken assumptions in our codebase.
Signed-off-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2018-04-10 15:42:14 +00:00
|
|
|
goto cleanup;
|
2015-07-24 14:06:33 +00:00
|
|
|
|
2009-11-30 19:01:31 +00:00
|
|
|
if (virTestGetDebug()) {
|
2020-07-28 19:57:28 +00:00
|
|
|
g_autofree char *caps_str = NULL;
|
2009-09-10 10:07:20 +00:00
|
|
|
|
|
|
|
caps_str = virCapabilitiesFormatXML(caps);
|
|
|
|
if (!caps_str)
|
|
|
|
goto cleanup;
|
|
|
|
|
2015-04-23 17:38:00 +00:00
|
|
|
VIR_TEST_DEBUG("QEMU driver capabilities:\n%s", caps_str);
|
2009-09-10 10:07:20 +00:00
|
|
|
}
|
|
|
|
|
2008-05-16 16:51:30 +00:00
|
|
|
return caps;
|
|
|
|
|
2014-03-25 06:53:44 +00:00
|
|
|
cleanup:
|
2016-08-04 11:16:55 +00:00
|
|
|
caps->host.cpu = NULL;
|
2013-02-01 12:26:18 +00:00
|
|
|
virObjectUnref(caps);
|
2008-05-16 16:51:30 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2013-05-17 10:34:24 +00:00
|
|
|
|
2022-05-05 14:27:55 +00:00
|
|
|
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;
|
2022-05-03 20:15:05 +00:00
|
|
|
case QEMU_CPU_DEF_POWER10: return cpuPower10;
|
2022-05-05 14:27:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-04 11:16:55 +00:00
|
|
|
void
|
2021-03-11 07:16:13 +00:00
|
|
|
qemuTestSetHostArch(virQEMUDriver *driver,
|
2016-08-04 11:16:55 +00:00
|
|
|
virArch arch)
|
|
|
|
{
|
|
|
|
if (arch == VIR_ARCH_NONE)
|
|
|
|
arch = VIR_ARCH_X86_64;
|
2019-11-26 17:51:22 +00:00
|
|
|
|
2022-05-05 13:27:14 +00:00
|
|
|
virTestSetHostArch(arch);
|
2019-11-26 17:51:22 +00:00
|
|
|
driver->hostarch = virArchFromHost();
|
|
|
|
driver->caps->host.arch = virArchFromHost();
|
2019-11-29 10:40:39 +00:00
|
|
|
qemuTestSetHostCPU(driver, arch, NULL);
|
2016-08-04 11:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2021-03-11 07:16:13 +00:00
|
|
|
qemuTestSetHostCPU(virQEMUDriver *driver,
|
2019-11-29 10:40:39 +00:00
|
|
|
virArch arch,
|
2021-03-11 07:16:13 +00:00
|
|
|
virCPUDef *cpu)
|
2016-08-04 11:16:55 +00:00
|
|
|
{
|
|
|
|
if (!cpu) {
|
|
|
|
if (ARCH_IS_X86(arch))
|
|
|
|
cpu = cpuDefault;
|
2016-09-13 20:27:09 +00:00
|
|
|
else if (ARCH_IS_PPC64(arch))
|
|
|
|
cpu = cpuPower8;
|
2016-08-04 11:16:55 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 17:16:19 +00:00
|
|
|
g_unsetenv("VIR_TEST_MOCK_FAKE_HOST_CPU");
|
2017-07-21 12:24:51 +00:00
|
|
|
if (cpu) {
|
|
|
|
if (cpu->model)
|
2019-12-18 17:16:19 +00:00
|
|
|
g_setenv("VIR_TEST_MOCK_FAKE_HOST_CPU", cpu->model, TRUE);
|
2017-07-21 12:24:51 +00:00
|
|
|
}
|
2019-11-29 10:40:39 +00:00
|
|
|
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;
|
|
|
|
}
|
2016-08-04 11:16:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-11 07:16:13 +00:00
|
|
|
virQEMUCaps *
|
2018-04-04 07:17:52 +00:00
|
|
|
qemuTestParseCapabilitiesArch(virArch arch,
|
|
|
|
const char *capsFile)
|
2014-06-27 14:39:27 +00:00
|
|
|
{
|
2020-02-21 07:28:13 +00:00
|
|
|
g_autofree char *binary = g_strdup_printf("/usr/bin/qemu-system-%s",
|
|
|
|
virArchToString(arch));
|
2023-03-03 08:27:45 +00:00
|
|
|
g_autoptr(virQEMUCaps) qemuCaps = virQEMUCapsNewBinary(binary);
|
2014-06-27 14:39:27 +00:00
|
|
|
|
2023-03-03 08:27:45 +00:00
|
|
|
if (virQEMUCapsLoadCache(arch, qemuCaps, capsFile, true) < 0)
|
2021-08-20 11:35:05 +00:00
|
|
|
return NULL;
|
2014-06-27 14:39:27 +00:00
|
|
|
|
2021-08-20 11:35:05 +00:00
|
|
|
return g_steal_pointer(&qemuCaps);
|
2014-06-27 14:39:27 +00:00
|
|
|
}
|
2015-09-15 06:16:02 +00:00
|
|
|
|
2018-04-04 07:17:52 +00:00
|
|
|
|
2015-09-15 06:16:02 +00:00
|
|
|
void qemuTestDriverFree(virQEMUDriver *driver)
|
|
|
|
{
|
2015-09-22 14:12:39 +00:00
|
|
|
virMutexDestroy(&driver->lock);
|
2020-05-07 20:31:04 +00:00
|
|
|
if (driver->config) {
|
|
|
|
virFileDeleteTree(driver->config->stateDir);
|
|
|
|
virFileDeleteTree(driver->config->configDir);
|
|
|
|
}
|
2017-07-19 15:01:56 +00:00
|
|
|
virObjectUnref(driver->qemuCapsCache);
|
2015-09-15 06:16:02 +00:00
|
|
|
virObjectUnref(driver->xmlopt);
|
|
|
|
virObjectUnref(driver->caps);
|
|
|
|
virObjectUnref(driver->config);
|
2016-07-29 16:06:51 +00:00
|
|
|
virObjectUnref(driver->securityManager);
|
2024-07-03 21:40:13 +00:00
|
|
|
virObjectUnref(driver->domainEventState);
|
2022-07-08 19:30:30 +00:00
|
|
|
g_clear_object(&driver->nbdkitCapsCache);
|
2021-07-30 08:34:50 +00:00
|
|
|
|
|
|
|
virCPUDefFree(cpuDefault);
|
|
|
|
virCPUDefFree(cpuHaswell);
|
|
|
|
virCPUDefFree(cpuPower8);
|
|
|
|
virCPUDefFree(cpuPower9);
|
2022-05-03 20:15:05 +00:00
|
|
|
virCPUDefFree(cpuPower10);
|
2015-09-15 06:16:02 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 16:11:30 +00:00
|
|
|
|
2022-01-05 19:53:18 +00:00
|
|
|
static int
|
|
|
|
qemuTestCapsCacheInsertData(virFileCache *cache,
|
|
|
|
const char *binary,
|
|
|
|
virQEMUCaps *caps)
|
|
|
|
{
|
|
|
|
if (virFileCacheInsertData(cache, binary, virObjectRef(caps)) < 0) {
|
|
|
|
virObjectUnref(caps);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-08 16:49:28 +00:00
|
|
|
int
|
|
|
|
qemuTestCapsCacheInsert(virFileCache *cache,
|
|
|
|
virQEMUCaps *caps)
|
2015-09-09 14:03:14 +00:00
|
|
|
{
|
2023-08-31 07:44:10 +00:00
|
|
|
/* At this point we support only real capabilities. */
|
|
|
|
if (virQEMUCapsGetArch(caps) == VIR_ARCH_NONE ||
|
|
|
|
!virQEMUCapsGetBinary(caps)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
"missing 'arch' or 'binary' in qemuCaps to be inserted into testing cache");
|
|
|
|
return -1;
|
2017-04-11 12:02:06 +00:00
|
|
|
}
|
2015-09-09 14:03:14 +00:00
|
|
|
|
2023-08-31 07:44:10 +00:00
|
|
|
if (qemuTestCapsCacheInsertData(cache, virQEMUCapsGetBinary(caps), caps) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2017-04-11 12:02:06 +00:00
|
|
|
return 0;
|
2015-09-09 14:03:14 +00:00
|
|
|
}
|
|
|
|
|
2020-05-07 20:31:04 +00:00
|
|
|
# define STATEDIRTEMPLATE abs_builddir "/qemustatedir-XXXXXX"
|
|
|
|
# define CONFIGDIRTEMPLATE abs_builddir "/qemuconfigdir-XXXXXX"
|
2017-03-06 07:27:23 +00:00
|
|
|
|
2015-09-15 06:16:02 +00:00
|
|
|
int qemuTestDriverInit(virQEMUDriver *driver)
|
|
|
|
{
|
2023-03-02 18:48:23 +00:00
|
|
|
virQEMUDriverConfig *cfg = NULL;
|
2021-03-11 07:16:13 +00:00
|
|
|
virSecurityManager *mgr = NULL;
|
2020-05-07 20:31:04 +00:00
|
|
|
char statedir[] = STATEDIRTEMPLATE;
|
|
|
|
char configdir[] = CONFIGDIRTEMPLATE;
|
2016-03-23 15:19:26 +00:00
|
|
|
|
2016-01-18 08:11:19 +00:00
|
|
|
memset(driver, 0, sizeof(*driver));
|
|
|
|
|
2023-03-03 09:33:09 +00:00
|
|
|
cpuDefault = virCPUDefCopy(&cpuDefaultData);
|
|
|
|
cpuHaswell = virCPUDefCopy(&cpuHaswellData);
|
|
|
|
cpuPower8 = virCPUDefCopy(&cpuPower8Data);
|
|
|
|
cpuPower9 = virCPUDefCopy(&cpuPower9Data);
|
|
|
|
cpuPower10 = virCPUDefCopy(&cpuPower10Data);
|
2019-11-29 10:40:39 +00:00
|
|
|
|
2015-09-22 14:12:39 +00:00
|
|
|
if (virMutexInit(&driver->lock) < 0)
|
|
|
|
return -1;
|
|
|
|
|
2019-11-26 17:51:22 +00:00
|
|
|
driver->hostarch = virArchFromHost();
|
2023-03-02 18:48:23 +00:00
|
|
|
|
2023-03-06 14:25:22 +00:00
|
|
|
cfg = virQEMUDriverConfigNew(false, NULL);
|
2023-03-02 18:48:23 +00:00
|
|
|
if (!cfg)
|
2015-09-22 14:12:39 +00:00
|
|
|
goto error;
|
2023-03-02 18:48:23 +00:00
|
|
|
driver->config = cfg;
|
2015-09-15 06:16:02 +00:00
|
|
|
|
2020-05-07 20:31:04 +00:00
|
|
|
/* Do this early so that qemuTestDriverFree() doesn't see (unlink) the real
|
|
|
|
* dirs. */
|
2023-03-02 18:48:23 +00:00
|
|
|
VIR_FREE(cfg->stateDir);
|
|
|
|
VIR_FREE(cfg->configDir);
|
2020-05-07 20:31:04 +00:00
|
|
|
|
2023-03-06 14:25:22 +00:00
|
|
|
/* Override paths to ensure predictable output
|
|
|
|
*
|
|
|
|
* FIXME Find a way to achieve the same result while avoiding
|
|
|
|
* code duplication
|
|
|
|
*/
|
|
|
|
VIR_FREE(cfg->libDir);
|
|
|
|
cfg->libDir = g_strdup("/var/lib/libvirt/qemu");
|
|
|
|
VIR_FREE(cfg->channelTargetDir);
|
2023-04-20 08:16:43 +00:00
|
|
|
cfg->channelTargetDir = g_strdup("/var/run/libvirt/qemu/channel");
|
2023-03-06 14:25:22 +00:00
|
|
|
VIR_FREE(cfg->memoryBackingDir);
|
|
|
|
cfg->memoryBackingDir = g_strdup("/var/lib/libvirt/qemu/ram");
|
|
|
|
VIR_FREE(cfg->nvramDir);
|
|
|
|
cfg->nvramDir = g_strdup("/var/lib/libvirt/qemu/nvram");
|
|
|
|
VIR_FREE(cfg->passtStateDir);
|
|
|
|
cfg->passtStateDir = g_strdup("/var/run/libvirt/qemu/passt");
|
|
|
|
VIR_FREE(cfg->dbusStateDir);
|
|
|
|
cfg->dbusStateDir = g_strdup("/var/run/libvirt/qemu/dbus");
|
|
|
|
|
2020-05-07 20:31:04 +00:00
|
|
|
if (!g_mkdtemp(statedir)) {
|
|
|
|
fprintf(stderr, "Cannot create fake stateDir");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2023-03-02 18:48:23 +00:00
|
|
|
cfg->stateDir = g_strdup(statedir);
|
2020-05-07 20:31:04 +00:00
|
|
|
|
|
|
|
if (!g_mkdtemp(configdir)) {
|
|
|
|
fprintf(stderr, "Cannot create fake configDir");
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2023-03-02 18:48:23 +00:00
|
|
|
cfg->configDir = g_strdup(configdir);
|
2020-05-07 20:31:04 +00:00
|
|
|
|
2015-09-15 06:16:02 +00:00
|
|
|
driver->caps = testQemuCapsInit();
|
|
|
|
if (!driver->caps)
|
|
|
|
goto error;
|
|
|
|
|
2015-09-09 14:03:15 +00:00
|
|
|
/* Using /dev/null for libDir and cacheDir automatically produces errors
|
|
|
|
* upon attempt to use any of them */
|
2019-04-12 19:21:05 +00:00
|
|
|
driver->qemuCapsCache = virQEMUCapsCacheNew("/dev/null", "/dev/null", 0, 0);
|
2015-09-09 14:03:15 +00:00
|
|
|
if (!driver->qemuCapsCache)
|
|
|
|
goto error;
|
|
|
|
|
2022-07-08 19:30:30 +00:00
|
|
|
driver->nbdkitCapsCache = qemuNbdkitCapsCacheNew("/dev/null");
|
|
|
|
/* the nbdkitCapsCache just interprets the presence of a non-null private
|
|
|
|
* data pointer as a signal to skip cache validation. This prevents the
|
|
|
|
* cache from trying to validate the plugindir mtime, etc during test */
|
|
|
|
virFileCacheSetPriv(driver->nbdkitCapsCache, GUINT_TO_POINTER(1));
|
|
|
|
|
2019-11-26 18:57:30 +00:00
|
|
|
driver->xmlopt = virQEMUDriverCreateXMLConf(driver, "none");
|
2015-09-15 06:16:02 +00:00
|
|
|
if (!driver->xmlopt)
|
|
|
|
goto error;
|
|
|
|
|
2018-10-02 13:08:28 +00:00
|
|
|
if (!(mgr = virSecurityManagerNew("none", "qemu",
|
2016-03-23 15:19:26 +00:00
|
|
|
VIR_SECURITY_MANAGER_PRIVILEGED)))
|
|
|
|
goto error;
|
|
|
|
if (!(driver->securityManager = virSecurityManagerNewStack(mgr)))
|
|
|
|
goto error;
|
|
|
|
|
2024-07-03 21:40:13 +00:00
|
|
|
if (!(driver->domainEventState = virObjectEventStateNew()))
|
|
|
|
goto error;
|
|
|
|
|
2019-11-29 10:40:39 +00:00
|
|
|
qemuTestSetHostCPU(driver, driver->hostarch, NULL);
|
|
|
|
|
2023-03-02 17:31:00 +00:00
|
|
|
VIR_FREE(cfg->vncTLSx509certdir);
|
|
|
|
cfg->vncTLSx509certdir = g_strdup("/etc/pki/libvirt-vnc");
|
|
|
|
VIR_FREE(cfg->spiceTLSx509certdir);
|
|
|
|
cfg->spiceTLSx509certdir = g_strdup("/etc/pki/libvirt-spice");
|
|
|
|
VIR_FREE(cfg->chardevTLSx509certdir);
|
|
|
|
cfg->chardevTLSx509certdir = g_strdup("/etc/pki/libvirt-chardev");
|
|
|
|
VIR_FREE(cfg->vxhsTLSx509certdir);
|
2023-03-02 17:34:19 +00:00
|
|
|
cfg->vxhsTLSx509certdir = g_strdup("/etc/pki/libvirt-vxhs");
|
2023-03-02 17:31:00 +00:00
|
|
|
VIR_FREE(cfg->nbdTLSx509certdir);
|
2023-03-02 17:34:19 +00:00
|
|
|
cfg->nbdTLSx509certdir = g_strdup("/etc/pki/libvirt-nbd");
|
2023-03-02 17:35:31 +00:00
|
|
|
VIR_FREE(cfg->migrateTLSx509certdir);
|
|
|
|
cfg->migrateTLSx509certdir = g_strdup("/etc/pki/libvirt-migrate");
|
|
|
|
VIR_FREE(cfg->backupTLSx509certdir);
|
|
|
|
cfg->backupTLSx509certdir = g_strdup("/etc/pki/libvirt-backup");
|
2023-03-02 17:31:00 +00:00
|
|
|
|
|
|
|
VIR_FREE(cfg->vncSASLdir);
|
2023-03-02 19:40:55 +00:00
|
|
|
cfg->vncSASLdir = g_strdup("/etc/sasl2");
|
2023-03-02 17:31:00 +00:00
|
|
|
VIR_FREE(cfg->spiceSASLdir);
|
2023-03-02 19:40:55 +00:00
|
|
|
cfg->spiceSASLdir = g_strdup("/etc/sasl2");
|
2023-03-02 17:31:00 +00:00
|
|
|
|
|
|
|
VIR_FREE(cfg->spicePassword);
|
|
|
|
cfg->spicePassword = g_strdup("123456");
|
|
|
|
|
|
|
|
VIR_FREE(cfg->hugetlbfs);
|
|
|
|
cfg->hugetlbfs = g_new0(virHugeTLBFS, 2);
|
|
|
|
cfg->nhugetlbfs = 2;
|
|
|
|
cfg->hugetlbfs[0].mnt_dir = g_strdup("/dev/hugepages2M");
|
|
|
|
cfg->hugetlbfs[1].mnt_dir = g_strdup("/dev/hugepages1G");
|
|
|
|
cfg->hugetlbfs[0].size = 2048;
|
|
|
|
cfg->hugetlbfs[0].deflt = true;
|
|
|
|
cfg->hugetlbfs[1].size = 1048576;
|
|
|
|
|
2024-09-18 13:32:45 +00:00
|
|
|
cfg->dumpGuestCore = false;
|
|
|
|
|
2023-03-02 16:54:19 +00:00
|
|
|
driver->privileged = true;
|
|
|
|
|
2015-09-15 06:16:02 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2016-03-23 15:19:26 +00:00
|
|
|
virObjectUnref(mgr);
|
2015-09-15 06:16:02 +00:00
|
|
|
qemuTestDriverFree(driver);
|
|
|
|
return -1;
|
|
|
|
}
|
2015-09-09 14:03:14 +00:00
|
|
|
|
2016-05-10 09:35:43 +00:00
|
|
|
int
|
2021-03-11 07:16:13 +00:00
|
|
|
testQemuCapsSetGIC(virQEMUCaps *qemuCaps,
|
2016-05-10 09:35:43 +00:00
|
|
|
int gic)
|
|
|
|
{
|
|
|
|
virGICCapability *gicCapabilities = NULL;
|
|
|
|
size_t ngicCapabilities = 0;
|
|
|
|
|
2020-09-22 23:04:17 +00:00
|
|
|
gicCapabilities = g_new0(virGICCapability, 2);
|
2016-05-10 09:35:43 +00:00
|
|
|
|
|
|
|
# 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);
|
|
|
|
|
2020-01-06 21:57:49 +00:00
|
|
|
return 0;
|
2016-05-10 09:35:43 +00:00
|
|
|
}
|
|
|
|
|
2008-06-26 09:37:51 +00:00
|
|
|
#endif
|
2018-04-18 08:47:31 +00:00
|
|
|
|
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
struct testQemuCapsFile {
|
|
|
|
unsigned long long ver;
|
|
|
|
char *path;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
testQemuCapsFileFree(struct testQemuCapsFile *f)
|
|
|
|
{
|
|
|
|
if (!f)
|
|
|
|
return;
|
|
|
|
|
|
|
|
g_free(f->path);
|
|
|
|
g_free(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-18 08:47:31 +00:00
|
|
|
char *
|
2019-04-16 10:26:22 +00:00
|
|
|
testQemuGetLatestCapsForArch(const char *arch,
|
2018-04-18 08:47:31 +00:00
|
|
|
const char *suffix)
|
|
|
|
{
|
2023-03-07 15:01:35 +00:00
|
|
|
g_autoptr(GHashTable) caps = testQemuGetLatestCaps();
|
|
|
|
struct testQemuCapsFile *f;
|
2018-04-18 08:47:31 +00:00
|
|
|
|
2023-03-07 15:01:35 +00:00
|
|
|
if (!(f = g_hash_table_lookup(caps, arch))) {
|
2019-05-03 08:45:58 +00:00
|
|
|
VIR_TEST_VERBOSE("failed to find capabilities for '%s' in '%s'",
|
2019-04-16 10:26:22 +00:00
|
|
|
arch, TEST_QEMU_CAPS_PATH);
|
2020-10-27 21:49:11 +00:00
|
|
|
return NULL;
|
2018-04-18 08:47:31 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 15:01:35 +00:00
|
|
|
if (STRNEQ(suffix, "xml")) {
|
|
|
|
ignore_value(virStringStripSuffix(f->path, "xml"));
|
|
|
|
return g_strdup_printf("%s%s", f->path, suffix);
|
|
|
|
}
|
2019-03-07 14:54:55 +00:00
|
|
|
|
2023-03-07 15:01:35 +00:00
|
|
|
return g_steal_pointer(&f->path);
|
|
|
|
}
|
2019-03-07 14:54:55 +00:00
|
|
|
|
2020-10-22 17:04:18 +00:00
|
|
|
GHashTable *
|
2019-03-31 16:43:44 +00:00
|
|
|
testQemuGetLatestCaps(void)
|
|
|
|
{
|
2023-03-07 13:44:34 +00:00
|
|
|
g_autoptr(GHashTable) caps = virHashNew((GDestroyNotify)testQemuCapsFileFree);
|
|
|
|
struct dirent *ent;
|
|
|
|
g_autoptr(DIR) dir = NULL;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (virDirOpen(&dir, TEST_QEMU_CAPS_PATH) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
while ((rc = virDirRead(dir, &ent, TEST_QEMU_CAPS_PATH)) > 0) {
|
|
|
|
g_autofree char *version = NULL;
|
|
|
|
char *arch = NULL;
|
|
|
|
unsigned long long ver;
|
|
|
|
struct testQemuCapsFile *f;
|
|
|
|
|
|
|
|
if (!(version = g_strdup(STRSKIP(ent->d_name, "caps_"))))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!virStringStripSuffix(version, ".xml"))
|
|
|
|
continue;
|
2019-03-31 16:43:44 +00:00
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
if (!(arch = strchr(version, '_'))) {
|
|
|
|
VIR_TEST_VERBOSE("malformed caps file name '%s'", ent->d_name);
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-03-31 16:43:44 +00:00
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
*arch = '\0';
|
|
|
|
arch++;
|
2019-03-31 16:43:44 +00:00
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
if (virStringParseVersion(&ver, version, false) < 0) {
|
|
|
|
VIR_TEST_VERBOSE("malformed caps file name '%s'", ent->d_name);
|
2021-07-05 14:22:49 +00:00
|
|
|
return NULL;
|
2023-03-07 13:44:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(f = g_hash_table_lookup(caps, arch))) {
|
|
|
|
VIR_TEST_DEBUG("CAPS: '%s': 'X' -> '%llu'", arch, ver);
|
|
|
|
f = g_new0(struct testQemuCapsFile, 1);
|
|
|
|
f->ver = ver;
|
|
|
|
f->path = g_strdup_printf("%s/%s", TEST_QEMU_CAPS_PATH, ent->d_name);
|
|
|
|
g_hash_table_insert(caps, g_strdup(arch), f);
|
|
|
|
continue;
|
|
|
|
}
|
2019-03-31 16:43:44 +00:00
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
if (f->ver < ver) {
|
|
|
|
VIR_TEST_DEBUG("CAPS: '%s': '%llu' -> '%llu'", arch, f->ver, ver);
|
|
|
|
f->ver = ver;
|
|
|
|
g_free(f->path);
|
|
|
|
f->path = g_strdup_printf("%s/%s", TEST_QEMU_CAPS_PATH, ent->d_name);
|
|
|
|
}
|
2019-03-31 16:43:44 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
if (rc < 0)
|
|
|
|
return NULL;
|
2019-05-03 08:45:58 +00:00
|
|
|
|
2023-03-07 13:44:34 +00:00
|
|
|
return g_steal_pointer(&caps);
|
2019-03-31 16:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-07 14:54:55 +00:00
|
|
|
int
|
2019-04-16 10:33:14 +00:00
|
|
|
testQemuCapsIterate(const char *suffix,
|
2019-03-07 14:54:55 +00:00
|
|
|
testQemuCapsIterateCallback callback,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
struct dirent *ent;
|
2020-10-25 21:50:51 +00:00
|
|
|
g_autoptr(DIR) dir = NULL;
|
2019-03-07 14:54:55 +00:00
|
|
|
int rc;
|
2019-10-25 11:59:46 +00:00
|
|
|
bool fail = false;
|
2019-03-07 14:54:55 +00:00
|
|
|
|
|
|
|
if (!callback)
|
|
|
|
return 0;
|
|
|
|
|
2019-10-22 16:34:03 +00:00
|
|
|
/* Validate suffix */
|
|
|
|
if (!STRPREFIX(suffix, ".")) {
|
|
|
|
VIR_TEST_VERBOSE("malformed suffix '%s'", suffix);
|
2020-10-27 21:49:11 +00:00
|
|
|
return -1;
|
2019-10-22 16:34:03 +00:00
|
|
|
}
|
|
|
|
|
2019-04-16 10:33:14 +00:00
|
|
|
if (virDirOpen(&dir, TEST_QEMU_CAPS_PATH) < 0)
|
2020-10-27 21:49:11 +00:00
|
|
|
return -1;
|
2019-03-07 14:54:55 +00:00
|
|
|
|
2019-06-04 18:56:58 +00:00
|
|
|
while ((rc = virDirRead(dir, &ent, TEST_QEMU_CAPS_PATH)) > 0) {
|
2019-10-22 16:31:09 +00:00
|
|
|
g_autofree char *tmp = g_strdup(ent->d_name);
|
2019-10-22 14:08:10 +00:00
|
|
|
char *version = NULL;
|
2019-03-07 14:54:55 +00:00
|
|
|
char *archName = NULL;
|
2023-03-08 13:49:56 +00:00
|
|
|
g_autofree char *variant = NULL;
|
|
|
|
char *var;
|
2019-03-07 14:54:55 +00:00
|
|
|
|
|
|
|
/* Strip the trailing suffix, moving on if it's not present */
|
|
|
|
if (!virStringStripSuffix(tmp, suffix))
|
|
|
|
continue;
|
|
|
|
|
2019-10-22 14:08:10 +00:00
|
|
|
/* Strip the leading prefix */
|
|
|
|
if (!(version = STRSKIP(tmp, "caps_"))) {
|
|
|
|
VIR_TEST_VERBOSE("malformed file name '%s'", ent->d_name);
|
2020-10-27 21:49:11 +00:00
|
|
|
return -1;
|
2019-10-22 14:08:10 +00:00
|
|
|
}
|
|
|
|
|
2023-03-07 14:07:27 +00:00
|
|
|
/* Find the underscore separating version from arch */
|
|
|
|
if (!(archName = strchr(version, '_'))) {
|
2019-10-22 16:31:09 +00:00
|
|
|
VIR_TEST_VERBOSE("malformed file name '%s'", ent->d_name);
|
2020-10-27 21:49:11 +00:00
|
|
|
return -1;
|
2019-10-22 16:31:09 +00:00
|
|
|
}
|
2019-03-07 14:54:55 +00:00
|
|
|
|
2019-10-22 14:08:10 +00:00
|
|
|
/* The version number and the architecture name are separated by
|
2023-03-07 14:07:27 +00:00
|
|
|
* a underscore: overwriting that underscore with \0 results in both
|
|
|
|
* being usable as independent, null-terminated strings */
|
2019-03-07 14:54:55 +00:00
|
|
|
archName[0] = '\0';
|
|
|
|
archName++;
|
|
|
|
|
2023-03-08 13:49:56 +00:00
|
|
|
/* Find the 'variant' of the test and split it including the leading '+' */
|
|
|
|
if ((var = strchr(archName, '+'))) {
|
|
|
|
variant = g_strdup(var);
|
|
|
|
var[0] = '\0';
|
|
|
|
} else {
|
|
|
|
variant = g_strdup("");
|
|
|
|
}
|
|
|
|
|
2019-10-22 13:44:37 +00:00
|
|
|
/* 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.
|
|
|
|
*/
|
2019-10-22 14:08:10 +00:00
|
|
|
if (callback(TEST_QEMU_CAPS_PATH, "caps", version,
|
2023-03-08 13:49:56 +00:00
|
|
|
archName, variant, suffix + 1, opaque) < 0)
|
2019-10-25 11:59:46 +00:00
|
|
|
fail = true;
|
2019-03-07 14:54:55 +00:00
|
|
|
}
|
|
|
|
|
2019-10-25 11:59:46 +00:00
|
|
|
if (rc < 0 || fail)
|
2020-10-27 21:49:11 +00:00
|
|
|
return -1;
|
2019-03-07 14:54:55 +00:00
|
|
|
|
2020-10-27 21:49:11 +00:00
|
|
|
return 0;
|
2019-03-07 14:54:55 +00:00
|
|
|
}
|
2019-03-31 15:49:34 +00:00
|
|
|
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
void
|
2023-12-04 21:28:10 +00:00
|
|
|
testQemuInfoSetArgs(testQemuInfo *info,
|
2023-12-04 16:00:54 +00:00
|
|
|
va_list argptr)
|
2019-03-31 15:49:34 +00:00
|
|
|
{
|
|
|
|
testQemuInfoArgName argname;
|
|
|
|
int flag;
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
info->args.newargs = true;
|
2021-08-17 14:26:58 +00:00
|
|
|
|
2021-08-17 12:24:13 +00:00
|
|
|
while ((argname = va_arg(argptr, testQemuInfoArgName)) != ARG_END) {
|
2019-03-31 15:49:34 +00:00
|
|
|
switch (argname) {
|
|
|
|
case ARG_QEMU_CAPS:
|
2023-03-03 12:07:41 +00:00
|
|
|
if (!(info->args.fakeCapsAdd))
|
|
|
|
info->args.fakeCapsAdd = virBitmapNew(QEMU_CAPS_LAST);
|
2019-03-31 15:49:34 +00:00
|
|
|
|
|
|
|
while ((flag = va_arg(argptr, int)) < QEMU_CAPS_LAST)
|
2023-03-03 12:07:41 +00:00
|
|
|
ignore_value(virBitmapSetBit(info->args.fakeCapsAdd, flag));
|
2019-03-31 15:49:34 +00:00
|
|
|
break;
|
|
|
|
|
2023-03-03 12:11:51 +00:00
|
|
|
case ARG_QEMU_CAPS_DEL:
|
|
|
|
if (!(info->args.fakeCapsDel))
|
|
|
|
info->args.fakeCapsDel = virBitmapNew(QEMU_CAPS_LAST);
|
|
|
|
|
|
|
|
while ((flag = va_arg(argptr, int)) < QEMU_CAPS_LAST)
|
|
|
|
ignore_value(virBitmapSetBit(info->args.fakeCapsDel, flag));
|
|
|
|
break;
|
|
|
|
|
2022-07-08 19:30:30 +00:00
|
|
|
case ARG_NBDKIT_CAPS:
|
|
|
|
if (!(info->args.fakeNbdkitCaps))
|
|
|
|
info->args.fakeNbdkitCaps = virBitmapNew(QEMU_NBDKIT_CAPS_LAST);
|
|
|
|
|
|
|
|
while ((flag = va_arg(argptr, int)) < QEMU_NBDKIT_CAPS_LAST)
|
|
|
|
ignore_value(virBitmapSetBit(info->args.fakeNbdkitCaps, flag));
|
|
|
|
break;
|
|
|
|
|
2019-03-31 15:49:34 +00:00
|
|
|
case ARG_GIC:
|
2021-08-17 13:30:44 +00:00
|
|
|
info->args.gic = va_arg(argptr, int);
|
2019-03-31 15:49:34 +00:00
|
|
|
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:
|
2021-08-17 13:30:44 +00:00
|
|
|
info->args.capsarch = va_arg(argptr, char *);
|
2019-03-31 15:49:34 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ARG_CAPS_VER:
|
2021-08-17 13:30:44 +00:00
|
|
|
info->args.capsver = va_arg(argptr, char *);
|
2019-03-31 15:49:34 +00:00
|
|
|
break;
|
|
|
|
|
2023-03-08 16:23:05 +00:00
|
|
|
case ARG_CAPS_VARIANT:
|
|
|
|
info->args.capsvariant = va_arg(argptr, char *);
|
|
|
|
break;
|
|
|
|
|
testutilsqemu: introduce ARG_CAPS_HOST_CPU_MODEL
When loading a latest caps for an arch for the first time the following
occurs in testQemuInfoInitArgs():
- the caps file is located. It's not in the cache since it's the first time
it's being read;
- the cachecaps are retrieved using qemuTestParseCapabilitiesArch() and
stored in the capscache;
- FLAG_REAL_CAPS is set and regular flow continues.
Loading the same latest caps for the second time the caps are loaded from the
cache, skipping qemuTestParseCapabilitiesArch(). By skipping this function it
means that it also skips virQEMUCapsLoadCache() and, more relevant to
our case, virQEMUCapsInitHostCPUModel(). This function will use the
current arch and cpuModel settings to write the qemuCaps that are being
stored in the cache. And we're also setting FLAG_REAL_CAPS, meaning that
we won't be updating the qemucaps host model via testUpdateQEMUCaps() as
well.
This has side-effects such as:
- the first time the latest caps for an arch is loaded determines the
cpuModel it'll use during the current qemuxml2argvtest run. For
example, when running all tests, the first time the latest ppc64 caps
are read is on "disk-floppy-pseries" test. Since the current host arch
at this point is x86_64, the cpuModel that will be set for this
capability is "core2duo";
- every other latest arch test will use the same hostCPU as the first
one set since we read it from the cache after the first run.
qemuTestSetHostCPU() makes no difference because we won't update the
host model due to FLAG_REAL_CAPS being set. Using the previous example,
every other latest ppc64 test that will be run will be using the
"core2duo" cpuModel.
Using fake capabilities (e.g. using DO_TEST()) prevents FLAG_REAL_CAPS to
be set, meaning that the cpuModel will be updated using the current
settings the test is being ran due to testUpdateQEMUCaps().
Note that not all latest caps arch tests care about the cpuModel being
set to an unexpected default cpuModel. But some tests will care, e.g.
"pseries-cpu-compat-power9", and changing it from DO_TEST() to
DO_TEST_CAPS_ARCH_LATEST() will make it fail every time the
"disk-floppy-pseries" is being ran first.
One way of fixing it is to rethink all the existing logic, for example
not setting FLAG_REAL_CAPS for latest arch tests. Another way is
presented here. ARGS_CAPS_HOST_CPU_MODEL is a new testQemuInfo arg that
allow us to set any specific host CPU model we want when running latest
arch caps tests. This new arg can then be used when converting existing
DO_TEST() testcases to DO_TEST_CAPS_ARCH_LATEST() that requires a
specific host CPU setting to be successful, which we're going to do in
the next patch with "pseries-cpu-compat-power9".
Reviewed-by: Martin Kletzander <mkletzan@redhat.com>
Signed-off-by: Daniel Henrique Barboza <danielhb413@gmail.com>
2022-05-20 13:58:23 +00:00
|
|
|
case ARG_CAPS_HOST_CPU_MODEL:
|
|
|
|
info->args.capsHostCPUModel = va_arg(argptr, int);
|
|
|
|
break;
|
|
|
|
|
2022-10-11 13:52:54 +00:00
|
|
|
case ARG_FD_GROUP: {
|
|
|
|
virStorageSourceFDTuple *new = virStorageSourceFDTupleNew();
|
|
|
|
const char *fdname = va_arg(argptr, char *);
|
|
|
|
VIR_AUTOCLOSE fakefd = open("/dev/zero", O_RDWR);
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
new->nfds = va_arg(argptr, unsigned int);
|
|
|
|
new->fds = g_new0(int, new->nfds);
|
|
|
|
new->testfds = g_new0(int, new->nfds);
|
|
|
|
|
|
|
|
for (i = 0; i < new->nfds; i++) {
|
|
|
|
new->testfds[i] = va_arg(argptr, unsigned int);
|
|
|
|
|
|
|
|
if (fcntl(new->testfds[i], F_GETFD) != -1) {
|
|
|
|
fprintf(stderr, "fd '%d' is already in use\n", new->fds[i]);
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((new->fds[i] = dup(fakefd)) < 0) {
|
|
|
|
fprintf(stderr, "failed to duplicate fake fd: %s",
|
|
|
|
g_strerror(errno));
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!info->args.fds)
|
|
|
|
info->args.fds = virHashNew(g_object_unref);
|
|
|
|
|
|
|
|
g_hash_table_insert(info->args.fds, g_strdup(fdname), new);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2023-03-17 14:58:36 +00:00
|
|
|
case ARG_VDPA_FD: {
|
|
|
|
const char *vdpadev = va_arg(argptr, char *);
|
|
|
|
int vdpafd = va_arg(argptr, unsigned int);
|
|
|
|
|
|
|
|
if (!info->args.vdpafds)
|
|
|
|
info->args.vdpafds = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
|
|
|
|
g_hash_table_insert(info->args.vdpafds, g_strdup(vdpadev), GINT_TO_POINTER(vdpafd));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-03-31 15:49:34 +00:00
|
|
|
case ARG_END:
|
|
|
|
default:
|
2021-08-17 13:30:44 +00:00
|
|
|
info->args.invalidarg = true;
|
|
|
|
break;
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
2021-08-17 13:30:44 +00:00
|
|
|
|
|
|
|
if (info->args.invalidarg)
|
|
|
|
break;
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
2021-08-17 13:30:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-03-09 13:24:45 +00:00
|
|
|
/**
|
2023-07-03 12:50:42 +00:00
|
|
|
* See testQemuGetRealCaps, this helper returns the pointer to the virQEMUCaps
|
|
|
|
* object as stored in the cache hash table.
|
2023-03-09 13:24:45 +00:00
|
|
|
*/
|
2023-07-03 12:50:42 +00:00
|
|
|
static virQEMUCaps *
|
|
|
|
testQemuGetRealCapsInternal(const char *arch,
|
|
|
|
const char *version,
|
|
|
|
const char *variant,
|
|
|
|
GHashTable *capsLatestFiles,
|
|
|
|
GHashTable *capsCache,
|
|
|
|
GHashTable *schemaCache,
|
|
|
|
GHashTable **schema)
|
2023-03-09 13:24:45 +00:00
|
|
|
{
|
|
|
|
g_autofree char *capsfile = NULL;
|
|
|
|
bool stripmachinealiases = false;
|
|
|
|
virQEMUCaps *cachedcaps = NULL;
|
|
|
|
|
|
|
|
if (STREQ(version, "latest")) {
|
|
|
|
g_autofree char *archvariant = g_strdup_printf("%s%s", arch, variant);
|
|
|
|
struct testQemuCapsFile *f = g_hash_table_lookup(capsLatestFiles, archvariant);
|
|
|
|
|
|
|
|
if (!f) {
|
|
|
|
VIR_TEST_VERBOSE("'latest' caps for '%s' were not found\n", arch);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
capsfile = g_strdup(f->path);
|
|
|
|
stripmachinealiases = true;
|
|
|
|
} else {
|
|
|
|
capsfile = g_strdup_printf("%s/caps_%s_%s%s.xml",
|
|
|
|
TEST_QEMU_CAPS_PATH,
|
|
|
|
version, arch, variant);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!g_hash_table_lookup_extended(capsCache, capsfile, NULL, (void **) &cachedcaps)) {
|
|
|
|
if (!(cachedcaps = qemuTestParseCapabilitiesArch(virArchFromString(arch), capsfile))) {
|
|
|
|
VIR_TEST_VERBOSE("Failed to parse qemu capabilities file '%s'", capsfile);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-07-03 12:41:38 +00:00
|
|
|
if (stripmachinealiases)
|
|
|
|
virQEMUCapsStripMachineAliases(cachedcaps);
|
|
|
|
|
2023-03-09 13:24:45 +00:00
|
|
|
g_hash_table_insert(capsCache, g_strdup(capsfile), cachedcaps);
|
|
|
|
}
|
|
|
|
|
2023-03-09 14:15:40 +00:00
|
|
|
/* strip 'xml' suffix so that we can format the file to '.replies' */
|
|
|
|
capsfile[strlen(capsfile) - 3] = '\0';
|
|
|
|
|
|
|
|
if (schemaCache && schema) {
|
|
|
|
g_autofree char *schemafile = g_strdup_printf("%sreplies", capsfile);
|
|
|
|
|
|
|
|
if (!g_hash_table_lookup_extended(schemaCache, schemafile, NULL, (void **) schema)) {
|
|
|
|
*schema = testQEMUSchemaLoad(schemafile);
|
|
|
|
g_hash_table_insert(schemaCache, g_strdup(schemafile), *schema);
|
|
|
|
}
|
2023-03-09 13:24:45 +00:00
|
|
|
}
|
|
|
|
|
2023-07-03 12:50:42 +00:00
|
|
|
return cachedcaps;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* testQemuGetRealCaps:
|
|
|
|
*
|
|
|
|
* @arch: architecture to fetch caps for
|
|
|
|
* @version: qemu version to fetch caps for ("latest" for fetching the latest version from @capsLatestFiles)
|
|
|
|
* @variant: capabilities variant to fetch caps for
|
|
|
|
* @capsLatestFiles: hash table containing latest version of capabilities for the @arch+@variant tuple
|
|
|
|
* @capsCache: hash table filled with the cache of capabilities
|
|
|
|
* @schemaCache: hash table for caching QMP schemas (may be NULL, see below)
|
|
|
|
* @schema: Filled with the QMP schema (hash table) (may be NULL, see below)
|
|
|
|
*
|
|
|
|
* Fetches and returns the appropriate virQEMUCaps for the @arch+@version+@variant
|
|
|
|
* tuple. The returned pointer is a copy of the cached object and thus can
|
|
|
|
* be freely modified. Caller is responsible for freeing it.
|
|
|
|
*
|
|
|
|
* If @schemaCache and @schema are non-NULL, @schema is filled with with a
|
|
|
|
* pointer (borrowed from the cache) to the hash table representing the QEMU QMP
|
|
|
|
* schema used for validation of the monitor traffic.
|
|
|
|
*/
|
|
|
|
virQEMUCaps *
|
|
|
|
testQemuGetRealCaps(const char *arch,
|
|
|
|
const char *version,
|
|
|
|
const char *variant,
|
|
|
|
GHashTable *capsLatestFiles,
|
|
|
|
GHashTable *capsCache,
|
|
|
|
GHashTable *schemaCache,
|
|
|
|
GHashTable **schema)
|
|
|
|
{
|
|
|
|
virQEMUCaps *cachedcaps;
|
|
|
|
|
|
|
|
if (!(cachedcaps = testQemuGetRealCapsInternal(arch, version, variant,
|
|
|
|
capsLatestFiles, capsCache,
|
|
|
|
schemaCache, schema)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return virQEMUCapsNewCopy(cachedcaps);
|
2023-03-09 13:24:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-07-03 13:25:30 +00:00
|
|
|
/**
|
|
|
|
* testQemuInsertRealCaps:
|
|
|
|
*
|
|
|
|
* @arch: architecture to fetch caps for
|
|
|
|
* @version: qemu version to fetch caps for ("latest" for fetching the latest version from @capsLatestFiles)
|
|
|
|
* @variant: capabilities variant to fetch caps for
|
|
|
|
* @capsLatestFiles: hash table containing latest version of capabilities for the @arch+@variant tuple
|
|
|
|
* @capsCache: hash table filled with the cache of capabilities
|
|
|
|
* @schemaCache: hash table for caching QMP schemas (may be NULL, see below)
|
|
|
|
* @schema: Filled with the QMP schema (hash table) (may be NULL, see below)
|
|
|
|
*
|
|
|
|
* Fetches and inserts into the test capability cache the appropriate virQEMUCaps
|
|
|
|
* for the @arch+@version+@variant tuple. Note that the data inserted into
|
|
|
|
* the cache is borrowed from the cache thus must not be further modified.
|
|
|
|
*
|
|
|
|
* If @schemaCache and @schema are non-NULL, @schema is filled with with a
|
|
|
|
* pointer (borrowed from the cache) to the hash table representing the QEMU QMP
|
|
|
|
* schema used for validation of the monitor traffic.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
testQemuInsertRealCaps(virFileCache *cache,
|
|
|
|
const char *arch,
|
|
|
|
const char *version,
|
|
|
|
const char *variant,
|
|
|
|
GHashTable *capsLatestFiles,
|
|
|
|
GHashTable *capsCache,
|
|
|
|
GHashTable *schemaCache,
|
|
|
|
GHashTable **schema)
|
|
|
|
{
|
|
|
|
virQEMUCaps *cachedcaps;
|
|
|
|
|
|
|
|
virFileCacheClear(cache);
|
|
|
|
|
|
|
|
if (!(cachedcaps = testQemuGetRealCapsInternal(arch, version, variant,
|
|
|
|
capsLatestFiles, capsCache,
|
|
|
|
schemaCache, schema)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (qemuTestCapsCacheInsertData(cache, virQEMUCapsGetBinary(cachedcaps), cachedcaps) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
int
|
2023-12-04 21:28:10 +00:00
|
|
|
testQemuInfoInitArgs(testQemuInfo *info)
|
2021-08-17 13:30:44 +00:00
|
|
|
{
|
2023-03-03 12:07:41 +00:00
|
|
|
ssize_t cap;
|
2021-08-17 13:30:44 +00:00
|
|
|
|
|
|
|
if (!info->args.newargs)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
info->args.newargs = false;
|
|
|
|
|
2023-03-08 16:23:05 +00:00
|
|
|
if (!info->args.capsvariant)
|
|
|
|
info->args.capsvariant = "";
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
if (info->args.invalidarg) {
|
2021-08-25 12:50:25 +00:00
|
|
|
fprintf(stderr, "Invalid argument encountered by 'testQemuInfoSetArgs'\n");
|
2021-08-17 13:30:44 +00:00
|
|
|
return -1;
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
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) {
|
|
|
|
info->arch = virArchFromString(info->args.capsarch);
|
2019-03-31 15:49:34 +00:00
|
|
|
info->flags |= FLAG_REAL_CAPS;
|
2023-03-09 13:24:45 +00:00
|
|
|
info->qemuCaps = testQemuGetRealCaps(info->args.capsarch,
|
|
|
|
info->args.capsver,
|
|
|
|
info->args.capsvariant,
|
|
|
|
info->conf->capslatest,
|
|
|
|
info->conf->capscache,
|
2023-03-09 14:15:40 +00:00
|
|
|
info->conf->qapiSchemaCache,
|
|
|
|
&info->qmpSchema);
|
2023-03-09 13:24:45 +00:00
|
|
|
|
|
|
|
if (!info->qemuCaps)
|
|
|
|
return -1;
|
2021-08-16 14:42:18 +00:00
|
|
|
} else {
|
2023-03-03 12:07:41 +00:00
|
|
|
info->qemuCaps = virQEMUCapsNew();
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
|
|
|
|
2023-03-03 12:07:41 +00:00
|
|
|
for (cap = -1; (cap = virBitmapNextSetBit(info->args.fakeCapsAdd, cap)) >= 0;)
|
|
|
|
virQEMUCapsSet(info->qemuCaps, cap);
|
|
|
|
|
2023-03-03 12:11:51 +00:00
|
|
|
for (cap = -1; (cap = virBitmapNextSetBit(info->args.fakeCapsDel, cap)) >= 0;)
|
|
|
|
virQEMUCapsClear(info->qemuCaps, cap);
|
|
|
|
|
2022-07-08 19:30:30 +00:00
|
|
|
info->nbdkitCaps = qemuNbdkitCapsNew(TEST_NBDKIT_PATH);
|
|
|
|
|
|
|
|
for (cap = -1; (cap = virBitmapNextSetBit(info->args.fakeNbdkitCaps, cap)) >= 0;)
|
|
|
|
qemuNbdkitCapsSet(info->nbdkitCaps, cap);
|
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
if (info->args.gic != GIC_NONE &&
|
|
|
|
testQemuCapsSetGIC(info->qemuCaps, info->args.gic) < 0)
|
|
|
|
return -1;
|
2019-03-31 15:49:34 +00:00
|
|
|
|
2021-08-17 13:30:44 +00:00
|
|
|
return 0;
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2023-12-04 16:00:54 +00:00
|
|
|
testQemuInfoFree(testQemuInfo *info)
|
2019-03-31 15:49:34 +00:00
|
|
|
{
|
|
|
|
VIR_FREE(info->infile);
|
|
|
|
VIR_FREE(info->outfile);
|
2023-12-15 15:30:20 +00:00
|
|
|
VIR_FREE(info->out_xml_active);
|
|
|
|
VIR_FREE(info->out_xml_inactive);
|
2020-09-22 11:57:39 +00:00
|
|
|
VIR_FREE(info->errfile);
|
2023-12-15 15:30:20 +00:00
|
|
|
virDomainDefFree(info->def);
|
2019-03-31 15:49:34 +00:00
|
|
|
virObjectUnref(info->qemuCaps);
|
2023-03-03 12:07:41 +00:00
|
|
|
g_clear_pointer(&info->args.fakeCapsAdd, virBitmapFree);
|
2023-03-03 12:11:51 +00:00
|
|
|
g_clear_pointer(&info->args.fakeCapsDel, virBitmapFree);
|
2022-10-11 13:52:54 +00:00
|
|
|
g_clear_pointer(&info->args.fds, g_hash_table_unref);
|
2024-05-03 12:37:23 +00:00
|
|
|
g_clear_pointer(&info->args.vdpafds, g_hash_table_unref);
|
2022-07-08 19:30:30 +00:00
|
|
|
g_clear_object(&info->nbdkitCaps);
|
|
|
|
g_clear_pointer(&info->args.fakeNbdkitCaps, virBitmapFree);
|
2023-12-04 16:00:54 +00:00
|
|
|
g_free(info);
|
2019-03-31 15:49:34 +00:00
|
|
|
}
|
2022-02-09 11:30:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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:
|
2022-03-18 20:51:39 +00:00
|
|
|
case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
|
2022-05-13 18:38:17 +00:00
|
|
|
case VIR_DOMAIN_CHR_TYPE_DBUS:
|
2022-02-09 11:30:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_FILE:
|
|
|
|
fakesourcefd = 1750;
|
2022-05-16 14:08:18 +00:00
|
|
|
|
|
|
|
if (fcntl(fakesourcefd, F_GETFD) != -1)
|
|
|
|
abort();
|
|
|
|
|
|
|
|
charpriv->sourcefd = qemuFDPassNew(devalias, priv);
|
|
|
|
qemuFDPassAddFD(charpriv->sourcefd, &fakesourcefd, "-source");
|
2022-02-09 11:30:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_DOMAIN_CHR_TYPE_UNIX:
|
2022-05-16 14:08:18 +00:00
|
|
|
if (chardev->data.nix.listen) {
|
|
|
|
g_autofree char *name = g_strdup_printf("%s-source", devalias);
|
2022-02-09 11:30:25 +00:00
|
|
|
fakesourcefd = 1729;
|
|
|
|
|
2022-05-16 14:08:18 +00:00
|
|
|
charpriv->directfd = qemuFDPassDirectNew(name, &fakesourcefd);
|
|
|
|
}
|
|
|
|
|
2022-02-09 11:30:25 +00:00
|
|
|
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);
|
|
|
|
|
2022-05-04 12:12:58 +00:00
|
|
|
qemuFDPassAddFD(charpriv->logfd, &fd, "-log");
|
2022-02-09 11:30:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|