/*  File: memsubs.c
 *  Author: Richard Durbin (rd@sanger.ac.uk)
 *  Copyright (C) J Thierry-Mieg and R Durbin, 1998
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@mrc-lmb.cam.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -------------------------------------------------------------------
 * This file is part of the ACEDB genome database package, written by
 * 	Richard Durbin (MRC LMB, UK) rd@sanger.ac.uk, and
 *	Jean Thierry-Mieg (CRBM du CNRS, France) mieg@kaa.cnrs-mop.fr
 *
 * Description:
 * Exported functions:
 * HISTORY:
 * Last edited: Dec 19 11:16 2001 (edgrif)
 * * Aug 25 15:58 2000 (edgrif): Add check for users "datasize" limit, sometimes
 *              users forget to increase this.
 * * sept 99, threadsafe, exept the reporter values totMessAllloc, never mind
 * * May 19 15:13 1999 (fw): changed all functions that will require
 *              memory to be allocated to receive filename and lineno
 *              from the pre-processor macro, so it's location
 *              in the user-code can be tracked.
 * Created: Thu Aug 20 16:54:55 1998 (rd)
 *-------------------------------------------------------------------
 */

/* define MALLOC_CHECK here to check mallocs - also in regular.h */

#include "regular.h"

#if defined(NEXT) || defined(HP) || defined(MACINTOSH) 
extern void* malloc(mysize_t size) ;
#elif !defined(WIN32) && !defined(FreeBSD) && !defined (DARWIN)
#include <malloc.h>   /* normal machines  */
#endif


/* just test new datasize check code on linux and alphas for now....         */
#if defined(ALPHA) || defined(LINUX) || defined(OPTERON) || defined(SOLARIS)
#include <sys/time.h>
#include <sys/resource.h>
#endif



/********** primary type definition **************/

typedef struct _STORE_HANDLE_STRUCT {
  STORE_HANDLE next ;	/* for chaining together on Handles */
  STORE_HANDLE back ;	/* to unchain */
  void (*final)(void*) ;	/* finalisation routine */
  int size ;			/* of user memory to follow */
#ifdef MALLOC_CHECK
  int check1 ;			/* set to known value */
#endif
} STORE_HANDLE_STRUCT ;

/*********************************************************************/
/********** memory allocation - messalloc() and handles  *************/

static int numMessAlloc = 0 ;
static int totMessAlloc = 0 ;


  /* Calculate to size of an STORE_HANDLE_STRUCT rounded to the nearest upward
     multiple of sizeof(double). This avoids alignment problems when
     we put an STORE_HANDLE_STRUCT at the start of a memory block */

#define STORE_OFFSET ((((sizeof(STORE_HANDLE_STRUCT)-1)/MALLOC_ALIGNMENT)+1)\
                             * MALLOC_ALIGNMENT)


  /* macros to convert between a void* and the corresponding STORE_HANDLE */

#define toAllocUnit(x) (STORE_HANDLE) ((char*)(x) - STORE_OFFSET)
#define toMemPtr(unit)((void*)((char*)(unit) + STORE_OFFSET))

#ifdef MALLOC_CHECK

static BOOL handlesInitialised = FALSE;

#ifndef MULTITHREAD
static Array handles = 0 ;
#endif

  /* macro to give the terminal check int for an STORE_HANDLE_STRUCT */
  /* unit->size must be a multiple of sizeof(int) */
#define check2(unit)  *(int*)(((char*)toMemPtr(unit)) + (unit)->size)

static void checkUnit (STORE_HANDLE unit) ;
static int handleOrder (void *a, void  *b)
{ return (*((STORE_HANDLE *)a) == *((STORE_HANDLE *)b)) ? 0 : 
	  (*((STORE_HANDLE *)a) > *((STORE_HANDLE *)b)) ? 1 : -1 ;
}
#endif

#if defined(MALLOC_CHECK)
static STORE_HANDLE_STRUCT handle0 ;
#endif

/************** halloc(): key function - messalloc() calls this ****************/

void *halloc_dbg(int size, STORE_HANDLE handle,
		 const char *hfname, int hlineno) 
{ 
  STORE_HANDLE unit ;

  /* Basic check on size which _must_ be greater than zero.                  */
  if (size <= 0)
    {
      uMessSetErrorOrigin(hfname, hlineno),
	uMessCrash(HANDLE_ALLOC_FAILED_STR
		   " caused by serious internal error, acedb requested zero or fewer bytes: %d.",
		   size) ;
    }
  
#ifdef MALLOC_CHECK
  if (!handlesInitialised)		/* initialise */
    { handlesInitialised = TRUE;
      /* BEWARE, arrayCreate calls handleAlloc, line above must precede
         following line to avoid infinite recursion */
#ifndef MULTITHREAD
      handles = arrayCreate (16, STORE_HANDLE) ;
      array (handles, 0, STORE_HANDLE) = &handle0 ;
#endif
      handle0.next = 0 ;
    }

  while (size % INT_ALIGNMENT) size++ ; /* so check2 alignment is OK */
  unit = (STORE_HANDLE) malloc(STORE_OFFSET + size + sizeof(int)) ;
  if (unit) memset (unit, 0, STORE_OFFSET + size + sizeof(int)) ;
#else  /* !MALLOC_CHECK */
  unit = (STORE_HANDLE) malloc(STORE_OFFSET + size) ;
  if (unit) memset (unit, 0, STORE_OFFSET + size) ;
#endif  /* !MALLOC_CHECK */

  /* Oh, oh, out of memory so messcrash, if possible check users process     */
  /* datasize limit to see if they could increase it.                        */
  if (!unit)
    {
#if defined(RLIMIT_DATA)
      struct rlimit limit ;
      char buffer[512] ;				    /* Don't use dynamic buffer because of */
							    /* potential recursion.*/
#endif
      char *user_limit = "" ;

#if defined(RLIMIT_DATA)				    /* Usually in sys/resource.h */
      /* We look at RLIMIT_DATA to see what size data area the users process */
      /* is allowed, this includes initialised, uninitialised and heap data. */
      /* Depending on this value we give the user advice about what to do.   */
      /* Note that we can't be too clever here because we call code that does*/
      /* lots of mallocs that are not accounted for in our totMessAlloc.     */
      /* NOTE: if any call fails (sprintf etc), we don't output this bit.    */
      if (getrlimit(RLIMIT_DATA, &limit) == 0)
	{
	  if (limit.rlim_cur == RLIM_INFINITY)
	    user_limit = " Your machine seems to have run out of memory "
	      "as your current process datasize limit is set to \"unlimited\"." ;
	  else if (limit.rlim_cur == limit.rlim_max)
	    {
	      if (sprintf(&(buffer[0]),
			  " You may need to ask systems support to "
			  "raise your process \"datasize\" limit which is currently %lu bytes.",
			  limit.rlim_cur) > 0)
		user_limit = &(buffer[0]) ;
	    }
	  else
	    {
	      if (sprintf(&(buffer[0]),
			  " Your process datasize is currently %lu bytes "
			  "but you could raise it to %lu bytes%s using "
			  "the limit/unlimit or ulimit shell commands.",
			  limit.rlim_cur, limit.rlim_max,
			  limit.rlim_max == RLIM_INFINITY ? " (max. possible)" : "") > 0)
		user_limit = &(buffer[0]) ;
	    }
	}
#endif

      uMessSetErrorOrigin(hfname, hlineno),
	uMessCrash (HANDLE_ALLOC_FAILED_STR
		    " when acedb requested %d bytes, acedb has already allocated %d bytes.%s", 
		    size, totMessAlloc, user_limit) ;
    }

#ifdef MALLOC_CHECK
#ifndef MULTITHREAD
  if (!handle)
    handle = &handle0 ;
#endif
#endif /* MALLOC_CHECK */

  if (handle) 
    { unit->next = handle->next ;
      unit->back = handle ;
      if (handle->next) (handle->next)->back = unit ;
      handle->next = unit ;
    }

  unit->size = size ;
#ifdef MALLOC_CHECK
  unit->check1 = 0x12345678 ;
  check2(unit) = 0x12345678 ;
#endif /* MALLOC_CHECK */

  ++numMessAlloc ;
  totMessAlloc += size ;  /* not thread safe */

  return toMemPtr(unit) ;
}

void blockSetFinalise(void *block, void (*final)(void *))
{ STORE_HANDLE unit = toAllocUnit(block);

  if (unit->final)
    messcrash("blockSetFinalise() trying to overwrite finalisation func ptr");
  unit->final = final ;
}  

/***** handleAlloc() - does halloc() + blockSetFinalise() - archaic *****/

void *handleAlloc_dbg (void (*final)(void*), STORE_HANDLE handle, int size,
		       const char *hfname, int hlineno)
{
  void *result = halloc_dbg(size, handle, hfname, hlineno) ;
  if (final) 
    blockSetFinalise(result, final);

  return result;
}

/****************** useful utility ************/

char *strnew_dbg(char *old, STORE_HANDLE handle, const char *hfname, int hlineno)
{ char *result = 0 ;
  if (old)
    { result = (char *)halloc_dbg(1+strlen(old), handle, hfname, hlineno) ;
      strcpy(result, old);
    }
  return result;
}

/****************** messfree ***************/

void uMessFree (void *cp)
{
  STORE_HANDLE unit = toAllocUnit(cp) ;

#ifdef MALLOC_CHECK
  checkUnit (unit) ;
  unit->check1 = 0x87654321; /* test for double free */
#endif

  if (unit->final)
    (*unit->final)(cp) ;

  if (unit->back) 
    { (unit->back)->next = unit->next;
      if (unit->next) (unit->next)->back = unit->back;
    }
  
  --numMessAlloc ;
  totMessAlloc -= unit->size ;
  free (unit) ;
}

/************** create and destroy handles **************/

/* NOTE: handleDestroy is #defined in regular.h to be messfree */
/* The actual work is done by handleFinalise, which is the finalisation */
/* routine attached to all STORE_HANDLEs. This allows multiple levels */
/* of free-ing by allocating new STORE_HANDLES on old ones, using */
/* handleHandleCreate. handleCreate is simply defined as handleHandleCreate(0) */

static void handleFinalise (void *p)
{
  STORE_HANDLE handle = (STORE_HANDLE)p;
  STORE_HANDLE next, unit = handle->next ;

/* do handle finalisation first */  
  if (handle->final)
    (*handle->final)((void *)handle->back);

      while (unit)
    { 
#ifdef MALLOC_CHECK
      checkUnit (unit) ;
      unit->check1 = 0x87654321; /* test for double free */
#endif
      if (unit->final)
	(*unit->final)(toMemPtr(unit)) ;
      next = unit->next ;
      --numMessAlloc ;
      totMessAlloc -= unit->size ;
      free (unit) ;
      unit = next ;
    }

#ifdef MALLOC_CHECK
#ifndef MULTITHREAD
      arrayRemove (handles, &p, handleOrder) ;
#endif
#endif

/* This is a finalisation routine, the actual store is freed in messfree,
   or another invokation of itself. */
}
  
void handleSetFinalise(STORE_HANDLE handle, void (*final)(void *), void *arg)
{ handle->final = final;
  handle->back = (STORE_HANDLE)arg;
}

/* create a handle that is allocated upon another handle */
STORE_HANDLE handleHandleCreate_dbg(STORE_HANDLE handle,
				    const char *hfname, int hlineno)
{ 
  STORE_HANDLE res = (STORE_HANDLE) handleAlloc_dbg(handleFinalise, 
						    handle,
						    sizeof(STORE_HANDLE_STRUCT),
						    hfname, hlineno);
#ifdef MALLOC_CHECK
  /* NB call to handleAlloc above ensures that handles is initialised here */
#ifndef MULTITHREAD
  arrayInsert (handles, &res, handleOrder) ;
#endif
#endif /* MALLOC_CHECK */
  res->next = res->back = 0 ; /* No blocks on this handle yet. */
  res->final = 0 ; /* No handle finalisation */
  return res ;
}

BOOL finalCleanup = FALSE ;

#ifdef MALLOC_CHECK
void handleCleanUp (void) 
{ finalCleanup = TRUE ;
  handleFinalise ((void *)&handle0) ;
}
#endif /* MALLOC_CHECK */


/************** checking functions, require MALLOC_CHECK *****/

#ifdef MALLOC_CHECK
static void checkUnit (STORE_HANDLE unit)
{
  if (unit->check1 == 0x87654321)
    messerror ("Block at %x freed twice - bad things will happen.",
	       toMemPtr(unit));
  else
    if (unit->check1 != 0x12345678)
      messerror ("Malloc error at %x length %d: "
		 "start overwritten with %x",
		 toMemPtr(unit), unit->size, unit->check1) ;
  
  if (check2(unit) != 0x12345678)
    messerror ("Malloc error at %x length %d: "
	       "end overwritten with %x",
	       toMemPtr(unit), unit->size, check2(unit)) ;
}

void messalloccheck (void)
{
#ifndef MULTITHREAD

  int i ;
  STORE_HANDLE unit ;

  if (!handles) return ;

  for (i = 0 ; i < arrayMax(handles) ; ++i) 
    for (unit = arr(handles,i,STORE_HANDLE)->next ; unit ; unit=unit->next)
      checkUnit (unit) ;

#endif
}
#else  /* !MALLOC_CHECK */
void messalloccheck (void) {}
#endif /* !MALLOC_CHECK */

/******************* status monitoring functions ******************/

void handleInfo (STORE_HANDLE handle, int *number, int *size)
{
  STORE_HANDLE unit = handle->next;

  *number = 0;
  *size = 0;

  while (unit)
    { ++*number ;
      *size += unit->size ;
      unit = unit->next ;
    }
}

/* not thread safe, tant pis */

int messAllocStatus (int *mem)
{ 
  *mem = totMessAlloc ;
  return numMessAlloc ;
}

/*************************** end of file ************************/
/****************************************************************/
