Index: src/Makefile.am =================================================================== RCS file: /data/cvs/libvirt/src/Makefile.am,v retrieving revision 1.29 diff -c -r1.29 Makefile.am *** src/Makefile.am 21 Sep 2006 15:24:37 -0000 1.29 --- src/Makefile.am 15 Nov 2006 02:33:59 -0000 *************** *** 27,33 **** virterror.c \ driver.h \ proxy_internal.c proxy_internal.h \ ! conf.c conf.h bin_PROGRAMS = virsh --- 27,34 ---- virterror.c \ driver.h \ proxy_internal.c proxy_internal.h \ ! conf.c conf.h \ ! xm_internal.c xm_internal.h bin_PROGRAMS = virsh Index: src/driver.h =================================================================== RCS file: /data/cvs/libvirt/src/driver.h,v retrieving revision 1.12 diff -c -r1.12 driver.h *** src/driver.h 30 Aug 2006 14:21:03 -0000 1.12 --- src/driver.h 15 Nov 2006 02:33:59 -0000 *************** *** 21,27 **** VIR_DRV_XEN_STORE = 2, VIR_DRV_XEN_DAEMON = 3, VIR_DRV_TEST = 4, ! VIR_DRV_XEN_PROXY = 5 } virDrvNo; --- 21,28 ---- VIR_DRV_XEN_STORE = 2, VIR_DRV_XEN_DAEMON = 3, VIR_DRV_TEST = 4, ! VIR_DRV_XEN_PROXY = 5, ! VIR_DRV_XEN_XM = 6 } virDrvNo; Index: src/internal.h =================================================================== RCS file: /data/cvs/libvirt/src/internal.h,v retrieving revision 1.24 diff -c -r1.24 internal.h *** src/internal.h 21 Sep 2006 15:24:37 -0000 1.24 --- src/internal.h 15 Nov 2006 02:34:00 -0000 *************** *** 84,90 **** #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 /* * Flags for Xen connections --- 84,90 ---- #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 10 /* * Flags for Xen connections Index: src/libvirt.c =================================================================== RCS file: /data/cvs/libvirt/src/libvirt.c,v retrieving revision 1.48 diff -c -r1.48 libvirt.c *** src/libvirt.c 8 Nov 2006 13:53:29 -0000 1.48 --- src/libvirt.c 15 Nov 2006 02:34:01 -0000 *************** *** 28,33 **** --- 28,34 ---- #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,81 **** --- 77,83 ---- xenProxyRegister(); xenDaemonRegister(); xenStoreRegister(); + xenXMRegister(); testRegister(); return(0); } Index: src/xm_internal.c =================================================================== RCS file: src/xm_internal.c diff -N src/xm_internal.c *** /dev/null 1 Jan 1970 00:00:00 -0000 --- src/xm_internal.c 15 Nov 2006 02:57:26 -0000 *************** *** 0 **** --- 1,1366 ---- + /* + * 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 */ + }; + + 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; + virRegisterDriver(&xenXMDriver); + + if ((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 cruft that clearly aren't config files*/ + /* Like 'dot' files */ + if (!strncmp(ent->d_name, ".", 1)) + continue; + /* The XenD server config file */ + if (!strncmp(ent->d_name, XEND_CONFIG_FILE, strlen(XEND_CONFIG_FILE))) + continue; + /* Random PCI config cruft */ + if (!strncmp(ent->d_name, XEND_PCI_CONFIG_PREFIX, strlen(XEND_PCI_CONFIG_PREFIX))) + continue; + /* The example domain configs */ + if (!strncmp(ent->d_name, XM_EXAMPLE_PREFIX, strlen(XM_EXAMPLE_PREFIX))) + continue; + /* The QEMUU networking script */ + if (!strncmp(ent->d_name, QEMU_IF_SCRIPT, strlen(QEMU_IF_SCRIPT))) + continue; + + /* 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(XM_CONFIG_DIR) + 1 + strlen(ent->d_name) + 1) > PATH_MAX) + continue; + strcpy(path, XM_CONFIG_DIR); + 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, "