/**************************************************************************
 *{@C
 *      Copyright:      1988-2024 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         Utilities
 *      Filename:       UT_MemUserLevel.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    The memory management library provides all the
 *                      functions for dynamically allocating and deallocating
 *                      memory.
 *                      
 *                      The memory management library consists of two layers:
 *
 *                      Level one gets memory from the operating system, cares
 *                      about fragmentation, and eventually returns memory
 *                      to the operating system.
 *
 *                      Level two is an interface to the "outside world",
 *                      consisting of a set of functions that (hopefully) make
 *                      if easier to handle dynamically allocated memory.
 *                      The functions in level two can be compiled so
 *                      that they detect violation of block boundaries and
 *                      memory blocks that are never or more than once
 *                      returned to the operating system.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *                      UT_MemAlignPos
 *                      UT_MemAlignNeg
 *                      UT_MemTempAlloc
 *                      UT_MemPermAlloc
 *                      UT_MemFree
 *                      UT_MemRemember
 *                      UT_MemRestore
 *                      UT_MemSave
 *                      UT_MemDestroy
 *                      UT_MemCheck
 *                      UT_MemCheckBlock
 *
 **************************************************************************/

#include <stdio.h>
#include <stdint.h>

#include "UT_Compat.h"

#if defined (OS_IS_WIN32)
    #include <windows.h>
    #define DPSAPI_VERSION 1
    #include <psapi.h>
#elif defined (Darwin)
    #include <mach/mach_init.h>
    #include <mach/task.h>
    #include <sys/types.h>
    #include <sys/sysctl.h>
#elif defined (OS_IS_SYS5_UNIX)
    #include <unistd.h>
#endif

#include "UT_Error.h"

#include "UT_Memory.h"
#include "UT_MemPrivate.h"

static UT_MemHdr
        *firstperm = NULL,              /* First block in permanent list */
        *lastperm = NULL,               /* Last block in permanent list */
        *firsttemp = NULL,              /* First block in temporary list */
        *lasttemp = NULL;               /* Last block in temporary list */

static UT_Bool
        initialized = UT_False,         /* Set to UT_True upon initialization */
        destroymode = UT_False;         /* UT_True while destroy mode is on */

#define destroypattern  0xaa            /* Bit pattern used to overwrite a
                                           memory block when destroy mode is
                                           switched on */

#define checkbytebegin  ~0x07041966     /* Bit pattern prepended to the
                                           beginning of a memory block */

#define checkbyteend    ~0x02081968     /* Bit pattern appended at the end
                                           of a memory block */

#define checkbytefree   ~0x14091935     /* Bit pattern used to mark free
                                           memory blocks */


/* Initialize the memory management library's internal data structures. */

static void init (void)
{
    initialized = UT_True;
    return;
}

/* Wrapper around standard memory management functions malloc and free. */

static void *memAlloc (size_t size)
{
    void *ptr;

    if (!(ptr = malloc (size))) {
        UT_ErrSetNum (UT_ErrFromOs (), str_try_alloc, size);
    }
    return ptr;
}

static void memFree (void *ptr, size_t size)
{
    (void) size;
    free (ptr);
    return;
}

/* Given the size of the user data area of a memory block, compute the
total amount of memory needed for the block, including the block header,
and the check bytes at the beginning and at the end. */

#if defined(DEBUG_MEM)

    static size_t totalsize (size_t usize)
    {
        return MEM_ALIGN_POS (usize) + memheadersize + 2 * checkbytesize;
    }

#else

    static size_t totalsize (size_t usize)
    {
        return MEM_ALIGN_POS (usize) + memheadersize;
    }

#endif


/* Given the total size of a memory block (including block header etc.),
determine the size of the user data area. */

#if defined(DEBUG_MEM)

    static size_t usersize (size_t tsize)
    {
        return tsize - memheadersize - 2 * checkbytesize;
    }

#else

    static size_t usersize (size_t tsize)
    {
        return tsize - memheadersize;
    }

#endif


/* Given the address of a block header, compute the address of the
user data area and the adresses of the check bytes at the beginning
and at the end of the block. */

#if defined (DEBUG_MEM)

    static void header_to_user
            (UT_MemHdr *header,
             char **user,
             size_t **checkbegin,
             size_t **checkend)
    {
        *checkbegin = (size_t *)((char *)header + memheadersize);
        *user = (char *)*checkbegin + checkbytesize;
        *checkend = (size_t *)(*user + usersize (header->size));
        return;
    }

#else

    static void header_to_user (UT_MemHdr *header, char **user)
    {
        *user = (char *)header + memheadersize;
        return;
    }

#endif


/* Given the address of the user data area of a memory block, compute the
address of the block header and the adresses of the check bytes at the
beginning and at the end of the block. */

#if defined (DEBUG_MEM)

    static void user_to_header
            (char *user,
             UT_MemHdr **header,
             size_t **checkbegin,
             size_t **checkend)
    {
        *checkbegin = (size_t *)(user - checkbytesize);
        *header = (UT_MemHdr *)((char *)*checkbegin - memheadersize);
        *checkend = (size_t *)(user + usersize ((*header)->size));
        return;
    }

#else

    static void user_to_header (char *user, UT_MemHdr **header)
    {
        *header = (UT_MemHdr *)(user - memheadersize);
        return;
    }

#endif


/* Destroy the contents of the user data area of a memory block by
overwriting with a constant bit pattern. */

static void destroy_data (char *data, size_t nbytes)
{
    char *stop = data + nbytes;
    while (data < stop)
        *(data++) = (char) destroypattern;
    return;
}


/* Print name and size of a memory block to stderr. */

#if defined(DEBUG_MEM)

    static void printblockinfo (UT_MemHdr *header)
    {
        size_t *checkbegin, *checkend;
        char *user;

        fprintf (stderr, "\tname: ");
        fprintf (stderr, "'%s' ", header->name);
        fprintf (stderr, " - address: 0x%lx - size: %ld bytes\n",
            (size_t)header, header->size);
        header_to_user (header, &user, &checkbegin, &checkend);
        if (*checkbegin != checkbytebegin)
            fprintf (stderr, "\t\tcheckbytebegin changed\n");
        if (*checkend != checkbyteend)
            fprintf (stderr, "\t\tcheckbyteend changed\n");
        return;
    }

#endif


/* Print name and size of all blocks in a linked list. */

#if defined(DEBUG_MEM)

    static void printblocklist (UT_MemHdr *header)
    {
        while (header)
        {
            printblockinfo (header);
            header = header->next;
        }
        return;
    }

#endif


/* Print name and size of a memory block to stderr, then call UT_ErrFatal. */

#if defined(DEBUG_MEM)

    static void fatal (UT_MemHdr *header, char func[], char message[])
    {
        fprintf (stderr, "\n\nmemory block - name: ");
        fprintf (stderr, "'%s' ", header->name);
        fprintf (stderr, " - address: 0x%lx - size: %ld bytes\n",
            (size_t)header, header->size);
        UT_ErrFatal (func, message);
    }

#endif

/***************************************************************************
 *[@e
 *      Name:           UT_MemAlignPos
 *
 *      Usage:          Find next positive aligned address.
 *
 *      Synopsis:       void *UT_MemAlignPos (const void *addr)
 *
 *      Description:    Find the first address that is greater than or equal
 *                      to a given address and that is aligned so that it can
 *                      be used as the address of any data object.
 *
 *      Return value:   The aligned address.
 *
 *      See also:
 *
 ***************************************************************************/

void *UT_MemAlignPos (const void *addr)
{
    return (void *)MEM_ALIGN_POS ((size_t)addr);
}


/***************************************************************************
 *[@e
 *      Name:           UT_MemAlignNeg
 *
 *      Usage:          Find next negative aligned address.
 *
 *      Synopsis:       void *UT_MemAlignNeg (const void *addr)
 *
 *      Description:    Find the first address that is less than or equal to
 *                      a given address and that is aligned so that it can be
 *                      used as the address of any data object.
 *
 *      Return value:   The aligned address.
 *
 *      See also:
 *
 ***************************************************************************/

void *UT_MemAlignNeg (const void *addr)
{
    return (void *)MEM_ALIGN_NEG ((size_t)addr);
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemTempAlloc
 *
 *      Usage:          Allocate a temporary memory block from the
 *                      operating system.
 *
 *      Synopsis:       void *UT_MemTempAlloc (size_t size, const char *name)
 *
 *      Description:    "Size" is the number of bytes to allocate. "Name"
 *                      should be a pointer to a static string constant
 *                      explaining for what the memory block is needed
 *                      (this is quite helpful when debugging programs
 *                      which forget to free unused memory blocks; a list
 *                      of all temporary and permanent memory blocks can
 *                      be printed with UT_MemCheck).
 *
 *                      Notes:
 *                      
 *                      - The newly allocated temporary memory block is not
 *                        cleared; it contains random data.
 *
 *                      - The new temporary block can be deallocated with
 *                        UT_MemRestore or UT_MemFree; it can also be converted
 *                        into a permanent block with UT_MemSave.
 *
 *                      - The UT_MemTemp and UT_MemTempArray macros
 *                        provide a slightly more comfortable interface
 *                        to UT_MemTempAlloc.
 *
 *      Return value:   A pointer to a temporary block of "size" bytes,
 *                      suitably aligned for any use, is returned, if enough
 *                      memory is available, otherwise the result is NULL.
 *
 *      See also:
 *
 ***************************************************************************/

void *UT_MemTempAlloc (size_t size, const char *name)
{
    UT_MemHdr   *header;
    char        *user;

    #if defined(DEBUG_MEM)
        size_t *checkbegin,
                   *checkend;
    #endif

    if (!initialized) {
        init ();
    }

    /* Determine the total size of the block, including the header and
    the checkbytes, and allocate memory for it. */

    size = totalsize (size);
    if (!(header = memAlloc (size))) {
        return NULL;
    }

    /* Link the block to the end of the temporary block list. */

    header->previous = lasttemp;
    lasttemp = header;
    header->next = NULL;
    if (firsttemp) {
        header->previous->next = header;
    } else {
        firsttemp = header;
    }

    /* Initialize the block header and the checkbytes. */

    header->size = size;

    #if defined(DEBUG_MEM)
        header_to_user (header, &user, &checkbegin, &checkend);
        header->name = name;
        *checkbegin = checkbytebegin;
        *checkend = checkbyteend;
    #else
        header_to_user (header, &user);
    #endif

    return (void *)user;
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemPermAlloc
 *
 *      Usage:          Allocate a permanent memory block from the
 *                      operating system.
 *
 *      Synopsis:       void *UT_MemPermAlloc (size_t size, const char *name)
 *
 *      Description:    "Size" is the number of bytes to allocate. "Name"
 *                      should be a pointer to a static string constant
 *                      explaining for what the memory block is needed,
 *                      as in UT_MemTempAlloc.
 *
 *                      Notes:
 *                      
 *                      - The newly allocated permanent memory block is not
 *                        cleared; it contains random data.
 *
 *                      - The new temporary block can be deallocated only
 *                        with UT_MemFree; calls to UT_MemRestore and UT_MemSave
 *                        have no effect on the block.
 *
 *                      - The UT_MemPerm and UT_MemPermArray macros
 *                        provide a slightly more comfortable interface
 *                        to UT_MemPermAlloc.
 *
 *      Return value:   A pointer to a permanent block of "size" bytes,
 *                      suitably aligned for any use, is returned, if enough
 *                      memory is available, otherwise the result is NULL.
 *                      The newly allocated block is not affected by any
 *                      calls to UT_MemRestore or UT_MemSave.
 *
 *      See also:
 *
 ***************************************************************************/

void *UT_MemPermAlloc (size_t size, const char *name)
{
    UT_MemHdr   *header;
    char        *user;

    #if defined(DEBUG_MEM)
        size_t *checkbegin,
                   *checkend;
    #endif

    if (!initialized) {
        init ();
    }

    /* Determine the total size of the block, including the header and
    the checkbytes, and allocate memory for it. */

    size = totalsize (size);
    if (!(header = memAlloc (size))) {
        return NULL;
    }

    /* Link the block to the end of the permanent block list. */

    header->previous = lastperm;
    lastperm = header;
    header->next = NULL;
    if (firstperm) {
        header->previous->next = header;
    } else {
        firstperm = header;
    }

    /* Initialize the block header and the checkbytes. */

    header->size = size;

    #if defined(DEBUG_MEM)
        header_to_user (header, &user, &checkbegin, &checkend);
        header->name = name;
        *checkbegin = checkbytebegin;
        *checkend = checkbyteend;
    #else
        header_to_user (header, &user);
    #endif

    return (void *)user;
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemFree
 *
 *      Usage:          Return a block of memory to the operating system.
 *
 *      Synopsis:       void UT_MemFree (void *blk)
 *
 *      Description:    "Blk" must be a pointer to a temporary or a permanent
 *                      memory block previously allocated with UT_MemTempAlloc
 *                      or UT_MemPermAlloc.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_MemFree (void *blk)
{
    UT_MemHdr   *header;

    #if defined(DEBUG_MEM)
        size_t  *checkbegin,
                *checkend;
    #endif

    if (!initialized) {
        init ();
    }

    /* Find the address of the memory block header and the checkbytes.
    Then do some consistency checks. */

    #if defined(DEBUG_MEM)
        user_to_header (blk, &header, &checkbegin, &checkend);
        if (*checkbegin == checkbytefree)
            fatal (header, "UT_MemFree",
                   "block freed twice");
        if (*checkbegin != checkbytebegin)
            fatal (header, "UT_MemFree",
                   "block boundary violation; "
                   "write before start of block");
        if (*checkend != checkbyteend)
            fatal (header, "UT_MemFree",
                   "block boundary violation; "
                   "write after end of block");
    #else
        user_to_header (blk, &header);
    #endif

    if (header->size < memheadersize) {
        UT_ErrFatal ("UT_MemFree", "invalid memory block size");
    }

    /* Determine in which of the memory block lists the block is,
    remove it from that list, and deallocate the block. */

    if (header->previous) {
        header->previous->next = header->next;
    } else {
        if (firsttemp == header) {
            firsttemp = header->next;
        } else {
            firstperm = header->next;
        }
    }
    if (header->next) {
        header->next->previous = header->previous;
    } else {
        if (lasttemp == header) {
            lasttemp = header->previous;
        } else {
            lastperm = header->previous;
        }
    }

    /* If destroy mode is switched on, we erase the contents of the
    user data area of the block. */

    if (destroymode) {
        destroy_data (blk, usersize (header->size));
    }

    /* Before we deallocate the block, we change the checkbytes at the
    beginning, so that we can detect if a memory block is freed twice. */

    #if defined(DEBUG_MEM)
        *checkbegin = checkbytefree;
    #endif

    memFree (header, header->size);
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemRemember
 *
 *      Usage:          Save the status of the memory allocation list.
 *
 *      Synopsis:       UT_MemState UT_MemRemember (void)
 *
 *      Description:    Any function which has to temporary allocate memory,
 *                      should call UT_MemRemember on entry. While it performs
 *                      its task, it may allocate and deallocate temporary
 *                      memory as it likes. Just before the function returns,
 *                      it decides whether it wants to keep the allocated
 *                      memory (and make it permanent by calling UT_MemSave),
 *                      or whether it wants to deallocate all of it (by
 *                      calling UT_MemRestore).
 *
 *      Return value:   A descriptor for the current internal status of the
 *                      memory allocation list for later use with UT_MemRestore
 *                      and/or UT_MemSave.
 *                      Note that this descriptor becomes invalid as soon as
 *                      you deallocate any temporary memory block that was
 *                      allocated before the call to UT_MemRemember!
 *
 *      See also:
 *
 ***************************************************************************/

UT_MemState UT_MemRemember (void)
{
    if (!initialized) {
        init ();
    }
    return lasttemp;
}


/***************************************************************************
 *[@e
 *      Name:           UT_MemRestore
 *
 *      Usage:          Restore the memory allocation list to a state
 *                      previously saved with UT_MemRemember.
 *
 *      Synopsis:       void UT_MemRestore (UT_MemState mh)
 *
 *      Description:    "Mh" must be a descriptor which was previously
 *                      obtained from UT_MemRemember. All temporary memory
 *                      blocks which were allocated but not freed, and all
 *                      descriptors returned by UT_MemRemember after the call
 *                      to UT_MemRemember which returned "mh", are deallocated.
 *                      "Mh" itself remains valid.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_MemRestore (UT_MemState mh)
{
    UT_MemHdr   *header,
                *next;
    char        *user;

    #if defined(DEBUG_MEM)
        size_t  *checkbegin,
                *checkend;
    #endif

    if (!initialized) {
        init ();
    }

    /* Check if mh can possibly be a valid pointer. */

    if (mh && mh->size < memheadersize) {
        UT_ErrFatal ("UT_MemRestore", "called with invalid argument");
    }

    /* Find the first block we have to deallocate. */

    header = mh? mh->next: firsttemp;

    /* Remove this and all following memory blocks from the
    temporary block list. */

    if (mh) {
        mh->next = NULL;
    } else {
        firsttemp = NULL;
    }
    lasttemp = mh;

    /* Go through the list of blocks that have been removed from the
    temporary block list, perform some consistency checks and deallocate
    the blocks. */

    while (header) {
        #if defined(DEBUG_MEM)
            header_to_user (header, &user, &checkbegin, &checkend);
            if (*checkbegin == checkbytefree)
                fatal (header, "UT_MemRestore",
                       "block freed twice");
            if (*checkbegin != checkbytebegin)
                fatal (header, "UT_MemRestore",
                       "block boundary violation; "
                       "write before start of block");
            if (*checkend != checkbyteend)
                fatal (header, "UT_MemRestore",
                       "block boundary violation; "
                       "write after end of block");
        #else
            header_to_user (header, &user);
        #endif

        if (header->size < memheadersize) {
            UT_ErrFatal ("UT_MemRestore", "memory block size invalid");
        }

        /* If destroy mode is switched on, we erase the contents of
        the block's user data area. */

        if (destroymode) {
            destroy_data (user, usersize (header->size));
        }

        /* Before we deallocate the block, we change the checkbytes
        at the beginning, so that we can detect if a memory block is
        freed twice. */

        #if defined(DEBUG_MEM)
            *checkbegin = checkbytefree;
        #endif

        next = header->next;
        memFree (header, header->size);
        header = next;
    }
    return;
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemSave
 *
 *      Usage:          Protect memory blocks from being deallocated
 *                      by UT_MemRestore.
 *
 *      Synopsis:       void UT_MemSave (UT_MemState mh)
 *
 *      Description:    "Mh" must be a descriptor which was previously obtained
 *                      from UT_MemRemember. All temporary memory blocks which
 *                      were allocated but not freed after the call to
 *                      UT_MemRemember, which returned "mh", are made permanent,
 *                      and all descriptors returned by UT_MemRemember after the
 *                      call to UT_MemRemember, which returned "mh", become
 *                      invalid. "Mh" remains valid.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_MemSave (UT_MemState mh)
{
    UT_MemHdr   *first,
                *last;

    if (!initialized) {
        init ();
    }

    /* Check if mh can possibly be a valid pointer. */

    if (mh && mh->size < memheadersize) {
        UT_ErrFatal ("UT_MemSave", "called with invalid argument");
    }

    /* Check if any blocks were allocated and not freed since
    the last call to UT_MemRemember. */

    if (mh == lasttemp) {
        return;
    }

    /* Find the first and the last block we have to move. */

    first = mh? mh->next: firsttemp;
    last = lasttemp;

    /* Remove the blocks from "first" to "last" from
    the temporary memory block list. */

    if (mh) {
        mh->next = NULL;
    } else {
        firsttemp = NULL;
    }
    lasttemp = mh;

    /* Attach them to the beginning of the permanent
    memory block list. */

    if ((first && !last) || (last && !first)) {
        UT_ErrFatal ("UT_MemSave", "inconsistent temporary block list");
    }
    if (first) {
        if (firstperm) {
            firstperm->previous = last;
        } else {
            lastperm = last;
        }
        last->next = firstperm;
        first->previous = NULL;
        firstperm = first;
    }
    return;
}


/***************************************************************************
 *[@e
 *      Name:           UT_MemDestroy
 *
 *      Usage:          Switch destroy mode on or off.
 *
 *      Synopsis:       void UT_MemDestroy (UT_Bool onoff)
 *
 *      Description:    If "onoff" is UT_True, destroy mode is switched on, i.e.
 *                      the contents of all memory blocks deallocated using
 *                      UT_MemFree or UT_MemRestore are immediately destroyed
 *                      by overwriting them with a constant bit pattern. 
 *                      If "onoff" is UT_False, destroy mode is switched off.
 *                      Destroy mode can help to detect read accesses to a
 *                      memory block after that block has been deallocated.
 *
 *      Return value:   None.
 *
 *      See also:
 *
 ***************************************************************************/

void UT_MemDestroy (UT_Bool onoff)
{
    if (!initialized) {
        init ();
    }
    destroymode = onoff;
    return;
}


/***************************************************************************
 *[@e
 *      Name:           UT_MemCheck
 *
 *      Usage:          Check if all memory blocks allocated by UT_MemTempAlloc
 *                      were deallocated (using UT_MemFree or UT_MemRestore).
 *
 *      Synopsis:       UT_Bool UT_MemCheck (void)
 *
 *      Description:    This function is intended for debugging memory
 *                      handling of a program. It should be called just
 *                      before a program exits, to check if the program
 *                      forgot to deallocate any memory.
 *
 *      Return value:   UT_True if there are no more dynamically allocated
 *                      memory blocks around, otherwise UT_False.
 *
 *                      Note: If the memory management library has been
 *                      compiled with the preprocessor macro DEBUG_MEM defined,
 *                      UT_MemCheck prints a list of the names of all memory
 *                      blocks to stderr.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool UT_MemCheck (void)
{
    if (!initialized) {
        init ();
    }

    if ((firsttemp && !lasttemp) || (lasttemp && !firsttemp)) {
        UT_ErrFatal ("UT_MemCheck", "temporary block list inconsistent");
    }
    if ((firstperm && !lastperm) || (lastperm && !firstperm)) {
        UT_ErrFatal ("UT_MemCheck", "permanent block list inconsistent");
    }
    if (!firsttemp && !firstperm) {
        #if defined(DEBUG_MEM)
            fprintf (stderr, "\nUT_MemCheck OK\n");
        #endif
        return UT_True;
    }

    #if defined(DEBUG_MEM)
        fprintf (stderr, "\nCURRENTLY ALLOCATED MEMORY BLOCKS:\n\n");
        fprintf (stderr, "Temporary:\n");
        printblocklist (firsttemp);
        fprintf (stderr, "Permanent:\n");
        printblocklist (firstperm);
        fprintf (stderr, "\nUT_MemCheck FAILED\n");
    #endif

    return UT_False;
}

/***************************************************************************
 *[@e
 *      Name:           UT_MemCheckBlock
 *
 *      Usage:          Check for block boundary violations.
 *
 *      Synopsis:       UT_Bool UT_MemCheckBlock (void *blk)
 *
 *      Description:    UT_MemCheckBlock tests if there were any write accesses
 *                      to the bytes just before the first byte of memory
 *                      block "blk" or right after the last byte of the block.
 *                      This function is intended for debugging a program's
 *                      memory handling.
 *
 *      Return value:   UT_False if write accesses outside the boundaries of
 *                      the block are detected, otherwise UT_True.
 *
 *                      Note: If the memory management library has been
 *                      compiled without defining the preprocessor macro
 *                      DEBUG_MEM, UT_MemCheckBlock always returns UT_True.
 *
 *      See also:
 *
 ***************************************************************************/

UT_Bool UT_MemCheckBlock (void *blk)
{
    #if defined (DEBUG_MEM)
        UT_MemHdr       *header;
        size_t          *checkbegin,
                        *checkend;

        /* Calculate the addresses of the memory block header
        and the checkbytes. */

        user_to_header (blk, &header, &checkbegin, &checkend);

        /* Check if the size of the memory block equal to -1, which
        means that the block has already been freed. */

        if (*checkbegin == checkbytefree) {
            UT_ErrFatal ("UT_MemCheckBlock", "block already freed");
        }

        /* See if the check bytes at both ends of the block still
        have their original values. If they have changed, the
        boundaries of the memory block were obviously violated. */

        if (*checkbegin != checkbytebegin) {
            fprintf (stdout, "checkbytebegin changed\n");
            fflush (stdout);
            return UT_False;
        }
        if (*checkend != checkbyteend) {
            fprintf (stdout, "checkbyteend changed\n");
            fflush (stdout);
            return UT_False;
        }
    #endif /* DEBUG_MEM */
    return UT_True;
}
