libvirt/src/lxc/lxc_process.c
Fangge Jin 500810f3c5 src: fix word spell typos
Signed-off-by: Fangge Jin <fjin@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Signed-off-by: Ján Tomko <jtomko@redhat.com>
2020-07-09 10:01:41 +02:00

1715 lines
54 KiB
C

/*
* Copyright (C) 2010-2016 Red Hat, Inc.
* Copyright IBM Corp. 2008
*
* lxc_process.c: LXC process lifecycle management
*
* 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
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include "lxc_process.h"
#include "lxc_domain.h"
#include "lxc_container.h"
#include "lxc_cgroup.h"
#include "lxc_fuse.h"
#include "datatypes.h"
#include "virfile.h"
#include "virpidfile.h"
#include "virnetdev.h"
#include "virnetdevveth.h"
#include "virnetdevbridge.h"
#include "virnetdevopenvswitch.h"
#include "virtime.h"
#include "domain_nwfilter.h"
#include "viralloc.h"
#include "domain_audit.h"
#include "virerror.h"
#include "virlog.h"
#include "vircommand.h"
#include "lxc_hostdev.h"
#include "virhook.h"
#include "virstring.h"
#include "virprocess.h"
#include "virsystemd.h"
#include "netdev_bandwidth_conf.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_LXC
VIR_LOG_INIT("lxc.lxc_process");
#define START_POSTFIX ": starting up\n"
static void
lxcProcessAutoDestroy(virDomainObjPtr dom,
virConnectPtr conn,
void *opaque)
{
virLXCDriverPtr driver = opaque;
virObjectEventPtr event = NULL;
virLXCDomainObjPrivatePtr priv;
VIR_DEBUG("driver=%p dom=%s conn=%p", driver, dom->def->name, conn);
priv = dom->privateData;
VIR_DEBUG("Killing domain");
virLXCProcessStop(driver, dom, VIR_DOMAIN_SHUTOFF_DESTROYED);
virDomainAuditStop(dom, "destroyed");
event = virDomainEventLifecycleNewFromObj(dom,
VIR_DOMAIN_EVENT_STOPPED,
VIR_DOMAIN_EVENT_STOPPED_DESTROYED);
priv->doneStopEvent = true;
if (!dom->persistent)
virDomainObjListRemove(driver->domains, dom);
virObjectEventStateQueue(driver->domainEventState, event);
}
/*
* Precondition: driver is locked
*/
static int
virLXCProcessReboot(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
virConnectPtr conn = virCloseCallbacksGetConn(driver->closeCallbacks, vm);
int reason = vm->state.reason;
bool autodestroy = false;
int ret = -1;
virDomainDefPtr savedDef;
VIR_DEBUG("Faking reboot");
if (conn) {
virObjectRef(conn);
autodestroy = true;
} else {
conn = virConnectOpen("lxc:///system");
/* Ignoring NULL conn which is mostly harmless here */
}
/* In a reboot scenario, we need to make sure we continue
* to use the current 'def', and not switch to 'newDef'.
* So temporarily hide the newDef and then reinstate it
*/
savedDef = vm->newDef;
vm->newDef = NULL;
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
vm->newDef = savedDef;
if (virLXCProcessStart(conn, driver, vm,
0, NULL, autodestroy, reason) < 0) {
VIR_WARN("Unable to handle reboot of vm %s",
vm->def->name);
goto cleanup;
}
ret = 0;
cleanup:
virObjectUnref(conn);
return ret;
}
static void
lxcProcessRemoveDomainStatus(virLXCDriverConfigPtr cfg,
virDomainObjPtr vm)
{
g_autofree char *file = g_strdup_printf("%s/%s.xml",
cfg->stateDir,
vm->def->name);
if (unlink(file) < 0 && errno != ENOENT && errno != ENOTDIR)
VIR_WARN("Failed to remove domain XML for %s: %s",
vm->def->name, g_strerror(errno));
}
/**
* virLXCProcessCleanup:
* @driver: pointer to driver structure
* @vm: pointer to VM to clean up
* @reason: reason for switching the VM to shutoff state
*
* Cleanout resources associated with the now dead VM
*
*/
static void virLXCProcessCleanup(virLXCDriverPtr driver,
virDomainObjPtr vm,
virDomainShutoffReason reason)
{
size_t i;
virLXCDomainObjPrivatePtr priv = vm->privateData;
const virNetDevVPortProfile *vport = NULL;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
virConnectPtr conn = NULL;
VIR_DEBUG("Cleanup VM name=%s pid=%d reason=%d",
vm->def->name, (int)vm->pid, (int)reason);
/* now that we know it's stopped call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/* we can't stop the operation even if the script raised an error */
virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_STOPPED, VIR_HOOK_SUBOP_END,
NULL, xml, NULL);
}
virSecurityManagerRestoreAllLabel(driver->securityManager,
vm->def, false, false);
virSecurityManagerReleaseLabel(driver->securityManager, vm->def);
/* Clear out dynamically assigned labels */
if (vm->def->nseclabels &&
vm->def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
g_free(vm->def->seclabels[0]->model);
g_free(vm->def->seclabels[0]->label);
g_free(vm->def->seclabels[0]->imagelabel);
vm->def->seclabels[0]->model = NULL;
vm->def->seclabels[0]->label = NULL;
vm->def->seclabels[0]->imagelabel = NULL;
}
/* Stop autodestroy in case guest is restarted */
virCloseCallbacksUnset(driver->closeCallbacks, vm,
lxcProcessAutoDestroy);
if (priv->monitor) {
virLXCMonitorClose(priv->monitor);
virObjectUnref(priv->monitor);
priv->monitor = NULL;
}
virPidFileDelete(cfg->stateDir, vm->def->name);
lxcProcessRemoveDomainStatus(cfg, vm);
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, reason);
vm->pid = -1;
vm->def->id = -1;
if (!!g_atomic_int_dec_and_test(&driver->nactive) && driver->inhibitCallback)
driver->inhibitCallback(false, driver->inhibitOpaque);
virLXCDomainReAttachHostDevices(driver, vm->def);
for (i = 0; i < vm->def->nnets; i++) {
virDomainNetDefPtr iface = vm->def->nets[i];
vport = virDomainNetGetActualVirtPortProfile(iface);
if (iface->ifname) {
if (vport &&
vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)
ignore_value(virNetDevOpenvswitchRemovePort(
virDomainNetGetActualBridgeName(iface),
iface->ifname));
ignore_value(virNetDevVethDelete(iface->ifname));
}
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
if (conn || (conn = virGetConnectNetwork()))
virDomainNetReleaseActualDevice(conn, vm->def, iface);
else
VIR_WARN("Unable to release network device '%s'", NULLSTR(iface->ifname));
}
}
virDomainConfVMNWFilterTeardown(vm);
if (priv->cgroup) {
virCgroupRemove(priv->cgroup);
virCgroupFree(&priv->cgroup);
}
/* Get machined to terminate the machine as it may not have cleaned it
* properly. See https://bugs.freedesktop.org/show_bug.cgi?id=68370 for
* the bug we are working around here.
*/
virCgroupTerminateMachine(priv->machineName);
g_free(priv->machineName);
priv->machineName = NULL;
/* The "release" hook cleans up additional resources */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/* we can't stop the operation even if the script raised an error */
virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_RELEASE, VIR_HOOK_SUBOP_END,
NULL, xml, NULL);
}
virDomainObjRemoveTransientDef(vm);
virObjectUnref(cfg);
virObjectUnref(conn);
}
int
virLXCProcessValidateInterface(virDomainNetDefPtr net)
{
if (net->script) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("scripts are not supported on LXC network interfaces"));
return -1;
}
return 0;
}
char *
virLXCProcessSetupInterfaceTap(virDomainDefPtr vm,
virDomainNetDefPtr net,
const char *brname)
{
char *parentVeth;
char *containerVeth = NULL;
const virNetDevVPortProfile *vport = virDomainNetGetActualVirtPortProfile(net);
VIR_DEBUG("calling vethCreate()");
parentVeth = net->ifname;
if (virNetDevVethCreate(&parentVeth, &containerVeth) < 0)
return NULL;
VIR_DEBUG("parentVeth: %s, containerVeth: %s", parentVeth, containerVeth);
if (net->ifname == NULL)
net->ifname = parentVeth;
if (virNetDevSetMAC(containerVeth, &net->mac) < 0)
return NULL;
if (brname) {
if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) {
if (virNetDevOpenvswitchAddPort(brname, parentVeth, &net->mac, vm->uuid,
vport, virDomainNetGetActualVlan(net)) < 0)
return NULL;
} else {
if (virNetDevBridgeAddPort(brname, parentVeth) < 0)
return NULL;
if (virDomainNetGetActualPortOptionsIsolated(net) == VIR_TRISTATE_BOOL_YES &&
virNetDevBridgePortSetIsolated(brname, parentVeth, true) < 0) {
virErrorPtr err;
virErrorPreserveLast(&err);
ignore_value(virNetDevBridgeRemovePort(brname, parentVeth));
virErrorRestore(&err);
return NULL;
}
}
}
if (virNetDevSetOnline(parentVeth, true) < 0)
return NULL;
if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_ETHERNET) {
/* Set IP info for the host side, but only if the type is
* 'ethernet'.
*/
if (virNetDevIPInfoAddToDev(parentVeth, &net->hostIP) < 0)
return NULL;
}
if (net->filter &&
virDomainConfNWFilterInstantiate(vm->name, vm->uuid, net, false) < 0)
return NULL;
return containerVeth;
}
char *
virLXCProcessSetupInterfaceDirect(virLXCDriverPtr driver,
virDomainDefPtr def,
virDomainNetDefPtr net)
{
char *ret = NULL;
char *res_ifname = NULL;
const virNetDevBandwidth *bw;
const virNetDevVPortProfile *prof;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
const char *linkdev = virDomainNetGetActualDirectDev(net);
unsigned int macvlan_create_flags = VIR_NETDEV_MACVLAN_CREATE_IFUP;
/* XXX how todo bandwidth controls ?
* Since the 'net-ifname' is about to be moved to a different
* namespace & renamed, there will be no host side visible
* interface for the container to attach rules to
*/
bw = virDomainNetGetActualBandwidth(net);
if (bw) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Unable to set network bandwidth on direct interfaces"));
return NULL;
}
/* XXX how todo port profiles ?
* Although we can do the association during container
* startup, at shutdown we are unable to disassociate
* because the macvlan device was moved to the container
* and automagically dies when the container dies. So
* we have no dev to perform disassociation with.
*/
prof = virDomainNetGetActualVirtPortProfile(net);
if (prof) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Unable to set port profile on direct interfaces"));
return NULL;
}
if (virNetDevMacVLanCreateWithVPortProfile(
net->ifname, &net->mac,
linkdev,
virDomainNetGetActualDirectMode(net),
virDomainNetGetActualVlan(net),
def->uuid,
prof,
&res_ifname,
VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
cfg->stateDir,
NULL, 0,
macvlan_create_flags) < 0)
goto cleanup;
ret = res_ifname;
cleanup:
virObjectUnref(cfg);
return ret;
}
static const char *nsInfoLocal[VIR_LXC_DOMAIN_NAMESPACE_LAST] = {
[VIR_LXC_DOMAIN_NAMESPACE_SHARENET] = "net",
[VIR_LXC_DOMAIN_NAMESPACE_SHAREIPC] = "ipc",
[VIR_LXC_DOMAIN_NAMESPACE_SHAREUTS] = "uts",
};
static int virLXCProcessSetupNamespaceName(virLXCDriverPtr driver,
int ns_type,
const char *name)
{
int fd = -1;
virDomainObjPtr vm;
virLXCDomainObjPrivatePtr priv;
g_autofree char *path = NULL;
vm = virDomainObjListFindByName(driver->domains, name);
if (!vm) {
virReportError(VIR_ERR_NO_DOMAIN,
_("No domain with matching name '%s'"), name);
return -1;
}
priv = vm->privateData;
if (!priv->initpid) {
virReportError(VIR_ERR_OPERATION_INVALID, "%s",
_("Init pid is not yet available"));
goto cleanup;
}
path = g_strdup_printf("/proc/%lld/ns/%s", (long long int)priv->initpid,
nsInfoLocal[ns_type]);
if ((fd = open(path, O_RDONLY)) < 0) {
virReportSystemError(errno,
_("failed to open ns %s"),
virLXCDomainNamespaceTypeToString(ns_type));
goto cleanup;
}
cleanup:
virDomainObjEndAPI(&vm);
return fd;
}
static int virLXCProcessSetupNamespacePID(int ns_type, const char *name)
{
g_autofree char *path = g_strdup_printf("/proc/%s/ns/%s",
name, nsInfoLocal[ns_type]);
int fd = open(path, O_RDONLY);
if (fd < 0) {
virReportSystemError(errno,
_("failed to open ns %s"),
virLXCDomainNamespaceTypeToString(ns_type));
return -1;
}
return fd;
}
static int virLXCProcessSetupNamespaceNet(int ns_type, const char *name)
{
g_autofree char *path = NULL;
int fd;
if (ns_type != VIR_LXC_DOMAIN_NAMESPACE_SHARENET) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("'netns' namespace source can only be "
"used with sharenet"));
return -1;
}
path = g_strdup_printf("%s/netns/%s", RUNSTATEDIR, name);
fd = open(path, O_RDONLY);
if (fd < 0) {
virReportSystemError(errno,
_("failed to open netns %s"), name);
return -1;
}
return fd;
}
/**
* virLXCProcessSetupNamespaces:
* @driver: pointer to driver structure
* @def: pointer to virtual machines namespaceData
* @nsFDs: out parameter to store the namespace FD
*
* Opens the specified namespace that needs to be shared and
* will moved into the container namespace later after clone has been called.
*
* Returns 0 on success or -1 in case of error
*/
static int
virLXCProcessSetupNamespaces(virLXCDriverPtr driver,
lxcDomainDefPtr lxcDef,
int *nsFDs)
{
size_t i;
for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++)
nsFDs[i] = -1;
/* If there are no namespaces to be opened just return success */
if (lxcDef == NULL)
return 0;
for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) {
switch (lxcDef->ns_source[i]) {
case VIR_LXC_DOMAIN_NAMESPACE_SOURCE_NONE:
continue;
case VIR_LXC_DOMAIN_NAMESPACE_SOURCE_NAME:
if ((nsFDs[i] = virLXCProcessSetupNamespaceName(driver, i,
lxcDef->ns_val[i])) < 0)
return -1;
break;
case VIR_LXC_DOMAIN_NAMESPACE_SOURCE_PID:
if ((nsFDs[i] = virLXCProcessSetupNamespacePID(i, lxcDef->ns_val[i])) < 0)
return -1;
break;
case VIR_LXC_DOMAIN_NAMESPACE_SOURCE_NETNS:
if ((nsFDs[i] = virLXCProcessSetupNamespaceNet(i, lxcDef->ns_val[i])) < 0)
return -1;
break;
}
}
return 0;
}
/**
* virLXCProcessSetupInterfaces:
* @driver: pointer to driver structure
* @def: pointer to virtual machine structure
* @veths: string list of interface names
*
* Sets up the container interfaces by creating the veth device pairs and
* attaching the parent end to the appropriate bridge. The container end
* will moved into the container namespace later after clone has been called.
*
* Returns 0 on success or -1 in case of error
*/
static int
virLXCProcessSetupInterfaces(virLXCDriverPtr driver,
virDomainDefPtr def,
char ***veths)
{
int ret = -1;
size_t i;
size_t niface = 0;
virDomainNetDefPtr net;
virDomainNetType type;
virConnectPtr netconn = NULL;
virErrorPtr save_err = NULL;
*veths = g_new0(char *, def->nnets + 1);
for (i = 0; i < def->nnets; i++) {
char *veth = NULL;
const virNetDevBandwidth *actualBandwidth;
/* If appropriate, grab a physical device from the configured
* network's pool of devices, or resolve bridge device name
* to the one defined in the network definition.
*/
net = def->nets[i];
if (virLXCProcessValidateInterface(net) < 0)
goto cleanup;
if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
if (!netconn && !(netconn = virGetConnectNetwork()))
goto cleanup;
if (virDomainNetAllocateActualDevice(netconn, def, net) < 0)
goto cleanup;
}
/* final validation now that actual type is known */
if (virDomainActualNetDefValidate(net) < 0)
return -1;
type = virDomainNetGetActualType(net);
switch (type) {
case VIR_DOMAIN_NET_TYPE_NETWORK:
case VIR_DOMAIN_NET_TYPE_BRIDGE: {
const char *brname = virDomainNetGetActualBridgeName(net);
if (!brname) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("No bridge name specified"));
goto cleanup;
}
if (!(veth = virLXCProcessSetupInterfaceTap(def, net, brname)))
goto cleanup;
} break;
case VIR_DOMAIN_NET_TYPE_ETHERNET:
if (!(veth = virLXCProcessSetupInterfaceTap(def, net, NULL)))
goto cleanup;
break;
case VIR_DOMAIN_NET_TYPE_DIRECT:
if (!(veth = virLXCProcessSetupInterfaceDirect(driver, def, net)))
goto cleanup;
break;
case VIR_DOMAIN_NET_TYPE_USER:
case VIR_DOMAIN_NET_TYPE_VHOSTUSER:
case VIR_DOMAIN_NET_TYPE_SERVER:
case VIR_DOMAIN_NET_TYPE_CLIENT:
case VIR_DOMAIN_NET_TYPE_MCAST:
case VIR_DOMAIN_NET_TYPE_UDP:
case VIR_DOMAIN_NET_TYPE_INTERNAL:
case VIR_DOMAIN_NET_TYPE_LAST:
case VIR_DOMAIN_NET_TYPE_HOSTDEV:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unsupported network type %s"),
virDomainNetTypeToString(type));
goto cleanup;
}
/* Set bandwidth or warn if requested and not supported. */
actualBandwidth = virDomainNetGetActualBandwidth(net);
if (actualBandwidth) {
if (virNetDevSupportsBandwidth(type)) {
if (virNetDevBandwidthSet(net->ifname, actualBandwidth, false,
!virDomainNetTypeSharesHostView(net)) < 0)
goto cleanup;
} else {
VIR_WARN("setting bandwidth on interfaces of "
"type '%s' is not implemented yet",
virDomainNetTypeToString(type));
}
}
(*veths)[i] = veth;
def->nets[i]->ifname_guest_actual = g_strdup(veth);
/* Make sure all net definitions will have a name in the container */
if (!net->ifname_guest) {
net->ifname_guest = g_strdup_printf("eth%zu", niface);
niface++;
}
}
ret = 0;
cleanup:
if (ret < 0) {
virErrorPreserveLast(&save_err);
for (i = 0; i < def->nnets; i++) {
virDomainNetDefPtr iface = def->nets[i];
const virNetDevVPortProfile *vport = virDomainNetGetActualVirtPortProfile(iface);
if (vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)
ignore_value(virNetDevOpenvswitchRemovePort(
virDomainNetGetActualBridgeName(iface),
iface->ifname));
if (iface->type == VIR_DOMAIN_NET_TYPE_NETWORK && netconn)
virDomainNetReleaseActualDevice(netconn, def, iface);
}
virErrorRestore(&save_err);
}
virObjectUnref(netconn);
return ret;
}
static void
virLXCProcessCleanInterfaces(virDomainDefPtr def)
{
size_t i;
for (i = 0; i < def->nnets; i++) {
g_free(def->nets[i]->ifname_guest_actual);
def->nets[i]->ifname_guest_actual = NULL;
VIR_DEBUG("Cleared net names: %s", def->nets[i]->ifname_guest);
}
}
extern virLXCDriverPtr lxc_driver;
static void virLXCProcessMonitorEOFNotify(virLXCMonitorPtr mon,
virDomainObjPtr vm)
{
virLXCDriverPtr driver = lxc_driver;
virObjectEventPtr event = NULL;
virLXCDomainObjPrivatePtr priv;
VIR_DEBUG("mon=%p vm=%p", mon, vm);
virObjectLock(vm);
priv = vm->privateData;
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
if (!priv->wantReboot) {
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN);
if (!priv->doneStopEvent) {
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
priv->stopReason);
virDomainAuditStop(vm, "shutdown");
} else {
VIR_DEBUG("Stop event has already been sent");
}
if (!vm->persistent)
virDomainObjListRemove(driver->domains, vm);
} else {
int ret = virLXCProcessReboot(driver, vm);
virDomainAuditStop(vm, "reboot");
virDomainAuditStart(vm, "reboot", ret == 0);
if (ret == 0) {
event = virDomainEventRebootNewFromObj(vm);
} else {
event = virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STOPPED,
priv->stopReason);
if (!vm->persistent)
virDomainObjListRemove(driver->domains, vm);
}
}
/* NB: virLXCProcessConnectMonitor will perform the virObjectRef(vm)
* before adding monitorCallbacks. Since we are now done with the @vm
* we can Unref/Unlock */
virDomainObjEndAPI(&vm);
virObjectEventStateQueue(driver->domainEventState, event);
}
static void virLXCProcessMonitorExitNotify(virLXCMonitorPtr mon G_GNUC_UNUSED,
virLXCMonitorExitStatus status,
virDomainObjPtr vm)
{
virLXCDomainObjPrivatePtr priv = vm->privateData;
virObjectLock(vm);
switch (status) {
case VIR_LXC_MONITOR_EXIT_STATUS_SHUTDOWN:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN;
break;
case VIR_LXC_MONITOR_EXIT_STATUS_ERROR:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
break;
case VIR_LXC_MONITOR_EXIT_STATUS_REBOOT:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN;
priv->wantReboot = true;
break;
default:
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
break;
}
VIR_DEBUG("Domain shutoff reason %d (from status %d)",
priv->stopReason, status);
virObjectUnlock(vm);
}
static int
virLXCProcessGetNsInode(pid_t pid,
const char *nsname,
ino_t *inode)
{
g_autofree char *path = NULL;
struct stat sb;
path = g_strdup_printf("/proc/%lld/ns/%s", (long long)pid, nsname);
if (stat(path, &sb) < 0) {
virReportSystemError(errno,
_("Unable to stat %s"), path);
return -1;
}
*inode = sb.st_ino;
return 0;
}
/* XXX a little evil */
extern virLXCDriverPtr lxc_driver;
static void virLXCProcessMonitorInitNotify(virLXCMonitorPtr mon G_GNUC_UNUSED,
pid_t initpid,
virDomainObjPtr vm)
{
virLXCDriverPtr driver = lxc_driver;
virLXCDomainObjPrivatePtr priv;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
ino_t inode = 0;
virObjectLock(vm);
priv = vm->privateData;
priv->initpid = initpid;
if (virLXCProcessGetNsInode(initpid, "pid", &inode) < 0) {
VIR_WARN("Cannot obtain pid NS inode for %lld: %s",
(long long)initpid,
virGetLastErrorMessage());
virResetLastError();
}
virDomainAuditInit(vm, initpid, inode);
if (virDomainObjSave(vm, lxc_driver->xmlopt, cfg->stateDir) < 0)
VIR_WARN("Cannot update XML with PID for LXC %s", vm->def->name);
virObjectUnlock(vm);
virObjectUnref(cfg);
}
static virLXCMonitorCallbacks monitorCallbacks = {
.eofNotify = virLXCProcessMonitorEOFNotify,
.exitNotify = virLXCProcessMonitorExitNotify,
.initNotify = virLXCProcessMonitorInitNotify,
};
static virLXCMonitorPtr virLXCProcessConnectMonitor(virLXCDriverPtr driver,
virDomainObjPtr vm)
{
virLXCMonitorPtr monitor = NULL;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
if (virSecurityManagerSetSocketLabel(driver->securityManager, vm->def) < 0)
goto cleanup;
/* Hold an extra reference because we can't allow 'vm' to be
* deleted while the monitor is active. This will be unreffed
* during EOFNotify processing. */
virObjectRef(vm);
monitor = virLXCMonitorNew(vm, cfg->stateDir, &monitorCallbacks);
if (monitor == NULL)
virObjectUnref(vm);
if (virSecurityManagerClearSocketLabel(driver->securityManager, vm->def) < 0) {
if (monitor) {
virObjectUnref(monitor);
monitor = NULL;
}
goto cleanup;
}
cleanup:
virObjectUnref(cfg);
return monitor;
}
int virLXCProcessStop(virLXCDriverPtr driver,
virDomainObjPtr vm,
virDomainShutoffReason reason)
{
int rc;
virLXCDomainObjPrivatePtr priv;
VIR_DEBUG("Stopping VM name=%s pid=%d reason=%d",
vm->def->name, (int)vm->pid, (int)reason);
if (!virDomainObjIsActive(vm)) {
VIR_DEBUG("VM '%s' not active", vm->def->name);
return 0;
}
priv = vm->privateData;
/* If the LXC domain is suspended we send all processes a SIGKILL
* and thaw them. Upon wakeup the process sees the pending signal
* and dies immediately. It is guaranteed that priv->cgroup != NULL
* here because the domain has already been suspended using the
* freezer cgroup.
*/
if (reason == VIR_DOMAIN_SHUTOFF_DESTROYED &&
virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) {
if (virCgroupKillRecursive(priv->cgroup, SIGKILL) <= 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to kill all processes"));
return -1;
}
if (virCgroupSetFreezerState(priv->cgroup, "THAWED") < 0) {
virReportError(VIR_ERR_OPERATION_FAILED, "%s",
_("Unable to thaw all processes"));
return -1;
}
goto cleanup;
}
if (priv->cgroup) {
rc = virCgroupKillPainfully(priv->cgroup);
if (rc < 0)
return -1;
if (rc > 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Some processes refused to die"));
return -1;
}
} else if (vm->pid > 0) {
/* If cgroup doesn't exist, just try cleaning up the
* libvirt_lxc process */
if (virProcessKillPainfully(vm->pid, true) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Processes %d refused to die"), (int)vm->pid);
return -1;
}
}
cleanup:
virLXCProcessCleanup(driver, vm, reason);
return 0;
}
static virCommandPtr
virLXCProcessBuildControllerCmd(virLXCDriverPtr driver,
virDomainObjPtr vm,
char **veths,
int *ttyFDs,
size_t nttyFDs,
int *nsInheritFDs,
int *files,
size_t nfiles,
int handshakefd,
int * const logfd,
const char *pidfile)
{
size_t i;
g_autofree char *filterstr = NULL;
g_autofree char *outputstr = NULL;
virCommandPtr cmd;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
cmd = virCommandNew(vm->def->emulator);
/* The controller may call ip command, so we have to retain PATH. */
virCommandAddEnvPass(cmd, "PATH");
virCommandAddEnvFormat(cmd, "LIBVIRT_DEBUG=%d",
virLogGetDefaultPriority());
if (virLogGetNbFilters() > 0) {
filterstr = virLogGetFilters();
if (!filterstr) {
virReportOOMError();
goto error;
}
virCommandAddEnvPair(cmd, "LIBVIRT_LOG_FILTERS", filterstr);
}
if (cfg->log_libvirtd) {
if (virLogGetNbOutputs() > 0) {
outputstr = virLogGetOutputs();
if (!outputstr) {
virReportOOMError();
goto error;
}
virCommandAddEnvPair(cmd, "LIBVIRT_LOG_OUTPUTS", outputstr);
}
} else {
virCommandAddEnvFormat(cmd,
"LIBVIRT_LOG_OUTPUTS=%d:stderr",
virLogGetDefaultPriority());
}
virCommandAddArgList(cmd, "--name", vm->def->name, NULL);
for (i = 0; i < nttyFDs; i++) {
virCommandAddArg(cmd, "--console");
virCommandAddArgFormat(cmd, "%d", ttyFDs[i]);
virCommandPassFD(cmd, ttyFDs[i], 0);
}
for (i = 0; i < nfiles; i++) {
virCommandAddArg(cmd, "--passfd");
virCommandAddArgFormat(cmd, "%d", files[i]);
virCommandPassFD(cmd, files[i], 0);
}
for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) {
if (nsInheritFDs[i] > 0) {
g_autofree char *tmp = g_strdup_printf("--share-%s",
nsInfoLocal[i]);
virCommandAddArg(cmd, tmp);
virCommandAddArgFormat(cmd, "%d", nsInheritFDs[i]);
virCommandPassFD(cmd, nsInheritFDs[i], 0);
}
}
virCommandAddArgPair(cmd, "--security",
virSecurityManagerGetModel(driver->securityManager));
virCommandAddArg(cmd, "--handshake");
virCommandAddArgFormat(cmd, "%d", handshakefd);
for (i = 0; veths && veths[i]; i++)
virCommandAddArgList(cmd, "--veth", veths[i], NULL);
virCommandPassFD(cmd, handshakefd, 0);
virCommandDaemonize(cmd);
virCommandSetPidFile(cmd, pidfile);
virCommandSetOutputFD(cmd, logfd);
virCommandSetErrorFD(cmd, logfd);
/* So we can pause before exec'ing the controller to
* write the live domain status XML with the PID */
virCommandRequireHandshake(cmd);
cleanup:
virObjectUnref(cfg);
return cmd;
error:
virCommandFree(cmd);
cmd = NULL;
goto cleanup;
}
static bool
virLXCProcessIgnorableLogLine(const char *str)
{
if (virLogProbablyLogMessage(str))
return true;
if (strstr(str, "PATH="))
return true;
if (strstr(str, "error receiving signal from container"))
return true;
if (STREQ(str, ""))
return true;
return false;
}
static int
virLXCProcessReadLogOutputData(virDomainObjPtr vm,
int fd,
char *buf,
size_t buflen)
{
int retries = 10;
int got = 0;
char *filter_next = buf;
buf[0] = '\0';
while (retries) {
ssize_t bytes;
bool isdead = false;
char *eol;
if (vm->pid <= 0 ||
(kill(vm->pid, 0) == -1 && errno == ESRCH))
isdead = true;
/* Any failures should be detected before we read the log, so we
* always have something useful to report on failure. */
bytes = saferead(fd, buf+got, buflen-got-1);
if (bytes < 0) {
virReportSystemError(errno, "%s",
_("Failure while reading log output"));
return -1;
}
got += bytes;
buf[got] = '\0';
/* Filter out debug messages from intermediate libvirt process */
while ((eol = strchr(filter_next, '\n'))) {
*eol = '\0';
if (virLXCProcessIgnorableLogLine(filter_next)) {
memmove(filter_next, eol + 1, got - (eol - buf));
got -= eol + 1 - filter_next;
} else {
filter_next = eol + 1;
*eol = '\n';
}
}
if (got == buflen-1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Out of space while reading log output: %s"),
buf);
return -1;
}
if (isdead)
return got;
g_usleep(100*1000);
retries--;
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Timed out while reading log output: %s"),
buf);
return -1;
}
static int
virLXCProcessReadLogOutput(virDomainObjPtr vm,
char *logfile,
off_t pos,
char *buf,
size_t buflen)
{
int fd = -1;
int ret;
if ((fd = open(logfile, O_RDONLY)) < 0) {
virReportSystemError(errno,
_("Unable to open log file %s"),
logfile);
return -1;
}
if (lseek(fd, pos, SEEK_SET) < 0) {
virReportSystemError(errno,
_("Unable to seek log file %s to %llu"),
logfile, (unsigned long long)pos);
VIR_FORCE_CLOSE(fd);
return -1;
}
ret = virLXCProcessReadLogOutputData(vm,
fd,
buf,
buflen);
VIR_FORCE_CLOSE(fd);
return ret;
}
static int
virLXCProcessEnsureRootFS(virDomainObjPtr vm)
{
virDomainFSDefPtr root = virDomainGetFilesystemForTarget(vm->def, "/");
if (root)
return 0;
if (!(root = virDomainFSDefNew(NULL)))
goto error;
root->type = VIR_DOMAIN_FS_TYPE_MOUNT;
root->src->path = g_strdup("/");
root->dst = g_strdup("/");
if (VIR_INSERT_ELEMENT(vm->def->fss,
0,
vm->def->nfss,
root) < 0)
goto error;
return 0;
error:
virDomainFSDefFree(root);
return -1;
}
/**
* virLXCProcessStart:
* @conn: pointer to connection
* @driver: pointer to driver structure
* @vm: pointer to virtual machine structure
* @autoDestroy: mark the domain for auto destruction
* @reason: reason for switching vm to running state
*
* Starts a vm
*
* Returns 0 on success or -1 in case of error
*/
int virLXCProcessStart(virConnectPtr conn,
virLXCDriverPtr driver,
virDomainObjPtr vm,
unsigned int nfiles, int *files,
bool autoDestroy,
virDomainRunningReason reason)
{
int rc = -1, r;
size_t nttyFDs = 0;
g_autofree int *ttyFDs = NULL;
size_t i;
g_autofree char *logfile = NULL;
int logfd = -1;
VIR_AUTOSTRINGLIST veths = NULL;
int handshakefds[2] = { -1, -1 };
off_t pos = -1;
char ebuf[1024];
g_autofree char *timestamp = NULL;
int nsInheritFDs[VIR_LXC_DOMAIN_NAMESPACE_LAST];
virCommandPtr cmd = NULL;
virLXCDomainObjPrivatePtr priv = vm->privateData;
virCapsPtr caps = NULL;
virErrorPtr err = NULL;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
virCgroupPtr selfcgroup;
int status;
g_autofree char *pidfile = NULL;
if (virCgroupNewSelf(&selfcgroup) < 0)
return -1;
if (!virCgroupHasController(selfcgroup,
VIR_CGROUP_CONTROLLER_CPUACCT)) {
virCgroupFree(&selfcgroup);
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'cpuacct' cgroups controller mount"));
return -1;
}
if (!virCgroupHasController(selfcgroup,
VIR_CGROUP_CONTROLLER_DEVICES)) {
virCgroupFree(&selfcgroup);
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'devices' cgroups controller mount"));
return -1;
}
if (!virCgroupHasController(selfcgroup,
VIR_CGROUP_CONTROLLER_MEMORY)) {
virCgroupFree(&selfcgroup);
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to find 'memory' cgroups controller mount"));
return -1;
}
virCgroupFree(&selfcgroup);
if (vm->def->nconsoles == 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("At least one PTY console is required"));
return -1;
}
for (i = 0; i < vm->def->nconsoles; i++) {
if (vm->def->consoles[i]->source->type != VIR_DOMAIN_CHR_TYPE_PTY) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("Only PTY console types are supported"));
return -1;
}
}
if (virFileMakePath(cfg->logDir) < 0) {
virReportSystemError(errno,
_("Cannot create log directory '%s'"),
cfg->logDir);
return -1;
}
if (!vm->def->resource) {
virDomainResourceDefPtr res = g_new0(virDomainResourceDef, 1);
res->partition = g_strdup("/machine");
vm->def->resource = res;
}
logfile = g_strdup_printf("%s/%s.log", cfg->logDir, vm->def->name);
if (!(pidfile = virPidFileBuildPath(cfg->stateDir, vm->def->name)))
goto cleanup;
if (!(caps = virLXCDriverGetCapabilities(driver, false)))
goto cleanup;
/* Do this up front, so any part of the startup process can add
* runtime state to vm->def that won't be persisted. This let's us
* report implicit runtime defaults in the XML, like vnc listen/socket
*/
VIR_DEBUG("Setting current domain def as transient");
if (virDomainObjSetDefTransient(driver->xmlopt, vm, NULL) < 0)
goto cleanup;
/* Run an early hook to set-up missing devices */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/*
* If the script raised an error abort the launch
*/
if (virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL) < 0)
goto cleanup;
}
if (virLXCProcessEnsureRootFS(vm) < 0)
goto cleanup;
/* Must be run before security labelling */
VIR_DEBUG("Preparing host devices");
if (virLXCPrepareHostDevices(driver, vm->def) < 0)
goto cleanup;
/* Here we open all the PTYs we need on the host OS side.
* The LXC controller will open the guest OS side PTYs
* and forward I/O between them.
*/
nttyFDs = vm->def->nconsoles;
ttyFDs = g_new0(int, nttyFDs);
for (i = 0; i < vm->def->nconsoles; i++)
ttyFDs[i] = -1;
/* If you are using a SecurityDriver with dynamic labelling,
then generate a security label for isolation */
VIR_DEBUG("Generating domain security label (if required)");
if (vm->def->nseclabels &&
vm->def->seclabels[0]->type == VIR_DOMAIN_SECLABEL_DEFAULT)
vm->def->seclabels[0]->type = VIR_DOMAIN_SECLABEL_NONE;
if (virSecurityManagerCheckAllLabel(driver->securityManager, vm->def) < 0)
goto cleanup;
if (virSecurityManagerGenLabel(driver->securityManager, vm->def) < 0) {
virDomainAuditSecurityLabel(vm, false);
goto cleanup;
}
virDomainAuditSecurityLabel(vm, true);
VIR_DEBUG("Setting domain security labels");
if (virSecurityManagerSetAllLabel(driver->securityManager,
vm->def, NULL, false, false) < 0)
goto cleanup;
VIR_DEBUG("Setting up consoles");
for (i = 0; i < vm->def->nconsoles; i++) {
char *ttyPath;
if (virFileOpenTty(&ttyFDs[i], &ttyPath, 1) < 0) {
virReportSystemError(errno, "%s",
_("Failed to allocate tty"));
goto cleanup;
}
g_free(vm->def->consoles[i]->source->data.file.path);
vm->def->consoles[i]->source->data.file.path = ttyPath;
g_free(vm->def->consoles[i]->info.alias);
vm->def->consoles[i]->info.alias = g_strdup_printf("console%zu", i);
}
VIR_DEBUG("Setting up Interfaces");
if (virLXCProcessSetupInterfaces(driver, vm->def, &veths) < 0)
goto cleanup;
VIR_DEBUG("Setting up namespaces if any");
if (virLXCProcessSetupNamespaces(driver, vm->def->namespaceData, nsInheritFDs) < 0)
goto cleanup;
VIR_DEBUG("Preparing to launch");
if ((logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT,
S_IRUSR|S_IWUSR)) < 0) {
virReportSystemError(errno,
_("Failed to open '%s'"),
logfile);
goto cleanup;
}
if (virPipe(handshakefds) < 0)
goto cleanup;
if (!(cmd = virLXCProcessBuildControllerCmd(driver,
vm,
veths,
ttyFDs, nttyFDs,
nsInheritFDs,
files, nfiles,
handshakefds[1],
&logfd,
pidfile)))
goto cleanup;
/* now that we know it is about to start call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/*
* If the script raised an error abort the launch
*/
if (virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_START, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL) < 0)
goto cleanup;
}
/* Log timestamp */
if ((timestamp = virTimeStringNow()) == NULL)
goto cleanup;
if (safewrite(logfd, timestamp, strlen(timestamp)) < 0 ||
safewrite(logfd, START_POSTFIX, strlen(START_POSTFIX)) < 0) {
VIR_WARN("Unable to write timestamp to logfile: %s",
g_strerror(errno));
}
/* Log generated command line */
virCommandWriteArgLog(cmd, logfd);
if ((pos = lseek(logfd, 0, SEEK_END)) < 0)
VIR_WARN("Unable to seek to end of logfile: %s",
g_strerror(errno));
VIR_DEBUG("Launching container");
virCommandRawStatus(cmd);
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
if (status != 0) {
if (virLXCProcessReadLogOutput(vm, logfile, pos, ebuf,
sizeof(ebuf)) <= 0) {
if (WIFEXITED(status))
g_snprintf(ebuf, sizeof(ebuf), _("unexpected exit status %d"),
WEXITSTATUS(status));
else
g_snprintf(ebuf, sizeof(ebuf), "%s", _("terminated abnormally"));
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("guest failed to start: %s"), ebuf);
goto cleanup;
}
/* It has started running, so get its pid */
if ((r = virPidFileReadPath(pidfile, &vm->pid)) < 0) {
if (virLXCProcessReadLogOutput(vm, logfile, pos, ebuf, sizeof(ebuf)) > 0)
virReportError(VIR_ERR_INTERNAL_ERROR,
_("guest failed to start: %s"), ebuf);
else
virReportSystemError(-r,
_("Failed to read pid file %s"),
pidfile);
goto cleanup;
}
priv->stopReason = VIR_DOMAIN_EVENT_STOPPED_FAILED;
priv->wantReboot = false;
vm->def->id = vm->pid;
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason);
priv->doneStopEvent = false;
if (VIR_CLOSE(handshakefds[1]) < 0) {
virReportSystemError(errno, "%s", _("could not close handshake fd"));
goto cleanup;
}
if (virCommandHandshakeWait(cmd) < 0)
goto cleanup;
/* Write domain status to disk for the controller to
* read when it starts */
if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0)
goto cleanup;
/* Allow the child to exec the controller */
if (virCommandHandshakeNotify(cmd) < 0)
goto cleanup;
if (g_atomic_int_add(&driver->nactive, 1) == 0 && driver->inhibitCallback)
driver->inhibitCallback(true, driver->inhibitOpaque);
if (lxcContainerWaitForContinue(handshakefds[0]) < 0) {
char out[1024];
if (!(virLXCProcessReadLogOutput(vm, logfile, pos, out, 1024) < 0)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("guest failed to start: %s"), out);
}
goto cleanup;
}
priv->machineName = virLXCDomainGetMachineName(vm->def, vm->pid);
if (!priv->machineName)
goto cleanup;
/* We know the cgroup must exist by this synchronization
* point so lets detect that first, since it gives us a
* more reliable way to kill everything off if something
* goes wrong from here onwards ... */
if (virCgroupNewDetectMachine(vm->def->name, "lxc",
vm->pid, -1, priv->machineName,
&priv->cgroup) < 0)
goto cleanup;
if (!priv->cgroup) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("No valid cgroup for machine %s"),
vm->def->name);
goto cleanup;
}
/* And we can get the first monitor connection now too */
if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm))) {
/* Intentionally overwrite the real monitor error message,
* since a better one is almost always found in the logs
*/
if (virLXCProcessReadLogOutput(vm, logfile, pos, ebuf, sizeof(ebuf)) > 0) {
virResetLastError();
virReportError(VIR_ERR_INTERNAL_ERROR,
_("guest failed to start: %s"), ebuf);
}
goto cleanup;
}
if (autoDestroy &&
virCloseCallbacksSet(driver->closeCallbacks, vm,
conn, lxcProcessAutoDestroy) < 0)
goto cleanup;
/* We don't need the temporary NIC names anymore, clear them */
virLXCProcessCleanInterfaces(vm->def);
/* finally we can call the 'started' hook script if any */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/*
* If the script raised an error abort the launch
*/
if (virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_STARTED, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL) < 0)
goto cleanup;
}
rc = 0;
cleanup:
if (VIR_CLOSE(logfd) < 0) {
virReportSystemError(errno, "%s", _("could not close logfile"));
rc = -1;
}
if (rc != 0) {
virErrorPreserveLast(&err);
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED);
}
virCommandFree(cmd);
for (i = 0; i < nttyFDs; i++)
VIR_FORCE_CLOSE(ttyFDs[i]);
VIR_FORCE_CLOSE(handshakefds[0]);
VIR_FORCE_CLOSE(handshakefds[1]);
virObjectUnref(cfg);
virObjectUnref(caps);
virErrorRestore(&err);
return rc;
}
struct virLXCProcessAutostartData {
virLXCDriverPtr driver;
virConnectPtr conn;
};
static int
virLXCProcessAutostartDomain(virDomainObjPtr vm,
void *opaque)
{
const struct virLXCProcessAutostartData *data = opaque;
int ret = 0;
virObjectLock(vm);
if (vm->autostart &&
!virDomainObjIsActive(vm)) {
ret = virLXCProcessStart(data->conn, data->driver, vm,
0, NULL, false,
VIR_DOMAIN_RUNNING_BOOTED);
virDomainAuditStart(vm, "booted", ret >= 0);
if (ret < 0) {
VIR_ERROR(_("Failed to autostart VM '%s': %s"),
vm->def->name,
virGetLastErrorMessage());
} else {
virObjectEventPtr event =
virDomainEventLifecycleNewFromObj(vm,
VIR_DOMAIN_EVENT_STARTED,
VIR_DOMAIN_EVENT_STARTED_BOOTED);
virObjectEventStateQueue(data->driver->domainEventState, event);
}
}
virObjectUnlock(vm);
return ret;
}
void
virLXCProcessAutostartAll(virLXCDriverPtr 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("lxc:///system");
/* Ignoring NULL conn which is mostly harmless here */
struct virLXCProcessAutostartData data = { driver, conn };
virDomainObjListForEach(driver->domains, false,
virLXCProcessAutostartDomain,
&data);
virObjectUnref(conn);
}
static void
virLXCProcessReconnectNotifyNets(virDomainDefPtr def)
{
size_t i;
virConnectPtr conn = NULL;
for (i = 0; i < def->nnets; i++) {
virDomainNetDefPtr net = def->nets[i];
/* keep others from trying to use the macvtap device name, but
* don't return error if this happens, since that causes the
* domain to be unceremoniously killed, which would be *very*
* impolite.
*/
if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_DIRECT)
ignore_value(virNetDevMacVLanReserveName(net->ifname, false));
if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
if (!conn && !(conn = virGetConnectNetwork()))
continue;
virDomainNetNotifyActualDevice(conn, def, net);
}
}
virObjectUnref(conn);
}
static int
virLXCProcessReconnectDomain(virDomainObjPtr vm,
void *opaque)
{
virLXCDriverPtr driver = opaque;
virLXCDomainObjPrivatePtr priv;
virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver);
int ret = -1;
virObjectLock(vm);
VIR_DEBUG("Reconnect id=%d pid=%d state=%d", vm->def->id, vm->pid, vm->state.state);
priv = vm->privateData;
if (vm->pid != 0) {
vm->def->id = vm->pid;
virDomainObjSetState(vm, VIR_DOMAIN_RUNNING,
VIR_DOMAIN_RUNNING_UNKNOWN);
if (g_atomic_int_add(&driver->nactive, 1) == 0 && driver->inhibitCallback)
driver->inhibitCallback(true, driver->inhibitOpaque);
if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm)))
goto error;
priv->machineName = virLXCDomainGetMachineName(vm->def, vm->pid);
if (!priv->machineName)
goto cleanup;
if (virCgroupNewDetectMachine(vm->def->name, "lxc", vm->pid, -1,
priv->machineName, &priv->cgroup) < 0)
goto error;
if (!priv->cgroup) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("No valid cgroup for machine %s"),
vm->def->name);
goto error;
}
if (virLXCUpdateActiveUSBHostdevs(driver, vm->def) < 0)
goto error;
if (virSecurityManagerReserveLabel(driver->securityManager,
vm->def, vm->pid) < 0)
goto error;
virLXCProcessReconnectNotifyNets(vm->def);
if (virDomainObjSave(vm, driver->xmlopt, cfg->stateDir) < 0)
VIR_WARN("Cannot update XML for running LXC guest %s", vm->def->name);
/* now that we know it's reconnected call the hook if present */
if (virHookPresent(VIR_HOOK_DRIVER_LXC)) {
g_autofree char *xml = virDomainDefFormat(vm->def, driver->xmlopt, 0);
/* we can't stop the operation even if the script raised an error */
if (virHookCall(VIR_HOOK_DRIVER_LXC, vm->def->name,
VIR_HOOK_LXC_OP_RECONNECT, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL) < 0)
goto error;
}
} else {
vm->def->id = -1;
}
ret = 0;
cleanup:
virObjectUnref(cfg);
virObjectUnlock(vm);
return ret;
error:
virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED);
virDomainAuditStop(vm, "failed");
goto cleanup;
}
int virLXCProcessReconnectAll(virLXCDriverPtr driver,
virDomainObjListPtr doms)
{
virDomainObjListForEach(doms, false, virLXCProcessReconnectDomain, driver);
return 0;
}