[PATCH 1/3] [bhyve] list pci devices on host
by Alexander Shursha
Linux gets the list via sysfs. FreeBSD can get the list through
ioctl
Sponsored by: Future Crew, LLC
Signed-off-by: Alexander Shursha <kekek2(a)ya.ru>
---
src/bhyve/bhyve_capabilities.c | 2 +-
src/conf/node_device_conf.c | 2 +-
src/node_device/node_device_driver.c | 2 +-
src/util/virmdev.c | 2 +-
src/util/virpci.c | 400 ++++++++++++++++++++++++++-
5 files changed, 399 insertions(+), 9 deletions(-)
diff --git a/src/bhyve/bhyve_capabilities.c b/src/bhyve/bhyve_capabilities.c
index b065256cf0..fcef91c435 100644
--- a/src/bhyve/bhyve_capabilities.c
+++ b/src/bhyve/bhyve_capabilities.c
@@ -108,7 +108,7 @@ virBhyveDomainCapsFill(virDomainCaps *caps,
VIR_DOMAIN_CAPS_ENUM_SET(caps->video.modelType, VIR_DOMAIN_VIDEO_TYPE_GOP);
}
- caps->hostdev.supported = VIR_TRISTATE_BOOL_NO;
+ caps->hostdev.supported = VIR_TRISTATE_BOOL_YES;
caps->features[VIR_DOMAIN_CAPS_FEATURE_IOTHREADS] = VIR_TRISTATE_BOOL_NO;
caps->features[VIR_DOMAIN_CAPS_FEATURE_VMCOREINFO] = VIR_TRISTATE_BOOL_NO;
caps->features[VIR_DOMAIN_CAPS_FEATURE_GENID] = VIR_TRISTATE_BOOL_NO;
diff --git a/src/conf/node_device_conf.c b/src/conf/node_device_conf.c
index 08a89942ba..39e6b78f3d 100644
--- a/src/conf/node_device_conf.c
+++ b/src/conf/node_device_conf.c
@@ -2836,7 +2836,7 @@ virNodeDeviceSyncMdevActiveConfig(virNodeDeviceDef *def)
}
}
-#ifdef __linux__
+#if defined(__linux__) || defined(__FreeBSD__)
int
virNodeDeviceGetSCSIHostCaps(virNodeDevCapSCSIHost *scsi_host)
diff --git a/src/node_device/node_device_driver.c b/src/node_device/node_device_driver.c
index fa5db0d5d5..282af02724 100644
--- a/src/node_device/node_device_driver.c
+++ b/src/node_device/node_device_driver.c
@@ -111,7 +111,7 @@ int nodeConnectIsAlive(virConnectPtr conn G_GNUC_UNUSED)
return 1;
}
-#if defined (__linux__) && defined(WITH_UDEV)
+#if (defined(__linux__) || defined(__FreeBSD__)) && defined(WITH_UDEV)
/* NB: It was previously believed that changes in driver name were
* relayed to libvirt as "change" events by udev, and the udev event
* notification is setup to recognize such events and effectively
diff --git a/src/util/virmdev.c b/src/util/virmdev.c
index 6ecdbdf0ab..3a07ba75f2 100644
--- a/src/util/virmdev.c
+++ b/src/util/virmdev.c
@@ -565,7 +565,7 @@ virMediatedDeviceParentGetAddress(const char *sysfspath,
return -1;
}
-#ifdef __linux__
+#if defined(__linux__) || defined(__FreeBSD__)
ssize_t
virMediatedDeviceGetMdevTypes(const char *sysfspath,
diff --git a/src/util/virpci.c b/src/util/virpci.c
index 90617e69c6..f954ce4df2 100644
--- a/src/util/virpci.c
+++ b/src/util/virpci.c
@@ -30,6 +30,13 @@
#include <sys/stat.h>
#include <unistd.h>
+#ifdef __FreeBSD__
+# ifdef WITH_BHYVE
+# include <libudev.h>
+# endif
+# include <sys/pciio.h>
+#endif
+
#ifdef __linux__
# include <sys/utsname.h>
#endif
@@ -72,7 +79,11 @@ struct _virPCIDevice {
char *name; /* domain:bus:slot.function */
char id[PCI_ID_LEN]; /* product vendor */
+#ifndef __FreeBSD__
char *path;
+#else
+ struct pci_match_conf patterns[1];
+#endif
/* The driver:domain which uses the device */
char *used_by_drvname;
@@ -99,6 +110,9 @@ struct _virPCIDevice {
bool unbind_from_stub;
bool remove_slot;
bool reprobe;
+#ifdef __FreeBSD__
+ u_int8_t pc_hdr; /* PCI header type */
+#endif
};
struct _virPCIDeviceList {
@@ -359,6 +373,7 @@ virPCIDeviceGetCurrentDriverNameAndType(virPCIDevice *dev,
}
+#ifndef __FreeBSD__
static int
virPCIDeviceConfigOpenInternal(virPCIDevice *dev, bool readonly, bool fatal)
{
@@ -429,6 +444,7 @@ virPCIDeviceRead(virPCIDevice *dev,
return 0;
}
+#endif
/**
* virPCIDeviceReadN:
@@ -450,6 +466,7 @@ virPCIDeviceRead(virPCIDevice *dev,
* and the return value is 0, then the config file really does contain
* the value 0 at @pos.
*/
+#ifndef __FreeBSD__
static uint8_t
virPCIDeviceRead8(virPCIDevice *dev, int cfgfd, unsigned int pos)
{
@@ -473,7 +490,56 @@ virPCIDeviceRead32(virPCIDevice *dev, int cfgfd, unsigned int pos)
virPCIDeviceRead(dev, cfgfd, pos, &buf[0], sizeof(buf));
return (buf[0] << 0) | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
+#else
+static int
+virPCIDeviceRead(virPCIDevice *dev, int pi_reg, int pi_width, u_int32_t *pi_data)
+{
+ struct pci_io pc;
+ int fd;
+
+ bzero(&pc, sizeof(struct pci_io));
+ pc.pi_sel.pc_domain = dev->address.domain;
+ pc.pi_sel.pc_bus = dev->address.bus;
+ pc.pi_sel.pc_dev = dev->address.slot;
+ pc.pi_sel.pc_func = dev->address.function;
+ pc.pi_reg = pi_reg;
+ pc.pi_width = pi_width;
+
+ fd = open("/dev/pci", O_RDWR, 0);
+ errno = 0;
+ if (ioctl(fd, PCIOCREAD, &pc) == -1) {
+ VIR_FORCE_CLOSE(fd);
+ VIR_WARN("Failed to read from '%s' : %s", dev->name, g_strerror(errno));
+ return -1;
+ }
+ VIR_FORCE_CLOSE(fd);
+ *pi_data = pc.pi_data;
+ return 0;
+}
+static uint8_t
+virPCIDeviceRead8(virPCIDevice *dev, int pi_reg)
+{
+ u_int32_t pi_data;
+ virPCIDeviceRead(dev, pi_reg, 1, &pi_data);
+ return pi_data & 0xff;
+}
+static uint16_t
+virPCIDeviceRead16(virPCIDevice *dev, int pi_reg)
+{
+ u_int32_t pi_data;
+ virPCIDeviceRead(dev, pi_reg, 2, &pi_data);
+ return pi_data & 0xffff;
+}
+
+static uint32_t
+virPCIDeviceRead32(virPCIDevice *dev, int pi_reg)
+{
+ u_int32_t pi_data;
+ virPCIDeviceRead(dev, pi_reg, 4, &pi_data);
+ return pi_data;
+}
+#endif
static int
virPCIDeviceReadClass(virPCIDevice *dev, uint16_t *device_class)
{
@@ -499,6 +565,7 @@ virPCIDeviceReadClass(virPCIDevice *dev, uint16_t *device_class)
return 0;
}
+#ifndef __FreeBSD__
static int
virPCIDeviceWrite(virPCIDevice *dev,
int cfgfd,
@@ -528,6 +595,44 @@ virPCIDeviceWrite32(virPCIDevice *dev, int cfgfd, unsigned int pos, uint32_t val
uint8_t buf[4] = { (val >> 0), (val >> 8), (val >> 16), (val >> 24) };
virPCIDeviceWrite(dev, cfgfd, pos, &buf[0], sizeof(buf));
}
+#else
+static int
+virPCIDeviceWrite(virPCIDevice *dev, int pi_reg, int pi_width, u_int32_t pi_data)
+{
+ struct pci_io pc;
+ int fd;
+
+ bzero(&pc, sizeof(struct pci_io));
+ pc.pi_sel.pc_domain = dev->address.domain;
+ pc.pi_sel.pc_bus = dev->address.bus;
+ pc.pi_sel.pc_dev = dev->address.slot;
+ pc.pi_sel.pc_func = dev->address.function;
+ pc.pi_reg = pi_reg;
+ pc.pi_width = pi_width;
+ pc.pi_data = pi_data;
+
+ fd = open("/dev/pci", O_RDWR, 0);
+ errno = 0;
+ if (ioctl(fd, PCIOCWRITE, &pc) == -1) {
+ VIR_FORCE_CLOSE(fd);
+ VIR_WARN("Failed to write to '%s' : %s", dev->name, g_strerror(errno));
+ return -1;
+ }
+ VIR_FORCE_CLOSE(fd);
+ return 0;
+}
+static void
+virPCIDeviceWrite16(virPCIDevice *dev, int pi_reg, u_int16_t val)
+{
+ virPCIDeviceWrite(dev, pi_reg, 2, val);
+}
+
+static void
+virPCIDeviceWrite32(virPCIDevice *dev, int pi_reg, uint32_t val)
+{
+ virPCIDeviceWrite(dev, pi_reg, 4, val);
+}
+#endif
typedef int (*virPCIDeviceIterPredicate)(virPCIDevice *, virPCIDevice *,
void *);
@@ -610,7 +715,9 @@ virPCIDeviceIterDevices(virPCIDeviceIterPredicate predicate,
*/
static int
virPCIDeviceFindCapabilityOffset(virPCIDevice *dev,
+#ifndef __FreeBSD__
int cfgfd,
+#endif
unsigned int capability,
unsigned int *offset)
{
@@ -619,11 +726,19 @@ virPCIDeviceFindCapabilityOffset(virPCIDevice *dev,
*offset = 0; /* assume failure (*nothing* can be at offset 0) */
+#ifndef __FreeBSD__
status = virPCIDeviceRead16(dev, cfgfd, PCI_STATUS);
+#else
+ status = virPCIDeviceRead16(dev, PCI_STATUS);
+#endif
if (errno != 0 || !(status & PCI_STATUS_CAP_LIST))
goto error;
+#ifndef __FreeBSD__
pos = virPCIDeviceRead8(dev, cfgfd, PCI_CAPABILITY_LIST);
+#else
+ pos = virPCIDeviceRead8(dev, PCI_CAPABILITY_LIST);
+#endif
if (errno != 0)
goto error;
@@ -635,7 +750,11 @@ virPCIDeviceFindCapabilityOffset(virPCIDevice *dev,
* capabilities here.
*/
while (pos >= PCI_CONF_HEADER_LEN && pos != 0xff) {
+#ifndef __FreeBSD__
uint8_t capid = virPCIDeviceRead8(dev, cfgfd, pos);
+#else
+ uint8_t capid = virPCIDeviceRead8(dev, pos);
+#endif
if (errno != 0)
goto error;
@@ -646,7 +765,11 @@ virPCIDeviceFindCapabilityOffset(virPCIDevice *dev,
return 0;
}
+#ifndef __FreeBSD__
pos = virPCIDeviceRead8(dev, cfgfd, pos + 1);
+#else
+ pos = virPCIDeviceRead8(dev, pos + 1);
+#endif
if (errno != 0)
goto error;
}
@@ -665,7 +788,9 @@ virPCIDeviceFindCapabilityOffset(virPCIDevice *dev,
static unsigned int
virPCIDeviceFindExtendedCapabilityOffset(virPCIDevice *dev,
+#ifndef __FreeBSD__
int cfgfd,
+#endif
unsigned int capability)
{
int ttl;
@@ -677,7 +802,11 @@ virPCIDeviceFindExtendedCapabilityOffset(virPCIDevice *dev,
pos = PCI_EXT_CAP_BASE;
while (ttl > 0 && pos >= PCI_EXT_CAP_BASE) {
+#ifndef __FreeBSD__
header = virPCIDeviceRead32(dev, cfgfd, pos);
+#else
+ header = virPCIDeviceRead32(dev, pos);
+#endif
if ((header & PCI_EXT_CAP_ID_MASK) == capability)
return pos;
@@ -693,7 +822,11 @@ virPCIDeviceFindExtendedCapabilityOffset(virPCIDevice *dev,
* not have FLR, 1 if it does, and -1 on error
*/
static bool
+#ifndef __FreeBSD__
virPCIDeviceDetectFunctionLevelReset(virPCIDevice *dev, int cfgfd)
+#else
+virPCIDeviceDetectFunctionLevelReset(virPCIDevice *dev)
+#endif
{
uint32_t caps;
unsigned int pos;
@@ -707,7 +840,11 @@ virPCIDeviceDetectFunctionLevelReset(virPCIDevice *dev, int cfgfd)
* on SR-IOV NICs at the moment.
*/
if (dev->pcie_cap_pos) {
+#ifndef __FreeBSD__
caps = virPCIDeviceRead32(dev, cfgfd, dev->pcie_cap_pos + PCI_EXP_DEVCAP);
+#else
+ caps = virPCIDeviceRead32(dev, dev->pcie_cap_pos + PCI_EXP_DEVCAP);
+#endif
if (caps & PCI_EXP_DEVCAP_FLR) {
VIR_DEBUG("%s %s: detected PCIe FLR capability", dev->id, dev->name);
return true;
@@ -718,11 +855,19 @@ virPCIDeviceDetectFunctionLevelReset(virPCIDevice *dev, int cfgfd)
* the same thing, except for conventional PCI
* devices. This is not common yet.
*/
+#ifndef __FreeBSD__
if (virPCIDeviceFindCapabilityOffset(dev, cfgfd, PCI_CAP_ID_AF, &pos) < 0)
+#else
+ if (virPCIDeviceFindCapabilityOffset(dev, PCI_CAP_ID_AF, &pos) < 0)
+#endif
goto error;
if (pos) {
+#ifndef __FreeBSD__
caps = virPCIDeviceRead16(dev, cfgfd, pos + PCI_AF_CAP);
+#else
+ caps = virPCIDeviceRead16(dev, pos + PCI_AF_CAP);
+#endif
if (caps & PCI_AF_CAP_FLR) {
VIR_DEBUG("%s %s: detected PCI FLR capability", dev->id, dev->name);
return true;
@@ -754,13 +899,21 @@ virPCIDeviceDetectFunctionLevelReset(virPCIDevice *dev, int cfgfd)
* internal reset, not just a soft reset.
*/
static bool
+#ifndef __FreeBSD__
virPCIDeviceDetectPowerManagementReset(virPCIDevice *dev, int cfgfd)
+#else
+virPCIDeviceDetectPowerManagementReset(virPCIDevice *dev)
+#endif
{
if (dev->pci_pm_cap_pos) {
uint32_t ctl;
/* require the NO_SOFT_RESET bit is clear */
+#ifndef __FreeBSD__
ctl = virPCIDeviceRead32(dev, cfgfd, dev->pci_pm_cap_pos + PCI_PM_CTRL);
+#else
+ ctl = virPCIDeviceRead32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL);
+#endif
if (!(ctl & PCI_PM_CTRL_NO_SOFT_RESET)) {
VIR_DEBUG("%s %s: detected PM reset capability", dev->id, dev->name);
return true;
@@ -811,13 +964,17 @@ virPCIDeviceIsParent(virPCIDevice *dev, virPCIDevice *check, void *data)
uint8_t header_type, secondary, subordinate;
virPCIDevice **best = data;
int ret = 0;
+#ifndef __FreeBSD__
int fd;
+#endif
if (dev->address.domain != check->address.domain)
return 0;
+#ifndef __FreeBSD__
if ((fd = virPCIDeviceConfigOpenTry(check)) < 0)
return 0;
+#endif
/* Is it a bridge? */
ret = virPCIDeviceReadClass(check, &device_class);
@@ -825,12 +982,21 @@ virPCIDeviceIsParent(virPCIDevice *dev, virPCIDevice *check, void *data)
goto cleanup;
/* Is it a plane? */
+#ifndef __FreeBSD__
header_type = virPCIDeviceRead8(check, fd, PCI_HEADER_TYPE);
+#else
+ header_type = virPCIDeviceRead8(check, PCI_HEADER_TYPE);
+#endif
if ((header_type & PCI_HEADER_TYPE_MASK) != PCI_HEADER_TYPE_BRIDGE)
goto cleanup;
+#ifndef __FreeBSD__
secondary = virPCIDeviceRead8(check, fd, PCI_SECONDARY_BUS);
subordinate = virPCIDeviceRead8(check, fd, PCI_SUBORDINATE_BUS);
+#else
+ secondary = virPCIDeviceRead8(check, PCI_SECONDARY_BUS);
+ subordinate = virPCIDeviceRead8(check, PCI_SUBORDINATE_BUS);
+#endif
VIR_DEBUG("%s %s: found parent device %s", dev->id, dev->name, check->name);
@@ -858,13 +1024,19 @@ virPCIDeviceIsParent(virPCIDevice *dev, virPCIDevice *check, void *data)
* parent. See if the current device is more restrictive than the
* best, and if so, make it the new best
*/
+#ifndef __FreeBSD__
int bestfd;
+#endif
uint8_t best_secondary;
+#ifndef __FreeBSD__
if ((bestfd = virPCIDeviceConfigOpenTry(*best)) < 0)
goto cleanup;
best_secondary = virPCIDeviceRead8(*best, bestfd, PCI_SECONDARY_BUS);
virPCIDeviceConfigClose(*best, bestfd);
+#else
+ best_secondary = virPCIDeviceRead8(*best, PCI_SECONDARY_BUS);
+#endif
if (secondary > best_secondary) {
virPCIDeviceFree(*best);
@@ -878,7 +1050,9 @@ virPCIDeviceIsParent(virPCIDevice *dev, virPCIDevice *check, void *data)
}
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(check, fd);
+#endif
return ret;
}
@@ -902,7 +1076,9 @@ virPCIDeviceGetParent(virPCIDevice *dev, virPCIDevice **parent)
*/
static int
virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
+#ifndef __FreeBSD__
int cfgfd,
+#endif
virPCIDeviceList *inactiveDevs)
{
g_autoptr(virPCIDevice) parent = NULL;
@@ -910,7 +1086,9 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
uint8_t config_space[PCI_CONF_LEN];
uint16_t ctl;
int ret = -1;
+#ifndef __FreeBSD__
int parentfd;
+#endif
/* Refuse to do a secondary bus reset if there are other
* devices/functions behind the bus are used by the host
@@ -932,8 +1110,11 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
dev->name);
return -1;
}
+
+#ifndef __FreeBSD__
if ((parentfd = virPCIDeviceConfigOpenWrite(parent)) < 0)
goto out;
+#endif
VIR_DEBUG("%s %s: doing a secondary bus reset", dev->id, dev->name);
@@ -941,7 +1122,11 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
* for the supplied device since we refuse to do a reset if there
* are multiple devices/functions
*/
+#ifndef __FreeBSD__
if (virPCIDeviceRead(dev, cfgfd, 0, config_space, PCI_CONF_LEN) < 0) {
+#else
+ if (virPCIDeviceRead(dev, 0, PCI_CONF_LEN, (u_int32_t *) config_space) < 0) {
+#endif
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to read PCI config space for %1$s"),
dev->name);
@@ -951,6 +1136,7 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
/* Read the control register, set the reset flag, wait 200ms,
* unset the reset flag and wait 200ms.
*/
+#ifndef __FreeBSD__
ctl = virPCIDeviceRead16(dev, parentfd, PCI_BRIDGE_CONTROL);
virPCIDeviceWrite16(parent, parentfd, PCI_BRIDGE_CONTROL,
@@ -963,6 +1149,20 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
g_usleep(200 * 1000); /* sleep 200ms */
if (virPCIDeviceWrite(dev, cfgfd, 0, config_space, PCI_CONF_LEN) < 0) {
+#else
+ ctl = virPCIDeviceRead16(parent, PCI_BRIDGE_CONTROL);
+
+ virPCIDeviceWrite16(parent, PCI_BRIDGE_CONTROL,
+ ctl | PCI_BRIDGE_CTL_RESET);
+
+ g_usleep(200 * 1000); /* sleep 200ms */
+
+ virPCIDeviceWrite16(parent, PCI_BRIDGE_CONTROL, ctl);
+
+ g_usleep(200 * 1000); /* sleep 200ms */
+
+ if (virPCIDeviceWrite(dev, 0, PCI_CONF_LEN, (config_space[0] << 0) | (config_space[1] << 8) | (config_space[2] << 16) | (config_space[3] << 24)) < 0) {
+#endif
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to restore PCI config space for %1$s"),
dev->name);
@@ -971,7 +1171,9 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
ret = 0;
out:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(parent, parentfd);
+#endif
return ret;
}
@@ -980,7 +1182,11 @@ virPCIDeviceTrySecondaryBusReset(virPCIDevice *dev,
* above we require the device supports a full internal reset.
*/
static int
+#ifndef __FreeBSD__
virPCIDeviceTryPowerManagementReset(virPCIDevice *dev, int cfgfd)
+#else
+virPCIDeviceTryPowerManagementReset(virPCIDevice *dev)
+#endif
{
uint8_t config_space[PCI_CONF_LEN];
uint32_t ctl;
@@ -989,6 +1195,7 @@ virPCIDeviceTryPowerManagementReset(virPCIDevice *dev, int cfgfd)
return -1;
/* Save and restore the device's config space. */
+#ifndef __FreeBSD__
if (virPCIDeviceRead(dev, cfgfd, 0, &config_space[0], PCI_CONF_LEN) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to read PCI config space for %1$s"),
@@ -1012,6 +1219,31 @@ virPCIDeviceTryPowerManagementReset(virPCIDevice *dev, int cfgfd)
g_usleep(10 * 1000); /* sleep 10ms */
if (virPCIDeviceWrite(dev, cfgfd, 0, &config_space[0], PCI_CONF_LEN) < 0) {
+#else
+ if (virPCIDeviceRead(dev, 0, PCI_CONF_LEN, (u_int32_t *) config_space) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Failed to read PCI config space for %1$s"),
+ dev->name);
+ return -1;
+ }
+
+ VIR_DEBUG("%s %s: doing a power management reset", dev->id, dev->name);
+
+ ctl = virPCIDeviceRead32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL);
+ ctl &= ~PCI_PM_CTRL_STATE_MASK;
+
+ virPCIDeviceWrite32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL,
+ ctl | PCI_PM_CTRL_STATE_D3hot);
+
+ g_usleep(10 * 1000); /* sleep 10ms */
+
+ virPCIDeviceWrite32(dev, dev->pci_pm_cap_pos + PCI_PM_CTRL,
+ ctl | PCI_PM_CTRL_STATE_D0);
+
+ g_usleep(10 * 1000); /* sleep 10ms */
+
+ if (virPCIDeviceWrite(dev, 0, PCI_CONF_LEN, (config_space[0] << 0) | (config_space[1] << 8) | (config_space[2] << 16) | (config_space[3] << 24)) < 0) {
+#endif
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to restore PCI config space for %1$s"),
dev->name);
@@ -1046,10 +1278,18 @@ virPCIDeviceTryPowerManagementReset(virPCIDevice *dev, int cfgfd)
* Always returns success (0) (for now)
*/
static int
+#ifndef __FreeBSD__
virPCIDeviceInit(virPCIDevice *dev, int cfgfd)
+#else
+virPCIDeviceInit(virPCIDevice *dev)
+#endif
{
dev->is_pcie = false;
+#ifndef __FreeBSD__
if (virPCIDeviceFindCapabilityOffset(dev, cfgfd, PCI_CAP_ID_EXP, &dev->pcie_cap_pos) < 0) {
+#else
+ if (virPCIDeviceFindCapabilityOffset(dev, PCI_CAP_ID_EXP, &dev->pcie_cap_pos) < 0) {
+#endif
/* an unprivileged process is unable to read *all* of a
* device's PCI config (it can only read the first 64
* bytes, which isn't enough for see the Express
@@ -1065,6 +1305,7 @@ virPCIDeviceInit(virPCIDevice *dev, int cfgfd)
* -1), then we blindly assume the most likely outcome -
* PCIe.
*/
+#ifndef __FreeBSD__
off_t configLen = virFileLength(virPCIDeviceGetConfigPath(dev), -1);
if (configLen != 256)
@@ -1077,6 +1318,16 @@ virPCIDeviceInit(virPCIDevice *dev, int cfgfd)
virPCIDeviceFindCapabilityOffset(dev, cfgfd, PCI_CAP_ID_PM, &dev->pci_pm_cap_pos);
dev->has_flr = virPCIDeviceDetectFunctionLevelReset(dev, cfgfd);
dev->has_pm_reset = virPCIDeviceDetectPowerManagementReset(dev, cfgfd);
+#else
+
+ } else {
+ dev->is_pcie = (dev->pcie_cap_pos != 0);
+ }
+
+ virPCIDeviceFindCapabilityOffset(dev, PCI_CAP_ID_PM, &dev->pci_pm_cap_pos);
+ dev->has_flr = virPCIDeviceDetectFunctionLevelReset(dev);
+ dev->has_pm_reset = virPCIDeviceDetectPowerManagementReset(dev);
+#endif
return 0;
}
@@ -1089,7 +1340,9 @@ virPCIDeviceReset(virPCIDevice *dev,
g_autofree char *drvName = NULL;
virPCIStubDriver drvType;
int ret = -1;
+#ifndef __FreeBSD__
int fd = -1;
+#endif
int hdrType = -1;
if (virPCIGetHeaderType(dev, &hdrType) < 0)
@@ -1125,10 +1378,14 @@ virPCIDeviceReset(virPCIDevice *dev,
VIR_DEBUG("Resetting device %s", dev->name);
+#ifndef __FreeBSD__
if ((fd = virPCIDeviceConfigOpenWrite(dev)) < 0)
goto cleanup;
if (virPCIDeviceInit(dev, fd) < 0)
+#else
+ if (virPCIDeviceInit(dev) < 0)
+#endif
goto cleanup;
/* KVM will perform FLR when starting and stopping
@@ -1144,11 +1401,19 @@ virPCIDeviceReset(virPCIDevice *dev,
* the function, not the whole device.
*/
if (dev->has_pm_reset)
+#ifndef __FreeBSD__
ret = virPCIDeviceTryPowerManagementReset(dev, fd);
+#else
+ ret = virPCIDeviceTryPowerManagementReset(dev);
+#endif
/* Bus reset is not an option with the root bus */
if (ret < 0 && dev->address.bus != 0)
+#ifndef __FreeBSD__
ret = virPCIDeviceTrySecondaryBusReset(dev, fd, inactiveDevs);
+#else
+ ret = virPCIDeviceTrySecondaryBusReset(dev, inactiveDevs);
+#endif
if (ret < 0) {
virErrorPtr err = virGetLastError();
@@ -1160,7 +1425,9 @@ virPCIDeviceReset(virPCIDevice *dev,
}
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(dev, fd);
+#endif
return ret;
}
@@ -1868,6 +2135,12 @@ virPCIDeviceNew(const virPCIDeviceAddress *address)
g_autoptr(virPCIDevice) dev = NULL;
g_autofree char *vendor = NULL;
g_autofree char *product = NULL;
+#ifdef __FreeBSD__
+ struct pci_conf_io pc;
+ struct pci_match_conf patterns[1];
+ struct pci_conf conf[1];
+ int fd;
+#endif
dev = g_new0(virPCIDevice, 1);
@@ -1875,6 +2148,7 @@ virPCIDeviceNew(const virPCIDeviceAddress *address)
dev->name = virPCIDeviceAddressAsString(&dev->address);
+#ifndef __FreeBSD__
dev->path = g_strdup_printf(PCI_SYSFS "devices/%s/config", dev->name);
if (!virFileExists(dev->path)) {
@@ -1902,6 +2176,52 @@ virPCIDeviceNew(const virPCIDeviceAddress *address)
&vendor[2], &product[2]);
return NULL;
}
+#else
+ fd = open("/dev/pci", O_RDONLY, 0);
+ if (fd < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("Error open /dev/pci: %1$d"), errno);
+ return NULL;
+ }
+
+ bzero(&pc, sizeof(struct pci_conf_io));
+ pc.match_buf_len = sizeof(conf);
+ pc.matches = conf;
+
+ bzero(patterns, sizeof(patterns));
+ patterns[0].pc_sel.pc_domain = address->domain;
+ patterns[0].pc_sel.pc_bus = address->bus;
+ patterns[0].pc_sel.pc_dev = address->slot;
+ patterns[0].pc_sel.pc_func = address->function;
+
+ patterns[0].flags = PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS | PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC;
+ pc.num_patterns = 1;
+ pc.pat_buf_len = sizeof(patterns);
+ pc.patterns = patterns;
+
+ if (ioctl(fd, PCIOCGETCONF, &pc) == -1) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("ioctl(PCIOCGETCONF) eroor: %1$d"), errno);
+ return NULL;
+ }
+ if (pc.status != PCI_GETCONF_LAST_DEVICE && pc.status != PCI_GETCONF_MORE_DEVS) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("error returned from PCIOCGETCONF ioctl: %1$d"), pc.status);
+ return NULL;
+ }
+ VIR_FORCE_CLOSE(fd);
+ if (pc.num_matches == 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("Device not found: %1$d"), pc.num_matches);
+ return NULL;
+ }
+
+ /* strings contain '0x' prefix */
+ if (g_snprintf(dev->id, sizeof(dev->id), "%x %x", pc.matches->pc_vendor,
+ pc.matches->pc_device) >= sizeof(dev->id)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("dev->id buffer overflow: %1$x %2$x"),
+ pc.matches->pc_vendor, pc.matches->pc_device);
+ return NULL;
+ }
+ dev->pc_hdr = pc.matches->pc_hdr;
+#endif
VIR_DEBUG("%s %s: initialized", dev->id, dev->name);
@@ -1918,10 +2238,12 @@ virPCIDeviceCopy(virPCIDevice *dev)
/* shallow copy to take care of most attributes */
*copy = *dev;
+#ifndef __FreeBSD__
copy->path = NULL;
+ copy->path = g_strdup(dev->path);
+#endif
copy->used_by_drvname = copy->used_by_domname = NULL;
copy->name = g_strdup(dev->name);
- copy->path = g_strdup(dev->path);
copy->used_by_drvname = g_strdup(dev->used_by_drvname);
copy->used_by_domname = g_strdup(dev->used_by_domname);
copy->stubDriverName = g_strdup(dev->stubDriverName);
@@ -1936,7 +2258,9 @@ virPCIDeviceFree(virPCIDevice *dev)
return;
VIR_DEBUG("%s %s: freeing", dev->id, dev->name);
g_free(dev->name);
+#ifndef __FreeBSD__
g_free(dev->path);
+#endif
g_free(dev->used_by_drvname);
g_free(dev->used_by_domname);
g_free(dev->stubDriverName);
@@ -1970,11 +2294,19 @@ virPCIDeviceGetName(virPCIDevice *dev)
* Returns a pointer to a string containing the path of @dev's PCI
* config file.
*/
+#ifndef __FreeBSD__
const char *
virPCIDeviceGetConfigPath(virPCIDevice *dev)
{
return dev->path;
}
+#else
+const char *
+virPCIDeviceGetConfigPath(virPCIDevice *dev G_GNUC_UNUSED)
+{
+ return NULL;
+}
+#endif
void virPCIDeviceSetManaged(virPCIDevice *dev, bool managed)
{
@@ -2484,14 +2816,20 @@ virPCIDeviceDownstreamLacksACS(virPCIDevice *dev)
uint16_t flags;
uint16_t ctrl;
unsigned int pos;
+#ifndef __FreeBSD__
int fd;
+#endif
int ret = 0;
uint16_t device_class;
+#ifndef __FreeBSD__
if ((fd = virPCIDeviceConfigOpen(dev)) < 0)
return -1;
if (virPCIDeviceInit(dev, fd) < 0) {
+#else
+ if (virPCIDeviceInit(dev) < 0) {
+#endif
ret = -1;
goto cleanup;
}
@@ -2503,18 +2841,30 @@ virPCIDeviceDownstreamLacksACS(virPCIDevice *dev)
if (!pos || device_class != PCI_CLASS_BRIDGE_PCI)
goto cleanup;
+#ifndef __FreeBSD__
flags = virPCIDeviceRead16(dev, fd, pos + PCI_EXP_FLAGS);
+#else
+ flags = virPCIDeviceRead16(dev, pos + PCI_EXP_FLAGS);
+#endif
if (((flags & PCI_EXP_FLAGS_TYPE) >> 4) != PCI_EXP_TYPE_DOWNSTREAM)
goto cleanup;
+#ifndef __FreeBSD__
pos = virPCIDeviceFindExtendedCapabilityOffset(dev, fd, PCI_EXT_CAP_ID_ACS);
+#else
+ pos = virPCIDeviceFindExtendedCapabilityOffset(dev, PCI_EXT_CAP_ID_ACS);
+#endif
if (!pos) {
VIR_DEBUG("%s %s: downstream port lacks ACS", dev->id, dev->name);
ret = 1;
goto cleanup;
}
+#ifndef __FreeBSD__
ctrl = virPCIDeviceRead16(dev, fd, pos + PCI_EXT_ACS_CTRL);
+#else
+ ctrl = virPCIDeviceRead16(dev, pos + PCI_EXT_ACS_CTRL);
+#endif
if ((ctrl & PCI_EXT_CAP_ACS_ENABLED) != PCI_EXT_CAP_ACS_ENABLED) {
VIR_DEBUG("%s %s: downstream port has ACS disabled",
dev->id, dev->name);
@@ -2523,7 +2873,9 @@ virPCIDeviceDownstreamLacksACS(virPCIDevice *dev)
}
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(dev, fd);
+#endif
return ret;
}
@@ -2689,7 +3041,7 @@ virPCIGetVirtualFunctions(const char *sysfs_path,
}
-#ifdef __linux__
+#if defined(__linux__) || defined(__FreeBSD__)
virPCIDeviceAddress *
virPCIGetDeviceAddressFromSysfsLink(const char *device_link)
@@ -3189,33 +3541,43 @@ virPCIDeviceGetVPD(virPCIDevice *dev G_GNUC_UNUSED)
int
virPCIDeviceIsPCIExpress(virPCIDevice *dev)
{
+int ret = -1;
+#ifndef __FreeBSD__
int fd;
- int ret = -1;
if ((fd = virPCIDeviceConfigOpen(dev)) < 0)
return ret;
if (virPCIDeviceInit(dev, fd) < 0)
+#else
+ if (virPCIDeviceInit(dev) < 0)
+#endif
goto cleanup;
ret = dev->is_pcie;
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(dev, fd);
+#endif
return ret;
}
int
virPCIDeviceHasPCIExpressLink(virPCIDevice *dev)
{
- int fd;
int ret = -1;
uint16_t cap, type;
+#ifndef __FreeBSD__
+ int fd;
if ((fd = virPCIDeviceConfigOpen(dev)) < 0)
return ret;
if (virPCIDeviceInit(dev, fd) < 0)
+#else
+ if (virPCIDeviceInit(dev) < 0)
+#endif
goto cleanup;
if (dev->pcie_cap_pos == 0) {
@@ -3223,13 +3585,19 @@ virPCIDeviceHasPCIExpressLink(virPCIDevice *dev)
goto cleanup;
}
+#ifndef __FreeBSD__
cap = virPCIDeviceRead16(dev, fd, dev->pcie_cap_pos + PCI_CAP_FLAGS);
+#else
+ cap = virPCIDeviceRead16(dev, dev->pcie_cap_pos + PCI_CAP_FLAGS);
+#endif
type = (cap & PCI_EXP_FLAGS_TYPE) >> 4;
ret = type != PCI_EXP_TYPE_ROOT_INT_EP && type != PCI_EXP_TYPE_ROOT_EC;
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(dev, fd);
+#endif
return ret;
}
@@ -3242,13 +3610,17 @@ virPCIDeviceGetLinkCapSta(virPCIDevice *dev,
unsigned int *sta_width)
{
uint32_t t;
- int fd;
int ret = -1;
+#ifndef __FreeBSD__
+ int fd;
if ((fd = virPCIDeviceConfigOpen(dev)) < 0)
return ret;
if (virPCIDeviceInit(dev, fd) < 0)
+#else
+ if (virPCIDeviceInit(dev) < 0)
+#endif
goto cleanup;
if (!dev->pcie_cap_pos) {
@@ -3258,26 +3630,37 @@ virPCIDeviceGetLinkCapSta(virPCIDevice *dev,
goto cleanup;
}
+#ifndef __FreeBSD__
t = virPCIDeviceRead32(dev, fd, dev->pcie_cap_pos + PCI_EXP_LNKCAP);
+#else
+ t = virPCIDeviceRead32(dev, dev->pcie_cap_pos + PCI_EXP_LNKCAP);
+#endif
*cap_port = t >> 24;
*cap_speed = t & PCI_EXP_LNKCAP_SPEED;
*cap_width = (t & PCI_EXP_LNKCAP_WIDTH) >> 4;
+#ifndef __FreeBSD__
t = virPCIDeviceRead16(dev, fd, dev->pcie_cap_pos + PCI_EXP_LNKSTA);
+#else
+ t = virPCIDeviceRead16(dev, dev->pcie_cap_pos + PCI_EXP_LNKSTA);
+#endif
*sta_speed = t & PCI_EXP_LNKSTA_SPEED;
*sta_width = (t & PCI_EXP_LNKSTA_WIDTH) >> 4;
ret = 0;
cleanup:
+#ifndef __FreeBSD__
virPCIDeviceConfigClose(dev, fd);
+#endif
return ret;
}
int virPCIGetHeaderType(virPCIDevice *dev, int *hdrType)
{
+#ifndef __FreeBSD__
int fd;
uint8_t type;
@@ -3289,6 +3672,13 @@ int virPCIGetHeaderType(virPCIDevice *dev, int *hdrType)
type = virPCIDeviceRead8(dev, fd, PCI_HEADER_TYPE);
virPCIDeviceConfigClose(dev, fd);
+#else
+ uint8_t type = dev->pc_hdr;
+
+ *hdrType = -1;
+
+ type = virPCIDeviceRead8(dev, PCI_HEADER_TYPE);
+#endif
type &= PCI_HEADER_TYPE_MASK;
if (type >= VIR_PCI_HEADER_LAST) {
--
2.47.1
1 week
[PATCH] qemu: introduce load qemu.conf for "virt-qemu-run"
by Adam Julis
Adding a new option --config (or -c) for specifying a custom
qemu.conf file.
Previously, virt-qemu-run loaded default configuration values for
QEMU via qemuStateInitialize(). The configuration was loaded from
a temporary ../etc/ directory using virQEMUDriverConfigLoadFile(),
and any qemu.conf file present in that directory was also loaded
automatically.
This patch allows users to specify a custom configuration file,
which is copied into the temporary directory (or a permanent
folder if the -r option is used) before loading the
configuration. If an existing qemu.conf is present, it is
properly backed up and restored in case of a permanent folder.
The custom qemu.conf is always removed when the program exits.
Resolves: https://gitlab.com/libvirt/libvirt/-/issues/723
Signed-off-by: Adam Julis <ajulis(a)redhat.com>
---
docs/manpages/virt-qemu-run.rst | 9 +++
src/qemu/qemu_shim.c | 121 +++++++++++++++++++++++++++++++-
2 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/docs/manpages/virt-qemu-run.rst b/docs/manpages/virt-qemu-run.rst
index 4d546ff8cc..ba1c90b52a 100644
--- a/docs/manpages/virt-qemu-run.rst
+++ b/docs/manpages/virt-qemu-run.rst
@@ -72,6 +72,15 @@ whose UUID should match a secret referenced in the guest domain XML.
Display verbose information about startup.
+``-c`` *QEMU-CONF-FILE*,
+``--config``\ =\ *QEMU-CONF-FILE*
+
+Specify the QEMU configuration file to be used for starting the VM.
+*QEMU-CONF-FILE* is the full path to the QEMU configuration file.
+
+If this parameter is omitted, the default configuration values will
+be used.
+
``-h``, ``--help``
Display the command line help.
diff --git a/src/qemu/qemu_shim.c b/src/qemu/qemu_shim.c
index 7fdd69b538..64c87ab264 100644
--- a/src/qemu/qemu_shim.c
+++ b/src/qemu/qemu_shim.c
@@ -23,6 +23,7 @@
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
+#include <fcntl.h>
#include "virfile.h"
#include "virgettext.h"
@@ -132,6 +133,100 @@ qemuShimQuench(void *userData G_GNUC_UNUSED,
{
}
+/* Load specific QEMU config file, is the -c option is used */
+static int
+qemuAddConfigFile(const char *source_file, const char *root, bool *config_to_delete, long long deltams)
+{
+ int ret = -1;
+ struct stat st;
+ VIR_AUTOCLOSE srcFD = -1;
+ VIR_AUTOCLOSE dstFD = -1;
+ g_autofree char *config_dir = NULL;
+ g_autofree char *source_file_real = NULL;
+ g_autofree char *config_path_file = NULL;
+ g_autofree char *config_path_file_real = NULL;
+
+ if ((srcFD = open(source_file, O_RDONLY)) < 0) {
+ g_printerr("Couldn't open specific config file\n");
+ goto cleanup;
+ }
+
+ if (fstat(srcFD, &st) != 0) {
+ g_printerr("Specific config file does not exist\n");
+ goto cleanup;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ g_printerr("Specific config is not a regular file\n");
+ goto cleanup;
+ }
+
+ /* Since source file exists, make the destination path or
+ * validate that it already exists */
+ config_dir = g_strdup_printf("%s/etc/", root);
+
+ if (g_mkdir_with_parents(config_dir, 0777) < 0) {
+ g_printerr("Couldn't make the directory for specific config file\n");
+ goto cleanup;
+ }
+
+ config_path_file = g_strdup_printf("%sqemu.conf", config_dir);
+
+ /* If the source file is same as the destination file, no action needed */
+ if ((source_file_real = realpath(source_file, NULL)) &&
+ (config_path_file_real = realpath(config_path_file, NULL))) {
+ if (STREQ(source_file_real, config_path_file_real)) {
+ ret = 0;
+ goto cleanup;
+ }
+ }
+
+ /* Check already existing qemu.conf in the subfolder, if so, renamed
+ * (appended via deltams constant - should be unique). Final cleanup
+ * at main() will revert this change */
+ if (access(config_path_file, R_OK) == 0) {
+ if (rename(config_path_file, g_strdup_printf("%sqemu_old_%lld.conf",
+ config_dir, deltams)) != 0) {
+ g_printerr("Couldn't rename old config file, try delete it\n");
+ goto cleanup;
+ }
+ }
+
+ if ((dstFD = open(config_path_file, O_WRONLY | O_CREAT | O_TRUNC, 0644)) < 0) {
+ g_printerr("Couldn't open file for define specific config\n");
+ goto cleanup;
+ }
+
+ /* Set the flag for deleting the new/modified config in main() cleanup,
+ * for ensure that behaviour without the user config will be always
+ * consistent and take default value */
+ *config_to_delete = true;
+
+ do {
+ char buffer[1024];
+ ssize_t readed = 0;
+
+ readed = saferead(srcFD, buffer, 1024);
+
+ if (readed < 0) {
+ g_printerr("Couldn't read from specific config\n");
+ goto cleanup;
+ } else if (readed == 0) {
+ break;
+ } else {
+ ssize_t writed = safewrite(dstFD, buffer, readed);
+ if (writed != readed) {
+ g_printerr("Couldn't write to file for define specific config\n");
+ goto cleanup;
+ }
+ }
+ } while (1);
+
+ ret = 0;
+
+ cleanup:
+ return ret;
+}
+
int main(int argc, char **argv)
{
g_autoptr(virIdentity) sysident = NULL;
@@ -142,8 +237,11 @@ int main(int argc, char **argv)
g_autofree char *uri = NULL;
g_autofree char *suri = NULL;
const char *root = NULL;
+ const char *config = NULL;
+ long long delta_ms = 0;
g_autofree char *escaped = NULL;
bool tmproot = false;
+ bool config_to_delete = false;
int ret = 1;
g_autoptr(GError) error = NULL;
g_auto(GStrv) secrets = NULL;
@@ -156,6 +254,7 @@ int main(int argc, char **argv)
{ "root", 'r', 0, G_OPTION_ARG_STRING, &root, "Root directory", "DIR" },
{ "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "Debug output", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose output", NULL },
+ { "config", 'c', 0, G_OPTION_ARG_STRING, &config, "Load specific QEMU configuration file", "QEMU-CONF-FILE"},
{ 0 }
};
int quitfd[2] = {-1, -1};
@@ -172,7 +271,7 @@ int main(int argc, char **argv)
return 1;
}
- if (argc != 2) {
+ if (argc != 2 && argc != 3) {
g_autofree char *help = g_option_context_get_help(ctx, TRUE, NULL);
g_printerr("%s", help);
return 1;
@@ -234,6 +333,10 @@ int main(int argc, char **argv)
goto cleanup;
}
+ delta_ms = deltams();
+ if (config && (qemuAddConfigFile(config, root, &config_to_delete, delta_ms) < 0))
+ g_printerr("Specific config file was not loaded, default was used\n");
+
escaped = g_uri_escape_string(root, NULL, true);
virFileActivateDirOverrideForProg(argv[0]);
@@ -402,6 +505,22 @@ int main(int argc, char **argv)
VIR_FORCE_CLOSE(quitfd[0]);
VIR_FORCE_CLOSE(quitfd[1]);
+ if (config_to_delete) {
+ g_autofree char *possible_old_file = g_strdup_printf("%s/etc/qemu_old_%lld.conf",
+ root, delta_ms);
+ g_autofree char *to_delete = g_strdup_printf("%s/etc/qemu.conf", root);
+
+ if (remove(to_delete) != 0)
+ g_printerr("Deleting specific config failed, located in: %s\n",
+ to_delete);
+
+ if (access(possible_old_file, R_OK) == 0) {
+ if (rename(possible_old_file, to_delete) != 0)
+ g_printerr("Renaming your old qemu.conf failed, ups, located in %s\n",
+ possible_old_file);
+ }
+ }
+
if (dom != NULL)
virDomainFree(dom);
if (sconn != NULL)
--
2.47.1
1 week, 3 days
[PATCH 0/5] Drop support for VirtualBox-6.1
by Michal Privoznik
This was initiated by the following issue:
https://gitlab.com/libvirt/libvirt/-/issues/681
and I tried to add support for VBOX-7.1 but unfortunately got hit by a
bug in VBOX which renders the way we initialize C bindings useless. I've
reported the bug here:
https://www.virtualbox.org/ticket/22224
but it didn't get any attention.
Michal Prívozník (5):
vbox: Use g_autofree in tryLoadOne()
vbox: Report an error when VBox CAPI initialization fails
vbox: Drop support for VirtualBox-6.1.x
vbox: Drop code supporting old VBox version
NEWS: Document VBOX-6.1 removal
NEWS.rst | 5 +
src/vbox/meson.build | 1 -
src/vbox/vbox_CAPI_v6_1.h | 32896 --------------------------------
src/vbox/vbox_V6_1.c | 13 -
src/vbox/vbox_XPCOMCGlue.c | 5 +-
src/vbox/vbox_XPCOMCGlue.h | 2 +-
src/vbox/vbox_common.h | 4 +-
src/vbox/vbox_storage.c | 4 +-
src/vbox/vbox_tmpl.c | 30 +-
src/vbox/vbox_uniformed_api.h | 1 -
10 files changed, 16 insertions(+), 32945 deletions(-)
delete mode 100644 src/vbox/vbox_CAPI_v6_1.h
delete mode 100644 src/vbox/vbox_V6_1.c
--
2.45.2
1 week, 3 days
[PATCH V2 0/3] qemu: Improve opening and verifying save images
by Jim Fehlig
V2 of https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/D...
Changes in V2:
* Move unlinking corrupt save images from qemuSaveImageOpen to the only
caller using that functionality
* Add a function to read save image header
* Correctly position file pointer of save image for QEMU
Jim Fehlig (3):
qemu: Move unlinking corrupt save image file to caller
qemu: Decompose qemuSaveImageOpen
qemu: Check for valid save image format when verifying image header
src/qemu/qemu_driver.c | 46 ++++---
src/qemu/qemu_saveimage.c | 268 +++++++++++++++++++++++---------------
src/qemu/qemu_saveimage.h | 21 ++-
src/qemu/qemu_snapshot.c | 9 +-
4 files changed, 207 insertions(+), 137 deletions(-)
--
2.43.0
1 week, 3 days
[PATCH 0/2] qemu: Improve opening and verifying save images
by Jim Fehlig
qemuSaveImageOpen does a lot of stuff. When bypass-cache is specified,
it creates a virFileWrapperFd, which is not cleaned up in the subsequent
failure paths. This results in errors from the iohelper, which can
overwrite the actual error. E.g. consider libvirt attempting to restore
an image saved in an unknown format
# virsh restore --bypass-cache /data/test.sav.sparse
error: Failed to restore domain from /data/test.sav.sparse
error: internal error: Child process (LIBVIRT_LOG_OUTPUTS=1:stderr /usr/lib64/libvirt/libvirt_iohelper /data/test.sav.sparse 0) unexpected fatal signal 13
When not using the iohelper, and not creating a virFileWrapperFd, we see
the real error:
# virsh restore /data/test.sav.sparse
error: Failed to restore domain from /data/test.sav.sparse
error: operation failed: Invalid compressed save format 6
Although that error highlights a spot I missed when removing the
"compression" implications around the 'foo_image_format' settings in
qemu.conf with commit bd6d7ebf622 :-). IMO, the error message would be
best fixed by checking for valid values when reading the save image
metadata.
Patch 1 decomposes qemuSaveImageOpen to allow for better error handling
and more flexibility. Patch 2 checks for a valid format when checking the
other header fields.
Jim Fehlig (2):
qemu: Decompose qemuSaveImageOpen
qemu: Check for valid save image format when verifying image header
src/qemu/qemu_driver.c | 37 +++++++--------
src/qemu/qemu_saveimage.c | 95 +++++++++++++++++++++++++--------------
src/qemu/qemu_saveimage.h | 16 ++++---
src/qemu/qemu_snapshot.c | 9 ++--
4 files changed, 95 insertions(+), 62 deletions(-)
--
2.43.0
1 week, 3 days
[PATCH 00/19] Add qemu RDP server support
by marcandre.lureau@redhat.com
From: Marc-André Lureau <marcandre.lureau(a)redhat.com>
Hi,
This patch series offers an out-of-process Remote Desktop Protocol (RDP)
server solution utilizing QEMU's -display dbus interface, offering improved
modularity and potential security benefits compared to built-in server.
This initiative was spearheaded by Mihnea Buzatu during the QEMU Summer of Code
2023. The project's goal was to develop an out-of-process RDP server using the
-display dbus interface, implemented in Rust. Given that the IronRDP crate
lacked some server support at the time, investments in IronRDP were required.
I finally released an initial v0.1 version of qemu-rdp on crates.io
(https://crates.io/crates/qemu-rdp). That should allow more people to review and
evaluate the state of this work.
On unix systems, with cargo/rust toolchain installed, it should be as easy as
running "cargo install qemu-rdp", apply this patch series for libvirt, set the
"rdp_tls_x509_cert_dir" location for your TLS certificates, and configure a VM
with both dbus & rdp graphics (run "virsh domdisplay DOMAIN" to get the display
connection details).
Thanks for the reviews & feedback!
Marc-André Lureau (19):
build-sys: drop -Winline
build: fix -Werror=maybe-uninitialized
qemu-slirp: drop unneeded check for OOM
util: add conn != NULL precondition in virGDBusCallMethod()
qemu: report an error for unsupported graphics
qemu: add rdp state directory
qemu: add qemu RDP configuration
conf: parse optional RDP username & password
conf: generalize virDomainDefHasSpiceGraphics
qemu: use virDomainDefHasGraphics
qemu: add RDP ports range allocator
qemu: limit to one <graphics type='rdp'>
qemu/dbus: keep a connection to the VM D-Bus
qemu/dbus: log daemon stdout/err
qemu: validate RDP configuration
qemu: if -display dbus capability is supported, accept rdp
qemu: add qemu-rdp helper unit
qemu: add RDP support
tests: add qemu <graphics type='rdp'/> test
docs/formatdomain.rst | 25 +-
meson.build | 1 -
po/POTFILES | 1 +
src/conf/domain_conf.c | 28 +-
src/conf/domain_conf.h | 5 +-
src/conf/schemas/domaincommon.rng | 10 +
src/libvirt_private.syms | 2 +-
src/qemu/libvirtd_qemu.aug | 7 +
src/qemu/meson.build | 1 +
src/qemu/qemu.conf.in | 31 ++
src/qemu/qemu_capabilities.c | 7 +-
src/qemu/qemu_command.c | 11 +-
src/qemu/qemu_conf.c | 47 ++
src/qemu/qemu_conf.h | 13 +
src/qemu/qemu_dbus.c | 83 +++-
src/qemu/qemu_domain.c | 1 +
src/qemu/qemu_domain.h | 4 +
src/qemu/qemu_driver.c | 20 +
src/qemu/qemu_extdevice.c | 46 +-
src/qemu/qemu_hotplug.c | 49 +-
src/qemu/qemu_hotplug.h | 1 +
src/qemu/qemu_process.c | 167 ++++++-
src/qemu/qemu_rdp.c | 427 ++++++++++++++++++
src/qemu/qemu_rdp.h | 71 +++
src/qemu/qemu_slirp.c | 6 -
src/qemu/qemu_validate.c | 45 +-
src/qemu/test_libvirtd_qemu.aug.in | 5 +
src/util/virgdbus.c | 4 +
tests/domaincapsdata/qemu_10.0.0.s390x.xml | 1 +
.../domaincapsdata/qemu_7.0.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_7.0.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_7.0.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_7.1.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_7.1.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_7.1.0.x86_64.xml | 1 +
.../qemu_7.2.0-hvf.x86_64+hvf.xml | 1 +
.../domaincapsdata/qemu_7.2.0-q35.x86_64.xml | 1 +
.../qemu_7.2.0-tcg.x86_64+hvf.xml | 1 +
.../domaincapsdata/qemu_7.2.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_7.2.0.ppc.xml | 1 +
tests/domaincapsdata/qemu_7.2.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_8.0.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_8.0.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_8.0.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_8.1.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_8.1.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_8.1.0.s390x.xml | 1 +
tests/domaincapsdata/qemu_8.1.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_8.2.0-q35.x86_64.xml | 1 +
.../qemu_8.2.0-tcg-virt.loongarch64.xml | 1 +
.../domaincapsdata/qemu_8.2.0-tcg.x86_64.xml | 1 +
.../qemu_8.2.0-virt.aarch64.xml | 1 +
.../qemu_8.2.0-virt.loongarch64.xml | 1 +
tests/domaincapsdata/qemu_8.2.0.aarch64.xml | 1 +
tests/domaincapsdata/qemu_8.2.0.armv7l.xml | 1 +
tests/domaincapsdata/qemu_8.2.0.s390x.xml | 1 +
tests/domaincapsdata/qemu_8.2.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_9.0.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_9.0.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_9.0.0.sparc.xml | 1 +
tests/domaincapsdata/qemu_9.0.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_9.1.0-q35.x86_64.xml | 1 +
.../qemu_9.1.0-tcg-virt.riscv64.xml | 1 +
.../domaincapsdata/qemu_9.1.0-tcg.x86_64.xml | 1 +
.../qemu_9.1.0-virt.riscv64.xml | 1 +
tests/domaincapsdata/qemu_9.1.0.s390x.xml | 1 +
tests/domaincapsdata/qemu_9.1.0.x86_64.xml | 1 +
.../domaincapsdata/qemu_9.2.0-q35.x86_64.xml | 1 +
.../domaincapsdata/qemu_9.2.0-tcg.x86_64.xml | 1 +
tests/domaincapsdata/qemu_9.2.0.s390x.xml | 1 +
tests/domaincapsdata/qemu_9.2.0.x86_64.xml | 1 +
.../graphics-rdp.x86_64-latest.args | 35 ++
.../graphics-rdp.x86_64-latest.xml | 1 +
tests/qemuxmlconfdata/graphics-rdp.xml | 43 ++
tests/qemuxmlconftest.c | 2 +
tests/testutilsqemu.c | 4 +
tools/nss/libvirt_nss_leases.c | 2 +-
tools/nss/libvirt_nss_macs.c | 2 +-
78 files changed, 1172 insertions(+), 78 deletions(-)
create mode 100644 src/qemu/qemu_rdp.c
create mode 100644 src/qemu/qemu_rdp.h
create mode 100644 tests/qemuxmlconfdata/graphics-rdp.x86_64-latest.args
create mode 120000 tests/qemuxmlconfdata/graphics-rdp.x86_64-latest.xml
create mode 100644 tests/qemuxmlconfdata/graphics-rdp.xml
--
2.47.0
1 week, 4 days
[PATCH 0/2] finish sysusers support
by Daniel P. Berrangé
This fixes another missing sysusers file and changes the
RPM spec to take account of new RPM processing of sysusers
files at install time.
Daniel P. Berrangé (2):
tools: add sysusers file to create 'virtlogin' group
rpm: disable account creation for Fedora >= 42
libvirt.spec.in | 19 +++++++++++++++++++
tools/libvirt-login-shell.sysusers.conf | 1 +
tools/meson.build | 7 +++++++
3 files changed, 27 insertions(+)
create mode 100644 tools/libvirt-login-shell.sysusers.conf
--
2.47.1
1 week, 4 days
[RFC PATCH 0/5] qemu: Route hostdevs to multiple nested SMMUs
by Nathan Chen
Hi,
This is a draft solution for supporting multiple vSMMU instances in a qemu VM.
Based on discussions/suggestions received for a previous RFC by Nicolin here[0],
the association of vSMMUs to VFIO devices in VM PCIe topology should be moved
out of qemu into libvirt. In addition, the nested SMMU nodes should be passed
to qemu as pluggable devices.
To address these changes, this patch series introduces a new "nestedSmmuv3"
IOMMU model and "nestedSmmuv3" device type. Upon specifying the nestedSmmuv3
IOMMU model, nestedSmmuv3 devices will be auto-added to the VM definition based
on the available SMMU nodes in the host's sysfs. The nestedSmmuv3 devices will
each be attached to a separate PXB controller, and VFIO devices will be routed
to PXBs based on their association with host SMMU nodes. This will maintain a VM
PCIe topology that allows for multiple nested SMMUs per Nicolin's original qemu
patch series in [0] and Shameer's work in [1] to remove VM topology changes from
qemu and allow the nested SMMUs to be specified as pluggable devices.
For instance, if we specify the nestedSmmuv3 IOMMU model and a hostdev for
passthrough:
<devices>
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0009' bus='0x01' slot='0x00' function='0x0'/>
</source>
</hostdev>
<iommu model='nestedSmmuv3'/>
</devices>
Libvirt will scan sysfs and populate the VM definition with controllers and
nestedSmmuv3 devices based on host config. So if
/sys/bus/pci/devices/0009:01:00.0/iommu is a symlink to the host SMMU node
represented by
/sys/devices/platform/arm-smmu-v3.8.auto/iommu/smmu3.0x0000000016000000
and there are 3 host SMMU nodes under /sys/class/iommu/, we'll see three
auto-added nestedSmmuv3 devices, each routed to a pcie-expander-bus controller.
Then the hostdev will be routed to a PXB controller that has a matching host
SMMU node associated with it:
<devices>
...
<controller type='pci' index='1' model='pcie-expander-bus'>
<model name='pxb-pcie'/>
<target busNr='254'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0'/>
</controller>
<controller type='pci' index='2' model='pcie-expander-bus'>
<model name='pxb-pcie'/>
<target busNr='251'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</controller>
<controller type='pci' index='3' model='pcie-expander-bus'>
<model name='pxb-pcie'/>
<target busNr='249'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='7' port='0x8'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x01' function='0x0'/>
</controller>
<hostdev mode='subsystem' type='pci' managed='no'>
<source>
<address domain='0x0009' bus='0x01' slot='0x00' function='0x0'/>
</source>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</hostdev>
<iommu model='nestedSmmuv3'/>
<nestedSmmuv3>
<name>smmu3.0x0000000012000000</name>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</nestedSmmuv3>
<nestedSmmuv3>
<name>smmu3.0x0000000016000000</name>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</nestedSmmuv3>
<nestedSmmuv3>
<name>smmu3.0x0000000011000000</name>
<address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</nestedSmmuv3>
<iommu model='nestedSmmuv3'/>
</devices>
TODO:
- No DMA mapping can found by UEFI when specifying multiple passthrough devices
in the VM definition, and VM boot is subsequently blocked. We need to
investigate this for the next revision, but we don't encounter this issue when
passing through a single device. We'll include iommufd support in the next
revision to narrow down whether the required fix would be outside of libvirt.
- Shameer's qemu branch specifies nestedSmmuv3 bus number with "pci-bus"
instead of "bus", so the libvirt compilation test args and qemu args in
qemuBuildPCINestedSmmuv3DevProps() need to be modified to match this revision
of qemu. It will be reverted to using "bus" in the next qemu revision.
- This patchset decrements PXB busNr based on how many devices are attached
downstream, and the libvirt documentation states we must reserve busNr for the
PXB itself in addition to any devices attached downstream. When I launch a VM
and a PXB has a pcie-root-port and hostdev attached downstream, busNrs 253,
252, and 251 are reserved. But the PXB itself already has a bus number
assigned via the <address/> attribute, and I see 253 and 252 assigned to the
hostdev and pcie-root-port in the VM but not 251. Should we decrement busNr
based on libvirt documentation or do we only need two busNrs 253 and 252 in
the example here?
This series is on Github:
https://github.com/NathanChenNVIDIA/libvirt/tree/nested-smmuv3-12-05-24
Thanks,
Nathan
[0] https://lore.kernel.org/qemu-devel/cover.1719361174.git.nicolinc@nvidia.com/
[1] https://lore.kernel.org/qemu-devel/20241108125242.60136-1-shameerali.kolo...
Signed-off-by: Nathan Chen <nathanc(a)nvidia.com>
Nathan Chen (5):
conf: Add a nestedSmmuv3 IOMMU model
qemu: Implement and auto-add a nestedSmmuv3 device type
qemu: Create PXBs and auto-assign VFIO devs and nested SMMUs
qemu: Update PXB busNr for nestedSmmuv3 controllers
qemu: Add test case for specifying multiple nested SMMUs
docs/formatdomain.rst | 25 ++-
src/ch/ch_domain.c | 1 +
src/conf/domain_addr.c | 26 ++-
src/conf/domain_addr.h | 4 +-
src/conf/domain_conf.c | 188 +++++++++++++++++
src/conf/domain_conf.h | 15 ++
src/conf/domain_postparse.c | 1 +
src/conf/domain_validate.c | 24 +++
src/conf/schemas/domaincommon.rng | 17 ++
src/conf/virconftypes.h | 2 +
src/libvirt_private.syms | 2 +
src/lxc/lxc_driver.c | 6 +
src/qemu/qemu_command.c | 64 +++++-
src/qemu/qemu_command.h | 4 +
src/qemu/qemu_domain.c | 2 +
src/qemu/qemu_domain_address.c | 193 ++++++++++++++++++
src/qemu/qemu_driver.c | 3 +
src/qemu/qemu_hotplug.c | 5 +
src/qemu/qemu_postparse.c | 1 +
src/qemu/qemu_validate.c | 16 ++
src/test/test_driver.c | 4 +
tests/meson.build | 1 +
.../iommu-nestedsmmuv3.aarch64-latest.args | 38 ++++
.../iommu-nestedsmmuv3.aarch64-latest.xml | 61 ++++++
tests/qemuxmlconfdata/iommu-nestedsmmuv3.xml | 29 +++
tests/qemuxmlconftest.c | 4 +-
tests/schemas/device.rng.in | 1 +
tests/virnestedsmmuv3mock.c | 57 ++++++
28 files changed, 788 insertions(+), 6 deletions(-)
create mode 100644 tests/qemuxmlconfdata/iommu-nestedsmmuv3.aarch64-latest.args
create mode 100644 tests/qemuxmlconfdata/iommu-nestedsmmuv3.aarch64-latest.xml
create mode 100644 tests/qemuxmlconfdata/iommu-nestedsmmuv3.xml
create mode 100644 tests/virnestedsmmuv3mock.c
--
2.34.1
1 week, 4 days
[PATCH v2 00/15] qemu: Store I/O error messages for disks
by Peter Krempa
v2:
- ACK'd patches were pushed
- the QEMU 'qom-path' regression is no longer mentioned
- it's worker around by prefering lookup via node-name
- the patch is justified without the need to mention it
- 'qom-path' is now properly used for lookup of the disk
- I/O error messages are now stored in virStorageSource
- the messages can be viewed via virDomainGetMessages()
- various refactors to make the addition clean added
- news added
Peter Krempa (15):
qemu: Handle quirks of 'device' field of BLOCK_IO_ERROR event in
monitor code
qemu: Rename 'diskAlias' to 'device' in qemu IO error event handling
qemuProcessHandleIOError: Rename local variables
qemuMonitorJSONHandleIOError: Do not munge 'reason' field of IO error
event
qemuProcessHandleIOError: Prefer lookup by node name
qemuMonitorJSONHandleIOError: Propagate new 'qom-path' field
virStorageSource: Add fields for storing last I/O error message
qemuProcessHandleIOError: Populate I/O error reason to
virStorageSource
qemuProcessHandleIOError: Log IO errors in the VM log file
libxlDomainGetMessages: Add existing flags to 'virCheckFlags'
virDomainObjGetMessages: Refactor using GPtrArray
virDomainGetMessages: Introduce VIR_DOMAIN_MESSAGE_IOERRORS
include: libvirt-domain: Reword documentation for @reason of
VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON
include: libvirt-domain: Add 'hypervisor-message' @reason of
VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON
NEWS: Mention preserving I/O error messages for qemu VMs
NEWS.rst | 6 +++
include/libvirt/libvirt-domain.h | 19 ++++++--
src/conf/domain_conf.c | 80 +++++++++++++++++++++-----------
src/conf/domain_conf.h | 9 +++-
src/conf/storage_source_conf.c | 5 ++
src/conf/storage_source_conf.h | 6 +++
src/libvirt_private.syms | 1 +
src/libxl/libxl_driver.c | 13 +++++-
src/qemu/qemu_driver.c | 24 +++++++++-
src/qemu/qemu_monitor.c | 6 ++-
src/qemu/qemu_monitor.h | 8 +++-
src/qemu/qemu_monitor_json.c | 23 ++++++---
src/qemu/qemu_process.c | 61 +++++++++++++++---------
src/test/test_driver.c | 12 ++++-
14 files changed, 202 insertions(+), 71 deletions(-)
--
2.48.1
1 week, 4 days
[PATCH] glibcompat: Updating "backport" 'g_string_replace'
by Adam Julis
Update the vir_g_string_replace with following commits from glib:
c9e48947e gstring: Fix a heap buffer overflow in the new
g_string_replace() code
e8517e777 remove quadratic behavior in g_string_replace
Signed-off-by: Adam Julis <ajulis(a)redhat.com>
---
src/util/glibcompat.c | 125 +++++++++++++++++++++++++++++++++++-------
1 file changed, 105 insertions(+), 20 deletions(-)
diff --git a/src/util/glibcompat.c b/src/util/glibcompat.c
index bcb666992a..47e3edef13 100644
--- a/src/util/glibcompat.c
+++ b/src/util/glibcompat.c
@@ -65,7 +65,7 @@
/**
* Adapted (to pass syntax check) from 'g_string_replace' from
- * glib-2.81.1. Drop once minimum glib is bumped to 2.68.
+ * glib-2.83.3. Drop once minimum glib is bumped to 2.68.
*
* g_string_replace:
* @string: a #GString
@@ -94,35 +94,120 @@ vir_g_string_replace(GString *string,
const gchar *replace,
guint limit)
{
- gsize f_len, r_len, pos;
- gchar *cur, *next;
- guint n = 0;
+ GString *new_string = NULL;
+ gsize f_len, r_len, new_len;
+ gchar *cur, *next, *first, *dst;
+ guint n;
g_return_val_if_fail(string != NULL, 0);
g_return_val_if_fail(find != NULL, 0);
g_return_val_if_fail(replace != NULL, 0);
+ first = strstr(string->str, find);
+
+ if (first == NULL)
+ return 0;
+
+ new_len = string->len;
f_len = strlen(find);
r_len = strlen(replace);
- cur = string->str;
- while ((next = strstr(cur, find)) != NULL) {
- pos = next - string->str;
- g_string_erase(string, pos, f_len);
- g_string_insert(string, pos, replace);
- cur = string->str + pos + r_len;
- n++;
- /* Only match the empty string once at any given position, to
- * avoid infinite loops */
- if (f_len == 0) {
- if (cur[0] == '\0')
- break;
- else
- cur++;
+ /* It removes a lot of branches and possibility for infinite loops if we
+ * handle the case of an empty @find string separately. */
+ if (G_UNLIKELY(f_len == 0)) {
+ size_t i;
+ if (limit == 0 || limit > string->len) {
+ if (string->len > G_MAXSIZE - 1)
+ g_error("inserting in every position in string would overflow");
+
+ limit = string->len + 1;
+ }
+
+ if (r_len > 0 &&
+ (limit > G_MAXSIZE / r_len ||
+ limit * r_len > G_MAXSIZE - string->len))
+ g_error("inserting in every position in string would overflow");
+
+ new_len = string->len + limit * r_len;
+ new_string = g_string_sized_new(new_len);
+ for (i = 0; i < limit; i++) {
+ g_string_append_len(new_string, replace, r_len);
+ if (i < string->len)
+ g_string_append_c(new_string, string->str[i]);
}
- if (n == limit)
- break;
+ if (limit < string->len)
+ g_string_append_len(new_string, string->str + limit, string->len - limit);
+
+ g_free(string->str);
+ string->allocated_len = new_string->allocated_len;
+ string->len = new_string->len;
+ string->str = g_string_free(g_steal_pointer(&new_string), FALSE);
+
+ return limit;
}
+ /* Potentially do two passes: the first to calculate the length of the new string,
+ * new_len, if it’s going to be longer than the original string; and the second to
+ * do the replacements. The first pass is skipped if the new string is going to be
+ * no longer than the original.
+ *
+ * The second pass calls various g_string_insert_len() (and similar) methods
+ * which would normally potentially reallocate string->str, and hence
+ * invalidate the cur/next/first/dst pointers. Because we’ve pre-calculated
+ * the new_len and do all the string manipulations on new_string, that
+ * shouldn’t happen. This means we scan `string` while modifying
+ * `new_string`. */
+ do {
+ dst = first;
+ cur = first;
+ n = 0;
+ while ((next = strstr(cur, find)) != NULL) {
+ n++;
+
+ if (r_len <= f_len) {
+ memmove(dst, cur, next - cur);
+ dst += next - cur;
+ memcpy(dst, replace, r_len);
+ dst += r_len;
+ } else {
+ if (new_string == NULL) {
+ new_len += r_len - f_len;
+ } else {
+ g_string_append_len(new_string, cur, next - cur);
+ g_string_append_len(new_string, replace, r_len);
+ }
+ }
+ cur = next + f_len;
+
+ if (n == limit)
+ break;
+ }
+
+ /* Append the trailing characters from after the final instance of @find
+ * in the input string. */
+ if (r_len <= f_len) {
+ /* First pass skipped. */
+ gchar *end = string->str + string->len;
+ memmove(dst, cur, end - cur);
+ end = dst + (end - cur);
+ *end = 0;
+ string->len = end - string->str;
+ break;
+ } else {
+ if (new_string == NULL) {
+ /* First pass. */
+ new_string = g_string_sized_new(new_len);
+ g_string_append_len(new_string, string->str, first - string->str);
+ } else {
+ /* Second pass. */
+ g_string_append_len(new_string, cur, (string->str + string->len) - cur);
+ g_free(string->str);
+ string->allocated_len = new_string->allocated_len;
+ string->len = new_string->len;
+ string->str = g_string_free(g_steal_pointer(&new_string), FALSE);
+ break;
+ }
+ }
+ } while (1);
return n;
}
--
2.47.1
1 week, 4 days