/* * qemu_conf.c: QEMU configuration management * * Copyright (C) 2006-2012 Red Hat, Inc. * 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, see * . * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include #include #include #include "virerror.h" #include "qemu_conf.h" #include "qemu_command.h" #include "qemu_capabilities.h" #include "qemu_bridge_filter.h" #include "viruuid.h" #include "virbuffer.h" #include "virconf.h" #include "virutil.h" #include "viralloc.h" #include "datatypes.h" #include "virxml.h" #include "nodeinfo.h" #include "virlog.h" #include "cpu/cpu.h" #include "domain_nwfilter.h" #include "virfile.h" #include "virstring.h" #include "viratomic.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_QEMU static virClassPtr virQEMUDriverConfigClass; static void virQEMUDriverConfigDispose(void *obj); static int virQEMUConfigOnceInit(void) { if (!(virQEMUDriverConfigClass = virClassNew(virClassForObject(), "virQEMUDriverConfig", sizeof(virQEMUDriverConfig), virQEMUDriverConfigDispose))) return -1; return 0; } VIR_ONCE_GLOBAL_INIT(virQEMUConfig) struct _qemuDriverCloseDef { virConnectPtr conn; qemuDriverCloseCallback cb; }; void qemuDriverLock(virQEMUDriverPtr driver) { virMutexLock(&driver->lock); } void qemuDriverUnlock(virQEMUDriverPtr driver) { virMutexUnlock(&driver->lock); } virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) { virQEMUDriverConfigPtr cfg; if (virQEMUConfigInitialize() < 0) return NULL; if (!(cfg = virObjectNew(virQEMUDriverConfigClass))) return NULL; cfg->privileged = privileged; cfg->uri = privileged ? "qemu:///system" : "qemu:///session"; if (privileged) { if (virGetUserID(QEMU_USER, &cfg->user) < 0) goto error; if (virGetGroupID(QEMU_GROUP, &cfg->group) < 0) goto error; } else { cfg->user = 0; cfg->group = 0; } cfg->dynamicOwnership = privileged; cfg->cgroupControllers = (1 << VIR_CGROUP_CONTROLLER_CPU) | (1 << VIR_CGROUP_CONTROLLER_DEVICES) | (1 << VIR_CGROUP_CONTROLLER_MEMORY) | (1 << VIR_CGROUP_CONTROLLER_BLKIO) | (1 << VIR_CGROUP_CONTROLLER_CPUSET) | (1 << VIR_CGROUP_CONTROLLER_CPUACCT); if (privileged) { if (virAsprintf(&cfg->logDir, "%s/log/libvirt/qemu", LOCALSTATEDIR) < 0) goto no_memory; if ((cfg->configBaseDir = strdup(SYSCONFDIR "/libvirt")) == NULL) goto no_memory; if (virAsprintf(&cfg->stateDir, "%s/run/libvirt/qemu", LOCALSTATEDIR) < 0) goto no_memory; if (virAsprintf(&cfg->libDir, "%s/lib/libvirt/qemu", LOCALSTATEDIR) < 0) goto no_memory; if (virAsprintf(&cfg->cacheDir, "%s/cache/libvirt/qemu", LOCALSTATEDIR) < 0) goto no_memory; if (virAsprintf(&cfg->saveDir, "%s/lib/libvirt/qemu/save", LOCALSTATEDIR) < 0) goto no_memory; if (virAsprintf(&cfg->snapshotDir, "%s/lib/libvirt/qemu/snapshot", LOCALSTATEDIR) < 0) goto no_memory; if (virAsprintf(&cfg->autoDumpPath, "%s/lib/libvirt/qemu/dump", LOCALSTATEDIR) < 0) goto no_memory; } else { char *rundir; char *cachedir; cachedir = virGetUserCacheDirectory(); if (!cachedir) goto error; if (virAsprintf(&cfg->logDir, "%s/qemu/log", cachedir) < 0) { VIR_FREE(cachedir); goto no_memory; } if (virAsprintf(&cfg->cacheDir, "%s/qemu/cache", cachedir) < 0) { VIR_FREE(cachedir); goto no_memory; } VIR_FREE(cachedir); rundir = virGetUserRuntimeDirectory(); if (!rundir) goto error; if (virAsprintf(&cfg->stateDir, "%s/qemu/run", rundir) < 0) { VIR_FREE(rundir); goto no_memory; } VIR_FREE(rundir); if (!(cfg->configBaseDir = virGetUserConfigDirectory())) goto error; if (virAsprintf(&cfg->libDir, "%s/qemu/lib", cfg->configBaseDir) < 0) goto no_memory; if (virAsprintf(&cfg->saveDir, "%s/qemu/save", cfg->configBaseDir) < 0) goto no_memory; if (virAsprintf(&cfg->snapshotDir, "%s/qemu/snapshot", cfg->configBaseDir) < 0) goto no_memory; if (virAsprintf(&cfg->autoDumpPath, "%s/qemu/dump", cfg->configBaseDir) < 0) goto no_memory; } if (virAsprintf(&cfg->configDir, "%s/qemu", cfg->configBaseDir) < 0) goto no_memory; if (virAsprintf(&cfg->autostartDir, "%s/qemu/autostart", cfg->configBaseDir) < 0) goto no_memory; if (!(cfg->vncListen = strdup("127.0.0.1"))) goto no_memory; if (!(cfg->vncTLSx509certdir = strdup(SYSCONFDIR "/pki/libvirt-vnc"))) goto no_memory; if (!(cfg->spiceListen = strdup("127.0.0.1"))) goto no_memory; if (!(cfg->spiceTLSx509certdir = strdup(SYSCONFDIR "/pki/libvirt-spice"))) goto no_memory; cfg->remotePortMin = QEMU_REMOTE_PORT_MIN; cfg->remotePortMax = QEMU_REMOTE_PORT_MAX; #if defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R /* For privileged driver, try and find hugepage mount automatically. * Non-privileged driver requires admin to create a dir for the * user, chown it, and then let user configure it manually */ if (privileged && !(cfg->hugetlbfsMount = virFileFindMountPoint("hugetlbfs"))) { if (errno != ENOENT) { virReportSystemError(errno, "%s", _("unable to find hugetlbfs mountpoint")); goto error; } } #endif cfg->clearEmulatorCapabilities = true; cfg->securityDefaultConfined = true; cfg->securityRequireConfined = false; cfg->keepAliveInterval = 5; cfg->keepAliveCount = 5; cfg->seccompSandbox = -1; return cfg; no_memory: virReportOOMError(); error: virObjectUnref(cfg); return NULL; } static void virQEMUDriverConfigDispose(void *obj) { virQEMUDriverConfigPtr cfg = obj; virStringFreeList(cfg->cgroupDeviceACL); VIR_FREE(cfg->configBaseDir); VIR_FREE(cfg->configDir); VIR_FREE(cfg->autostartDir); VIR_FREE(cfg->logDir); VIR_FREE(cfg->stateDir); VIR_FREE(cfg->libDir); VIR_FREE(cfg->cacheDir); VIR_FREE(cfg->saveDir); VIR_FREE(cfg->snapshotDir); VIR_FREE(cfg->vncTLSx509certdir); VIR_FREE(cfg->vncListen); VIR_FREE(cfg->vncPassword); VIR_FREE(cfg->vncSASLdir); VIR_FREE(cfg->spiceTLSx509certdir); VIR_FREE(cfg->spiceListen); VIR_FREE(cfg->spicePassword); VIR_FREE(cfg->hugetlbfsMount); VIR_FREE(cfg->hugepagePath); VIR_FREE(cfg->saveImageFormat); VIR_FREE(cfg->dumpImageFormat); VIR_FREE(cfg->autoDumpPath); virStringFreeList(cfg->securityDriverNames); VIR_FREE(cfg->lockManagerName); } int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, const char *filename) { virConfPtr conf = NULL; virConfValuePtr p; int ret = -1; int i; /* Just check the file is readable before opening it, otherwise * libvirt emits an error. */ if (access(filename, R_OK) == -1) { VIR_INFO("Could not read qemu config file %s", filename); return 0; } if (!(conf = virConfReadFile(filename, 0))) goto cleanup; #define CHECK_TYPE(name,typ) \ if (p && p->type != (typ)) { \ virReportError(VIR_ERR_INTERNAL_ERROR, \ "%s: %s: expected type " #typ, \ filename, (name)); \ goto cleanup; \ } #define GET_VALUE_LONG(NAME, VAR) \ p = virConfGetValue(conf, NAME); \ CHECK_TYPE(NAME, VIR_CONF_LONG); \ if (p) \ VAR = p->l; #define GET_VALUE_BOOL(NAME, VAR) \ p = virConfGetValue(conf, NAME); \ CHECK_TYPE(NAME, VIR_CONF_LONG); \ if (p) \ VAR = p->l != 0; #define GET_VALUE_STR(NAME, VAR) \ p = virConfGetValue(conf, NAME); \ CHECK_TYPE(NAME, VIR_CONF_STRING); \ if (p && p->str) { \ VIR_FREE(VAR); \ if (!(VAR = strdup(p->str))) \ goto no_memory; \ } GET_VALUE_BOOL("vnc_auto_unix_socket", cfg->vncAutoUnixSocket); GET_VALUE_BOOL("vnc_tls", cfg->vncTLS); GET_VALUE_BOOL("vnc_tls_x509_verify", cfg->vncTLSx509verify); GET_VALUE_STR("vnc_tls_x509_cert_dir", cfg->vncTLSx509certdir); GET_VALUE_STR("vnc_listen", cfg->vncListen); GET_VALUE_STR("vnc_password", cfg->vncPassword); GET_VALUE_BOOL("vnc_sasl", cfg->vncSASL); GET_VALUE_STR("vnc_sasl_dir", cfg->vncSASLdir); GET_VALUE_BOOL("vnc_allow_host_audio", cfg->vncAllowHostAudio); p = virConfGetValue(conf, "security_driver"); if (p && p->type == VIR_CONF_LIST) { size_t len; virConfValuePtr pp; /* Calc length and check items */ for (len = 0, pp = p->list; pp; len++, pp = pp->next) { if (pp->type != VIR_CONF_STRING) { virReportError(VIR_ERR_CONF_SYNTAX, "%s", _("security_driver must be a list of strings")); goto cleanup; } } if (VIR_ALLOC_N(cfg->securityDriverNames, len + 1) < 0) goto no_memory; for (i = 0, pp = p->list; pp; i++, pp = pp->next) { if (!(cfg->securityDriverNames[i] = strdup(pp->str))) goto no_memory; } cfg->securityDriverNames[len] = NULL; } else { CHECK_TYPE("security_driver", VIR_CONF_STRING); if (p && p->str) { if (VIR_ALLOC_N(cfg->securityDriverNames, 2) < 0 || !(cfg->securityDriverNames[0] = strdup(p->str))) goto no_memory; cfg->securityDriverNames[1] = NULL; } } GET_VALUE_BOOL("security_default_confined", cfg->securityDefaultConfined); GET_VALUE_BOOL("security_require_confined", cfg->securityRequireConfined); GET_VALUE_BOOL("spice_tls", cfg->spiceTLS); GET_VALUE_STR("spice_tls_x509_cert_dir", cfg->spiceTLSx509certdir); GET_VALUE_STR("spice_listen", cfg->spiceListen); GET_VALUE_STR("spice_password", cfg->spicePassword); GET_VALUE_LONG("remote_display_port_min", cfg->remotePortMin); if (cfg->remotePortMin < QEMU_REMOTE_PORT_MIN) { /* if the port is too low, we can't get the display name * to tell to vnc (usually subtract 5900, e.g. localhost:1 * for port 5901) */ virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_display_port_min: port must be greater " "than or equal to %d"), filename, QEMU_REMOTE_PORT_MIN); goto cleanup; } GET_VALUE_LONG("remote_display_port_max", cfg->remotePortMax); if (cfg->remotePortMax > QEMU_REMOTE_PORT_MAX || cfg->remotePortMax < cfg->remotePortMin) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_display_port_max: port must be between " "the minimal port and %d"), filename, QEMU_REMOTE_PORT_MAX); goto cleanup; } if (cfg->remotePortMin > cfg->remotePortMax) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_display_port_min: min port must not be " "greater than max port"), filename); goto cleanup; } p = virConfGetValue(conf, "user"); CHECK_TYPE("user", VIR_CONF_STRING); if (p && p->str && virGetUserID(p->str, &cfg->user) < 0) goto cleanup; p = virConfGetValue(conf, "group"); CHECK_TYPE("group", VIR_CONF_STRING); if (p && p->str && virGetGroupID(p->str, &cfg->group) < 0) goto cleanup; GET_VALUE_BOOL("dynamic_ownership", cfg->dynamicOwnership); p = virConfGetValue(conf, "cgroup_controllers"); CHECK_TYPE("cgroup_controllers", VIR_CONF_LIST); if (p) { virConfValuePtr pp; for (i = 0, pp = p->list; pp; ++i, pp = pp->next) { int ctl; if (pp->type != VIR_CONF_STRING) { virReportError(VIR_ERR_CONF_SYNTAX, "%s", _("cgroup_controllers must be a " "list of strings")); goto cleanup; } if ((ctl = virCgroupControllerTypeFromString(pp->str)) < 0) { virReportError(VIR_ERR_CONF_SYNTAX, _("Unknown cgroup controller '%s'"), pp->str); goto cleanup; } cfg->cgroupControllers |= (1 << ctl); } } for (i = 0 ; i < VIR_CGROUP_CONTROLLER_LAST ; i++) { if (cfg->cgroupControllers & (1 << i)) { VIR_INFO("Configured cgroup controller '%s'", virCgroupControllerTypeToString(i)); } } p = virConfGetValue(conf, "cgroup_device_acl"); CHECK_TYPE("cgroup_device_acl", VIR_CONF_LIST); if (p) { int len = 0; virConfValuePtr pp; for (pp = p->list; pp; pp = pp->next) len++; if (VIR_ALLOC_N(cfg->cgroupDeviceACL, 1+len) < 0) goto no_memory; for (i = 0, pp = p->list; pp; ++i, pp = pp->next) { if (pp->type != VIR_CONF_STRING) { virReportError(VIR_ERR_CONF_SYNTAX, "%s", _("cgroup_device_acl must be a " "list of strings")); goto cleanup; } if (!(cfg->cgroupDeviceACL[i] = strdup(pp->str))) goto no_memory; } cfg->cgroupDeviceACL[i] = NULL; } GET_VALUE_STR("save_image_format", cfg->saveImageFormat); GET_VALUE_STR("dump_image_format", cfg->dumpImageFormat); GET_VALUE_STR("auto_dump_path", cfg->autoDumpPath); GET_VALUE_BOOL("auto_dump_bypass_cache", cfg->autoDumpBypassCache); GET_VALUE_BOOL("auto_start_bypass_cache", cfg->autoStartBypassCache); GET_VALUE_STR("hugetlbfs_mount", cfg->hugetlbfsMount); GET_VALUE_BOOL("mac_filter", cfg->macFilter); GET_VALUE_BOOL("relaxed_acs_check", cfg->relaxedACS); GET_VALUE_BOOL("clear_emulator_capabilities", cfg->clearEmulatorCapabilities); GET_VALUE_BOOL("allow_disk_format_probing", cfg->allowDiskFormatProbing); GET_VALUE_BOOL("set_process_name", cfg->setProcessName); GET_VALUE_LONG("max_processes", cfg->maxProcesses); GET_VALUE_LONG("max_files", cfg->maxFiles); GET_VALUE_STR("lock_manager", cfg->lockManagerName); GET_VALUE_LONG("max_queued", cfg->maxQueuedJobs); GET_VALUE_LONG("keepalive_interval", cfg->keepAliveInterval); GET_VALUE_LONG("keepalive_count", cfg->keepAliveCount); GET_VALUE_LONG("seccomp_sandbox", cfg->seccompSandbox); ret = 0; cleanup: virConfFree(conf); return ret; no_memory: virReportOOMError(); goto cleanup; } #undef GET_VALUE_BOOL #undef GET_VALUE_LONG #undef GET_VALUE_STRING virQEMUDriverConfigPtr virQEMUDriverGetConfig(virQEMUDriverPtr driver) { return virObjectRef(driver->config); } static void qemuDriverCloseCallbackFree(void *payload, const void *name ATTRIBUTE_UNUSED) { VIR_FREE(payload); } int qemuDriverCloseCallbackInit(virQEMUDriverPtr driver) { driver->closeCallbacks = virHashCreate(5, qemuDriverCloseCallbackFree); if (!driver->closeCallbacks) return -1; return 0; } void qemuDriverCloseCallbackShutdown(virQEMUDriverPtr driver) { virHashFree(driver->closeCallbacks); } int qemuDriverCloseCallbackSet(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn, qemuDriverCloseCallback cb) { char uuidstr[VIR_UUID_STRING_BUFLEN]; qemuDriverCloseDefPtr closeDef; virUUIDFormat(vm->def->uuid, uuidstr); VIR_DEBUG("vm=%s, uuid=%s, conn=%p, cb=%p", vm->def->name, uuidstr, conn, cb); closeDef = virHashLookup(driver->closeCallbacks, uuidstr); if (closeDef) { if (closeDef->conn != conn) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Close callback for domain %s already registered" " with another connection %p"), vm->def->name, closeDef->conn); return -1; } if (closeDef->cb && closeDef->cb != cb) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Another close callback is already defined for" " domain %s"), vm->def->name); return -1; } closeDef->cb = cb; } else { if (VIR_ALLOC(closeDef) < 0) { virReportOOMError(); return -1; } closeDef->conn = conn; closeDef->cb = cb; if (virHashAddEntry(driver->closeCallbacks, uuidstr, closeDef) < 0) { VIR_FREE(closeDef); return -1; } } return 0; } int qemuDriverCloseCallbackUnset(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDriverCloseCallback cb) { char uuidstr[VIR_UUID_STRING_BUFLEN]; qemuDriverCloseDefPtr closeDef; virUUIDFormat(vm->def->uuid, uuidstr); VIR_DEBUG("vm=%s, uuid=%s, cb=%p", vm->def->name, uuidstr, cb); closeDef = virHashLookup(driver->closeCallbacks, uuidstr); if (!closeDef) return -1; if (closeDef->cb && closeDef->cb != cb) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Trying to remove mismatching close callback for" " domain %s"), vm->def->name); return -1; } return virHashRemoveEntry(driver->closeCallbacks, uuidstr); } qemuDriverCloseCallback qemuDriverCloseCallbackGet(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn) { char uuidstr[VIR_UUID_STRING_BUFLEN]; qemuDriverCloseDefPtr closeDef; qemuDriverCloseCallback cb = NULL; virUUIDFormat(vm->def->uuid, uuidstr); VIR_DEBUG("vm=%s, uuid=%s, conn=%p", vm->def->name, uuidstr, conn); closeDef = virHashLookup(driver->closeCallbacks, uuidstr); if (closeDef && (!conn || closeDef->conn == conn)) cb = closeDef->cb; VIR_DEBUG("cb=%p", cb); return cb; } struct qemuDriverCloseCallbackData { virQEMUDriverPtr driver; virConnectPtr conn; }; static void qemuDriverCloseCallbackRun(void *payload, const void *name, void *opaque) { struct qemuDriverCloseCallbackData *data = opaque; qemuDriverCloseDefPtr closeDef = payload; unsigned char uuid[VIR_UUID_BUFLEN]; char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainObjPtr dom; VIR_DEBUG("conn=%p, thisconn=%p, uuid=%s, cb=%p", closeDef->conn, data->conn, (const char *)name, closeDef->cb); if (data->conn != closeDef->conn || !closeDef->cb) return; if (virUUIDParse(name, uuid) < 0) { VIR_WARN("Failed to parse %s", (const char *)name); return; } /* We need to reformat uuidstr, because closeDef->cb * might cause the current hash entry to be removed, * which means 'name' will have been free()d */ virUUIDFormat(uuid, uuidstr); if (!(dom = virDomainObjListFindByUUID(data->driver->domains, uuid))) { VIR_DEBUG("No domain object with UUID %s", uuidstr); return; } dom = closeDef->cb(data->driver, dom, data->conn); if (dom) virObjectUnlock(dom); virHashRemoveEntry(data->driver->closeCallbacks, uuidstr); } void qemuDriverCloseCallbackRunAll(virQEMUDriverPtr driver, virConnectPtr conn) { struct qemuDriverCloseCallbackData data = { driver, conn }; VIR_DEBUG("conn=%p", conn); virHashForEach(driver->closeCallbacks, qemuDriverCloseCallbackRun, &data); } /* Construct the hash key for sharedDisks as "major:minor" */ char * qemuGetSharedDiskKey(const char *disk_path) { int maj, min; char *key = NULL; int rc; if ((rc = virGetDeviceID(disk_path, &maj, &min)) < 0) { virReportSystemError(-rc, _("Unable to get minor number of device '%s'"), disk_path); return NULL; } if (virAsprintf(&key, "%d:%d", maj, min) < 0) { virReportOOMError(); return NULL; } return key; } /* Increase ref count if the entry already exists, otherwise * add a new entry. */ int qemuAddSharedDisk(virHashTablePtr sharedDisks, const char *disk_path) { size_t *ref = NULL; char *key = NULL; if (!(key = qemuGetSharedDiskKey(disk_path))) return -1; if ((ref = virHashLookup(sharedDisks, key))) { if (virHashUpdateEntry(sharedDisks, key, ++ref) < 0) { VIR_FREE(key); return -1; } } else { if (virHashAddEntry(sharedDisks, key, (void *)0x1)) { VIR_FREE(key); return -1; } } VIR_FREE(key); return 0; } /* Decrease the ref count if the entry already exists, otherwise * remove the entry. */ int qemuRemoveSharedDisk(virHashTablePtr sharedDisks, const char *disk_path) { size_t *ref = NULL; char *key = NULL; if (!(key = qemuGetSharedDiskKey(disk_path))) return -1; if (!(ref = virHashLookup(sharedDisks, key))) { VIR_FREE(key); return -1; } if (ref != (void *)0x1) { if (virHashUpdateEntry(sharedDisks, key, --ref) < 0) { VIR_FREE(key); return -1; } } else { if (virHashRemoveEntry(sharedDisks, key) < 0) { VIR_FREE(key); return -1; } } VIR_FREE(key); return 0; } int qemuDriverAllocateID(virQEMUDriverPtr driver) { return virAtomicIntInc(&driver->nextvmid); }