/* * bhyve_driver.c: core driver methods for managing bhyve guests * * Copyright (C) 2014 Roman Bogorodskiy * Copyright (C) 2014-2015 Red Hat, Inc. * * 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 * . */ #include #include #include #include "virerror.h" #include "datatypes.h" #include "virbuffer.h" #include "viruuid.h" #include "configmake.h" #include "viralloc.h" #include "network_conf.h" #include "interface_conf.h" #include "domain_audit.h" #include "domain_event.h" #include "snapshot_conf.h" #include "virfdstream.h" #include "storage_conf.h" #include "node_device_conf.h" #include "virdomainobjlist.h" #include "virxml.h" #include "virthread.h" #include "virlog.h" #include "virfile.h" #include "virpidfile.h" #include "virtypedparam.h" #include "virrandom.h" #include "virstring.h" #include "cpu/cpu.h" #include "viraccessapicheck.h" #include "virhostcpu.h" #include "virhostmem.h" #include "virportallocator.h" #include "conf/domain_capabilities.h" #include "bhyve_conf.h" #include "bhyve_device.h" #include "bhyve_driver.h" #include "bhyve_command.h" #include "bhyve_parse_command.h" #include "bhyve_domain.h" #include "bhyve_process.h" #include "bhyve_capabilities.h" #define VIR_FROM_THIS VIR_FROM_BHYVE VIR_LOG_INIT("bhyve.bhyve_driver"); bhyveConnPtr bhyve_driver = NULL; void bhyveDriverLock(bhyveConnPtr driver) { virMutexLock(&driver->lock); } void bhyveDriverUnlock(bhyveConnPtr driver) { virMutexUnlock(&driver->lock); } static int bhyveAutostartDomain(virDomainObjPtr vm, void *opaque) { const struct bhyveAutostartData *data = opaque; int ret = 0; virObjectLock(vm); if (vm->autostart && !virDomainObjIsActive(vm)) { virResetLastError(); ret = virBhyveProcessStart(data->conn, data->driver, vm, VIR_DOMAIN_RUNNING_BOOTED, 0); if (ret < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Failed to autostart VM '%s': %s"), vm->def->name, virGetLastErrorMessage()); } } virObjectUnlock(vm); return ret; } static void bhyveAutostartDomains(bhyveConnPtr driver) { /* XXX: Figure out a better way todo this. The domain * startup code needs a connection handle in order * to lookup the bridge associated with a virtual * network */ virConnectPtr conn = virConnectOpen("bhyve:///system"); /* Ignoring NULL conn which is mostly harmless here */ struct bhyveAutostartData data = { driver, conn }; virDomainObjListForEach(driver->domains, false, bhyveAutostartDomain, &data); virObjectUnref(conn); } /** * bhyveDriverGetCapabilities: * * Get a reference to the virCapsPtr instance for the * driver. * * The caller must release the reference with virObjetUnref * * Returns: a reference to a virCapsPtr instance or NULL */ virCapsPtr ATTRIBUTE_NONNULL(1) bhyveDriverGetCapabilities(bhyveConnPtr driver) { return virObjectRef(driver->caps); } static char * bhyveConnectGetCapabilities(virConnectPtr conn) { bhyveConnPtr privconn = conn->privateData; virCapsPtr caps; char *xml = NULL; if (virConnectGetCapabilitiesEnsureACL(conn) < 0) return NULL; if (!(caps = bhyveDriverGetCapabilities(privconn))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to get Capabilities")); goto cleanup; } if (!(xml = virCapabilitiesFormatXML(caps))) goto cleanup; cleanup: virObjectUnref(caps); return xml; } static virDomainObjPtr bhyveDomObjFromDomain(virDomainPtr domain) { virDomainObjPtr vm; bhyveConnPtr privconn = domain->conn->privateData; char uuidstr[VIR_UUID_STRING_BUFLEN]; vm = virDomainObjListFindByUUID(privconn->domains, domain->uuid); if (!vm) { virUUIDFormat(domain->uuid, uuidstr); virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching uuid '%s' (%s)"), uuidstr, domain->name); return NULL; } return vm; } static int bhyveConnectURIProbe(char **uri) { if (bhyve_driver == NULL) return 0; return VIR_STRDUP(*uri, "bhyve:///system"); } static virDrvOpenStatus bhyveConnectOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED, virConfPtr conf ATTRIBUTE_UNUSED, unsigned int flags) { virCheckFlags(VIR_CONNECT_RO, VIR_DRV_OPEN_ERROR); if (STRNEQ(conn->uri->path, "/system")) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected bhyve URI path '%s', try bhyve:///system"), conn->uri->path); return VIR_DRV_OPEN_ERROR; } if (bhyve_driver == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("bhyve state driver is not active")); return VIR_DRV_OPEN_ERROR; } if (virConnectOpenEnsureACL(conn) < 0) return VIR_DRV_OPEN_ERROR; conn->privateData = bhyve_driver; return VIR_DRV_OPEN_SUCCESS; } static int bhyveConnectClose(virConnectPtr conn) { bhyveConnPtr privconn = conn->privateData; virCloseCallbacksRun(privconn->closeCallbacks, conn, privconn->domains, privconn); conn->privateData = NULL; return 0; } static char * bhyveConnectGetHostname(virConnectPtr conn) { if (virConnectGetHostnameEnsureACL(conn) < 0) return NULL; return virGetHostname(); } static char * bhyveConnectGetSysinfo(virConnectPtr conn, unsigned int flags) { bhyveConnPtr privconn = conn->privateData; virBuffer buf = VIR_BUFFER_INITIALIZER; virCheckFlags(0, NULL); if (virConnectGetSysinfoEnsureACL(conn) < 0) return NULL; if (!privconn->hostsysinfo) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Host SMBIOS information is not available")); return NULL; } if (virSysinfoFormat(&buf, privconn->hostsysinfo) < 0) return NULL; if (virBufferCheckError(&buf) < 0) return NULL; return virBufferContentAndReset(&buf); } static int bhyveConnectGetVersion(virConnectPtr conn, unsigned long *version) { struct utsname ver; if (virConnectGetVersionEnsureACL(conn) < 0) return -1; uname(&ver); if (virParseVersionString(ver.release, version, true) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Unknown release: %s"), ver.release); return -1; } return 0; } static int bhyveDomainGetInfo(virDomainPtr domain, virDomainInfoPtr info) { virDomainObjPtr vm; int ret = -1; if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainGetInfoEnsureACL(domain->conn, vm->def) < 0) goto cleanup; if (virDomainObjIsActive(vm)) { if (virBhyveGetDomainTotalCpuStats(vm, &(info->cpuTime)) < 0) goto cleanup; } else { info->cpuTime = 0; } info->state = virDomainObjGetState(vm, NULL); info->maxMem = virDomainDefGetMemoryTotal(vm->def); info->nrVirtCpu = virDomainDefGetVcpus(vm->def); ret = 0; cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainGetState(virDomainPtr domain, int *state, int *reason, unsigned int flags) { virDomainObjPtr vm; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainGetStateEnsureACL(domain->conn, vm->def) < 0) goto cleanup; *state = virDomainObjGetState(vm, reason); ret = 0; cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainGetAutostart(virDomainPtr domain, int *autostart) { virDomainObjPtr vm; int ret = -1; if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainGetAutostartEnsureACL(domain->conn, vm->def) < 0) goto cleanup; *autostart = vm->autostart; ret = 0; cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainSetAutostart(virDomainPtr domain, int autostart) { virDomainObjPtr vm; char *configFile = NULL; char *autostartLink = NULL; int ret = -1; if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainSetAutostartEnsureACL(domain->conn, vm->def) < 0) goto cleanup; if (!vm->persistent) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot set autostart for transient domain")); goto cleanup; } autostart = (autostart != 0); if (vm->autostart != autostart) { if ((configFile = virDomainConfigFile(BHYVE_CONFIG_DIR, vm->def->name)) == NULL) goto cleanup; if ((autostartLink = virDomainConfigFile(BHYVE_AUTOSTART_DIR, vm->def->name)) == NULL) goto cleanup; if (autostart) { if (virFileMakePath(BHYVE_AUTOSTART_DIR) < 0) { virReportSystemError(errno, _("cannot create autostart directory %s"), BHYVE_AUTOSTART_DIR); goto cleanup; } if (symlink(configFile, autostartLink) < 0) { virReportSystemError(errno, _("Failed to create symlink '%s' to '%s'"), autostartLink, configFile); goto cleanup; } } else { if (unlink(autostartLink) < 0 && errno != ENOENT && errno != ENOTDIR) { virReportSystemError(errno, _("Failed to delete symlink '%s'"), autostartLink); goto cleanup; } } vm->autostart = autostart; } ret = 0; cleanup: VIR_FREE(configFile); VIR_FREE(autostartLink); virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainIsActive(virDomainPtr domain) { virDomainObjPtr obj; int ret = -1; if (!(obj = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainIsActiveEnsureACL(domain->conn, obj->def) < 0) goto cleanup; ret = virDomainObjIsActive(obj); cleanup: virDomainObjEndAPI(&obj); return ret; } static int bhyveDomainIsPersistent(virDomainPtr domain) { virDomainObjPtr obj; int ret = -1; if (!(obj = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainIsPersistentEnsureACL(domain->conn, obj->def) < 0) goto cleanup; ret = obj->persistent; cleanup: virDomainObjEndAPI(&obj); return ret; } static char * bhyveDomainGetOSType(virDomainPtr dom) { virDomainObjPtr vm; char *ret = NULL; if (!(vm = bhyveDomObjFromDomain(dom))) goto cleanup; if (virDomainGetOSTypeEnsureACL(dom->conn, vm->def) < 0) goto cleanup; if (VIR_STRDUP(ret, virDomainOSTypeToString(vm->def->os.type)) < 0) goto cleanup; cleanup: virDomainObjEndAPI(&vm); return ret; } static char * bhyveDomainGetXMLDesc(virDomainPtr domain, unsigned int flags) { bhyveConnPtr privconn = domain->conn->privateData; virDomainObjPtr vm; virCapsPtr caps = NULL; char *ret = NULL; virCheckFlags(VIR_DOMAIN_XML_COMMON_FLAGS, NULL); if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainGetXMLDescEnsureACL(domain->conn, vm->def, flags) < 0) goto cleanup; caps = bhyveDriverGetCapabilities(privconn); if (!caps) goto cleanup; ret = virDomainDefFormat(vm->def, caps, virDomainDefFormatConvertXMLFlags(flags)); virObjectUnref(caps); cleanup: virDomainObjEndAPI(&vm); return ret; } static virDomainPtr bhyveDomainDefineXMLFlags(virConnectPtr conn, const char *xml, unsigned int flags) { bhyveConnPtr privconn = conn->privateData; virDomainPtr dom = NULL; virDomainDefPtr def = NULL; virDomainDefPtr oldDef = NULL; virDomainObjPtr vm = NULL; virObjectEventPtr event = NULL; virCapsPtr caps = NULL; unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE; virCheckFlags(VIR_DOMAIN_DEFINE_VALIDATE, NULL); if (flags & VIR_DOMAIN_DEFINE_VALIDATE) parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; caps = bhyveDriverGetCapabilities(privconn); if (!caps) return NULL; if ((def = virDomainDefParseString(xml, caps, privconn->xmlopt, NULL, parse_flags)) == NULL) goto cleanup; if (virXMLCheckIllegalChars("name", def->name, "\n") < 0) goto cleanup; if (virDomainDefineXMLFlagsEnsureACL(conn, def) < 0) goto cleanup; if (bhyveDomainAssignAddresses(def, NULL) < 0) goto cleanup; if (!(vm = virDomainObjListAdd(privconn->domains, def, privconn->xmlopt, 0, &oldDef))) goto cleanup; def = NULL; vm->persistent = 1; if (virDomainSaveConfig(BHYVE_CONFIG_DIR, caps, vm->newDef ? vm->newDef : vm->def) < 0) { virDomainObjListRemove(privconn->domains, vm); goto cleanup; } event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_DEFINED, !oldDef ? VIR_DOMAIN_EVENT_DEFINED_ADDED : VIR_DOMAIN_EVENT_DEFINED_UPDATED); dom = virGetDomain(conn, vm->def->name, vm->def->uuid, vm->def->id); cleanup: virObjectUnref(caps); virDomainDefFree(def); virDomainDefFree(oldDef); virDomainObjEndAPI(&vm); virObjectEventStateQueue(privconn->domainEventState, event); return dom; } static virDomainPtr bhyveDomainDefineXML(virConnectPtr conn, const char *xml) { return bhyveDomainDefineXMLFlags(conn, xml, 0); } static int bhyveDomainUndefineFlags(virDomainPtr domain, unsigned int flags) { bhyveConnPtr privconn = domain->conn->privateData; virObjectEventPtr event = NULL; virDomainObjPtr vm; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainUndefineFlagsEnsureACL(domain->conn, vm->def) < 0) goto cleanup; if (!vm->persistent) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Cannot undefine transient domain")); goto cleanup; } if (virDomainDeleteConfig(BHYVE_CONFIG_DIR, BHYVE_AUTOSTART_DIR, vm) < 0) goto cleanup; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_UNDEFINED, VIR_DOMAIN_EVENT_UNDEFINED_REMOVED); if (virDomainObjIsActive(vm)) vm->persistent = 0; else virDomainObjListRemove(privconn->domains, vm); ret = 0; cleanup: virDomainObjEndAPI(&vm); virObjectEventStateQueue(privconn->domainEventState, event); return ret; } static int bhyveDomainUndefine(virDomainPtr domain) { return bhyveDomainUndefineFlags(domain, 0); } static int bhyveConnectListDomains(virConnectPtr conn, int *ids, int maxids) { bhyveConnPtr privconn = conn->privateData; int n; if (virConnectListDomainsEnsureACL(conn) < 0) return -1; n = virDomainObjListGetActiveIDs(privconn->domains, ids, maxids, virConnectListDomainsCheckACL, conn); return n; } static int bhyveConnectNumOfDomains(virConnectPtr conn) { bhyveConnPtr privconn = conn->privateData; int count; if (virConnectNumOfDomainsEnsureACL(conn) < 0) return -1; count = virDomainObjListNumOfDomains(privconn->domains, true, virConnectNumOfDomainsCheckACL, conn); return count; } static int bhyveConnectListDefinedDomains(virConnectPtr conn, char **const names, int maxnames) { bhyveConnPtr privconn = conn->privateData; int n; if (virConnectListDefinedDomainsEnsureACL(conn) < 0) return -1; memset(names, 0, sizeof(*names) * maxnames); n = virDomainObjListGetInactiveNames(privconn->domains, names, maxnames, virConnectListDefinedDomainsCheckACL, conn); return n; } static int bhyveConnectNumOfDefinedDomains(virConnectPtr conn) { bhyveConnPtr privconn = conn->privateData; int count; if (virConnectNumOfDefinedDomainsEnsureACL(conn) < 0) return -1; count = virDomainObjListNumOfDomains(privconn->domains, false, virConnectNumOfDefinedDomainsCheckACL, conn); return count; } static char * bhyveConnectDomainXMLToNative(virConnectPtr conn, const char *format, const char *xmlData, unsigned int flags) { virBuffer buf = VIR_BUFFER_INITIALIZER; bhyveConnPtr privconn = conn->privateData; virDomainDefPtr def = NULL; virCommandPtr cmd = NULL, loadcmd = NULL; virCapsPtr caps = NULL; char *ret = NULL; virCheckFlags(0, NULL); if (virConnectDomainXMLToNativeEnsureACL(conn) < 0) goto cleanup; if (STRNEQ(format, BHYVE_CONFIG_FORMAT_ARGV)) { virReportError(VIR_ERR_INVALID_ARG, _("Unsupported config type %s"), format); goto cleanup; } if (!(caps = bhyveDriverGetCapabilities(privconn))) goto cleanup; if (!(def = virDomainDefParseString(xmlData, caps, privconn->xmlopt, NULL, VIR_DOMAIN_DEF_PARSE_INACTIVE))) goto cleanup; if (bhyveDomainAssignAddresses(def, NULL) < 0) goto cleanup; if (def->os.bootloader == NULL && def->os.loader) { if ((def->os.loader->readonly != VIR_TRISTATE_BOOL_YES) || (def->os.loader->type != VIR_DOMAIN_LOADER_TYPE_PFLASH)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Only read-only pflash is supported.")); goto cleanup; } if ((bhyveDriverGetCaps(conn) & BHYVE_CAP_LPC_BOOTROM) == 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Installed bhyve binary does not support " "bootrom")); goto cleanup; } } else { if (!(loadcmd = virBhyveProcessBuildLoadCmd(conn, def, "", NULL))) goto cleanup; virBufferAdd(&buf, virCommandToString(loadcmd, false), -1); virBufferAddChar(&buf, '\n'); } if (!(cmd = virBhyveProcessBuildBhyveCmd(conn, def, true))) goto cleanup; virBufferAdd(&buf, virCommandToString(cmd, false), -1); if (virBufferCheckError(&buf) < 0) goto cleanup; ret = virBufferContentAndReset(&buf); cleanup: virCommandFree(loadcmd); virCommandFree(cmd); virDomainDefFree(def); virObjectUnref(caps); return ret; } static int bhyveConnectListAllDomains(virConnectPtr conn, virDomainPtr **domains, unsigned int flags) { bhyveConnPtr privconn = conn->privateData; int ret = -1; virCheckFlags(VIR_CONNECT_LIST_DOMAINS_FILTERS_ALL, -1); if (virConnectListAllDomainsEnsureACL(conn) < 0) return -1; ret = virDomainObjListExport(privconn->domains, conn, domains, virConnectListAllDomainsCheckACL, flags); return ret; } static virDomainPtr bhyveDomainLookupByUUID(virConnectPtr conn, const unsigned char *uuid) { bhyveConnPtr privconn = conn->privateData; virDomainObjPtr vm; virDomainPtr dom = NULL; vm = virDomainObjListFindByUUID(privconn->domains, uuid); if (!vm) { char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(uuid, uuidstr); virReportError(VIR_ERR_NO_DOMAIN, _("No domain with matching uuid '%s'"), uuidstr); goto cleanup; } if (virDomainLookupByUUIDEnsureACL(conn, vm->def) < 0) goto cleanup; dom = virGetDomain(conn, vm->def->name, vm->def->uuid, vm->def->id); cleanup: virDomainObjEndAPI(&vm); return dom; } static virDomainPtr bhyveDomainLookupByName(virConnectPtr conn, const char *name) { bhyveConnPtr privconn = conn->privateData; virDomainObjPtr vm; virDomainPtr dom = NULL; vm = virDomainObjListFindByName(privconn->domains, name); if (!vm) { virReportError(VIR_ERR_NO_DOMAIN, _("no domain with matching name '%s'"), name); goto cleanup; } if (virDomainLookupByNameEnsureACL(conn, vm->def) < 0) goto cleanup; dom = virGetDomain(conn, vm->def->name, vm->def->uuid, vm->def->id); cleanup: virDomainObjEndAPI(&vm); return dom; } static virDomainPtr bhyveDomainLookupByID(virConnectPtr conn, int id) { bhyveConnPtr privconn = conn->privateData; virDomainObjPtr vm; virDomainPtr dom = NULL; vm = virDomainObjListFindByID(privconn->domains, id); if (!vm) { virReportError(VIR_ERR_NO_DOMAIN, _("No domain with matching ID '%d'"), id); goto cleanup; } if (virDomainLookupByIDEnsureACL(conn, vm->def) < 0) goto cleanup; dom = virGetDomain(conn, vm->def->name, vm->def->uuid, vm->def->id); cleanup: virDomainObjEndAPI(&vm); return dom; } static int bhyveDomainCreateWithFlags(virDomainPtr dom, unsigned int flags) { bhyveConnPtr privconn = dom->conn->privateData; virDomainObjPtr vm; virObjectEventPtr event = NULL; unsigned int start_flags = 0; int ret = -1; virCheckFlags(VIR_DOMAIN_START_AUTODESTROY, -1); if (flags & VIR_DOMAIN_START_AUTODESTROY) start_flags |= VIR_BHYVE_PROCESS_START_AUTODESTROY; if (!(vm = bhyveDomObjFromDomain(dom))) goto cleanup; if (virDomainCreateWithFlagsEnsureACL(dom->conn, vm->def) < 0) goto cleanup; if (virDomainObjIsActive(vm)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Domain is already running")); goto cleanup; } ret = virBhyveProcessStart(dom->conn, privconn, vm, VIR_DOMAIN_RUNNING_BOOTED, start_flags); if (ret == 0) event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_BOOTED); cleanup: virDomainObjEndAPI(&vm); virObjectEventStateQueue(privconn->domainEventState, event); return ret; } static int bhyveDomainCreate(virDomainPtr dom) { return bhyveDomainCreateWithFlags(dom, 0); } static virDomainPtr bhyveDomainCreateXML(virConnectPtr conn, const char *xml, unsigned int flags) { bhyveConnPtr privconn = conn->privateData; virDomainPtr dom = NULL; virDomainDefPtr def = NULL; virDomainObjPtr vm = NULL; virObjectEventPtr event = NULL; virCapsPtr caps = NULL; unsigned int start_flags = 0; unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE; virCheckFlags(VIR_DOMAIN_START_AUTODESTROY | VIR_DOMAIN_START_VALIDATE, NULL); if (flags & VIR_DOMAIN_START_VALIDATE) parse_flags |= VIR_DOMAIN_DEF_PARSE_VALIDATE_SCHEMA; if (flags & VIR_DOMAIN_START_AUTODESTROY) start_flags |= VIR_BHYVE_PROCESS_START_AUTODESTROY; caps = bhyveDriverGetCapabilities(privconn); if (!caps) return NULL; if ((def = virDomainDefParseString(xml, caps, privconn->xmlopt, NULL, parse_flags)) == NULL) goto cleanup; if (virDomainCreateXMLEnsureACL(conn, def) < 0) goto cleanup; if (bhyveDomainAssignAddresses(def, NULL) < 0) goto cleanup; if (!(vm = virDomainObjListAdd(privconn->domains, def, privconn->xmlopt, VIR_DOMAIN_OBJ_LIST_ADD_LIVE | VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, NULL))) goto cleanup; def = NULL; if (virBhyveProcessStart(conn, privconn, vm, VIR_DOMAIN_RUNNING_BOOTED, start_flags) < 0) { /* If domain is not persistent, remove its data */ if (!vm->persistent) virDomainObjListRemove(privconn->domains, vm); goto cleanup; } event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_BOOTED); dom = virGetDomain(conn, vm->def->name, vm->def->uuid, vm->def->id); cleanup: virObjectUnref(caps); virDomainDefFree(def); virDomainObjEndAPI(&vm); virObjectEventStateQueue(privconn->domainEventState, event); return dom; } static int bhyveDomainDestroyFlags(virDomainPtr dom, unsigned int flags) { bhyveConnPtr privconn = dom->conn->privateData; virDomainObjPtr vm; virObjectEventPtr event = NULL; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(dom))) goto cleanup; if (virDomainDestroyFlagsEnsureACL(dom->conn, vm->def) < 0) goto cleanup; if (virDomainObjCheckActive(vm) < 0) goto cleanup; ret = virBhyveProcessStop(privconn, vm, VIR_DOMAIN_SHUTOFF_DESTROYED); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_DESTROYED); if (!vm->persistent) virDomainObjListRemove(privconn->domains, vm); cleanup: virDomainObjEndAPI(&vm); virObjectEventStateQueue(privconn->domainEventState, event); return ret; } static int bhyveDomainDestroy(virDomainPtr dom) { return bhyveDomainDestroyFlags(dom, 0); } static int bhyveDomainShutdownFlags(virDomainPtr dom, unsigned int flags) { virDomainObjPtr vm; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(dom))) goto cleanup; if (virDomainShutdownFlagsEnsureACL(dom->conn, vm->def, flags) < 0) goto cleanup; if (virDomainObjCheckActive(vm) < 0) goto cleanup; ret = virBhyveProcessShutdown(vm); cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainShutdown(virDomainPtr dom) { return bhyveDomainShutdownFlags(dom, 0); } static int bhyveDomainOpenConsole(virDomainPtr dom, const char *dev_name ATTRIBUTE_UNUSED, virStreamPtr st, unsigned int flags) { virDomainObjPtr vm = NULL; virDomainChrDefPtr chr = NULL; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(dom))) goto cleanup; if (virDomainOpenConsoleEnsureACL(dom->conn, vm->def) < 0) goto cleanup; if (virDomainObjCheckActive(vm) < 0) goto cleanup; if (!vm->def->nserials) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("no console devices available")); goto cleanup; } chr = vm->def->serials[0]; if (virFDStreamOpenPTY(st, chr->source->data.nmdm.slave, 0, 0, O_RDWR) < 0) goto cleanup; ret = 0; cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveDomainSetMetadata(virDomainPtr dom, int type, const char *metadata, const char *key, const char *uri, unsigned int flags) { bhyveConnPtr privconn = dom->conn->privateData; virDomainObjPtr vm; virCapsPtr caps = NULL; int ret = -1; virCheckFlags(VIR_DOMAIN_AFFECT_LIVE | VIR_DOMAIN_AFFECT_CONFIG, -1); if (!(vm = bhyveDomObjFromDomain(dom))) return -1; if (virDomainSetMetadataEnsureACL(dom->conn, vm->def, flags) < 0) goto cleanup; if (!(caps = bhyveDriverGetCapabilities(privconn))) goto cleanup; ret = virDomainObjSetMetadata(vm, type, metadata, key, uri, caps, privconn->xmlopt, BHYVE_STATE_DIR, BHYVE_CONFIG_DIR, flags); if (ret == 0) { virObjectEventPtr ev = NULL; ev = virDomainEventMetadataChangeNewFromObj(vm, type, uri); virObjectEventStateQueue(privconn->domainEventState, ev); } cleanup: virObjectUnref(caps); virDomainObjEndAPI(&vm); return ret; } static char * bhyveDomainGetMetadata(virDomainPtr dom, int type, const char *uri, unsigned int flags) { virDomainObjPtr vm; char *ret = NULL; if (!(vm = bhyveDomObjFromDomain(dom))) return NULL; if (virDomainGetMetadataEnsureACL(dom->conn, vm->def) < 0) goto cleanup; ret = virDomainObjGetMetadata(vm, type, uri, flags); cleanup: virDomainObjEndAPI(&vm); return ret; } static int bhyveNodeGetCPUStats(virConnectPtr conn, int cpuNum, virNodeCPUStatsPtr params, int *nparams, unsigned int flags) { if (virNodeGetCPUStatsEnsureACL(conn) < 0) return -1; return virHostCPUGetStats(cpuNum, params, nparams, flags); } static int bhyveNodeGetMemoryStats(virConnectPtr conn, int cellNum, virNodeMemoryStatsPtr params, int *nparams, unsigned int flags) { if (virNodeGetMemoryStatsEnsureACL(conn) < 0) return -1; return virHostMemGetStats(cellNum, params, nparams, flags); } static int bhyveNodeGetInfo(virConnectPtr conn, virNodeInfoPtr nodeinfo) { if (virNodeGetInfoEnsureACL(conn) < 0) return -1; return virCapabilitiesGetNodeInfo(nodeinfo); } static int bhyveStateCleanup(void) { VIR_DEBUG("bhyve state cleanup"); if (bhyve_driver == NULL) return -1; virObjectUnref(bhyve_driver->domains); virObjectUnref(bhyve_driver->caps); virObjectUnref(bhyve_driver->xmlopt); virSysinfoDefFree(bhyve_driver->hostsysinfo); virObjectUnref(bhyve_driver->closeCallbacks); virObjectUnref(bhyve_driver->domainEventState); virObjectUnref(bhyve_driver->config); virPortAllocatorRangeFree(bhyve_driver->remotePorts); if (bhyve_driver->lockFD != -1) virPidFileRelease(BHYVE_STATE_DIR, "driver", bhyve_driver->lockFD); virMutexDestroy(&bhyve_driver->lock); VIR_FREE(bhyve_driver); return 0; } static int bhyveStateInitialize(bool privileged, virStateInhibitCallback callback ATTRIBUTE_UNUSED, void *opaque ATTRIBUTE_UNUSED) { if (!privileged) { VIR_INFO("Not running privileged, disabling driver"); return VIR_DRV_STATE_INIT_SKIPPED; } if (VIR_ALLOC(bhyve_driver) < 0) return VIR_DRV_STATE_INIT_ERROR; bhyve_driver->lockFD = -1; if (virMutexInit(&bhyve_driver->lock) < 0) { VIR_FREE(bhyve_driver); return VIR_DRV_STATE_INIT_ERROR; } if (!(bhyve_driver->closeCallbacks = virCloseCallbacksNew())) goto cleanup; if (!(bhyve_driver->caps = virBhyveCapsBuild())) goto cleanup; if (virBhyveProbeCaps(&bhyve_driver->bhyvecaps) < 0) goto cleanup; if (virBhyveProbeGrubCaps(&bhyve_driver->grubcaps) < 0) goto cleanup; if (!(bhyve_driver->xmlopt = virBhyveDriverCreateXMLConf(bhyve_driver))) goto cleanup; if (!(bhyve_driver->domains = virDomainObjListNew())) goto cleanup; if (!(bhyve_driver->domainEventState = virObjectEventStateNew())) goto cleanup; if (!(bhyve_driver->remotePorts = virPortAllocatorRangeNew(_("display"), 5900, 65535))) goto cleanup; bhyve_driver->hostsysinfo = virSysinfoRead(); if (!(bhyve_driver->config = virBhyveDriverConfigNew())) goto cleanup; if (virBhyveLoadDriverConfig(bhyve_driver->config, SYSCONFDIR "/libvirt/bhyve.conf") < 0) goto cleanup; if (virFileMakePath(BHYVE_LOG_DIR) < 0) { virReportSystemError(errno, _("Failed to mkdir %s"), BHYVE_LOG_DIR); goto cleanup; } if (virFileMakePath(BHYVE_STATE_DIR) < 0) { virReportSystemError(errno, _("Failed to mkdir %s"), BHYVE_STATE_DIR); goto cleanup; } if ((bhyve_driver->lockFD = virPidFileAcquire(BHYVE_STATE_DIR, "driver", false, getpid())) < 0) goto cleanup; if (virDomainObjListLoadAllConfigs(bhyve_driver->domains, BHYVE_STATE_DIR, NULL, true, bhyve_driver->caps, bhyve_driver->xmlopt, NULL, NULL) < 0) goto cleanup; if (virDomainObjListLoadAllConfigs(bhyve_driver->domains, BHYVE_CONFIG_DIR, BHYVE_AUTOSTART_DIR, false, bhyve_driver->caps, bhyve_driver->xmlopt, NULL, NULL) < 0) goto cleanup; virBhyveProcessReconnectAll(bhyve_driver); bhyveAutostartDomains(bhyve_driver); return VIR_DRV_STATE_INIT_COMPLETE; cleanup: bhyveStateCleanup(); return VIR_DRV_STATE_INIT_ERROR; } unsigned bhyveDriverGetCaps(virConnectPtr conn) { bhyveConnPtr driver = conn->privateData; if (driver != NULL) return driver->bhyvecaps; return 0; } unsigned bhyveDriverGetGrubCaps(virConnectPtr conn) { bhyveConnPtr driver = conn->privateData; if (driver != NULL) return driver->grubcaps; return 0; } static int bhyveConnectGetMaxVcpus(virConnectPtr conn, const char *type) { if (virConnectGetMaxVcpusEnsureACL(conn) < 0) return -1; /* * Bhyve supports up to 16 VCPUs, but offers no method to check this * value. Hardcode 16... */ if (!type || STRCASEEQ(type, "bhyve")) return 16; virReportError(VIR_ERR_INVALID_ARG, _("unknown type '%s'"), type); return -1; } static unsigned long long bhyveNodeGetFreeMemory(virConnectPtr conn) { unsigned long long freeMem; if (virNodeGetFreeMemoryEnsureACL(conn) < 0) return 0; if (virHostMemGetInfo(NULL, &freeMem) < 0) return 0; return freeMem; } static int bhyveNodeGetCPUMap(virConnectPtr conn, unsigned char **cpumap, unsigned int *online, unsigned int flags) { if (virNodeGetCPUMapEnsureACL(conn) < 0) return -1; return virHostCPUGetMap(cpumap, online, flags); } static int bhyveNodeGetMemoryParameters(virConnectPtr conn, virTypedParameterPtr params, int *nparams, unsigned int flags) { if (virNodeGetMemoryParametersEnsureACL(conn) < 0) return -1; return virHostMemGetParameters(params, nparams, flags); } static int bhyveNodeSetMemoryParameters(virConnectPtr conn, virTypedParameterPtr params, int nparams, unsigned int flags) { if (virNodeSetMemoryParametersEnsureACL(conn) < 0) return -1; return virHostMemSetParameters(params, nparams, flags); } static char * bhyveConnectBaselineCPU(virConnectPtr conn, const char **xmlCPUs, unsigned int ncpus, unsigned int flags) { virCPUDefPtr *cpus = NULL; virCPUDefPtr cpu = NULL; char *cpustr = NULL; virCheckFlags(VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES | VIR_CONNECT_BASELINE_CPU_MIGRATABLE, NULL); if (virConnectBaselineCPUEnsureACL(conn) < 0) goto cleanup; if (!(cpus = virCPUDefListParse(xmlCPUs, ncpus, VIR_CPU_TYPE_HOST))) goto cleanup; if (!(cpu = virCPUBaseline(VIR_ARCH_NONE, cpus, ncpus, NULL, NULL, !!(flags & VIR_CONNECT_BASELINE_CPU_MIGRATABLE)))) goto cleanup; if ((flags & VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) && virCPUExpandFeatures(cpus[0]->arch, cpu) < 0) goto cleanup; cpustr = virCPUDefFormat(cpu, NULL); cleanup: virCPUDefListFree(cpus); virCPUDefFree(cpu); return cpustr; } static int bhyveConnectCompareCPU(virConnectPtr conn, const char *xmlDesc, unsigned int flags) { bhyveConnPtr driver = conn->privateData; int ret = VIR_CPU_COMPARE_ERROR; virCapsPtr caps = NULL; bool failIncompatible; virCheckFlags(VIR_CONNECT_COMPARE_CPU_FAIL_INCOMPATIBLE, VIR_CPU_COMPARE_ERROR); if (virConnectCompareCPUEnsureACL(conn) < 0) goto cleanup; failIncompatible = !!(flags & VIR_CONNECT_COMPARE_CPU_FAIL_INCOMPATIBLE); if (!(caps = bhyveDriverGetCapabilities(driver))) goto cleanup; if (!caps->host.cpu || !caps->host.cpu->model) { if (failIncompatible) { virReportError(VIR_ERR_CPU_INCOMPATIBLE, "%s", _("cannot get host CPU capabilities")); } else { VIR_WARN("cannot get host CPU capabilities"); ret = VIR_CPU_COMPARE_INCOMPATIBLE; } } else { ret = virCPUCompareXML(caps->host.arch, caps->host.cpu, xmlDesc, failIncompatible); } cleanup: virObjectUnref(caps); return ret; } static int bhyveConnectDomainEventRegisterAny(virConnectPtr conn, virDomainPtr dom, int eventID, virConnectDomainEventGenericCallback callback, void *opaque, virFreeCallback freecb) { bhyveConnPtr privconn = conn->privateData; int ret; if (virConnectDomainEventRegisterAnyEnsureACL(conn) < 0) return -1; if (virDomainEventStateRegisterID(conn, privconn->domainEventState, dom, eventID, callback, opaque, freecb, &ret) < 0) ret = -1; return ret; } static int bhyveConnectDomainEventDeregisterAny(virConnectPtr conn, int callbackID) { bhyveConnPtr privconn = conn->privateData; if (virConnectDomainEventDeregisterAnyEnsureACL(conn) < 0) return -1; if (virObjectEventStateDeregisterID(conn, privconn->domainEventState, callbackID, true) < 0) return -1; return 0; } static int bhyveDomainHasManagedSaveImage(virDomainPtr domain, unsigned int flags) { virDomainObjPtr vm = NULL; int ret = -1; virCheckFlags(0, -1); if (!(vm = bhyveDomObjFromDomain(domain))) goto cleanup; if (virDomainHasManagedSaveImageEnsureACL(domain->conn, vm->def) < 0) goto cleanup; ret = 0; cleanup: virDomainObjEndAPI(&vm); return ret; } static const char * bhyveConnectGetType(virConnectPtr conn) { if (virConnectGetTypeEnsureACL(conn) < 0) return NULL; return "BHYVE"; } static int bhyveConnectIsAlive(virConnectPtr conn ATTRIBUTE_UNUSED) { return 1; } static int bhyveConnectIsSecure(virConnectPtr conn ATTRIBUTE_UNUSED) { /* Trivially secure, since always inside the daemon */ return 1; } static int bhyveConnectIsEncrypted(virConnectPtr conn ATTRIBUTE_UNUSED) { /* Not encrypted, but remote driver takes care of that */ return 0; } static char * bhyveConnectDomainXMLFromNative(virConnectPtr conn, const char *nativeFormat, const char *nativeConfig, unsigned int flags) { char *xml = NULL; virDomainDefPtr def = NULL; bhyveConnPtr privconn = conn->privateData; virCapsPtr capabilities = NULL; unsigned caps = bhyveDriverGetCaps(conn); virCheckFlags(0, NULL); if (virConnectDomainXMLFromNativeEnsureACL(conn) < 0) return NULL; capabilities = bhyveDriverGetCapabilities(privconn); if (!capabilities) return NULL; if (STRNEQ(nativeFormat, BHYVE_CONFIG_FORMAT_ARGV)) { virReportError(VIR_ERR_INVALID_ARG, _("unsupported config type %s"), nativeFormat); goto cleanup; } def = bhyveParseCommandLineString(nativeConfig, caps, privconn->xmlopt); if (def == NULL) goto cleanup; xml = virDomainDefFormat(def, capabilities, 0); cleanup: virObjectUnref(capabilities); virDomainDefFree(def); return xml; } static char * bhyveConnectGetDomainCapabilities(virConnectPtr conn, const char *emulatorbin, const char *arch_str, const char *machine, const char *virttype_str, unsigned int flags) { virDomainCapsPtr caps = NULL; char *ret = NULL; int virttype = VIR_DOMAIN_VIRT_BHYVE; int arch = virArchFromHost(); /* virArch */ virCheckFlags(0, ret); if (virConnectGetDomainCapabilitiesEnsureACL(conn) < 0) return ret; if (virttype_str && (virttype = virDomainVirtTypeFromString(virttype_str)) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("unknown virttype: %s"), virttype_str); goto cleanup; } if (virttype != VIR_DOMAIN_VIRT_BHYVE) { virReportError(VIR_ERR_INVALID_ARG, _("unknown virttype: %s"), virttype_str); goto cleanup; } if (arch_str && (arch = virArchFromString(arch_str)) == VIR_ARCH_NONE) { virReportError(VIR_ERR_INVALID_ARG, _("unknown architecture: %s"), arch_str); goto cleanup; } if (!ARCH_IS_X86(arch)) { virReportError(VIR_ERR_NO_SUPPORT, _("unsupported architecture: %s"), virArchToString(arch)); goto cleanup; } if (emulatorbin == NULL) { emulatorbin = "/usr/sbin/bhyve"; } else if (STRNEQ(emulatorbin, "/usr/sbin/bhyve")) { virReportError(VIR_ERR_INVALID_ARG, _("unknown emulator binary: %s"), emulatorbin); goto cleanup; } if (!(caps = virBhyveDomainCapsBuild(conn->privateData, emulatorbin, machine, arch, virttype))) goto cleanup; ret = virDomainCapsFormat(caps); cleanup: virObjectUnref(caps); return ret; } static virHypervisorDriver bhyveHypervisorDriver = { .name = "bhyve", .connectURIProbe = bhyveConnectURIProbe, .connectOpen = bhyveConnectOpen, /* 1.2.2 */ .connectClose = bhyveConnectClose, /* 1.2.2 */ .connectGetVersion = bhyveConnectGetVersion, /* 1.2.2 */ .connectGetHostname = bhyveConnectGetHostname, /* 1.2.2 */ .connectGetSysinfo = bhyveConnectGetSysinfo, /* 1.2.5 */ .domainGetInfo = bhyveDomainGetInfo, /* 1.2.2 */ .domainGetState = bhyveDomainGetState, /* 1.2.2 */ .connectGetCapabilities = bhyveConnectGetCapabilities, /* 1.2.2 */ .connectListDomains = bhyveConnectListDomains, /* 1.2.2 */ .connectNumOfDomains = bhyveConnectNumOfDomains, /* 1.2.2 */ .connectListAllDomains = bhyveConnectListAllDomains, /* 1.2.2 */ .connectListDefinedDomains = bhyveConnectListDefinedDomains, /* 1.2.2 */ .connectNumOfDefinedDomains = bhyveConnectNumOfDefinedDomains, /* 1.2.2 */ .connectDomainXMLToNative = bhyveConnectDomainXMLToNative, /* 1.2.5 */ .domainCreate = bhyveDomainCreate, /* 1.2.2 */ .domainCreateWithFlags = bhyveDomainCreateWithFlags, /* 1.2.3 */ .domainCreateXML = bhyveDomainCreateXML, /* 1.2.4 */ .domainDestroy = bhyveDomainDestroy, /* 1.2.2 */ .domainDestroyFlags = bhyveDomainDestroyFlags, /* 5.6.0 */ .domainShutdown = bhyveDomainShutdown, /* 1.3.3 */ .domainShutdownFlags = bhyveDomainShutdownFlags, /* 5.6.0 */ .domainLookupByUUID = bhyveDomainLookupByUUID, /* 1.2.2 */ .domainLookupByName = bhyveDomainLookupByName, /* 1.2.2 */ .domainLookupByID = bhyveDomainLookupByID, /* 1.2.3 */ .domainDefineXML = bhyveDomainDefineXML, /* 1.2.2 */ .domainDefineXMLFlags = bhyveDomainDefineXMLFlags, /* 1.2.12 */ .domainUndefine = bhyveDomainUndefine, /* 1.2.2 */ .domainUndefineFlags = bhyveDomainUndefineFlags, /* 5.6.0 */ .domainGetOSType = bhyveDomainGetOSType, /* 1.2.21 */ .domainGetXMLDesc = bhyveDomainGetXMLDesc, /* 1.2.2 */ .domainIsActive = bhyveDomainIsActive, /* 1.2.2 */ .domainIsPersistent = bhyveDomainIsPersistent, /* 1.2.2 */ .domainGetAutostart = bhyveDomainGetAutostart, /* 1.2.4 */ .domainSetAutostart = bhyveDomainSetAutostart, /* 1.2.4 */ .domainOpenConsole = bhyveDomainOpenConsole, /* 1.2.4 */ .domainSetMetadata = bhyveDomainSetMetadata, /* 1.2.4 */ .domainGetMetadata = bhyveDomainGetMetadata, /* 1.2.4 */ .nodeGetCPUStats = bhyveNodeGetCPUStats, /* 1.2.2 */ .nodeGetMemoryStats = bhyveNodeGetMemoryStats, /* 1.2.2 */ .nodeGetInfo = bhyveNodeGetInfo, /* 1.2.3 */ .connectGetMaxVcpus = bhyveConnectGetMaxVcpus, /* 1.2.3 */ .nodeGetFreeMemory = bhyveNodeGetFreeMemory, /* 1.2.3 */ .nodeGetCPUMap = bhyveNodeGetCPUMap, /* 1.2.3 */ .nodeGetMemoryParameters = bhyveNodeGetMemoryParameters, /* 1.2.3 */ .nodeSetMemoryParameters = bhyveNodeSetMemoryParameters, /* 1.2.3 */ .connectBaselineCPU = bhyveConnectBaselineCPU, /* 1.2.4 */ .connectCompareCPU = bhyveConnectCompareCPU, /* 1.2.4 */ .connectDomainEventRegisterAny = bhyveConnectDomainEventRegisterAny, /* 1.2.5 */ .connectDomainEventDeregisterAny = bhyveConnectDomainEventDeregisterAny, /* 1.2.5 */ .domainHasManagedSaveImage = bhyveDomainHasManagedSaveImage, /* 1.2.13 */ .connectGetType = bhyveConnectGetType, /* 1.3.5 */ .connectIsAlive = bhyveConnectIsAlive, /* 1.3.5 */ .connectIsSecure = bhyveConnectIsSecure, /* 1.3.5 */ .connectIsEncrypted = bhyveConnectIsEncrypted, /* 1.3.5 */ .connectDomainXMLFromNative = bhyveConnectDomainXMLFromNative, /* 2.1.0 */ .connectGetDomainCapabilities = bhyveConnectGetDomainCapabilities, /* 2.1.0 */ }; static virConnectDriver bhyveConnectDriver = { .localOnly = true, .uriSchemes = (const char *[]){ "bhyve", NULL }, .hypervisorDriver = &bhyveHypervisorDriver, }; static virStateDriver bhyveStateDriver = { .name = "bhyve", .stateInitialize = bhyveStateInitialize, .stateCleanup = bhyveStateCleanup, }; int bhyveRegister(void) { if (virRegisterConnectDriver(&bhyveConnectDriver, true) < 0) return -1; if (virRegisterStateDriver(&bhyveStateDriver) < 0) return -1; return 0; }