/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

#include "zbxshmem.h"

#include "zbxstr.h"
#include "log.h"

/******************************************************************************
 *                                                                            *
 *                     Some information on memory layout                      *
 *                  ---------------------------------------                   *
 *                                                                            *
 * (*) chunk: a contiguous piece of memory that is either free or used        *
 *                                                                            *
 *                    +-------- size of + --------------+                     *
 *                    |       (8 bytes) |               |                     *
 *                    |                 v               |                     *
 *                    |                                 |                     *
 *                    |    +- allocatable memory --+    |                     *
 *                    |    | (user data goes here) |    |                     *
 *                    v    v                       v    v                     *
 *                                                                            *
 *                |--------|----------------...----|--------|                 *
 *                                                                            *
 *                ^        ^                       ^        ^                 *
 *            8-aligned    |                       |    8-aligned             *
 *                                                                            *
 *                     8-aligned               8-aligned                      *
 *                                                                            *
 *     when a chunk is used, `size' fields have SHMEM_FLG_USED bit set        *
 *                                                                            *
 *     when a chunk is free, the first 2 * ZBX_PTR_SIZE bytes of allocatable  *
 *     memory contain pointers to the previous and next chunks, in that order *
 *                                                                            *
 *     notes:                                                                 *
 *                                                                            *
 *         - user data is nicely 8-aligned                                    *
 *                                                                            *
 *         - size is kept on both left and right ends for quick merging       *
 *           (when freeing a chunk, we can quickly see if the previous        *
 *           and next chunks are free, those will not have SHMEM_FLG_USED)    *
 *                                                                            *
 * (*) free chunks are stored in doubly-linked lists according to their sizes *
 *                                                                            *
 *     a typical situation is thus as follows (1 used chunk, 2 free chunks)   *
 *                                                                            *
 *  +--------------------------- shared memory ----------------------------+  *
 *  |                         (can be misaligned)                          |  *
 *  |                                                                      |  *
 *  |                                                                      |  *
 *  |  +------ chunk A ------+------ chunk B -----+------ chunk C ------+  |  *
 *  |  |       (free)        |       (used)       |       (free)        |  |  *
 *  |  |                     |                    |                     |  |  *
 *  v  v                     v                    v                     v  v  *
 *           prevnext              user data            prevnext              *
 *  #--|----|--------...|----|----|---....---|----|----|--------...|----|--#  *
 *           NULL  |                                     |  NULL              *
 *     ^           |                              ^      |              ^     *
 *     |           |                              |      |              |     *
 *     |           +------------------------------+      |              |     *
 *     |                                                 |              |     *
 *     +-------------------------------------------------+              |     *
 *     |                                                                |     *
 *                                                                            *
 *  lo_bound             `size' fields in chunk B                   hi_bound  *
 *  (aligned)            have SHMEM_FLG_USED bit set               (aligned)  *
 *                                                                            *
 ******************************************************************************/

static void	*ALIGN4(void *ptr);
static void	*ALIGN8(void *ptr);
static void	*ALIGNPTR(void *ptr);

static zbx_uint64_t	mem_proper_alloc_size(zbx_uint64_t size);
static int	mem_bucket_by_size(zbx_uint64_t size);

static void	mem_set_chunk_size(void *chunk, zbx_uint64_t size);
static void	mem_set_used_chunk_size(void *chunk, zbx_uint64_t size);

static void	*mem_get_prev_chunk(void *chunk);
static void	mem_set_prev_chunk(void *chunk, void *prev);
static void	*mem_get_next_chunk(void *chunk);
static void	mem_set_next_chunk(void *chunk, void *next);
static void	**mem_ptr_to_prev_field(void *chunk);
static void	**mem_ptr_to_next_field(void *chunk, void **first_chunk);

static void	mem_link_chunk(zbx_shmem_info_t *info, void *chunk);
static void	mem_unlink_chunk(zbx_shmem_info_t *info, void *chunk);

static void	*__mem_malloc(zbx_shmem_info_t *info, zbx_uint64_t size);
static void	*__mem_realloc(zbx_shmem_info_t *info, void *old, zbx_uint64_t size);
static void	__mem_free(zbx_shmem_info_t *info, void *ptr);

#define SHMEM_SIZE_FIELD	sizeof(zbx_uint64_t)

#define SHMEM_FLG_USED		((__UINT64_C(1))<<63)

#define FREE_CHUNK(ptr)		(((*(zbx_uint64_t *)(ptr)) & SHMEM_FLG_USED) == 0)
#define CHUNK_SIZE(ptr)		((*(zbx_uint64_t *)(ptr)) & ~SHMEM_FLG_USED)

#define SHMEM_MIN_SIZE		__UINT64_C(128)
#define SHMEM_MAX_SIZE		__UINT64_C(0x1000000000)	/* 64 GB */

/* helper functions */

static void	*ALIGN4(void *ptr)
{
	return (void *)((uintptr_t)((char *)ptr + 3) & (uintptr_t)~3);
}

static void	*ALIGN8(void *ptr)
{
	return (void *)((uintptr_t)((char *)ptr + 7) & (uintptr_t)~7);
}

static void	*ALIGNPTR(void *ptr)
{
	if (4 == ZBX_PTR_SIZE)
		return ALIGN4(ptr);
	if (8 == ZBX_PTR_SIZE)
		return ALIGN8(ptr);
	assert(0);
}

static zbx_uint64_t	mem_proper_alloc_size(zbx_uint64_t size)
{
	if (size >= SHMEM_MIN_ALLOC)
		return size + ((8 - (size & 7)) & 7);	/* allocate in multiples of 8... */
	else
		return SHMEM_MIN_ALLOC;			/* ...and at least SHMEM_MIN_ALLOC */
}

static int	mem_bucket_by_size(zbx_uint64_t size)
{
	if (size < ZBX_SHMEM_MIN_BUCKET_SIZE)
		return 0;
	if (size < SHMEM_MAX_BUCKET_SIZE)
		return (size - ZBX_SHMEM_MIN_BUCKET_SIZE) >> 3;
	return ZBX_SHMEM_BUCKET_COUNT - 1;
}

static void	mem_set_chunk_size(void *chunk, zbx_uint64_t size)
{
	*(zbx_uint64_t *)chunk = size;
	*(zbx_uint64_t *)((char *)chunk + SHMEM_SIZE_FIELD + size) = size;
}

static void	mem_set_used_chunk_size(void *chunk, zbx_uint64_t size)
{
	*(zbx_uint64_t *)chunk = SHMEM_FLG_USED | size;
	*(zbx_uint64_t *)((char *)chunk + SHMEM_SIZE_FIELD + size) = SHMEM_FLG_USED | size;
}

static void	*mem_get_prev_chunk(void *chunk)
{
	return *(void **)((char *)chunk + SHMEM_SIZE_FIELD);
}

static void	mem_set_prev_chunk(void *chunk, void *prev)
{
	*(void **)((char *)chunk + SHMEM_SIZE_FIELD) = prev;
}

static void	*mem_get_next_chunk(void *chunk)
{
	return *(void **)((char *)chunk + SHMEM_SIZE_FIELD + ZBX_PTR_SIZE);
}

static void	mem_set_next_chunk(void *chunk, void *next)
{
	*(void **)((char *)chunk + SHMEM_SIZE_FIELD + ZBX_PTR_SIZE) = next;
}

static void	**mem_ptr_to_prev_field(void *chunk)
{
	return (NULL != chunk ? (void **)((char *)chunk + SHMEM_SIZE_FIELD) : NULL);
}

static void	**mem_ptr_to_next_field(void *chunk, void **first_chunk)
{
	return (NULL != chunk ? (void **)((char *)chunk + SHMEM_SIZE_FIELD + ZBX_PTR_SIZE) : first_chunk);
}

static void	mem_link_chunk(zbx_shmem_info_t *info, void *chunk)
{
	int	index;

	index = mem_bucket_by_size(CHUNK_SIZE(chunk));

	if (NULL != info->buckets[index])
		mem_set_prev_chunk(info->buckets[index], chunk);

	mem_set_prev_chunk(chunk, NULL);
	mem_set_next_chunk(chunk, info->buckets[index]);

	info->buckets[index] = chunk;
}

static void	mem_unlink_chunk(zbx_shmem_info_t *info, void *chunk)
{
	int	index;
	void	*prev_chunk, *next_chunk;
	void	**next_in_prev_chunk, **prev_in_next_chunk;

	index = mem_bucket_by_size(CHUNK_SIZE(chunk));

	prev_chunk = mem_get_prev_chunk(chunk);
	next_chunk = mem_get_next_chunk(chunk);

	next_in_prev_chunk = mem_ptr_to_next_field(prev_chunk, &info->buckets[index]);
	prev_in_next_chunk = mem_ptr_to_prev_field(next_chunk);

	*next_in_prev_chunk = next_chunk;
	if (NULL != prev_in_next_chunk)
		*prev_in_next_chunk = prev_chunk;
}

/* private memory functions */

static void	*__mem_malloc(zbx_shmem_info_t *info, zbx_uint64_t size)
{
	int		index;
	void		*chunk;
	zbx_uint64_t	chunk_size;

	size = mem_proper_alloc_size(size);

	/* try to find an appropriate chunk in special buckets */

	index = mem_bucket_by_size(size);

	while (index < ZBX_SHMEM_BUCKET_COUNT - 1 && NULL == info->buckets[index])
		index++;

	chunk = info->buckets[index];

	if (index == ZBX_SHMEM_BUCKET_COUNT - 1)
	{
		/* otherwise, find a chunk big enough according to first-fit strategy */

		int		counter = 0;
		zbx_uint64_t	skip_min = __UINT64_C(0xffffffffffffffff), skip_max = __UINT64_C(0);

		while (NULL != chunk && CHUNK_SIZE(chunk) < size)
		{
			counter++;
			skip_min = MIN(skip_min, CHUNK_SIZE(chunk));
			skip_max = MAX(skip_max, CHUNK_SIZE(chunk));
			chunk = mem_get_next_chunk(chunk);
		}

		/* don't log errors if malloc can return null in low memory situations */
		if (0 == info->allow_oom)
		{
			if (NULL == chunk)
			{
				zabbix_log(LOG_LEVEL_CRIT, "__mem_malloc: skipped %d asked " ZBX_FS_UI64 " skip_min "
						ZBX_FS_UI64 " skip_max " ZBX_FS_UI64,
						counter, size, skip_min, skip_max);
			}
			else if (counter >= 100)
			{
				zabbix_log(LOG_LEVEL_DEBUG, "__mem_malloc: skipped %d asked " ZBX_FS_UI64 " skip_min "
						ZBX_FS_UI64 " skip_max " ZBX_FS_UI64 " size " ZBX_FS_UI64, counter,
						size, skip_min, skip_max, CHUNK_SIZE(chunk));
			}
		}
	}

	if (NULL == chunk)
		return NULL;

	chunk_size = CHUNK_SIZE(chunk);
	mem_unlink_chunk(info, chunk);

	/* either use the full chunk or split it */

	if (chunk_size < size + 2 * SHMEM_SIZE_FIELD + SHMEM_MIN_ALLOC)
	{
		info->used_size += chunk_size;
		info->free_size -= chunk_size;

		mem_set_used_chunk_size(chunk, chunk_size);
	}
	else
	{
		void		*new_chunk;
		zbx_uint64_t	new_chunk_size;

		new_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + size + SHMEM_SIZE_FIELD);
		new_chunk_size = chunk_size - size - 2 * SHMEM_SIZE_FIELD;
		mem_set_chunk_size(new_chunk, new_chunk_size);
		mem_link_chunk(info, new_chunk);

		info->used_size += size;
		info->free_size -= chunk_size;
		info->free_size += new_chunk_size;

		mem_set_used_chunk_size(chunk, size);
	}

	return chunk;
}

static void	*__mem_realloc(zbx_shmem_info_t *info, void *old, zbx_uint64_t size)
{
	void		*chunk, *new_chunk, *next_chunk;
	zbx_uint64_t	chunk_size, new_chunk_size;
	int		next_free;

	size = mem_proper_alloc_size(size);

	chunk = (void *)((char *)old - SHMEM_SIZE_FIELD);
	chunk_size = CHUNK_SIZE(chunk);

	next_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + chunk_size + SHMEM_SIZE_FIELD);
	next_free = (next_chunk < info->hi_bound && FREE_CHUNK(next_chunk));

	if (size <= chunk_size)
	{
		/* do not reallocate if not much is freed */
		/* we are likely to want more memory again */
		if (size > chunk_size / 4)
			return chunk;

		if (next_free)
		{
			/* merge with next chunk */

			info->used_size -= chunk_size - size;
			info->free_size += chunk_size - size;

			new_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + size + SHMEM_SIZE_FIELD);
			new_chunk_size = CHUNK_SIZE(next_chunk) + (chunk_size - size);

			mem_unlink_chunk(info, next_chunk);

			mem_set_chunk_size(new_chunk, new_chunk_size);
			mem_link_chunk(info, new_chunk);

			mem_set_used_chunk_size(chunk, size);
		}
		else
		{
			/* split the current one */

			info->used_size -= chunk_size - size;
			info->free_size += chunk_size - size - 2 * SHMEM_SIZE_FIELD;

			new_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + size + SHMEM_SIZE_FIELD);
			new_chunk_size = chunk_size - size - 2 * SHMEM_SIZE_FIELD;

			mem_set_chunk_size(new_chunk, new_chunk_size);
			mem_link_chunk(info, new_chunk);

			mem_set_used_chunk_size(chunk, size);
		}

		return chunk;
	}

	if (next_free && chunk_size + 2 * SHMEM_SIZE_FIELD + CHUNK_SIZE(next_chunk) >= size)
	{
		info->used_size -= chunk_size;
		info->free_size += chunk_size + 2 * SHMEM_SIZE_FIELD;

		chunk_size += 2 * SHMEM_SIZE_FIELD + CHUNK_SIZE(next_chunk);

		mem_unlink_chunk(info, next_chunk);

		/* either use the full next_chunk or split it */

		if (chunk_size < size + 2 * SHMEM_SIZE_FIELD + SHMEM_MIN_ALLOC)
		{
			info->used_size += chunk_size;
			info->free_size -= chunk_size;

			mem_set_used_chunk_size(chunk, chunk_size);
		}
		else
		{
			new_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + size + SHMEM_SIZE_FIELD);
			new_chunk_size = chunk_size - size - 2 * SHMEM_SIZE_FIELD;
			mem_set_chunk_size(new_chunk, new_chunk_size);
			mem_link_chunk(info, new_chunk);

			info->used_size += size;
			info->free_size -= chunk_size;
			info->free_size += new_chunk_size;

			mem_set_used_chunk_size(chunk, size);
		}

		return chunk;
	}
	else if (NULL != (new_chunk = __mem_malloc(info, size)))
	{
		memcpy((char *)new_chunk + SHMEM_SIZE_FIELD, (char *)chunk + SHMEM_SIZE_FIELD, chunk_size);

		__mem_free(info, old);

		return new_chunk;
	}
	else
	{
		void	*tmp = NULL;

		/* check if there would be enough space if the current chunk */
		/* would be freed before allocating a new one                */
		new_chunk_size = chunk_size;

		if (0 != next_free)
			new_chunk_size += CHUNK_SIZE(next_chunk) + 2 * SHMEM_SIZE_FIELD;

		if (info->lo_bound < chunk && FREE_CHUNK((char *)chunk - SHMEM_SIZE_FIELD))
			new_chunk_size += CHUNK_SIZE((char *)chunk - SHMEM_SIZE_FIELD) + 2 * SHMEM_SIZE_FIELD;

		if (size > new_chunk_size)
			return NULL;

		tmp = zbx_malloc(tmp, chunk_size);

		memcpy(tmp, (char *)chunk + SHMEM_SIZE_FIELD, chunk_size);

		__mem_free(info, old);

		if (NULL == (new_chunk = __mem_malloc(info, size)))
		{
			THIS_SHOULD_NEVER_HAPPEN;
			exit(EXIT_FAILURE);
		}

		memcpy((char *)new_chunk + SHMEM_SIZE_FIELD, tmp, chunk_size);

		zbx_free(tmp);

		return new_chunk;
	}
}

static void	__mem_free(zbx_shmem_info_t *info, void *ptr)
{
	void		*chunk;
	void		*prev_chunk, *next_chunk;
	zbx_uint64_t	chunk_size;
	int		prev_free, next_free;

	chunk = (void *)((char *)ptr - SHMEM_SIZE_FIELD);
	chunk_size = CHUNK_SIZE(chunk);

	info->used_size -= chunk_size;
	info->free_size += chunk_size;

	/* see if we can merge with previous and next chunks */

	next_chunk = (void *)((char *)chunk + SHMEM_SIZE_FIELD + chunk_size + SHMEM_SIZE_FIELD);

	prev_free = (info->lo_bound < chunk && FREE_CHUNK((char *)chunk - SHMEM_SIZE_FIELD));
	next_free = (next_chunk < info->hi_bound && FREE_CHUNK(next_chunk));

	if (prev_free && next_free)
	{
		info->free_size += 4 * SHMEM_SIZE_FIELD;

		prev_chunk = (char *)chunk - SHMEM_SIZE_FIELD - CHUNK_SIZE((char *)chunk - SHMEM_SIZE_FIELD) -
				SHMEM_SIZE_FIELD;

		chunk_size += 4 * SHMEM_SIZE_FIELD + CHUNK_SIZE(prev_chunk) + CHUNK_SIZE(next_chunk);

		mem_unlink_chunk(info, prev_chunk);
		mem_unlink_chunk(info, next_chunk);

		chunk = prev_chunk;
		mem_set_chunk_size(chunk, chunk_size);
		mem_link_chunk(info, chunk);
	}
	else if (prev_free)
	{
		info->free_size += 2 * SHMEM_SIZE_FIELD;

		prev_chunk = (void *)((char *)chunk - SHMEM_SIZE_FIELD - CHUNK_SIZE((char *)chunk - SHMEM_SIZE_FIELD) -
				SHMEM_SIZE_FIELD);

		chunk_size += 2 * SHMEM_SIZE_FIELD + CHUNK_SIZE(prev_chunk);

		mem_unlink_chunk(info, prev_chunk);

		chunk = prev_chunk;
		mem_set_chunk_size(chunk, chunk_size);
		mem_link_chunk(info, chunk);
	}
	else if (next_free)
	{
		info->free_size += 2 * SHMEM_SIZE_FIELD;

		chunk_size += 2 * SHMEM_SIZE_FIELD + CHUNK_SIZE(next_chunk);

		mem_unlink_chunk(info, next_chunk);

		mem_set_chunk_size(chunk, chunk_size);
		mem_link_chunk(info, chunk);
	}
	else
	{
		mem_set_chunk_size(chunk, chunk_size);
		mem_link_chunk(info, chunk);
	}
}

/* public memory interface */

int	zbx_shmem_create(zbx_shmem_info_t **info, zbx_uint64_t size, const char *descr, const char *param,
		int allow_oom, char **error)
{
	int	shm_id, index, ret = FAIL;
	void	*base;

	descr = ZBX_NULL2STR(descr);
	param = ZBX_NULL2STR(param);

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() param:'%s' size:" ZBX_FS_SIZE_T, __func__, param, (zbx_fs_size_t)size);

	/* allocate shared memory */

	if (4 != ZBX_PTR_SIZE && 8 != ZBX_PTR_SIZE)
	{
		*error = zbx_dsprintf(*error, "failed assumption about pointer size (" ZBX_FS_SIZE_T " not in {4, 8})",
				(zbx_fs_size_t)ZBX_PTR_SIZE);
		goto out;
	}

	if (!(SHMEM_MIN_SIZE <= size && size <= SHMEM_MAX_SIZE))
	{
		*error = zbx_dsprintf(*error, "requested size " ZBX_FS_UI64 " not within bounds [" ZBX_FS_UI64
				" <= size <= " ZBX_FS_UI64 "]", size, SHMEM_MIN_SIZE, SHMEM_MAX_SIZE);
		goto out;
	}

	if (-1 == (shm_id = shmget(IPC_PRIVATE, size, 0600)))
	{
		*error = zbx_dsprintf(*error, "cannot get private shared memory of size " ZBX_FS_SIZE_T " for %s: %s",
				(zbx_fs_size_t)size, descr, zbx_strerror(errno));
		goto out;
	}

	if ((void *)(-1) == (base = shmat(shm_id, NULL, 0)))
	{
		*error = zbx_dsprintf(*error, "cannot attach shared memory for %s: %s", descr, zbx_strerror(errno));
		goto out;
	}

	if (-1 == shmctl(shm_id, IPC_RMID, NULL))
		zbx_error("cannot mark shared memory %d for destruction: %s", shm_id, zbx_strerror(errno));

	ret = SUCCEED;

	/* allocate zbx_shmem_info_t structure, its buckets, and description inside shared memory */

	*info = (zbx_shmem_info_t *)ALIGN8(base);
	(*info)->base = base;
	(*info)->shm_id = shm_id;
	(*info)->orig_size = size;
	size -= (char *)(*info + 1) - (char *)base;

	base = (void *)(*info + 1);

	(*info)->buckets = (void **)ALIGNPTR(base);
	memset((*info)->buckets, 0, ZBX_SHMEM_BUCKET_COUNT * ZBX_PTR_SIZE);
	size -= (char *)((*info)->buckets + ZBX_SHMEM_BUCKET_COUNT) - (char *)base;
	base = (void *)((*info)->buckets + ZBX_SHMEM_BUCKET_COUNT);

	zbx_strlcpy((char *)base, descr, size);
	(*info)->mem_descr = (char *)base;
	size -= strlen(descr) + 1;
	base = (void *)((char *)base + strlen(descr) + 1);

	zbx_strlcpy((char *)base, param, size);
	(*info)->mem_param = (char *)base;
	size -= strlen(param) + 1;
	base = (void *)((char *)base + strlen(param) + 1);

	(*info)->allow_oom = allow_oom;

	/* prepare shared memory for further allocation by creating one big chunk */
	(*info)->lo_bound = ALIGN8(base);
	(*info)->hi_bound = ALIGN8((char *)base + size - 8);

	(*info)->total_size = (zbx_uint64_t)((char *)((*info)->hi_bound) - (char *)((*info)->lo_bound) -
			2 * SHMEM_SIZE_FIELD);

	index = mem_bucket_by_size((*info)->total_size);
	(*info)->buckets[index] = (*info)->lo_bound;
	mem_set_chunk_size((*info)->buckets[index], (*info)->total_size);
	mem_set_prev_chunk((*info)->buckets[index], NULL);
	mem_set_next_chunk((*info)->buckets[index], NULL);

	(*info)->used_size = 0;
	(*info)->free_size = (*info)->total_size;

	zabbix_log(LOG_LEVEL_DEBUG, "valid user addresses: [%p, %p] total size: " ZBX_FS_SIZE_T,
			(void *)((char *)(*info)->lo_bound + SHMEM_SIZE_FIELD),
			(void *)((char *)(*info)->hi_bound - SHMEM_SIZE_FIELD),
			(zbx_fs_size_t)(*info)->total_size);
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: allocate the required shared memory size                          *
 *                                                                            *
 * Return value: SUCCEED - the memory was allocated successfully              *
 *               FAIL - otherwise                                             *
 *                                                                            *
 * Comments: When allocating shared memory with default zbx_shmem_create()    *
 *           function the available memory will reduced by the allocator      *
 *           overhead. This function estimates the overhead and requests      *
 *           enough memory so the available memory is greater or equal to the *
 *           requested size.                                                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_shmem_create_min(zbx_shmem_info_t **info, zbx_uint64_t size, const char *descr, const char *param,
		int allow_oom, char **error)
{
	void	*base = NULL;

	descr = ZBX_NULL2STR(descr);
	param = ZBX_NULL2STR(param);

	base = (void *)((zbx_shmem_info_t *)(base) + 1);
	base = ALIGNPTR(base);
	base = (void *)((void **)base + ZBX_SHMEM_BUCKET_COUNT);
	base = (void *)((char *)base + strlen(descr) + 1);
	base = (void *)((char *)base + strlen(param) + 1);
	base = ALIGN8(base);

	size += (size_t )base;

	size += 8;
	size += 2 * SHMEM_SIZE_FIELD;

	return zbx_shmem_create(info, size, descr, param, allow_oom, error);
}

void	zbx_shmem_destroy(zbx_shmem_info_t *info)
{
	(void)shmdt(info->base);
}

void	*__zbx_shmem_malloc(const char *file, int line, zbx_shmem_info_t *info, const void *old, size_t size)
{
	void	*chunk;

	if (NULL != old)
	{
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): allocating already allocated memory",
				file, line, __func__);
		exit(EXIT_FAILURE);
	}

	if (0 == size || size > SHMEM_MAX_SIZE)
	{
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): asking for a bad number of bytes (" ZBX_FS_SIZE_T
				")", file, line, __func__, (zbx_fs_size_t)size);
		exit(EXIT_FAILURE);
	}

	chunk = __mem_malloc(info, size);

	if (NULL == chunk)
	{
		if (1 == info->allow_oom)
			return NULL;

		zbx_shmem_dump_stats(LOG_LEVEL_CRIT, info);
		zbx_backtrace();

		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): out of memory (requested " ZBX_FS_SIZE_T " bytes)",
				file, line, __func__, (zbx_fs_size_t)size);
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): please increase %s configuration parameter",
				file, line, __func__, info->mem_param);
		exit(EXIT_FAILURE);
	}

	return (void *)((char *)chunk + SHMEM_SIZE_FIELD);
}

void	*__zbx_shmem_realloc(const char *file, int line, zbx_shmem_info_t *info, void *old, size_t size)
{
	void	*chunk;

	if (0 == size || size > SHMEM_MAX_SIZE)
	{
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): asking for a bad number of bytes (" ZBX_FS_SIZE_T
				")", file, line, __func__, (zbx_fs_size_t)size);
		exit(EXIT_FAILURE);
	}

	if (NULL == old)
		chunk = __mem_malloc(info, size);
	else
		chunk = __mem_realloc(info, old, size);

	if (NULL == chunk)
	{
		if (1 == info->allow_oom)
			return NULL;

		zbx_shmem_dump_stats(LOG_LEVEL_CRIT, info);
		zbx_backtrace();

		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): out of memory (requested " ZBX_FS_SIZE_T " bytes)",
				file, line, __func__, (zbx_fs_size_t)size);
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): please increase %s configuration parameter",
				file, line, __func__, info->mem_param);
		exit(EXIT_FAILURE);
	}

	return (void *)((char *)chunk + SHMEM_SIZE_FIELD);
}

void	__zbx_shmem_free(const char *file, int line, zbx_shmem_info_t *info, void *ptr)
{
	if (NULL == ptr)
	{
		zabbix_log(LOG_LEVEL_CRIT, "[file:%s,line:%d] %s(): freeing a NULL pointer", file, line, __func__);
		exit(EXIT_FAILURE);
	}

	__mem_free(info, ptr);
}

void	zbx_shmem_clear(zbx_shmem_info_t *info)
{
	int	index;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	memset(info->buckets, 0, ZBX_SHMEM_BUCKET_COUNT * ZBX_PTR_SIZE);
	index = mem_bucket_by_size(info->total_size);
	info->buckets[index] = info->lo_bound;
	mem_set_chunk_size(info->buckets[index], info->total_size);
	mem_set_prev_chunk(info->buckets[index], NULL);
	mem_set_next_chunk(info->buckets[index], NULL);
	info->used_size = 0;
	info->free_size = info->total_size;

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

void	zbx_shmem_get_stats(const zbx_shmem_info_t *info, zbx_shmem_stats_t *stats)
{
	void		*chunk;
	int		i;
	zbx_uint64_t	counter;

	stats->free_chunks = 0;
	stats->max_chunk_size = __UINT64_C(0);
	stats->min_chunk_size = __UINT64_C(0xffffffffffffffff);

	for (i = 0; i < ZBX_SHMEM_BUCKET_COUNT; i++)
	{
		counter = 0;
		chunk = info->buckets[i];

		while (NULL != chunk)
		{
			counter++;
			stats->min_chunk_size = MIN(stats->min_chunk_size, CHUNK_SIZE(chunk));
			stats->max_chunk_size = MAX(stats->max_chunk_size, CHUNK_SIZE(chunk));
			chunk = mem_get_next_chunk(chunk);
		}

		stats->free_chunks += counter;
		stats->chunks_num[i] = counter;
	}

	stats->overhead = info->total_size - info->used_size - info->free_size;
	stats->used_chunks = stats->overhead / (2 * SHMEM_SIZE_FIELD) + 1 - stats->free_chunks;
	stats->free_size = info->free_size;
	stats->used_size = info->used_size;
}

void	zbx_shmem_dump_stats(int level, zbx_shmem_info_t *info)
{
	zbx_shmem_stats_t	stats;
	int		i;

	zbx_shmem_get_stats(info, &stats);

	zabbix_log(level, "=== memory statistics for %s ===", info->mem_descr);

	for (i = 0; i < ZBX_SHMEM_BUCKET_COUNT; i++)
	{
		if (0 == stats.chunks_num[i])
			continue;

		zabbix_log(level, "free chunks of size %2s %3d bytes: %8u", i == ZBX_SHMEM_BUCKET_COUNT - 1 ? ">=" : "",
			ZBX_SHMEM_MIN_BUCKET_SIZE + 8 * i, stats.chunks_num[i]);
	}

	zabbix_log(level, "min chunk size: %10llu bytes", (unsigned long long)stats.min_chunk_size);
	zabbix_log(level, "max chunk size: %10llu bytes", (unsigned long long)stats.max_chunk_size);

	zabbix_log(level, "memory of total size %llu bytes fragmented into %llu chunks",
			(unsigned long long)stats.free_size + stats.used_size,
			(unsigned long long)stats.free_chunks + stats.used_chunks);
	zabbix_log(level, "of those, %10llu bytes are in %8llu free chunks",
			(unsigned long long)stats.free_size, (unsigned long long)stats.free_chunks);
	zabbix_log(level, "of those, %10llu bytes are in %8llu used chunks",
			(unsigned long long)stats.used_size, (unsigned long long)stats.used_chunks);
	zabbix_log(level, "of those, %10llu bytes are used by allocation overhead",
			(unsigned long long)stats.overhead);

	zabbix_log(level, "================================");
}

size_t	zbx_shmem_required_size(int chunks_num, const char *descr, const char *param)
{
	size_t	size = 0;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() size:" ZBX_FS_SIZE_T " chunks_num:%d descr:'%s' param:'%s'",
			__func__, (zbx_fs_size_t)size, chunks_num, descr, param);

	/* shared memory of what size should we allocate so that there is a guarantee */
	/* that we will be able to get ourselves 'chunks_num' pieces of memory with a */
	/* total size of 'size', given that we also have to store 'descr' and 'param'? */

	size += 7;					/* ensure we allocate enough to 8-align zbx_shmem_info_t */
	size += sizeof(zbx_shmem_info_t);
	size += ZBX_PTR_SIZE - 1;			/* ensure we allocate enough to align bucket pointers */
	size += ZBX_PTR_SIZE * ZBX_SHMEM_BUCKET_COUNT;
	size += strlen(descr) + 1;
	size += strlen(param) + 1;
	size += (SHMEM_SIZE_FIELD - 1) + 8;		/* ensure we allocate enough to align the first chunk */
	size += (SHMEM_SIZE_FIELD - 1) + 8;		/* ensure we allocate enough to align right size field */

	size += (chunks_num - 1) * SHMEM_SIZE_FIELD * 2;/* each additional chunk requires 16 bytes of overhead */
	size += chunks_num * (SHMEM_MIN_ALLOC - 1);	/* each chunk has size of at least SHMEM_MIN_ALLOC bytes */

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() size:" ZBX_FS_SIZE_T, __func__, (zbx_fs_size_t)size);

	return size;
}

zbx_uint64_t	zbx_shmem_required_chunk_size(zbx_uint64_t size)
{
	if (0 == size)
		return 0;

	return mem_proper_alloc_size(size) + SHMEM_SIZE_FIELD * 2;
}