libvirt/src/xen/xen_inotify.c
Matthias Bolte 6ed7374c5a Fix xen driver refcounting.
The commit cb51aa48a7 "Fix up connection
reference counting." changed the driver closing and virConnectPtr
unref-logic in virConnectClose().

Before this commit virConnectClose() closed all drivers of the given
virConnectPtr and virUnrefConnect()'ed it afterwards. After this
commit the driver-closing is done in virUnrefConnect() if and only if
the ref-count of the virConnectPtr dropped to zero.

This change in execution order leads to a virConnectPtr leak, at least
for connections to Xen.

The relevant call sequences:

virConnectOpen() -> xenUnifiedOpen() ...

... xenInotifyOpen() -> virConnectRef(conn)

... xenStoreOpen() -> xenStoreAddWatch() -> conn->refs++

virConnectClose() -> xenUnifiedClose() ...

... xenInotifyClose() -> virUnrefConnect(conn)

... xenStoreClose() -> xenStoreRemoveWatch() -> virUnrefConnect(conn)

Before the commit this additional virConnectRef/virUnrefConnect calls
where no problem, because virConnectClose() closed the drivers
explicitly and the additional refs added by the Xen subdrivers were
removed properly. After the commit this additional refs result in a
virConnectPtr leak (including a leak of the hypercall file handle;
that's how I noticed this problem), because now the drivers are only
close if and only if the ref-count drops to zero, but this cannot
happen anymore, because the additional refs from the Xen subdrivers
would only be removed if the drivers get closed, but that doesn't
happen because the ref-count cannot drop to zero.

The fix for this problem is simple: remove the
virConnectRef/virUnrefConnect calls from the Xen subdrivers (see
attached patch). Maybe someone could explain why the Xen Inotify and
Xen Store driver do this extra ref-counting, but none of the other Xen
subdrivers. It seems unnecessary to me and can be removed without
problems.

Signed-off-by: Chris Lalancette <clalance@redhat.com>
2009-09-22 15:12:48 +02:00

491 lines
16 KiB
C

/*
* xen_inofify.c: Xen notification of xml file activity in the
* following dirs:
* /etc/xen
* /var/lib/xend/domains
*
* Copyright (C) 2008 VirtualIron
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: Ben Guthro
*/
#include <config.h>
#include <dirent.h>
#include <sys/inotify.h>
#include "virterror_internal.h"
#include "datatypes.h"
#include "driver.h"
#include "memory.h"
#include "event.h"
#include "xen_driver.h"
#include "conf.h"
#include "domain_conf.h"
#include "xen_inotify.h"
#include "xend_internal.h"
#include "logging.h"
#include "uuid.h"
#include "xm_internal.h" /* for xenXMDomainConfigParse */
#define VIR_FROM_THIS VIR_FROM_XEN_INOTIFY
#define virXenInotifyError(conn, code, fmt...) \
virReportErrorHelper(NULL, VIR_FROM_XEN_INOTIFY, code, __FILE__, \
__FUNCTION__, __LINE__, fmt)
#define LIBVIRTD_DOMAINS_DIR "/var/lib/xend/domains"
struct xenUnifiedDriver xenInotifyDriver = {
xenInotifyOpen, /* open */
xenInotifyClose, /* close */
NULL, /* version */
NULL, /* hostname */
NULL, /* nodeGetInfo */
NULL, /* getCapabilities */
NULL, /* listDomains */
NULL, /* numOfDomains */
NULL, /* domainCreateLinux */
NULL, /* domainSuspend */
NULL, /* domainResume */
NULL, /* domainShutdown */
NULL, /* domainReboot */
NULL, /* domainDestroy */
NULL, /* domainGetOSType */
NULL, /* domainGetMaxMemory */
NULL, /* domainSetMaxMemory */
NULL, /* domainSetMemory */
NULL, /* domainGetInfo */
NULL, /* domainSave */
NULL, /* domainRestore */
NULL, /* domainCoreDump */
NULL, /* domainSetVcpus */
NULL, /* domainPinVcpu */
NULL, /* domainGetVcpus */
NULL, /* domainGetMaxVcpus */
NULL, /* listDefinedDomains */
NULL, /* numOfDefinedDomains */
NULL, /* domainCreate */
NULL, /* domainDefineXML */
NULL, /* domainUndefine */
NULL, /* domainAttachDevice */
NULL, /* domainDetachDevice */
NULL, /* domainGetAutostart */
NULL, /* domainSetAutostart */
NULL, /* domainGetSchedulerType */
NULL, /* domainGetSchedulerParameters */
NULL, /* domainSetSchedulerParameters */
};
static int
xenInotifyXenCacheLookup(virConnectPtr conn,
const char *filename,
char **name, unsigned char *uuid) {
xenUnifiedPrivatePtr priv = conn->privateData;
xenXMConfCachePtr entry;
if (!(entry = virHashLookup(priv->configCache, filename))) {
DEBUG("No config found for %s", filename);
return -1;
}
*name = strdup(entry->def->name);
memcpy(uuid, entry->def->uuid, VIR_UUID_BUFLEN);
if (!*name) {
DEBUG0("Error getting dom from def");
return -1;
}
return 0;
}
static int
xenInotifyXendDomainsDirLookup(virConnectPtr conn, const char *filename,
char **name, unsigned char *uuid) {
int i;
virDomainPtr dom;
const char *uuid_str;
unsigned char rawuuid[VIR_UUID_BUFLEN];
xenUnifiedPrivatePtr priv = conn->privateData;
/* xend is managing domains. we will get
* a filename in the manner:
* /var/lib/xend/domains/<uuid>/
*/
uuid_str = filename + strlen(LIBVIRTD_DOMAINS_DIR) + 1;
if (virUUIDParse(uuid_str, rawuuid) < 0) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
_("parsing uuid %s"), uuid_str);
return -1;
}
/* call directly into xend here, as driver may not yet
be set during open while we are building our
initial list of domains */
DEBUG("Looking for dom with uuid: %s", uuid_str);
/* XXX Should not have to go via a virDomainPtr obj instance */
if(!(dom = xenDaemonLookupByUUID(conn, rawuuid))) {
/* If we are here, the domain has gone away.
search for, and create a domain from the stored
list info */
for (i = 0 ; i < priv->configInfoList->count ; i++) {
if (!memcmp(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN)) {
*name = strdup(priv->configInfoList->doms[i]->name);
if (!*name) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
_("finding dom for %s"), uuid_str);
return -1;
}
memcpy(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN);
DEBUG0("Found dom on list");
return 0;
}
}
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("finding dom on config list"));
return -1;
}
if (!(*name = strdup(dom->name)))
return -1;
memcpy(uuid, dom->uuid, VIR_UUID_BUFLEN);
virDomainFree(dom);
/* succeeded too find domain by uuid */
return 0;
}
static int
xenInotifyDomainLookup(virConnectPtr conn,
const char *filename,
char **name, unsigned char *uuid) {
xenUnifiedPrivatePtr priv = conn->privateData;
if (priv->useXenConfigCache)
return xenInotifyXenCacheLookup(conn, filename, name, uuid);
else
return xenInotifyXendDomainsDirLookup(conn, filename, name, uuid);
}
static virDomainEventPtr
xenInotifyDomainEventFromFile(virConnectPtr conn,
const char *filename,
int type, int detail) {
virDomainEventPtr event;
char *name = NULL;
unsigned char uuid[VIR_UUID_BUFLEN];
if (xenInotifyDomainLookup(conn, filename, &name, uuid) < 0)
return NULL;
event = virDomainEventNew(-1, name, uuid, type, detail);
VIR_FREE(name);
return event;
}
static int
xenInotifyXendDomainsDirRemoveEntry(virConnectPtr conn,
const char *fname) {
xenUnifiedPrivatePtr priv = conn->privateData;
const char *uuidstr = fname + strlen(LIBVIRTD_DOMAINS_DIR) + 1;
unsigned char uuid[VIR_UUID_BUFLEN];
int i;
if (virUUIDParse(uuidstr, uuid) < 0) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
_("parsing uuid %s"), uuidstr);
return -1;
}
/* match and remove on uuid */
for (i = 0 ; i < priv->configInfoList->count ; i++) {
if (!memcmp(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN)) {
VIR_FREE(priv->configInfoList->doms[i]->name);
VIR_FREE(priv->configInfoList->doms[i]);
if (i < (priv->configInfoList->count - 1))
memmove(priv->configInfoList->doms + i,
priv->configInfoList->doms + i + 1,
sizeof(*(priv->configInfoList->doms)) *
(priv->configInfoList->count - (i + 1)));
if (VIR_REALLOC_N(priv->configInfoList->doms,
priv->configInfoList->count - 1) < 0) {
; /* Failure to reduce memory allocation isn't fatal */
}
priv->configInfoList->count--;
return 0;
}
}
return -1;
}
static int
xenInotifyXendDomainsDirAddEntry(virConnectPtr conn,
const char *fname) {
char *name = NULL;
unsigned char uuid[VIR_UUID_BUFLEN];
xenUnifiedPrivatePtr priv = conn->privateData;
if (xenInotifyDomainLookup(conn, fname, &name, uuid) < 0) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("Error looking up domain"));
return -1;
}
if (xenUnifiedAddDomainInfo(priv->configInfoList,
-1, name, uuid) < 0) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("Error adding file to config cache"));
VIR_FREE(name);
return -1;
}
VIR_FREE(name);
return 0;
}
static int
xenInotifyRemoveDomainConfigInfo(virConnectPtr conn,
const char *fname) {
xenUnifiedPrivatePtr priv = conn->privateData;
return priv->useXenConfigCache ?
xenXMConfigCacheRemoveFile(conn, fname) :
xenInotifyXendDomainsDirRemoveEntry(conn, fname);
}
static int
xenInotifyAddDomainConfigInfo(virConnectPtr conn,
const char *fname) {
xenUnifiedPrivatePtr priv = conn->privateData;
return priv->useXenConfigCache ?
xenXMConfigCacheAddFile(conn, fname) :
xenInotifyXendDomainsDirAddEntry(conn, fname);
}
static void
xenInotifyEvent(int watch ATTRIBUTE_UNUSED,
int fd,
int events ATTRIBUTE_UNUSED,
void *data)
{
char buf[1024];
char fname[1024];
struct inotify_event *e;
int got;
char *tmp, *name;
virConnectPtr conn = data;
xenUnifiedPrivatePtr priv = NULL;
DEBUG0("got inotify event");
if( conn && conn->privateData ) {
priv = conn->privateData;
} else {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("conn, or private data is NULL"));
return;
}
xenUnifiedLock(priv);
reread:
got = read(fd, buf, sizeof(buf));
if (got == -1) {
if (errno == EINTR)
goto reread;
goto cleanup;
}
tmp = buf;
while (got) {
if (got < sizeof(struct inotify_event))
goto cleanup; /* bad */
e = (struct inotify_event *)tmp;
tmp += sizeof(struct inotify_event);
got -= sizeof(struct inotify_event);
if (got < e->len)
goto cleanup;
tmp += e->len;
got -= e->len;
name = (char *)&(e->name);
snprintf(fname, 1024, "%s/%s",
priv->configDir, name);
if (e->mask & (IN_DELETE | IN_MOVED_FROM)) {
virDomainEventPtr event =
xenInotifyDomainEventFromFile(conn, fname,
VIR_DOMAIN_EVENT_UNDEFINED,
VIR_DOMAIN_EVENT_UNDEFINED_REMOVED);
if (!event)
xenUnifiedDomainEventDispatch(conn->privateData, event);
else
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("looking up dom"));
if (xenInotifyRemoveDomainConfigInfo(conn, fname) < 0 ) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("Error adding file to config cache"));
goto cleanup;
}
} else if (e->mask & ( IN_CREATE | IN_CLOSE_WRITE | IN_MOVED_TO) ) {
virDomainEventPtr event;
if (xenInotifyAddDomainConfigInfo(conn, fname) < 0 ) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("Error adding file to config cache"));
goto cleanup;
}
event = xenInotifyDomainEventFromFile(conn, fname,
VIR_DOMAIN_EVENT_DEFINED,
VIR_DOMAIN_EVENT_DEFINED_ADDED);
if (event)
xenUnifiedDomainEventDispatch(conn->privateData, event);
else
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("looking up dom"));
}
}
cleanup:
xenUnifiedUnlock(priv);
}
/**
* xenInotifyOpen:
* @conn: pointer to the connection block
* @name: URL for the target, NULL for local
* @flags: combination of virDrvOpenFlag(s)
*
* Connects and starts listening for inotify events
*
* Returns 0 or -1 in case of error.
*/
virDrvOpenStatus
xenInotifyOpen(virConnectPtr conn ATTRIBUTE_UNUSED,
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
int flags ATTRIBUTE_UNUSED)
{
DIR *dh;
struct dirent *ent;
char path[PATH_MAX];
xenUnifiedPrivatePtr priv = (xenUnifiedPrivatePtr) conn->privateData;
if (priv->configDir) {
priv->useXenConfigCache = 1;
} else {
/* /var/lib/xend/domains/<uuid>/config.sxp */
priv->configDir = LIBVIRTD_DOMAINS_DIR;
priv->useXenConfigCache = 0;
if (VIR_ALLOC(priv->configInfoList) < 0) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("failed to allocate configInfoList"));
return -1;
}
/* populate initial list */
if (!(dh = opendir(priv->configDir))) {
virReportSystemError(NULL, errno,
_("cannot open directory: %s"),
priv->configDir);
return -1;
}
while ((ent = readdir(dh))) {
if (STRPREFIX(ent->d_name, "."))
continue;
/* Build the full file path */
if ((strlen(priv->configDir) + 1 +
strlen(ent->d_name) + 1) > PATH_MAX)
continue;
strcpy(path, priv->configDir);
strcat(path, "/");
strcat(path, ent->d_name);
if (xenInotifyAddDomainConfigInfo(conn, path) < 0 ) {
virXenInotifyError(NULL, VIR_ERR_INTERNAL_ERROR,
"%s", _("Error adding file to config list"));
closedir(dh);
return -1;
}
}
closedir(dh);
}
if ((priv->inotifyFD = inotify_init()) < 0) {
virReportSystemError(NULL, errno,
"%s", _("initializing inotify"));
return -1;
}
DEBUG("Adding a watch on %s", priv->configDir);
if (inotify_add_watch(priv->inotifyFD,
priv->configDir,
IN_CREATE |
IN_CLOSE_WRITE | IN_DELETE |
IN_MOVED_TO | IN_MOVED_FROM) < 0) {
virReportSystemError(NULL, errno,
_("adding watch on %s"),
priv->configDir);
return -1;
}
DEBUG0("Building initial config cache");
if (priv->useXenConfigCache &&
xenXMConfigCacheRefresh (conn) < 0) {
DEBUG("Failed to enable XM config cache %s", conn->err.message);
return -1;
}
DEBUG0("Registering with event loop");
/* Add the handle for monitoring */
if ((priv->inotifyWatch = virEventAddHandle(priv->inotifyFD, VIR_EVENT_HANDLE_READABLE,
xenInotifyEvent, conn, NULL)) < 0) {
DEBUG0("Failed to add inotify handle, disabling events");
}
return 0;
}
/**
* xenInotifyClose:
* @conn: pointer to the connection block
*
* Close and stop listening for inotify events
*
* Returns 0 in case of success or -1 in case of error.
*/
int
xenInotifyClose(virConnectPtr conn)
{
xenUnifiedPrivatePtr priv = conn->privateData;
if (priv->configInfoList)
xenUnifiedDomainInfoListFree(priv->configInfoList);
if (priv->inotifyWatch != -1)
virEventRemoveHandle(priv->inotifyWatch);
close(priv->inotifyFD);
return 0;
}