// ---------------------------------------------------------------------------
// - cmem.cxx                                                                -
// - standard system library - c memory function implementation              -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2008 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "csys.hpp"
#include "csio.hpp"
#include "cthr.hpp"
#include "cmem.hpp"
#include "cmem.hxx"

namespace afnix {

  // the memory access control lock
  static void* mtxmem = 0;

  // the structure for traced memory
  struct s_gptr {
    s_gptr* p_prev;
    s_gptr* p_next;
    void*   p_bptr;
    long    d_size;
    long    d_magic;
  };

  // this function computes the number of pages for a given size
  static long get_psize (const long size) {
    long psize = c_pagesize ();
    long result = size / psize;
    if ((size % psize) != 0) result++;
    return (result * psize);
  }
  // this function computes the number of pages for a given size
  static long get_psize (const long size, const long foff) {
    long psize = c_pagesize ();
    long result = size / psize + ((foff == 0) ? 0 : 1);
    if ((size % psize) != 0) result++;
    return (result * psize);
  }

  // this function adjust an offset on a page boundary
  static long get_osize (const long size) {
    long psize = c_pagesize ();
    long result = size / psize;
    return (result * psize);
  }

  // simple function to align on a 8 bytes basis
  static unsigned long align (const unsigned long size) {
    unsigned long pad = size % 8;
    if (pad == 0) return size;
    return (((size / 8) + 1) * 8);
  }

  // the size with the padding
  unsigned long offset = align (sizeof (s_gptr));
  
  // the root pointer for traced memory
  static s_gptr* groot = 0;

  // the memory tracing counter and the magic number
  static long gacnt        = 0;
  static long gfcnt        = 0;
  const  long GALLOC_MAGIC = 0x0fabcdef;

  // the cleanup counter and function array
  typedef   void (*t_cfunc) (void);
  static    long  gccnt = 0;
  static t_cfunc* gcfcn = 0;

  // the initialize flag for memory tracing
  static bool gflag = false;
  static bool gmchk = (c_getenv ("AFNIX_GALLOC_CHECK") != nilp);
  static bool gpstk = (c_getenv ("AFNIX_GALLOC_DEBUG") != nilp);
  static bool gctrc = (c_getenv ("AFNIX_GALLOC_TRACE") != nilp);
  static bool gdymd = (c_getenv ("AFNIX_GALLOC_DYNMD") != nilp);
  static bool gctrl = gmchk || gpstk || gctrc;
  static bool gdctr = false;

  // enable/disable dynamic memory debug

  void c_setmdbg (const bool flag) {
    // do nothing if the dynamic debugging
    // was not previsouly authorized
    if (gdymd == false) return;
    // get the memory mutex
    c_mtxlock (mtxmem);
    // set the control flag
    gctrl = flag;
    // set the dynamic control
    gdctr = true;
    // release memeory mutex
     c_mtxunlock (mtxmem);
  }

  // this function report the garbage memory at exit
  static void galloc_report (void) {
    while (groot != 0) {
      if (groot->d_magic != GALLOC_MAGIC) {
        fprintf (stderr, "galloc: invalid pointer at %p\n", groot);
        abort ();
      }
      void* handle = ((char*) groot) + offset;
      fprintf (stderr, "garbage allocation of %ld bytes\n",groot->d_size); 
      fprintf (stderr, "\tobject: %p\n",handle); 
      c_printtrace (groot->p_bptr);
      groot = groot->p_next;
    }
    fprintf (stderr, "total allocated memory: %ld\n", gacnt);
    fprintf (stderr, "total freed     memory: %ld\n", gfcnt);
  }

  // this function calls the memory cleanup functions and the report
  // function for garbage memory
  static void galloc_cleanup (void) {
    // call the cleanup functions
    for (long i = 0; i < gccnt; i++) {
      t_cfunc func = (t_cfunc) gcfcn[i];
      func ();
    }
    // do the report
    galloc_report ();
    // clean the mutex
    c_mtxdestroy (mtxmem);
  }

  // this function initalize the memory tracing
  static void galloc_init (void) {
    if ((gctrl == false) || (gflag == true)) return;
    c_atexit (galloc_cleanup);
    gflag  = true;
    mtxmem = c_mtxcreate ();
  }

  // this function clean the memory debug stack
  static void galloc_clean (s_gptr* handle) {
    // get the lock before cleaning up
    c_mtxlock (mtxmem);

    // unlink the structure and free it
    s_gptr* prev = handle->p_prev;
    s_gptr* next = handle->p_next;
    if (prev == 0) {
      groot = next;
      if (groot != 0) groot->p_prev = 0;
    } else {
      prev->p_next = next;
      if (next != 0) next->p_prev = prev;
    }
    gfcnt += handle->d_size;

    // check if we print the stack trace for the allocation
    if (gpstk == true) {
      fprintf (stderr, "destruction of %ld bytes\n", handle->d_size);
      fprintf (stderr, "object: %p\n", handle);
      c_printtrace (handle->p_bptr);
    }
    c_destroytrace (handle->p_bptr);
    free (handle);
    // release the lock
    c_mtxunlock (mtxmem);
  }

  // allocate some memory for tracing
  
  void* c_galloc (const long size) {
    // do nothing in non tracing mode
    if (gctrl == false) return malloc (size);

    // handle first the check mode
    if (gmchk == true) {
      void* result = malloc (size + 8);
      (* (long long int*) result) = 0;
      return ((char*) result + 8);
    }

    // initialize the memory tracing
    if (gflag == false) galloc_init ();

    // get the lock before allocation
    c_mtxlock (mtxmem);

    // allocate the memory block
    s_gptr* handle  = (s_gptr*) malloc (size + offset);
    if (groot != 0) groot->p_prev = handle;
    handle->p_next  = groot;
    handle->p_prev  = 0;
    handle->d_size  = size;
    handle->d_magic = GALLOC_MAGIC;
    handle->p_bptr  = c_backtrace ();
    groot  = handle;
    gacnt += size;
    // re-align result
    void* result = ((char*) handle) + offset;

    // check if we print the stack trace for the allocation
    if (gpstk == true) {
      fprintf (stderr, "allocation of %ld bytes\n", size);
      fprintf (stderr, "object: %p\n", handle);
      c_printtrace (handle->p_bptr);
    }
    c_mtxunlock (mtxmem);
    return result;
  }

  // free some memory in tracing mode

  void c_gfree (void* ptr) {
    // handle the simple free vs complex one
    if (gctrl == false) {
      if (gdctr == false) {
	// here the memory debugger was never turned on dynamically
	free (ptr);
      } else {
	// here the memory debugger was turned off dynamically
	s_gptr* handle = (s_gptr*) ((char*) (ptr) - offset);
	if (handle->d_magic == GALLOC_MAGIC) {
	  galloc_clean (handle);
	} else {
	  free (ptr);
	}
      }
      return;
    }

    // handle memory check first
    if (gmchk == true) {
      c_mtxlock (mtxmem);
      void* handle = (char*) ptr - 8;
      if ((* (long long int*) handle) != 0) {
        fprintf (stderr, "galloc: invalid memory free\n");
        c_mtxunlock (mtxmem);
        return;
      }
      *(long long int*) handle = 1;
      c_mtxunlock (mtxmem);
      return;
    }

    // get the structure and check the magic number
    s_gptr* handle = (s_gptr*) ((char*) (ptr) - offset);
    if (handle->d_magic != GALLOC_MAGIC) {
      if (gdctr == false) {
	// here the memory debugger was never turned on dynamically
	fprintf (stderr, "galloc: invalid pointer to free at %p\n", ptr);
	abort ();
      } else {
	// assume here the pointer was allocated before turning on
	// the memory debugger
	free (ptr);
      }
      return;
    }
    // clean the handle
    galloc_clean (handle);
  }


  // register a memory cleanup function

  void c_gcleanup (void (*func) (void)) {
    // just use atexit if no tracing
    if (gctrl == false) {
      c_atexit (func);
      return;
    }
    // allocate a new array of cleanup functions
    t_cfunc* array = (t_cfunc*) malloc ((gccnt+1) * sizeof (t_cfunc));
    // copy the old array to the new one
    for (long i = 0; i < gccnt; i++) array[i] = gcfcn[i];
    array[gccnt++] = func;
    free (gcfcn);
    gcfcn = array;
  }

  // allocate some memory with malloc

  void* c_malloc (const long size) {
    if (size <= 0) return 0;
    return malloc (size);
  }

  // free some memory with normal free

  void c_free (void* handle) {
    if (handle == 0) return;
    free (handle);
  }

  // map some memory with a file descriptor
  
  void* c_mmap (const int sid, const long size, const long foff) {
    if ((sid == -1) || (size == 0)) return nilp;
    // get the memory to allocate in page
    long psize = get_psize (size, foff);
    // get the offset aligned to pages
    long osize = get_osize (foff);
    long opage = foff - osize;
    char* ptr  = (char*) mmap (0, psize, PROT_READ|PROT_WRITE, MAP_PRIVATE,
			       sid, osize);
    if (ptr == MAP_FAILED) return nilp;
    return (ptr + opage);
  }

  // unmap a memory block
  void c_munmap (void* ptr, const long size) {
    long psize = get_psize (size);
    munmap ((caddr_t) ptr, psize);
  }
}

#ifdef AFNIX_HAVE_SYSCONF
namespace afnix {
  // return the system memory page
  long c_pagesize (void) {
    return sysconf (_SC_PAGESIZE);
  }
}
#endif

#ifdef AFNIX_HAVE_PAGESIZE
namespace afnix {
  // return the system memory page
  long c_pagesize (void) {
    return getpagesize ();
  }
}
#endif

#ifdef AFNIX_HAVE_MAPANON
namespace afnix {
  // allocate a block of memory
  void* c_mmap (const long size) {
    long  psize = get_psize (size);
    void* ptr   = mmap (0, psize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON,
			-1, 0);
    return (ptr == MAP_FAILED) ? nilp : ptr;
  }
}
#else
namespace afnix {
  // allocate a block of memory
  void* c_mmap (const long size) {
    int fd = open ("/dev/zero", O_RDWR);
    if (fd == -1) return nilp;
    long psize = get_psize (size);
    void* ptr  = mmap (0,psize,PROT_READ|PROT_WRITE,MAP_PRIVATE, fd ,0);
    close (fd);
    return (ptr == MAP_FAILED) ? nilp : ptr;
  }
}
#endif

#ifdef AFNIX_HAVE_MREMAP
namespace afnix { 
  // reallocate a block of memory
  void* c_mremap (void* optr, const long osize, const long nsize) {
    long  posize = get_psize (osize);
    long  pnsize = get_psize (nsize);
    void* ptr    = mremap ((caddr_t) optr,posize,pnsize,MREMAP_MAYMOVE);
    return (ptr == MAP_FAILED) ? nilp : ptr;
  }
}
#else
namespace afnix {
  // reallocate a block of memory - on sun we have to allocate and copy
  void* c_mremap (void* optr, const long osize, const long nsize) {
    if (nsize <= osize) return optr;
    void* nptr = c_mmap (nsize);
    unsigned char* cnptr = (unsigned char*) nptr;
    unsigned char* coptr = (unsigned char*) optr;
    for (long i = 0; i < osize; i++) *cnptr++ = *coptr++;
    c_munmap (optr, osize);;
    return nptr;
  }
}
#endif
