/* Cesar project {{{ * * Copyright (C) 2007 Spidcom * * <<>> * * }}} */ /** * \file lib/src/restrack.c * \brief Resources tracker implementation. * \ingroup lib */ #include "common/std.h" #include "lib/restrack.h" #include "lib/set.h" #include #include /** Recorded change for a resource. */ struct restrack_resource_change_t { /** Set node for set of change. */ set_node_t node; /** Function doing the change. */ const char *function; /** Corresponding source line. */ int line; /** Accumulated change. */ int change; }; typedef struct restrack_resource_change_t restrack_resource_change_t; /** Information about a resource. */ struct restrack_resource_t { /** Set node for set of resources. */ set_node_t node; /** Resource pointer. */ void *resource; /** Current reference counter value. */ int references; /** Creator function. */ const char *creator_function; /** Creator corresponding source line. */ int creator_line; /** Set of recorded changes. */ set_t changes_set; }; typedef struct restrack_resource_t restrack_resource_t; /** Resources tracker context. */ struct restrack_t { /** Set of tracked resources. */ set_t resources_set; /** Automatic initialisation depends on this to be initialised to zero by * the compiler. */ bool inited; /** Should the clean up function be registered? */ bool atexit_done; }; typedef struct restrack_t restrack_t; /** Global context. */ restrack_t restrack_global; /** Set node comparison function for changes. */ static bool restrack_resource_change_less (set_node_t *left, set_node_t *right) { dbg_assert_ptr (left); dbg_assert_ptr (right); restrack_resource_change_t *l, *r; l = PARENT_OF (restrack_resource_change_t, node, left); r = PARENT_OF (restrack_resource_change_t, node, right); /* Function strings are compared by address. This is a feature as the * provided function string is supposed to always have the same address at * each call. */ return l->function == r->function ? l->line < r->line : l->function < r->function; } /** Set node comparison function for resources. */ static bool restrack_resource_less (set_node_t *left, set_node_t *right) { dbg_assert_ptr (left); dbg_assert_ptr (right); restrack_resource_t *l, *r; l = PARENT_OF (restrack_resource_t, node, left); r = PARENT_OF (restrack_resource_t, node, right); return l->resource < r->resource; } /** * Get and eventually initialise global context. * \return unique instance pointer */ static restrack_t * restrack_get_instance (void) { restrack_t *ctx = &restrack_global; if (!ctx->inited) { set_init (&ctx->resources_set, restrack_resource_less); ctx->inited = true; } if (!ctx->atexit_done) { atexit (restrack_uninit); ctx->atexit_done = true; } return ctx; } static restrack_resource_t * restrack_resource_new (void *resource, const char *function, int line) { dbg_assert_ptr (resource); restrack_resource_t *r; /* Malloc should be replaced with a slab allocator. */ r = malloc (sizeof (*r)); if (!r) dbg_fatal ("exhausted virtual memory"); set_node_init (&r->node); r->resource = resource; r->references = 0; r->creator_function = function; r->creator_line = line; set_init (&r->changes_set, restrack_resource_change_less); return r; } static void restrack_resource_delete (restrack_resource_t *r) { dbg_assert_ptr (r); set_node_t *i, *last, *in; /* Remove all changes node. */ for (i = set_begin (&r->changes_set), last = set_end (&r->changes_set); i != last; i = in) { in = set_next (&r->changes_set, i); set_remove (&r->changes_set, i); free (PARENT_OF (restrack_resource_t, node, i)); } dbg_assert (set_empty (&r->changes_set)); free (r); } /** * Dump a resource. * \param r resource information structure */ static void restrack_resource_dump (restrack_resource_t *r) { dbg_assert_ptr (r); fprintf (stderr, "[%#8x] %3d (created: %s:%d)\n", (u32) r->resource, r->references, r->creator_function, r->creator_line); set_node_t *i, *last; for (i = set_begin (&r->changes_set), last = set_end (&r->changes_set); i != last; i = set_next (&r->changes_set, i)) { restrack_resource_change_t *c = PARENT_OF (restrack_resource_change_t, node, i); fprintf (stderr, " %s:%d: %3d\n", c->function, c->line, c->change); } } void restrack_create (void *owner, void *resource, const char *function, int line, int initial) { dbg_assert_ptr (resource); restrack_t *ctx = restrack_get_instance (); restrack_resource_t *r; set_node_t *n; /* Check for duplicates. */ r = restrack_resource_new (resource, function, line); n = set_find (&ctx->resources_set, &r->node); if (n) { fprintf (stderr, "!! Duplicate resource creation\n"); restrack_resource_dump (PARENT_OF (restrack_resource_t, node, n)); fputc ('\n', stderr); restrack_resource_delete (r); } else { /* No duplicates, insert the new node. */ dbg_check (set_insert (&ctx->resources_set, &r->node)); } /* In all cases, update the node. */ restrack_update (owner, resource, function, line, initial); } void restrack_update (void *owner, void *resource, const char *function, int line, int change) { dbg_assert_ptr (resource); if (change == 0) return; restrack_t *ctx = restrack_get_instance (); set_node_t *n; /* Look up the specified resource. */ restrack_resource_t k; set_node_init (&k.node); k.resource = resource; n = set_find (&ctx->resources_set, &k.node); /* If not found, this is a fatal error. */ if (!n) { dbg_fatal ("!! Unknown resource update\n" " %s:%d: %3d\n", function, line, change); } else { /* Else, update. */ restrack_resource_t *r = PARENT_OF (restrack_resource_t, node, n); r->references += change; restrack_resource_change_t ck; set_node_init (&ck.node); ck.function = function; ck.line = line; n = set_find (&r->changes_set, &ck.node); /* Update an entry or create a new one. */ if (n) { PARENT_OF (restrack_resource_change_t, node, n)->change += change; } else { restrack_resource_change_t *c; /* Malloc should be replaced with a slab allocator. */ c = malloc (sizeof (*c)); if (!c) dbg_fatal ("exhausted virtual memory"); set_node_init (&c->node); c->function = function; c->line = line; c->change = change; dbg_check (set_insert (&r->changes_set, &c->node)); } /* Test for too many releases. */ if (r->references < 0) { fprintf (stderr, "!! Negative resource reference count\n" " %s:%d: %3d\n", function, line, change); restrack_resource_dump (r); fputc ('\n', stderr); } } } void restrack_destroy (void *owner, void *resource, const char *function, int line, int change) { dbg_assert_ptr (resource); restrack_t *ctx = restrack_get_instance (); set_node_t *n; /* Look up the specified resource. */ restrack_resource_t k; set_node_init (&k.node); k.resource = resource; n = set_find (&ctx->resources_set, &k.node); /* If not found, this is a fatal error. */ if (!n) { dbg_fatal ("!! Unknown resource destruction\n" " %s:%d: %3d\n", function, line, change); } else { /* Else, update the entry... */ restrack_update (owner, resource, function, line, change); #if !CONFIG_RESTRACK_KEEP /* ...and remove it. */ set_remove (&ctx->resources_set, n); restrack_resource_t *r = PARENT_OF (restrack_resource_t, node, n); if (r->references != 0) { fprintf (stderr, "!! Unbalanced resource destructed\n" " %s:%d: %3d\n", function, line, change); restrack_resource_dump (r); fputc ('\n', stderr); } restrack_resource_delete (r); #endif /* !CONFIG_RESTRACK_KEEP */ } } bool restrack_check (void) { restrack_t *ctx = restrack_get_instance (); set_node_t *i, *last; bool errors = false; /* Travel the resources set and bark if unbalanced resources are found. */ for (i = set_begin (&ctx->resources_set), last = set_end (&ctx->resources_set); i != last; i = set_next (&ctx->resources_set, i)) { restrack_resource_t *r = PARENT_OF (restrack_resource_t, node, i); if (r->references != 0) { if (!errors) { errors = true; fprintf (stderr, "!! Unbalanced resources follow:\n"); } restrack_resource_dump (r); } } if (errors) fputc ('\n', stderr); return !errors; } void restrack_uninit (void) { restrack_t *ctx = &restrack_global; if (ctx->inited) { /* First check for leaks. */ restrack_check (); /* Remove all resources nodes. */ set_node_t *i, *last, *in; for (i = set_begin (&ctx->resources_set), last = set_end (&ctx->resources_set); i != last; i = in) { in = set_next (&ctx->resources_set, i); set_remove (&ctx->resources_set, i); restrack_resource_t *r = PARENT_OF (restrack_resource_t, node, i); restrack_resource_delete (r); } /* Prepare for an eventual reinitialisation. */ dbg_assert (set_empty (&ctx->resources_set)); ctx->inited = false; } }