/* free(3) implementation
   Copyright 1993, 1994 Tristan Gingold
		  Written December 1993 by Tristan Gingold
		  
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 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
General Public License for more details.

You should have received a copy of the GNU General Public License 
along with this program; see the file COPYING.  If not, write to
the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address marc@david.saclay.cea.fr,
   or (US/French mail) as Tristan Gingold 
   			  8 rue Parmentier
   			  F91120 PALAISEAU
   			  FRANCE
*/
#define _MALLOC_INTERNAL
#include "malloc.h"

#ifdef CHECKER
#include "errlist.h"
#endif
#include "message.h"

struct malloc_header *_youngerblock;
struct malloc_header *_olderblock;
unsigned int _agedblock;

#ifndef HAS_INLINE_LOG_SIZE
/* Return log2 of size.
 * If the log is greather (or equal) than HASH_SIZE, returns HASH_SIZE - 1
 * See bitops.h
 */
int
log_size(size_t size)
{
 int log = 0;
 while((size >>= 1) != 0)
   log++;
 if (log >= HASH_SIZE)
   log = HASH_SIZE - 1;
 return log;
}
#endif

/* Subroutine of free
 * _internal_free is also called by malloc
 */
void
_internal_free(struct malloc_header *block)
{
 struct malloc_header *tmp;
 size_t size;
 int l_size;

 size = block->size;
 
 /* Can we free memory ? */
 if(_lastblock == block && size >= FINAL_FREE_SIZE)
   {
#ifndef CHKR_IS_SAFE 
     if (block->next)
       chkr_printf(M_BAD_NLINK_4BLOCK, block);
#endif /* CHKR_IS_SAFE */
     /* Updates _lastblock */
     _lastblock = block->prev;
     _lastblock->next = NULL_HEADER;
     /* Is there only one block ? */
     if (_firstblock == block)
       _firstblock = NULL_HEADER;
     /* Free memory */
     morecore(-(size + HEADER_SIZE));
     return;	/* That's all */
   }
 
 /* compute the log size */
 l_size = log_size(size);
   
 block->state = MDFREE;
#ifndef NO_HEAPINDEX 
 /* Update _heapindex, the biggest size of free blocks available */
 if (l_size > _heapindex)
   _heapindex = l_size;
#endif /* NO_HEAPINDEX */
 
 tmp = _heapinfo[l_size];
 /* The list of free blocks must be sorted by size
  * It is the smallest free block of the list ?
  * This can be the case if there was no block in the list */
 if ( !tmp || tmp->size < size)
   {
     /* put it at the begin of the list */
     block->info.free.next = tmp;
     block->info.free.prev = NULL_HEADER;
     /* Set the 'prev' link of the next block, if necessary */
     if (tmp)
       tmp->info.free.prev = block;
     _heapinfo[l_size] = block;
   }
 else
   {
     /* Find the good slot */
     while (tmp->info.free.next && tmp->info.free.next->size > size)
       tmp = tmp->info.free.next;
     /* put it after tmp */
     block->info.free.prev = tmp;
     block->info.free.next = tmp->info.free.next;
     /* Set the 'prev' link of the next block */
     if (tmp->info.free.next)
       tmp->info.free.next->info.free.prev = block;
     tmp->info.free.next = block;
   }
}

/* The well-known free function */
void
_free(__ptr_t ptr)
{
 struct malloc_header *tmp;
 struct malloc_header *tmp1; 
 int l_size;
 
 /* ANSI-C test */
 if (ptr == (__ptr_t)0)
   return;
   
 /* Compute the address of the header from ptr */
 tmp = (struct malloc_header*)(ptr - HEADER_SIZE - be_red_zone);
 /* Check if the address is good */
 if (tmp != find_header(ptr))
   {
     chkr_errno = E_BADADDR;
     chkr_perror();
     return;
   }
 /* A little check: the block must be busy, to be freed */
 if (tmp->state != MDBUSY)
   {
     chkr_errno = E_FREEFFRAG;
     chkr_perror();
     return;
   }
   
#ifdef CHKR_HEAPBITMAP
  /* Disable reading and writing right in the block */
  chkr_set_right((__ptr_t)tmp + HEADER_SIZE + be_red_zone,
                 tmp->info.busy.real_size, CHKR_UN);
#endif /* CHKR_HEAPBITMAP */

#ifndef NO_AGE
 /* save the stack */
#ifdef CHKR_SAVESTACK
    chkr_save_stack(	/* save the stack */
    	(__ptr_t*)((u_int)tmp + HEADER_SIZE),
    	0, /* number of frame to forget. with 0, show malloc */
    	(tmp->size - af_red_zone) / sizeof(void*)); /* number of frames to save */
#endif /* CHKR_SAVESTACK */
 /* Put it in the aged list */
 tmp->info.aged.next = _youngerblock;
 tmp->info.aged.prev = NULL_HEADER;
 tmp->state = MDAGED;
 if (_youngerblock)
   _youngerblock->info.aged.prev = tmp;
 _youngerblock = tmp;
 if (_olderblock == NULL_HEADER)
   _olderblock = tmp;
 if(_agedblock >= BLOCK_AGE)
   { 
     /* 'tmp' is now the older block, which must be freed */
     tmp = _olderblock;
     if (tmp->state != MDAGED)
       chkr_printf(M_FOUND_AGED_BLOCK, tmp);
     _olderblock = tmp->info.aged.prev;
     _olderblock->info.aged.next = NULL_HEADER;
   }
 else
  {
     /* No too old block */
     _agedblock++;
     return;
  }
#endif /* NO_AGE */
 
 /* try to coalise this block with its next one */
 if ( (tmp1 = tmp->next) && tmp->next->state == MDFREE)
   {
     l_size = log_size(tmp1->size);
     if (_heapinfo[l_size] == tmp1)
       _heapinfo[l_size] = tmp1->info.free.next;	/* BUG: _heapindex */
     if (tmp1->info.free.next)
       tmp1->info.free.next->info.free.prev = tmp1->info.free.prev;
     if (tmp1->info.free.prev)
       tmp1->info.free.prev->info.free.next = tmp1->info.free.next;
     tmp->size += tmp1->size + HEADER_SIZE;
     tmp->next = tmp1->next;
     if (tmp->next)
       tmp->next->prev = tmp;
     if (_lastblock == tmp1)
      _lastblock = tmp;
   }
 
 /* try to coalise this block with its predecessor */
 if ( (tmp1 = tmp->prev) && tmp->prev->state == MDFREE)
   {
     l_size = log_size(tmp1->size);
     if (_heapinfo[l_size] == tmp1)
       _heapinfo[l_size] = tmp1->info.free.next;	/* BUG: _heapindex */
     if (tmp1->info.free.next)
       tmp1->info.free.next->info.free.prev = tmp1->info.free.prev;
     if (tmp1->info.free.prev)
       tmp1->info.free.prev->info.free.next = tmp1->info.free.next;
     tmp1->size += tmp->size + HEADER_SIZE;
     tmp1->next = tmp->next;
     if (tmp->next)
       tmp->next->prev = tmp1;
     if (_lastblock == tmp)
       _lastblock = tmp1;
     tmp = tmp1;
   }

 _internal_free(tmp);
}

/* The well-known free function */
void
free(__ptr_t ptr)
{
#ifdef CHKR_STACKBITMAP
 chkr_check_addr(&ptr, sizeof(__ptr_t), CHKR_RO);
#endif
 _free(ptr);
}

/*
 * Blocks must be like that:
 * +--------+-------------+----------------------------+-------------+
 * | header | be_red_zone |           block            | af_red_zone |
 * |        |             |                            |             |
 * +--------+-------------+----------------------------+-------------+
 *                        |<-------- real_size ------->|
 *          |<------------------------ size ------------------------>|                               
 */
/* Search the header for an address in a block.
 * Return NULL_HEADER if not found
 */
struct malloc_header *
find_header(const __ptr_t ptr)
{
  struct malloc_header *res;
  u_int addr;
  if (_firstblock == NULL_HEADER)
    return NULL_HEADER;
  res = _firstblock;
  addr = (u_int)ptr - HEADER_SIZE;
  for (; res != NULL_HEADER; res = res->next)
    {
      if ((addr >= (u_int)(res)) && (addr <= ((u_int)res + res->size)))
        return res;
    }
  return NULL_HEADER;
}

/* Get the age of block.
 * If no age, returns -1.
 */
int
get_age(struct malloc_header *block)
{
#ifdef NO_AGE
 return -1;
#else
 struct malloc_header *tmp;
 int age = 0;
 
 if (block->state != MDAGED || _youngerblock == NULL_HEADER)
   return -1;
 tmp = _youngerblock;
 do
   {
     if (tmp == block)
       return age;
     age++;
     tmp = tmp->info.aged.next;
   }
 while(tmp);
 return -1;
#endif
}
 
 