libvirt/src/util/virlockspace.c
Peter Krempa 690efb960f Switch away from virHashFree
Use 'g_clear_pointer(&ptr, g_hash_table_unref)' instead.

In few instances it allows us to also remove explicit clearing of
pointers.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Martin Kletzander <mkletzan@redhat.com>
2021-12-01 13:53:12 +01:00

719 lines
20 KiB
C

/*
* virlockspace.c: simple file based lockspaces
*
* Copyright (C) 2012 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
* <http://www.gnu.org/licenses/>.
*
*/
#include <config.h>
#include "virlockspace.h"
#include "virlog.h"
#include "viralloc.h"
#include "virerror.h"
#include "virutil.h"
#include "virfile.h"
#include "virhash.h"
#include "virthread.h"
#include "virstring.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define VIR_FROM_THIS VIR_FROM_LOCKSPACE
VIR_LOG_INIT("util.lockspace");
#define VIR_LOCKSPACE_TABLE_SIZE 10
typedef struct _virLockSpaceResource virLockSpaceResource;
struct _virLockSpaceResource {
char *name;
char *path;
int fd;
bool lockHeld;
unsigned int flags;
size_t nOwners;
pid_t *owners;
};
struct _virLockSpace {
char *dir;
virMutex lock;
GHashTable *resources;
};
static char *virLockSpaceGetResourcePath(virLockSpace *lockspace,
const char *resname)
{
char *ret;
if (lockspace->dir)
ret = g_strdup_printf("%s/%s", lockspace->dir, resname);
else
ret = g_strdup(resname);
return ret;
}
static void virLockSpaceResourceFree(virLockSpaceResource *res)
{
if (!res)
return;
if (res->lockHeld &&
(res->flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE)) {
if (res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) {
/* We must upgrade to an exclusive lock to ensure
* no one else still has it before trying to delete */
if (virFileLock(res->fd, false, 0, 1, false) < 0) {
VIR_DEBUG("Could not upgrade shared lease to exclusive, not deleting");
} else {
if (unlink(res->path) < 0 &&
errno != ENOENT) {
VIR_WARN("Failed to unlink resource %s: %s",
res->path, g_strerror(errno));
}
}
} else {
if (unlink(res->path) < 0 &&
errno != ENOENT) {
VIR_WARN("Failed to unlink resource %s: %s",
res->path, g_strerror(errno));
}
}
}
g_free(res->owners);
VIR_FORCE_CLOSE(res->fd);
g_free(res->path);
g_free(res->name);
g_free(res);
}
static virLockSpaceResource *
virLockSpaceResourceNew(virLockSpace *lockspace,
const char *resname,
unsigned int flags,
pid_t owner)
{
virLockSpaceResource *res;
bool shared = !!(flags & VIR_LOCK_SPACE_ACQUIRE_SHARED);
res = g_new0(virLockSpaceResource, 1);
res->fd = -1;
res->flags = flags;
res->name = g_strdup(resname);
if (!(res->path = virLockSpaceGetResourcePath(lockspace, resname)))
goto error;
if (flags & VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE) {
while (1) {
struct stat a, b;
if ((res->fd = open(res->path, O_RDWR|O_CREAT, 0600)) < 0) {
virReportSystemError(errno,
_("Unable to open/create resource %s"),
res->path);
goto error;
}
if (virSetCloseExec(res->fd) < 0) {
virReportSystemError(errno,
_("Failed to set close-on-exec flag '%s'"),
res->path);
goto error;
}
if (fstat(res->fd, &b) < 0) {
virReportSystemError(errno,
_("Unable to check status of pid file '%s'"),
res->path);
goto error;
}
if (virFileLock(res->fd, shared, 0, 1, false) < 0) {
if (errno == EACCES || errno == EAGAIN) {
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is locked"),
resname);
} else {
virReportSystemError(errno,
_("Unable to acquire lock on '%s'"),
res->path);
}
goto error;
}
/* Now make sure the pidfile we locked is the same
* one that now exists on the filesystem
*/
if (stat(res->path, &a) < 0) {
VIR_DEBUG("Resource '%s' disappeared: %s",
res->path, g_strerror(errno));
VIR_FORCE_CLOSE(res->fd);
/* Someone else must be racing with us, so try again */
continue;
}
if (a.st_ino == b.st_ino)
break;
VIR_DEBUG("Resource '%s' was recreated", res->path);
VIR_FORCE_CLOSE(res->fd);
/* Someone else must be racing with us, so try again */
}
} else {
if ((res->fd = open(res->path, O_RDWR)) < 0) {
virReportSystemError(errno,
_("Unable to open resource %s"),
res->path);
goto error;
}
if (virSetCloseExec(res->fd) < 0) {
virReportSystemError(errno,
_("Failed to set close-on-exec flag '%s'"),
res->path);
goto error;
}
if (virFileLock(res->fd, shared, 0, 1, false) < 0) {
if (errno == EACCES || errno == EAGAIN) {
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is locked"),
resname);
} else {
virReportSystemError(errno,
_("Unable to acquire lock on '%s'"),
res->path);
}
goto error;
}
}
res->lockHeld = true;
VIR_EXPAND_N(res->owners, res->nOwners, 1);
res->owners[res->nOwners-1] = owner;
return res;
error:
virLockSpaceResourceFree(res);
return NULL;
}
static void virLockSpaceResourceDataFree(void *opaque)
{
virLockSpaceResource *res = opaque;
virLockSpaceResourceFree(res);
}
virLockSpace *virLockSpaceNew(const char *directory)
{
virLockSpace *lockspace;
VIR_DEBUG("directory=%s", NULLSTR(directory));
lockspace = g_new0(virLockSpace, 1);
if (virMutexInit(&lockspace->lock) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to initialize lockspace mutex"));
VIR_FREE(lockspace);
return NULL;
}
lockspace->dir = g_strdup(directory);
lockspace->resources = virHashNew(virLockSpaceResourceDataFree);
if (directory) {
if (virFileExists(directory)) {
if (!virFileIsDir(directory)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Lockspace location %s exists, but is not a directory"),
directory);
goto error;
}
} else {
if (g_mkdir_with_parents(directory, 0700) < 0) {
virReportSystemError(errno,
_("Unable to create lockspace %s"),
directory);
goto error;
}
}
}
return lockspace;
error:
virLockSpaceFree(lockspace);
return NULL;
}
virLockSpace *virLockSpaceNewPostExecRestart(virJSONValue *object)
{
virLockSpace *lockspace;
virJSONValue *resources;
size_t i;
VIR_DEBUG("object=%p", object);
lockspace = g_new0(virLockSpace, 1);
if (virMutexInit(&lockspace->lock) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Unable to initialize lockspace mutex"));
VIR_FREE(lockspace);
return NULL;
}
lockspace->resources = virHashNew(virLockSpaceResourceDataFree);
if (virJSONValueObjectHasKey(object, "directory")) {
const char *dir = virJSONValueObjectGetString(object, "directory");
lockspace->dir = g_strdup(dir);
}
if (!(resources = virJSONValueObjectGet(object, "resources"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resources value in JSON document"));
goto error;
}
if (!virJSONValueIsArray(resources)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed resources array"));
goto error;
}
for (i = 0; i < virJSONValueArraySize(resources); i++) {
virJSONValue *child = virJSONValueArrayGet(resources, i);
virLockSpaceResource *res;
const char *tmp;
virJSONValue *owners;
size_t j;
res = g_new0(virLockSpaceResource, 1);
res->fd = -1;
if (!(tmp = virJSONValueObjectGetString(child, "name"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource name in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
res->name = g_strdup(tmp);
if (!(tmp = virJSONValueObjectGetString(child, "path"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource path in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
res->path = g_strdup(tmp);
if (virJSONValueObjectGetNumberInt(child, "fd", &res->fd) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource fd in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
if (virSetInherit(res->fd, false) < 0) {
virReportSystemError(errno, "%s",
_("Cannot enable close-on-exec flag"));
virLockSpaceResourceFree(res);
goto error;
}
if (virJSONValueObjectGetBoolean(child, "lockHeld", &res->lockHeld) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource lockHeld in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
if (virJSONValueObjectGetNumberUint(child, "flags", &res->flags) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource flags in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
if (!(owners = virJSONValueObjectGet(child, "owners"))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Missing resource owners in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
if (!virJSONValueIsArray(owners)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed owners array"));
virLockSpaceResourceFree(res);
goto error;
}
res->nOwners = virJSONValueArraySize(owners);
res->owners = g_new0(pid_t, res->nOwners);
for (j = 0; j < res->nOwners; j++) {
unsigned long long int owner;
virJSONValue *ownerval = virJSONValueArrayGet(owners, j);
if (virJSONValueGetNumberUlong(ownerval, &owner) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Malformed owner value in JSON document"));
virLockSpaceResourceFree(res);
goto error;
}
res->owners[j] = (pid_t)owner;
}
if (virHashAddEntry(lockspace->resources, res->name, res) < 0) {
virLockSpaceResourceFree(res);
goto error;
}
}
return lockspace;
error:
virLockSpaceFree(lockspace);
return NULL;
}
virJSONValue *virLockSpacePreExecRestart(virLockSpace *lockspace)
{
g_autoptr(virJSONValue) object = virJSONValueNewObject();
g_autoptr(virJSONValue) resources = virJSONValueNewArray();
g_autofree virHashKeyValuePair *pairs = NULL;
virHashKeyValuePair *tmp;
virMutexLock(&lockspace->lock);
if (lockspace->dir &&
virJSONValueObjectAppendString(object, "directory", lockspace->dir) < 0)
goto error;
tmp = pairs = virHashGetItems(lockspace->resources, NULL, false);
while (tmp && tmp->value) {
virLockSpaceResource *res = (virLockSpaceResource *)tmp->value;
g_autoptr(virJSONValue) child = virJSONValueNewObject();
g_autoptr(virJSONValue) owners = virJSONValueNewArray();
size_t i;
if (virJSONValueObjectAppendString(child, "name", res->name) < 0 ||
virJSONValueObjectAppendString(child, "path", res->path) < 0 ||
virJSONValueObjectAppendNumberInt(child, "fd", res->fd) < 0 ||
virJSONValueObjectAppendBoolean(child, "lockHeld", res->lockHeld) < 0 ||
virJSONValueObjectAppendNumberUint(child, "flags", res->flags) < 0)
goto error;
if (virSetInherit(res->fd, true) < 0) {
virReportSystemError(errno, "%s",
_("Cannot disable close-on-exec flag"));
goto error;
}
for (i = 0; i < res->nOwners; i++) {
g_autoptr(virJSONValue) owner = virJSONValueNewNumberUlong(res->owners[i]);
if (!owner)
goto error;
if (virJSONValueArrayAppend(owners, &owner) < 0)
goto error;
}
if (virJSONValueObjectAppend(child, "owners", &owners) < 0)
goto error;
if (virJSONValueArrayAppend(resources, &child) < 0)
goto error;
tmp++;
}
if (virJSONValueObjectAppend(object, "resources", &resources) < 0)
goto error;
virMutexUnlock(&lockspace->lock);
return g_steal_pointer(&object);
error:
virMutexUnlock(&lockspace->lock);
return NULL;
}
void virLockSpaceFree(virLockSpace *lockspace)
{
if (!lockspace)
return;
g_clear_pointer(&lockspace->resources, g_hash_table_unref);
g_free(lockspace->dir);
virMutexDestroy(&lockspace->lock);
g_free(lockspace);
}
const char *virLockSpaceGetDirectory(virLockSpace *lockspace)
{
return lockspace->dir;
}
int virLockSpaceCreateResource(virLockSpace *lockspace,
const char *resname)
{
int ret = -1;
g_autofree char *respath = NULL;
VIR_DEBUG("lockspace=%p resname=%s", lockspace, resname);
virMutexLock(&lockspace->lock);
if (virHashLookup(lockspace->resources, resname) != NULL) {
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is locked"),
resname);
goto cleanup;
}
if (!(respath = virLockSpaceGetResourcePath(lockspace, resname)))
goto cleanup;
if (virFileTouch(respath, 0600) < 0)
goto cleanup;
ret = 0;
cleanup:
virMutexUnlock(&lockspace->lock);
return ret;
}
int virLockSpaceDeleteResource(virLockSpace *lockspace,
const char *resname)
{
int ret = -1;
g_autofree char *respath = NULL;
VIR_DEBUG("lockspace=%p resname=%s", lockspace, resname);
virMutexLock(&lockspace->lock);
if (virHashLookup(lockspace->resources, resname) != NULL) {
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is locked"),
resname);
goto cleanup;
}
if (!(respath = virLockSpaceGetResourcePath(lockspace, resname)))
goto cleanup;
if (unlink(respath) < 0 &&
errno != ENOENT) {
virReportSystemError(errno,
_("Unable to delete lockspace resource %s"),
respath);
goto cleanup;
}
ret = 0;
cleanup:
virMutexUnlock(&lockspace->lock);
return ret;
}
int virLockSpaceAcquireResource(virLockSpace *lockspace,
const char *resname,
pid_t owner,
unsigned int flags)
{
int ret = -1;
virLockSpaceResource *res;
VIR_DEBUG("lockspace=%p resname=%s flags=0x%x owner=%lld",
lockspace, resname, flags, (unsigned long long)owner);
virCheckFlags(VIR_LOCK_SPACE_ACQUIRE_SHARED |
VIR_LOCK_SPACE_ACQUIRE_AUTOCREATE, -1);
virMutexLock(&lockspace->lock);
if ((res = virHashLookup(lockspace->resources, resname))) {
if ((res->flags & VIR_LOCK_SPACE_ACQUIRE_SHARED) &&
(flags & VIR_LOCK_SPACE_ACQUIRE_SHARED)) {
VIR_EXPAND_N(res->owners, res->nOwners, 1);
res->owners[res->nOwners-1] = owner;
goto done;
}
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is locked"),
resname);
goto cleanup;
}
if (!(res = virLockSpaceResourceNew(lockspace, resname, flags, owner)))
goto cleanup;
if (virHashAddEntry(lockspace->resources, resname, res) < 0) {
virLockSpaceResourceFree(res);
goto cleanup;
}
done:
ret = 0;
cleanup:
virMutexUnlock(&lockspace->lock);
return ret;
}
int virLockSpaceReleaseResource(virLockSpace *lockspace,
const char *resname,
pid_t owner)
{
int ret = -1;
virLockSpaceResource *res;
size_t i;
VIR_DEBUG("lockspace=%p resname=%s owner=%lld",
lockspace, resname, (unsigned long long)owner);
virMutexLock(&lockspace->lock);
if (!(res = virHashLookup(lockspace->resources, resname))) {
virReportError(VIR_ERR_RESOURCE_BUSY,
_("Lockspace resource '%s' is not locked"),
resname);
goto cleanup;
}
for (i = 0; i < res->nOwners; i++) {
if (res->owners[i] == owner)
break;
}
if (i == res->nOwners) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("owner %lld does not hold the resource lock"),
(unsigned long long)owner);
goto cleanup;
}
VIR_DELETE_ELEMENT(res->owners, i, res->nOwners);
if ((res->nOwners == 0) &&
virHashRemoveEntry(lockspace->resources, resname) < 0)
goto cleanup;
ret = 0;
cleanup:
virMutexUnlock(&lockspace->lock);
return ret;
}
struct virLockSpaceRemoveData {
pid_t owner;
size_t count;
};
static int
virLockSpaceRemoveResourcesForOwner(const void *payload,
const char *name G_GNUC_UNUSED,
const void *opaque)
{
virLockSpaceResource *res = (virLockSpaceResource *)payload;
struct virLockSpaceRemoveData *data = (struct virLockSpaceRemoveData *)opaque;
size_t i;
VIR_DEBUG("res %s owner %lld", res->name, (unsigned long long)data->owner);
for (i = 0; i < res->nOwners; i++) {
if (res->owners[i] == data->owner)
break;
}
if (i == res->nOwners)
return 0;
data->count++;
VIR_DELETE_ELEMENT(res->owners, i, res->nOwners);
if (res->nOwners) {
VIR_DEBUG("Other shared owners remain");
return 0;
}
VIR_DEBUG("No more owners, remove it");
return 1;
}
int virLockSpaceReleaseResourcesForOwner(virLockSpace *lockspace,
pid_t owner)
{
int ret = 0;
struct virLockSpaceRemoveData data = {
owner, 0
};
VIR_DEBUG("lockspace=%p owner=%lld", lockspace, (unsigned long long)owner);
virMutexLock(&lockspace->lock);
if (virHashRemoveSet(lockspace->resources,
virLockSpaceRemoveResourcesForOwner,
&data) < 0)
goto error;
ret = data.count;
virMutexUnlock(&lockspace->lock);
return ret;
error:
virMutexUnlock(&lockspace->lock);
return -1;
}