/* * 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, "