/*
 * (c) Copyright 1990 Conor P. Cahill (uunet!virtech!cpcahil).
 * You may copy, distribute, and use this software as long as this
 * copyright statement is not removed.
 */
#include <stdio.h>
#include <fcntl.h>
#include "malloc.h"
#include "tostring.h"

/*
 * Function:    malloc()
 *
 * Purpose: memory allocator
 *
 * Arguments:   size    - size of data area needed
 *
 * Returns: pointer to allocated area, or NULL if unable
 *      to allocate addtional data.
 *
 * Narrative:
 *
 */
#ifndef lint
static
char rcs_hdr[] = "$Id: malloc.c,v 1.2 2006-07-25 10:08:36 rt Exp $";
#endif

extern int    malloc_checking;
char        * malloc_data_start;
char        * malloc_data_end;
struct mlist    * malloc_end;
int       malloc_errfd = 2;
int       malloc_errno;
int       malloc_fatal_level = M_HANDLE_CORE;
struct mlist      malloc_start;
int       malloc_warn_level;
void          malloc_memset();

char *
malloc(size)
    unsigned int      size;
{
    char        * func = "malloc";
    char        * getenv();
    void          malloc_fatal();
    void          malloc_init();
    void          malloc_split();
    void          malloc_warning();
    unsigned int      need;
    struct mlist    * oldptr;
    struct mlist    * ptr;
    char        * sbrk();

    /*
     * If this is the first call to malloc...
     */
    if( malloc_data_start == (char *) 0 )
    {
        malloc_init();
    }

    /*
     * If malloc chain checking is on, go do it.
     */
    if( malloc_checking )
    {
        (void) malloc_chain_check(1);
    }

    /*
     * always make sure there is at least on extra byte in the malloc
     * area so that we can verify that the user does not overrun the
     * data area.
     */
    size++;

    /*
     * Now look for a free area of memory of size bytes...
     */
    oldptr = NULL;
    for(ptr = &malloc_start; ; ptr = ptr->next)
    {
        /*
         * Since the malloc chain is a forward only chain, any
         * pointer that we get should always be positioned in
         * memory following the previous pointer.  If this is not
         * so, we must have a corrupted chain.
         */
        if( ptr )
        {
            if( ptr<oldptr )
            {
                malloc_errno = M_CODE_CHAIN_BROKE;
                malloc_fatal(func);
                return(NULL);
            }
            oldptr = ptr;
        }
        else if( oldptr != malloc_end )
        {
            /*
             * This should never happen.  If it does, then
             * we got a real problem.
             */
            malloc_errno = M_CODE_NO_END;
            malloc_fatal(func);
            return(NULL);
        }


        /*
         * if this element is already in use...
         */
        if( ptr && ((ptr->flag & M_INUSE) != 0) )
        {
            continue;
        }

        /*
         * if there isn't room for this block..
         */
        if( ptr && (ptr->s.size < size) )
        {
            continue;
        }

        /*
         * If ptr is null, we have run out of memory and must sbrk more
         */
        if( ptr == NULL )
        {
            need = (size + M_SIZE) * (size > 10*1024 ? 1:2);
            if( need < M_BLOCKSIZE )
            {
                need = M_BLOCKSIZE;
            }
            else if( need & (M_BLOCKSIZE-1) )
            {
                need &= ~(M_BLOCKSIZE-1);
                need += M_BLOCKSIZE;
            }
            ptr = (struct mlist *) sbrk((int)need);
            if( ptr == (struct mlist *) -1 )
            {
                malloc_errno = M_CODE_NOMORE_MEM;
                malloc_fatal(func);
            }
            malloc_data_end = sbrk((int)0);

            ptr->prev   = oldptr;
            ptr->next   = (struct mlist *) 0;
            ptr->s.size = need - M_SIZE;
            ptr->flag  = M_MAGIC;

            oldptr->next = ptr;
            malloc_end = ptr;


        } /* if( ptr ==... */

        /*
          * Now ptr points to a memory location that can store
         * this data, so lets go to work.
         */

        ptr->r_size = size;     /* save requested size  */
        ptr->flag |= M_INUSE;

        /*
          * split off unneeded data area in this block, if possible...
         */
        malloc_split(ptr);

        /*
         * re-adjust the requested size so that it is what the user
         * actually requested...
         */

        ptr->r_size--;

        /*
         * just to make sure that noone is misusing malloced
          * memory without initializing it, lets set it to
         * all '\01's.  We call local_memset() because memset()
         * may be checking for malloc'd ptrs and this isn't
         * a malloc'd ptr yet.
         */
        malloc_memset(ptr->data,M_FILL,(int)ptr->s.size);

        return( ptr->data);

    } /* for(... */

} /* malloc(... */

/*
 * Function:    malloc_split()
 *
 * Purpose: to split a malloc segment if there is enough room at the
 *      end of the segment that isn't being used
 *
 * Arguments:   ptr - pointer to segment to split
 *
 * Returns: nothing of any use.
 *
 * Narrative:
 *      get the needed size of the module
 *      round the size up to appropriat boundry
 *      calculate amount of left over space
 *      if there is enough left over space
 *          create new malloc block out of remainder
 *          if next block is free
 *          join the two blocks together
 *          fill new empty block with free space filler
 *          re-adjust pointers and size of current malloc block
 *
 *
 *
 * Mod History:
 *   90/01/27   cpcahil     Initial revision.
 */
void
malloc_split(ptr)
    struct mlist        * ptr;
{
    extern struct mlist * malloc_end;
    void              malloc_join();
    int           rest;
    int           size;
    struct mlist        * tptr;

    size = ptr->r_size;

    /*
     * roundup size to the appropriate boundry
     */

    M_ROUNDUP(size);

    /*
     * figure out how much room is left in the array.
     * if there is enough room, create a new mlist
     *     structure there.
     */

    if( ptr->s.size > size )
    {
        rest = ptr->s.size - size;
    }
    else
    {
        rest = 0;
    }

    if( rest > (M_SIZE+M_RND) )
    {
        tptr = (struct mlist *) (ptr->data+size);
        tptr->prev = ptr;
        tptr->next = ptr->next;
        tptr->flag = M_MAGIC;
        tptr->s.size = rest - M_SIZE;

        /*
         * If possible, join this segment with the next one
         */

        malloc_join(tptr, tptr->next,0,0);

        if( tptr->next )
        {
            tptr->next->prev = tptr;
        }

        malloc_memset(tptr->data,M_FREE_FILL, (int)tptr->s.size);

        ptr->next = tptr;
        ptr->s.size = size;

        if( malloc_end == ptr )
        {
            malloc_end = tptr;
        }
    }

} /* malloc_split(... */

/*
 * Function:    malloc_join()
 *
 * Purpose: to join two malloc segments together (if possible)
 *
 * Arguments:   ptr - pointer to segment to join to.
 *      nextptr - pointer to next segment to join to ptr.
 *
 * Returns: nothing of any values.
 *
 * Narrative:
 *
 * Mod History:
 *   90/01/27   cpcahil     Initial revision.
 */
void
malloc_join(ptr,nextptr, inuse_override, fill_flag)
    struct mlist    * ptr;
    struct mlist    * nextptr;
    int       inuse_override;
    int       fill_flag;
{
    unsigned int      newsize;

    if(     ptr     && ! (inuse_override || (ptr->flag & M_INUSE)) &&
        nextptr && ! (nextptr->flag & M_INUSE) &&
        ((ptr->data+ptr->s.size) == (char *) nextptr) )
    {
        if( malloc_end == nextptr )
        {
            malloc_end = ptr;
        }
        ptr->next = nextptr->next;
        newsize = nextptr->s.size + M_SIZE;

        /*
         * if we are to fill and this segment is in use,
         *   fill in with M_FILL newly added space...
          */

        if(fill_flag && (ptr->flag & M_INUSE) )
        {
            malloc_memset(ptr->data+ptr->s.size,
                      M_FILL, (int)(nextptr->s.size + M_SIZE));
        }

        ptr->s.size += newsize;
        if( ptr->next )
        {
            ptr->next->prev = ptr;
        }
    }

} /* malloc_join(... */


/*
 * The following mess is just to ensure that the versions of these functions in
 * the current library are included (to make sure that we don't accidentaly get
 * the libc versions. (This is the lazy man's -u ld directive)
 */

void free();
int strcmp();
int memcmp();
char    * realloc();

void        (*malloc_void_funcs[])() =
{
    free,
};

int     (*malloc_int_funcs[])() =
{
    strcmp,
    memcmp,
};

char        * (*malloc_char_star_funcs[])() =
{
    realloc,
};

/*
 * This is malloc's own memset which is used without checking the parameters.
 */

void
malloc_memset(ptr,byte,len)
    char        * ptr;
    char          byte;
    int       len;
{

    while(len-- > 0)
    {
        *ptr++ = byte;
    }

} /* malloc_memset(... */

/*
 * Function:    malloc_fatal()
 *
 * Purpose: to display fatal error message and take approrpriate action
 *
 * Arguments:   funcname - name of function calling this routine
 *
 * Returns: nothing of any value
 *
 * Narrative:
 *
 * Notes:   This routine does not make use of any libc functions to build
 *      and/or disply the error message.  This is due to the fact that
 *      we are probably at a point where malloc is having a real problem
 *      and we don't want to call any function that may use malloc.
 */
void
malloc_fatal(funcname)
    char        * funcname;
{
    char          errbuf[128];
    void          exit();
    void          malloc_err_handler();
    extern char * malloc_err_strings[];
    extern int    malloc_errno;
    extern int    malloc_fatal_level;
    char        * s;
    char        * t;

    s = errbuf;
    t = "Fatal error: ";
    while( *s = *t++)
    {
        s++;
    }
    t = funcname;
    while( *s = *t++)
    {
        s++;
    }

    t = "(): ";
    while( *s = *t++)
    {
        s++;
    }

    t = malloc_err_strings[malloc_errno];
    while( *s = *t++)
    {
        s++;
    }

    *(s++) = '\n';

    if( write(malloc_errfd,errbuf,(unsigned)(s-errbuf)) != (s-errbuf))
    {
        (void) write(2,"I/O error to error file\n",(unsigned)24);
        exit(110);
    }
    malloc_err_handler(malloc_fatal_level);

} /* malloc_fatal(... */

/*
 * Function:    malloc_warning()
 *
 * Purpose: to display warning error message and take approrpriate action
 *
 * Arguments:   funcname - name of function calling this routine
 *
 * Returns: nothing of any value
 *
 * Narrative:
 *
 * Notes:   This routine does not make use of any libc functions to build
 *      and/or disply the error message.  This is due to the fact that
 *      we are probably at a point where malloc is having a real problem
 *      and we don't want to call any function that may use malloc.
 */
void
malloc_warning(funcname)
    char        * funcname;
{
    char          errbuf[128];
    void          exit();
    void          malloc_err_handler();
    extern char * malloc_err_strings[];
    extern int    malloc_errno;
    extern int    malloc_warn_level;
    char        * s;
    char        * t;

    s = errbuf;
    t = "Warning: ";
    while( *s = *t++)
    {
        s++;
    }
    t = funcname;
    while( *s = *t++)
    {
        s++;
    }

    t = "(): ";
    while( *s = *t++)
    {
        s++;
    }

    t = malloc_err_strings[malloc_errno];
    while( *s = *t++)
    {
        s++;
    }

    *(s++) = '\n';

    if( write(malloc_errfd,errbuf,(unsigned)(s-errbuf)) != (s-errbuf))
    {
        (void) write(2,"I/O error to error file\n",(unsigned)24);
        exit(110);
    }

    malloc_err_handler(malloc_warn_level);

} /* malloc_warning(... */

/*
 * Function:    malloc_err_handler()
 *
 * Purpose: to take the appropriate action for warning and/or fatal
 *      error conditions.
 *
 * Arguments:   level - error handling level
 *
 * Returns: nothing of any value
 *
 * Narrative:
 *
 * Notes:   This routine does not make use of any libc functions to build
 *      and/or disply the error message.  This is due to the fact that
 *      we are probably at a point where malloc is having a real problem
 *      and we don't want to call any function that may use malloc.
 */
void
malloc_err_handler(level)
{
    void          exit();
    void          malloc_dump();
    extern int    malloc_errfd;

    if( level & M_HANDLE_DUMP )
    {
        malloc_dump(malloc_errfd);
    }

    switch( level & ~M_HANDLE_DUMP )
    {
        /*
         * If we are to drop a core file and exit
         */
        case M_HANDLE_ABORT:
            (void) abort();
            break;

        /*
         * If we are to exit..
         */
        case M_HANDLE_EXIT:
            exit(200);
            break;

#ifndef __MSDOS__
        /*
         * If we are to dump a core, but keep going on our merry way
         */
        case M_HANDLE_CORE:
            {
                int   pid;

                /*
                 * fork so child can abort (and dump core)
                 */
                if( (pid = fork()) == 0 )
                {
                    (void) write(2,"Child dumping core\n",
                            (unsigned)9);
                    (void) abort();
                }

                /*
                  * wait for child to finish dumping core
                 */
                while( wait((int *)0) != pid)
                {
                }

                /*
                 * Move core file to core.pid.cnt so
                 * multiple cores don't overwrite each
                 * other.
                 */
                if( access("core",0) == 0 )
                {
                    static int    corecnt;
                    char          filenam[32];
                    filenam[0] = 'c';
                    filenam[1] = 'o';
                    filenam[2] = 'r';
                    filenam[3] = 'e';
                    filenam[4] = '.';
                    (void)tostring(filenam+5,getpid(),
                        5, B_DEC, '0');
                    filenam[10] = '.';
                    (void)tostring(filenam+11,corecnt++,
                        3, B_DEC, '0');
                    filenam[14] = '\0';
                    (void) unlink(filenam);
                    if( link("core",filenam) == 0)
                    {
                        (void) unlink("core");
                    }
                }
            }
#endif


        /*
         * If we are to just ignore the error and keep on processing
         */
        case M_HANDLE_IGNORE:
            break;

    } /* switch(... */

} /* malloc_err_handler(... */