/*
** mg_dm.c for  in 
** 
** Made by vianney rancurel
** Login   <vianney@epita.fr>
** 
** Started on  Wed Aug 25 12:15:58 1999 vianney rancurel
** Last update Thu Oct 28 20:16:15 1999 
*/
#include "mg_dm.h"

/* initializes a debug malloc structure. 
   Note that dm->id MUST be provided by the user (see id_new(3)). */
VOID_FUNC		dm_init(dm)
t_dm			*dm;
{
  dm->cumulated = 0;
  dm->in_use = 0;
  dm->verbose = FALSE;
  dm->go_on = FALSE;
  dm->regexp = NULL;
}

/* register a pointer to a debug malloc environnement.
   Comment1 is the major comment. Comment2 is the minor comment, it is
   in the form procedure_cookie:item_cookie. Comment1 is passed through
   all procedures and identifies the requester. Comment2 is used locally
   as described in dm_unregister_ptr(3).
   It aborts(3) if there is an error in the debug malloc routines themselves
   (avoiding self debugging).
   Returns a debug malloc elt. */
t_dm_elt		*dm_register_ptr(dm,ptr,size,comment1,comment2)
t_dm			*dm;
VOID_PTR		ptr;
size_t			size;
char			*comment1;
char			*comment2;
{
  t_dm_elt		*dme;
  t_status		dmstatus;
  t_hash_elt		*he;

  if (he = id_get(dm->id,ptr))
    {
      dme = (t_dm_elt *)(he->value);
    }
  else
    {
      if ((dme = alloc_malloc(sizeof (t_dm_elt),
			      "dm_register_ptr",
			      "dm_register_ptr:dme",
			      &dmstatus)) == NULL)
	{
	  err_print(-dmstatus,"dm_register_ptr: allocing dme");
	  abort();
	}
       if ((dmstatus = id_add(dm->id,
			      ptr,
			      dme)) != 0)
	 {
	   err_print(-dmstatus,"dm_register_ptr: id_add");
	   abort();
	 }
    }
  dme->ptr = ptr;
  dme->size = size;
  dme->comment1 = comment1;
  dme->comment2 = comment2;
  dme->available = FALSE;
  return (dme);
}

/* unregisters a pointer from a debug malloc env.
   If ptr doesn't exist, it will abort(3) unless dm->go_on is TRUE.
   If ptr exists, comment2 will be matched (using gmatch(3)) with the one
   registered. E.g. if a pointer was registered with 
   "dict_str_override:value" and unregistered with "*:value", it will
   be OK, otherwise it will print a warning. This facility allows the
   programmer to allocate a memory zone in a function (with the possibility
   of identifying it) and free it in another one keeping consistence.
   This function also tells if the pointer was previously freed by
   another routine (it doesn't really free the debug malloc elt, it marks
   it as available).
   Returns the debug malloc elt marked as available. */
t_dm_elt		*dm_unregister_ptr(dm,ptr,comment1,comment2)
t_dm			*dm;
VOID_PTR		ptr;
char			*comment1;
char			*comment2;
{
  t_hash_elt		*he;
  t_dm_elt		*dme;
  t_status		dmstatus;

  if ((he = id_get(dm->id,ptr)) == NULL)
    {
      fprintf(stderr,"trying to unmark a non-existent ptr %p %s-%s\n",
	      ptr,
	      comment1,
	      comment2);
      if (dm->go_on)
	return (NULL);
      else
	abort();
    }
  dme = (t_dm_elt *)(he->value);
  if (dme->available)
    {
      fprintf(stderr,
	      "trying to unmark a previously freed ptr %p %s-%s (%s-%s)\n",
	      ptr,
	      comment1,
	      comment2,
	      dme->comment1,
	      dme->comment2);
      abort();
    }
  if (!gmatch(dme->comment2,comment2))
    {
      fprintf(stderr,"warning %p: %s-%s != %s-%s\n",
	      ptr,
	      comment1,
	      comment2,
	      dme->comment1,
	      dme->comment2);
    }
  dme->available = TRUE;
  return (dme);
}

/* mallocs-and-registers the pointer.
   Returns a pointer. */
VOID_PTR		dm_malloc(dm,size,comment1,comment2,status)
t_dm			*dm;
size_t			size;
char			*comment1;
char			*comment2;
t_status		*status;
{
  VOID_PTR		ptr;
  
  if ((ptr = alloc_malloc(size,
			  "dm_malloc",
			  "dm_malloc:ptr",
			  status)) == NULL)
    {
      return (NULL);
    }
  if (dm->verbose)
    {
      if (dm->regexp)
	if (!gmatch(comment2,dm->regexp))
	  goto no;
      fprintf(stderr,"malloced %p %d %s-%s\n",ptr,size,comment1,comment2);
    }
no:
  dm_register_ptr(dm,ptr,size,comment1,comment2);
  dm->cumulated += size;
  dm->in_use += size;
  return (ptr);
}

/* reallocs-and-registers a pointer.
   Returns a pointer */
VOID_PTR		dm_realloc(dm,old_ptr,size,comment1,comment2,status)
t_dm			*dm;
VOID_PTR		old_ptr;
size_t			size;
char			*comment1;
char			*comment2;
t_status		*status;
{
  VOID_PTR		ptr;
  t_dm_elt		*dme;
  t_hash_elt		*he;
  t_status		dmstatus;

  if ((he = id_get(dm->id,old_ptr)) == NULL)
    {
      fprintf(stderr,"trying to realloc a non-existent ptr %p\n",old_ptr);
      abort();
    }
  if ((ptr = realloc_realloc(old_ptr,
			     size,
			     comment1,
			     comment2,
			     status)) == NULL)
    {
      return (NULL);
    }
  dme = (t_dm_elt *)(he->value);
  if (dm->verbose)
    {
      if (dm->regexp)
	if (!gmatch(comment2,dm->regexp))
	  goto no;
      fprintf(stderr,"realloced %p (was %p %d) %d %s-%s\n",
	      ptr,
	      old_ptr,
	      dme->size,
	      size,
	      comment1,
	      comment2);
    }
no:
  dm->in_use -= dme->size;
  dme->available = TRUE;
  dme = dm_register_ptr(dm,ptr,size,comment1,comment2);
  dm->in_use += dme->size;
  dm->cumulated += dme->size;
  return (ptr);
}

/* frees-and-unregisters a pointer. */
VOID_FUNC		dm_free(dm,ptr,comment1,comment2)
t_dm			*dm;
VOID_PTR		ptr;
char			*comment1;
char			*comment2;
{
  t_dm_elt		*dme;

  if (dme = dm_unregister_ptr(dm,ptr,comment1,comment2))
    dm->in_use -= dme->size;
  free_free(ptr,comment1,comment2);
  if (dm->verbose)
    {
      if (dm->regexp)
	if (!gmatch(comment2,dm->regexp))
	  goto no;
      fprintf(stderr,"freed %p %s-%s\n",ptr,comment1,comment2);
    }
no:
  return ;
}

/* unregisters a pointer.
   Some routines (such as XDestroyImage(3X)) frees data that the user
   has allocated. To keep consistence when examining memory status we
   unmark it. */
VOID_FUNC		dm_unmark(dm,ptr,comment1,comment2)
t_dm			*dm;
VOID_PTR		ptr;
char			*comment1;
char			*comment2;
{
  t_dm_elt		*dme;

  dme = dm_unregister_ptr(dm,ptr,comment1,comment2);
  dm->in_use -= dme->size;
  if (dm->verbose)
    fprintf(stderr,"unmarked %p %s-%s\n",ptr,comment1,comment2);
}

/* is a t_dict_walk_proc.
   It is used internally by dm_show(3). */
t_status		dm_show_walk(he,dm)
t_hash_elt		*he;
t_dm			*dm;
{
  t_dm_elt		*dme;

  dme = (t_dm_elt *)(he->value);
  if (dme->available)
    return (0);
  fprintf(stderr,"%p %d %s-%s\n",
	  dme->ptr,
	  dme->size,
	  dme->comment1,
	  dme->comment2);
  return (0);
}

/* displays a status about the current pointers allocated. 
   It also shows the cumulated memory size used. */
VOID_FUNC		dm_show(dm)
t_dm			*dm;
{
  t_status		dmstatus;

  fprintf(stderr,"cumulated=%d\n",dm->cumulated);
  fprintf(stderr,"in_use=%d\n",dm->in_use);
  dmstatus = id_walk(dm->id,
		     (t_id_walk_proc)dm_show_walk,
		     dm);
  assert(dmstatus == 0);
}

/* is a t_dict_walk_proc.
   It is used internally by dm_destroy(3). */
t_status		dm_destroy_walk(he,dm)
t_hash_elt		*he;
t_dm			*dm;
{
  t_dm_elt		*dme;

  dme = (t_dm_elt *)(he->value);
  free_free(he->value,
	    "dm_destroy_walk",
	    "*:dme");
  return (0);
}

/* destroys all the debug malloc elts.
   Note that it doesn't destroy the id (see id_new(3))
   nor the debug malloc structure. */
VOID_FUNC		dm_destroy(dm)
t_dm			*dm;
{
  t_status		dmstatus;

  dm->cumulated = 0;
  dm->in_use = 0;
  dmstatus = id_walk(dm->id,
		     (t_id_walk_proc)dm_destroy_walk,
		     dm);
  assert(dmstatus == 0);
}
