/*
**	Copyright (c) 1994, University of Kansas, All Rights Reserved
**
**	This code will be used only if LY_FIND_LEAKS is defined.
**
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
**	10-30-97	modified to handle StrAllocCopy() and
**			  StrAllocCat(). - KW & FM
*/

/*
**	Disable the overriding of the memory routines for this file.
*/
#define NO_MEMORY_TRACKING

#include <HTUtils.h>
#include <LYexit.h>
#include <LYLeaks.h>
#include <LYUtils.h>
#include <LYGlobalDefs.h>

#ifdef LY_FIND_LEAKS

PRIVATE AllocationList *ALp_RunTimeAllocations = NULL;

#define LEAK_SUMMARY

#ifdef LEAK_SUMMARY

PRIVATE long now_allocated = 0;
PRIVATE long peak_alloced = 0;

PRIVATE long total_alloced = 0;
PRIVATE long total_freed = 0;

PRIVATE long count_mallocs = 0;
PRIVATE long count_frees = 0;

PRIVATE void CountMallocs ARGS1(long, size)
{
    ++count_mallocs;
    total_alloced += size;
    now_allocated += size;
    if (peak_alloced < now_allocated)
	peak_alloced = now_allocated;
}

PRIVATE void CountFrees ARGS1(long, size)
{
    ++count_frees;
    total_freed += size;
    now_allocated -= size;
}
#else
#define CountMallocs() ++count_mallocs
#define CountFrees() /* nothing */
#endif

/*
**  Purpose:	Add a new allocation item to the list.
**  Arguments:		ALp_new The new item to add.
**  Return Value:	void
**  Remarks/Portability/Dependencies/Restrictions:
**		Static function made to make code reusable in projects beyond
**		Lynx (some might ask why not use HTList).
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PRIVATE void AddToList ARGS1(
	AllocationList *,	ALp_new)
{
    /*
     *	Just make this the first item in the list.
     */
    ALp_new->ALp_Next = ALp_RunTimeAllocations;
    ALp_RunTimeAllocations = ALp_new;
}

/*
**  Purpose:	Find the place in the list where vp_find is currently
**		tracked.
**  Arguments:		vp_find A pointer to look for in the list.
**  Return Value:	AllocationList *	Either vp_find's place in the
**						list or NULL if not found.
**  Remarks/Portability/Dependencies/Restrictions:
**		Static function made to make code reusable in projects outside
**		of Lynx (some might ask why not use HTList).
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PRIVATE AllocationList *FindInList ARGS1(
	void *,		vp_find)
{
    AllocationList *ALp_find = ALp_RunTimeAllocations;

    /*
     *	Go through the list of allocated pointers until end of list
     *		or vp_find is found.
     */
    while (ALp_find != NULL) {
	if (ALp_find->vp_Alloced == vp_find) {
	    break;
	}
	ALp_find = ALp_find->ALp_Next;
    }

    return(ALp_find);
}

/*
**  Purpose:	Remove the specified item from the list.
**  Arguments:		ALp_del The item to remove from the list.
**  Return Value:	void
**  Remarks/Portability/Dependencies/Restrictions:
**		Static function made to make code reusable in projects outside
**		of Lynx (some might ask why not use HTList).
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PRIVATE void RemoveFromList ARGS1(
	AllocationList *,	ALp_del)
{
    AllocationList *ALp_findbefore = ALp_RunTimeAllocations;

    /*
     *	There is one special case, where the item to remove is the
     *		first in the list.
     */
    if (ALp_del == ALp_findbefore) {
	ALp_RunTimeAllocations = ALp_del->ALp_Next;
	return;
    }

    /*
     *	Loop through checking all of the next values, if a match
     *	don't continue.  Always assume the item will be found.
     */
    while (ALp_findbefore->ALp_Next != ALp_del) {
	ALp_findbefore = ALp_findbefore->ALp_Next;
    }

    /*
     *	We are one item before the one to get rid of.
     *	Get rid of it.
     */
    ALp_findbefore->ALp_Next = ALp_del->ALp_Next;
}

/*
 *  Make the malloc-sequence available for debugging/tracing.
 */
PUBLIC long LYLeakSequence NOARGS
{
    return count_mallocs;
}

/*
**  Purpose:	Print a report of all memory left unallocated by
**		Lynx code or attempted unallocations on
**		pointers that are not valid and then free
**		all unfreed memory.
**  Arguments:		void
**  Return Value:	void
**  Remarks/Portability/Dependencies/Restrictions:
**		This function should be registered for execution with the
**		atexit (stdlib.h) function as the first statement
**		in main.
**		All output of this function is sent to the file defined in
**		the header LYLeaks.h (LEAKAGE_SINK).
*/
PUBLIC void LYLeaks NOARGS
{
    AllocationList *ALp_head;
    size_t st_total = (size_t)0;
    FILE *Fp_leakagesink;

    if (LYfind_leaks == FALSE)
	return;

    /*
     *	Open the leakage sink to take all the output.
     *	Recreate the file each time.
     *	Do nothing if unable to open the file.
     */
    Fp_leakagesink = LYNewTxtFile(LEAKAGE_SINK);
    if (Fp_leakagesink == NULL) {
	return;
    }

    while (ALp_RunTimeAllocations != NULL) {
	/*
	 *  Take the head off of the run time allocation list.
	 */
	ALp_head = ALp_RunTimeAllocations;
	ALp_RunTimeAllocations = ALp_head->ALp_Next;

	/*
	 *  Print the type of leak/error.
	 *  Free off memory when we no longer need it.
	 */
	if (ALp_head->vp_Alloced == NULL) {
	    /*
	     *	If there is realloc information on the
	     *	bad request, then it was a bad pointer
	     *	value in a realloc statement.
	     */
	    fprintf(Fp_leakagesink, "%s.\n",
		    gettext("Invalid pointer detected."));
	    fprintf(Fp_leakagesink, "%s\t%ld\n",
		    gettext("Sequence:"),
		    ALp_head->st_Sequence);
	    fprintf(Fp_leakagesink, "%s\t%p\n",
		    gettext("Pointer:"), ALp_head->vp_BadRequest);

	    /*
	     *	Don't free the bad request, it is an invalid pointer.
	     *	If the free source information is empty, we
	     *	should check the realloc information
	     *	too since it can get passed bad pointer
	     *	values also.
	     */
	    if (ALp_head->SL_memory.cp_FileName == NULL) {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("FileName:"),
			ALp_head->SL_realloc.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_realloc.ssi_LineNumber);
	    } else {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("FileName:"),
			ALp_head->SL_memory.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_memory.ssi_LineNumber);
	    }
	} else {
	    size_t i_counter;
	    char *value = (char *)(ALp_head->vp_Alloced);

	    /*
	     *	Increment the count of total memory lost and
	     *	then print the information.
	     */
	    st_total += ALp_head->st_Bytes;

	    fprintf(Fp_leakagesink, "%s\n",
		    gettext("Memory leak detected."));
	    fprintf(Fp_leakagesink, "%s\t%ld\n",
		    gettext("Sequence:"),
		    ALp_head->st_Sequence);
	    fprintf(Fp_leakagesink, "%s\t%p\n",
		    gettext("Pointer:"),
		    ALp_head->vp_Alloced);
	    fprintf(Fp_leakagesink, "%s\t",
		    gettext("Contains:"));
	    for (i_counter = 0;
		 i_counter < ALp_head->st_Bytes &&
		 i_counter < MAX_CONTENT_LENGTH;
		 i_counter++) {
		if (isprint(UCH(value[i_counter]))) {
		    fprintf(Fp_leakagesink, "%c", value[i_counter]);
		} else {
		    fprintf(Fp_leakagesink, "|");
		}
	    }
	    fprintf(Fp_leakagesink, "\n");
	    fprintf(Fp_leakagesink, "%s\t%d\n",
				    gettext("ByteSize:"),
				    (int)(ALp_head->st_Bytes));
	    fprintf(Fp_leakagesink, "%s\t%s\n",
				    gettext("FileName:"),
				    ALp_head->SL_memory.cp_FileName);
	    fprintf(Fp_leakagesink, "%s\t%d\n",
				    gettext("LineCount:"),
				    ALp_head->SL_memory.ssi_LineNumber);
	    /*
	     *	Give the last time the pointer was realloced
	     *	if it happened also.
	     */
	    if (ALp_head->SL_realloc.cp_FileName != NULL) {
		fprintf(Fp_leakagesink, "%s\t%s\n",
			gettext("realloced:"),
			ALp_head->SL_realloc.cp_FileName);
		fprintf(Fp_leakagesink, "%s\t%d\n",
			gettext("LineCount:"),
			ALp_head->SL_realloc.ssi_LineNumber);
	    }
	    fflush(Fp_leakagesink);
	    FREE(ALp_head->vp_Alloced);
	}

	/*
	 *  Create a blank line and release the memory held
	 *  by the item.
	 */
	fprintf(Fp_leakagesink, "\n");
	FREE(ALp_head);
    }

    /*
     *	Give a grand total of the leakage.
     *	Close the output file.
     */
    fprintf(Fp_leakagesink, "%s\t%u\n",
	    gettext("Total memory leakage this run:"),
	    (unsigned)st_total);
#ifdef LEAK_SUMMARY
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Peak allocation"), peak_alloced);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Bytes allocated"), total_alloced);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Total mallocs"), count_mallocs);
    fprintf(Fp_leakagesink, "%s\t%ld\n", gettext("Total frees"), count_frees);
#endif
    fclose(Fp_leakagesink);

    HTSYS_purge(LEAKAGE_SINK);
#if defined(NCURSES) && defined(HAVE__NC_FREEALL)
    _nc_freeall();
#endif
}

/*
**  Purpose:	Capture allocations using malloc (stdlib.h) and track
**		the information in a list.
**  Arguments:	st_bytes	The size of the allocation requested
**				in bytes.
**		cp_File		The file from which the request for
**				allocation came from.
**		ssi_Line	The line number in cp_File where the
**				allocation request came from.
**  Return Value:	void *	A pointer to the allocated memory or NULL on
**				failure as per malloc (stdlib.h)
**  Remarks/Portability/Dependencies/Restrictions:
**		If no memory is allocated, then no entry is added to the
**		allocation list.
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PUBLIC void *LYLeakMalloc ARGS3(
	size_t,		st_bytes,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    void *vp_malloc;

    if (LYfind_leaks == FALSE)
	return (void *)malloc(st_bytes);

    /*
     *	Do the actual allocation.
     */
    vp_malloc = (void *)malloc(st_bytes);
    CountMallocs(st_bytes);

    /*
     *	Only on successful allocation do we track any information.
     */
    if (vp_malloc != NULL) {
	/*
	 *  Further allocate memory to store the information.
	 *  Just return on failure to allocate more.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    return(vp_malloc);
	}
	/*
	 *  Copy over the relevant information.
	 *  There is no need to allocate more memory for the
	 *  file name as it is a static string anyhow.
	 */
	ALp_new->st_Sequence = count_mallocs;
	ALp_new->vp_Alloced = vp_malloc;
	ALp_new->st_Bytes = st_bytes;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 *  Add the new item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return(vp_malloc);
}

/*
**  Purpose:	Add information about new allocation to the list,
**		after a call to malloc or calloc or an equivalent
**		function which may or may not have already created
**		a list entry.
**  Arguments:	vp_malloc	The pointer to newly allocated memory.
**  Arguments:	st_bytes	The size of the allocation requested
**				in bytes.
**		cp_File		The file from which the request for
**				allocation came from.
**		ssi_Line	The line number in cp_File where the
**				allocation request came from.
**  Return Value:	void *	A pointer to the allocated memory or NULL on
**				failure.
**  Remarks/Portability/Dependencies/Restrictions:
**		If no memory is allocated, then no entry is added to the
**		allocation list.
**  Revision History:
**	1999-02-08	created, modelled after LYLeakMalloc - kw
*/
PUBLIC AllocationList *LYLeak_mark_malloced ARGS4(
	void *,		vp_malloced,
	size_t,		st_bytes,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    AllocationList *ALp_new = NULL;

    if (LYfind_leaks == FALSE)
	return NULL;

    /*
     *	The actual allocation has already been done!
     *
     *	Only on successful allocation do we track any information.
     */
    if (vp_malloced != NULL) {
	/*
	 *  See if there is already an entry.  If so, just
	 *  update the source location info.
	 */
	ALp_new = FindInList(vp_malloced);
	if (ALp_new) {
	    ALp_new->SL_memory.cp_FileName = cp_File;
	    ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
	    return(ALp_new);
	}
	/*
	 *  Further allocate memory to store the information.
	 *  Just return on failure to allocate more.
	 */
	ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    return(NULL);
	}
	/*
	 *  Copy over the relevant information.
	 */
	ALp_new->vp_Alloced = vp_malloced;
	ALp_new->st_Bytes = st_bytes;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 *  Add the new item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return(ALp_new);
}

/*
**  Purpose:	Capture allocations by calloc (stdlib.h) and
**		save relevant information in a list.
**  Arguments:	st_number	The number of items to allocate.
**		st_bytes	The size of each item.
**		cp_File		The file which wants to allocation.
**		ssi_Line	The line number in cp_File requesting
**				the allocation.
**  Return Value:	void *	The allocated memory, or NULL on failure as
**				per calloc (stdlib.h)
**  Remarks/Portability/Dependencies/Restrictions:
**		If no memory can be allocated, then no entry will be added
**		to the list.
**  Revision History:
**		05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PUBLIC void *LYLeakCalloc ARGS4(
	size_t,		st_number,
	size_t,		st_bytes,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    void *vp_calloc;

    if (LYfind_leaks == FALSE)
	return (void *)calloc(st_number, st_bytes);

    /*
     *	Allocate the requested memory.
     */
    vp_calloc = (void *)calloc(st_number, st_bytes);
    CountMallocs(st_bytes);

    /*
     *	Only if the allocation was a success do we track information.
     */
    if (vp_calloc != NULL) {
	/*
	 *  Allocate memory for the item to be in the list.
	 *  If unable, just return.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
		return(vp_calloc);
	}

	/*
	 *  Copy over the relevant information.
	 *  There is no need to allocate memory for the file
	 *  name as it is a static string anyway.
	 */
	ALp_new->st_Sequence = count_mallocs;
	ALp_new->vp_Alloced = vp_calloc;
	ALp_new->st_Bytes = (st_number * st_bytes);
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 *	Add the item to the allocation list.
	 */
	AddToList(ALp_new);
    }

    return(vp_calloc);
}

/*
**  Purpose:	Capture any realloc (stdlib.h) calls in order to
**		properly keep track of our run time allocation
**		table.
**  Arguments:	vp_Alloced	The previously allocated block of
**				memory to resize.  If NULL,
**				realloc works just like
**				malloc.
**		st_newBytes	The new size of the chunk of memory.
**		cp_File		The file containing the realloc.
**		ssi_Line	The line containing the realloc in cp_File.
**  Return Value:	void *	The new pointer value (could be the same) or
**				NULL if unable to resize (old block
**				still exists).
**  Remarks/Portability/Dependencies/Restrictions:
**		If unable to resize vp_Alloced, then no change in the
**		allocation list will be made.
**		If vp_Alloced is an invalid pointer value, the program will
**		exit after one last entry is added to the allocation list.
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PUBLIC void *LYLeakRealloc ARGS4(
	void *,		vp_Alloced,
	size_t,		st_newBytes,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    void *vp_realloc;
    AllocationList *ALp_renew;

    if (LYfind_leaks == FALSE)
	return (void *)realloc(vp_Alloced, st_newBytes);

    /*
     *	If we are asked to resize a NULL pointer, this is just a
     *	malloc call.
     */
    if (vp_Alloced == NULL) {
	return(LYLeakMalloc(st_newBytes, cp_File, ssi_Line));
    }

    /*
     *	Find the current vp_Alloced block in the list.
     *	If NULL, this is an invalid pointer value.
     */
    ALp_renew = FindInList(vp_Alloced);
    if (ALp_renew == NULL) {
	/*
	 *  Track the invalid pointer value and then exit.
	 *  If unable to allocate, just exit.
	 */
	auto AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    exit(EXIT_FAILURE);
	}

	/*
	 *  Set the information up; no need to allocate file name
	 *  since it is a static string.
	 */
	ALp_new->vp_Alloced = NULL;
	ALp_new->vp_BadRequest = vp_Alloced;
	ALp_new->SL_realloc.cp_FileName = cp_File;
	ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;

	/*
	 *  Add the item to the list.
	 *  Exit.
	 */
	AddToList(ALp_new);
	exit(EXIT_FAILURE);
    }

    /*
     *	Perform the resize.
     *	If not NULL, record the information.
     */
    vp_realloc = (void *)realloc(vp_Alloced, st_newBytes);
    CountMallocs(st_newBytes);
    CountFrees(ALp_renew->st_Bytes);

    if (vp_realloc != NULL) {
	ALp_renew->st_Sequence = count_mallocs;
	ALp_renew->vp_Alloced = vp_realloc;
	ALp_renew->st_Bytes = st_newBytes;

	/*
	 *  Update the realloc information, too.
	 *  No need to allocate file name, static string.
	 */
	ALp_renew->SL_realloc.cp_FileName = cp_File;
	ALp_renew->SL_realloc.ssi_LineNumber = ssi_Line;
    }

    return(vp_realloc);
}

/*
**  Purpose:	Add information about reallocated memory to the list,
**		after a call to realloc or an equivalent
**		function which has not already created or updated
**		a list entry.
**  Arguments:	ALp_old		List entry for previously allocated
**				block of memory to resize.  If NULL,
**				mark_realloced works just like
**				mark_malloced.
**		vp_realloced	The new pointer, after resizing.
**		st_newBytes	The new size of the chunk of memory.
**		cp_File		The file to record.
**		ssi_Line	The line to record.
**  Return Value:		Pointer to new or updated list entry
**				for this memory block.
**				NULL on allocation error.
**  Revision History:
**	1999-02-11	created kw
*/
#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)
PRIVATE AllocationList *mark_realloced ARGS5(
	AllocationList *, ALp_old,
	void *,		vp_realloced,
	size_t,		st_newBytes,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    /*
     *	If there is no list entry for the old allocation, treat this
     *	as if a new allocation had happened.
     */
    if (ALp_old == NULL) {
	return(LYLeak_mark_malloced(
	    vp_realloced, st_newBytes, cp_File, ssi_Line));
    }

    /*
     *	ALp_old represents the memory block before reallocation.
     *  Assume that if we get here, there isn't yet a list entry
     *  for the new, possibly different, address after realloc,
     *  that is our list hasn't been updated - so we're going to
     *  do that now.
     */

    if (vp_realloced != NULL) {
	ALp_old->vp_Alloced = vp_realloced;
	ALp_old->st_Bytes = st_newBytes;
	ALp_old->SL_realloc.cp_FileName = cp_File;
	ALp_old->SL_realloc.ssi_LineNumber = ssi_Line;
    }

    return(ALp_old);
}
#endif /* not LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */

/*
**  Purpose:	Capture all requests to free information and also
**		remove items from the allocation list.
**  Arguments:	vp_Alloced	The memory to free.
**		cp_File		The file calling free.
**		ssi_Line	The line of cp_File calling free.
**  Return Value:	void
**  Remarks/Portability/Dependencies/Restrictions:
**		If the pointer value is invalid, then an item will be added
**		to the list and nothing else is done.
**		I really like the name of this function and one day hope
**		that Lynx is Leak Free.
**  Revision History:
**	05-26-94	created Lynx 2-3-1 Garrett Arch Blythe
*/
PUBLIC void LYLeakFree ARGS3(
	void *,		vp_Alloced,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    AllocationList *ALp_free;

    if (LYfind_leaks == FALSE) {
	free(vp_Alloced);
	return;
    }

    /*
     *	Find the pointer in the allocated list.
     *	If not found, bad pointer.
     *	If found, free list item and vp_Allloced.
     */
    ALp_free = FindInList(vp_Alloced);
    if (ALp_free == NULL) {
	/*
	 *  Create the final entry before exiting marking this error.
	 *  If unable to allocate more memory just exit.
	 */
	AllocationList *ALp_new = typecalloc(AllocationList);

	if (ALp_new == NULL) {
	    exit(EXIT_FAILURE);
	}

	/*
	 *  Set up the information, no memory need be allocated
	 *  for the file name since it is a static string.
	 */
	ALp_new->vp_Alloced = NULL;
	ALp_new->vp_BadRequest = vp_Alloced;
	ALp_new->SL_memory.cp_FileName = cp_File;
	ALp_new->SL_memory.ssi_LineNumber = ssi_Line;

	/*
	 *  Add the entry to the list and then return.
	 */
	AddToList(ALp_new);
	return;
    } else {
	/*
	 *  Free off the memory.
	 *  Take entry out of allocation list.
	 */
	CountFrees(ALp_free->st_Bytes);
	RemoveFromList(ALp_free);
	FREE(ALp_free);
	FREE(vp_Alloced);
    }
}

/*
**  Allocates a new copy of a string, and returns it.
**  Tracks allocations by using other LYLeakFoo functions.
**  Equivalent to HTSACopy in HTUtils.c - KW
*/
PUBLIC char * LYLeakSACopy ARGS4(
	char **,	dest,
	CONST char *,	src,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    if (src != NULL && src == *dest) {
	CTRACE((tfp,
	       "LYLeakSACopy: *dest equals src, contains \"%s\"\n",
	       src));
	return *dest;
    }
    if (*dest) {
	LYLeakFree(*dest, cp_File, ssi_Line);
	*dest = NULL;
    }
    if (src) {
	*dest = (char *)LYLeakMalloc(strlen(src) + 1, cp_File, ssi_Line);
	if (*dest == NULL)
	    outofmem(__FILE__, "LYLeakSACopy");
	strcpy (*dest, src);
    }
    return *dest;
}

/*
**  String Allocate and Concatenate.
**  Tracks allocations by using other LYLeakFoo functions.
**  Equivalent to HTSACat in HTUtils.c - KW
*/
PUBLIC char * LYLeakSACat ARGS4(
	char **,	dest,
	CONST char *,	src,
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    if (src && *src) {
	if (src == *dest) {
	    CTRACE((tfp,
		   "LYLeakSACat:  *dest equals src, contains \"%s\"\n",
		   src));
	    return *dest;
	}
	if (*dest) {
	    int length = strlen(*dest);
	    *dest = (char *)LYLeakRealloc(*dest,
					  (length + strlen(src) + 1),
					  cp_File,
					  ssi_Line);
	    if (*dest == NULL)
		outofmem(__FILE__, "LYLeakSACat");
	    strcpy (*dest + length, src);
	} else {
	    *dest = (char *)LYLeakMalloc((strlen(src) + 1),
					 cp_File,
					 ssi_Line);
	    if (*dest == NULL)
		outofmem(__FILE__, "LYLeakSACat");
	    strcpy (*dest, src);
	}
    }
    return *dest;
}

#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)

PUBLIC CONST char * leak_cp_File_hack = __FILE__;
PUBLIC short leak_ssi_Line_hack = __LINE__;

/*
** Purpose:	A wrapper around StrAllocVsprintf (the workhorse of
**		HTSprintf/HTSprintf0, implemented in HTString.c) that
**		tries to make sure that our allocation list is always
**		properly updated, whether StrAllocVsprintf itself was
**		compiled with memory tracking or not (or even a mixture,
**		like tracking the freeing but not the new allocation).
**		Some source files can be compiled with LY_FIND_LEAKS_EXTENDED
**		in effect while others only have LY_FIND_LEAKS in effect,
**		and as long as HTString.c is complied with memory tracking
**		(of either kind) string objects allocated by HTSprintf/
**		HTSprintf0 (or otherwise) can be passed around among them and
**		manipulated both ways.
**  Arguments:	dest		As for StrAllocVsprintf.
**		cp_File		The source file of the caller (i.e. the
**				caller of HTSprintf/HTSprintf0, hopefully).
**		ssi_Line	The line of cp_File calling.
**		inuse,fmt,ap	As for StrAllocVsprintf.
**  Return Value:	The char pointer to resulting string, as set
**			by StrAllocVsprintf, or
**			NULL if dest==0 (wrong use!).
**  Remarks/Portability/Dependencies/Restrictions:
**		The price for generality is severe inefficiency: several
**		list lookups are done to be on the safe side.
**		We don't get he real allocation size, only a minimum based
**		on the string length of the result.  So the amount of memory
**		leakage may get underestimated.
**		If *dest is an invalid pointer value on entry (i.e. was not
**		tracked), the program will exit after one last entry is added
**		to the allocation list.
**		If StrAllocVsprintf fails to return a valid string via the
**		indirect string pointer (its first parameter), invalid memory
**		access will result and the program will probably terminate
**		with a signal.  This can happen if, on entry, *dest is NULL
**		and fmt is empty or NULL, so just Don't Do That.
**  Revision History:
**	1999-02-11	created kw
**	1999-10-15	added comments kw
*/
PRIVATE char * LYLeakSAVsprintf ARGS6(
	char **,	dest,
	CONST char *,	cp_File,
	CONST short,	ssi_Line,
	size_t,		inuse,
	CONST char *,	fmt,
	va_list *,	ap)
{
    AllocationList *ALp_old;
    void *vp_oldAlloced;

    CONST char * old_cp_File = __FILE__;
    short old_ssi_Line = __LINE__;

    if (!dest)
	return NULL;

    if (LYfind_leaks == FALSE) {
	StrAllocVsprintf(dest, inuse, fmt, ap);
	return (*dest);
    }

    vp_oldAlloced = *dest;
    if (!vp_oldAlloced) {
	StrAllocVsprintf(dest, inuse, fmt, ap);
	LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
	return(*dest);
    } else {
	void * vp_realloced;
	ALp_old = FindInList(vp_oldAlloced);
	if (ALp_old == NULL) {
	    /*
	     *  Track the invalid pointer value and then exit.
	     *  If unable to allocate, just exit.
	     */
	    auto AllocationList *ALp_new = typecalloc(AllocationList);

	    if (ALp_new == NULL) {
		exit(EXIT_FAILURE);
	    }

	    /*
	     *  Set the information up; no need to allocate file name
	     *  since it is a static string.
	     */
	    ALp_new->vp_Alloced = NULL;
	    ALp_new->vp_BadRequest = vp_oldAlloced;
	    ALp_new->SL_realloc.cp_FileName = cp_File;
	    ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;

	    /*
	     *  Add the item to the list.
	     *  Exit.
	     */
	    AddToList(ALp_new);
	    exit(EXIT_FAILURE);
	}

	old_cp_File = ALp_old->SL_memory.cp_FileName;
	old_ssi_Line = ALp_old->SL_memory.ssi_LineNumber;
	/*
	 *	DO THE REAL WORK, by calling StrAllocVsprintf.
	 *	If result is not NULL, record the information.
	 */
	StrAllocVsprintf(dest, inuse, fmt, ap);
	vp_realloced = (void *)*dest;
	if (vp_realloced != NULL) {
	    AllocationList *ALp_new = FindInList(vp_realloced);
	    if (!ALp_new) {
		/* Look up again, list may have changed! - kw */
		ALp_old = FindInList(vp_oldAlloced);
		if (ALp_old == NULL) {
		    LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
		    return(*dest);
		}
		mark_realloced(ALp_old, *dest, strlen(*dest) + 1, cp_File, ssi_Line);
		return(*dest);
	    }
	    if (vp_realloced == vp_oldAlloced) {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
		return(*dest);
	    }
	    /* Look up again, list may have changed! - kw */
	    ALp_old = FindInList(vp_oldAlloced);
	    if (ALp_old == NULL) {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
	    } else {
		ALp_new->SL_memory.cp_FileName = old_cp_File;
		ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
		ALp_new->SL_realloc.cp_FileName = cp_File;
		ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
	    }
	}
	return(*dest);
    }
}

/* Note: the following may need updating if HTSprintf in HTString.c
 * is changed. - kw */
#ifdef ANSI_VARARGS
PRIVATE char * LYLeakHTSprintf (char **pstr, CONST char *fmt, ...)
#else
PRIVATE char * LYLeakHTSprintf (va_alist)
    va_dcl
#endif
{
    char *str;
    size_t inuse = 0;
    va_list ap;
    LYva_start(ap,fmt);
    {
#ifndef ANSI_VARARGS
	char **		pstr = va_arg(ap, char **);
	CONST char *	fmt  = va_arg(ap, CONST char *);
#endif
	if (pstr != 0 && *pstr != 0)
	    inuse = strlen(*pstr);
	str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
			       inuse, fmt, &ap);
    }
    va_end(ap);
    return str;
}

/* Note: the following may need updating if HTSprintf0 in HTString.c
 * is changed. - kw */
#ifdef ANSI_VARARGS
PRIVATE char * LYLeakHTSprintf0 (char **pstr, CONST char *fmt, ...)
#else
PRIVATE char * LYLeakHTSprintf0 (va_alist)
    va_dcl
#endif
{
    char *str;
    va_list ap;
    LYva_start(ap,fmt);
    {
#ifndef ANSI_VARARGS
	char **		pstr = va_arg(ap, char **);
	CONST char *	fmt  = va_arg(ap, CONST char *);
#endif
	str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
			       0, fmt, &ap);
    }
    va_end(ap);
    return str;
}

/*
 *  HTSprintf and HTSprintf0 will be defined such that they effectively
 *  call one of the following two functions that store away a copy to
 *  the File & Line info in temporary hack variables, and then call
 *  the real function (which is returned here as a function pointer)
 *  to the regular HTSprintf/HTSprintf0 arguments.
 *  It's probably a bit inefficient, but that shouldn't be noticeable
 *  compared to all the time that memory tracking takes up for list
 *  traversal. - kw
 */
PUBLIC HTSprintflike *Get_htsprintf_fn ARGS2(
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    leak_cp_File_hack = cp_File;
    leak_ssi_Line_hack = ssi_Line;
    return &LYLeakHTSprintf;
}

PUBLIC HTSprintflike *Get_htsprintf0_fn ARGS2(
	CONST char *,	cp_File,
	CONST short,	ssi_Line)
{
    leak_cp_File_hack = cp_File;
    leak_ssi_Line_hack = ssi_Line;
    return &LYLeakHTSprintf0;
}

#endif /* LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */
#else
/* Standard C forbids an empty file */
void no_leak_checking NOPARAMS;
void no_leak_checking NOARGS { }
#endif /* LY_FIND_LEAKS */
