libvirt/src/xen/xen_inotify.c
Daniel P. Berrange 427067f7ed xen: fix race in refresh of config cache
The xenXMConfigCacheRefresh method scans /etc/xen and loads
all config files it finds. It then scans its internal hash
table and purges any (previously) loaded config files whose
refresh timestamp does not match the timestamp recorded at
the start of xenXMConfigCacheRefresh(). There is unfortunately
a subtle flaw in this, because if loading the config files
takes longer than 1 second, some of the config files will
have a refresh timestamp that is 1 or more seconds different
(newer) than is checked for. So we immediately purge a bunch
of valid config files we just loaded.

To avoid this flaw, we must pass the timestamp we record at
the start of xenXMConfigCacheRefresh() into the
xenXMConfigCacheAddFile() method, instead of letting the
latter call time(NULL) again.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2015-09-11 17:25:29 +01:00

451 lines
13 KiB
C

/*
* xen_inotify.c: Xen notification of xml file activity in the
* following dirs:
* /etc/xen
* /var/lib/xend/domains
*
* Copyright (C) 2010-2014 Red Hat, Inc.
* 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, see
* <http://www.gnu.org/licenses/>.
*
* Author: Ben Guthro
*/
#include <config.h>
#include <dirent.h>
#include <sys/inotify.h>
#include "virerror.h"
#include "datatypes.h"
#include "driver.h"
#include "viralloc.h"
#include "xen_driver.h"
#include "virconf.h"
#include "domain_conf.h"
#include "xen_inotify.h"
#include "xend_internal.h"
#include "virlog.h"
#include "viruuid.h"
#include "virfile.h"
#include "virstring.h"
#include "xm_internal.h" /* for xenXMDomainConfigParse */
#define VIR_FROM_THIS VIR_FROM_XEN_INOTIFY
VIR_LOG_INIT("xen.xen_inotify");
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))) {
VIR_DEBUG("No config found for %s", filename);
return -1;
}
memcpy(uuid, entry->def->uuid, VIR_UUID_BUFLEN);
if (VIR_STRDUP(*name, entry->def->name) < 0) {
VIR_DEBUG("Error getting dom from def");
return -1;
}
return 0;
}
static int
xenInotifyXendDomainsDirLookup(virConnectPtr conn,
const char *filename,
char **name,
unsigned char *uuid)
{
size_t i;
virDomainDefPtr def;
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(XEND_DOMAINS_DIR) + 1;
if (virUUIDParse(uuid_str, rawuuid) < 0) {
virReportError(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 */
VIR_DEBUG("Looking for dom with uuid: %s", uuid_str);
if (!(def = 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(rawuuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN)) {
if (VIR_STRDUP(*name, priv->configInfoList->doms[i]->name) < 0)
return -1;
memcpy(uuid, priv->configInfoList->doms[i]->uuid, VIR_UUID_BUFLEN);
VIR_DEBUG("Found dom on list");
return 0;
}
}
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("finding dom on config list"));
return -1;
}
if (VIR_STRDUP(*name, def->name) < 0) {
virDomainDefFree(def);
return -1;
}
memcpy(uuid, def->uuid, VIR_UUID_BUFLEN);
virDomainDefFree(def);
/* 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 virObjectEventPtr
xenInotifyDomainEventFromFile(virConnectPtr conn,
const char *filename,
int type,
int detail)
{
virObjectEventPtr event;
char *name = NULL;
unsigned char uuid[VIR_UUID_BUFLEN];
if (xenInotifyDomainLookup(conn, filename, &name, uuid) < 0)
return NULL;
event = virDomainEventLifecycleNew(-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(XEND_DOMAINS_DIR) + 1;
unsigned char uuid[VIR_UUID_BUFLEN];
size_t i;
if (virUUIDParse(uuidstr, uuid) < 0) {
virReportError(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]);
VIR_DELETE_ELEMENT(priv->configInfoList->doms, i,
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) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Error looking up domain"));
return -1;
}
if (xenUnifiedAddDomainInfo(priv->configInfoList,
-1, name, uuid) < 0) {
virReportError(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, time_t now)
{
xenUnifiedPrivatePtr priv = conn->privateData;
return priv->useXenConfigCache ?
xenXMConfigCacheAddFile(conn, fname, now) :
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;
time_t now = time(NULL);
VIR_DEBUG("got inotify event");
if (conn && conn->privateData) {
priv = conn->privateData;
} else {
virReportError(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 */
VIR_WARNINGS_NO_CAST_ALIGN
e = (struct inotify_event *)tmp;
VIR_WARNINGS_RESET
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)) {
virObjectEventPtr event =
xenInotifyDomainEventFromFile(conn, fname,
VIR_DOMAIN_EVENT_UNDEFINED,
VIR_DOMAIN_EVENT_UNDEFINED_REMOVED);
if (event)
xenUnifiedDomainEventDispatch(conn->privateData, event);
else
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("looking up dom"));
if (xenInotifyRemoveDomainConfigInfo(conn, fname) < 0) {
virReportError(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)) {
virObjectEventPtr event;
if (xenInotifyAddDomainConfigInfo(conn, fname, now) < 0) {
virReportError(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
virReportError(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.
*/
int
xenInotifyOpen(virConnectPtr conn,
virConnectAuthPtr auth ATTRIBUTE_UNUSED,
unsigned int flags)
{
DIR *dh;
struct dirent *ent;
char *path;
xenUnifiedPrivatePtr priv = conn->privateData;
int direrr;
time_t now = time(NULL);
virCheckFlags(VIR_CONNECT_RO, -1);
if (priv->configDir) {
priv->useXenConfigCache = 1;
} else {
/* /var/lib/xend/domains/<uuid>/config.sxp */
priv->configDir = XEND_DOMAINS_DIR;
priv->useXenConfigCache = 0;
if (VIR_ALLOC(priv->configInfoList) < 0)
return -1;
/* populate initial list */
if (!(dh = opendir(priv->configDir))) {
virReportSystemError(errno,
_("cannot open directory: %s"),
priv->configDir);
return -1;
}
while ((direrr = virDirRead(dh, &ent, priv->configDir)) > 0) {
if (STRPREFIX(ent->d_name, "."))
continue;
/* Build the full file path */
if (!(path = virFileBuildPath(priv->configDir, ent->d_name, NULL))) {
closedir(dh);
return -1;
}
if (xenInotifyAddDomainConfigInfo(conn, path, now) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("Error adding file to config list"));
closedir(dh);
VIR_FREE(path);
return -1;
}
VIR_FREE(path);
}
closedir(dh);
if (direrr < 0)
return -1;
}
if ((priv->inotifyFD = inotify_init()) < 0) {
virReportSystemError(errno,
"%s", _("initializing inotify"));
return -1;
}
VIR_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(errno,
_("adding watch on %s"),
priv->configDir);
return -1;
}
VIR_DEBUG("Building initial config cache");
if (priv->useXenConfigCache &&
xenXMConfigCacheRefresh(conn) < 0) {
VIR_DEBUG("Failed to enable XM config cache %s", conn->err.message);
return -1;
}
VIR_DEBUG("Registering with event loop");
/* Add the handle for monitoring */
if ((priv->inotifyWatch = virEventAddHandle(priv->inotifyFD, VIR_EVENT_HANDLE_READABLE,
xenInotifyEvent, conn, NULL)) < 0) {
VIR_DEBUG("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);
VIR_FORCE_CLOSE(priv->inotifyFD);
return 0;
}