/*-
 * Copyright 2019 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file mem_pool.h
 * \brief Memory pools library.
 *
 * Memory pools library. Library is designed to implement efficient way to
 * store data in memory avoiding calling of many malloc/free. It has overhead
 * because of fact that objects live in pool for rather long time and are not freed
 * immediately after use, but if we know certainly when these objects can be used, we
 * can use pool for them
 */

#ifndef RSPAMD_MEM_POOL_H
#define RSPAMD_MEM_POOL_H

#include "config.h"


#if defined(HAVE_PTHREAD_PROCESS_SHARED) && !defined(DISABLE_PTHREAD_MUTEX)
#include <pthread.h>
#endif

#ifdef __cplusplus
#define MEMPOOL_STR_FUNC __FUNCTION__
#else
#define MEMPOOL_STR_FUNC G_STRFUNC
#endif

#ifdef __cplusplus
extern "C" {
#endif

struct f_str_s;

#ifdef __has_attribute
#if __has_attribute(alloc_size)
#define RSPAMD_ATTR_ALLOC_SIZE(pos) __attribute__((alloc_size(pos)))
#else
#define RSPAMD_ATTR_ALLOC_SIZE(pos)
#endif

#if __has_attribute(assume_aligned)
#define RSPAMD_ATTR_ALLOC_ALIGN(al) __attribute__((assume_aligned(al)))
#else
#define RSPAMD_ATTR_ALLOC_ALIGN(al)
#endif
#if __has_attribute(returns_nonnull)
#define RSPAMD_ATTR_RETURNS_NONNUL __attribute__((returns_nonnull))
#else
#define RSPAMD_ATTR_RETURNS_NONNUL
#endif
#else
#define RSPAMD_ATTR_ALLOC_SIZE(pos)
#define RSPAMD_ATTR_ALLOC_ALIGN(al)
#define RSPAMD_ATTR_RETURNS_NONNUL
#endif

#define MEMPOOL_TAG_LEN 20
#define MEMPOOL_UID_LEN 20
/* All pointers are aligned as this variable */
#define MIN_MEM_ALIGNMENT G_MEM_ALIGN

/**
 * Destructor type definition
 */
typedef void (*rspamd_mempool_destruct_t)(void *ptr);

/**
 * Pool mutex structure
 */
#if !defined(HAVE_PTHREAD_PROCESS_SHARED) || defined(DISABLE_PTHREAD_MUTEX)
typedef struct memory_pool_mutex_s {
	gint lock;
	pid_t owner;
	guint spin;
} rspamd_mempool_mutex_t;
/**
 * Rwlock for locking shared memory regions
 */
typedef struct memory_pool_rwlock_s {
	rspamd_mempool_mutex_t *__r_lock; /**< read mutex (private)								*/
	rspamd_mempool_mutex_t *__w_lock; /**< write mutex (private)								*/
} rspamd_mempool_rwlock_t;
#else
typedef pthread_mutex_t rspamd_mempool_mutex_t;
typedef pthread_rwlock_t rspamd_mempool_rwlock_t;
#endif

/**
 * Tag to use for logging purposes
 */
struct rspamd_mempool_tag {
	gchar tagname[MEMPOOL_TAG_LEN]; /**< readable name							*/
	gchar uid[MEMPOOL_UID_LEN];     /**< unique id								*/
};

enum rspamd_mempool_flags {
	RSPAMD_MEMPOOL_DEBUG = (1u << 0u),
};

/**
 * Memory pool type
 */
struct rspamd_mempool_entry_point;
struct rspamd_mutex_s;
struct rspamd_mempool_specific;
typedef struct memory_pool_s {
	struct rspamd_mempool_specific *priv;
	struct rspamd_mempool_tag tag; /**< memory pool tag						*/
} rspamd_mempool_t;

/**
 * Statistics structure
 */
typedef struct memory_pool_stat_s {
	guint pools_allocated;         /**< total number of allocated pools					*/
	guint pools_freed;             /**< number of freed pools								*/
	guint bytes_allocated;         /**< bytes that are allocated with pool allocator		*/
	guint chunks_allocated;        /**< number of chunks that are allocated				*/
	guint shared_chunks_allocated; /**< shared chunks allocated							*/
	guint chunks_freed;            /**< chunks freed										*/
	guint oversized_chunks;        /**< oversized chunks									*/
	guint fragmented_size;         /**< fragmentation size								*/
} rspamd_mempool_stat_t;


/**
 * Allocate new memory poll
 * @param size size of pool's page
 * @return new memory pool object
 */
rspamd_mempool_t *rspamd_mempool_new_(gsize size, const gchar *tag, gint flags,
									  const gchar *loc);

#define rspamd_mempool_new(size, tag, flags) \
	rspamd_mempool_new_((size), (tag), (flags), G_STRLOC)
#define rspamd_mempool_new_default(tag, flags) \
	rspamd_mempool_new_(rspamd_mempool_suggest_size_(G_STRLOC), (tag), (flags), G_STRLOC)

/**
 * Get memory from pool
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc)
	RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;
/**
 * Allocates array handling potential integer overflow
 * @param pool
 * @param nmemb
 * @param size
 * @param alignment
 * @param loc
 * @return
 */
void *rspamd_mempool_alloc_array_(rspamd_mempool_t *pool, gsize nmemb, gsize size, gsize alignment, const gchar *loc)
	RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;
#define rspamd_mempool_alloc(pool, size) \
	rspamd_mempool_alloc_((pool), (size), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc_array(pool, nmemb, size) \
	rspamd_mempool_alloc_array_((pool), (nmemb), (size), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc_array_type(pool, nmemb, type) \
	(type *) rspamd_mempool_alloc_array_((pool), (nmemb), sizeof(type), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc_type(pool, type)             \
	(type *) (rspamd_mempool_alloc_((pool), sizeof(type), \
									MAX(MIN_MEM_ALIGNMENT, RSPAMD_ALIGNOF(type)), (G_STRLOC)))
#define rspamd_mempool_alloc_buffer(pool, buflen) \
	(char *) (rspamd_mempool_alloc_((pool), sizeof(char) * (buflen), MIN_MEM_ALIGNMENT, (G_STRLOC)))
/**
 * Notify external memory usage for memory pool
 * @param pool
 * @param size
 * @param loc
 */
void rspamd_mempool_notify_alloc_(rspamd_mempool_t *pool, gsize size, const gchar *loc);
#define rspamd_mempool_notify_alloc(pool, size) \
	rspamd_mempool_notify_alloc_((pool), (size), (G_STRLOC))

/**
 * Get memory and set it to zero
 * @param pool memory pool object
 * @param size bytes to allocate
 * @return pointer to allocated object
 */
void *rspamd_mempool_alloc0_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc)
	RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;
#define rspamd_mempool_alloc0(pool, size) \
	rspamd_mempool_alloc0_((pool), (size), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc0_type(pool, type)             \
	(type *) (rspamd_mempool_alloc0_((pool), sizeof(type), \
									 MAX(MIN_MEM_ALIGNMENT, RSPAMD_ALIGNOF(type)), (G_STRLOC)))

/**
 * Make a copy of string in pool
 * @param pool memory pool object
 * @param src source string
 * @return pointer to newly created string that is copy of src
 */
gchar *rspamd_mempool_strdup_(rspamd_mempool_t *pool, const gchar *src, const gchar *loc)
	RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT);
#define rspamd_mempool_strdup(pool, src) \
	rspamd_mempool_strdup_((pool), (src), (G_STRLOC))

struct f_str_tok;

/**
 * Make a copy of fixed string token in pool as null terminated string
 * @param pool memory pool object
 * @param src source string
 * @return pointer to newly created string that is copy of src
 */
gchar *rspamd_mempool_ftokdup_(rspamd_mempool_t *pool,
							   const struct f_str_tok *src,
							   const gchar *loc)
	RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT);
#define rspamd_mempool_ftokdup(pool, src) \
	rspamd_mempool_ftokdup_((pool), (src), (G_STRLOC))

/**
 * Allocate piece of shared memory
 * @param pool memory pool object
 * @param size bytes to allocate
 */
void *rspamd_mempool_alloc_shared_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc)
	RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;
#define rspamd_mempool_alloc_shared(pool, size) \
	rspamd_mempool_alloc_shared_((pool), (size), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc_shared_type(pool, type)             \
	(type *) (rspamd_mempool_alloc_shared_((pool), sizeof(type), \
										   MAX(MIN_MEM_ALIGNMENT, RSPAMD_ALIGNOF(type)), (G_STRLOC)))

void *rspamd_mempool_alloc0_shared_(rspamd_mempool_t *pool, gsize size, gsize alignment, const gchar *loc)
	RSPAMD_ATTR_ALLOC_SIZE(2) RSPAMD_ATTR_ALLOC_ALIGN(MIN_MEM_ALIGNMENT) RSPAMD_ATTR_RETURNS_NONNUL;
#define rspamd_mempool_alloc0_shared(pool, size) \
	rspamd_mempool_alloc0_shared_((pool), (size), MIN_MEM_ALIGNMENT, (G_STRLOC))
#define rspamd_mempool_alloc0_shared_type(pool, type)             \
	(type *) (rspamd_mempool_alloc0_shared_((pool), sizeof(type), \
											MAX(MIN_MEM_ALIGNMENT, RSPAMD_ALIGNOF(type)), (G_STRLOC)))

/**
 * Add destructor callback to pool
 * @param pool memory pool object
 * @param func pointer to function-destructor
 * @param data pointer to data that would be passed to destructor
 */
void rspamd_mempool_add_destructor_full(rspamd_mempool_t *pool,
										rspamd_mempool_destruct_t func,
										void *data,
										const gchar *function,
										const gchar *line);

/* Macros for common usage */
#define rspamd_mempool_add_destructor(pool, func, data) \
	rspamd_mempool_add_destructor_full(pool, func, data, (MEMPOOL_STR_FUNC), (G_STRLOC))

/**
 * Replace destructor callback to pool for specified pointer
 * @param pool memory pool object
 * @param func pointer to function-destructor
 * @param old_data pointer to old data
 * @param new_data pointer to data that would be passed to destructor
 */
void rspamd_mempool_replace_destructor(rspamd_mempool_t *pool,
									   rspamd_mempool_destruct_t func,
									   void *old_data, void *new_data);

/**
 * Calls all destructors associated with the specific memory pool without removing
 * of the pool itself
 * @param pool
 */
void rspamd_mempool_destructors_enforce(rspamd_mempool_t *pool);

/**
 * Delete pool, free all its chunks and call destructors chain
 * @param pool memory pool object
 */
void rspamd_mempool_delete(rspamd_mempool_t *pool);

/**
 * Get new mutex from pool (allocated in shared memory)
 * @param pool memory pool object
 * @return mutex object
 */
rspamd_mempool_mutex_t *rspamd_mempool_get_mutex(rspamd_mempool_t *pool);

/**
 * Lock mutex
 * @param mutex mutex to lock
 */
void rspamd_mempool_lock_mutex(rspamd_mempool_mutex_t *mutex);

/**
 * Unlock mutex
 * @param mutex mutex to unlock
 */
void rspamd_mempool_unlock_mutex(rspamd_mempool_mutex_t *mutex);

/**
 * Create new rwlock and place it in shared memory
 * @param pool memory pool object
 * @return rwlock object
 */
rspamd_mempool_rwlock_t *rspamd_mempool_get_rwlock(rspamd_mempool_t *pool);

/**
 * Acquire read lock
 * @param lock rwlock object
 */
void rspamd_mempool_rlock_rwlock(rspamd_mempool_rwlock_t *lock);

/**
 * Acquire write lock
 * @param lock rwlock object
 */
void rspamd_mempool_wlock_rwlock(rspamd_mempool_rwlock_t *lock);

/**
 * Release read lock
 * @param lock rwlock object
 */
void rspamd_mempool_runlock_rwlock(rspamd_mempool_rwlock_t *lock);

/**
 * Release write lock
 * @param lock rwlock object
 */
void rspamd_mempool_wunlock_rwlock(rspamd_mempool_rwlock_t *lock);

/**
 * Get pool allocator statistics
 * @param st stat pool struct
 */
void rspamd_mempool_stat(rspamd_mempool_stat_t *st);

/**
 * Reset memory pool stat
 */
void rspamd_mempool_stat_reset(void);

/**
 * Get optimal pool size based on page size for this system
 * @return size of memory page in system
 */
#define rspamd_mempool_suggest_size() rspamd_mempool_suggest_size_(G_STRLOC)

gsize rspamd_mempool_suggest_size_(const char *loc);

gsize rspamd_mempool_get_used_size(rspamd_mempool_t *pool);
gsize rspamd_mempool_get_wasted_size(rspamd_mempool_t *pool);

/**
 * Set memory pool variable
 * @param pool memory pool object
 * @param name name of variable
 * @param gpointer value of variable
 * @param destructor pointer to function-destructor
 */
void rspamd_mempool_set_variable(rspamd_mempool_t *pool,
								 const gchar *name,
								 gpointer value,
								 rspamd_mempool_destruct_t destructor);

/**
 * Get memory pool variable
 * @param pool memory pool object
 * @param name name of variable
 * @return NULL or pointer to variable data
 */
gpointer rspamd_mempool_get_variable(rspamd_mempool_t *pool,
									 const gchar *name);

/**
 * Removes variable from memory pool
 * @param pool memory pool object
 * @param name name of variable
 */
void rspamd_mempool_remove_variable(rspamd_mempool_t *pool,
									const gchar *name);

/**
 * Prepend element to a list creating it in the memory pool
 * @param l
 * @param p
 * @return
 */
GList *rspamd_mempool_glist_prepend(rspamd_mempool_t *pool,
									GList *l, gpointer p) G_GNUC_WARN_UNUSED_RESULT;

/**
 * Append element to a list creating it in the memory pool
 * @param l
 * @param p
 * @return
 */
GList *rspamd_mempool_glist_append(rspamd_mempool_t *pool,
								   GList *l, gpointer p) G_GNUC_WARN_UNUSED_RESULT;

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
#include <stdexcept> /* For std::runtime_error */

namespace rspamd {

template<class T>
class mempool_allocator {
public:
	typedef T value_type;

	mempool_allocator() = delete;
	template<class U>
	mempool_allocator(const mempool_allocator<U> &other)
		: pool(other.pool)
	{
	}
	mempool_allocator(rspamd_mempool_t *_pool)
		: pool(_pool)
	{
	}
	[[nodiscard]] constexpr T *allocate(std::size_t n)
	{
		if (G_MAXSIZE / 2 / sizeof(T) > n) {
			throw std::runtime_error("integer overflow");
		}
		return reinterpret_cast<T *>(rspamd_mempool_alloc(pool, n * sizeof(T)));
	}
	constexpr void deallocate(T *p, std::size_t n)
	{
		/* Do nothing */
	}

private:
	rspamd_mempool_t *pool;
};

}// namespace rspamd
#endif

#endif
