/*
 * $LynxId: LYLeaks.c,v 1.41 2018/03/30 00:27:58 tom Exp $
 *
 *	Copyright (c) 1994, University of Kansas, All Rights Reserved
 *	(this file was rewritten twice - 1998/1999 and 2003/2004)
 *
 *	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
 *	07-23-07	free leaks of THIS module too -TD
 *	02-09-12	add bstring functions -TD
 */

/*
 *	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

static AllocationList *ALp_RunTimeAllocations = NULL;

#define LEAK_SUMMARY

#ifdef LEAK_SUMMARY

static size_t now_allocated = 0;
static size_t peak_alloced = 0;

static size_t total_alloced = 0;
static size_t total_freed = 0;

static long count_mallocs = 0;
static long count_frees = 0;

static void CountMallocs(size_t size)
{
    ++count_mallocs;
    total_alloced += size;
    now_allocated += size;
    if (peak_alloced < now_allocated)
	peak_alloced = now_allocated;
}

static void CountFrees(size_t size)
{
    ++count_frees;
    total_freed += size;
    now_allocated -= size;
}

#else
#define CountMallocs(size) ++count_mallocs
#define CountFrees(size)	/* 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
 */
static void AddToList(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
 */
static AllocationList *FindInList(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
 */
static void RemoveFromList(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;
    } else {

	/*
	 * 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.
 */
#ifndef LYLeakSequence
long LYLeakSequence(void)
{
    return count_mallocs;
}
#endif

/*
 *  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).
 */
void LYLeaks(void)
{
    AllocationList *ALp_head;
    size_t st_total = (size_t) 0;
    FILE *Fp_leakagesink;

    CTRACE((tfp, "entering LYLeaks, flag=%d\n", LYfind_leaks));

    if (LYfind_leaks == FALSE) {
	/*
	 * Free MY leaks too, in case someone else is watching.
	 */
	while (ALp_RunTimeAllocations != NULL) {
	    ALp_head = ALp_RunTimeAllocations;
	    ALp_RunTimeAllocations = ALp_head->ALp_Next;
	    free(ALp_head);
	}
	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(LYLeaksPath);
    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.  Release 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%lu\n", gettext("Peak allocation"), (unsigned long) peak_alloced);
    fprintf(Fp_leakagesink,
	    "%s\t%lu\n", gettext("Bytes allocated"), (unsigned long) 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);
}

/*
 *  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
 */
void *LYLeakMalloc(size_t st_bytes, const char *cp_File,
		   const short ssi_Line)
{
    void *vp_malloc;

    if (LYfind_leaks == FALSE) {
	vp_malloc = (void *) malloc(st_bytes);
    } else {

	/*
	 * 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) {
		/*
		 * Copy over the relevant information.  There is no need to
		 * allocate more memory for the file name as it is a static
		 * string anyway.
		 */
		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
 */
AllocationList *LYLeak_mark_malloced(void *vp_malloced,
				     size_t st_bytes,
				     const char *cp_File,
				     const short ssi_Line)
{
    AllocationList *ALp_new = NULL;

    if (LYfind_leaks != FALSE) {
	/*
	 * 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;
	    } else {
		/*
		 * Further allocate memory to store the information.  Just
		 * return on failure to allocate more.
		 */
		ALp_new = typecalloc(AllocationList);
		if (ALp_new != 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);
		    CountMallocs(st_bytes);
		}
	    }
	}
    }
    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
 */
void *LYLeakCalloc(size_t st_number, size_t st_bytes, const char *cp_File,
		   const short ssi_Line)
{
    void *vp_calloc;

    if (LYfind_leaks == FALSE) {
	vp_calloc = (void *) calloc(st_number, st_bytes);
    } else {

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

	/*
	 * 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) {

		/*
		 * 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
 */
void *LYLeakRealloc(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) {
	vp_realloc = (void *) realloc(vp_Alloced, st_newBytes);

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

    } else {

	/*
	 * 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.
	     */
	    AllocationList *ALp_new = typecalloc(AllocationList);

	    if (ALp_new == NULL) {
		exit_immediately(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_immediately(EXIT_FAILURE);
	}

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

	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)
static AllocationList *mark_realloced(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
 */
void LYLeakFree(void *vp_Alloced,
		const char *cp_File,
		const short ssi_Line)
{
    AllocationList *ALp_free;

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

	/*
	 * Find the pointer in the allocated list.  If not found, bad pointer. 
	 * If found, free list item and vp_Alloced.
	 */
	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_immediately(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);
	} 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);
	}
    }
}

/*
 * Check for leaked strdup() results -TD
 */
char *LYLeakStrdup(const char *source,
		   const char *cp_File,
		   const short ssi_Line)
{
    size_t length = strlen(source) + 1;
    char *target = (char *) LYLeakMalloc(length, cp_File, ssi_Line);

    if (target != 0) {
	memcpy(target, source, length);
    }
    return target;
}

/*
 *  Allocates a new copy of a string, and returns it.
 *  Tracks allocations by using other LYLeakFoo functions.
 *  Equivalent to HTSACopy in HTString.c - KW
 */
char *LYLeakSACopy(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));
    } else {
	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
 */
char *LYLeakSACat(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));
	} else if (*dest) {
	    size_t 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;
}

/******************************************************************************/

/*
 * Equivalents for bstring functions in HTString.c -TD
 */
/* same as HTSABAlloc */
void LYLeakSABAlloc(bstring **dest,
		    int len,
		    const char *cp_File,
		    const short ssi_Line)
{
    if (*dest == 0) {
	*dest = LYLeakCalloc(1, sizeof(bstring), cp_File, ssi_Line);
    }

    if ((*dest)->len != len) {
	(*dest)->str = (char *) LYLeakRealloc((*dest)->str,
					      (size_t) len,
					      cp_File,
					      ssi_Line);
	if ((*dest)->str == NULL)
	    outofmem(__FILE__, "LYLeakSABalloc");

	(*dest)->len = len;
    }
}

/* same as HTSABCopy */
void LYLeakSABCopy(bstring **dest,
		   const char *src,
		   int len,
		   const char *cp_File,
		   const short ssi_Line)
{
    bstring *t;
    unsigned need = (unsigned) (len + 1);

    CTRACE2(TRACE_BSTRING,
	    (tfp, "HTSABCopy(%p, %p, %d)\n",
	     (void *) dest, (const void *) src, len));
    LYLeakSABFree(dest, cp_File, ssi_Line);
    if (src) {
	if (TRACE_BSTRING) {
	    CTRACE((tfp, "===    %4d:", len));
	    trace_bstring2(src, len);
	    CTRACE((tfp, "\n"));
	}
	if ((t = (bstring *) LYLeakMalloc(sizeof(bstring), cp_File, ssi_Line))
	    == NULL)
	      outofmem(__FILE__, "HTSABCopy");

	if ((t->str = (char *) LYLeakMalloc(need, cp_File, ssi_Line)) == NULL)
	    outofmem(__FILE__, "HTSABCopy");

	MemCpy(t->str, src, len);
	t->len = len;
	t->str[t->len] = '\0';
	*dest = t;
    }
    if (TRACE_BSTRING) {
	CTRACE((tfp, "=>     %4d:", BStrLen(*dest)));
	trace_bstring(*dest);
	CTRACE((tfp, "\n"));
    }
}

/* same as HTSABCopy0 */
void LYLeakSABCopy0(bstring **dest,
		    const char *src,
		    const char *cp_File,
		    const short ssi_Line)
{
    LYLeakSABCopy(dest, src, (int) strlen(src), cp_File, ssi_Line);
}

/* same as HTSABCat */
void LYLeakSABCat(bstring **dest,
		  const char *src,
		  int len,
		  const char *cp_File,
		  const short ssi_Line)
{
    bstring *t = *dest;

    CTRACE2(TRACE_BSTRING,
	    (tfp, "HTSABCat(%p, %p, %d)\n",
	     (void *) dest, (const void *) src, len));
    if (src) {
	unsigned need = (unsigned) (len + 1);

	if (TRACE_BSTRING) {
	    CTRACE((tfp, "===    %4d:", len));
	    trace_bstring2(src, len);
	    CTRACE((tfp, "\n"));
	}
	if (t) {
	    unsigned length = (unsigned) t->len + need;

	    t->str = (char *) LYLeakRealloc(t->str, length, cp_File, ssi_Line);
	} else {
	    if ((t = (bstring *) LYLeakCalloc(1, sizeof(bstring), cp_File,
					      ssi_Line)) == NULL)
		  outofmem(__FILE__, "HTSACat");

	    t->str = (char *) LYLeakMalloc(need, cp_File, ssi_Line);
	}
	if (t->str == NULL)
	    outofmem(__FILE__, "HTSACat");

	MemCpy(t->str + t->len, src, len);
	t->len += len;
	t->str[t->len] = '\0';
	*dest = t;
    }
    if (TRACE_BSTRING) {
	CTRACE((tfp, "=>     %4d:", BStrLen(*dest)));
	trace_bstring(*dest);
	CTRACE((tfp, "\n"));
    }
}

/* same as HTSABCat0 */
void LYLeakSABCat0(bstring **dest,
		   const char *src,
		   const char *cp_File,
		   const short ssi_Line)
{
    LYLeakSABCat(dest, src, (int) strlen(src), cp_File, ssi_Line);
}

/* same as HTSABFree */
void LYLeakSABFree(bstring **ptr,
		   const char *cp_File,
		   const short ssi_Line)
{
    if (*ptr != NULL) {
	if ((*ptr)->str)
	    LYLeakFree((*ptr)->str, cp_File, ssi_Line);
	LYLeakFree(*ptr, cp_File, ssi_Line);
	*ptr = NULL;
    }
}

/******************************************************************************/

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

const char *leak_cp_File_hack = __FILE__;
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 the 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
 */
static char *LYLeakSAVsprintf(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.
	     */
	    AllocationList *ALp_new = typecalloc(AllocationList);

	    if (ALp_new == NULL) {
		exit_immediately(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_immediately(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);
	    }
	    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 */
static char *LYLeakHTSprintf(char **pstr, const char *fmt,...)
{
    char *str;
    size_t inuse = 0;
    va_list ap;

    LYva_start(ap, fmt);

    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 */
static char *LYLeakHTSprintf0(char **pstr, const char *fmt,...)
{
    char *str;
    va_list ap;

    LYva_start(ap, fmt);

    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
 */
HTSprintflike *Get_htsprintf_fn(const char *cp_File,
				const short ssi_Line)
{
    leak_cp_File_hack = cp_File;
    leak_ssi_Line_hack = ssi_Line;
    return &LYLeakHTSprintf;
}

HTSprintflike *Get_htsprintf0_fn(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(void);
void no_leak_checking(void)
{
}
#endif /* LY_FIND_LEAKS */
