/* * xm_internal.h: helper routines for dealing with inactive domains * * Copyright (C) 2006-2007 Red Hat * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Daniel P. Berrange * */ #ifdef WITH_XEN #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NAME_MAX #define NAME_MAX 255 #endif #include "xen_unified.h" #include "xm_internal.h" #include "xend_internal.h" #include "conf.h" #include "hash.h" #include "internal.h" #include "xml.h" #include "buf.h" #include "uuid.h" typedef struct xenXMConfCache *xenXMConfCachePtr; typedef struct xenXMConfCache { time_t refreshedAt; char filename[PATH_MAX]; virConfPtr conf; } xenXMConfCache; static char configDir[PATH_MAX]; /* Config file name to config object */ static virHashTablePtr configCache = NULL; /* Name to config file name */ static virHashTablePtr nameConfigMap = 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" struct xenUnifiedDriver xenXMDriver = { xenXMOpen, /* open */ xenXMClose, /* close */ xenXMGetType, /* type */ NULL, /* version */ NULL, /* hostname */ NULL, /* URI */ NULL, /* nodeGetInfo */ NULL, /* getCapabilities */ NULL, /* listDomains */ NULL, /* numOfDomains */ NULL, /* domainCreateLinux */ NULL, /* domainSuspend */ NULL, /* domainResume */ NULL, /* domainShutdown */ NULL, /* domainReboot */ NULL, /* domainDestroy */ NULL, /* domainGetOSType */ xenXMDomainGetMaxMemory, /* domainGetMaxMemory */ xenXMDomainSetMaxMemory, /* domainSetMaxMemory */ xenXMDomainSetMemory, /* domainMaxMemory */ xenXMDomainGetInfo, /* domainGetInfo */ NULL, /* domainSave */ NULL, /* domainRestore */ NULL, /* domainCoreDump */ xenXMDomainSetVcpus, /* domainSetVcpus */ NULL, /* domainPinVcpu */ NULL, /* domainGetVcpus */ NULL, /* domainGetMaxVcpus */ xenXMListDefinedDomains, /* listDefinedDomains */ xenXMNumOfDefinedDomains, /* numOfDefinedDomains */ xenXMDomainCreate, /* domainCreate */ xenXMDomainDefineXML, /* domainDefineXML */ xenXMDomainUndefine, /* domainUndefine */ NULL, /* domainAttachDevice */ NULL, /* domainDetachDevice */ NULL, /* domainGetAutostart */ NULL, /* domainSetAutostart */ NULL, /* domainGetSchedulerType */ NULL, /* domainGetSchedulerParameters */ NULL, /* domainSetSchedulerParameters */ }; 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, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR, errmsg, info, NULL, 0, 0, errmsg, info); } int xenXMInit (void) { char *envConfigDir; int safeMode = 0; /* 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); } 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; 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 (virUUIDParse(val->str, uuid) < 0) return (-1); return (0); } /* Ensure that a config object has a valid UUID in it, if it doesn't then (re-)generate one */ static int xenXMConfigEnsureIdentity(virConfPtr conf, const char *filename) { unsigned char uuid[VIR_UUID_BUFLEN]; const char *name; /* Had better have a name...*/ if (xenXMConfigGetString(conf, "name", &name) < 0) { virConfValuePtr value; value = malloc(sizeof(virConfValue)); if (!value) { return (-1); } /* Set name based on filename */ value->type = VIR_CONF_STRING; value->str = strdup(filename); if (!value->str) { free(value); return (-1); } if (virConfSetValue(conf, "name", value) < 0) return (-1); } /* If there is no uuid...*/ if (xenXMConfigGetUUID(conf, "uuid", uuid) < 0) { virConfValuePtr value; char uuidstr[VIR_UUID_STRING_BUFLEN]; value = malloc(sizeof(virConfValue)); if (!value) { return (-1); } /* ... then generate one */ virUUIDGenerate(uuid); virUUIDFormat(uuid, uuidstr); 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); } /* 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) { const char *olddomname; /* We're going to pure this config file, so check if it is currently mapped as owner of a named domain. */ if (xenXMConfigGetString(entry->conf, "name", &olddomname) != -1) { char *nameowner = (char *)virHashLookup(nameConfigMap, olddomname); if (nameowner && !strcmp(nameowner, key)) { virHashRemoveEntry(nameConfigMap, olddomname, NULL); } } return (1); } return (0); } /* 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 (virConnectPtr conn) { DIR *dh; struct dirent *ent; time_t now = time(NULL); int ret = -1; if (now == ((time_t)-1)) { xenXMError (conn, VIR_ERR_SYSTEM_ERROR, strerror (errno)); 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))) { xenXMError (conn, VIR_ERR_SYSTEM_ERROR, strerror (errno)); return (-1); } while ((ent = readdir(dh))) { xenXMConfCachePtr entry; struct stat st; int newborn = 0; char path[PATH_MAX]; const char *domname = NULL; /* * 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, path))) { const char *olddomname = NULL; if (entry->refreshedAt >= st.st_mtime) { entry->refreshedAt = now; continue; } /* If we currently own the name, then release it and re-acquire it later - just in case it was renamed */ if (xenXMConfigGetString(entry->conf, "name", &olddomname) != -1) { char *nameowner = (char *)virHashLookup(nameConfigMap, olddomname); if (nameowner && !strcmp(nameowner, path)) { virHashRemoveEntry(nameConfigMap, olddomname, NULL); } } /* Clear existing config entry which needs refresh */ virConfFree(entry->conf); entry->conf = NULL; } else { /* Completely new entry */ newborn = 1; if (!(entry = malloc(sizeof(xenXMConfCache)))) { xenXMError (conn, VIR_ERR_NO_MEMORY, strerror (errno)); goto cleanup; } memcpy(entry->filename, path, PATH_MAX); } entry->refreshedAt = now; if (!(entry->conf = virConfReadFile(entry->filename)) || xenXMConfigEnsureIdentity(entry->conf, ent->d_name) < 0) { if (!newborn) { virHashRemoveEntry(configCache, path, NULL); } free(entry); continue; } /* Lookup what domain name the conf contains */ if (xenXMConfigGetString(entry->conf, "name", &domname) < 0) { if (!newborn) { virHashRemoveEntry(configCache, path, NULL); } free(entry); xenXMError (conn, VIR_ERR_INTERNAL_ERROR, "xenXMConfigCacheRefresh: name"); goto cleanup; } /* If its a completely new entry, it must be stuck into the cache (refresh'd entries are already registered) */ if (newborn) { if (virHashAddEntry(configCache, entry->filename, entry) < 0) { virConfFree(entry->conf); free(entry); xenXMError (conn, VIR_ERR_INTERNAL_ERROR, "xenXMConfigCacheRefresh: virHashAddEntry"); goto cleanup; } } /* See if we need to map this config file in as the primary owner * of the domain in question */ if (!virHashLookup(nameConfigMap, domname)) { if (virHashAddEntry(nameConfigMap, domname, entry->filename) < 0) { virHashRemoveEntry(configCache, ent->d_name, NULL); virConfFree(entry->conf); free(entry); } } } /* 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, xmlURIPtr uri ATTRIBUTE_UNUSED, int flags ATTRIBUTE_UNUSED) { if (configCache == NULL) { configCache = virHashCreate(50); if (!configCache) return (-1); nameConfigMap = virHashCreate(50); if (!nameConfigMap) { virHashFree(configCache, NULL); configCache = NULL; return (-1); } /* Force the cache to be reloaded next time that * xenXMConfigCacheRefresh is called. */ lastRefresh = 0; } nconnections++; return (0); } /* * Free the config files in the cache if this is the * last connection */ int xenXMClose(virConnectPtr conn ATTRIBUTE_UNUSED) { nconnections--; if (nconnections <= 0) { virHashFree(nameConfigMap, NULL); nameConfigMap = NULL; 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) { const char *filename; 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->id != -1) return (-1); if (!(filename = virHashLookup(nameConfigMap, domain->name))) return (-1); if (!(entry = virHashLookup(configCache, filename))) return (-1); memset(info, 0, sizeof(virDomainInfo)); if (xenXMConfigGetInt(entry->conf, "memory", &mem) < 0 || mem < 0) info->memory = MIN_XEN_GUEST_SIZE * 1024 * 2; 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); } #define MAX_VFB 1024 /* * Turn a config record into a lump of XML describing the * domain, suitable for later feeding for virDomainCreateLinux */ char *xenXMDomainFormatXML(virConnectPtr conn, virConfPtr conf) { virBufferPtr buf; char *xml; const char *name; unsigned char uuid[VIR_UUID_BUFLEN]; char uuidstr[VIR_UUID_STRING_BUFLEN]; const char *str; int hvm = 0; long val; virConfValuePtr list; int vnc = 0, sdl = 0; char vfb[MAX_VFB]; long vncdisplay; long vncunused = 1; const char *vnclisten = NULL; const char *vncpasswd = NULL; const char *keymap = NULL; xenUnifiedPrivatePtr priv = (xenUnifiedPrivatePtr) conn->privateData; if (xenXMConfigGetString(conf, "name", &name) < 0) return (NULL); if (xenXMConfigGetUUID(conf, "uuid", uuid) < 0) return (NULL); buf = virBufferNew(4096); virBufferAdd(buf, "\n", -1); virBufferVSprintf(buf, " %s\n", name); virUUIDFormat(uuid, uuidstr); virBufferVSprintf(buf, " %s\n", uuidstr); if ((xenXMConfigGetString(conf, "builder", &str) == 0) && !strcmp(str, "hvm")) hvm = 1; if (hvm) { const char *boot; virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " hvm\n", -1); if (xenXMConfigGetString(conf, "kernel", &str) == 0) virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "boot", &boot) < 0) boot = "c"; while (*boot) { const char *dev; switch (*boot) { case 'a': dev = "fd"; break; case 'd': dev = "cdrom"; break; case 'c': default: dev = "hd"; break; } virBufferVSprintf(buf, " \n", dev); boot++; } virBufferAdd(buf, " \n", -1); } else { if (xenXMConfigGetString(conf, "bootloader", &str) == 0) virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "bootargs", &str) == 0) virBufferEscapeString(buf, " %s\n", str); if (xenXMConfigGetString(conf, "kernel", &str) == 0) { virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " linux\n", -1); virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "ramdisk", &str) == 0) virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "extra", &str) == 0) virBufferEscapeString(buf, " %s\n", str); virBufferAdd(buf, " \n", -1); } } if (xenXMConfigGetInt(conf, "memory", &val) < 0) val = MIN_XEN_GUEST_SIZE * 2; virBufferVSprintf(buf, " %ld\n", val * 1024); if (xenXMConfigGetInt(conf, "maxmem", &val) < 0) if (xenXMConfigGetInt(conf, "memory", &val) < 0) val = MIN_XEN_GUEST_SIZE * 2; virBufferVSprintf(buf, " %ld\n", val * 1024); virBufferVSprintf(buf, " %ld\n", val); if (xenXMConfigGetString(conf, "on_poweroff", &str) < 0) str = "destroy"; virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "on_reboot", &str) < 0) str = "restart"; virBufferVSprintf(buf, " %s\n", str); if (xenXMConfigGetString(conf, "on_crash", &str) < 0) str = "restart"; virBufferVSprintf(buf, " %s\n", str); if (hvm) { virBufferAdd(buf, " \n", -1); if (xenXMConfigGetInt(conf, "pae", &val) == 0 && val) virBufferAdd(buf, " \n", -1); if (xenXMConfigGetInt(conf, "acpi", &val) == 0 && val) virBufferAdd(buf, " \n", -1); if (xenXMConfigGetInt(conf, "apic", &val) == 0 && val) virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " \n", -1); if (xenXMConfigGetInt(conf, "localtime", &val) < 0) val = 0; virBufferVSprintf(buf, " \n", val ? "localtime" : "utc"); } virBufferAdd(buf, " \n", -1); if (hvm) { if (xenXMConfigGetString(conf, "device_model", &str) == 0) virBufferVSprintf(buf, " %s\n", str); } list = virConfGetValue(conf, "disk"); if (list && list->type == VIR_CONF_LIST) { list = list->list; while (list) { int block = 0; int cdrom = 0; char src[PATH_MAX]; char dev[NAME_MAX]; char drvName[NAME_MAX] = ""; char drvType[NAME_MAX] = ""; char *head; char *offset; char *tmp, *tmp1; if ((list->type != VIR_CONF_STRING) || (list->str == NULL)) goto skipdisk; head = list->str; /* * Disks have 3 components, SOURCE,DEST-DEVICE,MODE * eg, phy:/dev/HostVG/XenGuest1,xvda,w * The SOURCE is usually prefixed with a driver type, * and optionally driver sub-type * The DEST-DEVICE is optionally post-fixed with disk type */ /* Extract the source */ if (!(offset = strchr(head, ',')) || offset[0] == '\0') goto skipdisk; if ((offset - head) >= (PATH_MAX-1)) goto skipdisk; strncpy(src, head, (offset - head)); src[(offset-head)] = '\0'; head = offset + 1; /* Extract the dest */ if (!(offset = strchr(head, ',')) || offset[0] == '\0') goto skipdisk; if ((offset - head) >= (PATH_MAX-1)) goto skipdisk; strncpy(dev, head, (offset - head)); dev[(offset-head)] = '\0'; head = offset + 1; /* Extract source driver type */ if (!src[0]) { strcpy(drvName, "phy"); tmp = &src[0]; } else if (!(tmp = strchr(src, ':')) || !tmp[0]) { goto skipdisk; } else { strncpy(drvName, src, (tmp-src)); drvName[tmp-src] = '\0'; } /* And the source driver sub-type */ if (!strncmp(drvName, "tap", 3)) { if (!(tmp1 = strchr(tmp+1, ':')) || !tmp1[0]) goto skipdisk; strncpy(drvType, tmp+1, (tmp1-(tmp+1))); memmove(src, src+(tmp1-src)+1, strlen(src)-(tmp1-src)); } else { drvType[0] = '\0'; if (src[0]) memmove(src, src+(tmp-src)+1, strlen(src)-(tmp-src)); } /* phy: type indicates a block device */ if (!strcmp(drvName, "phy")) { block = 1; } /* Remove legacy ioemu: junk */ if (!strncmp(dev, "ioemu:", 6)) { memmove(dev, dev+6, strlen(dev)-5); } /* Check for a :cdrom/:disk postfix */ if ((tmp = strchr(dev, ':')) != NULL) { if (!strcmp(tmp, ":cdrom")) cdrom = 1; tmp[0] = '\0'; } virBufferVSprintf(buf, " \n", block ? "block" : "file", cdrom ? "cdrom" : "disk"); if (drvType[0]) virBufferVSprintf(buf, " \n", drvName, drvType); else virBufferVSprintf(buf, " \n", drvName); if (src[0]) virBufferVSprintf(buf, " \n", block ? "dev" : "file", src); virBufferVSprintf(buf, " \n", dev); if (!strcmp(head, "r") || !strcmp(head, "ro")) virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " \n", -1); skipdisk: list = list->next; } } if (hvm && priv->xendConfigVersion == 1) { if (xenXMConfigGetString(conf, "cdrom", &str) == 0) { virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " \n", -1); virBufferVSprintf(buf, " \n", str); virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " \n", -1); virBufferAdd(buf, " \n", -1); } } list = virConfGetValue(conf, "vif"); if (list && list->type == VIR_CONF_LIST) { list = list->list; while (list) { int type = -1; char script[PATH_MAX]; char ip[16]; char mac[18]; char bridge[50]; char *key; bridge[0] = '\0'; mac[0] = '\0'; script[0] = '\0'; ip[0] = '\0'; if ((list->type != VIR_CONF_STRING) || (list->str == NULL)) goto skipnic; key = list->str; while (key) { char *data; char *nextkey = strchr(key, ','); if (!(data = strchr(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)) { int len = nextkey ? (nextkey - data) : sizeof(bridge)-1; type = 1; if (len > (sizeof(bridge)-1)) len = sizeof(bridge)-1; strncpy(bridge, data, len); bridge[len] = '\0'; } 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 (type == 1 && bridge[0]) virBufferVSprintf(buf, " \n", bridge); if (script[0]) virBufferVSprintf(buf, "