/* 
   Samba Unix SMB/CIFS implementation.

   Samba temporary memory allocation functions - new interface

   Copyright (C) Andrew Tridgell 2004
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   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.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
  inspired by http://swapped.cc/halloc/
*/

#include "includes.h"

#define MAX_TALLOC_SIZE 0x10000000
#define TALLOC_MAGIC 0xe814ec4f
#define TALLOC_MAGIC_FREE 0x7faebef3

struct talloc_chunk {
	struct talloc_chunk *next, *prev;
	struct talloc_chunk *parent, *child;
	size_t size;
	uint_t magic;
	uint_t ref_count;
	int (*destructor)(void *);
	char *name;
};

/* panic if we get a bad magic value */
static struct talloc_chunk *talloc_chunk_from_ptr(void *ptr)
{
	struct talloc_chunk *tc = ((struct talloc_chunk *)ptr)-1;
	if (tc->magic != TALLOC_MAGIC) {
		if (tc->magic == TALLOC_MAGIC_FREE) {
			smb_panic("Bad talloc magic value - double free\n");
		} else {
			smb_panic("Bad talloc magic value\n");
		}
	}
	return tc;
}

/* 
   Allocate a bit of memory as a child of an existing pointer
*/
void *talloc(void *context, size_t size)
{
	struct talloc_chunk *tc;

	if (size >= MAX_TALLOC_SIZE) {
		return NULL;
	}

	tc = malloc(sizeof(*tc)+size);
	if (tc == NULL) {
		return NULL;
	}

	tc->size = size;
	tc->magic = TALLOC_MAGIC;
	tc->ref_count = 1;
	tc->destructor = NULL;
	tc->child = NULL;
	tc->name = NULL;

	if (context) {
		struct talloc_chunk *parent = talloc_chunk_from_ptr(context);

		tc->parent = parent;

		if (parent->child) {
			parent->child->parent = NULL;
		}

		DLIST_ADD(parent->child, tc);
	} else {
		tc->next = tc->prev = tc->parent = NULL;
	}

	return (void *)(tc+1);
}


/*
  setup a destructor to be called on free of a pointer
  the destructor should return 0 on success, or -1 on failure.
  if the destructor fails then the free is failed, and the memory can
  be continued to be used
*/
void talloc_set_destructor(void *ptr, int (*destructor)(void *))
{
	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
	tc->destructor = destructor;
}

/*
  increase the reference count on a piece of memory. To decrease the 
  reference count call talloc_free(), which will free the memory if 
  the reference count reaches zero
*/
void talloc_increase_ref_count(void *ptr)
{
	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
	tc->ref_count++;
}


/*
  add a name to an existing pointer - va_list version
*/
static void talloc_set_name_v(void *ptr, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);

static void talloc_set_name_v(void *ptr, const char *fmt, va_list ap)
{
	struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr);
	vasprintf(&tc->name, fmt, ap);
}

/*
  add a name to an existing pointer
*/
void talloc_set_name(void *ptr, const char *fmt, ...) _PRINTF_ATTRIBUTE(2,3)
{
	va_list ap;
	va_start(ap, fmt);
	talloc_set_name_v(ptr, fmt, ap);
	va_end(ap);
}

/*
  create a named talloc pointer. Any talloc pointer can be named, and
  talloc_named() operates just like talloc() except that it allows you
  to name the pointer.
*/
void *talloc_named(void *context, size_t size, 
		   const char *fmt, ...) _PRINTF_ATTRIBUTE(3,4)
{
	va_list ap;
	void *ptr;

	ptr = talloc(context, size);
	if (ptr == NULL) {
		return NULL;
	}

	va_start(ap, fmt);
	talloc_set_name_v(ptr, fmt, ap);
	va_end(ap);

	return ptr;
}

/*
  this is for compatibility with older versions of talloc
*/
void *talloc_init(const char *fmt, ...) _PRINTF_ATTRIBUTE(1,2)
{
	va_list ap;
	void *ptr;

	ptr = talloc(NULL, 0);
	if (ptr == NULL) {
		return NULL;
	}

	va_start(ap, fmt);
	talloc_set_name_v(ptr, fmt, ap);
	va_end(ap);

	return ptr;
}



/* 
   free a talloc pointer. This also frees all child pointers of this 
   pointer recursively

   return 0 if the memory is actually freed, otherwise -1. The memory
   will not be freed if the ref_count is > 1 or the destructor (if
   any) returns non-zero
*/
int talloc_free(void *ptr)
{
	struct talloc_chunk *tc;

	if (ptr == NULL) {
		return -1;
	}

	tc = talloc_chunk_from_ptr(ptr);

	tc->ref_count--;
	if (tc->ref_count != 0) {
		return -1;
	}

	if (tc->destructor && tc->destructor(ptr) == -1) {
		tc->ref_count++;
		return -1;
	}

	while (tc->child) {
		if (talloc_free(tc->child + 1) != 0) {
			tc->child->parent = NULL;
			break;
		}
	}

	if (tc->parent) {
		DLIST_REMOVE(tc->parent->child, tc);
		if (tc->parent->child) {
			tc->parent->child->parent = tc->parent;
		}
	} else {
		if (tc->prev) tc->prev->next = tc->next;
		if (tc->next) tc->next->prev = tc->prev;
	}

	tc->magic = TALLOC_MAGIC_FREE;
	if (tc->name) free(tc->name);

	free(tc);
	return 0;
}



/*
  A talloc version of realloc 
*/
void *talloc_realloc(void *ptr, size_t size)
{
	struct talloc_chunk *tc;
	void *new_ptr;

	/* size zero is equivalent to free() */
	if (size == 0) {
		talloc_free(ptr);
		return NULL;
	}

	/* realloc(NULL) is equavalent to malloc() */
	if (ptr == NULL) {
		return talloc(NULL, size);
	}

	tc = talloc_chunk_from_ptr(ptr);

	/* by resetting magic we catch users of the old memory */
	tc->magic = TALLOC_MAGIC_FREE;

	new_ptr = realloc(tc, size + sizeof(*tc));
	if (!new_ptr) {
		tc->magic = TALLOC_MAGIC;
		return NULL;
	}

	tc = new_ptr;
	tc->magic = TALLOC_MAGIC;
	if (tc->parent) {
		tc->parent->child = new_ptr;
	}

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

	tc->size = size;

	return (void *)(tc+1);
}

/* 
   move a lump of memory from one talloc context to another return the
   ptr on success, or NUL if it could not be transferred
*/
void *talloc_steal(void *new_ctx, void *ptr)
{
	struct talloc_chunk *tc, *new_tc;

	if (!ptr) {
		return NULL;
	}

	tc = talloc_chunk_from_ptr(ptr);
	new_tc = talloc_chunk_from_ptr(new_ctx);

	if (tc == new_tc) {
		return ptr;
	}

	if (tc->parent) {
		DLIST_REMOVE(tc->parent->child, tc);
		if (tc->parent->child) {
			tc->parent->child->parent = tc->parent;
		}
	} else {
		if (tc->prev) tc->prev->next = tc->next;
		if (tc->next) tc->next->prev = tc->prev;
	}

	tc->parent = new_tc;
	if (new_tc->child) new_tc->child->parent = NULL;
	DLIST_ADD(new_tc->child, tc);

	return ptr;
}

/*
  return the total size of a talloc pool (subtree)
*/
off_t talloc_total_size(void *ptr)
{
	off_t total = 0;
	struct talloc_chunk *c, *tc = talloc_chunk_from_ptr(ptr);

	total = tc->size;
	for (c=tc->child;c;c=c->next) {
		total += talloc_total_size(c+1);
	}
	return total;
}


/* 
   talloc and zero memory. 
*/
void *talloc_zero(void *t, size_t size)
{
	void *p = talloc(t, size);

	if (p) {
		memset(p, '\0', size);
	}

	return p;
}


/*
  memdup with a talloc. 
*/
void *talloc_memdup(void *t, const void *p, size_t size)
{
	void *newp = talloc(t,size);

	if (newp) {
		memcpy(newp, p, size);
	}

	return newp;
}

/*
  strdup with a talloc 
*/
char *talloc_strdup(void *t, const char *p)
{
	if (!p) {
		return NULL;
	}
	return talloc_memdup(t, p, strlen(p) + 1);
}

/*
  strndup with a talloc 
*/
char *talloc_strndup(void *t, const char *p, size_t n)
{
	size_t len = strnlen(p, n);
	char *ret;

	ret = talloc(t, len + 1);
	if (!ret) { return NULL; }
	memcpy(ret, p, len);
	ret[len] = 0;
	return ret;
}

char *talloc_vasprintf(void *t, const char *fmt, va_list ap) _PRINTF_ATTRIBUTE(2,0)
{	
	int len;
	char *ret;
	va_list ap2;
	
	VA_COPY(ap2, ap);

	len = vsnprintf(NULL, 0, fmt, ap2);

	ret = talloc(t, len+1);
	if (ret) {
		VA_COPY(ap2, ap);
		vsnprintf(ret, len+1, fmt, ap2);
	}

	return ret;
}


/*
  Perform string formatting, and return a pointer to newly allocated
  memory holding the result, inside a memory pool.
 */
char *talloc_asprintf(void *t, const char *fmt, ...) _PRINTF_ATTRIBUTE(2,3)
{
	va_list ap;
	char *ret;

	va_start(ap, fmt);
	ret = talloc_vasprintf(t, fmt, ap);
	va_end(ap);
	return ret;
}


/**
 * Realloc @p s to append the formatted result of @p fmt and @p ap,
 * and return @p s, which may have moved.  Good for gradually
 * accumulating output into a string buffer.
 **/

static char *talloc_vasprintf_append(char *s,
				     const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0);

static char *talloc_vasprintf_append(char *s,
			      const char *fmt, va_list ap)
{	
	int len, s_len;
	va_list ap2;

	VA_COPY(ap2, ap);

	if (s) {
		s_len = strlen(s);
	} else {
		s_len = 0;
	}
	len = vsnprintf(NULL, 0, fmt, ap2);

	s = talloc_realloc(s, s_len + len+1);
	if (!s) return NULL;

	VA_COPY(ap2, ap);

	vsnprintf(s+s_len, len+1, fmt, ap2);

	return s;
}

/*
  Realloc @p s to append the formatted result of @p fmt and return @p
  s, which may have moved.  Good for gradually accumulating output
  into a string buffer.
 */
char *talloc_asprintf_append(char *s,
			     const char *fmt, ...) _PRINTF_ATTRIBUTE(2,3)
{
	va_list ap;

	va_start(ap, fmt);
	s = talloc_vasprintf_append(s, fmt, ap);
	va_end(ap);
	return s;
}

/*
  alloc an array, checking for integer overflow in the array size
*/
void *talloc_array(void *ctx, size_t el_size, uint_t count)
{
	if (count == 0 ||
	    count >= MAX_TALLOC_SIZE/el_size) {
		return NULL;
	}
	return talloc(ctx, el_size * count);
}


/*
  realloc an array, checking for integer overflow in the array size
*/
void *talloc_realloc_array(void *ptr, size_t el_size, uint_t count)
{
	if (count == 0 ||
	    count >= MAX_TALLOC_SIZE/el_size) {
		return NULL;
	}
	return talloc_realloc(ptr, el_size * count);
}

/*
  a alloc function for ldb that uses talloc
*/
void *talloc_ldb_alloc(void *context, void *ptr, size_t size)
{
	if (ptr == NULL) {
		return talloc(context, size);
	}
	if (size == 0) {
		talloc_free(ptr);
		return NULL;
	}
	return talloc_realloc(ptr, size);
}