From f991089ce4d8e1eb9e7e7b7dcc6d6a05c9c0baa6 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Thu, 16 Nov 2006 19:06:13 +0000 Subject: [PATCH] Support for inactive domain management --- ChangeLog | 10 + src/Makefile.am | 3 +- src/driver.h | 3 +- src/internal.h | 2 +- src/libvirt.c | 2 + src/xm_internal.c | 1377 +++++++++++++++++++++++++++++++++++++++++++++ src/xm_internal.h | 47 ++ 7 files changed, 1441 insertions(+), 3 deletions(-) create mode 100644 src/xm_internal.c create mode 100644 src/xm_internal.h diff --git a/ChangeLog b/ChangeLog index 7389c25252..d736a65b63 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +Thu Nov 16 13:09:42 EST 2006 Daniel Berrange + + * src/Makefile.am: Added new xm_internal.c source file + * src/libvirt.c: Call the xenXMRegister() method to activate + the XM backend + * src/driver.h: Added a unique id for XM driver backend + * src/internal.h: Increase number of drivers allowed + * src/xm_internal.h, src/xm_internal.c: New driver for + managing inactive domains from /etc/xen config files + Thu Nov 16 18:18:12 CET 2006 Daniel Veillard * include/libvirt/libvirt.h include/libvirt/libvirt.h.in diff --git a/src/Makefile.am b/src/Makefile.am index c4ff604fe0..07405d998d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -27,7 +27,8 @@ libvirt_la_SOURCES = \ virterror.c \ driver.h \ proxy_internal.c proxy_internal.h \ - conf.c conf.h + conf.c conf.h \ + xm_internal.c xm_internal.h bin_PROGRAMS = virsh diff --git a/src/driver.h b/src/driver.h index e2ae5c6fab..329240631a 100644 --- a/src/driver.h +++ b/src/driver.h @@ -21,7 +21,8 @@ typedef enum { VIR_DRV_XEN_STORE = 2, VIR_DRV_XEN_DAEMON = 3, VIR_DRV_TEST = 4, - VIR_DRV_XEN_PROXY = 5 + VIR_DRV_XEN_PROXY = 5, + VIR_DRV_XEN_XM = 6 } virDrvNo; diff --git a/src/internal.h b/src/internal.h index 8d36224a3b..c8a284a6b9 100644 --- a/src/internal.h +++ b/src/internal.h @@ -84,7 +84,7 @@ extern "C" { #define VIR_IS_DOMAIN(obj) ((obj) && (obj)->magic==VIR_DOMAIN_MAGIC) #define VIR_IS_CONNECTED_DOMAIN(obj) (VIR_IS_DOMAIN(obj) && VIR_IS_CONNECT((obj)->conn)) -#define MAX_DRIVERS 5 +#define MAX_DRIVERS 10 /* * Flags for Xen connections diff --git a/src/libvirt.c b/src/libvirt.c index 85410c920c..9b4cd86dc7 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -28,6 +28,7 @@ #include "xen_internal.h" #include "xend_internal.h" #include "xs_internal.h" +#include "xm_internal.h" #include "proxy_internal.h" #include "xml.h" #include "test.h" @@ -76,6 +77,7 @@ virInitialize(void) xenProxyRegister(); xenDaemonRegister(); xenStoreRegister(); + xenXMRegister(); testRegister(); return(0); } diff --git a/src/xm_internal.c b/src/xm_internal.c new file mode 100644 index 0000000000..9268edcd90 --- /dev/null +++ b/src/xm_internal.c @@ -0,0 +1,1377 @@ +/* + * xm_internal.h: helper routines for dealing with inactive domains + * + * Copyright (C) 2006 + * + * Daniel Berrange + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License. See the file COPYING.LIB in the main directory of this + * archive for more details. + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + + +#include "xm_internal.h" +#include "xend_internal.h" +#include "conf.h" +#include "hash.h" +#include "internal.h" +#include "xml.h" + +typedef struct xenXMConfCache *xenXMConfCachePtr; +typedef struct xenXMConfCache { + time_t refreshedAt; + char filename[PATH_MAX]; + virConfPtr conf; +} xenXMConfCache; + +static char configDir[PATH_MAX]; +static virHashTablePtr configCache = NULL; +static int nconnections = 0; +static time_t lastRefresh = 0; + +#define XM_REFRESH_INTERVAL 10 + +#define XM_CONFIG_DIR "/etc/xen" +#define XM_EXAMPLE_PREFIX "xmexample" +#define XEND_CONFIG_FILE "xend-config.sxp" +#define XEND_PCI_CONFIG_PREFIX "xend-pci-" +#define QEMU_IF_SCRIPT "qemu-ifup" + +static virDriver xenXMDriver = { + VIR_DRV_XEN_XM, + "XenXM", + (DOM0_INTERFACE_VERSION >> 24) * 1000000 + + ((DOM0_INTERFACE_VERSION >> 16) & 0xFF) * 1000 + + (DOM0_INTERFACE_VERSION & 0xFFFF), + NULL, /* init */ + xenXMOpen, /* open */ + xenXMClose, /* close */ + xenXMGetType, /* type */ + NULL, /* version */ + NULL, /* nodeGetInfo */ + NULL, /* listDomains */ + NULL, /* numOfDomains */ + NULL, /* domainCreateLinux */ + NULL, /* domainLookupByID */ + xenXMDomainLookupByUUID, /* domainLookupByUUID */ + xenXMDomainLookupByName, /* domainLookupByName */ + NULL, /* domainSuspend */ + NULL, /* domainResume */ + NULL, /* domainShutdown */ + NULL, /* domainReboot */ + NULL, /* domainDestroy */ + NULL, /* domainFree */ + NULL, /* domainGetName */ + NULL, /* domainGetID */ + NULL, /* domainGetUUID */ + NULL, /* domainGetOSType */ + xenXMDomainGetMaxMemory, /* domainGetMaxMemory */ + xenXMDomainSetMaxMemory, /* domainSetMaxMemory */ + xenXMDomainSetMemory, /* domainMaxMemory */ + xenXMDomainGetInfo, /* domainGetInfo */ + NULL, /* domainSave */ + NULL, /* domainRestore */ + xenXMDomainSetVcpus, /* domainSetVcpus */ + NULL, /* domainPinVcpu */ + NULL, /* domainGetVcpus */ + xenXMDomainDumpXML, /* domainDumpXML */ + xenXMListDefinedDomains, /* listDefinedDomains */ + xenXMNumOfDefinedDomains, /* numOfDefinedDomains */ + xenXMDomainCreate, /* domainCreate */ + xenXMDomainDefineXML, /* domainDefineXML */ + xenXMDomainUndefine, /* domainUndefine */ + NULL, /* domainAttachDevice */ + NULL, /* domainDetachDevice */ +}; + +static void +xenXMError(virConnectPtr conn, virErrorNumber error, const char *info) +{ + const char *errmsg; + + if (error == VIR_ERR_OK) + return; + + errmsg = __virErrorMsg(error, info); + __virRaiseError(conn, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR, + errmsg, info, NULL, 0, 0, errmsg, info); +} + +void xenXMRegister(void) +{ + char *envConfigDir; + int safeMode = 0; + virRegisterDriver(&xenXMDriver); + + /* Disable use of env variable if running setuid */ + if ((geteuid() != getuid()) || + (getegid() != getgid())) + safeMode = 1; + + if (!safeMode && + (envConfigDir = getenv("LIBVIRT_XM_CONFIG_DIR")) != NULL) { + strncpy(configDir, envConfigDir, PATH_MAX-1); + configDir[PATH_MAX-1] = '\0'; + } else { + strcpy(configDir, XM_CONFIG_DIR); + } +} + + +/* Remove any configs which were not refreshed recently */ +static int xenXMConfigReaper(const void *payload, const char *key ATTRIBUTE_UNUSED, const void *data) { + time_t now = *(const time_t *)data; + xenXMConfCachePtr entry = (xenXMConfCachePtr)payload; + + if (entry->refreshedAt != now) + return (1); + return (0); +} + +/* Convenience method to grab a int from the config file object */ +static int xenXMConfigGetInt(virConfPtr conf, const char *name, long *value) { + virConfValuePtr val; + if (!value || !name || !conf) + return (-1); + + if (!(val = virConfGetValue(conf, name))) { + return (-1); + } + + if (val->type == VIR_CONF_LONG) { + *value = val->l; + } else if (val->type == VIR_CONF_STRING) { + char *ret; + if (!val->str) + return (-1); + *value = strtol(val->str, &ret, 10); + if (ret == val->str) + return (-1); + } else { + return (-1); + } + return (0); +} + + +/* Convenience method to grab a string from the config file object */ +static int xenXMConfigGetString(virConfPtr conf, const char *name, const char **value) { + virConfValuePtr val; + if (!value || !name || !conf) + return (-1); + *value = NULL; + if (!(val = virConfGetValue(conf, name))) { + return (-1); + } + if (val->type != VIR_CONF_STRING) + return (-1); + if (!val->str) + return (-1); + *value = val->str; + return (0); +} + +/* Convenience method to grab a string UUID from the config file object */ +static int xenXMConfigGetUUID(virConfPtr conf, const char *name, unsigned char *uuid) { + virConfValuePtr val; + char *rawuuid = (char *)uuid; + if (!uuid || !name || !conf) + return (-1); + if (!(val = virConfGetValue(conf, name))) { + return (-1); + } + + if (val->type != VIR_CONF_STRING) + return (-1); + if (!val->str) + return (-1); + + if (!virParseUUID(&rawuuid, val->str)) + return (-1); + + return (0); +} + +/* Generate a rnadom UUID - used if domain doesn't already + have one in its config */ +static void xenXMConfigGenerateUUID(unsigned char *uuid) { + int i; + for (i = 0 ; i < 16 ; i++) { + uuid[i] = (unsigned char)(1 + (int) (256.0 * (rand() / (RAND_MAX + 1.0)))); + } +} + +/* Ensure that a config object has a valid UUID in it, + if it doesn't then (re-)generate one */ +static int xenXMConfigEnsureUUID(virConfPtr conf) { + unsigned char uuid[16]; + + /* If there is no uuid...*/ + if (xenXMConfigGetUUID(conf, "uuid", uuid) < 0) { + virConfValuePtr value; + char uuidstr[37]; + + value = malloc(sizeof(virConfValue)); + if (!value) { + return (-1); + } + + /* ... then generate one */ + xenXMConfigGenerateUUID(uuid); + snprintf(uuidstr, 37, + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + uuidstr[36] = '\0'; + + value->type = VIR_CONF_STRING; + value->str = strdup(uuidstr); + if (!value->str) { + free(value); + return (-1); + } + + /* And stuff the UUID back into the config file */ + if (virConfSetValue(conf, "uuid", value) < 0) + return (-1); + } + return (0); +} + +/* Release memory associated with a cached config object */ +static void xenXMConfigFree(void *payload, const char *key ATTRIBUTE_UNUSED) { + xenXMConfCachePtr entry = (xenXMConfCachePtr)payload; + virConfFree(entry->conf); + free(entry); +} + + +/* This method is called by various methods to scan /etc/xen + (or whatever directory was set by LIBVIRT_XM_CONFIG_DIR + environment variable) and process any domain configs. It + has rate-limited so never rescans more frequently than + once every X seconds */ +static int xenXMConfigCacheRefresh(void) { + DIR *dh; + struct dirent *ent; + time_t now = time(NULL); + int ret = -1; + + if (now == ((time_t)-1)) { + return (-1); + } + + /* Rate limit re-scans */ + if ((now - lastRefresh) < XM_REFRESH_INTERVAL) + return (0); + + lastRefresh = now; + + /* Process the files in the config dir */ + if (!(dh = opendir(configDir))) { + return (-1); + } + + while ((ent = readdir(dh))) { + xenXMConfCachePtr entry; + struct stat st; + int newborn = 0; + char path[PATH_MAX]; + + /* + * Skip a bunch of crufty files that clearly aren't config files + */ + + /* Like 'dot' files... */ + if (!strncmp(ent->d_name, ".", 1)) + continue; + /* ...and the XenD server config file */ + if (!strncmp(ent->d_name, XEND_CONFIG_FILE, strlen(XEND_CONFIG_FILE))) + continue; + /* ...and random PCI config cruft */ + if (!strncmp(ent->d_name, XEND_PCI_CONFIG_PREFIX, strlen(XEND_PCI_CONFIG_PREFIX))) + continue; + /* ...and the example domain configs */ + if (!strncmp(ent->d_name, XM_EXAMPLE_PREFIX, strlen(XM_EXAMPLE_PREFIX))) + continue; + /* ...and the QEMU networking script */ + if (!strncmp(ent->d_name, QEMU_IF_SCRIPT, strlen(QEMU_IF_SCRIPT))) + continue; + + /* ...and editor backups */ + if (ent->d_name[0] == '#') + continue; + if (ent->d_name[strlen(ent->d_name)-1] == '~') + continue; + + /* Build the full file path */ + if ((strlen(configDir) + 1 + strlen(ent->d_name) + 1) > PATH_MAX) + continue; + strcpy(path, configDir); + strcat(path, "/"); + strcat(path, ent->d_name); + + /* Skip anything which isn't a file (takes care of scripts/ subdir */ + if ((stat(path, &st) < 0) || + (!S_ISREG(st.st_mode))) { + continue; + } + + /* If we already have a matching entry and it is not + modified, then carry on to next one*/ + if ((entry = virHashLookup(configCache, ent->d_name))) { + if (entry->refreshedAt >= st.st_mtime) { + entry->refreshedAt = now; + continue; + } + } + + if (entry) { /* Existing entry which needs refresh */ + virConfFree(entry->conf); + entry->conf = NULL; + } else { /* Completely new entry */ + newborn = 1; + if (!(entry = malloc(sizeof(xenXMConfCache)))) { + goto cleanup; + } + memcpy(entry->filename, path, PATH_MAX); + } + entry->refreshedAt = now; + + if (!(entry->conf = virConfReadFile(entry->filename)) || + xenXMConfigEnsureUUID(entry->conf) < 0) { + if (!newborn) { + virHashRemoveEntry(configCache, ent->d_name, NULL); + } + free(entry); + continue; + } + + /* If its a completely new entry, it must be stuck into + the cache (refresh'd entries are already registered) */ + if (newborn) { + if (virHashAddEntry(configCache, ent->d_name, entry) < 0) { + virConfFree(entry->conf); + free(entry); + goto cleanup; + } + } + } + + /* Reap all entries which were not changed, by comparing + their refresh timestamp - the timestamp should match + 'now' if they were refreshed. If timestamp doesn't match + then the config is no longer on disk */ + virHashRemoveSet(configCache, xenXMConfigReaper, xenXMConfigFree, (const void*) &now); + ret = 0; + + cleanup: + if (dh) + closedir(dh); + + return (ret); +} + + +/* + * Open a 'connection' to the config file directory ;-) + * We just create a hash table to store config files in. + * We only support a single directory, so repeated calls + * to open all end up using the same cache of files + */ +int xenXMOpen(virConnectPtr conn ATTRIBUTE_UNUSED, const char *name, int flags ATTRIBUTE_UNUSED) { + if (name && + strcasecmp(name, "xen")) { + return (-1); + } + + if (nconnections == 0) { + configCache = virHashCreate(50); + if (!configCache) + return (-1); + } + nconnections++; + + return (0); +} + +/* + * Free the config files in the cache if this is the + * last connection + */ +int xenXMClose(virConnectPtr conn ATTRIBUTE_UNUSED) { + if (!nconnections--) { + virHashFree(configCache, xenXMConfigFree); + configCache = NULL; + } + return (0); +} + +/* + * Our backend type + */ +const char *xenXMGetType(virConnectPtr conn ATTRIBUTE_UNUSED) { + return ("XenXM"); +} + +/* + * Since these are all offline domains, we only return info about + * VCPUs and memory. + */ +int xenXMDomainGetInfo(virDomainPtr domain, virDomainInfoPtr info) { + xenXMConfCachePtr entry; + long vcpus; + long mem; + if ((domain == NULL) || (domain->conn == NULL) || (domain->name == NULL)) { + xenXMError((domain ? domain->conn : NULL), VIR_ERR_INVALID_ARG, + __FUNCTION__); + return(-1); + } + + if (domain->handle != -1) + return (-1); + + if (!(entry = virHashLookup(configCache, domain->name))) + return (-1); + + memset(info, 0, sizeof(virDomainInfo)); + if (xenXMConfigGetInt(entry->conf, "memory", &mem) < 0 || + mem < 0) + info->memory = 64 * 1024; + else + info->memory = (unsigned long)mem * 1024; + if (xenXMConfigGetInt(entry->conf, "maxmem", &mem) < 0 || + mem < 0) + info->maxMem = info->memory; + else + info->maxMem = (unsigned long)mem * 1024; + + if (xenXMConfigGetInt(entry->conf, "vcpus", &vcpus) < 0 || + vcpus < 0) + info->nrVirtCpu = 1; + else + info->nrVirtCpu = (unsigned short)vcpus; + info->state = VIR_DOMAIN_SHUTOFF; + info->cpuTime = 0; + + return (0); + +} + +/* + * Turn a config record into a lump of XML describing the + * domain, suitable for later feeding for virDomainCreateLinux + */ +char *xenXMDomainDumpXML(virDomainPtr domain, int flags ATTRIBUTE_UNUSED) { + virBufferPtr buf; + xenXMConfCachePtr entry; + char *xml; + const char *name; + unsigned char uuid[16]; + const char *str; + int hvm = 0; + long val; + virConfValuePtr list; + + if ((domain == NULL) || (domain->conn == NULL) || (domain->name == NULL)) { + xenXMError((domain ? domain->conn : NULL), VIR_ERR_INVALID_ARG, + __FUNCTION__); + return(NULL); + } + if (domain->handle != -1) + return (NULL); + if (!(entry = virHashLookup(configCache, domain->name))) + return (NULL); + + if (xenXMConfigGetString(entry->conf, "name", &name) < 0) + return (NULL); + if (xenXMConfigGetUUID(entry->conf, "uuid", uuid) < 0) + return (NULL); + + buf = virBufferNew(4096); + + virBufferAdd(buf, "\n", -1); + virBufferVSprintf(buf, " %s\n", name); + virBufferVSprintf(buf, + " %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], uuid[6], uuid[7], + uuid[8], uuid[9], uuid[10], uuid[11], + uuid[12], uuid[13], uuid[14], uuid[15]); + + if ((xenXMConfigGetString(entry->conf, "builder", &str) == 0) && + !strcmp(str, "hvm")) + hvm = 1; + + if (hvm) { + virBufferAdd(buf, " \n", -1); + virBufferAdd(buf, " hvm\n", -1); + if (xenXMConfigGetString(entry->conf, "kernel", &str) == 0) + virBufferVSprintf(buf, " %s\n", str); + virBufferAdd(buf, " \n", -1); + } else { + + if (xenXMConfigGetString(entry->conf, "bootloader", &str) == 0) + virBufferVSprintf(buf, " %s\n", str); + if (xenXMConfigGetString(entry->conf, "kernel", &str) == 0) { + virBufferAdd(buf, " \n", -1); + virBufferAdd(buf, " linux\n", -1); + virBufferVSprintf(buf, " %s\n", str); + if (xenXMConfigGetString(entry->conf, "ramdisk", &str) == 0) + virBufferVSprintf(buf, " %s\n", str); + if (xenXMConfigGetString(entry->conf, "extra", &str) == 0) + virBufferVSprintf(buf, " %s\n", str); + virBufferAdd(buf, " \n", -1); + } + } + + if (xenXMConfigGetInt(entry->conf, "memory", &val) < 0) + val = 64; + virBufferVSprintf(buf, " %ld\n", val * 1024); + + if (xenXMConfigGetInt(entry->conf, "vcpus", &val) < 0) + val = 1; + virBufferVSprintf(buf, " %ld\n", val); + + + + if (xenXMConfigGetString(entry->conf, "on_poweroff", &str) < 0) + str = "destroy"; + virBufferVSprintf(buf, " %s\n", str); + + if (xenXMConfigGetString(entry->conf, "on_reboot", &str) < 0) + str = "restart"; + virBufferVSprintf(buf, " %s\n", str); + + if (xenXMConfigGetString(entry->conf, "on_crash", &str) < 0) + str = "restart"; + virBufferVSprintf(buf, " %s\n", str); + + + if (hvm) { + virBufferAdd(buf, " \n", -1); + if (xenXMConfigGetInt(entry->conf, "pae", &val) == 0 && + val) + virBufferAdd(buf, " \n", -1); + if (xenXMConfigGetInt(entry->conf, "acpi", &val) == 0 && + val) + virBufferAdd(buf, " \n", -1); + if (xenXMConfigGetInt(entry->conf, "apic", &val) == 0 && + val) + virBufferAdd(buf, " \n", -1); + virBufferAdd(buf, " \n", -1); + } + + virBufferAdd(buf, " \n", -1); + + list = virConfGetValue(entry->conf, "disk"); + while (list && list->type == VIR_CONF_LIST) { + virConfValuePtr el = list->list; + int block = 0; + char dev[NAME_MAX]; + char src[PATH_MAX]; + char drvName[NAME_MAX] = ""; + char drvType[NAME_MAX] = ""; + char *device; + char *mode; + char *path; + + if ((el== NULL) || (el->type != VIR_CONF_STRING) || (el->str == NULL)) + goto skipdisk; + + if (!(device = index(el->str, ',')) || device[0] == '\0') + goto skipdisk; + device++; + if (!(mode = index(device, ',')) || mode[0] == '\0') + goto skipdisk; + mode++; + + if (!(path = index(el->str, ':')) || path[0] == '\0' || path > device) + goto skipdisk; + + strncpy(drvName, el->str, (path-el->str)); + if (!strcmp(drvName, "tap")) { + if (!(path = index(el->str+4, ':')) || path[0] == '\0' || path > device) + goto skipdisk; + + strncpy(drvType, el->str+4, (path-(el->str+4))); + } + if ((device-path) > PATH_MAX) + goto skipdisk; + + strncpy(src, path+1, (device-(path+1))-1); + src[(device-(path+1))-1] = '\0'; + + if (!strcmp(drvName, "phy")) { + block = 1; + } + + if ((mode-device-1) > (NAME_MAX-1)) { + goto skipdisk; + } + strncpy(dev, device, (mode-device-1)); + dev[(mode-device-1)] = '\0'; + + virBufferVSprintf(buf, " \n", block ? "block" : "file"); + if (drvType[0]) + virBufferVSprintf(buf, " \n", drvName, drvType); + else + virBufferVSprintf(buf, " \n", drvName); + virBufferVSprintf(buf, " \n", block ? "dev" : "file", src); + virBufferVSprintf(buf, " \n", dev); + if (*mode == 'r') + virBufferAdd(buf, " \n", -1); + virBufferAdd(buf, " \n", -1); + + skipdisk: + list = list->next; + } + + list = virConfGetValue(entry->conf, "vif"); + while (list && list->type == VIR_CONF_LIST) { + virConfValuePtr el = list->list; + int type = -1; + char script[PATH_MAX]; + char ip[16]; + char mac[18]; + char *key; + + mac[0] = '\0'; + script[0] = '\0'; + ip[0] = '\0'; + + if ((el== NULL) || (el->type != VIR_CONF_STRING) || (el->str == NULL)) + goto skipnic; + + key = el->str; + while (key) { + char *data; + char *nextkey = index(key, ','); + + if (!(data = index(key, '=')) || (data[0] == '\0')) + goto skipnic; + data++; + + if (!strncmp(key, "mac=", 4)) { + int len = nextkey ? (nextkey - data) : 17; + if (len > 17) + len = 17; + strncpy(mac, data, len); + mac[len] = '\0'; + } else if (!strncmp(key, "bridge=", 7)) { + type = 1; + } else if (!strncmp(key, "script=", 7)) { + int len = nextkey ? (nextkey - data) : PATH_MAX-1; + if (len > (PATH_MAX-1)) + len = PATH_MAX-1; + strncpy(script, data, len); + script[len] = '\0'; + } else if (!strncmp(key, "ip=", 3)) { + int len = nextkey ? (nextkey - data) : 15; + if (len > 15) + len = 15; + strncpy(ip, data, len); + ip[len] = '\0'; + } + + while (nextkey && (nextkey[0] == ',' || + nextkey[0] == ' ' || + nextkey[0] == '\t')) + nextkey++; + key = nextkey; + } + + /* XXX Forcing to pretend its a bridge */ + if (type == -1) { + type = 1; + } + + virBufferAdd(buf, " \n", -1); + if (mac[0]) + virBufferVSprintf(buf, " \n", mac); + if (script[0]) + virBufferVSprintf(buf, "