/*****************************************************************************\
* Copyright (c) 2002 Pelle Johansson.                                         *
* All rights reserved.                                                        *
*                                                                             *
* This file is part of the moftpd package. Use and distribution of            *
* this software is governed by the terms in the file LICENCE, which           *
* should have come with this package.                                         *
\*****************************************************************************/

/* $moftpd: memory.c 1224 2004-10-28 22:42:00Z morth $ */

#include "system.h"

#include "memory.h"

#ifdef DEBUG
extern int debug;
static void DOBUG (const char *fmt, ...)
{
  va_list ap;
  
  va_start (ap, fmt);
  vsyslog (LOG_DEBUG, fmt, ap);
  va_end (ap);
}
#undef DEBUG
#define DEBUG(level, msg) if (debug >= level) DOBUG msg
#define DEXPR(x) x
#else
#define DEBUG(level, msg)
#define DEXPR(x)
#endif

#define stringHashSize 31 // gcc chokes on const int

static memory_t *tMem;
static memdata_t *stringHash[stringHashSize];
static unsigned char randVals[256];
static unsigned int sweepMark;
static memdata_t *memChain, *rootChain;

void mem_init (void)
{
  int fd = open ("/dev/urandom", O_RDONLY);
  ssize_t j;
  int i;
  
  i = 256;
  if (fd >= 0)
  {
    for (; i > 0; i -= j)
    {
      j = read (fd, randVals + 256 - i, (size_t)i);
      if (j < 0)
	break;
    }
    (void)close (fd);
  }
  
  if (i)
  {
    srand ((unsigned int)time (NULL));
    for (; i > 0; i--)
      randVals[256 - i] = random ();
  }
}

void *talloc(size_t sz)
{
  memory_t *mem = calloc(1, sz + sizeof(memory_t) * pNumTempLinks);
  
  if(!mem)
    return NULL;
  
  mem[pNextTemp].link = tMem;
  tMem = mem;
  
  DEBUG (2, ("talloc: %p", mem + pNumTempLinks));
  
  return mem + pNumTempLinks;
}

void *trealloc(void *ptr, size_t sz)
{
  memory_t *mem = (memory_t*)ptr - pNumTempLinks, *res, *pmem, *nmem;
  
  if (!ptr)
    return talloc (sz);
  
  nmem = mem[pNextTemp].link;
  res = realloc(mem, sz + sizeof(memory_t) * pNumTempLinks);
  
  if (res == mem)
    return res + pNumTempLinks;
  
  DEBUG (2, ("trealloc: %p -> %p", mem + pNumTempLinks, res + pNumTempLinks));
  
  if(mem == tMem)
  {
    if(res)
      tMem = res;
    else
      tMem = nmem;
  }
  else
  {
    for (pmem = tMem; pmem[pNextTemp].link && pmem[pNextTemp].link != mem;
	 pmem = pmem->link)
      ;
    if (pmem[pNextTemp].link)
    {
      if(res)
	pmem[pNextTemp].link = res;
      else
	pmem[pNextTemp].link = nmem;
    }
  }
  
  if(res)
    return res + pNumTempLinks;
  return NULL;
}

char *tstring(const char *str)
{
  char *res;
  
  if (!str)
    return NULL;
  
  res = talloc (strlen (str) + 1);
  if(res)
    strcpy(res, str);
  
  return res;
}

static void mark_memory (memdata_t *mem)
{
  memory_t *cmem, *nmem;
  
  if (mem->mark == sweepMark)
    return;
  mem->mark = sweepMark;
  if (mem->isHashed)
    return;
  for (cmem = mem->firstChild.link; cmem; cmem = nmem)
  {
    nmem = cmem[pNextChild].link;
#ifdef MEMORY_LOCATION
    if (cmem[pLocation].ptr && *cmem[pLocation].ptr != mem + 1)
    {
      pfree (cmem[pChild].data + 1, mem + 1);
      continue;
    }
#endif
    mark_memory (cmem[pChild].data);
  }
}

void tfree_all(void)
{
  memory_t *nmem;
  memdata_t *chain, *pchain;
#ifdef MEMORY_LOCATION
  memory_t *mem, *pmem;
#endif
  
  while(tMem)
  {
    nmem = tMem[pNextTemp].link;
    DEBUG (2, ("tfree: %p", tMem + pNumTempLinks));
    free (tMem);
    tMem = nmem;
  }
  
  sweepMark ^= 1;
#ifdef MEMORY_LOCATION
  pchain = NULL;
#endif
  chain = rootChain;
  while (chain)
  {
#ifdef MEMORY_LOCATION
    if (chain->firstParent.link)
    {
      pmem = NULL;
      for (mem = chain->firstParent.link; mem; mem = nmem)
      {
	nmem = mem[pNextParent].link;
	if (mem[pLocation].ptr && *mem[pLocation].ptr != chain + 1)
	{
	  if (pmem)
	    pmem[pNextParent].link = nmem;
	  else
	    chain->firstParent.link = nmem;
	  free (pmem);
	}
	else
	  pmem = mem;
      }
      if (!chain->firstParent.link)
      {
	if (pchain)
	{
	  pchain->next = chain->next;
	  DEBUG (3, ("tfree: %p->next -> %p", pchain, pchain->next));
	}
	else
	  rootChain = chain->next;
	DEBUG (2, ("collected: %p", chain + 1));
	pfree (chain + 1, (void*)-1);
	if (pchain)
	  chain = pchain->next;
	else
	  chain = memChain;
	continue;
      }
    }
#endif
    mark_memory (chain);
#ifdef MEMORY_LOCAIION
    pchain = chain;
#endif
    chain = chain->next;
  }
  pchain = NULL;
  chain = memChain;
  while (chain)
  {
    if (chain->mark != sweepMark)
    {
      if (pchain)
      {
	pchain->next = chain->next;
	DEBUG (3, ("tfree: %p->next -> %p", pchain, pchain->next));
      }
      else
	memChain = chain->next;
      DEBUG (2, ("collected: %p", chain + 1));
      pfree (chain + 1, (void*)-1);
      if (pchain)
	chain = pchain->next;
      else
	chain = memChain;
    }
    else
    {
      pchain = chain;
      chain = chain->next;
    }
  }
}

void *proot(
#ifdef MEMORY_LOCATION
      void **loc
#else
      void
#endif
      )
{
#ifdef MEMORY_LOCATION
  return palloc (1, NULL, NULL, loc);
#else
  return palloc(1, NULL, NULL);
#endif
}

void *palloc(size_t sz, /*@null@*/void *parent, /*@null@*/void (*deallocator)(void *ptr)
#ifdef MEMORY_LOCATION
      , void **loc
#endif
      )
{
  memdata_t *mem = calloc (1, sz + sizeof (memdata_t));
  
  if(!mem)
    return NULL;
  
  mem->mark = sweepMark;
  mem->deallocator = deallocator;
  mem->next = rootChain;
  DEBUG (3, ("palloc: %p->next -> %p", mem, mem->next));
  rootChain = mem;
  
  DEBUG (2, ("palloc: %p", mem + 1));
  
  if (!parent)
    return mem + 1;
  
  return pattach (mem + 1, parent
#ifdef MEMORY_LOCATION
        , loc
#endif
        );
}

void *prealloc(void *ptr, size_t sz)
{
  memdata_t *mem = (memdata_t*)ptr - 1, *res;
  memory_t *plink = mem->firstParent.link, *clink = mem->firstChild.link,
    *nlink;
  memdata_t *chain;
  
  res = realloc (mem, sz + sizeof (memdata_t));
  
  if (res == mem)
    return res + 1;
  
  DEBUG (2, ("prealloc: %p -> %p", mem + 1, res + 1));
  
  if (mem == rootChain)
    rootChain = res;
  else if (mem == memChain)
    memChain = res;
  else
  {
    for (chain = rootChain; chain && chain->next != mem; chain = chain->next)
      ;
    if (!chain)
      for (chain = memChain; DEXPR (chain &&) chain->next != mem; chain = chain->next)
	;
#ifdef DEBUG
    if (!chain)
      DEBUG (1, ("prealloc: %p not found in chain.", mem + 1));
#endif
    chain->next = res;
    DEBUG (3, ("prealloc: %p->next -> %p", chain, chain->next));
  }
  while(clink)
  {
    nlink = clink[pNextChild].link;
    if(res)
    {
      clink[pParent].data = res;
#ifdef MEMORY_LOCATION
      if (clink[pLocation].ptr)
      {
	void **newptr = (char*)res + ((char*)clink[pLocation].ptr - (char*)mem);
	
	if (*newptr == clink[pChild].data + 1)
	  clink[pLocation].ptr = newptr;
      }
#endif
    }
    else
      pfree(clink[pChild].data + 1, ptr);
    clink = nlink;
  }
  while(plink)
  {
    nlink = plink[pNextParent].link;
    if(res)
    {
      plink[pChild].data = res;
#ifdef MEMORY_LOCATION
      if (*plink[pLocation].ptr == mem + 1)
	*plink[pLocation].ptr = res + 1;
#endif
    }
#ifdef MEMORY_LOCATION
    else if (plink[pParent])
#else
    else
#endif
    {
      if (plink[pParent].data->firstChild.link == plink)
	plink[pParent].data->firstChild.link = plink[pNextChild].link;
      else
      {
	for (clink = plink[pParent].data->firstChild.link;
	     clink[pNextChild].link != plink; clink = clink[pNextChild].link)
	  ;
	clink[pNextChild].link = plink[pNextChild].link;
      }
      DEBUG (2, ("prealloc: unlink %p -> %p", plink[pParent], plink[pChild]));
      free (plink);
    }
    plink = nlink;
  }
  
  if(res)
    return res + 1;
  return NULL;
}

void *pattach(/*@null@*/void *child, void *parent
#ifdef MEMORY_LOCATION
      , /*@null@*/void **loc
#endif
      )
{
  memdata_t *pmem = (memdata_t*)parent - 1;
  memdata_t *cmem = (memdata_t*)child - 1;
  memory_t *mem;
  memdata_t *chain;
  
  if(!child)
    return NULL;
#ifdef MEMORY_LOCATION
  if (!parent && !loc)
    return child;
#else
  if (!parent)
    return child;
#endif
#ifdef DEBUG
#ifdef MEMORY_LOCATION
  if (parent)
#endif
  {
    if (pmem->isHashed)
    {
      DEBUG (1, ("pattach: parent %p is hashed (child %p)", parent, child));
      return NULL;
    }
    
    for(mem = cmem->firstParent.link; mem; mem = mem[pNextParent].link)
    {
      if(mem[pParent].data == pmem)
	DEBUG (2, ("pattach: %p -> %p already linked.", parent, child));
#ifdef MEMORY_LOCATION
      if (!mem[pParent].data && *mem[pLocation].ptr == child)
	DEBUG (2, ("pattach: %p is located root.", child));
#endif
    }
  }
#endif
  
  mem = malloc((size_t)(sizeof(memory_t) * pNumLinkLinks));
  if(mem)
  {
    DEBUG (2, ("pattach: linking %p -> %p", parent, child));
    mem[pNextParent].link = cmem->firstParent.link;
    mem[pNextChild].link = pmem->firstChild.link;
#ifdef MEMORY_LOCATION
    if (parent)
      mem[pParent].data = pmem;
    else
      mem[pParent].data = NULL;
#else
    mem[pParent].data = pmem;
#endif
    mem[pChild].data = cmem;
#ifdef MEMORY_LOCATION
    mem[pLocation].ptr = loc;
    if (loc)
      *loc = child;
#endif
    cmem->firstParent.link = mem;
    pmem->firstChild.link = mem;
  }
  else
    DEBUG (1, ("pattach: malloc failed for %p -> %p: %m", parent, child));
  
#ifdef MEMORY_LOCATION
  if (parent)
#else
  if (!mem || !mem[pNextParent].link)
#endif
  {
    if (cmem == rootChain)
    {
      DEBUG (2, ("pattach: Unrooting %p", child));
      rootChain = cmem->next;
      cmem->next = memChain;
      DEBUG (3, ("pattach: %p->next -> %p", cmem, cmem->next));
      memChain = cmem;
    }
    else
    {
      for (chain = rootChain; chain && chain->next != cmem; chain = chain->next)
	;
      if (chain)
      {
	DEBUG (2, ("pattach: Unrooting %p", child));
	chain->next = cmem->next;
	DEBUG (3, ("prealloc: %p->next -> %p", chain, chain->next));
	cmem->next = memChain;
	DEBUG (3, ("prealloc: %p->next -> %p", cmem, cmem->next));
	memChain = cmem;
      }
    }
  }
  
  return child;
}

void *padopt(void *child, void *oldparent, void *newparent
#ifdef MEMORY_LOCATION
      , void **newloc
#endif
      )
{
  memdata_t *opmem = (memdata_t*)oldparent - 1;
  memdata_t *npmem = (memdata_t*)newparent - 1;
  memdata_t *cmem = (memdata_t*)child - 1;
  memory_t *mem, *ccmem = NULL, *pcmem, *ppmem;
  int i;
  
  if (!child)
    return NULL;
  if (!newparent || npmem->isHashed)
    return child;
  
  pcmem = NULL;
  i = 0;
  for(mem = cmem->firstParent.link; mem && i < 3; mem = mem[pNextParent].link)
  {
    if(!(i & 1))
      ccmem = mem;
    if(mem[pParent].data == npmem)
      i |= 2;
    else if(mem[pParent].data == opmem)
      i |= 1;
    if(!(i & 1))
      pcmem = mem;
  }
  
  switch(i)
  {
  case 0:
    DEBUG (1, ("padopt: No old link %p -> %p.", oldparent, child));
    return pattach (child, newparent
#ifdef MEMORY_LOCATION
	  , newloc
#endif
          );
  case 3:
    DEBUG (1, ("padopt: Both %p and %p linked to %p.", oldparent, newparent, child));
    /* fallthrough */
  case 1:
    /* This is the one we _should_ get. */
    DEBUG (2, ("padopt: %p adopting %p from %p.", newparent, child, oldparent));
    if(ccmem == opmem->firstChild.link)
      opmem->firstChild.link = ccmem[pNextChild].link;
    else
    {
      for(ppmem = opmem->firstChild.link; DEXPR (ppmem[pNextChild].link &&)
		ppmem[pNextChild].link != ccmem; ppmem = ppmem[pNextChild].link)
	;
#ifdef DEBUG
      if(!ppmem[pNextChild].link)
      {
	DEBUG (1, ("padopt: Couldn't find link to child in old parent."));
	return child;
      }
#endif
      ppmem[pNextChild].link = ccmem[pNextChild].link;
    }
    ccmem[pNextChild].link = npmem->firstChild.link;
    ccmem[pParent].data = npmem;
#ifdef MEMORY_LOCATION
    ccmem[pLocation].ptr = newloc;
    if (newloc)
      *newloc = child;
#endif
    npmem->firstChild.link = ccmem;
    return child;
  default:
    DEBUG (1, ("padopt: %p is already parent of %p and %p isn't.", newparent, child, oldparent));
    return pattach (child, newparent
#ifdef MEMORY_LOCATION
	  , newloc
#endif
	  );
  }
}

void *prootify (void *ptr)
{
  memdata_t *mem = (memdata_t*)ptr - 1, *pmem;
  
  if (!ptr)
    return NULL;
  
  while (mem->firstParent.link)
  {
    DEBUG (2, ("prootify: %p has parent %p.", ptr, mem->firstParent.link[pParent].data + 1));
    pfree (ptr, mem->firstParent.link[pParent].data + 1);
  }
  
  if (mem == memChain)
    memChain = mem->next;
  else
  {
    pmem = memChain;
    while (pmem && pmem->next != mem)
      pmem = pmem->next;
    if (!pmem)
    {
#ifdef DEBUG
      if (mem == rootChain)
	pmem = mem;
      else
      {
	pmem = rootChain;
	while (pmem && pmem->next != mem)
	  pmem = pmem->next;
      }
      if (pmem)
      {
	DEBUG (1, ("prootify: %p already root.", ptr));
      }
      else
	DEBUG (1, ("prootify: %p not found in chain.", ptr));
#endif
      return ptr;
    }
    pmem->next = mem->next;
    DEBUG (3, ("prootify: %p->next -> %p.", pmem, pmem->next));
  }
  DEBUG (2, ("prootify: rooting %p.", ptr));
  mem->next = rootChain;
  DEBUG (3, ("prootify: %p->next -> %p.", mem, mem->next));
  rootChain = mem;
  return ptr;
}

void pfree(void *child, void *parent)
{
  memdata_t *cmem = (memdata_t*)child - 1;
  memdata_t *pmem = (memdata_t*)parent - 1;
  memory_t *mem, *pcmem, *ppmem;
  memhash_t *hash, *phash;
  memdata_t *chain;
  
  if(!child)
    return;
  
  pcmem = NULL;
  if (parent && parent != (void*)-1)
  {
#ifdef DEBUG
    if (pmem->isHashed)
    {
      DEBUG (1, ("pfree: Parent %p is hashed (child %p).", parent, child));
      return;
    }
#endif
    
    for (mem = cmem->firstParent.link; mem; mem = mem[pNextParent].link)
    {
      if(mem[pParent].data == pmem)
	break;
      pcmem = mem;
    }
    if (!mem)
    {
#ifdef DEBUG
      DEBUG (1, ("pfree: Parent %p not found in child %p.", parent, child));
      ppmem = NULL;
      for (mem = pmem->firstChild.link; mem; mem = mem[pNextChild].link)
      {
	if (mem[pChild].data == cmem)
	  break;
	ppmem = mem;
      }
      if (mem)
      {
	DEBUG (1, ("pfree: but child found in parent."));
	ppmem[pNextChild].link = mem[pNextChild].link;
	free (mem);
      }
#endif
      return;
    }
    
    if (mem == pmem->firstChild.link)
      pmem->firstChild.link = mem[pNextChild].link;
    else
    {
      for (ppmem = pmem->firstChild.link; DEXPR (ppmem[pNextChild].link &&)
	     ppmem[pNextChild].link != mem; ppmem = ppmem[pNextChild].link)
	;
#ifdef DEBUG
      if (!ppmem[pNextChild].link)
	DEBUG (1, ("pfree: Child %p not found in parent %p.", child, parent));
#endif
      ppmem[pNextChild].link = mem[pNextChild].link;
    }
    if (pcmem)
      pcmem[pNextParent].link = mem[pNextParent].link;
    else
      cmem->firstParent.link = mem[pNextParent].link;
    
    DEBUG (2, ("pfree: Unlinking %p -> %p.", parent, child));
    free(mem);
  }
  else
  {
    DEBUG (2, ("pfree: Really freeing %p.", child));
    if (cmem->isHashed)
    {
      hash = cmem->firstChild.hash;
      if (*hash->bucket && (*hash->bucket)->firstChild.hash == hash)
	*hash->bucket = hash->next;
      else
      {
	for (phash = (*hash->bucket)->firstChild.hash; phash->next &&
	       phash->next->firstChild.hash != hash;
	     phash = phash->next->firstChild.hash)
	  ;
#ifdef DEBUG
	if (!phash)
	  DEBUG (1, ("%p not found in bucket.", child));
#endif
	phash->next = hash->next;
	DEBUG (3, ("pfree: %p->next -> %p", phash, phash->next));
	free (cmem->firstChild.hash);
      }
    }
    else
    {
      while(cmem->firstChild.link)
	pfree (cmem->firstChild.link[pChild].data + 1, child);
    }
    while ((ppmem = cmem->firstParent.link))
    {
#ifdef MEMORY_LOCATION
      if (!ppmem[pParent].data)
      {
	cmem->firstParent.link = ppmem[pNextParent].link;
	free (ppmem);
      }
      else
#endif
	pfree (child, ppmem[pParent].data + 1);
    }
    if (parent != (void*)-1)
    {
      if (cmem == rootChain)
	rootChain = cmem->next;
      else if (cmem == memChain)
	memChain = cmem->next;
      else
      {
	for (chain = rootChain; chain && chain->next != cmem; chain = chain->next)
	  ;
	if (!chain)
	  for (chain = memChain; DEXPR (chain &&) chain->next != cmem; chain = chain->next)
	    ;
#ifdef DEBUG
	if (!chain)
	  DEBUG (1, ("%p not found in chain.", child));
#endif
	chain->next = cmem->next;
	DEBUG (3, ("pfree: %p->next -> %p", chain, chain->next));
      }
    }
    if (cmem->deallocator)
      cmem->deallocator (child);
    free(cmem);
  }
}

void *pchild(const void *parent, const void *prevChild)
{
  const memdata_t *const pmem = (memdata_t*)parent - 1;
  
  if (!parent)
    return NULL;
  
  if(prevChild)
  {
    const memdata_t *const cmem = (memdata_t*)prevChild - 1;
    const memory_t *mem;
    
    /*
     * Most nodes tend to have more children
     * than parents, so usually it should be faster to search for the
     * parent in the child's list.
     */
    for(mem = cmem->firstParent.link; mem; mem = mem[pNextParent].link)
      if(mem[pParent].data == pmem)
      {
	if(mem[pNextChild].link)
	  return mem[pNextChild].link[pChild].data + 1;
	return NULL;
      }
  }
  else if(pmem->firstChild.link)
    return pmem->firstChild.link[pChild].data + 1;
  return NULL;
}

int plinked (const void *child, const void *parent)
{
  const memdata_t *pmem = (memdata_t*)parent - 1;
  const memdata_t *cmem = (memdata_t*)child - 1;
  const memory_t *mem;
  
  if (!child || !parent)
    return 0;
  
  for (mem = cmem->firstParent.link; mem; mem = mem[pNextParent].link)
  {
    if(mem[pParent].data == pmem)
      return 1;
  }
  return 0;
}

int pnparents (const void *child)
{
  const memdata_t *cmem = (memdata_t*)child - 1;
  const memory_t *mem;
  int res;
  
  if (!child)
    return 0;
  
  res = 0;
  for (mem = cmem->firstParent.link; mem; mem = mem[pNextParent].link)
    res++;
  
  return res;
}

char *pstring(const char *str, void *parent)
{
  int hval = 0;
  const unsigned char *sp = (unsigned char*)str;
  memdata_t *mem;
  char *res;
  
  if (!str)
    return NULL;
  
  while (*sp)
  {
    hval += randVals[(*sp + ((char*)sp - str)) & 0xFF];
    sp++;
  }
  hval %= stringHashSize;
  
  mem = stringHash[hval];
  while (mem)
  {
    if (!strcmp ((char*)(mem + 1), str))
      return (char*)pattach (mem + 1, parent);
    mem = mem->firstChild.hash->next;
  }
  
  res = palloc ((char*)sp - str + 1, parent, NULL);
  
  if (res)
  {
    strcpy (res, str);
    mem = (memdata_t*)res - 1;
    mem->firstChild.hash = malloc (sizeof (memhash_t));
    if (mem->firstChild.hash)
    {
      mem->isHashed = 1;
      mem->firstChild.hash->bucket = &stringHash[hval];
      mem->firstChild.hash->next = stringHash[hval];
      DEBUG (3, ("pstring: %p->next -> %p", mem->firstChild.hash, mem->firstChild.hash->next));
      stringHash[hval] = mem;
    }
  }
  
  return res;
}


syntax highlighted by Code2HTML, v. 0.9.1