/*
* memory.c: safer memory allocation
*
- * Copyright (C) 2010-2011 Red Hat, Inc.
+ * Copyright (C) 2010-2012 Red Hat, Inc.
* Copyright (C) 2008 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
#include <stdlib.h>
#include "memory.h"
-
+#include "logging.h"
#if TEST_OOM
static int testMallocNext = 0;
}
}
+/**
+ * virInsertElementsN:
+ * @ptrptr: pointer to hold address of allocated memory
+ * @size: the size of one element in bytes
+ * @at: index within array where new elements should be added
+ * @countptr: variable tracking number of elements currently allocated
+ * @add: number of elements to add
+ * @newelems: pointer to array of one or more new elements to move into
+ * place (the originals will be zeroed out if successful
+ * and if clearOriginal is true)
+ * @clearOriginal: false if the new item in the array should be copied
+ * from the original, and the original left intact.
+ * true if the original should be 0'd out on success.
+ * @inPlace: false if we should expand the allocated memory before
+ * moving, true if we should assume someone else *has
+ * already* done that.
+ *
+ * Re-allocate an array of *countptr elements, each sizeof(*ptrptr) bytes
+ * long, to be *countptr+add elements long, then appropriately move
+ * the elements starting at ptrptr[at] up by add elements, copy the
+ * items from newelems into ptrptr[at], then store the address of
+ * allocated memory in *ptrptr and the new size in *countptr. If
+ * newelems is NULL, the new elements at ptrptr[at] are instead filled
+ * with zero.
+ *
+ * Returns -1 on failure, 0 on success
+ */
+int
+virInsertElementsN(void *ptrptr, size_t size, size_t at,
+ size_t *countptr,
+ size_t add, void *newelems,
+ bool clearOriginal, bool inPlace)
+{
+ if (at > *countptr) {
+ VIR_WARN("out of bounds index - count %zu at %zu add %zu",
+ *countptr, at, add);
+ return -1;
+ }
+
+ if (inPlace) {
+ *countptr += add;
+ } else if (virExpandN(ptrptr, size, countptr, add) < 0) {
+ return -1;
+ }
+
+ /* memory was successfully re-allocated. Move up all elements from
+ * ptrptr[at] to the end (if we're not "inserting" at the end
+ * already), memcpy in the new elements, and clear the elements
+ * from their original location. Remember that *countptr has
+ * already been updated with new element count!
+ */
+ if (at < *countptr - add) {
+ memmove(*(char**)ptrptr + (size * (at + add)),
+ *(char**)ptrptr + (size * at),
+ size * (*countptr - add - at));
+ }
+
+ if (newelems) {
+ memcpy(*(char**)ptrptr + (size * at), newelems, size * add);
+ if (clearOriginal)
+ memset((char*)newelems, 0, size * add);
+ } else if (inPlace || (at < *countptr - add)) {
+ /* NB: if inPlace, assume memory at the end wasn't initialized */
+ memset(*(char**)ptrptr + (size * at), 0, size * add);
+ }
+
+ return 0;
+}
+
+/**
+ * virDeleteElementsN:
+ * @ptrptr: pointer to hold address of allocated memory
+ * @size: the size of one element in bytes
+ * @at: index within array where new elements should be deleted
+ * @countptr: variable tracking number of elements currently allocated
+ * @remove: number of elements to remove
+ * @inPlace: false if we should shrink the allocated memory when done,
+ * true if we should assume someone else will do that.
+ *
+ * Re-allocate an array of *countptr elements, each sizeof(*ptrptr)
+ * bytes long, to be *countptr-remove elements long, then store the
+ * address of allocated memory in *ptrptr and the new size in *countptr.
+ * If *countptr <= remove, the entire array is freed.
+ *
+ * Returns -1 on failure, 0 on success
+ */
+int
+virDeleteElementsN(void *ptrptr, size_t size, size_t at,
+ size_t *countptr, size_t remove,
+ bool inPlace)
+{
+ if (at + remove > *countptr) {
+ VIR_WARN("out of bounds index - count %zu at %zu remove %zu",
+ *countptr, at, remove);
+ return -1;
+ }
+
+ /* First move down the elements at the end that won't be deleted,
+ * then realloc. We assume that the items being deleted have
+ * already been cleared.
+ */
+ memmove(*(char**)ptrptr + (size * at),
+ *(char**)ptrptr + (size * (at + remove)),
+ size * (*countptr - remove - at));
+ if (inPlace)
+ *countptr -= remove;
+ else
+ virShrinkN(ptrptr, size, countptr, remove);
+ return 0;
+}
/**
* Vir_Alloc_Var:
/*
* memory.c: safer memory allocation
*
- * Copyright (C) 2010-2011 Red Hat, Inc.
+ * Copyright (C) 2010-2012 Red Hat, Inc.
* Copyright (C) 2008 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
void virShrinkN(void *ptrptr, size_t size, size_t *count, size_t toremove)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3);
+int virInsertElementsN(void *ptrptr, size_t size, size_t at, size_t *countptr,
+ size_t add, void *newelem,
+ bool clearOriginal, bool inPlace)
+ ATTRIBUTE_RETURN_CHECK ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(4);
+int virDeleteElementsN(void *ptrptr, size_t size, size_t at, size_t *countptr,
+ size_t remove, bool inPlace)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(4);
int virAllocVar(void *ptrptr,
size_t struct_size,
size_t element_size,
# define VIR_SHRINK_N(ptr, count, remove) \
virShrinkN(&(ptr), sizeof(*(ptr)), &(count), remove)
+/*
+ * VIR_TYPEMATCH:
+ *
+ * The following macro seems a bit cryptic, so it needs a thorough
+ * explanation. Its purpose is to check for assignment compatibility
+ * and identical size between two values without creating any side
+ * effects (by doing something silly like actually assigning one to
+ * the other). Note that it takes advantage of the C89-guaranteed
+ * property of sizeof() - it cannot have any side effects, so anything
+ * that happens inside sizeof() will not have any effect at runtime.
+ *
+ * VIR_TYPEMATCH evaluates to "1" if the two passed values are both
+ * assignment-compatible and the same size, and otherwise generates a
+ * compile-time error. It determines the result by performing the
+ * following three operations:
+ *
+ * * sizeof(*(a) = *(b)) assures that *a and *b are
+ * assignment-compatible (they may still have a different size
+ * though! e.g. longVar = intVar) (If not, there is a compile-time
+ * error. If so, the result of that subexpression is sizeof(*(a)),
+ * i.e. one element of the array)
+ *
+ * * sizeof(*(a) = *(b)) == sizeof(*(b)) checks if *a and *b are also
+ * of the same size (so that, e.g. you don't accidentally copy an
+ * int plus the random bytes following it into an array of long). It
+ * evaluates to 1 if they are the same, and 0 otherwise.
+ *
+ * * sizeof(char[2 * (result of previous step) - 1]) evaluates to 1
+ * if the previous step was successful (char [(2*1) - 1] i.e.
+ * char[1]), or generates a compile error if it wasn't successful
+ * (char[2*0 -1] i.e. char[-1], which isn't valid in C).
+ *
+ * So VIR_TYPECHECK(a, b) will either abort the compile with an error,
+ * or evaluate to "1", and in the meantime check that we've actually
+ * added the correct &'s and/or *'s to the arguments. (Whew!)
+*/
+# define VIR_TYPEMATCH(a, b) \
+ sizeof(char[2 * (sizeof(*(a) = *(b)) == sizeof(*(b))) - 1])
+
+/**
+ * VIR_INSERT_ELEMENT:
+ * @ptr: pointer to array of objects (*not* ptr to ptr)
+ * @at: index within array where new elements should be added
+ * @count: variable tracking number of elements currently allocated
+ * @newelem: the new element to move into place (*not* a pointer to
+ * the element, but the element itself).
+ * (the original will be zeroed out if successful)
+ *
+ * Re-allocate an array of 'count' elements, each sizeof(*ptr) bytes
+ * long, to be 'count' + 1 elements long, then appropriately move
+ * the elements starting at ptr[at] up by 1 element, copy the
+ * item 'newelem' into ptr[at], then store the address of
+ * allocated memory in 'ptr' and the new size in 'count'.
+ *
+ * VIR_INSERT_ELEMENT_COPY is identical, but doesn't clear out the
+ * original element to 0 on success, so there are two copies of the
+ * element. This is useful if the "element" is actually just a
+ * pointer to the real data, and you want to maintain a reference to
+ * it for use after the insert is completed; but if the "element" is
+ * an object that points to other allocated memory, having multiple
+ * copies can cause problems (e.g. double free).
+ *
+ * VIR_INSERT_ELEMENT_*INPLACE are identical, but assume any necessary
+ * memory re-allocation has already been done.
+ *
+ * VIR_INSERT_ELEMENT_* all need to send "1" as the "add" argument to
+ * virInsertElementsN (which has the currently-unused capability of
+ * inserting multiple items at once). We use this to our advantage by
+ * replacing it with VIR_TYPECHECK(ptr, &newelem) so that we can be
+ * assured ptr and &newelem are of compatible types.
+ *
+ * Returns -1 on failure, 0 on success
+ *
+ *
+ */
+# define VIR_INSERT_ELEMENT(ptr, at, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), at, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), true, false)
+# define VIR_INSERT_ELEMENT_COPY(ptr, at, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), at, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), false, false)
+# define VIR_INSERT_ELEMENT_INPLACE(ptr, at, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), at, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), true, true)
+# define VIR_INSERT_ELEMENT_COPY_INPLACE(ptr, at, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), at, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), false, true)
+
+/**
+ * VIR_APPEND_ELEMENT:
+ * @ptr: pointer to array of objects (*not* ptr to ptr)
+ * @count: variable tracking number of elements currently allocated
+ * @newelem: the new element to move into place (*not* a pointer to
+ * the element, but the element itself).
+ * (the original will be zeroed out if successful)
+ *
+ * Re-allocate an array of 'count' elements, each sizeof(*ptr) bytes
+ * long, to be 'count' + 1 elements long, then copy the item from
+ * 'newelem' into ptr[count+1], and store the address of allocated
+ * memory in 'ptr' and the new size in 'count'. If 'newelem' is NULL,
+ * the new element at ptr[at] is instead filled with zero.
+ *
+ * VIR_APPEND_ELEMENT_COPY is identical, but doesn't clear out the
+ * original element to 0 on success, so there are two copies of the
+ * element. This is useful if the "element" is actually just a
+ * pointer to the real data, and you want to maintain a reference to
+ * it for use after the append is completed; but if the "element" is
+ * an object that points to other allocated memory, having multiple
+ * copies can cause problems (e.g. double free).
+ *
+ * VIR_APPEND_ELEMENT_*INPLACE are identical, but assume any
+ * necessary memory re-allocation has already been done.
+ *
+ * VIR_APPEND_ELEMENT_* all need to send "1" as the "add" argument to
+ * virInsertElementsN (which has the currently-unused capability of
+ * inserting multiple items at once). We use this to our advantage by
+ * replacing it with VIR_TYPECHECK(ptr, &newelem) so that we can be
+ * assured ptr and &newelem are of compatible types.
+ *
+ * Returns -1 on failure, 0 on success
+ *
+ *
+ */
+# define VIR_APPEND_ELEMENT(ptr, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), count, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), true, false)
+# define VIR_APPEND_ELEMENT_COPY(ptr, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), count, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), false, false)
+# define VIR_APPEND_ELEMENT_INPLACE(ptr, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), count, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), true, true)
+# define VIR_APPEND_ELEMENT_COPY_INPLACE(ptr, count, newelem) \
+ virInsertElementsN(&(ptr), sizeof(*(ptr)), count, &(count), \
+ VIR_TYPEMATCH(ptr, &(newelem)), &(newelem), false, true)
+
+/**
+ * VIR_DELETE_ELEMENT:
+ * @ptr: pointer to array of objects (*not* ptr to ptr)
+ * @at: index within array where new elements should be deleted
+ * @count: variable tracking number of elements currently allocated
+ *
+ * Re-allocate an array of 'count' elements, each sizeof(*ptr)
+ * bytes long, to be 'count' - 1 elements long, then store the
+ * address of allocated memory in 'ptr' and the new size in 'count'.
+ * If 'count' <= 1, the entire array is freed.
+ *
+ * VIR_DELETE_ELEMENT_INPLACE is identical, but assumes any
+ * necessary memory re-allocation will be done later.
+ *
+ * Returns -1 on failure, 0 on success
+ */
+# define VIR_DELETE_ELEMENT(ptr, at, count) \
+ virDeleteElementsN(&(ptr), sizeof(*(ptr)), at, &(count), 1, false)
+# define VIR_DELETE_ELEMENT_INPLACE(ptr, at, count) \
+ virDeleteElementsN(&(ptr), sizeof(*(ptr)), at, &(count), 1, true)
+
/*
* VIR_ALLOC_VAR_OVERSIZED:
* @M: size of base structure