/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.  
 * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved.
 *
 * This file is part of the device-mapper userspace tools.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "libdm/misc/dmlib.h"
#include <assert.h>

struct block {
	struct block *next;
	size_t size;
	void *data;
};

typedef struct {
	unsigned block_serialno;	/* Non-decreasing serialno of block */
	unsigned blocks_allocated;	/* Current number of blocks allocated */
	unsigned blocks_max;	/* Max no of concurrently-allocated blocks */
	unsigned int bytes, maxbytes;
} pool_stats;

struct dm_pool {
	struct dm_list list;
	const char *name;
	void *orig_pool;	/* to pair it with first allocation call */
	unsigned locked;
	long crc;

	int begun;
	struct block *object;

	struct block *blocks;
	struct block *tail;

	pool_stats stats;
};

/* by default things come out aligned for doubles */
#define DEFAULT_ALIGNMENT __alignof__ (double)

struct dm_pool *dm_pool_create(const char *name, size_t chunk_hint)
{
	struct dm_pool *mem = dm_zalloc(sizeof(*mem));

	if (!mem) {
		log_error("Couldn't create memory pool %s (size %"
			  PRIsize_t ")", name, sizeof(*mem));
		return NULL;
	}

	mem->name = name;
	mem->orig_pool = mem;

#ifdef DEBUG_POOL
	log_debug_mem("Created mempool %s at %p", name, mem);
#endif

	dm_list_add(&_dm_pools, &mem->list);
	return mem;
}

static void _free_blocks(struct dm_pool *p, struct block *b)
{
	struct block *n;

	if (p->locked)
		log_error(INTERNAL_ERROR "_free_blocks from locked pool %s",
			  p->name);

	while (b) {
		p->stats.bytes -= b->size;
		p->stats.blocks_allocated--;

		n = b->next;
		dm_free(b->data);
		dm_free(b);
		b = n;
	}
}

static void _pool_stats(struct dm_pool *p, const char *action)
{
#ifdef DEBUG_POOL
	log_debug_mem("%s mempool %s at %p: %u/%u bytes, %u/%u blocks, "
		      "%u allocations)", action, p->name, p, p->stats.bytes,
		      p->stats.maxbytes, p->stats.blocks_allocated,
		      p->stats.blocks_max, p->stats.block_serialno);
#else
	;
#endif
}

void dm_pool_destroy(struct dm_pool *p)
{
	_pool_stats(p, "Destroying");
	_free_blocks(p, p->blocks);
	dm_list_del(&p->list);
	dm_free(p);
}

void *dm_pool_alloc(struct dm_pool *p, size_t s)
{
	return dm_pool_alloc_aligned(p, s, DEFAULT_ALIGNMENT);
}

static void _append_block(struct dm_pool *p, struct block *b)
{
	if (p->locked)
		log_error(INTERNAL_ERROR "_append_blocks to locked pool %s",
			  p->name);

	if (p->tail) {
		p->tail->next = b;
		p->tail = b;
	} else
		p->blocks = p->tail = b;

	p->stats.block_serialno++;
	p->stats.blocks_allocated++;
	if (p->stats.blocks_allocated > p->stats.blocks_max)
		p->stats.blocks_max = p->stats.blocks_allocated;

	p->stats.bytes += b->size;
	if (p->stats.bytes > p->stats.maxbytes)
		p->stats.maxbytes = p->stats.bytes;
}

static struct block *_new_block(size_t s, unsigned alignment)
{
	/* FIXME: I'm currently ignoring the alignment arg. */
	size_t len = sizeof(struct block) + s;
	struct block *b = dm_malloc(len);

	/*
	 * Too lazy to implement alignment for debug version, and
	 * I don't think LVM will use anything but default
	 * align.
	 */
	assert(alignment <= DEFAULT_ALIGNMENT);

	if (!b) {
		log_error("Out of memory");
		return NULL;
	}

	if (!(b->data = dm_malloc(s))) {
		log_error("Out of memory");
		dm_free(b);
		return NULL;
	}

	b->next = NULL;
	b->size = s;

	return b;
}

void *dm_pool_alloc_aligned(struct dm_pool *p, size_t s, unsigned alignment)
{
	struct block *b = _new_block(s, alignment);

	if (!b)
		return_NULL;

	_append_block(p, b);

	return b->data;
}

void dm_pool_empty(struct dm_pool *p)
{
	_pool_stats(p, "Emptying");
	_free_blocks(p, p->blocks);
	p->blocks = p->tail = NULL;
}

void dm_pool_free(struct dm_pool *p, void *ptr)
{
	struct block *b, *prev = NULL;

	_pool_stats(p, "Freeing (before)");

	for (b = p->blocks; b; b = b->next) {
		if (b->data == ptr)
			break;
		prev = b;
	}

	/*
	 * If this fires then you tried to free a
	 * pointer that either wasn't from this
	 * pool, or isn't the start of a block.
	 */
	assert(b);

	_free_blocks(p, b);

	if (prev) {
		p->tail = prev;
		prev->next = NULL;
	} else
		p->blocks = p->tail = NULL;

	_pool_stats(p, "Freeing (after)");
}

int dm_pool_begin_object(struct dm_pool *p, size_t init_size)
{
	assert(!p->begun);
	p->begun = 1;
	return 1;
}

int dm_pool_grow_object(struct dm_pool *p, const void *extra, size_t delta)
{
	struct block *new;
	size_t new_size;

	if (p->locked)
		log_error(INTERNAL_ERROR "Grow objects in locked pool %s",
			  p->name);

	if (!delta)
		delta = strlen(extra);

	assert(p->begun);

	if (p->object)
		new_size = delta + p->object->size;
	else
		new_size = delta;

	if (!(new = _new_block(new_size, DEFAULT_ALIGNMENT))) {
		log_error("Couldn't extend object.");
		return 0;
	}

	if (p->object) {
		memcpy(new->data, p->object->data, p->object->size);
		dm_free(p->object->data);
		dm_free(p->object);
	}
	p->object = new;

	memcpy((char*)new->data + new_size - delta, extra, delta);

	return 1;
}

void *dm_pool_end_object(struct dm_pool *p)
{
	assert(p->begun);
	_append_block(p, p->object);

	p->begun = 0;
	p->object = NULL;
	return p->tail->data;
}

void dm_pool_abandon_object(struct dm_pool *p)
{
	assert(p->begun);
	dm_free(p->object);
	p->begun = 0;
	p->object = NULL;
}

static long _pool_crc(const struct dm_pool *p)
{
#ifndef DEBUG_ENFORCE_POOL_LOCKING
#warning pool crc not implemented with pool debug
#endif
	return 0;
}

static int _pool_protect(struct dm_pool *p, int prot)
{
#ifdef DEBUG_ENFORCE_POOL_LOCKING
#warning pool mprotect not implemented with pool debug
#endif
	return 1;
}