#include "rsync.h"

#define POOL_DEF_EXTENT	(32 * 1024)

#define POOL_QALIGN_P2		(1<<16)		/* power-of-2 qalign	*/

struct alloc_pool
{
	size_t			size;		/* extent size		*/
	size_t			quantum;	/* allocation quantum	*/
	struct pool_extent	*extents;	/* top extent is "live" */
	void			(*bomb)(const char*, const char*, int);	/* called if malloc fails */
	int			flags;

	/* statistical data */
	unsigned long		e_created;	/* extents created	*/
	unsigned long		e_freed;	/* extents destroyed	*/
	int64			n_allocated;	/* calls to alloc	*/
	int64			n_freed;	/* calls to free	*/
	int64			b_allocated;	/* cum. bytes allocated	*/
	int64			b_freed;	/* cum. bytes freed	*/
};

struct pool_extent
{
	struct pool_extent	*next;
	void			*start;		/* starting address	*/
	size_t			free;		/* free bytecount	*/
	size_t			bound;		/* trapped free bytes	*/
};

struct align_test {
	uchar foo;
	union {
	    int64 i;
	    void *p;
	} bar;
};

#define MINALIGN	offsetof(struct align_test, bar)

/* Temporarily cast a void* var into a char* var when adding an offset (to
 * keep some compilers from complaining about the pointer arithmetic). */
#define PTR_ADD(b,o)	( (void*) ((char*)(b) + (o)) )
#define PTR_SUB(b,o)	( (void*) ((char*)(b) - (o)) )

/* Under AddressSanitizer, fence each pool_alloc() chunk with a poisoned
 * redzone just below it (allocations grow downward from the top of an extent).
 * A bump allocator hands out chunks from one big malloc, so ASan cannot see a
 * write that underflows one chunk into its neighbour -- e.g. a miscomputed
 * F_SUM() reaching before a file_struct's extras.  The redzone turns that into
 * a hard ASan report.  We unpoison a whole extent whenever its space is reused
 * (reset/reclaim), so legitimate later allocations never trip over old
 * redzones; ASan unpoisons freed extents itself via free(). */
#if defined(__SANITIZE_ADDRESS__)
# define POOL_ASAN 1
#elif defined(__has_feature)
# if __has_feature(address_sanitizer)
#  define POOL_ASAN 1
# endif
#endif

#ifdef POOL_ASAN
# include <sanitizer/asan_interface.h>
# define POOL_REDZONE 16  /* >= the largest pool-relative underflow we guard */
# define POOL_POISON(p,n)   ASAN_POISON_MEMORY_REGION((p), (n))
# define POOL_UNPOISON(p,n) ASAN_UNPOISON_MEMORY_REGION((p), (n))
#else
# define POOL_POISON(p,n)   ((void)0)
# define POOL_UNPOISON(p,n) ((void)0)
#endif

alloc_pool_t
pool_create(size_t size, size_t quantum, void (*bomb)(const char*, const char*, int), int flags)
{
	struct alloc_pool *pool;

	if ((MINALIGN & (MINALIGN - 1)) != (0)) {
		if (bomb)
			(*bomb)("Compiler error: MINALIGN is not a power of 2", __FILE__, __LINE__);
		return NULL;
	}

	if (!(pool = new0(struct alloc_pool)))
		return NULL;

	if (!size)
		size = POOL_DEF_EXTENT;
	if (!quantum)
		quantum = MINALIGN;

	if (flags & POOL_INTERN) {
		if (size <= sizeof (struct pool_extent))
			size = quantum;
		else
			size -= sizeof (struct pool_extent);
		flags |= POOL_PREPEND;
	}

	if (quantum <= 1)
		flags = (flags | POOL_NO_QALIGN) & ~POOL_QALIGN_P2;
	else if (!(flags & POOL_NO_QALIGN)) {
		if (size % quantum)
			size += quantum - size % quantum;
		/* If quantum is a power of 2, we'll avoid using modulus. */
		if (!(quantum & (quantum - 1)))
			flags |= POOL_QALIGN_P2;
	}

	pool->size = size;
	pool->quantum = quantum;
	pool->bomb = bomb;
	pool->flags = flags;

	return pool;
}

void
pool_destroy(alloc_pool_t p)
{
	struct alloc_pool *pool = (struct alloc_pool *) p;
	struct pool_extent *cur, *next;

	if (!pool)
		return;

	for (cur = pool->extents; cur; cur = next) {
		next = cur->next;
		if (pool->flags & POOL_PREPEND)
			free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
		else {
			free(cur->start);
			free(cur);
		}
	}

	free(pool);
}

void *
pool_alloc(alloc_pool_t p, size_t len, const char *bomb_msg)
{
	struct alloc_pool *pool = (struct alloc_pool *) p;
	if (!pool)
		return NULL;

	if (!len)
		len = pool->quantum;
	else if (pool->flags & POOL_QALIGN_P2) {
		if (len & (pool->quantum - 1))
			len += pool->quantum - (len & (pool->quantum - 1));
	} else if (!(pool->flags & POOL_NO_QALIGN)) {
		if (len % pool->quantum)
			len += pool->quantum - len % pool->quantum;
	}

	if (len > pool->size)
		goto bomb_out;

	if (!pool->extents || len > pool->extents->free) {
		void *start;
		size_t asize;
		struct pool_extent *ext;

		asize = pool->size;
		if (pool->flags & POOL_PREPEND)
			asize += sizeof (struct pool_extent);

		if (!(start = new_array(char, asize)))
			goto bomb_out;

		if (pool->flags & POOL_CLEAR)
			memset(start, 0, asize);

		if (pool->flags & POOL_PREPEND) {
			ext = start;
			start = PTR_ADD(start, sizeof (struct pool_extent));
		} else if (!(ext = new(struct pool_extent)))
			goto bomb_out;
		ext->start = start;
		ext->free = pool->size;
		ext->bound = 0;
		ext->next = pool->extents;
		pool->extents = ext;

		pool->e_created++;
	}

	pool->n_allocated++;
	pool->b_allocated += len;

	pool->extents->free -= len;

	{
		void *ret = PTR_ADD(pool->extents->start, pool->extents->free);
#ifdef POOL_ASAN
		size_t rz = pool->extents->free < POOL_REDZONE
			  ? pool->extents->free : POOL_REDZONE;
		if (rz) {
			pool->extents->free -= rz;
			POOL_POISON(PTR_ADD(pool->extents->start, pool->extents->free), rz);
		}
#endif
		return ret;
	}

  bomb_out:
	if (pool->bomb)
		(*pool->bomb)(bomb_msg, __FILE__, __LINE__);
	return NULL;
}

/* This function allows you to declare memory in the pool that you are done
 * using.  If you free all the memory in a pool's extent, that extent will
 * be freed. */
void
pool_free(alloc_pool_t p, size_t len, void *addr)
{
	struct alloc_pool *pool = (struct alloc_pool *)p;
	struct pool_extent *cur, *prev;

	if (!pool)
		return;

	if (!addr) {
		/* A NULL addr starts a fresh extent for new allocations. */
		if ((cur = pool->extents) != NULL && cur->free != pool->size) {
			cur->bound += cur->free;
			cur->free = 0;
		}
		return;
	}

	if (!len)
		len = pool->quantum;
	else if (pool->flags & POOL_QALIGN_P2) {
		if (len & (pool->quantum - 1))
			len += pool->quantum - (len & (pool->quantum - 1));
	} else if (!(pool->flags & POOL_NO_QALIGN)) {
		if (len % pool->quantum)
			len += pool->quantum - len % pool->quantum;
	}

	pool->n_freed++;
	pool->b_freed += len;

	for (prev = NULL, cur = pool->extents; cur; prev = cur, cur = cur->next) {
		if (addr >= cur->start
		    && addr < PTR_ADD(cur->start, pool->size))
			break;
	}
	if (!cur)
		return;

	/* This extent's space may be reused (and POOL_CLEAR may memset it)
	 * below, so drop any redzones in it first. */
	POOL_UNPOISON(cur->start, pool->size);

	if (!prev) {
		/* The "live" extent is kept ready for more allocations. */
		if (cur->free + cur->bound + len >= pool->size) {
			if (pool->flags & POOL_CLEAR) {
				memset(PTR_ADD(cur->start, cur->free), 0,
				       pool->size - cur->free);
			}
			cur->free = pool->size;
			cur->bound = 0;
		} else if (addr == PTR_ADD(cur->start, cur->free)) {
			if (pool->flags & POOL_CLEAR)
				memset(addr, 0, len);
			cur->free += len;
		} else
			cur->bound += len;
	} else {
		cur->bound += len;

		if (cur->free + cur->bound >= pool->size) {
			prev->next = cur->next;
			if (pool->flags & POOL_PREPEND)
				free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
			else {
				free(cur->start);
				free(cur);
			}
			pool->e_freed++;
		} else if (prev != pool->extents) {
			/* Move the extent to be the first non-live extent. */
			prev->next = cur->next;
			cur->next = pool->extents->next;
			pool->extents->next = cur;
		}
	}
}

/* This allows you to declare that the given address marks the edge of some
 * pool memory that is no longer needed.  Any extents that hold only data
 * older than the boundary address are freed.  NOTE: You MUST NOT USE BOTH
 * pool_free() and pool_free_old() on the same pool!! */
void
pool_free_old(alloc_pool_t p, void *addr)
{
	struct alloc_pool *pool = (struct alloc_pool *)p;
	struct pool_extent *cur, *prev, *next;

	if (!pool || !addr)
		return;

	for (prev = NULL, cur = pool->extents; cur; prev = cur, cur = cur->next) {
		if (addr >= cur->start
		    && addr < PTR_ADD(cur->start, pool->size))
			break;
	}
	if (!cur)
		return;

	POOL_UNPOISON(cur->start, pool->size);

	if (addr == PTR_ADD(cur->start, cur->free)) {
		if (prev) {
			prev->next = NULL;
			next = cur;
		} else {
			/* The most recent live extent can just be reset. */
			if (pool->flags & POOL_CLEAR)
				memset(addr, 0, pool->size - cur->free);
			cur->free = pool->size;
			cur->bound = 0;
			next = cur->next;
			cur->next = NULL;
		}
	} else {
		next = cur->next;
		cur->next = NULL;
	}

	while ((cur = next) != NULL) {
		next = cur->next;
		if (pool->flags & POOL_PREPEND)
			free(PTR_SUB(cur->start, sizeof (struct pool_extent)));
		else {
			free(cur->start);
			free(cur);
		}
		pool->e_freed++;
	}
}

/* If the current extent doesn't have "len" free space in it, mark it as full
 * so that the next alloc will start a new extent.  If len is (size_t)-1, this
 * bump will always occur.  The function returns a boundary address that can
 * be used with pool_free_old(), or a NULL if no memory is allocated. */
void *
pool_boundary(alloc_pool_t p, size_t len)
{
	struct alloc_pool *pool = (struct alloc_pool *)p;
	struct pool_extent *cur;

	if (!pool || !pool->extents)
		return NULL;

	cur = pool->extents;

	if (cur->free < len) {
		cur->bound += cur->free;
		cur->free = 0;
	}

	return PTR_ADD(cur->start, cur->free);
}

#define FDPRINT(label, value) \
	do { \
		int len = snprintf(buf, sizeof buf, label, value); \
		if (write(fd, buf, len) != len) \
			ret = -1; \
	} while (0)

#define FDEXTSTAT(ext) \
	do { \
		int len = snprintf(buf, sizeof buf, "  %12ld  %5ld\n", \
				   (long)ext->free, (long)ext->bound); \
		if (write(fd, buf, len) != len) \
			ret = -1; \
	} while (0)

int
pool_stats(alloc_pool_t p, int fd, int summarize)
{
	struct alloc_pool *pool = (struct alloc_pool *) p;
	struct pool_extent *cur;
	char buf[BUFSIZ];
	int ret = 0;

	if (!pool)
		return ret;

	FDPRINT("  Extent size:       %12ld\n",	(long)	pool->size);
	FDPRINT("  Alloc quantum:     %12ld\n",	(long)	pool->quantum);
	FDPRINT("  Extents created:   %12ld\n",		pool->e_created);
	FDPRINT("  Extents freed:     %12ld\n",		pool->e_freed);
	FDPRINT("  Alloc count:       %12.0f\n", (double) pool->n_allocated);
	FDPRINT("  Free Count:        %12.0f\n", (double) pool->n_freed);
	FDPRINT("  Bytes allocated:   %12.0f\n", (double) pool->b_allocated);
	FDPRINT("  Bytes freed:       %12.0f\n", (double) pool->b_freed);

	if (summarize)
		return ret;

	if (!pool->extents)
		return ret;

	if (write(fd, "\n", 1) != 1)
		ret = -1;

	for (cur = pool->extents; cur; cur = cur->next)
		FDEXTSTAT(cur);

	return ret;
}
