/* * qemu_conf.c: QEMU configuration management * * Copyright (C) 2006-2013 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 "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 "storage_conf.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_QEMU static virClassPtr virQEMUDriverConfigClass; static void virQEMUDriverConfigDispose(void *obj); static int virQEMUConfigOnceInit(void) { virQEMUDriverConfigClass = virClassNew(virClassForObject(), "virQEMUDriverConfig", sizeof(virQEMUDriverConfig), virQEMUDriverConfigDispose); if (!virQEMUDriverConfigClass) return -1; else return 0; } VIR_ONCE_GLOBAL_INIT(virQEMUConfig) static void qemuDriverLock(virQEMUDriverPtr driver) { virMutexLock(&driver->lock); } static void qemuDriverUnlock(virQEMUDriverPtr driver) { virMutexUnlock(&driver->lock); } void qemuDomainCmdlineDefFree(qemuDomainCmdlineDefPtr def) { size_t i; if (!def) return; for (i = 0; i < def->num_args; i++) VIR_FREE(def->args[i]); for (i = 0; i < def->num_env; i++) { VIR_FREE(def->env_name[i]); VIR_FREE(def->env_value[i]); } VIR_FREE(def->args); VIR_FREE(def->env_name); VIR_FREE(def->env_value); VIR_FREE(def); } 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 = (uid_t)-1; cfg->group = (gid_t)-1; } cfg->dynamicOwnership = privileged; cfg->cgroupControllers = -1; /* -1 == auto-detect */ if (privileged) { if (virAsprintf(&cfg->logDir, "%s/log/libvirt/qemu", LOCALSTATEDIR) < 0) goto error; if (VIR_STRDUP(cfg->configBaseDir, SYSCONFDIR "/libvirt") < 0) goto error; if (virAsprintf(&cfg->stateDir, "%s/run/libvirt/qemu", LOCALSTATEDIR) < 0) goto error; if (virAsprintf(&cfg->libDir, "%s/lib/libvirt/qemu", LOCALSTATEDIR) < 0) goto error; if (virAsprintf(&cfg->cacheDir, "%s/cache/libvirt/qemu", LOCALSTATEDIR) < 0) goto error; if (virAsprintf(&cfg->saveDir, "%s/lib/libvirt/qemu/save", LOCALSTATEDIR) < 0) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/lib/libvirt/qemu/snapshot", LOCALSTATEDIR) < 0) goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/lib/libvirt/qemu/dump", LOCALSTATEDIR) < 0) goto error; } else { char *rundir; char *cachedir; cachedir = virGetUserCacheDirectory(); if (!cachedir) goto error; if (virAsprintf(&cfg->logDir, "%s/qemu/log", cachedir) < 0) { VIR_FREE(cachedir); goto error; } if (virAsprintf(&cfg->cacheDir, "%s/qemu/cache", cachedir) < 0) { VIR_FREE(cachedir); goto error; } VIR_FREE(cachedir); rundir = virGetUserRuntimeDirectory(); if (!rundir) goto error; if (virAsprintf(&cfg->stateDir, "%s/qemu/run", rundir) < 0) { VIR_FREE(rundir); goto error; } VIR_FREE(rundir); if (!(cfg->configBaseDir = virGetUserConfigDirectory())) goto error; if (virAsprintf(&cfg->libDir, "%s/qemu/lib", cfg->configBaseDir) < 0) goto error; if (virAsprintf(&cfg->saveDir, "%s/qemu/save", cfg->configBaseDir) < 0) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/qemu/snapshot", cfg->configBaseDir) < 0) goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/qemu/dump", cfg->configBaseDir) < 0) goto error; } if (virAsprintf(&cfg->configDir, "%s/qemu", cfg->configBaseDir) < 0) goto error; if (virAsprintf(&cfg->autostartDir, "%s/qemu/autostart", cfg->configBaseDir) < 0) goto error; if (VIR_STRDUP(cfg->vncListen, "127.0.0.1") < 0) goto error; if (VIR_STRDUP(cfg->vncTLSx509certdir, SYSCONFDIR "/pki/libvirt-vnc") < 0) goto error; if (VIR_STRDUP(cfg->spiceListen, "127.0.0.1") < 0) goto error; if (VIR_STRDUP(cfg->spiceTLSx509certdir, SYSCONFDIR "/pki/libvirt-spice") < 0) goto error; cfg->remotePortMin = QEMU_REMOTE_PORT_MIN; cfg->remotePortMax = QEMU_REMOTE_PORT_MAX; cfg->webSocketPortMin = QEMU_WEBSOCKET_PORT_MIN; cfg->webSocketPortMax = QEMU_WEBSOCKET_PORT_MAX; cfg->migrationPortMin = QEMU_MIGRATION_PORT_MIN; cfg->migrationPortMax = QEMU_MIGRATION_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 if (VIR_STRDUP(cfg->bridgeHelperName, "/usr/libexec/qemu-bridge-helper") < 0) goto error; cfg->clearEmulatorCapabilities = true; cfg->securityDefaultConfined = true; cfg->securityRequireConfined = false; cfg->keepAliveInterval = 5; cfg->keepAliveCount = 5; cfg->seccompSandbox = -1; return cfg; 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->spiceSASLdir); VIR_FREE(cfg->hugetlbfsMount); VIR_FREE(cfg->hugepagePath); VIR_FREE(cfg->bridgeHelperName); 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; size_t 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 (VIR_STRDUP(VAR, p->str) < 0) \ goto cleanup; \ } 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); GET_VALUE_BOOL("nographics_allow_host_audio", cfg->nogfxAllowHostAudio); 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 cleanup; for (i = 0, pp = p->list; pp; i++, pp = pp->next) { if (VIR_STRDUP(cfg->securityDriverNames[i], pp->str) < 0) goto cleanup; } cfg->securityDriverNames[len] = NULL; } else { CHECK_TYPE("security_driver", VIR_CONF_STRING); if (p && p->str) { if (VIR_ALLOC_N(cfg->securityDriverNames, 2) < 0) goto cleanup; if (VIR_STRDUP(cfg->securityDriverNames[0], p->str) < 0) goto cleanup; 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_BOOL("spice_sasl", cfg->spiceSASL); GET_VALUE_STR("spice_sasl_dir", cfg->spiceSASLdir); GET_VALUE_STR("spice_listen", cfg->spiceListen); GET_VALUE_STR("spice_password", cfg->spicePassword); GET_VALUE_LONG("remote_websocket_port_min", cfg->webSocketPortMin); if (cfg->webSocketPortMin < QEMU_WEBSOCKET_PORT_MIN) { /* if the port is too low, we can't get the display name * to tell to vnc (usually subtract 5700, e.g. localhost:1 * for port 5701) */ virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_websocket_port_min: port must be greater " "than or equal to %d"), filename, QEMU_WEBSOCKET_PORT_MIN); goto cleanup; } GET_VALUE_LONG("remote_websocket_port_max", cfg->webSocketPortMax); if (cfg->webSocketPortMax > QEMU_WEBSOCKET_PORT_MAX || cfg->webSocketPortMax < cfg->webSocketPortMin) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_websocket_port_max: port must be between " "the minimal port and %d"), filename, QEMU_WEBSOCKET_PORT_MAX); goto cleanup; } if (cfg->webSocketPortMin > cfg->webSocketPortMax) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: remote_websocket_port_min: min port must not be " "greater than max port"), filename); goto cleanup; } 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; } GET_VALUE_LONG("migration_port_min", cfg->migrationPortMin); if (cfg->migrationPortMin <= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: migration_port_min: port must be greater than 0"), filename); goto cleanup; } GET_VALUE_LONG("migration_port_max", cfg->migrationPortMax); if (cfg->migrationPortMax > 65535 || cfg->migrationPortMax < cfg->migrationPortMin) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: migration_port_max: port must be between " "the minimal port %d and 65535"), filename, cfg->migrationPortMin); 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) { cfg->cgroupControllers = 0; 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); } } 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 cleanup; 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 (VIR_STRDUP(cfg->cgroupDeviceACL[i], pp->str) < 0) goto cleanup; } cfg->cgroupDeviceACL[i] = NULL; } GET_VALUE_STR("save_image_format", cfg->saveImageFormat); GET_VALUE_STR("dump_image_format", cfg->dumpImageFormat); GET_VALUE_STR("snapshot_image_format", cfg->snapshotImageFormat); 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_STR("bridge_helper", cfg->bridgeHelperName); 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); GET_VALUE_STR("migration_address", cfg->migrationAddress); ret = 0; cleanup: virConfFree(conf); return ret; } #undef GET_VALUE_BOOL #undef GET_VALUE_LONG #undef GET_VALUE_STRING virQEMUDriverConfigPtr virQEMUDriverGetConfig(virQEMUDriverPtr driver) { virQEMUDriverConfigPtr conf; qemuDriverLock(driver); conf = virObjectRef(driver->config); qemuDriverUnlock(driver); return conf; } virDomainXMLOptionPtr virQEMUDriverCreateXMLConf(virQEMUDriverPtr driver) { virQEMUDriverDomainDefParserConfig.priv = driver; return virDomainXMLOptionNew(&virQEMUDriverDomainDefParserConfig, &virQEMUDriverPrivateDataCallbacks, &virQEMUDriverDomainXMLNamespace); } virCapsPtr virQEMUDriverCreateCapabilities(virQEMUDriverPtr driver) { size_t i, j; virCapsPtr caps; virSecurityManagerPtr *sec_managers = NULL; /* Security driver data */ const char *doi, *model, *lbl, *type; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); const int virtTypes[] = {VIR_DOMAIN_VIRT_KVM, VIR_DOMAIN_VIRT_QEMU,}; /* Basic host arch / guest machine capabilities */ if (!(caps = virQEMUCapsInit(driver->qemuCapsCache))) goto error; if (virGetHostUUID(caps->host.host_uuid)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot get the host uuid")); goto error; } /* access sec drivers and create a sec model for each one */ if (!(sec_managers = virSecurityManagerGetNested(driver->securityManager))) goto error; /* calculate length */ for (i = 0; sec_managers[i]; i++) ; caps->host.nsecModels = i; if (VIR_ALLOC_N(caps->host.secModels, caps->host.nsecModels) < 0) goto error; for (i = 0; sec_managers[i]; i++) { virCapsHostSecModelPtr sm = &caps->host.secModels[i]; doi = virSecurityManagerGetDOI(sec_managers[i]); model = virSecurityManagerGetModel(sec_managers[i]); if (VIR_STRDUP(sm->model, model) < 0 || VIR_STRDUP(sm->doi, doi) < 0) goto error; for (j = 0; j < ARRAY_CARDINALITY(virtTypes); j++) { lbl = virSecurityManagerGetBaseLabel(sec_managers[i], virtTypes[j]); type = virDomainVirtTypeToString(virtTypes[j]); if (lbl && virCapabilitiesHostSecModelAddBaseLabel(sm, type, lbl) < 0) goto error; } VIR_DEBUG("Initialized caps for security driver \"%s\" with " "DOI \"%s\"", model, doi); } VIR_FREE(sec_managers); virObjectUnref(cfg); return caps; error: VIR_FREE(sec_managers); virObjectUnref(caps); virObjectUnref(cfg); return NULL; } /** * virQEMUDriverGetCapabilities: * * Get a reference to the virCapsPtr instance for the * driver. If @refresh is true, the capabilities will be * rebuilt first * * The caller must release the reference with virObjetUnref * * Returns: a reference to a virCapsPtr instance or NULL */ virCapsPtr virQEMUDriverGetCapabilities(virQEMUDriverPtr driver, bool refresh) { virCapsPtr ret = NULL; if (refresh) { virCapsPtr caps = NULL; if ((caps = virQEMUDriverCreateCapabilities(driver)) == NULL) return NULL; qemuDriverLock(driver); virObjectUnref(driver->caps); driver->caps = caps; } else { qemuDriverLock(driver); } ret = virObjectRef(driver->caps); qemuDriverUnlock(driver); return ret; } struct _qemuSharedDeviceEntry { size_t ref; char **domains; /* array of domain names */ }; /* Construct the hash key for sharedDevices as "major:minor" */ char * qemuGetSharedDeviceKey(const char *device_path) { int maj, min; char *key = NULL; int rc; if ((rc = virGetDeviceID(device_path, &maj, &min)) < 0) { virReportSystemError(-rc, _("Unable to get minor number of device '%s'"), device_path); return NULL; } if (virAsprintf(&key, "%d:%d", maj, min) < 0) return NULL; return key; } /* Check if a shared device's setting conflicts with the conf * used by other domain(s). Currently only checks the sgio * setting. Note that this should only be called for disk with * block source if the device type is disk. * * Returns 0 if no conflicts, otherwise returns -1. */ static int qemuCheckSharedDevice(virHashTablePtr sharedDevices, virDomainDeviceDefPtr dev) { virDomainDiskDefPtr disk = NULL; virDomainHostdevDefPtr hostdev = NULL; char *sysfs_path = NULL; char *key = NULL; char *hostdev_name = NULL; char *hostdev_path = NULL; char *device_path = NULL; int val; int ret = 0; if (dev->type == VIR_DOMAIN_DEVICE_DISK) { disk = dev->data.disk; /* The only conflicts between shared disk we care about now * is sgio setting, which is only valid for device='lun'. */ if (disk->device != VIR_DOMAIN_DISK_DEVICE_LUN) return 0; device_path = disk->src; } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { hostdev = dev->data.hostdev; if (!(hostdev_name = virSCSIDeviceGetDevName(hostdev->source.subsys.u.scsi.adapter, hostdev->source.subsys.u.scsi.bus, hostdev->source.subsys.u.scsi.target, hostdev->source.subsys.u.scsi.unit))) goto cleanup; if (virAsprintf(&hostdev_path, "/dev/%s", hostdev_name) < 0) goto cleanup; device_path = hostdev_path; } else { return 0; } if (!(sysfs_path = virGetUnprivSGIOSysfsPath(device_path, NULL))) { ret = -1; goto cleanup; } /* It can't be conflict if unpriv_sgio is not supported * by kernel. */ if (!virFileExists(sysfs_path)) goto cleanup; if (!(key = qemuGetSharedDeviceKey(device_path))) { ret = -1; goto cleanup; } /* It can't be conflict if no other domain is * is sharing it. */ if (!(virHashLookup(sharedDevices, key))) goto cleanup; if (virGetDeviceUnprivSGIO(device_path, NULL, &val) < 0) { ret = -1; goto cleanup; } if ((val == 0 && (disk->sgio == VIR_DOMAIN_DEVICE_SGIO_FILTERED || disk->sgio == VIR_DOMAIN_DEVICE_SGIO_DEFAULT)) || (val == 1 && disk->sgio == VIR_DOMAIN_DEVICE_SGIO_UNFILTERED)) goto cleanup; if (dev->type == VIR_DOMAIN_DEVICE_DISK) { if (disk->type == VIR_DOMAIN_DISK_TYPE_VOLUME) { virReportError(VIR_ERR_OPERATION_INVALID, _("sgio of shared disk 'pool=%s' 'volume=%s' conflicts " "with other active domains"), disk->srcpool->pool, disk->srcpool->volume); } else { virReportError(VIR_ERR_OPERATION_INVALID, _("sgio of shared disk '%s' conflicts with other " "active domains"), disk->src); } } else { virReportError(VIR_ERR_OPERATION_INVALID, _("sgio of shared scsi host device '%s-%d-%d-%d' conflicts " "with other active domains"), hostdev->source.subsys.u.scsi.adapter, hostdev->source.subsys.u.scsi.bus, hostdev->source.subsys.u.scsi.target, hostdev->source.subsys.u.scsi.unit); } ret = -1; cleanup: VIR_FREE(hostdev_name); VIR_FREE(hostdev_path); VIR_FREE(sysfs_path); VIR_FREE(key); return ret; } bool qemuSharedDeviceEntryDomainExists(qemuSharedDeviceEntryPtr entry, const char *name, int *idx) { size_t i; for (i = 0; i < entry->ref; i++) { if (STREQ(entry->domains[i], name)) { if (idx) *idx = i; return true; } } return false; } void qemuSharedDeviceEntryFree(void *payload, const void *name ATTRIBUTE_UNUSED) { qemuSharedDeviceEntryPtr entry = payload; size_t i; if (!entry) return; for (i = 0; i < entry->ref; i++) { VIR_FREE(entry->domains[i]); } VIR_FREE(entry->domains); VIR_FREE(entry); } static qemuSharedDeviceEntryPtr qemuSharedDeviceEntryCopy(const qemuSharedDeviceEntry *entry) { qemuSharedDeviceEntryPtr ret = NULL; size_t i; if (VIR_ALLOC(ret) < 0) return NULL; if (VIR_ALLOC_N(ret->domains, entry->ref) < 0) goto cleanup; for (i = 0; i < entry->ref; i++) { if (VIR_STRDUP(ret->domains[i], entry->domains[i]) < 0) goto cleanup; ret->ref++; } return ret; cleanup: qemuSharedDeviceEntryFree(ret, NULL); return NULL; } /* qemuAddSharedDevice: * @driver: Pointer to qemu driver struct * @dev: The device def * @name: The domain name * * Increase ref count and add the domain name into the list which * records all the domains that use the shared device if the entry * already exists, otherwise add a new entry. */ int qemuAddSharedDevice(virQEMUDriverPtr driver, virDomainDeviceDefPtr dev, const char *name) { qemuSharedDeviceEntry *entry = NULL; qemuSharedDeviceEntry *new_entry = NULL; virDomainDiskDefPtr disk = NULL; virDomainHostdevDefPtr hostdev = NULL; char *dev_name = NULL; char *dev_path = NULL; char *key = NULL; int ret = -1; /* Currently the only conflicts we have to care about for * the shared disk and shared host device is "sgio" setting, * which is only valid for block disk and scsi host device. */ if (dev->type == VIR_DOMAIN_DEVICE_DISK) { disk = dev->data.disk; if (!disk->shared || !virDomainDiskSourceIsBlockType(disk)) return 0; } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { hostdev = dev->data.hostdev; if (!hostdev->shareable || !(hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI)) return 0; } else { return 0; } qemuDriverLock(driver); if (qemuCheckSharedDevice(driver->sharedDevices, dev) < 0) goto cleanup; if (dev->type == VIR_DOMAIN_DEVICE_DISK) { if (!(key = qemuGetSharedDeviceKey(disk->src))) goto cleanup; } else { if (!(dev_name = virSCSIDeviceGetDevName(hostdev->source.subsys.u.scsi.adapter, hostdev->source.subsys.u.scsi.bus, hostdev->source.subsys.u.scsi.target, hostdev->source.subsys.u.scsi.unit))) goto cleanup; if (virAsprintf(&dev_path, "/dev/%s", dev_name) < 0) goto cleanup; if (!(key = qemuGetSharedDeviceKey(dev_path))) goto cleanup; } if ((entry = virHashLookup(driver->sharedDevices, key))) { /* Nothing to do if the shared scsi host device is already * recorded in the table. */ if (qemuSharedDeviceEntryDomainExists(entry, name, NULL)) { ret = 0; goto cleanup; } if (!(new_entry = qemuSharedDeviceEntryCopy(entry))) goto cleanup; if (VIR_EXPAND_N(new_entry->domains, new_entry->ref, 1) < 0 || VIR_STRDUP(new_entry->domains[new_entry->ref - 1], name) < 0) { qemuSharedDeviceEntryFree(new_entry, NULL); goto cleanup; } if (virHashUpdateEntry(driver->sharedDevices, key, new_entry) < 0) { qemuSharedDeviceEntryFree(new_entry, NULL); goto cleanup; } } else { if (VIR_ALLOC(entry) < 0 || VIR_ALLOC_N(entry->domains, 1) < 0 || VIR_STRDUP(entry->domains[0], name) < 0) { qemuSharedDeviceEntryFree(entry, NULL); goto cleanup; } entry->ref = 1; if (virHashAddEntry(driver->sharedDevices, key, entry)) goto cleanup; } ret = 0; cleanup: qemuDriverUnlock(driver); VIR_FREE(dev_name); VIR_FREE(dev_path); VIR_FREE(key); return ret; } /* qemuRemoveSharedDevice: * @driver: Pointer to qemu driver struct * @device: The device def * @name: The domain name * * Decrease ref count and remove the domain name from the list which * records all the domains that use the shared device if ref is not * 1, otherwise remove the entry. */ int qemuRemoveSharedDevice(virQEMUDriverPtr driver, virDomainDeviceDefPtr dev, const char *name) { qemuSharedDeviceEntryPtr entry = NULL; qemuSharedDeviceEntryPtr new_entry = NULL; virDomainDiskDefPtr disk = NULL; virDomainHostdevDefPtr hostdev = NULL; char *key = NULL; char *dev_name = NULL; char *dev_path = NULL; int ret = -1; int idx; if (dev->type == VIR_DOMAIN_DEVICE_DISK) { disk = dev->data.disk; if (!disk->shared || !virDomainDiskSourceIsBlockType(disk)) return 0; } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { hostdev = dev->data.hostdev; if (!hostdev->shareable || !(hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI)) return 0; } else { return 0; } qemuDriverLock(driver); if (dev->type == VIR_DOMAIN_DEVICE_DISK) { if (!(key = qemuGetSharedDeviceKey(disk->src))) goto cleanup; } else { if (!(dev_name = virSCSIDeviceGetDevName(hostdev->source.subsys.u.scsi.adapter, hostdev->source.subsys.u.scsi.bus, hostdev->source.subsys.u.scsi.target, hostdev->source.subsys.u.scsi.unit))) goto cleanup; if (virAsprintf(&dev_path, "/dev/%s", dev_name) < 0) goto cleanup; if (!(key = qemuGetSharedDeviceKey(dev_path))) goto cleanup; } if (!(entry = virHashLookup(driver->sharedDevices, key))) goto cleanup; /* Nothing to do if the shared disk is not recored in * the table. */ if (!qemuSharedDeviceEntryDomainExists(entry, name, &idx)) { ret = 0; goto cleanup; } if (entry->ref != 1) { if (!(new_entry = qemuSharedDeviceEntryCopy(entry))) goto cleanup; if (idx != new_entry->ref - 1) memmove(&new_entry->domains[idx], &new_entry->domains[idx + 1], sizeof(*new_entry->domains) * (new_entry->ref - idx - 1)); VIR_SHRINK_N(new_entry->domains, new_entry->ref, 1); if (virHashUpdateEntry(driver->sharedDevices, key, new_entry) < 0){ qemuSharedDeviceEntryFree(new_entry, NULL); goto cleanup; } } else { if (virHashRemoveEntry(driver->sharedDevices, key) < 0) goto cleanup; } ret = 0; cleanup: qemuDriverUnlock(driver); VIR_FREE(dev_name); VIR_FREE(dev_path); VIR_FREE(key); return ret; } int qemuSetUnprivSGIO(virDomainDeviceDefPtr dev) { virDomainDiskDefPtr disk = NULL; virDomainHostdevDefPtr hostdev = NULL; char *sysfs_path = NULL; char *path = NULL; char *hostdev_name = NULL; char *hostdev_path = NULL; int val = -1; int ret = 0; /* "sgio" is only valid for block disk; cdrom * and floopy disk can have empty source. */ if (dev->type == VIR_DOMAIN_DEVICE_DISK) { disk = dev->data.disk; if (disk->device != VIR_DOMAIN_DISK_DEVICE_LUN || !virDomainDiskSourceIsBlockType(disk)) return 0; path = disk->src; } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV) { hostdev = dev->data.hostdev; if (!hostdev->shareable || !(hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI)) return 0; if (!(hostdev_name = virSCSIDeviceGetDevName(hostdev->source.subsys.u.scsi.adapter, hostdev->source.subsys.u.scsi.bus, hostdev->source.subsys.u.scsi.target, hostdev->source.subsys.u.scsi.unit))) goto cleanup; if (virAsprintf(&hostdev_path, "/dev/%s", hostdev_name) < 0) goto cleanup; path = hostdev_path; } else { return 0; } sysfs_path = virGetUnprivSGIOSysfsPath(path, NULL); if (sysfs_path == NULL) { ret = -1; goto cleanup; } /* By default, filter the SG_IO commands, i.e. set unpriv_sgio to 0. */ if (dev->type == VIR_DOMAIN_DEVICE_DISK) val = (disk->sgio == VIR_DOMAIN_DEVICE_SGIO_UNFILTERED); else val = (hostdev->source.subsys.u.scsi.sgio == VIR_DOMAIN_DEVICE_SGIO_UNFILTERED); /* Do not do anything if unpriv_sgio is not supported by the kernel and the * whitelist is enabled. But if requesting unfiltered access, always call * virSetDeviceUnprivSGIO, to report an error for unsupported unpriv_sgio. */ if ((virFileExists(sysfs_path) || val == 1) && virSetDeviceUnprivSGIO(path, NULL, val) < 0) ret = -1; cleanup: VIR_FREE(sysfs_path); VIR_FREE(hostdev_name); VIR_FREE(hostdev_path); return ret; } int qemuDriverAllocateID(virQEMUDriverPtr driver) { return virAtomicIntInc(&driver->nextvmid); } static int qemuAddISCSIPoolSourceHost(virDomainDiskDefPtr def, virStoragePoolDefPtr pooldef) { int ret = -1; char **tokens = NULL; /* Only support one host */ if (pooldef->source.nhost != 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Expected exactly 1 host for the storage pool")); goto cleanup; } /* iscsi pool only supports one host */ def->nhosts = 1; if (VIR_ALLOC_N(def->hosts, def->nhosts) < 0) goto cleanup; if (VIR_STRDUP(def->hosts[0].name, pooldef->source.hosts[0].name) < 0) goto cleanup; if (virAsprintf(&def->hosts[0].port, "%d", pooldef->source.hosts[0].port ? pooldef->source.hosts[0].port : 3260) < 0) goto cleanup; /* iscsi volume has name like "unit:0:0:1" */ if (!(tokens = virStringSplit(def->srcpool->volume, ":", 0))) goto cleanup; if (virStringListLength(tokens) != 4) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected iscsi volume name '%s'"), def->srcpool->volume); goto cleanup; } /* iscsi pool has only one source device path */ if (virAsprintf(&def->src, "%s/%s", pooldef->source.devices[0].path, tokens[3]) < 0) goto cleanup; /* Storage pool have not supported these 2 attributes yet, * use the defaults. */ def->hosts[0].transport = VIR_DOMAIN_DISK_PROTO_TRANS_TCP; def->hosts[0].socket = NULL; def->protocol = VIR_DOMAIN_DISK_PROTOCOL_ISCSI; ret = 0; cleanup: virStringFreeList(tokens); return ret; } static int qemuTranslateDiskSourcePoolAuth(virDomainDiskDefPtr def, virStoragePoolDefPtr pooldef) { int ret = -1; /* Only necessary when authentication set */ if (pooldef->source.authType == VIR_STORAGE_POOL_AUTH_NONE) { ret = 0; goto cleanup; } /* Copy the authentication information from the storage pool * into the virDomainDiskDef */ if (pooldef->source.authType == VIR_STORAGE_POOL_AUTH_CHAP) { if (VIR_STRDUP(def->auth.username, pooldef->source.auth.chap.username) < 0) goto cleanup; if (pooldef->source.auth.chap.secret.uuidUsable) { def->auth.secretType = VIR_DOMAIN_DISK_SECRET_TYPE_UUID; memcpy(def->auth.secret.uuid, pooldef->source.auth.chap.secret.uuid, VIR_UUID_BUFLEN); } else { if (VIR_STRDUP(def->auth.secret.usage, pooldef->source.auth.chap.secret.usage) < 0) goto cleanup; def->auth.secretType = VIR_DOMAIN_DISK_SECRET_TYPE_USAGE; } } else if (pooldef->source.authType == VIR_STORAGE_POOL_AUTH_CEPHX) { if (VIR_STRDUP(def->auth.username, pooldef->source.auth.cephx.username) < 0) goto cleanup; if (pooldef->source.auth.cephx.secret.uuidUsable) { def->auth.secretType = VIR_DOMAIN_DISK_SECRET_TYPE_UUID; memcpy(def->auth.secret.uuid, pooldef->source.auth.cephx.secret.uuid, VIR_UUID_BUFLEN); } else { if (VIR_STRDUP(def->auth.secret.usage, pooldef->source.auth.cephx.secret.usage) < 0) goto cleanup; def->auth.secretType = VIR_DOMAIN_DISK_SECRET_TYPE_USAGE; } } ret = 0; cleanup: return ret; } int qemuDiskGetActualType(virDomainDiskDefPtr def) { if (def->type == VIR_DOMAIN_DISK_TYPE_VOLUME) return def->srcpool->actualtype; return def->type; } int qemuTranslateDiskSourcePool(virConnectPtr conn, virDomainDiskDefPtr def) { virStoragePoolDefPtr pooldef = NULL; virStoragePoolPtr pool = NULL; virStorageVolPtr vol = NULL; char *poolxml = NULL; virStorageVolInfo info; int ret = -1; virErrorPtr savedError = NULL; if (def->type != VIR_DOMAIN_DISK_TYPE_VOLUME) return 0; if (!def->srcpool) return 0; if (!(pool = virStoragePoolLookupByName(conn, def->srcpool->pool))) return -1; if (virStoragePoolIsActive(pool) != 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("storage pool '%s' containing volume '%s' " "is not active"), def->srcpool->pool, def->srcpool->volume); goto cleanup; } if (!(vol = virStorageVolLookupByName(pool, def->srcpool->volume))) goto cleanup; if (virStorageVolGetInfo(vol, &info) < 0) goto cleanup; if (!(poolxml = virStoragePoolGetXMLDesc(pool, 0))) goto cleanup; if (!(pooldef = virStoragePoolDefParseString(poolxml))) goto cleanup; def->srcpool->pooltype = pooldef->type; def->srcpool->voltype = info.type; if (def->srcpool->mode && pooldef->type != VIR_STORAGE_POOL_ISCSI) { virReportError(VIR_ERR_XML_ERROR, "%s", _("disk source mode is only valid when " "storage pool is of iscsi type")); goto cleanup; } VIR_FREE(def->src); virDomainDiskHostDefFree(def->nhosts, def->hosts); virDomainDiskAuthClear(def); switch ((enum virStoragePoolType) pooldef->type) { case VIR_STORAGE_POOL_DIR: case VIR_STORAGE_POOL_FS: case VIR_STORAGE_POOL_NETFS: case VIR_STORAGE_POOL_LOGICAL: case VIR_STORAGE_POOL_DISK: case VIR_STORAGE_POOL_SCSI: if (!(def->src = virStorageVolGetPath(vol))) goto cleanup; if (def->startupPolicy && info.type != VIR_STORAGE_VOL_FILE) { virReportError(VIR_ERR_XML_ERROR, "%s", _("'startupPolicy' is only valid for " "'file' type volume")); goto cleanup; } switch (info.type) { case VIR_STORAGE_VOL_FILE: def->srcpool->actualtype = VIR_DOMAIN_DISK_TYPE_FILE; break; case VIR_STORAGE_VOL_DIR: def->srcpool->actualtype = VIR_DOMAIN_DISK_TYPE_DIR; break; case VIR_STORAGE_VOL_BLOCK: def->srcpool->actualtype = VIR_DOMAIN_DISK_TYPE_BLOCK; break; case VIR_STORAGE_VOL_NETWORK: case VIR_STORAGE_VOL_NETDIR: virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected storage volume type '%s' " "for storage pool type '%s'"), virStorageVolTypeToString(info.type), virStoragePoolTypeToString(pooldef->type)); goto cleanup; } break; case VIR_STORAGE_POOL_ISCSI: if (def->startupPolicy) { virReportError(VIR_ERR_XML_ERROR, "%s", _("'startupPolicy' is only valid for " "'file' type volume")); goto cleanup; } switch (def->srcpool->mode) { case VIR_DOMAIN_DISK_SOURCE_POOL_MODE_DEFAULT: case VIR_DOMAIN_DISK_SOURCE_POOL_MODE_LAST: def->srcpool->mode = VIR_DOMAIN_DISK_SOURCE_POOL_MODE_HOST; /* fallthrough */ case VIR_DOMAIN_DISK_SOURCE_POOL_MODE_HOST: def->srcpool->actualtype = VIR_DOMAIN_DISK_TYPE_BLOCK; if (!(def->src = virStorageVolGetPath(vol))) goto cleanup; break; case VIR_DOMAIN_DISK_SOURCE_POOL_MODE_DIRECT: def->srcpool->actualtype = VIR_DOMAIN_DISK_TYPE_NETWORK; def->protocol = VIR_DOMAIN_DISK_PROTOCOL_ISCSI; if (qemuTranslateDiskSourcePoolAuth(def, pooldef) < 0) goto cleanup; if (qemuAddISCSIPoolSourceHost(def, pooldef) < 0) goto cleanup; break; } break; case VIR_STORAGE_POOL_MPATH: case VIR_STORAGE_POOL_RBD: case VIR_STORAGE_POOL_SHEEPDOG: case VIR_STORAGE_POOL_GLUSTER: case VIR_STORAGE_POOL_LAST: virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("using '%s' pools for backing 'volume' disks " "isn't yet supported"), virStoragePoolTypeToString(pooldef->type)); goto cleanup; } ret = 0; cleanup: if (ret < 0) savedError = virSaveLastError(); if (pool) virStoragePoolFree(pool); if (vol) virStorageVolFree(vol); if (savedError) { virSetError(savedError); virFreeError(savedError); } VIR_FREE(poolxml); virStoragePoolDefFree(pooldef); return ret; } int qemuSnapshotDiskGetActualType(virDomainSnapshotDiskDefPtr def) { if (def->type == -1) return VIR_DOMAIN_DISK_TYPE_FILE; return def->type; } int qemuTranslateSnapshotDiskSourcePool(virConnectPtr conn ATTRIBUTE_UNUSED, virDomainSnapshotDiskDefPtr def) { if (def->type != VIR_DOMAIN_DISK_TYPE_VOLUME) return 0; virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Snapshots are not yet supported with 'pool' volumes")); return -1; }