/***
 * Copyright 2020 HAProxy Technologies
 *
 * 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.
 */
#include "include.h"


static struct otc_dbg_mem *dbg_mem = nullptr;


/***
 * NAME
 *   otc_dbg_set_metadata -
 *
 * ARGUMENTS
 *   ptr  - the real address of the allocated data
 *   data -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
static void otc_dbg_set_metadata(void *ptr, struct otc_dbg_mem_data *data)
{
	struct otc_dbg_mem_metadata *metadata;

	if (ptr == nullptr)
		return;

	metadata        = OT_CAST_TYPEOF(metadata, ptr);
	metadata->data  = (data == nullptr) ? OT_CAST_TYPEOF(data, metadata) : data;
	metadata->magic = DBG_MEM_MAGIC;
}


/***
 * NAME
 *   otc_dbg_mem_add -
 *
 * ARGUMENTS
 *   func   -
 *   line   -
 *   ptr    - the real address of the allocated data
 *   size   -
 *   data   -
 *   op_idx -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
static void otc_dbg_mem_add(const char *func, int line, void *ptr, size_t size, struct otc_dbg_mem_data *data, int op_idx)
{
	(void)snprintf(data->func, sizeof(data->func), "%s:%d", func, line);

	data->ptr  = ptr;
	data->size = size;
	data->used = 1;

	dbg_mem->size += size;
	dbg_mem->op_cnt[op_idx]++;

	otc_dbg_set_metadata(ptr, data);
}


/***
 * NAME
 *   otc_dbg_mem_alloc -
 *
 * ARGUMENTS
 *   func    -
 *   line    -
 *   old_ptr - the address of the data returned to the program
 *   ptr     - the real address of the allocated data
 *   size    -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
static void otc_dbg_mem_alloc(const char *func, int line, void *old_ptr, void *ptr, size_t size)
{
	size_t i = 0;
	int    rc;

	if (dbg_mem == nullptr) {
		return;
	}
	else if (ptr == nullptr) {
		DBG_MEM_ERR("invalid memory address: %p", ptr);

		return;
	}

	if ((rc = pthread_mutex_lock(&(dbg_mem->mutex))) != 0) {
		DBG_MEM_ERR("cannot lock mutex: %s", otc_strerror(rc));

		return;
	}

	if (old_ptr != nullptr) {
		/* Reallocating memory. */
		struct otc_dbg_mem_metadata *metadata = OT_CAST_TYPEOF(metadata, ptr);

		if (metadata == nullptr) {
			DBG_MEM_ERR("no metadata: MEM_REALLOC %s:%d(%p -> %p %zu)", func, line, old_ptr, DBG_MEM_PTR(ptr), size);
		}
		else if (metadata->data == nullptr) {
			DBG_MEM_ERR("invalid metadata: MEM_REALLOC %s:%d(%p -> %p %zu)", func, line, old_ptr, DBG_MEM_PTR(ptr), size);
		}
		else if (metadata->data == OT_CAST_TYPEOF(metadata->data, metadata)) {
			DBG_MEM_ERR("unset metadata: MEM_REALLOC %s:%d(%p -> %p %zu)", func, line, old_ptr, DBG_MEM_PTR(ptr), size);
		}
		else if (metadata->magic != DBG_MEM_MAGIC) {
			DBG_MEM_ERR("invalid magic: MEM_REALLOC %s:%d(%p -> %p %zu) 0x%016" PRIu64, func, line, old_ptr, DBG_MEM_PTR(ptr), size, metadata->magic);
		}
		else if (metadata->data->used && (metadata->data->ptr == DBG_MEM_DATA(old_ptr))) {
			DBG_MEM_INFO(1, "MEM_REALLOC: %s:%d(%p %zu -> %p %zu)", func, line, old_ptr, metadata->data->size, DBG_MEM_PTR(ptr), size);

			dbg_mem->size -= metadata->data->size;
			otc_dbg_mem_add(func, line, ptr, size, metadata->data, 1);
		}
	} else {
		otc_dbg_set_metadata(ptr, nullptr);

		/*
		 * The first attempt is to find a location that has not been
		 * used at all so far.  If such is not found, an attempt is
		 * made to find the first available location.
		 */
		if (dbg_mem->unused < dbg_mem->count) {
			i = dbg_mem->unused++;
		} else {
			do {
				if (dbg_mem->reused >= dbg_mem->count)
					dbg_mem->reused = 0;

				if (!dbg_mem->data[dbg_mem->reused].used) {
					i = dbg_mem->reused++;

					break;
				}

				dbg_mem->reused++;
			} while (++i <= dbg_mem->count);
		}

		if (i < dbg_mem->count) {
			DBG_MEM_INFO(1, "MEM_ALLOC: %s:%d(%p %zu %zu)", func, line, DBG_MEM_PTR(ptr), size, i);

			otc_dbg_mem_add(func, line, ptr, size, dbg_mem->data + i, 0);
		}
	}

	if ((rc = pthread_mutex_unlock(&(dbg_mem->mutex))) != 0) {
		DBG_MEM_ERR("cannot unlock mutex: %s", otc_strerror(rc));

		return;
	}

	if (i >= dbg_mem->count)
		DBG_MEM_ERR("alloc overflow: %s:%d(%p -> %p %zu)", func, line, old_ptr, DBG_MEM_PTR(ptr), size);
}


/***
 * NAME
 *   otc_dbg_mem_release -
 *
 * ARGUMENTS
 *   func   -
 *   line   -
 *   ptr    - the address of the data returned to the program
 *   op_idx -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
static void otc_dbg_mem_release(const char *func, int line, void *ptr, int op_idx)
{
	struct otc_dbg_mem_metadata *metadata;
	bool                         flag_invalid = 0;
	size_t                       i;
	int                          rc;

	if (dbg_mem == nullptr) {
		return;
	}
	else if (ptr == nullptr) {
		DBG_MEM_ERR("invalid memory address: %p", ptr);

		return;
	}

	if ((rc = pthread_mutex_lock(&(dbg_mem->mutex))) != 0) {
		DBG_MEM_ERR("cannot lock mutex: %s", otc_strerror(rc));

		return;
	}

	metadata = DBG_MEM_DATA(ptr);
	if (metadata == nullptr) {
		DBG_MEM_ERR("no metadata: MEM_%s %s:%d(%p)", (op_idx == 2) ? "FREE" : "RELEASE", func, line, ptr);
	}
	else if (metadata->data == nullptr) {
		DBG_MEM_ERR("invalid metadata: MEM_%s %s:%d(%p)", (op_idx == 2) ? "FREE" : "RELEASE", func, line, ptr);
	}
	else if (metadata->data == OT_CAST_TYPEOF(metadata->data, metadata)) {
		DBG_MEM_ERR("unset metadata: MEM_%s %s:%d(%p)", (op_idx == 2) ? "FREE" : "RELEASE", func, line, ptr);
	}
	else if (metadata->magic != DBG_MEM_MAGIC) {
		DBG_MEM_ERR("invalid magic: MEM_%s %s:%d(%p) 0x%016" PRIu64, (op_idx == 2) ? "FREE" : "RELEASE", func, line, ptr, metadata->magic);
	}
	else if (metadata->data->used && (metadata->data->ptr == metadata)) {
		DBG_MEM_INFO(1, "MEM_%s: %s:%d(%p %zu)", (op_idx == 2) ? "FREE" : "RELEASE", func, line, ptr, metadata->data->size);

		metadata->data->used = 0;

		dbg_mem->size -= metadata->data->size;
		dbg_mem->op_cnt[op_idx]++;
	}
	else {
		flag_invalid = 1;
	}

	if ((rc = pthread_mutex_unlock(&(dbg_mem->mutex))) != 0) {
		DBG_MEM_ERR("cannot unlock mutex: %s", otc_strerror(rc));

		return;
	}

	if (flag_invalid) {
		DBG_MEM_ERR("invalid ptr: %s:%d(%p)", func, line, ptr);

		if (metadata != nullptr)
			for (i = 0; i < dbg_mem->count; i++)
				if (dbg_mem->data[i].ptr == metadata)
					DBG_MEM_ERR("possible previous use: %s %hhu", dbg_mem->data[i].func, dbg_mem->data[i].used);
	}
}


/***
 * NAME
 *   otc_dbg_malloc -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   size -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
void *otc_dbg_malloc(const char *func, int line, size_t size)
{
	void *retptr;

	retptr = malloc(DBG_MEM_SIZE(size));

	otc_dbg_mem_alloc(func, line, nullptr, retptr, size);

	return DBG_MEM_RETURN(retptr);
}


/***
 * NAME
 *   otc_dbg_calloc -
 *
 * ARGUMENTS
 *   func   -
 *   line   -
 *   nelem  -
 *   elsize -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
void *otc_dbg_calloc(const char *func, int line, size_t nelem, size_t elsize)
{
	void *retptr;

	retptr = malloc(DBG_MEM_SIZE(nelem * elsize));
	if (retptr != nullptr)
		(void)memset(retptr, 0, DBG_MEM_SIZE(nelem * elsize));

	otc_dbg_mem_alloc(func, line, nullptr, retptr, nelem * elsize);

	return DBG_MEM_RETURN(retptr);
}


/***
 * NAME
 *   otc_dbg_realloc -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   ptr  -
 *   size -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
void *otc_dbg_realloc(const char *func, int line, void *ptr, size_t size)
{
	void *retptr;

	if (ptr == nullptr) {
		retptr = malloc(DBG_MEM_SIZE(size));

		otc_dbg_mem_alloc(func, line, nullptr, retptr, size);
	} else {
		struct otc_dbg_mem_metadata *metadata = DBG_MEM_DATA(ptr);

		/*
		 * If memory is not allocated via these debug functions, it
		 * must not be reallocated via them either.
		 */
		if ((metadata == nullptr) || (metadata->data == nullptr) || (metadata->magic != DBG_MEM_MAGIC)) {
			retptr = realloc(ptr, size);

			return retptr;
		} else {
			retptr = realloc(DBG_MEM_DATA(ptr), DBG_MEM_SIZE(size));

			otc_dbg_mem_alloc(func, line, ptr, retptr, size);
		}
	}

	return DBG_MEM_RETURN(retptr);
}


/***
 * NAME
 *   otc_dbg_free -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   ptr  -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
void otc_dbg_free(const char *func, int line, void *ptr)
{
	struct otc_dbg_mem_metadata *metadata;

	otc_dbg_mem_release(func, line, ptr, 2);

	metadata = DBG_MEM_DATA(ptr);
	if ((metadata == nullptr) || (metadata->data == nullptr) || (metadata->magic != DBG_MEM_MAGIC))
		free(ptr);
	else
		free(DBG_MEM_DATA(ptr));
}


/***
 * NAME
 *   otc_dbg_strdup -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   s    -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
char *otc_dbg_strdup(const char *func, int line, const char *s)
{
	size_t  len = 0;
	char   *retptr = nullptr;

	if (s != nullptr) {
		len    = strlen(s) + 1;
		retptr = OT_CAST_TYPEOF(retptr, malloc(DBG_MEM_SIZE(len)));
		if (retptr != nullptr)
			(void)memcpy(DBG_MEM_PTR(retptr), s, len);
	}

	otc_dbg_mem_alloc(func, line, nullptr, retptr, len);

	return OT_CAST_TYPEOF(retptr, DBG_MEM_RETURN(retptr));
}


/***
 * NAME
 *   otc_dbg_strndup -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   s    -
 *   size -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
char *otc_dbg_strndup(const char *func, int line, const char *s, size_t size)
{
	size_t  len = 0;
	char   *retptr = nullptr;

	if (s != nullptr) {
		len    = strlen(s);
		len    = (len < size) ? len : size;
		retptr = OT_CAST_TYPEOF(retptr, malloc(DBG_MEM_SIZE(len + 1)));
		if (retptr != nullptr) {
			(void)memcpy(DBG_MEM_PTR(retptr), s, len);
			DBG_MEM_PTR(retptr)[len] = '\0';
		}
	}

	otc_dbg_mem_alloc(func, line, nullptr, retptr, len + 1);

	return OT_CAST_TYPEOF(retptr, DBG_MEM_RETURN(retptr));
}


/***
 * NAME
 *   otc_dbg_mem_init -
 *
 * ARGUMENTS
 *   mem   -
 *   data  -
 *   count -
 *   level -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
int otc_dbg_mem_init(struct otc_dbg_mem *mem, struct otc_dbg_mem_data *data, size_t count, uint8_t level)
{
	pthread_mutexattr_t attr;
	int                 rc, retval = -1;

	if ((mem == nullptr) || (data == nullptr) || (count == 0))
		return retval;

	(void)memset(mem, 0, sizeof(*mem));
	(void)memset(data, 0, sizeof(*data) * count);

	dbg_mem        = mem;
	dbg_mem->data  = data;
	dbg_mem->count = count;
	dbg_mem->level = level;

	if ((rc = pthread_mutexattr_init(&attr)) == 0)
		if ((rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) == 0)
			if ((rc = pthread_mutex_init(&(dbg_mem->mutex), &attr)) == 0)
				retval = 0;

	return retval;
}


/***
 * NAME
 *   otc_dbg_mem_disable -
 *
 * ARGUMENTS
 *   This function takes no arguments.
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
void otc_dbg_mem_disable(void)
{
	if (dbg_mem == nullptr)
		return;

	(void)pthread_mutex_destroy(&(dbg_mem->mutex));

	dbg_mem = nullptr;
}


/***
 * NAME
 *   otc_dbg_mem_info -
 *
 * ARGUMENTS
 *   This function takes no arguments.
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   This function does not return a value.
 */
void otc_dbg_mem_info(void)
{
#ifdef HAVE_MALLINFO
	struct mallinfo mi;
#endif
	size_t          i, n = 0;
	uint64_t        size = 0;

	if (dbg_mem == nullptr)
		return;

	DBG_MEM_INFO(0, "--- Memory info -------------------------------------");
	DBG_MEM_INFO(0, "  alloc/realloc: %" PRIu64 "/%" PRIu64 ", free/release: %" PRIu64 "/%" PRIu64, dbg_mem->op_cnt[0], dbg_mem->op_cnt[1], dbg_mem->op_cnt[2], dbg_mem->op_cnt[3]);
	DBG_MEM_INFO(0, "  unused: %zu, reused: %zu, count: %zu", dbg_mem->unused, dbg_mem->reused, dbg_mem->count);
	for (i = 0; i < dbg_mem->count; i++)
		if (dbg_mem->data[i].used) {
			DBG_MEM_INFO(0, "  %zu %s(%p %zu)", n, dbg_mem->data[i].func, dbg_mem->data[i].ptr, dbg_mem->data[i].size);

			size += dbg_mem->data[i].size;
			n++;
		}

	if (n > 0)
		DBG_MEM_INFO(0, "  allocated %zu byte(s) in %zu chunk(s)", size, n);

	if (dbg_mem->size != size)
		DBG_MEM_INFO(0, "  size does not match: %zu != %zu", dbg_mem->size, size);

#ifdef HAVE_MALLINFO
	mi = mallinfo();
	DBG_MEM_INFO(0, "--- Memory space usage ------------------------------");
	DBG_MEM_INFO(0, "  Total non-mmapped bytes:     %" PRI_MI, mi.arena);
	DBG_MEM_INFO(0, "  # of free chunks:            %" PRI_MI, mi.ordblks);
	DBG_MEM_INFO(0, "  # of free fastbin blocks:    %" PRI_MI, mi.smblks);
	DBG_MEM_INFO(0, "  Bytes in mapped regions:     %" PRI_MI, mi.hblkhd);
	DBG_MEM_INFO(0, "  # of mapped regions:         %" PRI_MI, mi.hblks);
	DBG_MEM_INFO(0, "  Max. total allocated space:  %" PRI_MI, mi.usmblks);
	DBG_MEM_INFO(0, "  Free bytes held in fastbins: %" PRI_MI, mi.fsmblks);
	DBG_MEM_INFO(0, "  Total allocated space:       %" PRI_MI, mi.uordblks);
	DBG_MEM_INFO(0, "  Total free space:            %" PRI_MI, mi.fordblks);
	DBG_MEM_INFO(0, "  Topmost releasable block:    %" PRI_MI, mi.keepcost);
#endif
}


/***
 * NAME
 *   otc_dbg_memdup -
 *
 * ARGUMENTS
 *   func -
 *   line -
 *   s    -
 *   size -
 *
 * DESCRIPTION
 *   -
 *
 * RETURN VALUE
 *   -
 */
void *otc_dbg_memdup(const char *func, int line, const void *s, size_t size)
{
	void *retptr = nullptr;

	if (s != nullptr) {
		retptr = malloc(DBG_MEM_SIZE(size + 1));
		if (retptr != nullptr) {
			(void)memcpy(DBG_MEM_PTR(retptr), s, size);
			DBG_MEM_PTR(retptr)[size] = '\0';
		}
	}

	otc_dbg_mem_alloc(func, line, nullptr, retptr, size);

	return DBG_MEM_RETURN(retptr);
}

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 *
 * vi: noexpandtab shiftwidth=8 tabstop=8
 */
