mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-10-13 09:39:17 +00:00
3c876d2428
Implementation for yet another part of firmware description specification. This one covers selecting which files to parse. There are three locations from which description files can be loaded. In order of preference, from most generic to most specific these are: /usr/share/qemu/firmware /etc/qemu/firmware $XDG_CONFIG_HOME/qemu/firmware If a file is found in two or more locations then the most specific one is used. Moreover, if file is empty then it means it is overriding some generic description and disabling it. Again, this is described in more details and with nice examples in firmware.json specification (qemu commit 3a0adfc9bf). However, there's one slight difference - for the root user the home directory is not searched. This follows rules laid out by similar look up processes, e.g. PKI x509 certs are not searched in /root but they are looked for under /home. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
1045 lines
27 KiB
C
1045 lines
27 KiB
C
/*
|
|
* qemu_firmware.c: QEMU firmware
|
|
*
|
|
* Copyright (C) 2019 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "qemu_firmware.h"
|
|
#include "configmake.h"
|
|
#include "qemu_capabilities.h"
|
|
#include "virarch.h"
|
|
#include "virfile.h"
|
|
#include "virhash.h"
|
|
#include "virjson.h"
|
|
#include "virlog.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
VIR_LOG_INIT("qemu.qemu_firmware");
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_OS_INTERFACE_NONE = 0,
|
|
QEMU_FIRMWARE_OS_INTERFACE_BIOS,
|
|
QEMU_FIRMWARE_OS_INTERFACE_OPENFIRMWARE,
|
|
QEMU_FIRMWARE_OS_INTERFACE_UBOOT,
|
|
QEMU_FIRMWARE_OS_INTERFACE_UEFI,
|
|
|
|
QEMU_FIRMWARE_OS_INTERFACE_LAST
|
|
} qemuFirmwareOSInterface;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareOSInterface);
|
|
VIR_ENUM_IMPL(qemuFirmwareOSInterface,
|
|
QEMU_FIRMWARE_OS_INTERFACE_LAST,
|
|
"",
|
|
"bios",
|
|
"openfirmware",
|
|
"uboot",
|
|
"uefi",
|
|
);
|
|
|
|
|
|
typedef struct _qemuFirmwareFlashFile qemuFirmwareFlashFile;
|
|
typedef qemuFirmwareFlashFile *qemuFirmwareFlashFilePtr;
|
|
struct _qemuFirmwareFlashFile {
|
|
char *filename;
|
|
char *format;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingFlash qemuFirmwareMappingFlash;
|
|
typedef qemuFirmwareMappingFlash *qemuFirmwareMappingFlashPtr;
|
|
struct _qemuFirmwareMappingFlash {
|
|
qemuFirmwareFlashFile executable;
|
|
qemuFirmwareFlashFile nvram_template;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingKernel qemuFirmwareMappingKernel;
|
|
typedef qemuFirmwareMappingKernel *qemuFirmwareMappingKernelPtr;
|
|
struct _qemuFirmwareMappingKernel {
|
|
char *filename;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareMappingMemory qemuFirmwareMappingMemory;
|
|
typedef qemuFirmwareMappingMemory *qemuFirmwareMappingMemoryPtr;
|
|
struct _qemuFirmwareMappingMemory {
|
|
char *filename;
|
|
};
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_DEVICE_NONE = 0,
|
|
QEMU_FIRMWARE_DEVICE_FLASH,
|
|
QEMU_FIRMWARE_DEVICE_KERNEL,
|
|
QEMU_FIRMWARE_DEVICE_MEMORY,
|
|
|
|
QEMU_FIRMWARE_DEVICE_LAST
|
|
} qemuFirmwareDevice;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareDevice);
|
|
VIR_ENUM_IMPL(qemuFirmwareDevice,
|
|
QEMU_FIRMWARE_DEVICE_LAST,
|
|
"",
|
|
"flash",
|
|
"kernel",
|
|
"memory",
|
|
);
|
|
|
|
|
|
typedef struct _qemuFirmwareMapping qemuFirmwareMapping;
|
|
typedef qemuFirmwareMapping *qemuFirmwareMappingPtr;
|
|
struct _qemuFirmwareMapping {
|
|
qemuFirmwareDevice device;
|
|
|
|
union {
|
|
qemuFirmwareMappingFlash flash;
|
|
qemuFirmwareMappingKernel kernel;
|
|
qemuFirmwareMappingMemory memory;
|
|
} data;
|
|
};
|
|
|
|
|
|
typedef struct _qemuFirmwareTarget qemuFirmwareTarget;
|
|
typedef qemuFirmwareTarget *qemuFirmwareTargetPtr;
|
|
struct _qemuFirmwareTarget {
|
|
virArch architecture;
|
|
size_t nmachines;
|
|
char **machines;
|
|
};
|
|
|
|
|
|
typedef enum {
|
|
QEMU_FIRMWARE_FEATURE_NONE = 0,
|
|
QEMU_FIRMWARE_FEATURE_ACPI_S3,
|
|
QEMU_FIRMWARE_FEATURE_ACPI_S4,
|
|
QEMU_FIRMWARE_FEATURE_AMD_SEV,
|
|
QEMU_FIRMWARE_FEATURE_ENROLLED_KEYS,
|
|
QEMU_FIRMWARE_FEATURE_REQUIRES_SMM,
|
|
QEMU_FIRMWARE_FEATURE_SECURE_BOOT,
|
|
QEMU_FIRMWARE_FEATURE_VERBOSE_DYNAMIC,
|
|
QEMU_FIRMWARE_FEATURE_VERBOSE_STATIC,
|
|
|
|
QEMU_FIRMWARE_FEATURE_LAST
|
|
} qemuFirmwareFeature;
|
|
|
|
VIR_ENUM_DECL(qemuFirmwareFeature);
|
|
VIR_ENUM_IMPL(qemuFirmwareFeature,
|
|
QEMU_FIRMWARE_FEATURE_LAST,
|
|
"",
|
|
"acpi-s3",
|
|
"acpi-s4",
|
|
"amd-sev",
|
|
"enrolled-keys",
|
|
"requires-smm",
|
|
"secure-boot",
|
|
"verbose-dynamic",
|
|
"verbose-static"
|
|
);
|
|
|
|
|
|
struct _qemuFirmware {
|
|
/* Description intentionally not parsed. */
|
|
|
|
size_t ninterfaces;
|
|
qemuFirmwareOSInterface *interfaces;
|
|
|
|
qemuFirmwareMapping mapping;
|
|
|
|
size_t ntargets;
|
|
qemuFirmwareTargetPtr *targets;
|
|
|
|
size_t nfeatures;
|
|
qemuFirmwareFeature *features;
|
|
|
|
/* Tags intentionally not parsed. */
|
|
};
|
|
|
|
|
|
static void
|
|
qemuFirmwareOSInterfaceFree(qemuFirmwareOSInterface *interfaces)
|
|
{
|
|
VIR_FREE(interfaces);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareOSInterface, qemuFirmwareOSInterfaceFree);
|
|
|
|
|
|
static void
|
|
qemuFirmwareFlashFileFree(qemuFirmwareFlashFile flash)
|
|
{
|
|
VIR_FREE(flash.filename);
|
|
VIR_FREE(flash.format);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingFlashFree(qemuFirmwareMappingFlash flash)
|
|
{
|
|
qemuFirmwareFlashFileFree(flash.executable);
|
|
qemuFirmwareFlashFileFree(flash.nvram_template);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingKernelFree(qemuFirmwareMappingKernel kernel)
|
|
{
|
|
VIR_FREE(kernel.filename);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingMemoryFree(qemuFirmwareMappingMemory memory)
|
|
{
|
|
VIR_FREE(memory.filename);
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareMappingFree(qemuFirmwareMapping mapping)
|
|
{
|
|
switch (mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
qemuFirmwareMappingFlashFree(mapping.data.flash);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
qemuFirmwareMappingKernelFree(mapping.data.kernel);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
qemuFirmwareMappingMemoryFree(mapping.data.memory);
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
qemuFirmwareTargetFree(qemuFirmwareTargetPtr target)
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
virStringListFreeCount(target->machines, target->nmachines);
|
|
|
|
VIR_FREE(target);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareTarget, qemuFirmwareTargetFree);
|
|
|
|
|
|
static void
|
|
qemuFirmwareFeatureFree(qemuFirmwareFeature *features)
|
|
{
|
|
VIR_FREE(features);
|
|
}
|
|
|
|
|
|
VIR_DEFINE_AUTOPTR_FUNC(qemuFirmwareFeature, qemuFirmwareFeatureFree);
|
|
|
|
|
|
void
|
|
qemuFirmwareFree(qemuFirmwarePtr fw)
|
|
{
|
|
size_t i;
|
|
|
|
if (!fw)
|
|
return;
|
|
|
|
qemuFirmwareOSInterfaceFree(fw->interfaces);
|
|
qemuFirmwareMappingFree(fw->mapping);
|
|
for (i = 0; i < fw->ntargets; i++)
|
|
qemuFirmwareTargetFree(fw->targets[i]);
|
|
VIR_FREE(fw->targets);
|
|
qemuFirmwareFeatureFree(fw->features);
|
|
|
|
VIR_FREE(fw);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareInterfaceParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr interfacesJSON;
|
|
VIR_AUTOPTR(qemuFirmwareOSInterface) interfaces = NULL;
|
|
VIR_AUTOCLEAN(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
size_t ninterfaces;
|
|
size_t i;
|
|
|
|
if (!(interfacesJSON = virJSONValueObjectGetArray(doc, "interface-types"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get interface-types from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
ninterfaces = virJSONValueArraySize(interfacesJSON);
|
|
|
|
if (VIR_ALLOC_N(interfaces, ninterfaces) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < ninterfaces; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(interfacesJSON, i);
|
|
const char *tmpStr = virJSONValueGetString(item);
|
|
int tmp;
|
|
|
|
if ((tmp = qemuFirmwareOSInterfaceTypeFromString(tmpStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown interface type: '%s'"),
|
|
tmpStr);
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(&buf, " %s", tmpStr);
|
|
interfaces[i] = tmp;
|
|
}
|
|
|
|
VIR_DEBUG("firmware description path '%s' supported interfaces: %s",
|
|
path, NULLSTR_MINUS(virBufferCurrentContent(&buf)));
|
|
|
|
VIR_STEAL_PTR(fw->interfaces, interfaces);
|
|
fw->ninterfaces = ninterfaces;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFlashFileParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareFlashFilePtr flash)
|
|
{
|
|
const char *filename;
|
|
const char *format;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_STRDUP(flash->filename, filename) < 0)
|
|
return -1;
|
|
|
|
if (!(format = virJSONValueObjectGetString(doc, "format"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'format' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (VIR_STRDUP(flash->format, format) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFlashParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingFlashPtr flash)
|
|
{
|
|
virJSONValuePtr executable;
|
|
virJSONValuePtr nvram_template;
|
|
|
|
if (!(executable = virJSONValueObjectGet(doc, "executable"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'executable' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (qemuFirmwareFlashFileParse(path, executable, &flash->executable) < 0)
|
|
return -1;
|
|
|
|
if (!(nvram_template = virJSONValueObjectGet(doc, "nvram-template"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'nvram-template' in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (qemuFirmwareFlashFileParse(path, nvram_template, &flash->nvram_template) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingKernelParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingKernelPtr kernel)
|
|
{
|
|
const char *filename;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
}
|
|
|
|
if (VIR_STRDUP(kernel->filename, filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingMemoryParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwareMappingMemoryPtr memory)
|
|
{
|
|
const char *filename;
|
|
|
|
if (!(filename = virJSONValueObjectGetString(doc, "filename"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'filename' in '%s'"),
|
|
path);
|
|
}
|
|
|
|
if (VIR_STRDUP(memory->filename, filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr mapping = virJSONValueObjectGet(doc, "mapping");
|
|
const char *deviceStr;
|
|
int tmp;
|
|
|
|
if (!(deviceStr = virJSONValueObjectGetString(mapping, "device"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing device type in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if ((tmp = qemuFirmwareDeviceTypeFromString(deviceStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown device type in '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
fw->mapping.device = tmp;
|
|
|
|
switch (fw->mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
if (qemuFirmwareMappingFlashParse(path, mapping, &fw->mapping.data.flash) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
if (qemuFirmwareMappingKernelParse(path, mapping, &fw->mapping.data.kernel) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
if (qemuFirmwareMappingMemoryParse(path, mapping, &fw->mapping.data.memory) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareTargetParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr targetsJSON;
|
|
qemuFirmwareTargetPtr *targets = NULL;
|
|
size_t ntargets;
|
|
size_t i;
|
|
int ret = -1;
|
|
|
|
if (!(targetsJSON = virJSONValueObjectGetArray(doc, "targets"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get targets from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
ntargets = virJSONValueArraySize(targetsJSON);
|
|
|
|
if (VIR_ALLOC_N(targets, ntargets) < 0)
|
|
return -1;
|
|
|
|
for (i = 0; i < ntargets; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(targetsJSON, i);
|
|
virJSONValuePtr machines;
|
|
VIR_AUTOPTR(qemuFirmwareTarget) t = NULL;
|
|
const char *architectureStr = NULL;
|
|
size_t nmachines;
|
|
size_t j;
|
|
|
|
if (VIR_ALLOC(t) < 0)
|
|
goto cleanup;
|
|
|
|
if (!(architectureStr = virJSONValueObjectGetString(item, "architecture"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'architecture' in '%s'"),
|
|
path);
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((t->architecture = virQEMUCapsArchFromString(architectureStr)) == VIR_ARCH_NONE) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown architecture '%s'"),
|
|
architectureStr);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(machines = virJSONValueObjectGetArray(item, "machines"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'machines' in '%s'"),
|
|
path);
|
|
goto cleanup;
|
|
}
|
|
|
|
nmachines = virJSONValueArraySize(machines);
|
|
|
|
if (VIR_ALLOC_N(t->machines, nmachines) < 0)
|
|
goto cleanup;
|
|
|
|
for (j = 0; j < nmachines; j++) {
|
|
virJSONValuePtr machine = virJSONValueArrayGet(machines, j);
|
|
VIR_AUTOFREE(char *) machineStr = NULL;
|
|
|
|
if (VIR_STRDUP(machineStr, virJSONValueGetString(machine)) < 0)
|
|
goto cleanup;
|
|
|
|
VIR_APPEND_ELEMENT_INPLACE(t->machines, t->nmachines, machineStr);
|
|
}
|
|
|
|
VIR_STEAL_PTR(targets[i], t);
|
|
}
|
|
|
|
VIR_STEAL_PTR(fw->targets, targets);
|
|
fw->ntargets = ntargets;
|
|
ntargets = 0;
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
for (i = 0; i < ntargets; i++)
|
|
qemuFirmwareTargetFree(targets[i]);
|
|
VIR_FREE(targets);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFeatureParse(const char *path,
|
|
virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
virJSONValuePtr featuresJSON;
|
|
VIR_AUTOPTR(qemuFirmwareFeature) features = NULL;
|
|
size_t nfeatures;
|
|
size_t i;
|
|
|
|
if (!(featuresJSON = virJSONValueObjectGetArray(doc, "features"))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("failed to get features from '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
nfeatures = virJSONValueArraySize(featuresJSON);
|
|
|
|
if (VIR_ALLOC_N(features, nfeatures) < 0)
|
|
return -1;
|
|
|
|
fw->nfeatures = nfeatures;
|
|
|
|
for (i = 0; i < nfeatures; i++) {
|
|
virJSONValuePtr item = virJSONValueArrayGet(featuresJSON, i);
|
|
const char *tmpStr = virJSONValueGetString(item);
|
|
int tmp;
|
|
|
|
if ((tmp = qemuFirmwareFeatureTypeFromString(tmpStr)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unknown feature %s"),
|
|
tmpStr);
|
|
return -1;
|
|
}
|
|
|
|
features[i] = tmp;
|
|
}
|
|
|
|
VIR_STEAL_PTR(fw->features, features);
|
|
fw->nfeatures = nfeatures;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* 1MiB should be enough for everybody (TM) */
|
|
#define DOCUMENT_SIZE (1024 * 1024)
|
|
|
|
qemuFirmwarePtr
|
|
qemuFirmwareParse(const char *path)
|
|
{
|
|
VIR_AUTOFREE(char *) cont = NULL;
|
|
VIR_AUTOPTR(virJSONValue) doc = NULL;
|
|
VIR_AUTOPTR(qemuFirmware) fw = NULL;
|
|
qemuFirmwarePtr ret = NULL;
|
|
|
|
if (virFileReadAll(path, DOCUMENT_SIZE, &cont) < 0)
|
|
return NULL;
|
|
|
|
if (!(doc = virJSONValueFromString(cont))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("unable to parse json file '%s'"),
|
|
path);
|
|
return NULL;
|
|
}
|
|
|
|
if (VIR_ALLOC(fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareInterfaceParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareMappingParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareTargetParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareFeatureParse(path, doc, fw) < 0)
|
|
return NULL;
|
|
|
|
VIR_STEAL_PTR(ret, fw);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareInterfaceFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) interfacesJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(interfacesJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->ninterfaces; i++) {
|
|
if (virJSONValueArrayAppendString(interfacesJSON,
|
|
qemuFirmwareOSInterfaceTypeToString(fw->interfaces[i])) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc,
|
|
"interface-types",
|
|
interfacesJSON) < 0)
|
|
return -1;
|
|
|
|
interfacesJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virJSONValuePtr
|
|
qemuFirmwareFlashFileFormat(qemuFirmwareFlashFile flash)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) json = NULL;
|
|
virJSONValuePtr ret;
|
|
|
|
if (!(json = virJSONValueNewObject()))
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAppendString(json,
|
|
"filename",
|
|
flash.filename) < 0)
|
|
return NULL;
|
|
|
|
if (virJSONValueObjectAppendString(json,
|
|
"format",
|
|
flash.format) < 0)
|
|
return NULL;
|
|
|
|
VIR_STEAL_PTR(ret, json);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFlashFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingFlashPtr flash)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) executable = NULL;
|
|
VIR_AUTOPTR(virJSONValue) nvram_template = NULL;
|
|
|
|
if (!(executable = qemuFirmwareFlashFileFormat(flash->executable)))
|
|
return -1;
|
|
|
|
if (!(nvram_template = qemuFirmwareFlashFileFormat(flash->nvram_template)))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppend(mapping,
|
|
"executable",
|
|
executable) < 0)
|
|
return -1;
|
|
|
|
executable = NULL;
|
|
|
|
if (virJSONValueObjectAppend(mapping,
|
|
"nvram-template",
|
|
nvram_template) < 0)
|
|
return -1;
|
|
|
|
nvram_template = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingKernelFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingKernelPtr kernel)
|
|
{
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"filename",
|
|
kernel->filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingMemoryFormat(virJSONValuePtr mapping,
|
|
qemuFirmwareMappingMemoryPtr memory)
|
|
{
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"filename",
|
|
memory->filename) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareMappingFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) mapping = NULL;
|
|
|
|
if (!(mapping = virJSONValueNewObject()))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppendString(mapping,
|
|
"device",
|
|
qemuFirmwareDeviceTypeToString(fw->mapping.device)) < 0)
|
|
return -1;
|
|
|
|
switch (fw->mapping.device) {
|
|
case QEMU_FIRMWARE_DEVICE_FLASH:
|
|
if (qemuFirmwareMappingFlashFormat(mapping, &fw->mapping.data.flash) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_KERNEL:
|
|
if (qemuFirmwareMappingKernelFormat(mapping, &fw->mapping.data.kernel) < 0)
|
|
return -1;
|
|
break;
|
|
case QEMU_FIRMWARE_DEVICE_MEMORY:
|
|
if (qemuFirmwareMappingMemoryFormat(mapping, &fw->mapping.data.memory) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case QEMU_FIRMWARE_DEVICE_NONE:
|
|
case QEMU_FIRMWARE_DEVICE_LAST:
|
|
break;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc, "mapping", mapping) < 0)
|
|
return -1;
|
|
|
|
mapping = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareTargetFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) targetsJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(targetsJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->ntargets; i++) {
|
|
qemuFirmwareTargetPtr t = fw->targets[i];
|
|
VIR_AUTOPTR(virJSONValue) target = NULL;
|
|
VIR_AUTOPTR(virJSONValue) machines = NULL;
|
|
size_t j;
|
|
|
|
if (!(target = virJSONValueNewObject()))
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppendString(target,
|
|
"architecture",
|
|
virQEMUCapsArchToString(t->architecture)) < 0)
|
|
return -1;
|
|
|
|
if (!(machines = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (j = 0; j < t->nmachines; j++) {
|
|
if (virJSONValueArrayAppendString(machines,
|
|
t->machines[j]) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(target, "machines", machines) < 0)
|
|
return -1;
|
|
|
|
machines = NULL;
|
|
|
|
if (virJSONValueArrayAppend(targetsJSON, target) < 0)
|
|
return -1;
|
|
|
|
target = NULL;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc, "targets", targetsJSON) < 0)
|
|
return -1;
|
|
|
|
targetsJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareFeatureFormat(virJSONValuePtr doc,
|
|
qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) featuresJSON = NULL;
|
|
size_t i;
|
|
|
|
if (!(featuresJSON = virJSONValueNewArray()))
|
|
return -1;
|
|
|
|
for (i = 0; i < fw->nfeatures; i++) {
|
|
if (virJSONValueArrayAppendString(featuresJSON,
|
|
qemuFirmwareFeatureTypeToString(fw->features[i])) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virJSONValueObjectAppend(doc,
|
|
"features",
|
|
featuresJSON) < 0)
|
|
return -1;
|
|
|
|
featuresJSON = NULL;
|
|
return 0;
|
|
}
|
|
|
|
|
|
char *
|
|
qemuFirmwareFormat(qemuFirmwarePtr fw)
|
|
{
|
|
VIR_AUTOPTR(virJSONValue) doc = NULL;
|
|
|
|
if (!fw)
|
|
return NULL;
|
|
|
|
if (!(doc = virJSONValueNewObject()))
|
|
return NULL;
|
|
|
|
if (qemuFirmwareInterfaceFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareMappingFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareTargetFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
if (qemuFirmwareFeatureFormat(doc, fw) < 0)
|
|
return NULL;
|
|
|
|
return virJSONValueToString(doc, true);
|
|
}
|
|
|
|
|
|
static int
|
|
qemuFirmwareBuildFileList(virHashTablePtr files, const char *dir)
|
|
{
|
|
DIR *dirp;
|
|
struct dirent *ent = NULL;
|
|
int rc;
|
|
int ret = -1;
|
|
|
|
if ((rc = virDirOpenIfExists(&dirp, dir)) < 0)
|
|
return -1;
|
|
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
while ((rc = virDirRead(dirp, &ent, dir)) > 0) {
|
|
VIR_AUTOFREE(char *) filename = NULL;
|
|
VIR_AUTOFREE(char *) path = NULL;
|
|
|
|
if (ent->d_type != DT_REG && ent->d_type != DT_LNK)
|
|
continue;
|
|
|
|
if (STRPREFIX(ent->d_name, "."))
|
|
continue;
|
|
|
|
if (VIR_STRDUP(filename, ent->d_name) < 0)
|
|
goto cleanup;
|
|
|
|
if (virAsprintf(&path, "%s/%s", dir, filename) < 0)
|
|
goto cleanup;
|
|
|
|
if (virHashUpdateEntry(files, filename, path) < 0)
|
|
goto cleanup;
|
|
|
|
path = NULL;
|
|
}
|
|
|
|
if (rc < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
virDirClose(&dirp);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
qemuFirmwareFilesSorter(const virHashKeyValuePair *a,
|
|
const virHashKeyValuePair *b)
|
|
{
|
|
return strcmp(a->key, b->key);
|
|
}
|
|
|
|
#define QEMU_FIRMWARE_SYSTEM_LOCATION PREFIX "/share/qemu/firmware"
|
|
#define QEMU_FIRMWARE_ETC_LOCATION SYSCONFDIR "/qemu/firmware"
|
|
|
|
int
|
|
qemuFirmwareFetchConfigs(char ***firmwares,
|
|
bool privileged)
|
|
{
|
|
VIR_AUTOPTR(virHashTable) files = NULL;
|
|
VIR_AUTOFREE(char *) homeConfig = NULL;
|
|
VIR_AUTOFREE(char *) xdgConfig = NULL;
|
|
VIR_AUTOFREE(virHashKeyValuePairPtr) pairs = NULL;
|
|
virHashKeyValuePairPtr tmp = NULL;
|
|
|
|
*firmwares = NULL;
|
|
|
|
if (privileged) {
|
|
/* This is a slight divergence from the specification.
|
|
* Since the system daemon runs as root, it doesn't make
|
|
* much sense to parse files in root's home directory. It
|
|
* makes sense only for session daemon which runs under
|
|
* regular user. */
|
|
if (VIR_STRDUP(xdgConfig, virGetEnvBlockSUID("XDG_CONFIG_HOME")) < 0)
|
|
return -1;
|
|
|
|
if (!xdgConfig) {
|
|
VIR_AUTOFREE(char *) home = virGetUserDirectory();
|
|
|
|
if (!home)
|
|
return -1;
|
|
|
|
if (virAsprintf(&xdgConfig, "%s/.config", home) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (virAsprintf(&homeConfig, "%s/qemu/firmware", xdgConfig) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!(files = virHashCreate(10, virHashValueFree)))
|
|
return -1;
|
|
|
|
if (qemuFirmwareBuildFileList(files, QEMU_FIRMWARE_SYSTEM_LOCATION) < 0)
|
|
return -1;
|
|
|
|
if (qemuFirmwareBuildFileList(files, QEMU_FIRMWARE_ETC_LOCATION) < 0)
|
|
return -1;
|
|
|
|
if (homeConfig &&
|
|
qemuFirmwareBuildFileList(files, homeConfig) < 0)
|
|
return -1;
|
|
|
|
/* At this point, the @files hash table contains unique set of filenames
|
|
* where each filename (as key) has the highest priority full pathname
|
|
* associated with it. */
|
|
|
|
if (virHashSize(files) == 0)
|
|
return 0;
|
|
|
|
if (!(pairs = virHashGetItems(files, qemuFirmwareFilesSorter)))
|
|
return -1;
|
|
|
|
for (tmp = pairs; tmp->key; tmp++) {
|
|
const char *path = tmp->value;
|
|
off_t len;
|
|
|
|
if ((len = virFileLength(path, -1)) < 0) {
|
|
virReportSystemError(errno,
|
|
_("unable to get size of '%s'"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("firmware description path '%s' len=%jd",
|
|
path, (intmax_t) len);
|
|
|
|
if (len == 0) {
|
|
/* Empty files are used to mask less specific instances
|
|
* of the same file. */
|
|
continue;
|
|
}
|
|
|
|
if (virStringListAdd(firmwares, path) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|