/*******************************************************************/
/*  sltdl: a surrogate ltdl implementation                         */
/*  Copyright (C) 2019 SysDeer Technologies, LLC                   */
/*  Released under the Standard MIT License; see COPYING.SLTDL.    */
/*******************************************************************/

#include <dlfcn.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <sltdl/sltdl.h>
#include "sltdl_core.h"

static const char * lt_dlerror_desc[] = {
	[SLTDL_ERR_OK]                              = 0,
	[SLTDL_ERR_SYSTEM_ERROR]                    = 0,
	[SLTDL_ERR_DLFCN_ERROR]                     = 0,
	[SLTDL_ERR_SLTDL_ERROR]                     = "sltdl internal error",
	[SLTDL_ERR_DLEXIT_REF_COUNT]                = "lt_dlexit() reference count already zero",
	[SLTDL_ERR_MODULE_REF_COUNT]                = "module reference count already zero",
	[SLTDL_ERR_MODULE_PTR_INVALID]              = "module handle invalid",
	[SLTDL_ERR_PATH_INVALID_FIRST_CHAR]         = "invalid path (does not begin with a forward slash)",
	[SLTDL_ERR_PATH_INVALID_SEPARATTOR_CHAR]    = "invalid path (separator character is not a colon)",
	[SLTDL_ERR_PATH_INVALID_MARK]               = "invalid path (mark not within range)",
	[SLTDL_ERR_PATH_INVALID_LEN]                = "invalid path (string too long)",
	[SLTDL_ERR_PATH_NO_ENTRY]                   = "invalid path (not found)",
	[SLTDL_ERR_CANNOT_OPEN]                     = "could not open module",
	[SLTDL_ERR_CANNOT_CLOSE]                    = "could not close module",
	[SLTDL_ERR_SYMBOL_NOT_FOUND]                = "symbol not found",
};

static int             lt_refs  = 0;
static int             lt_error = 0;
static int             lt_errno = 0;
static char *          lt_dlerr = 0;
static pthread_mutex_t lt_lock  = PTHREAD_MUTEX_INITIALIZER;

int lt_dlinit(void)
{
	if (pthread_mutex_lock(&lt_lock))
		return 1;

	lt_refs++;
	pthread_mutex_unlock(&lt_lock);

	return 0;
}

int lt_dlexit(void)
{
	if (pthread_mutex_lock(&lt_lock))
		return 1;

	if (!lt_refs) {
		lt_error = SLTDL_ERR_DLEXIT_REF_COUNT;
		pthread_mutex_unlock(&lt_lock);
		return 1;
	}

	lt_refs--;

	pthread_mutex_unlock(&lt_lock);

	return 0;
}

void lt_slock(void)
{
	int locked;

	do {
		locked = pthread_mutex_lock(&lt_lock);
	} while (locked);
}

int lt_sunlock(int ret, int error)
{
	if (error ==  0) {
		pthread_mutex_unlock(&lt_lock);
		return 0;
	}

	if ((error < 0) || (error >= SLTDL_ERR_CAP)) {
		error = SLTDL_ERR_SLTDL_ERROR;

	} else if (error == SLTDL_ERR_SYSTEM_ERROR) {
		lt_errno = errno;

	} else if (error == SLTDL_ERR_DLFCN_ERROR) {
		if (lt_dlerr)
			free(lt_dlerr);

		lt_dlerr = strdup(dlerror());
	}

	lt_error = error;
	pthread_mutex_unlock(&lt_lock);
	return ret;
}

const char * lt_dlerror(void)
{
	const char * errdesc;

	lt_slock();

	switch (lt_error) {
		case SLTDL_ERR_OK:
			errdesc = 0;
			break;

		case SLTDL_ERR_SYSTEM_ERROR:
			errdesc = strerror(lt_errno);
			break;

		case SLTDL_ERR_DLFCN_ERROR:
			errdesc = lt_dlerr;
			break;

		default:
			errdesc = lt_dlerror_desc[lt_error];
			break;
	}

	lt_error = 0;
	lt_sunlock(0,0);

	return errdesc;
}

int lt_dlseterror(int errcode)
{
	lt_slock();
	lt_sunlock(0,errcode);
	return 0;
}
