On Mon, Aug 5, 2013 at 8:13 PM, Laine Stump <laine(a)laine.org> wrote:
This patch addresses two concerns with the error reporting when an
incompatible PCI address is specified for a device:
1) It wasn't always apparent which device had the problem. With this
patch applied, any error about an incompatible address will always
contain the full address as given in the config, so it will be easier
to determine which device's config aused the problem.
2) In some cases when the problem came from bad config, the error
message was erroneously classified as VIR_ERR_INTERNAL_ERROR. With
this patch applied, the same error message will be changed to indicate
either "internal" or "xml" error depending on whether the address
came
from the config, or was automatically generated by libvirt.
Note that in the case of "internal" (due to bad auto-generation)
errors, the PCI address won't be of much use in finding the location
in config to change (because it was automatically generated). Of
course that makes perfect sense, but still the address could provide a
clue about a bug in libvirt attempting to use a type of pci bus that
doesn't have its flags set correctly (or something similar). In other
words, it's not perfect, but it is definitely better.
---
src/qemu/qemu_command.c | 224 +++++++++++++++++++++++++++++-------------------
1 file changed, 138 insertions(+), 86 deletions(-)
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 3d670f9..6060bb0 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -1445,10 +1445,15 @@ struct _qemuDomainPCIAddressSet {
static bool
qemuDomainPCIAddressFlagsCompatible(virDevicePCIAddressPtr addr,
+ char *addrStr,
Slight nitpick. const char maybe?
qemuDomainPCIConnectFlags
busFlags,
qemuDomainPCIConnectFlags devFlags,
- bool reportError)
+ bool reportError,
+ bool fromConfig)
{
+ virErrorNumber errType = (fromConfig
+ ? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
+
/* If this bus doesn't allow the type of connection (PCI
* vs. PCIe) required by the device, or if the device requires
* hot-plug and this bus doesn't have it, return false.
@@ -1456,24 +1461,24 @@ qemuDomainPCIAddressFlagsCompatible(virDevicePCIAddressPtr addr,
if (!(devFlags & busFlags & QEMU_PCI_CONNECT_TYPES_MASK)) {
if (reportError) {
if (devFlags & QEMU_PCI_CONNECT_TYPE_PCI) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("PCI bus %.4x:%.2x is not compatible with the
"
- "device. Device requires a standard PCI slot,
"
- "which is not provided by this bus"),
- addr->domain, addr->bus);
+ virReportError(errType,
+ _("PCI bus is not compatible with the device "
+ "at %s. Device requires a standard PCI slot,
"
+ "which is not provided by bus %.4x:%.2x"),
+ addrStr, addr->domain, addr->bus);
} else if (devFlags & QEMU_PCI_CONNECT_TYPE_PCIE) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("PCI bus %.4x:%.2x is not compatible with the
"
- "device. Device requires a PCI Express slot,
"
- "which is not provided by this bus"),
- addr->domain, addr->bus);
+ virReportError(errType,
+ _("PCI bus is not compatible with the device "
+ "at %s. Device requires a PCI Express slot,
"
+ "which is not provided by bus %.4x:%.2x"),
+ addrStr, addr->domain, addr->bus);
} else {
/* this should never happen. If it does, there is a
* bug in the code that sets the flag bits for devices.
*/
- virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
- _("The device information has no PCI "
- "connection types listed"));
+ virReportError(errType,
+ _("The device information for %s has no PCI "
+ "connection types listed"), addrStr);
}
}
return false;
@@ -1481,11 +1486,11 @@ qemuDomainPCIAddressFlagsCompatible(virDevicePCIAddressPtr addr,
if ((devFlags & QEMU_PCI_CONNECT_HOTPLUGGABLE) &&
!(busFlags & QEMU_PCI_CONNECT_HOTPLUGGABLE)) {
if (reportError) {
- virReportError(VIR_ERR_INTERNAL_ERROR,
- _("PCI bus %.4x:%.2x is not compatible with the "
- "device. Device requires hot-plug capability, "
- "which is not provided by the bus"),
- addr->domain, addr->bus);
+ virReportError(errType,
+ _("PCI bus is not compatible with the device "
+ "at %s. Device requires hot-plug capability, "
+ "which is not provided by bus %.4x:%.2x"),
+ addrStr, addr->domain, addr->bus);
}
return false;
}
@@ -1500,23 +1505,30 @@ qemuDomainPCIAddressFlagsCompatible(virDevicePCIAddressPtr addr,
static bool
qemuDomainPCIAddressValidate(qemuDomainPCIAddressSetPtr addrs,
virDevicePCIAddressPtr addr,
- qemuDomainPCIConnectFlags flags)
+ char *addrStr,
Same here? const char?
+ qemuDomainPCIConnectFlags flags,
+ bool fromConfig)
{
qemuDomainPCIAddressBusPtr bus;
+ virErrorNumber errType = (fromConfig
+ ? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
if (addrs->nbuses == 0) {
- virReportError(VIR_ERR_XML_ERROR, "%s", _("No PCI buses
available"));
+ virReportError(errType, "%s", _("No PCI buses available"));
return false;
}
if (addr->domain != 0) {
- virReportError(VIR_ERR_XML_ERROR, "%s",
- _("Only PCI domain 0 is available"));
+ virReportError(errType,
+ _("Invalid PCI address %s. "
+ "Only PCI domain 0 is available"),
+ addrStr);
return false;
}
if (addr->bus >= addrs->nbuses) {
- virReportError(VIR_ERR_XML_ERROR,
- _("Only PCI buses up to %zu are available"),
- addrs->nbuses - 1);
+ virReportError(errType,
+ _("Invalid PCI address %s. "
+ "Only PCI buses up to %zu are available"),
+ addrStr, addrs->nbuses - 1);
return false;
}
@@ -1525,26 +1537,27 @@ qemuDomainPCIAddressValidate(qemuDomainPCIAddressSetPtr addrs,
/* assure that at least one of the requested connection types is
* provided by this bus
*/
- if (!qemuDomainPCIAddressFlagsCompatible(addr, bus->flags, flags, true))
+ if (!qemuDomainPCIAddressFlagsCompatible(addr, addrStr, bus->flags,
+ flags, true, fromConfig))
return false;
/* some "buses" are really just a single port */
if (bus->minSlot && addr->slot < bus->minSlot) {
- virReportError(VIR_ERR_XML_ERROR,
- _("Invalid PCI address: slot must be >= %zu"),
- bus->minSlot);
+ virReportError(errType,
+ _("Invalid PCI address %s. slot must be >= %zu"),
+ addrStr, bus->minSlot);
return false;
}
if (addr->slot > bus->maxSlot) {
- virReportError(VIR_ERR_XML_ERROR,
- _("Invalid PCI address: slot must be <= %zu"),
- bus->maxSlot);
+ virReportError(errType,
+ _("Invalid PCI address %s. slot must be <= %zu"),
+ addrStr, bus->maxSlot);
return false;
}
if (addr->function > QEMU_PCI_ADDRESS_FUNCTION_LAST) {
- virReportError(VIR_ERR_XML_ERROR,
- _("Invalid PCI address: function must be <= %u"),
- QEMU_PCI_ADDRESS_FUNCTION_LAST);
+ virReportError(errType,
+ _("Invalid PCI address %s. function must be <=
%u"),
+ addrStr, QEMU_PCI_ADDRESS_FUNCTION_LAST);
return false;
}
return true;
@@ -1953,12 +1966,12 @@ qemuDomainPCIAddressReserveAddr(qemuDomainPCIAddressSetPtr
addrs,
bool fromConfig)
{
int ret = -1;
- char *str = NULL;
+ char *addrStr = NULL;
qemuDomainPCIAddressBusPtr bus;
virErrorNumber errType = (fromConfig
? VIR_ERR_XML_ERROR : VIR_ERR_INTERNAL_ERROR);
- if (!(str = qemuDomainPCIAddressAsString(addr)))
+ if (!(addrStr = qemuDomainPCIAddressAsString(addr)))
goto cleanup;
/* Add an extra bus if necessary */
@@ -1967,7 +1980,7 @@ qemuDomainPCIAddressReserveAddr(qemuDomainPCIAddressSetPtr addrs,
/* Check that the requested bus exists, is the correct type, and we
* are asking for a valid slot
*/
- if (!qemuDomainPCIAddressValidate(addrs, addr, flags))
+ if (!qemuDomainPCIAddressValidate(addrs, addr, addrStr, flags, fromConfig))
goto cleanup;
bus = &addrs->buses[addr->bus];
@@ -1977,31 +1990,32 @@ qemuDomainPCIAddressReserveAddr(qemuDomainPCIAddressSetPtr
addrs,
virReportError(errType,
_("Attempted double use of PCI slot %s "
"(may need \"multifunction='on'\"
for "
- "device on function 0)"), str);
+ "device on function 0)"), addrStr);
goto cleanup;
}
bus->slots[addr->slot] = 0xFF; /* reserve all functions of slot */
- VIR_DEBUG("Reserving PCI slot %s (multifunction='off')",
str);
+ VIR_DEBUG("Reserving PCI slot %s (multifunction='off')",
addrStr);
} else {
if (bus->slots[addr->slot] & (1 << addr->function)) {
if (addr->function == 0) {
virReportError(errType,
- _("Attempted double use of PCI Address %s"),
str);
+ _("Attempted double use of PCI Address %s"),
+ addrStr);
} else {
virReportError(errType,
_("Attempted double use of PCI Address %s "
"(may need
\"multifunction='on'\" "
- "for device on function 0)"), str);
+ "for device on function 0)"), addrStr);
}
goto cleanup;
}
bus->slots[addr->slot] |= (1 << addr->function);
- VIR_DEBUG("Reserving PCI address %s", str);
+ VIR_DEBUG("Reserving PCI address %s", addrStr);
}
ret = 0;
cleanup:
- VIR_FREE(str);
+ VIR_FREE(addrStr);
return ret;
}
@@ -2017,7 +2031,8 @@ qemuDomainPCIAddressReserveSlot(qemuDomainPCIAddressSetPtr addrs,
int qemuDomainPCIAddressEnsureAddr(qemuDomainPCIAddressSetPtr addrs,
virDomainDeviceInfoPtr dev)
{
- int ret = 0;
+ int ret = -1;
+ char *addrStr = NULL;
/* Flags should be set according to the particular device,
* but only the caller knows the type of device. Currently this
* function is only used for hot-plug, though, and hot-plug is
@@ -2026,6 +2041,9 @@ int qemuDomainPCIAddressEnsureAddr(qemuDomainPCIAddressSetPtr
addrs,
qemuDomainPCIConnectFlags flags = (QEMU_PCI_CONNECT_HOTPLUGGABLE |
QEMU_PCI_CONNECT_TYPE_PCI);
+ if (!(addrStr = qemuDomainPCIAddressAsString(&dev->addr.pci)))
+ goto cleanup;
+
if (dev->type == VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI) {
/* We do not support hotplug multi-function PCI device now, so we should
* reserve the whole slot. The function of the PCI device must be 0.
@@ -2034,16 +2052,20 @@ int qemuDomainPCIAddressEnsureAddr(qemuDomainPCIAddressSetPtr
addrs,
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Only PCI device addresses with function=0"
" are supported"));
- return -1;
+ goto cleanup;
}
- if (!qemuDomainPCIAddressValidate(addrs, &dev->addr.pci, flags))
- return -1;
+ if (!qemuDomainPCIAddressValidate(addrs, &dev->addr.pci,
+ addrStr, flags, true))
+ goto cleanup;
ret = qemuDomainPCIAddressReserveSlot(addrs, &dev->addr.pci, flags);
} else {
ret = qemuDomainPCIAddressReserveNextSlot(addrs, dev, flags);
}
+
+cleanup:
+ VIR_FREE(addrStr);
return ret;
}
@@ -2063,12 +2085,20 @@ qemuDomainPCIAddressReleaseSlot(qemuDomainPCIAddressSetPtr
addrs,
* already had it, and are giving it back.
*/
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_TYPES_MASK;
+ int ret = -1;
+ char *addrStr = NULL;
- if (!qemuDomainPCIAddressValidate(addrs, addr, flags))
- return -1;
+ if (!(addrStr = qemuDomainPCIAddressAsString(addr)))
+ goto cleanup;
+
+ if (!qemuDomainPCIAddressValidate(addrs, addr, addrStr, flags, false))
+ goto cleanup;
addrs->buses[addr->bus].slots[addr->slot] = 0;
- return 0;
+ ret = 0;
+cleanup:
+ VIR_FREE(addrStr);
+ return ret;
}
void qemuDomainPCIAddressSetFree(qemuDomainPCIAddressSetPtr addrs)
@@ -2090,6 +2120,7 @@ qemuDomainPCIAddressGetNextSlot(qemuDomainPCIAddressSetPtr addrs,
* 0000:00:00.0
*/
virDevicePCIAddress a = { 0, 0, 0, 0, false };
+ char *addrStr = NULL;
/* except if this search is for the exact same type of device as
* last time, continue the search from the previous match
@@ -2099,13 +2130,17 @@ qemuDomainPCIAddressGetNextSlot(qemuDomainPCIAddressSetPtr
addrs,
if (addrs->nbuses == 0) {
virReportError(VIR_ERR_XML_ERROR, "%s", _("No PCI buses
available"));
- return -1;
+ goto error;
}
/* Start the search at the last used bus and slot */
for (a.slot++; a.bus < addrs->nbuses; a.bus++) {
- if (!qemuDomainPCIAddressFlagsCompatible(&a, addrs->buses[a.bus].flags,
- flags, false)) {
+ addrStr = NULL;
+ if (!(addrStr = qemuDomainPCIAddressAsString(&a)))
+ goto error;
+ if (!qemuDomainPCIAddressFlagsCompatible(&a, addrStr,
+ addrs->buses[a.bus].flags,
+ flags, false, false)) {
VIR_DEBUG("PCI bus %.4x:%.2x is not compatible with the device",
a.domain, a.bus);
continue;
@@ -2124,13 +2159,17 @@ qemuDomainPCIAddressGetNextSlot(qemuDomainPCIAddressSetPtr
addrs,
if (addrs->dryRun) {
/* a is already set to the first new bus and slot 1 */
if (qemuDomainPCIAddressSetGrow(addrs, &a, flags) < 0)
- return -1;
+ goto error;
goto success;
} else if (flags == addrs->lastFlags) {
/* Check the buses from 0 up to the last used one */
for (a.bus = 0; a.bus <= addrs->lastaddr.bus; a.bus++) {
- if (!qemuDomainPCIAddressFlagsCompatible(&a,
addrs->buses[a.bus].flags,
- flags, false)) {
+ addrStr = NULL;
+ if (!(addrStr = qemuDomainPCIAddressAsString(&a)))
+ goto error;
+ if (!qemuDomainPCIAddressFlagsCompatible(&a, addrStr,
+ addrs->buses[a.bus].flags,
+ flags, false, false)) {
VIR_DEBUG("PCI bus %.4x:%.2x is not compatible with the
device",
a.domain, a.bus);
continue;
@@ -2147,12 +2186,15 @@ qemuDomainPCIAddressGetNextSlot(qemuDomainPCIAddressSetPtr
addrs,
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("No more available PCI slots"));
+error:
+ VIR_FREE(addrStr);
return -1;
success:
VIR_DEBUG("Found free PCI slot %.4x:%.2x:%.2x",
a.domain, a.bus, a.slot);
*next_addr = a;
+ VIR_FREE(addrStr);
return 0;
}
@@ -2217,10 +2259,12 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
qemuDomainPCIAddressSetPtr addrs)
{
+ int ret = -1;
size_t i;
virDevicePCIAddress tmp_addr;
bool qemuDeviceVideoUsable = virQEMUCapsGet(qemuCaps,
QEMU_CAPS_DEVICE_VIDEO_PRIMARY);
virDevicePCIAddressPtr addrptr;
+ char *addrStr = NULL;
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_HOTPLUGGABLE |
QEMU_PCI_CONNECT_TYPE_PCI;
/* Verify that first IDE and USB controllers (if any) is on the PIIX3, fn 1 */
@@ -2235,7 +2279,7 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
def->controllers[i]->info.addr.pci.function != 1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary IDE controller must have PCI address
0:0:1.1"));
- goto error;
+ goto cleanup;
}
} else {
def->controllers[i]->info.type =
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
@@ -2255,7 +2299,7 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
def->controllers[i]->info.addr.pci.function != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PIIX3 USB controller must have PCI address
0:0:1.2"));
- goto error;
+ goto cleanup;
}
} else {
def->controllers[i]->info.type =
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
@@ -2274,7 +2318,7 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
memset(&tmp_addr, 0, sizeof(tmp_addr));
tmp_addr.slot = 1;
if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
- goto error;
+ goto cleanup;
}
if (def->nvideos > 0) {
@@ -2293,8 +2337,11 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
primaryVideo->info.addr.pci.function = 0;
addrptr = &primaryVideo->info.addr.pci;
- if (!qemuDomainPCIAddressValidate(addrs, addrptr, flags))
- goto error;
+ if (!(addrStr = qemuDomainPCIAddressAsString(addrptr)))
+ goto cleanup;
+ if (!qemuDomainPCIAddressValidate(addrs, addrptr,
+ addrStr, flags, false))
+ goto cleanup;
if (qemuDomainPCIAddressSlotInUse(addrs, addrptr)) {
if (qemuDeviceVideoUsable) {
@@ -2302,15 +2349,15 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&primaryVideo->info,
flags) < 0)
- goto error;
+ goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PCI address 0:0:2.0 is in use, "
"QEMU needs it for primary video"));
- goto error;
+ goto cleanup;
}
} else if (qemuDomainPCIAddressReserveSlot(addrs, addrptr, flags) < 0) {
- goto error;
+ goto cleanup;
}
} else if (!qemuDeviceVideoUsable) {
if (primaryVideo->info.addr.pci.domain != 0 ||
@@ -2319,7 +2366,7 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
primaryVideo->info.addr.pci.function != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary video card must have PCI address
0:0:2.0"));
- goto error;
+ goto cleanup;
}
/* If TYPE==PCI, then qemuCollectPCIAddress() function
* has already reserved the address, so we must skip */
@@ -2334,13 +2381,13 @@ qemuValidateDevicePCISlotsPIIX3(virDomainDefPtr def,
" intervention");
virResetLastError();
} else if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
{
- goto error;
+ goto cleanup;
}
}
- return 0;
-
-error:
- return -1;
+ ret = 0;
+cleanup:
+ VIR_FREE(addrStr);
+ return ret;
}
@@ -2357,10 +2404,12 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
virQEMUCapsPtr qemuCaps,
qemuDomainPCIAddressSetPtr addrs)
{
+ int ret = -1;
size_t i;
virDevicePCIAddress tmp_addr;
bool qemuDeviceVideoUsable = virQEMUCapsGet(qemuCaps,
QEMU_CAPS_DEVICE_VIDEO_PRIMARY);
virDevicePCIAddressPtr addrptr;
+ char *addrStr = NULL;
qemuDomainPCIConnectFlags flags = QEMU_PCI_CONNECT_TYPE_PCIE;
/* Verify that the first SATA controller is at 00:1F.2 */
@@ -2375,7 +2424,7 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
def->controllers[i]->info.addr.pci.function != 2) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary SATA controller must have PCI address
0:0:1f.2"));
- goto error;
+ goto cleanup;
}
} else {
def->controllers[i]->info.type =
VIR_DOMAIN_DEVICE_ADDRESS_TYPE_PCI;
@@ -2399,12 +2448,12 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
tmp_addr.multi = 1;
if (qemuDomainPCIAddressReserveAddr(addrs, &tmp_addr, flags,
false, false) < 0)
- goto error;
+ goto cleanup;
tmp_addr.function = 3;
tmp_addr.multi = 0;
if (qemuDomainPCIAddressReserveAddr(addrs, &tmp_addr, flags,
false, false) < 0)
- goto error;
+ goto cleanup;
}
if (def->nvideos > 0) {
@@ -2423,8 +2472,11 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
primaryVideo->info.addr.pci.function = 0;
addrptr = &primaryVideo->info.addr.pci;
- if (!qemuDomainPCIAddressValidate(addrs, addrptr, flags))
- goto error;
+ if (!(addrStr = qemuDomainPCIAddressAsString(addrptr)))
+ goto cleanup;
+ if (!qemuDomainPCIAddressValidate(addrs, addrptr,
+ addrStr, flags, false))
+ goto cleanup;
if (qemuDomainPCIAddressSlotInUse(addrs, addrptr)) {
if (qemuDeviceVideoUsable) {
@@ -2432,15 +2484,15 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
if (qemuDomainPCIAddressReserveNextSlot(addrs,
&primaryVideo->info,
flags) < 0)
- goto error;
+ goto cleanup;
} else {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("PCI address 0:0:1.0 is in use, "
"QEMU needs it for primary video"));
- goto error;
+ goto cleanup;
}
} else if (qemuDomainPCIAddressReserveSlot(addrs, addrptr, flags) < 0) {
- goto error;
+ goto cleanup;
}
} else if (!qemuDeviceVideoUsable) {
if (primaryVideo->info.addr.pci.domain != 0 ||
@@ -2449,7 +2501,7 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
primaryVideo->info.addr.pci.function != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Primary video card must have PCI address
0:0:1.0"));
- goto error;
+ goto cleanup;
}
/* If TYPE==PCI, then qemuCollectPCIAddress() function
* has already reserved the address, so we must skip */
@@ -2464,13 +2516,13 @@ qemuDomainValidateDevicePCISlotsQ35(virDomainDefPtr def,
" intervention");
virResetLastError();
} else if (qemuDomainPCIAddressReserveSlot(addrs, &tmp_addr, flags) < 0)
{
- goto error;
+ goto cleanup;
}
}
- return 0;
-
-error:
- return -1;
+ ret = 0;
+cleanup:
+ VIR_FREE(addrStr);
+ return ret;
}
--
1.7.11.7
Other than my slight nitpick, visually this looks good. I'll give it a
whirl with some of my bad configs tonight.
--
Doug Goldstein