diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 4ad429a769..f6d26b04c8 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -6539,62 +6539,18 @@ static int qemuBuildCpuModelArgStr(virQEMUDriverPtr driver, const virDomainDef *def, virBufferPtr buf, - virQEMUCapsPtr qemuCaps, - bool *hasHwVirt, - bool migrating) + virQEMUCapsPtr qemuCaps) { int ret = -1; size_t i; - virCPUDefPtr host = NULL; - virCPUDefPtr guest = NULL; - virCPUDefPtr cpu = NULL; - virCPUDefPtr featCpu = NULL; - size_t ncpus = 0; - char **cpus = NULL; - virCPUDataPtr data = NULL; - const char *preferred; virCapsPtr caps = NULL; - bool compareAgainstHost = ((def->virtType == VIR_DOMAIN_VIRT_KVM || - def->cpu->mode != VIR_CPU_MODE_CUSTOM) && - def->cpu->mode != VIR_CPU_MODE_HOST_PASSTHROUGH); + virCPUDefPtr cpu = def->cpu; if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; - host = caps->host.cpu; - - if (!(cpu = virCPUDefCopy(def->cpu))) - goto cleanup; - - if (cpu->mode == VIR_CPU_MODE_HOST_MODEL && - !migrating && - virCPUUpdate(def->os.arch, cpu, host) < 0) - goto cleanup; - - if (compareAgainstHost && - cpuGuestData(host, cpu, &data, NULL) == VIR_CPU_COMPARE_ERROR) - goto cleanup; - - /* Only 'svm' requires --enable-nesting. The nested - * 'vmx' patches now simply hook off the CPU features - */ - if ((def->os.arch == VIR_ARCH_X86_64 || def->os.arch == VIR_ARCH_I686) && - compareAgainstHost) { - int hasSVM = virCPUDataCheckFeature(data, "svm"); - if (hasSVM < 0) - goto cleanup; - *hasHwVirt = hasSVM > 0 ? true : false; - } - - if ((cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) || - ((cpu->mode == VIR_CPU_MODE_HOST_MODEL) && - ARCH_IS_PPC64(def->os.arch))) { - if (def->virtType != VIR_DOMAIN_VIRT_KVM) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("CPU mode '%s' is only supported with kvm"), - virCPUModeTypeToString(cpu->mode)); - goto cleanup; - } + switch ((virCPUMode) cpu->mode) { + case VIR_CPU_MODE_HOST_PASSTHROUGH: virBufferAddLit(buf, "host"); if (def->os.arch == VIR_ARCH_ARMV7L && @@ -6606,69 +6562,55 @@ qemuBuildCpuModelArgStr(virQEMUDriverPtr driver, "aarch64 host")); goto cleanup; } - virBufferAddLit(buf, ",aarch64=off"); } + break; - if (ARCH_IS_PPC64(def->os.arch) && - cpu->mode == VIR_CPU_MODE_HOST_MODEL && - def->cpu->model != NULL) { - virBufferAsprintf(buf, ",compat=%s", def->cpu->model); + case VIR_CPU_MODE_HOST_MODEL: + if (ARCH_IS_PPC64(def->os.arch)) { + virBufferAddLit(buf, "host"); + if (cpu->model) + virBufferAsprintf(buf, ",compat=%s", cpu->model); } else { - featCpu = cpu; - } - } else { - if (VIR_ALLOC(guest) < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected host-model CPU for %s architecture"), + virArchToString(def->os.arch)); goto cleanup; - if (VIR_STRDUP(guest->vendor_id, cpu->vendor_id) < 0) - goto cleanup; - - if (compareAgainstHost) { - guest->arch = host->arch; - if (cpu->match == VIR_CPU_MATCH_MINIMUM) - preferred = host->model; - else - preferred = cpu->model; - - guest->type = VIR_CPU_TYPE_GUEST; - guest->fallback = cpu->fallback; - - if (virQEMUCapsGetCPUDefinitions(qemuCaps, &cpus, &ncpus) < 0) - goto cleanup; - - if (cpuDecode(guest, data, - (const char **)cpus, ncpus, preferred) < 0) - goto cleanup; - } else { - guest->arch = def->os.arch; - if (VIR_STRDUP(guest->model, cpu->model) < 0) - goto cleanup; } - virBufferAdd(buf, guest->model, -1); - if (guest->vendor_id) - virBufferAsprintf(buf, ",vendor=%s", guest->vendor_id); - featCpu = guest; + break; + + case VIR_CPU_MODE_CUSTOM: + virBufferAdd(buf, cpu->model, -1); + break; + + case VIR_CPU_MODE_LAST: + break; } - if (featCpu) { - for (i = 0; i < featCpu->nfeatures; i++) { - char sign; - if (featCpu->features[i].policy == VIR_CPU_FEATURE_DISABLE) - sign = '-'; - else - sign = '+'; + if (cpu->vendor_id) + virBufferAsprintf(buf, ",vendor=%s", cpu->vendor_id); - virBufferAsprintf(buf, ",%c%s", sign, featCpu->features[i].name); + for (i = 0; i < cpu->nfeatures; i++) { + switch ((virCPUFeaturePolicy) cpu->features[i].policy) { + case VIR_CPU_FEATURE_FORCE: + case VIR_CPU_FEATURE_REQUIRE: + virBufferAsprintf(buf, ",+%s", cpu->features[i].name); + break; + + case VIR_CPU_FEATURE_DISABLE: + case VIR_CPU_FEATURE_FORBID: + virBufferAsprintf(buf, ",-%s", cpu->features[i].name); + break; + + case VIR_CPU_FEATURE_OPTIONAL: + case VIR_CPU_FEATURE_LAST: + break; } } ret = 0; cleanup: virObjectUnref(caps); - cpuDataFree(data); - virCPUDefFree(guest); - virCPUDefFree(cpu); - virStringFreeListCount(cpus, ncpus); return ret; } @@ -6676,8 +6618,7 @@ static int qemuBuildCpuCommandLine(virCommandPtr cmd, virQEMUDriverPtr driver, const virDomainDef *def, - virQEMUCapsPtr qemuCaps, - bool migrating) + virQEMUCapsPtr qemuCaps) { virArch hostarch = virArchFromHost(); char *cpu = NULL; @@ -6695,10 +6636,28 @@ qemuBuildCpuCommandLine(virCommandPtr cmd, if (def->cpu && (def->cpu->mode != VIR_CPU_MODE_CUSTOM || def->cpu->model)) { - if (qemuBuildCpuModelArgStr(driver, def, &buf, qemuCaps, - &hasHwVirt, migrating) < 0) + if (qemuBuildCpuModelArgStr(driver, def, &buf, qemuCaps) < 0) goto cleanup; have_cpu = true; + + /* Only 'svm' requires --enable-nesting. The nested 'vmx' patches now + * simply hook off the CPU features. */ + if (ARCH_IS_X86(def->os.arch) && + def->virtType == VIR_DOMAIN_VIRT_KVM) { + virCPUDefPtr cpuDef = NULL; + + if (def->cpu->mode == VIR_CPU_MODE_CUSTOM) + cpuDef = def->cpu; + else if (def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) + cpuDef = virQEMUCapsGetHostModel(qemuCaps); + + if (cpuDef) { + int svm = virCPUCheckFeature(def->os.arch, cpuDef, "svm"); + if (svm < 0) + goto cleanup; + hasHwVirt = svm > 0; + } + } } else { /* * Need to force a 32-bit guest CPU type if @@ -9430,7 +9389,7 @@ qemuBuildCommandLine(virQEMUDriverPtr driver, if (qemuBuildMachineCommandLine(cmd, cfg, def, qemuCaps) < 0) goto error; - if (qemuBuildCpuCommandLine(cmd, driver, def, qemuCaps, !!migrateURI) < 0) + if (qemuBuildCpuCommandLine(cmd, driver, def, qemuCaps) < 0) goto error; if (qemuBuildDomainLoaderCommandLine(cmd, def, qemuCaps) < 0) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 537024f22e..1fbad91714 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -3399,14 +3399,8 @@ qemuDomainDefFormatBuf(virQEMUDriverPtr driver, /* Update guest CPU requirements according to host CPU */ if ((flags & VIR_DOMAIN_XML_UPDATE_CPU) && def->cpu && - (def->cpu->mode != VIR_CPU_MODE_CUSTOM || def->cpu->model)) { - if (!caps->host.cpu || - !caps->host.cpu->model) { - virReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("cannot get host CPU capabilities")); - goto cleanup; - } - + (def->cpu->mode != VIR_CPU_MODE_CUSTOM || + def->cpu->model)) { if (virCPUUpdate(def->os.arch, def->cpu, caps->host.cpu) < 0) goto cleanup; } @@ -3542,10 +3536,13 @@ char *qemuDomainFormatXML(virQEMUDriverPtr driver, { virDomainDefPtr def; - if ((flags & VIR_DOMAIN_XML_INACTIVE) && vm->newDef) + if ((flags & VIR_DOMAIN_XML_INACTIVE) && vm->newDef) { def = vm->newDef; - else + } else { def = vm->def; + if (virDomainObjIsActive(vm)) + flags &= ~VIR_DOMAIN_XML_UPDATE_CPU; + } return qemuDomainDefFormatXML(driver, def, flags); } diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 0414f96aab..27d04a4346 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4443,9 +4443,6 @@ qemuProcessStartValidateGraphics(virDomainObjPtr vm) } } - if (qemuProcessStartValidateGuestCPU(vm, qemuCaps, caps, flags) < 0) - return -1; - return 0; } @@ -4479,107 +4476,6 @@ qemuProcessStartValidateXML(virQEMUDriverPtr driver, } -static int -qemuProcessStartValidateGuestCPU(virDomainObjPtr vm, - virQEMUCapsPtr qemuCaps, - virCapsPtr caps, - unsigned int flags) -{ - int ret = -1; - virCPUDefPtr host = NULL; - virCPUDefPtr cpu = NULL; - size_t ncpus = 0; - char **cpus = NULL; - bool noTSX = false; - virCPUCompareResult cmp; - virCPUDataPtr data = NULL; - virCPUDataPtr hostData = NULL; - char *compare_msg = NULL; - - if (!vm->def->cpu || - (vm->def->cpu->mode == VIR_CPU_MODE_CUSTOM && - !vm->def->cpu->model)) - return 0; - - if ((vm->def->virtType != VIR_DOMAIN_VIRT_KVM && - vm->def->cpu->mode == VIR_CPU_MODE_CUSTOM) || - vm->def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) - return 0; - - host = caps->host.cpu; - - if (virQEMUCapsGetCPUDefinitions(qemuCaps, &cpus, &ncpus) < 0) - goto cleanup; - - if (!host || !host->model || ncpus == 0) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("CPU specification not supported by hypervisor")); - goto cleanup; - } - - if (!(cpu = virCPUDefCopy(vm->def->cpu))) - goto cleanup; - - if (cpu->mode == VIR_CPU_MODE_HOST_MODEL && - flags & VIR_QEMU_PROCESS_START_NEW && - virCPUUpdate(vm->def->os.arch, cpu, host) < 0) - goto cleanup; - - cmp = cpuGuestData(host, cpu, &data, &compare_msg); - switch (cmp) { - case VIR_CPU_COMPARE_INCOMPATIBLE: - if (cpuEncode(host->arch, host, NULL, &hostData, - NULL, NULL, NULL, NULL) == 0 && - (!virCPUDataCheckFeature(hostData, "hle") || - !virCPUDataCheckFeature(hostData, "rtm")) && - (STREQ_NULLABLE(cpu->model, "Haswell") || - STREQ_NULLABLE(cpu->model, "Broadwell"))) - noTSX = true; - - if (compare_msg) { - if (noTSX) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("guest and host CPU are not compatible: " - "%s; try using '%s-noTSX' CPU model"), - compare_msg, cpu->model); - } else { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("guest and host CPU are not compatible: " - "%s"), - compare_msg); - } - } else { - if (noTSX) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("guest CPU is not compatible with host " - "CPU; try using '%s-noTSX' CPU model"), - cpu->model); - } else { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("guest CPU is not compatible with host " - "CPU")); - } - } - /* fall through */ - case VIR_CPU_COMPARE_ERROR: - goto cleanup; - - default: - break; - } - - ret = 0; - - cleanup: - VIR_FREE(compare_msg); - cpuDataFree(data); - cpuDataFree(hostData); - virCPUDefFree(cpu); - virStringFreeListCount(cpus, ncpus); - return ret; -} - - /** * qemuProcessStartValidate: * @vm: domain object @@ -5052,6 +4948,76 @@ qemuProcessSetupHotpluggableVcpus(virQEMUDriverPtr driver, } +static int +qemuProcessUpdateGuestCPU(virDomainDefPtr def, + virQEMUCapsPtr qemuCaps, + virCapsPtr caps, + unsigned int flags) +{ + int ret = -1; + size_t nmodels = 0; + char **models = NULL; + + if (!def->cpu) + return 0; + + /* nothing to do if only topology part of CPU def is used */ + if (def->cpu->mode == VIR_CPU_MODE_CUSTOM && !def->cpu->model) + return 0; + + /* Old libvirt added host CPU model to host-model CPUs for migrations, + * while new libvirt just turns host-model into custom mode. We need + * to fix the mode to maintain backward compatibility and to avoid + * the CPU model to be replaced in virCPUUpdate. + */ + if (!(flags & VIR_QEMU_PROCESS_START_NEW) && + ARCH_IS_X86(def->os.arch) && + def->cpu->mode == VIR_CPU_MODE_HOST_MODEL && + def->cpu->model) { + def->cpu->mode = VIR_CPU_MODE_CUSTOM; + } + + if (!virQEMUCapsIsCPUModeSupported(qemuCaps, caps, def->virtType, + def->cpu->mode)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("CPU mode '%s' for %s %s domain on %s host is not " + "supported by hypervisor"), + virCPUModeTypeToString(def->cpu->mode), + virArchToString(def->os.arch), + virDomainVirtTypeToString(def->virtType), + virArchToString(caps->host.arch)); + return -1; + } + + /* nothing to update for host-passthrough */ + if (def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) + return 0; + + /* custom CPUs in TCG mode don't need to be compared to host CPU */ + if (def->virtType != VIR_DOMAIN_VIRT_QEMU || + def->cpu->mode != VIR_CPU_MODE_CUSTOM) { + if (virCPUCompare(caps->host.arch, virQEMUCapsGetHostModel(qemuCaps), + def->cpu, true) < 0) + return -1; + } + + if (virCPUUpdate(def->os.arch, def->cpu, + virQEMUCapsGetHostModel(qemuCaps)) < 0) + goto cleanup; + + if (virQEMUCapsGetCPUDefinitions(qemuCaps, &models, &nmodels) < 0 || + virCPUTranslate(def->os.arch, def->cpu, models, nmodels) < 0) + goto cleanup; + + def->cpu->fallback = VIR_CPU_FALLBACK_FORBID; + ret = 0; + + cleanup: + virStringFreeListCount(models, nmodels); + return ret; +} + + /** * qemuProcessPrepareDomain * @@ -5162,6 +5128,10 @@ qemuProcessPrepareDomain(virConnectPtr conn, priv->monStart = 0; priv->gotShutdown = false; + VIR_DEBUG("Updating guest CPU definition"); + if (qemuProcessUpdateGuestCPU(vm->def, priv->qemuCaps, caps, flags) < 0) + goto cleanup; + ret = 0; cleanup: VIR_FREE(nodeset); diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-Haswell3.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-Haswell3.args index ef0e68b147..2e74a67039 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-Haswell3.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-Haswell3.args @@ -8,7 +8,7 @@ QEMU_AUDIO_DRV=none \ -name QEMUGuest1 \ -S \ -M pc \ --cpu Haswell \ +-cpu Haswell,+rtm,+hle \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2-nofallback.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2-nofallback.args index 0693e70020..628be8324d 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2-nofallback.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2-nofallback.args @@ -8,7 +8,8 @@ QEMU_AUDIO_DRV=none \ -name QEMUGuest1 \ -S \ -M pc \ --cpu core2duo,+ds,+ht,+tm,+ds_cpl,+xtpr,+3dnowext,+lahf_lm,-nx \ +-cpu core2duo,+ds,+ht,+tm,+ds_cpl,+xtpr,+3dnowext,+lahf_lm,-nx,-cx16,-tm2,-pbe,\ +-ss,-sse4a,-wdt \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2.args index 0693e70020..628be8324d 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-exact2.args @@ -8,7 +8,8 @@ QEMU_AUDIO_DRV=none \ -name QEMUGuest1 \ -S \ -M pc \ --cpu core2duo,+ds,+ht,+tm,+ds_cpl,+xtpr,+3dnowext,+lahf_lm,-nx \ +-cpu core2duo,+ds,+ht,+tm,+ds_cpl,+xtpr,+3dnowext,+lahf_lm,-nx,-cx16,-tm2,-pbe,\ +-ss,-sse4a,-wdt \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-fallback.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-fallback.args index 0352eb7afd..6afcc584ac 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-fallback.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-fallback.args @@ -8,7 +8,7 @@ QEMU_AUDIO_DRV=none \ -name QEMUGuest1 \ -S \ -M pc \ --cpu Penryn,-sse4.1 \ +-cpu Penryn,-sse4.1,-sse4.2,-popcnt,-aes \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-cmt.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-cmt.args index e29daf7508..7ae4ac5ad4 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-cmt.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-cmt.args @@ -9,7 +9,7 @@ QEMU_AUDIO_DRV=none \ -S \ -M pc \ -cpu Haswell,+vme,+ds,+acpi,+ss,+ht,+tm,+pbe,+dtes64,+monitor,+ds_cpl,+vmx,\ -+smx,+est,+tm2,+xtpr,+pdcm,+osxsave,+f16c,+rdrand,+pdpe1gb,+abm \ ++smx,+est,+tm2,+xtpr,+pdcm,+osxsave,+f16c,+rdrand,+pdpe1gb,+abm,+lahf_lm \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-fallback.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-fallback.args index 4e53547ac4..26b838ef1e 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-fallback.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-host-model-fallback.args @@ -9,7 +9,7 @@ QEMU_AUDIO_DRV=none \ -S \ -M pc \ -cpu Penryn,+vme,+ds,+acpi,+ss,+ht,+tm,+pbe,+monitor,+ds_cpl,+vmx,+est,+tm2,\ -+xtpr,-sse4.1 \ ++xtpr,-sse4.1,+cx16,+lahf_lm \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-minimum2.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-minimum2.args index e1b902f532..d9821b6aa5 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-minimum2.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-minimum2.args @@ -9,7 +9,7 @@ QEMU_AUDIO_DRV=none \ -S \ -M pc \ -cpu core2duo,+ds,+acpi,+ss,+ht,+tm,+pbe,+ds_cpl,+vmx,+est,+tm2,+cx16,+xtpr,\ -+lahf_lm,-syscall,-nx,-lm \ ++lahf_lm,-syscall,-nx,-lm,-svm \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ diff --git a/tests/qemuxml2argvdata/qemuxml2argv-cpu-strict1.args b/tests/qemuxml2argvdata/qemuxml2argv-cpu-strict1.args index ad7b1c56e6..e011095409 100644 --- a/tests/qemuxml2argvdata/qemuxml2argv-cpu-strict1.args +++ b/tests/qemuxml2argvdata/qemuxml2argv-cpu-strict1.args @@ -8,7 +8,8 @@ QEMU_AUDIO_DRV=none \ -name QEMUGuest1 \ -S \ -M pc \ --cpu core2duo,+ds,+acpi,+ht,+tm,+ds_cpl,+vmx,+est,+xtpr,+3dnowext,+lahf_lm,-nx \ +-cpu core2duo,+ds,+acpi,+ht,+tm,+ds_cpl,+vmx,+est,+xtpr,+3dnowext,+lahf_lm,-nx,\ +-cx16,-tm2,-pbe,-ss,-sse4a,-wdt \ -m 214 \ -smp 6,sockets=6,cores=1,threads=1 \ -uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \