Commit 4e5111fb authored by Rusty Russell's avatar Rusty Russell

talloc_link; a replacement for talloc's references.

parent f8f43498
#include <stdio.h>
#include <string.h>
#include "config.h"
/**
* talloc_link - link helper for talloc
*
* Talloc references can be confusing and buggy. In the cases where an object
* needs multiple parents, all parents need to be aware of the situation; thus
* talloc_link is a helper where all "parents" talloc_link an object they
* agree to share ownership of.
*
* Example:
* // Silly program which keeps a cache of uppercased strings.
* // The cache wants to keep strings around even after they may have
* // been "freed" by the caller.
* #include <stdio.h>
* #include <err.h>
* #include <string.h>
* #include <ctype.h>
* #include <ccan/talloc/talloc.h>
* #include <ccan/talloc_link/talloc_link.h>
*
* struct upcache {
* const char *str;
* const char *upstr;
* };
*
* static struct upcache *cache;
* static unsigned int cache_hits = 0;
* #define CACHE_SIZE 4
* void init_upcase(void)
* {
* cache = talloc_zero_array(NULL, struct upcache, CACHE_SIZE);
* }
*
* static struct upcache *lookup_upcase(const char *str)
* {
* unsigned int i;
* for (i = 0; i < CACHE_SIZE; i++)
* if (cache[i].str && !strcmp(cache[i].str, str)) {
* cache_hits++;
* return &cache[i];
* }
* return NULL;
* }
*
* static struct upcache *new_upcase(const char *str)
* {
* unsigned int i;
* char *upstr;
*
* upstr = talloc_linked(cache, talloc_strdup(NULL, str));
* if (!upstr)
* return NULL;
*
* i = random() % CACHE_SIZE;
*
* // Throw out old: works fine if cache[i].upstr is NULL.
* talloc_delink(cache, cache[i].upstr);
*
* // Replace with new.
* cache[i].str = str;
* cache[i].upstr = upstr;
* while (*upstr) {
* *upstr = toupper(*upstr);
* upstr++;
* }
* return &cache[i];
* }
*
* // If you want to keep the result, talloc_link it.
* const char *get_upcase(const char *str)
* {
* struct upcache *uc = lookup_upcase(str);
* if (!uc)
* uc = new_upcase(str);
* if (!uc)
* return NULL;
* return uc->upstr;
* }
*
* void exit_upcase(void)
* {
* talloc_free(cache);
* printf("Cache hits: %u\n", cache_hits);
* }
*
* int main(int argc, char *argv[])
* {
* unsigned int i;
* const char **values;
*
* // Will dump any memory leaks to stderr on exit.
* talloc_enable_leak_report();
*
* // Initialize cache.
* init_upcase();
*
* // Throw values in.
* values = talloc_array(NULL, const char *, argc);
* for (i = 1; i < argc; i++) {
* values[i-1] = talloc_link(values, get_upcase(argv[i]));
* if (!values[i-1])
* err(1, "Out of memory");
* }
* // This will free all the values, but cache will still work.
* talloc_free(values);
*
* // Repeat!
* values = talloc_array(NULL, const char *, argc);
* for (i = 1; i < argc; i++) {
* values[i-1] = talloc_link(values, get_upcase(argv[i]));
* if (!values[i-1])
* err(1, "Out of memory");
* }
*
* // This will remove cache links, but we still have a link.
* exit_upcase();
*
* // Show values, so we output something.
* for (i = 0; i < argc - 1; i++)
* printf("%s ", values[i]);
* printf("\n");
*
* // This will finally free the upcase strings (last link).
* talloc_free(values);
*
* return 0;
* }
*
* Licence: GPL (2 or any later version)
*/
int main(int argc, char *argv[])
{
if (argc != 2)
return 1;
if (strcmp(argv[1], "depends") == 0) {
printf("ccan/talloc\n");
printf("ccan/list\n");
return 0;
}
return 1;
}
#include <ccan/list/list.h>
#include <ccan/talloc/talloc.h>
#include <ccan/talloc_link/talloc_link.h>
#include <assert.h>
/* Fake parent, if they care. */
static void *talloc_links = NULL;
/* This is the parent of the linked object, so we can implement delink. */
struct talloc_linked {
struct list_head links;
const void *obj;
};
/* This is a child of the linker, but not a parent of ref. */
struct talloc_link {
struct list_node list;
struct talloc_linked *linked;
};
static int destroy_link(struct talloc_link *link)
{
list_del(&link->list);
if (list_empty(&link->linked->links))
talloc_free(link->linked);
return 0;
}
static bool add_link(const void *ctx, struct talloc_linked *linked)
{
struct talloc_link *link = talloc(ctx, struct talloc_link);
if (!link)
return false;
link->linked = linked;
list_add(&linked->links, &link->list);
talloc_set_destructor(link, destroy_link);
return true;
}
void *_talloc_linked(const void *ctx, const void *newobj)
{
struct talloc_linked *linked;
if (talloc_parent(newobj)) {
/* Assume leak reporting is on: create dummy parent. */
if (!talloc_links)
talloc_links = talloc_named_const(NULL, 0,
"talloc_links");
/* This should now have same pseudo-NULL parent. */
assert(talloc_parent(newobj) == talloc_parent(talloc_links));
}
linked = talloc(talloc_links, struct talloc_linked);
if (!linked) {
talloc_free(newobj);
return NULL;
}
list_head_init(&linked->links);
linked->obj = talloc_steal(linked, newobj);
if (!add_link(ctx, linked)) {
talloc_free(linked);
return NULL;
}
return (void *)newobj;
}
void *_talloc_link(const void *ctx, const void *obj)
{
struct talloc_linked *linked;
linked = talloc_get_type(talloc_parent(obj), struct talloc_linked);
assert(!list_empty(&linked->links));
return add_link(ctx, linked) ? (void *)obj : NULL;
}
void talloc_delink(const void *ctx, const void *obj)
{
struct talloc_linked *linked;
struct talloc_link *i;
if (!obj)
return;
linked = talloc_get_type(talloc_parent(obj), struct talloc_linked);
list_for_each(&linked->links, i, list) {
if (talloc_is_parent(i, ctx)) {
talloc_free(i);
return;
}
}
abort();
}
#ifndef TALLOC_LINK_H
#define TALLOC_LINK_H
#include <ccan/talloc/talloc.h>
/**
* talloc_linked - set up an object with an initial link.
* @ctx - the context to initially link to
* @newobj - the newly allocated object (with a NULL parent)
*
* The object will be freed when @ctx is freed (or talloc_delink(ctx,
* newobj) is called), unless more links are added using
* talloc_link().
*
* For convenient chaining, it returns @newobj on success, or frees
* @newobj and returns NULL.
*/
#define talloc_linked(ctx, newobj) \
((_TALLOC_TYPEOF(newobj))_talloc_linked((ctx), (newobj)))
/**
* talloc_link - add another link to a linkable object.
* @ctx - the context to link to
* @obj - the object previously made linkable with talloc_linked().
*
* The @obj will only be freed when all contexts linked to it are
* freed (or talloc_delink()ed).
*
* Returns @obj, or NULL on failure (out of memory).
*/
#define talloc_link(ctx, obj) \
((_TALLOC_TYPEOF(obj))_talloc_link((ctx), (obj)))
/**
* talloc_delink - explicitly remove a link from a linkable object.
* @ctx - the context previously used for talloc_link/talloc_linked
* @obj - the object previously used for talloc_link/talloc_linked
*
* Explicitly remove a link: normally it is implied by freeing @ctx.
* Removing the last link frees the object.
*/
void talloc_delink(const void *ctx, const void *linked);
/* Internal helpers. */
void *_talloc_link(const void *ctx, const void *linked);
void *_talloc_linked(const void *ctx, const void *linked);
#endif /* TALLOC_LINK_H */
#include "talloc_link/talloc_link.h"
#include "tap/tap.h"
#include "talloc_link/talloc_link.c"
#include <stdlib.h>
#include <err.h>
static unsigned int destroy_count = 0;
static int destroy_obj(void *obj)
{
destroy_count++;
return 0;
}
int main(int argc, char *argv[])
{
void *obj, *p1, *p2, *p3;
plan_tests(16);
talloc_enable_leak_report();
/* Single parent case. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
ok(destroy_count == 0, "destroy_count = %u", destroy_count);
talloc_free(p1);
ok(destroy_count == 1, "destroy_count = %u", destroy_count);
/* Dual parent case. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
p2 = talloc(NULL, char);
talloc_link(p2, obj);
talloc_free(p1);
ok(destroy_count == 1, "destroy_count = %u", destroy_count);
talloc_free(p2);
ok(destroy_count == 2, "destroy_count = %u", destroy_count);
/* Triple parent case. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
p2 = talloc(NULL, char);
p3 = talloc(NULL, char);
talloc_link(p2, obj);
talloc_link(p3, obj);
talloc_free(p1);
ok(destroy_count == 2, "destroy_count = %u", destroy_count);
talloc_free(p2);
ok(destroy_count == 2, "destroy_count = %u", destroy_count);
talloc_free(p3);
ok(destroy_count == 3, "destroy_count = %u", destroy_count);
/* Single delink case. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
ok(destroy_count == 3, "destroy_count = %u", destroy_count);
talloc_delink(p1, obj);
ok(destroy_count == 4, "destroy_count = %u", destroy_count);
talloc_free(p1);
/* Double delink case. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
p2 = talloc(NULL, char);
talloc_link(p2, obj);
talloc_delink(p1, obj);
ok(destroy_count == 4, "destroy_count = %u", destroy_count);
talloc_delink(p2, obj);
ok(destroy_count == 5, "destroy_count = %u", destroy_count);
talloc_free(p1);
talloc_free(p2);
/* Delink and free. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
p2 = talloc(NULL, char);
talloc_link(p2, obj);
talloc_delink(p1, obj);
ok(destroy_count == 5, "destroy_count = %u", destroy_count);
talloc_free(p2);
ok(destroy_count == 6, "destroy_count = %u", destroy_count);
talloc_free(p1);
/* Free and delink. */
p1 = talloc(NULL, char);
obj = talloc_linked(p1, talloc(NULL, char));
talloc_set_destructor(obj, destroy_obj);
p2 = talloc(NULL, char);
talloc_link(p2, obj);
talloc_free(p1);
ok(destroy_count == 6, "destroy_count = %u", destroy_count);
talloc_delink(p2, obj);
ok(destroy_count == 7, "destroy_count = %u", destroy_count);
talloc_free(p2);
/* No leaks? */
ok1(talloc_total_size(NULL) == 0);
return exit_status();
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment