mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 10:51:12 +00:00
1c560052a8
Checking that the derived class is larger than the requested parent class saves us from some obvious mistakes, but as written, it does not catch all the cases; in particular, it is easy to forget to update a VIR_CLASS_NEW when changing the 'parent' member from virObject to virObjectLockabale, but where the size checks don't catch that. Add a parameter for one more layer of sanity checking. It would be cool if we could get gcc to stringize typeof(parent) into the string name of that type, so that we could confirm that the precise parent class is in use rather than just a struct that happens to have the same size as the parent class. But sizeof checks are better than nothing. Note that I did NOT change the fact that we require derived classes to be larger (as the difference in size makes it easy to tell classes apart), which means that even if a derived class has no functionality to add (but rather exists for compiler-enforced type-safety), it must still include a dummy member. But I did fix the wording of the error message to match the code. Signed-off-by: Eric Blake <eblake@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
656 lines
14 KiB
C
656 lines
14 KiB
C
/*
|
|
* virobject.c: libvirt reference counted object
|
|
*
|
|
* Copyright (C) 2012-2014 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>
|
|
|
|
#define VIR_PARENT_REQUIRED /* empty, to allow virObject to have no parent */
|
|
#include "virobject.h"
|
|
#include "virthread.h"
|
|
#include "viralloc.h"
|
|
#include "viratomic.h"
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virprobe.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
VIR_LOG_INIT("util.object");
|
|
|
|
static unsigned int magicCounter = 0xCAFE0000;
|
|
|
|
struct _virClass {
|
|
virClassPtr parent;
|
|
|
|
unsigned int magic;
|
|
char *name;
|
|
size_t objectSize;
|
|
|
|
virObjectDisposeCallback dispose;
|
|
};
|
|
|
|
#define VIR_OBJECT_NOTVALID(obj) (!obj || ((obj->u.s.magic & 0xFFFF0000) != 0xCAFE0000))
|
|
|
|
#define VIR_OBJECT_USAGE_PRINT_WARNING(anyobj, objclass) \
|
|
do { \
|
|
virObjectPtr obj = anyobj; \
|
|
if (VIR_OBJECT_NOTVALID(obj)) { \
|
|
if (!obj) \
|
|
VIR_WARN("Object cannot be NULL"); \
|
|
else \
|
|
VIR_WARN("Object %p has a bad magic number %X", \
|
|
obj, obj->u.s.magic); \
|
|
} else { \
|
|
VIR_WARN("Object %p (%s) is not a %s instance", \
|
|
anyobj, obj->klass->name, #objclass); \
|
|
} \
|
|
} while (0)
|
|
|
|
|
|
static virClassPtr virObjectClass;
|
|
static virClassPtr virObjectLockableClass;
|
|
static virClassPtr virObjectRWLockableClass;
|
|
|
|
static void virObjectLockableDispose(void *anyobj);
|
|
static void virObjectRWLockableDispose(void *anyobj);
|
|
|
|
static int
|
|
virObjectOnceInit(void)
|
|
{
|
|
if (!(virObjectClass = virClassNew(NULL,
|
|
"virObject",
|
|
sizeof(virObject),
|
|
0,
|
|
NULL)))
|
|
return -1;
|
|
|
|
if (!VIR_CLASS_NEW(virObjectLockable, virObjectClass))
|
|
return -1;
|
|
|
|
if (!VIR_CLASS_NEW(virObjectRWLockable, virObjectClass))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
VIR_ONCE_GLOBAL_INIT(virObject);
|
|
|
|
|
|
/**
|
|
* virClassForObject:
|
|
*
|
|
* Returns the class instance for the base virObject type
|
|
*/
|
|
virClassPtr
|
|
virClassForObject(void)
|
|
{
|
|
if (virObjectInitialize() < 0)
|
|
return NULL;
|
|
|
|
return virObjectClass;
|
|
}
|
|
|
|
|
|
/**
|
|
* virClassForObjectLockable:
|
|
*
|
|
* Returns the class instance for the virObjectLockable type
|
|
*/
|
|
virClassPtr
|
|
virClassForObjectLockable(void)
|
|
{
|
|
if (virObjectInitialize() < 0)
|
|
return NULL;
|
|
|
|
return virObjectLockableClass;
|
|
}
|
|
|
|
|
|
/**
|
|
* virClassForObjectRWLockable:
|
|
*
|
|
* Returns the class instance for the virObjectRWLockable type
|
|
*/
|
|
virClassPtr
|
|
virClassForObjectRWLockable(void)
|
|
{
|
|
if (virObjectInitialize() < 0)
|
|
return NULL;
|
|
|
|
return virObjectRWLockableClass;
|
|
}
|
|
|
|
|
|
/**
|
|
* virClassNew:
|
|
* @parent: the parent class
|
|
* @name: the class name
|
|
* @objectSize: total size of the object struct
|
|
* @dispose: callback to run to free object fields
|
|
*
|
|
* Register a new object class with @name. The @objectSize
|
|
* should give the total size of the object struct, which
|
|
* is expected to have a 'virObject parent;' field as (or
|
|
* contained in) its first member. When the last reference
|
|
* on the object is released, the @dispose callback will be
|
|
* invoked to free memory of the local object fields, as
|
|
* well as the dispose callbacks of the parent classes.
|
|
*
|
|
* Returns a new class instance
|
|
*/
|
|
virClassPtr
|
|
virClassNew(virClassPtr parent,
|
|
const char *name,
|
|
size_t objectSize,
|
|
size_t parentSize,
|
|
virObjectDisposeCallback dispose)
|
|
{
|
|
virClassPtr klass;
|
|
|
|
if (parent == NULL &&
|
|
STRNEQ(name, "virObject")) {
|
|
virReportInvalidNonNullArg(parent);
|
|
return NULL;
|
|
} else if (objectSize <= parentSize ||
|
|
parentSize != (parent ? parent->objectSize : 0)) {
|
|
sa_assert(parent);
|
|
virReportInvalidArg(objectSize,
|
|
_("object size %zu of %s is not larger than parent class %zu"),
|
|
objectSize, name, parent->objectSize);
|
|
return NULL;
|
|
}
|
|
|
|
if (VIR_ALLOC(klass) < 0)
|
|
goto error;
|
|
|
|
klass->parent = parent;
|
|
klass->magic = virAtomicIntInc(&magicCounter);
|
|
if (klass->magic > 0xCAFEFFFF) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("too many object classes defined"));
|
|
goto error;
|
|
}
|
|
if (VIR_STRDUP(klass->name, name) < 0)
|
|
goto error;
|
|
klass->objectSize = objectSize;
|
|
klass->dispose = dispose;
|
|
|
|
return klass;
|
|
|
|
error:
|
|
VIR_FREE(klass);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virClassIsDerivedFrom:
|
|
* @klass: the klass to check
|
|
* @parent: the possible parent class
|
|
*
|
|
* Determine if @klass is derived from @parent
|
|
*
|
|
* Return true if @klass is derived from @parent, false otherwise
|
|
*/
|
|
bool
|
|
virClassIsDerivedFrom(virClassPtr klass,
|
|
virClassPtr parent)
|
|
{
|
|
while (klass) {
|
|
if (klass->magic == parent->magic)
|
|
return true;
|
|
klass = klass->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectNew:
|
|
* @klass: the klass of object to create
|
|
*
|
|
* Allocates a new object of type @klass. The returned
|
|
* object will be an instance of "virObjectPtr", which
|
|
* can be cast to the struct associated with @klass.
|
|
*
|
|
* The initial reference count of the object will be 1.
|
|
*
|
|
* Returns the new object
|
|
*/
|
|
void *
|
|
virObjectNew(virClassPtr klass)
|
|
{
|
|
virObjectPtr obj = NULL;
|
|
|
|
if (VIR_ALLOC_VAR(obj,
|
|
char,
|
|
klass->objectSize - sizeof(virObject)) < 0)
|
|
return NULL;
|
|
|
|
obj->u.s.magic = klass->magic;
|
|
obj->klass = klass;
|
|
virAtomicIntSet(&obj->u.s.refs, 1);
|
|
|
|
PROBE(OBJECT_NEW, "obj=%p classname=%s", obj, obj->klass->name);
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
void *
|
|
virObjectLockableNew(virClassPtr klass)
|
|
{
|
|
virObjectLockablePtr obj;
|
|
|
|
if (!virClassIsDerivedFrom(klass, virClassForObjectLockable())) {
|
|
virReportInvalidArg(klass,
|
|
_("Class %s must derive from virObjectLockable"),
|
|
virClassName(klass));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(obj = virObjectNew(klass)))
|
|
return NULL;
|
|
|
|
if (virMutexInit(&obj->lock) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to initialize mutex"));
|
|
virObjectUnref(obj);
|
|
return NULL;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
void *
|
|
virObjectRWLockableNew(virClassPtr klass)
|
|
{
|
|
virObjectRWLockablePtr obj;
|
|
|
|
if (!virClassIsDerivedFrom(klass, virClassForObjectRWLockable())) {
|
|
virReportInvalidArg(klass,
|
|
_("Class %s must derive from virObjectRWLockable"),
|
|
virClassName(klass));
|
|
return NULL;
|
|
}
|
|
|
|
if (!(obj = virObjectNew(klass)))
|
|
return NULL;
|
|
|
|
if (virRWLockInit(&obj->lock) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to initialize RW lock"));
|
|
virObjectUnref(obj);
|
|
return NULL;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
static void
|
|
virObjectLockableDispose(void *anyobj)
|
|
{
|
|
virObjectLockablePtr obj = anyobj;
|
|
|
|
virMutexDestroy(&obj->lock);
|
|
}
|
|
|
|
|
|
static void
|
|
virObjectRWLockableDispose(void *anyobj)
|
|
{
|
|
virObjectRWLockablePtr obj = anyobj;
|
|
|
|
virRWLockDestroy(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectUnref:
|
|
* @anyobj: any instance of virObjectPtr
|
|
*
|
|
* Decrement the reference count on @anyobj and if
|
|
* it hits zero, runs the "dispose" callbacks associated
|
|
* with the object class and its parents before freeing
|
|
* @anyobj.
|
|
*
|
|
* Returns true if the remaining reference count is
|
|
* non-zero, false if the object was disposed of
|
|
*/
|
|
bool
|
|
virObjectUnref(void *anyobj)
|
|
{
|
|
virObjectPtr obj = anyobj;
|
|
|
|
if (VIR_OBJECT_NOTVALID(obj))
|
|
return false;
|
|
|
|
bool lastRef = virAtomicIntDecAndTest(&obj->u.s.refs);
|
|
PROBE(OBJECT_UNREF, "obj=%p", obj);
|
|
if (lastRef) {
|
|
PROBE(OBJECT_DISPOSE, "obj=%p", obj);
|
|
virClassPtr klass = obj->klass;
|
|
while (klass) {
|
|
if (klass->dispose)
|
|
klass->dispose(obj);
|
|
klass = klass->parent;
|
|
}
|
|
|
|
/* Clear & poison object */
|
|
memset(obj, 0, obj->klass->objectSize);
|
|
obj->u.s.magic = 0xDEADBEEF;
|
|
obj->klass = (void*)0xDEADBEEF;
|
|
VIR_FREE(obj);
|
|
}
|
|
|
|
return !lastRef;
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectAutoUnref:
|
|
*
|
|
* Helper used by VIR_AUTOUNREF
|
|
*/
|
|
void
|
|
virObjectAutoUnref(void *objptr)
|
|
{
|
|
virObjectPtr *obj = objptr;
|
|
virObjectUnref(*obj);
|
|
*obj = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectRef:
|
|
* @anyobj: any instance of virObjectPtr
|
|
*
|
|
* Increment the reference count on @anyobj and return
|
|
* the same pointer
|
|
*
|
|
* Returns @anyobj
|
|
*/
|
|
void *
|
|
virObjectRef(void *anyobj)
|
|
{
|
|
virObjectPtr obj = anyobj;
|
|
|
|
if (VIR_OBJECT_NOTVALID(obj))
|
|
return NULL;
|
|
virAtomicIntInc(&obj->u.s.refs);
|
|
PROBE(OBJECT_REF, "obj=%p", obj);
|
|
return anyobj;
|
|
}
|
|
|
|
|
|
static virObjectLockablePtr
|
|
virObjectGetLockableObj(void *anyobj)
|
|
{
|
|
if (virObjectIsClass(anyobj, virObjectLockableClass))
|
|
return anyobj;
|
|
|
|
VIR_OBJECT_USAGE_PRINT_WARNING(anyobj, virObjectLockable);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static virObjectRWLockablePtr
|
|
virObjectGetRWLockableObj(void *anyobj)
|
|
{
|
|
if (virObjectIsClass(anyobj, virObjectRWLockableClass))
|
|
return anyobj;
|
|
|
|
VIR_OBJECT_USAGE_PRINT_WARNING(anyobj, virObjectRWLockable);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectLock:
|
|
* @anyobj: any instance of virObjectLockable
|
|
*
|
|
* Acquire a lock on @anyobj. The lock must be released by
|
|
* virObjectUnlock.
|
|
*
|
|
* The caller is expected to have acquired a reference
|
|
* on the object before locking it (eg virObjectRef).
|
|
* The object must be unlocked before releasing this
|
|
* reference.
|
|
*/
|
|
void
|
|
virObjectLock(void *anyobj)
|
|
{
|
|
virObjectLockablePtr obj = virObjectGetLockableObj(anyobj);
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
virMutexLock(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectRWLockRead:
|
|
* @anyobj: any instance of virObjectRWLockable
|
|
*
|
|
* Acquire a read lock on @anyobj. The lock must be
|
|
* released by virObjectRWUnlock.
|
|
*
|
|
* The caller is expected to have acquired a reference
|
|
* on the object before locking it (eg virObjectRef).
|
|
* The object must be unlocked before releasing this
|
|
* reference.
|
|
*
|
|
* NB: It's possible to return without the lock if
|
|
* @anyobj was invalid - this has been considered
|
|
* a programming error rather than something that
|
|
* should be checked.
|
|
*/
|
|
void
|
|
virObjectRWLockRead(void *anyobj)
|
|
{
|
|
virObjectRWLockablePtr obj = virObjectGetRWLockableObj(anyobj);
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
virRWLockRead(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectRWLockWrite:
|
|
* @anyobj: any instance of virObjectRWLockable
|
|
*
|
|
* Acquire a write lock on @anyobj. The lock must be
|
|
* released by virObjectRWUnlock.
|
|
*
|
|
* The caller is expected to have acquired a reference
|
|
* on the object before locking it (eg virObjectRef).
|
|
* The object must be unlocked before releasing this
|
|
* reference.
|
|
*
|
|
* NB: It's possible to return without the lock if
|
|
* @anyobj was invalid - this has been considered
|
|
* a programming error rather than something that
|
|
* should be checked.
|
|
*/
|
|
void
|
|
virObjectRWLockWrite(void *anyobj)
|
|
{
|
|
virObjectRWLockablePtr obj = virObjectGetRWLockableObj(anyobj);
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
virRWLockWrite(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectUnlock:
|
|
* @anyobj: any instance of virObjectLockable
|
|
*
|
|
* Release a lock on @anyobj. The lock must have been acquired by
|
|
* virObjectLock.
|
|
*/
|
|
void
|
|
virObjectUnlock(void *anyobj)
|
|
{
|
|
virObjectLockablePtr obj = virObjectGetLockableObj(anyobj);
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
virMutexUnlock(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectRWUnlock:
|
|
* @anyobj: any instance of virObjectRWLockable
|
|
*
|
|
* Release a lock on @anyobj. The lock must have been acquired by
|
|
* virObjectRWLockRead or virObjectRWLockWrite.
|
|
*/
|
|
void
|
|
virObjectRWUnlock(void *anyobj)
|
|
{
|
|
virObjectRWLockablePtr obj = virObjectGetRWLockableObj(anyobj);
|
|
|
|
if (!obj)
|
|
return;
|
|
|
|
virRWLockUnlock(&obj->lock);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectIsClass:
|
|
* @anyobj: any instance of virObjectPtr
|
|
* @klass: the class to check
|
|
*
|
|
* Checks whether @anyobj is an instance of
|
|
* @klass
|
|
*
|
|
* Returns true if @anyobj is an instance of @klass
|
|
*/
|
|
bool
|
|
virObjectIsClass(void *anyobj,
|
|
virClassPtr klass)
|
|
{
|
|
virObjectPtr obj = anyobj;
|
|
if (VIR_OBJECT_NOTVALID(obj))
|
|
return false;
|
|
|
|
return virClassIsDerivedFrom(obj->klass, klass);
|
|
}
|
|
|
|
|
|
/**
|
|
* virClassName:
|
|
* @klass: the object class
|
|
*
|
|
* Returns the name of @klass
|
|
*/
|
|
const char *
|
|
virClassName(virClassPtr klass)
|
|
{
|
|
return klass->name;
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectFreeCallback:
|
|
* @opaque: a pointer to a virObject instance
|
|
*
|
|
* Provides identical functionality to virObjectUnref,
|
|
* but with the signature matching the virFreeCallback
|
|
* typedef.
|
|
*/
|
|
void virObjectFreeCallback(void *opaque)
|
|
{
|
|
virObjectUnref(opaque);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectFreeHashData:
|
|
* @opaque: a pointer to a virObject instance
|
|
* @name: ignored, name of the hash key being deleted
|
|
*
|
|
* Provides identical functionality to virObjectUnref,
|
|
* but with the signature matching the virHashDataFree
|
|
* typedef.
|
|
*/
|
|
void
|
|
virObjectFreeHashData(void *opaque,
|
|
const void *name ATTRIBUTE_UNUSED)
|
|
{
|
|
virObjectUnref(opaque);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectListFree:
|
|
* @list: A pointer to a NULL-terminated list of object pointers to free
|
|
*
|
|
* Unrefs all members of @list and frees the list itself.
|
|
*/
|
|
void
|
|
virObjectListFree(void *list)
|
|
{
|
|
void **next;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
for (next = (void **) list; *next; next++)
|
|
virObjectUnref(*next);
|
|
|
|
VIR_FREE(list);
|
|
}
|
|
|
|
|
|
/**
|
|
* virObjectListFreeCount:
|
|
* @list: A pointer to a list of object pointers to freea
|
|
* @count: Number of elements in the list.
|
|
*
|
|
* Unrefs all members of @list and frees the list itself.
|
|
*/
|
|
void
|
|
virObjectListFreeCount(void *list,
|
|
size_t count)
|
|
{
|
|
size_t i;
|
|
|
|
if (!list)
|
|
return;
|
|
|
|
for (i = 0; i < count; i++)
|
|
virObjectUnref(((void **)list)[i]);
|
|
|
|
VIR_FREE(list);
|
|
}
|