/*
 * user-level memory-allocation routines
 *
 * Copyright 1992 by Gray Watson and the Antaire Corporation
 *
 * This file is part of the malloc-debug package.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library (see COPYING-LIB); if not, write to the
 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * The author of the program may be contacted at gray.watson@antaire.com
 */

/*
 * This file contains the user-level calls to the memory allocation
 * routines.  It handles a lot of the miscellaneous support garbage for
 * chunk.c which is the real heap manager.
 */

#define MALLOC_DEBUG_DISABLE

#include "malloc.h"
#include "malloc_loc.h"

#include "chunk.h"
#include "compat.h"
#include "conf.h"
#include "dbg_values.h"
#include "error.h"
#include "error_str.h"
#include "error_val.h"
#include "heap.h"
#include "malloc_lp.h"

#if INCLUDE_RCS_IDS
LOCAL	char	*rcs_id =
  "$Id: malloc.c,v 1.1 2000/04/20 19:38:45 paul Exp $";
#endif

/*
 * exported variables
 */
/* logfile for dumping malloc info, MALLOC_LOGFILE env. var overrides this */
EXPORT	char		*malloc_logpath	= NULL;
/* internal malloc error number for reference purposes only */
EXPORT	int		malloc_errno = 0;

/* local routines */
LOCAL	int		malloc_startup(void);
EXPORT	void		malloc_shutdown(void);

/* local variables */
LOCAL	int		malloc_enabled	= FALSE; /* have we started yet? */
LOCAL	char		in_alloc	= FALSE; /* can't be here twice */
LOCAL	char		log_path[128]	= { NULLC }; /* storage for env path */

/* debug variables */
LOCAL	char		*malloc_address	= NULL;	/* address to catch */
LOCAL	int		address_count	= 0;	/* address argument */
LOCAL	char		start_file[128] = { NULLC }; /* file to start at */
LOCAL	int		start_line	= 0;	/* line in module to start */
LOCAL	int		start_count	= -1;	/* start after X */
LOCAL	int		check_interval	= -1;	/* check every X */

/****************************** local utilities ******************************/

/*
 * hexadecimal STR to int translation
 */
LOCAL	int	hex_to_int(char * str)
{
  int		ret;
  
  /* strip off spaces */
  for (; *str == ' ' || *str == '\t'; str++);
  
  /* skip a leading 0[xX] */
  if (*str == '0' && (*(str + 1) == 'x' || *(str + 1) == 'X'))
    str += 2;
  
  for (ret = 0;; str++) {
    if (*str >= '0' && *str <= '9')
      ret = ret * 16 + (*str - '0');
    else if (*str >= 'a' && *str <= 'f')
      ret = ret * 16 + (*str - 'a' + 10);
    else if (*str >= 'A' && *str <= 'F')
      ret = ret * 16 + (*str - 'A' + 10);
    else
      break;
  }
  
  return ret;
}

/*
 * a call to the alloc routines has been made, check the debug variables
 * returns [NO]ERROR.
 */
LOCAL	int	check_debug_vars(const char * file, const int line)
{
  static int	iterc = 0;
  
  if (in_alloc) {
    malloc_errno = MALLOC_IN_TWICE;
    _malloc_perror("check_debug_vars");
    /* malloc_perror may die already */
    _malloc_die();
    /*NOTREACHED*/
  }
  
  in_alloc = TRUE;
  
  if (! malloc_enabled)
    if (malloc_startup() != NOERROR)
      return ERROR;
  
  /* check start file/line specifications */
  if (! BIT_IS_SET(_malloc_debug, DEBUG_CHECK_HEAP)
      && start_file[0] != NULLC
      && file != NULL
      && strcmp(start_file, file) == 0
      && (line == 0 || line == start_line))
    BIT_SET(_malloc_debug, DEBUG_CHECK_HEAP);
  
  /* start checking heap after X times */
  if (start_count != -1 && --start_count == 0)
    BIT_SET(_malloc_debug, DEBUG_CHECK_HEAP);
  
  /* checking heap every X times */
  if (check_interval != -1) {
    if (++iterc >= check_interval) {
      BIT_SET(_malloc_debug, DEBUG_CHECK_HEAP);
      iterc = 0;
    }
    else
      BIT_CLEAR(_malloc_debug, DEBUG_CHECK_HEAP);
  }
  
  /* after all that, do we need to check the heap? */
  if (BIT_IS_SET(_malloc_debug, DEBUG_CHECK_HEAP))
    (void)_chunk_heap_check();
  
  return NOERROR;
}

/*
 * check out a pointer to see if we were looking for it.
 * may not return.
 */
LOCAL	void	check_var(const char * file, const int line, char * pnt)
{
  static int	addc = 0;
  
  if (malloc_address == NULL || pnt != malloc_address)
    return;
  
  if (++addc < address_count)
    return;
  
  if (BIT_IS_SET(_malloc_debug, DEBUG_LOG_BAD_POINTER))
    _malloc_message("found address '%#lx' after %d pass%s from '%s:%u'",
		    pnt, addc, (addc == 1 ? "" : "es"), file, line);
  malloc_errno = MALLOC_POINTER_FOUND;
  _malloc_perror("check_var");
}

/*
 * get the values of malloc environ variables
 */
LOCAL	void	get_environ(void)
{
  char		*env;
  
  /* get the malloc_debug value */
  env = (char *)getenv(DEBUG_ENVIRON);
  if (env != NULL)
    _malloc_debug = hex_to_int(env);
  
  /* get the malloc debug logfile name into a holding variable */
  env = (char *)getenv(LOGFILE_ENVIRON);
  if (env != NULL) {
    (void)strcpy(log_path, env);
    malloc_logpath = log_path;
  }
  
  /* watch for a specific address and die when we get it */
  env = (char *)getenv(ADDRESS_ENVIRON);
  if (env != NULL) {
    char	*addp;
    
    addp = index(env, ':');
    if (addp != NULL) {
      *addp = NULLC;
      address_count = atoi(addp + 1);
    }
    else
      address_count = 1;
    
    malloc_address = (char *)hex_to_int(env);
  }
  
  /* check the heap every X times */
  env = (char *)getenv(INTERVAL_ENVIRON);
  if (env != NULL)
    check_interval = atoi(env);
  
  /*
   * start checking the heap after X iterations OR
   * start at a file:line combination
   */
  env = (char *)getenv(START_ENVIRON);
  if (env != NULL) {
    char	*startp;
    
    BIT_CLEAR(_malloc_debug, DEBUG_CHECK_HEAP);
    
    startp = index(env, ':');
    if (startp != NULL) {
      *startp = NULLC;
      (void)strcpy(start_file, env);
      start_line = atoi(startp + 1);
      start_count = 0;
    }
    else
      start_count = atoi(env);
  }
}

/*************************** startup/shutdown calls **************************/

/*
 * startup the memory-allocation module
 */
LOCAL	int	malloc_startup(void)
{
  /* have we started already? */
  if (malloc_enabled)
    return ERROR;
  
  /* set this here so if an error occurs below, it will not try again */
  malloc_enabled = TRUE;
  
  /* get the environmental variables */
  get_environ();
  
  /* startup heap code */
  _heap_startup();
  
  /* startup the chunk lower-level code */
  if (_chunk_startup() == ERROR)
    return ERROR;
  
  return NOERROR;
}

/*
 * shutdown memory-allocation module, provide statistics if necessary
 */
EXPORT	void	malloc_shutdown(void)
{
  /* NOTE: do not test for IN_TWICE here */
  
  /* do we need to check the heap? */
  if (BIT_IS_SET(_malloc_debug, DEBUG_CHECK_HEAP))
    (void)_chunk_heap_check();
  
  /* dump some statistics to the logfile */
  if (BIT_IS_SET(_malloc_debug, DEBUG_LOG_STATS))
    _chunk_list_count();
  if (BIT_IS_SET(_malloc_debug, DEBUG_LOG_STATS))
    _chunk_stats();
  
  /* report on non-freed pointers */
  if (BIT_IS_SET(_malloc_debug, DEBUG_LOG_NONFREE))
    _chunk_dump_not_freed();
  
  /* NOTE: do not set malloc_enabled to false here */
}

/******************************** memory calls *******************************/

/*
 * allocate and return a SIZE block of bytes
 */
EXPORT	char	*malloc(unsigned int size)
{
  char		*newp;
  
  if (check_debug_vars(_malloc_file, _malloc_line) != NOERROR)
    return MALLOC_ERROR;
  
  newp = (char *)_chunk_malloc(_malloc_file, _malloc_line, size);
  check_var(_malloc_file, _malloc_line, newp);
  
  in_alloc = FALSE;
  
  return newp;
}

/*
 * allocate and return a block of bytes able to hold NUM_ELEMENTS of elements
 * of SIZE bytes and zero the block
 */
EXPORT	char	*calloc(unsigned int num_elements, unsigned int size)
{
  char		*newp;
  unsigned int	len = num_elements * size;
  
  if (check_debug_vars(_malloc_file, _malloc_line) != NOERROR)
    return CALLOC_ERROR;
  
  /* needs to be done here */
  _calloc_count++;
  
  /* alloc and watch for the die address */
  newp = (char *)_chunk_malloc(_malloc_file, _malloc_line, len);
  check_var(_malloc_file, _malloc_line, newp);
  
  (void)memset(newp, NULLC, len);
  
  in_alloc = FALSE;
  
  return newp;
}

/*
 * resizes OLD_PNT to SIZE bytes and return the new space after either copying
 * all of OLD_PNT to the new area or truncating
 */
EXPORT	char	*realloc(char * old_pnt, unsigned int new_size)
{
  char		*newp;
  
#if ALLOW_REALLOC_NULL
  if (old_pnt == NULL)
    return malloc(new_size);
#endif
  
  if (check_debug_vars(_malloc_file, _malloc_line) != NOERROR)
    return REALLOC_ERROR;
  
  check_var(_malloc_file, _malloc_line, old_pnt);
  newp = (char *)_chunk_realloc(_malloc_file, _malloc_line, old_pnt, new_size);
  check_var(_malloc_file, _malloc_line, newp);
  
  in_alloc = FALSE;
  
  return newp;
}

/*
 * release PNT in the heap, returning FREE_[NO]ERROR
 */
EXPORT	int	free(char * pnt)
{
  int		ret;
  if (!pnt)
    return NOERROR;
  
  if (check_debug_vars(_malloc_file, _malloc_line) != NOERROR)
    return FREE_ERROR;
  
  check_var(_malloc_file, _malloc_line, pnt);
  ret = _chunk_free(_malloc_file, _malloc_line, pnt);
  
  in_alloc = FALSE;
  
  return ret;
}

/******************************** utility calls ******************************/

/*
 * call through to _heap_map function, returns [NO]ERROR
 */
EXPORT	int	malloc_heap_map(void)
{
  if (check_debug_vars(NULL, 0) != NOERROR)
    return ERROR;
  
  _chunk_log_heap_map();
  
  in_alloc = FALSE;
  
  return NOERROR;
}

/*
 * verify pointer PNT, if PNT is 0 then check the entire heap.
 * returns MALLOC_VERIFY_[NO]ERROR
 */
EXPORT	int	malloc_verify(char * pnt)
{
  int	ret;
  
  if (check_debug_vars(NULL, 0) != NOERROR)
    return MALLOC_VERIFY_ERROR;
  
  if (pnt != 0)
    ret = _chunk_pnt_check("malloc_verify", pnt, 0, 0);
  else
    ret = _chunk_heap_check();
  
  in_alloc = FALSE;
  
  if (ret == NOERROR)
    return MALLOC_VERIFY_NOERROR;
  else
    return MALLOC_VERIFY_ERROR;
}

/*
 * set the global debug functionality flags to DEBUG (0 to disable).
 * returns [NO]ERROR
 */
EXPORT	int	malloc_debug(int debug)
{
  int	hold;
  
  _malloc_debug = debug;
  
  if (check_debug_vars(NULL, 0) != NOERROR)
    return MALLOC_ERROR;
  
  /* make sure that the not-changeable flags' values are preserved */
  
  hold = _malloc_debug & DEBUG_NOT_CHANGEABLE;
  debug &= ~DEBUG_NOT_CHANGEABLE;
  _malloc_debug = debug | hold;
  
  in_alloc = FALSE;
  
  return NOERROR;
}

/*
 * examine pointer PNT and returns SIZE, and FILE / LINE info on it
 * if any of the pointers are not NULL.
 * returns NOERROR or ERROR depending on whether PNT is good or not
 */
EXPORT	int	malloc_examine(char * pnt, unsigned int * size,
			       char ** file, unsigned int * line)
{
  int		ret;
  
  if (check_debug_vars(NULL, 0) != NOERROR)
    return ERROR;
  
  ret = _chunk_read_info(pnt, size, file, line);
  
  in_alloc = FALSE;
  
  if (ret == NOERROR)
    return NOERROR;
  else
    return ERROR;
}

/*
 * malloc version of strerror to return the string version of ERRNUM
 * returns the string for MALLOC_BAD_ERRNO if ERRNUM is out-of-range.
 */
EXPORT	char	*malloc_strerror(int errnum)
{
  /*
   * NOTE: should not check_debug_vars here because _malloc_perror calls this.
   */
  
  if (! IS_MALLOC_ERRNO(errnum))
    return malloc_errlist[MALLOC_BAD_ERRNO];
  else
    return malloc_errlist[errnum];
}
