
#include "../deps/json/json.hpp"


using json = nlohmann::json;
#define PROXYJSON

#include <iostream>
#include <thread>
#include "btree_map.h"
#include "proxysql.h"

#include <random>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//#define PROXYSQL_EXTERN
#include "cpp.h"

#include "mysqld_error.h"

#include "ProxySQL_Statistics.hpp"
#include "MySQL_PreparedStatement.h"
#include "PgSQL_PreparedStatement.h"
#include "ProxySQL_Cluster.hpp"
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"

#ifdef PROXYSQLGENAI
#include "MCP_Thread.h"
#include "GenAI_Thread.h"
#include "AI_Features_Manager.h"
#endif /* PROXYSQLGENAI */

#include "SQLite3_Server.h"
#include "MySQL_Query_Processor.h"
#include "PgSQL_Query_Processor.h"
#include "MySQL_Authentication.hpp"
#include "PgSQL_Authentication.h"
#include "MySQL_LDAP_Authentication.hpp"
#include "MySQL_Query_Cache.h"
#include "PgSQL_Query_Cache.h"
#include "proxysql_restapi.h"
#include "Web_Interface.hpp"
#include "proxysql_utils.h"
#include "PgSQL_Monitor.hpp"

#include "libdaemon/dfork.h"
#include "libdaemon/dsignal.h"
#include "libdaemon/dlog.h"
#include "libdaemon/dpid.h"
#include "libdaemon/dexec.h"
#include "ev.h"

#include "curl/curl.h"

#include "openssl/x509v3.h"

#include <sys/mman.h>

#if defined(__APPLE__)
#include <mach-o/dyld.h>
#endif

#include <uuid/uuid.h>
#include <atomic>

#ifdef DEBUG
#include "proxy_protocol_info.h"
#endif // DEBUG


/*
extern "C" MySQL_LDAP_Authentication * create_MySQL_LDAP_Authentication_func() {
	return NULL;
}
*/


using std::map;
using std::string;
using std::vector;


void sleep_iter(unsigned int iter) {
	usleep(50*iter);
#ifdef RUNNING_ON_VALGRIND
	usleep((1000+rand()%1000)*iter);
#endif // RUNNING_ON_VALGRIND
}


volatile create_MySQL_LDAP_Authentication_t * create_MySQL_LDAP_Authentication = NULL;
void * __mysql_ldap_auth;

volatile create_Web_Interface_t * create_Web_Interface = NULL;
void * __web_interface;

std::thread* pgsql_monitor_thread = nullptr;

extern int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg);

char *binary_sha1 = NULL;

// MariaDB client library redefines dlerror(), see https://mariadb.atlassian.net/browse/CONC-101
#ifdef dlerror
#undef dlerror
#endif

static pthread_mutex_t *lockarray;
#include "openssl/crypto.h"


// this fuction will be called as a deatached thread
static void * waitpid_thread(void *arg) {
	pid_t *cpid_ptr=(pid_t *)arg;
	int status;
	set_thread_name("waitpid");
	waitpid(*cpid_ptr, &status, 0);
	free(cpid_ptr);
	return NULL;
}



struct MemoryStruct {
	char *memory;
	size_t size;
};


static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
	size_t realsize = size * nmemb;
	struct MemoryStruct *mem = (struct MemoryStruct *)userp;
	mem->memory = (char *)realloc(mem->memory, mem->size + realsize + 1);
	assert(mem->memory);
	memcpy(&(mem->memory[mem->size]), contents, realsize);
	mem->size += realsize;
	mem->memory[mem->size] = 0;
	return realsize;
}


char * know_latest_version = NULL;
static unsigned int randID = 0;

/**
 * @brief Checks for the latest version of ProxySQL by querying the specified URL.
 *
 * This function sends an HTTP GET request to the ProxySQL website to fetch the latest version information.
 * It sets a custom user-agent string containing the version, SHA1 hash of the binary (if available), and a random ID.
 * The response is stored in memory and returned as a character pointer.
 *
 * @return A character pointer containing the response data from the Proxysql website.
 *         If an error occurs during the HTTP request, NULL is returned.
 */
static char * main_check_latest_version() {
	CURL *curl_handle; // CURL handle for performing HTTP requests
	CURLcode res; // Variable to store CURL operation result
	struct MemoryStruct chunk; // // Struct to store memory chunk received from HTTP response

	// Initialize memory struct to store response data
	chunk.memory = (char *)malloc(1);
	chunk.size = 0;

	// Initialize CURL library
	curl_global_init(CURL_GLOBAL_ALL);
	// Initialize CURL handle for HTTP request
	curl_handle = curl_easy_init();
	// Set CURL options for the HTTP request
	curl_easy_setopt(curl_handle, CURLOPT_URL, "https://www.proxysql.com/latest");
	curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
	curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L);
	curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYSTATUS, 0L);
	curl_easy_setopt(curl_handle, CURLOPT_RANGE, "0-31");
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);

	// Set custom user-agent string including ProxySQL version, binary SHA1 hash, and a random ID
	string s = "proxysql-agent/";
	s += PROXYSQL_VERSION;
	if (binary_sha1) {
		s += " (" ;
			s+= binary_sha1;
		s += ")" ;
	}
	s += " " + std::to_string(randID);
	curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, s.c_str());

	// Set timeout and connect timeout for the HTTP request
	curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 10);
	curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 10);

	// Perform the HTTP request
	res = curl_easy_perform(curl_handle);

	// Check if the request was successful
	if (res != CURLE_OK) {
		switch (res) {
			// Handle common errors and free memory if necessary
			case CURLE_COULDNT_RESOLVE_HOST:
			case CURLE_COULDNT_CONNECT:
			case CURLE_OPERATION_TIMEDOUT:
				// These errors are expected in case of network issues or timeouts
				break;
			default:
				// Log other errors using proxy_error and free memory
				proxy_error("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
				break;
		}
		free(chunk.memory);
		chunk.memory = NULL;
	}

	// Cleanup CURL handle and global resources
	curl_easy_cleanup(curl_handle);
	curl_global_cleanup();

	// Return the response data from the ProxySQL website
	return chunk.memory;
}


/**
 * @brief Thread function to check for the latest version of ProxySQL asynchronously.
 *
 * This function is intended to be executed as a separate thread to asynchronously check for the latest version of ProxySQL.
 * It calls the main_check_latest_version function to fetch the latest version information from the ProxySQL website.
 * If the fetched version information is valid and different from the currently known latest version,
 * it updates the known latest version and logs a message indicating the availability of the new version.
 *
 * @param arg Pointer to the argument passed to the thread function (unused).
 * @return NULL.
 */
void * main_check_latest_version_thread(void *arg) {
	set_thread_name("CheckLatestVers");
	// Fetch the latest version information
	char * latest_version = main_check_latest_version();
	// we check for potential invalid data , see issue #4042
	// Check for potential invalid data and update the known latest version if a new version is detected
	if (latest_version != NULL && strlen(latest_version) < 32) {
		if (
			(know_latest_version == NULL) // first check
			|| (strcmp(know_latest_version,latest_version)) // new version detected
		) {
			// Free previously known latest version and update it with the new version
			if (know_latest_version)
				free(know_latest_version);
			// Duplicate latest version string
			know_latest_version = strdup(latest_version);
			// Log the availability of the new version
			proxy_info("Latest ProxySQL version available: %s\n", latest_version);
		}
	}
	free(latest_version);
	return NULL;
}





// Note: if you are running ProxySQL under gdb, you may consider setting this
// variable immediately to 1
// Example:
// set disable_watchdog=1
volatile int disable_watchdog = 0;

void parent_open_error_log() {
	if (GloVars.global.foreground==false) {
		int outfd=0;
		int errfd=0;
		outfd=open(GloVars.errorlog, O_WRONLY | O_APPEND | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
		if (outfd>0) {
			dup2(outfd, STDOUT_FILENO);
			close(outfd);
		} else {
			proxy_error("Impossible to open file\n");
		}
		errfd=open(GloVars.errorlog, O_WRONLY | O_APPEND | O_CREAT , S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
		if (errfd>0) {
			dup2(errfd, STDERR_FILENO);
			close(errfd);
		} else {
			proxy_error("Impossible to open file\n");
		}
	}
}


void parent_close_error_log() {
	if (GloVars.global.foreground==false) {
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
	}
}

pid_t pid;

static const char * proxysql_pid_file() {
	static char fn[512];
	snprintf(fn, sizeof(fn), "%s", daemon_pid_file_ident);
	return fn;
}


/*struct cpu_timer
{
	~cpu_timer()
	{
		auto end = std::clock() ;
		std::cerr << double( end - begin ) / CLOCKS_PER_SEC << " secs.\n" ;
	};
	const std::clock_t begin = std::clock() ;
};
*/
struct cpu_timer
{
	cpu_timer() {
		begin = monotonic_time();
	}
	~cpu_timer()
	{
		unsigned long long end = monotonic_time();
#ifdef DEBUG
		std::cerr << double( end - begin ) / 1000000 << " secs.\n" ;
#endif /* DEBUG */
		begin=end-begin; // here only to make compiler happy
	};
	unsigned long long begin;
};


static unsigned long thread_id(void) {
	unsigned long ret;
	ret = (unsigned long)pthread_self();
	return ret;
}

static void init_locks(void) {
	int i;
	lockarray = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
	for(i = 0; i<CRYPTO_num_locks(); i++) {
		pthread_mutex_init(&(lockarray[i]), NULL);
	}
	CRYPTO_set_id_callback((unsigned long (*)())thread_id);
	// deprecated
	//CRYPTO_set_locking_callback((void (*)(int, int, const char *, int))lock_callback);
}


static bool check_openssl_version() {
	unsigned long version = OpenSSL_version_num();
	const unsigned long OPENSSL_3_0_0 = 0x30000000L;
	const unsigned long OPENSSL_3_2_0 = 0x30200000L;

	proxy_info("Using OpenSSL version: %s\n", OpenSSL_version(OPENSSL_VERSION));
	if (version > OPENSSL_3_0_0 && version < OPENSSL_3_2_0) {
		proxy_warning(
			"Detected OpenSSL version has known performance regressions, please upgrade to '3.2.0' or later."
			" For further details, please refer to: https://github.com/openssl/openssl/issues/18814\n"
		);
	}
	if (version < OPENSSL_3_0_0) {
		proxy_error("%s\n", "ProxySQL server required openssl version 3.0.0 or above");
		return false;
	}
	return true;
}


void ProxySQL_Main_init_SSL_module() {
	int rc = SSL_library_init();
	if (rc==0) {
		proxy_error("%s\n", SSL_alert_desc_string_long(rc));
	}
	init_locks();
	SSL_METHOD *ssl_method;
	OpenSSL_add_all_algorithms();
	SSL_load_error_strings();
	//ssl_method = (SSL_METHOD *)TLSv1_server_method();
	//ssl_method = (SSL_METHOD *)SSLv23_server_method();
	ssl_method = (SSL_METHOD *)TLS_server_method();
	GloVars.global.ssl_ctx = SSL_CTX_new(ssl_method);
	if (GloVars.global.ssl_ctx==NULL)	{
		ERR_print_errors_fp(stderr);
		proxy_error("Unable to initialize SSL. Shutting down...\n");
		exit(EXIT_SUCCESS); // we exit gracefully to not be restarted
	}
	if (!SSL_CTX_set_min_proto_version(GloVars.global.ssl_ctx,TLS1_VERSION)) {
		proxy_error("Unable to initialize SSL. SSL_set_min_proto_version failed. Shutting down...\n");
		exit(EXIT_SUCCESS); // we exit gracefully to not be restarted
	}
	//SSL_CTX_set_options(GloVars.global.ssl_ctx, SSL_OP_NO_SSLv3); // no necessary, because of previous SSL_CTX_set_min_proto_version
#ifdef DEBUG
#if 0
	{
		STACK_OF(SSL_CIPHER) *ciphers;
		ciphers = SSL_CTX_get_ciphers(GloVars.global.ssl_ctx);
		fprintf(stderr,"List of cipher avaiable:\n");
		if (ciphers) {
			int num = sk_SSL_CIPHER_num(ciphers);
			char buf[130];
			for(int i = 0; i < num; i++){
				const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(ciphers, i);
				fprintf(stderr,"%s:  %s", SSL_CIPHER_get_name(cipher), SSL_CIPHER_description(cipher, buf, 128));
			}
		}
		fprintf(stderr,"\n");
	}
#endif // 0
#endif // DEBUG
	std::string msg = "";
	ProxySQL_create_or_load_TLS(true, msg);
}


/*
void example_listern() {
// few examples tests to demonstrate the ability to add and remove listeners at runtime
	GloMTH->listener_add((char *)"0.0.0.0:6033");
	sleep(3);
	GloMTH->listener_add((char *)"127.0.0.1:5033");
	sleep(3);
	GloMTH->listener_add((char *)"127.0.0.2:5033");
	sleep(3);
	GloMTH->listener_add((char *)"/tmp/proxysql.sock");
	for (int t=0; t<10; t++) {
		GloMTH->listener_add((char *)"127.0.0.1",7000+t);
		sleep(3);
	}

	GloMTH->listener_del((char *)"0.0.0.0:6033");
	sleep(3);
	GloMTH->listener_del((char *)"127.0.0.1:5033");
	sleep(3);
	GloMTH->listener_del((char *)"127.0.0.2:5033");
	sleep(3);
	GloMTH->listener_del((char *)"/tmp/proxysql.sock");
}
*/




void * __qc;
void * __mysql_thread;
void * __mysql_threads_handler;
void * __query_processor;
//void * __mysql_auth;



using namespace std;


//__cmd_proxysql_config_file=NULL;
#define MAX_EVENTS 100

std::atomic<int> load_;

//__thread l_sfp *__thr_sfp=NULL;
//#ifdef DEBUG
//const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay,junk:true,tcache:false";
//#else
//const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay";
#ifndef __FreeBSD__
const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,prof:true,prof_accum:true,prof_leak:true,lg_prof_sample:20,lg_prof_interval:30,prof_active:false";
#endif
//#endif /* DEBUG */
//const char *malloc_conf = "prof_leak:true,lg_prof_sample:0,prof_final:true,xmalloc:true,lg_tcache_max:16";

int listen_fd;
int socket_fd;


MySQL_Query_Cache *GloMyQC;
PgSQL_Query_Cache* GloPgQC;
MySQL_Authentication *GloMyAuth;
PgSQL_Authentication* GloPgAuth;
MySQL_LDAP_Authentication *GloMyLdapAuth;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Authentication *GloClickHouseAuth;
#endif /* PROXYSQLCLICKHOUSE */
MySQL_Query_Processor* GloMyQPro;
PgSQL_Query_Processor* GloPgQPro;
ProxySQL_Admin *GloAdmin;
MySQL_Threads_Handler *GloMTH = NULL;
PgSQL_Threads_Handler* GloPTH = NULL;

#ifdef PROXYSQLGENAI
MCP_Threads_Handler* GloMCPH = NULL;
GenAI_Threads_Handler* GloGATH = NULL;
AI_Features_Manager *GloAI = NULL;
#endif /* PROXYSQLGENAI */

Web_Interface *GloWebInterface;
MySQL_STMT_Manager_v14 *GloMyStmt;
PgSQL_STMT_Manager *GloPgStmt;

MySQL_Monitor *GloMyMon;
PgSQL_Monitor *GloPgMon;
std::thread *MyMon_thread = NULL;

MySQL_Logger *GloMyLogger;
PgSQL_Logger* GloPgSQL_Logger;
MySQL_Variables mysql_variables;
PgSQL_Variables pgsql_variables;
SQLite3_Server *GloSQLite3Server;
#ifdef PROXYSQLCLICKHOUSE
ClickHouse_Server *GloClickHouseServer;
#endif /* PROXYSQLCLICKHOUSE */


ProxySQL_Cluster *GloProxyCluster = NULL;

ProxySQL_Statistics *GloProxyStats = NULL;

void * mysql_worker_thread_func(void *arg) {

//	__thr_sfp=l_mem_init();

	pthread_attr_t thread_attr;
	size_t tmp_stack_size=0;
	if (!pthread_attr_init(&thread_attr)) {
		if (!pthread_attr_getstacksize(&thread_attr , &tmp_stack_size )) {
			__sync_fetch_and_add(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
		}
	}

	proxysql_mysql_thread_t *mysql_thread=(proxysql_mysql_thread_t *)arg;
	MySQL_Thread *worker = new MySQL_Thread();
	mysql_thread->worker=worker;
	worker->init();
//	worker->poll_listener_add(listen_fd);
//	worker->poll_listener_add(socket_fd);
	load_ -= 1;
	unsigned int iter = 0;
	do { sleep_iter(++iter); } while (load_);

	worker->run();
	//delete worker;
	delete worker;
	mysql_thread->worker=NULL;
//	l_mem_destroy(__thr_sfp);
	__sync_fetch_and_sub(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
	return NULL;
}

#ifdef IDLE_THREADS
void * mysql_worker_thread_func_idles(void *arg) {

	pthread_attr_t thread_attr;
	size_t tmp_stack_size=0;
	if (!pthread_attr_init(&thread_attr)) {
		if (!pthread_attr_getstacksize(&thread_attr , &tmp_stack_size )) {
			__sync_fetch_and_add(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);
		}
	}

//	__thr_sfp=l_mem_init();
	proxysql_mysql_thread_t *mysql_thread=(proxysql_mysql_thread_t *)arg;
	MySQL_Thread *worker = new MySQL_Thread();
	mysql_thread->worker=worker;
	worker->epoll_thread=true;
	worker->init();
//	worker->poll_listener_add(listen_fd);
//	worker->poll_listener_add(socket_fd);
	load_ -= 1;
	unsigned int iter = 0;
	do { sleep_iter(++iter); } while (load_);

	worker->run();
	//delete worker;
	delete worker;
//	l_mem_destroy(__thr_sfp);

	__sync_fetch_and_sub(&GloVars.statuses.stack_memory_mysql_threads,tmp_stack_size);

	return NULL;
}
#endif // IDLE_THREADS

void* pgsql_worker_thread_func(void* arg) {

	//	__thr_sfp=l_mem_init();

	pthread_attr_t thread_attr;
	size_t tmp_stack_size = 0;
	if (!pthread_attr_init(&thread_attr)) {
		if (!pthread_attr_getstacksize(&thread_attr, &tmp_stack_size)) {
			__sync_fetch_and_add(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
		}
	}

	proxysql_pgsql_thread_t* pgsql_thread = (proxysql_pgsql_thread_t*)arg;
	PgSQL_Thread* worker = new PgSQL_Thread();
	pgsql_thread->worker = worker;
	worker->init();
	//	worker->poll_listener_add(listen_fd);
	//	worker->poll_listener_add(socket_fd);
	load_ -= 1;
	unsigned int iter = 0;
	do { sleep_iter(++iter); } while (load_);

	worker->run();
	//delete worker;
	delete worker;
	pgsql_thread->worker = NULL;
	//	l_mem_destroy(__thr_sfp);
	__sync_fetch_and_sub(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
	return NULL;
}

#ifdef IDLE_THREADS
void* pgsql_worker_thread_func_idles(void* arg) {

	pthread_attr_t thread_attr;
	size_t tmp_stack_size = 0;
	if (!pthread_attr_init(&thread_attr)) {
		if (!pthread_attr_getstacksize(&thread_attr, &tmp_stack_size)) {
			__sync_fetch_and_add(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);
		}
	}

	//	__thr_sfp=l_mem_init();
	proxysql_pgsql_thread_t* pgsql_thread = (proxysql_pgsql_thread_t*)arg;
	PgSQL_Thread* worker = new PgSQL_Thread();
	pgsql_thread->worker = worker;
	worker->epoll_thread = true;
	worker->init();
	//	worker->poll_listener_add(listen_fd);
	//	worker->poll_listener_add(socket_fd);
	load_ -= 1;
	unsigned int iter = 0;
	do { sleep_iter(++iter); } while (load_);

	worker->run();
	//delete worker;
	delete worker;
	//	l_mem_destroy(__thr_sfp);

	__sync_fetch_and_sub(&GloVars.statuses.stack_memory_pgsql_threads, tmp_stack_size);

	return NULL;
}
#endif // IDLE_THREADS

void* unified_query_cache_purge_thread(void *arg) {

	set_thread_name("QCPurgeThread");

	MySQL_Thread* mysql_thr = new MySQL_Thread();
	unsigned int MySQL_Monitor__thread_MySQL_Thread_Variables_version;
	MySQL_Monitor__thread_MySQL_Thread_Variables_version = GloMTH->get_global_version();
	mysql_thr->refresh_variables();
	uint64_t mysql_max_memory_size = static_cast<uint64_t>(mysql_thread___query_cache_size_MB * 1024ULL * 1024ULL);

	PgSQL_Thread* pgsql_thr = new PgSQL_Thread();
	unsigned int PgSQL_Monitor__thread_PgSQL_Thread_Variables_version;
	PgSQL_Monitor__thread_PgSQL_Thread_Variables_version = GloPTH->get_global_version();
	pgsql_thr->refresh_variables();
	uint64_t pgsql_max_memory_size = static_cast<uint64_t>(pgsql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
	
	// Both MySQL and PgSQL query caches use the same shutting_down value
	while (GloMyQC->shutting_down == false && GloPgQC->shutting_down == false) {

		// Both MySQL and PgSQL query caches share the same purge_loop_time value.
		// Therefore, using either purge_loop_time will have no impact on the behavior.
		usleep(GloMyQC->purge_loop_time);

		const unsigned int mysql_glover = GloMTH->get_global_version();
		if (MySQL_Monitor__thread_MySQL_Thread_Variables_version < mysql_glover) {
			MySQL_Monitor__thread_MySQL_Thread_Variables_version = mysql_glover;
			mysql_thr->refresh_variables();
			mysql_max_memory_size = static_cast<uint64_t>(mysql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
		}
		GloMyQC->purgeHash(mysql_max_memory_size);
		
		const unsigned int pgsql_glover = GloPTH->get_global_version();
		if (PgSQL_Monitor__thread_PgSQL_Thread_Variables_version < pgsql_glover) {
			PgSQL_Monitor__thread_PgSQL_Thread_Variables_version = pgsql_glover;
			pgsql_thr->refresh_variables();
			pgsql_max_memory_size = static_cast<uint64_t>(pgsql_thread___query_cache_size_MB * 1024ULL * 1024ULL);
		}
		GloPgQC->purgeHash(pgsql_max_memory_size);
	}

	delete mysql_thr;
	delete pgsql_thr;
	return NULL;
}

/*void* pgsql_shared_query_cache_funct(void* arg) {
	GloPgQC->purgeHash_thread(NULL);
	return NULL;
}*/

void ProxySQL_Main_process_global_variables(int argc, const char **argv) {
	GloVars.errorlog = NULL;
	GloVars.pid = NULL;
	GloVars.parse(argc,argv);
	GloVars.process_opts_pre();
	GloVars.restart_on_missing_heartbeats = 10; // default
	// alwasy try to open a config file
	if (GloVars.confFile->OpenFile(GloVars.config_file) == true) {
		GloVars.configfile_open=true;
		proxy_info("Using config file %s\n", GloVars.config_file);
		const Setting& root = GloVars.confFile->cfg.getRoot();
		if (root.exists("restart_on_missing_heartbeats")==true) {
			// restart_on_missing_heartbeats datadir from config file
			int restart_on_missing_heartbeats;
			bool rc;
			rc=root.lookupValue("restart_on_missing_heartbeats", restart_on_missing_heartbeats);
			if (rc==true) {
				GloVars.restart_on_missing_heartbeats=restart_on_missing_heartbeats;
			}
		}
		if (root.exists("execute_on_exit_failure")==true) {
			// restart_on_missing_heartbeats datadir from config file
			string execute_on_exit_failure;
			bool rc;
			rc=root.lookupValue("execute_on_exit_failure", execute_on_exit_failure);
			if (rc==true) {
				GloVars.execute_on_exit_failure=strdup(execute_on_exit_failure.c_str());
			}
		}
		if (root.exists("errorlog")==true) {
			// restart_on_missing_heartbeats datadir from config file
			string errorlog_path;
			bool rc;
			rc=root.lookupValue("errorlog", errorlog_path);
			if (rc==true) {
				GloVars.errorlog = strdup(errorlog_path.c_str());
			}
		}
		if (root.exists("uuid")==true) {
			string uuid;
			bool rc;
			rc=root.lookupValue("uuid", uuid);
			if (rc==true) {
				uuid_t uu;
				if (uuid_parse(uuid.c_str(), uu)==0) {
					if (GloVars.uuid == NULL) {
						// it is not set yet, that means it wasn't specified on the cmdline
						GloVars.uuid = strdup(uuid.c_str());
					}
				} else {
					proxy_error("The config file is configured with an invalid UUID: %s\n", uuid.c_str());
				}
			}
		}
		// if cluster_sync_interfaces is true, interfaces variables are synced too
		if (root.exists("cluster_sync_interfaces")==true) {
			bool value_bool;
			bool rc;
			rc=root.lookupValue("cluster_sync_interfaces", value_bool);
			if (rc==true) {
				GloVars.cluster_sync_interfaces=value_bool;
			} else {
				proxy_error("The config file is configured with an invalid cluster_sync_interfaces\n");
			}
		}
		if (root.exists("set_thread_name")==true) {
			bool value_bool;
			bool rc;
			rc=root.lookupValue("set_thread_name", value_bool);
			if (rc==true) {
				GloVars.set_thread_name=value_bool;
			} else {
				proxy_error("The config file is configured with an invalid set_thread_name\n");
			}
		}
		if (root.exists("pidfile")==true) {
			string pidfile_path;
			bool rc;
			rc=root.lookupValue("pidfile", pidfile_path);
			if (rc==true) {
				GloVars.pid = strdup(pidfile_path.c_str());
      }
    }
		if (root.exists("sqlite3_plugin")==true) {
			string sqlite3_plugin;
			bool rc;
			rc=root.lookupValue("sqlite3_plugin", sqlite3_plugin);
			if (rc==true) {
				GloVars.sqlite3_plugin=strdup(sqlite3_plugin.c_str());
			}
		}
		if (root.exists("web_interface_plugin")==true) {
			string web_interface_plugin;
			bool rc;
			rc=root.lookupValue("web_interface_plugin", web_interface_plugin);
			if (rc==true) {
				GloVars.web_interface_plugin=strdup(web_interface_plugin.c_str());
			}
		}
		if (root.exists("ldap_auth_plugin")==true) {
			string ldap_auth_plugin;
			bool rc;
			rc=root.lookupValue("ldap_auth_plugin", ldap_auth_plugin);
			if (rc==true) {
				GloVars.ldap_auth_plugin=strdup(ldap_auth_plugin.c_str());
			}
		}
		const map<string, char**> varnames_globals_map {
			{ "mysql-ssl_p2s_ca", &GloVars.global.gr_bootstrap_ssl_ca },
			{ "mysql-ssl_p2s_capath", &GloVars.global.gr_bootstrap_ssl_capath },
			{ "mysql-ssl_p2s_cert", &GloVars.global.gr_bootstrap_ssl_cert },
			{ "mysql-ssl_p2s_key", &GloVars.global.gr_bootstrap_ssl_key },
			{ "mysql-ssl_p2s_cipher", &GloVars.global.gr_bootstrap_ssl_cipher },
			{ "mysql-ssl_p2s_crl", &GloVars.global.gr_bootstrap_ssl_crl },
			{ "mysql-ssl_p2s_crlpath", &GloVars.global.gr_bootstrap_ssl_crlpath }
		};
		// Command line options always take precedence
		if (GloVars.global.gr_bootstrap_mode && root.exists("mysql_variables")) {
			const Setting& mysql_vars = root["mysql_variables"];

			for (const pair<const string,char**>& name_global : varnames_globals_map) {
				for (const auto& setting_it : mysql_vars) {
					if (*name_global.second == nullptr) {
						if (setting_it.getName() == name_global.first && setting_it.isString()) {
							const char* setting_val = setting_it.c_str();
							*name_global.second = strdup(setting_val);
						}
					}
				}
			}
		}
	} else {
		proxy_warning("Unable to open config file %s\n", GloVars.config_file); // issue #705
		if (GloVars.__cmd_proxysql_config_file) {
			proxy_error("Unable to open config file %s specified in the command line. Aborting!\n", GloVars.config_file);
			exit(EXIT_SUCCESS); // we exit gracefully to avoid restart
		}
	}
	char *t=getcwd(NULL, 512);
	if (GloVars.__cmd_proxysql_datadir==NULL) {
		// datadir was not specified , try to read config file
		if (GloVars.configfile_open==true) {
			const Setting& root = GloVars.confFile->cfg.getRoot();
			if (root.exists("datadir")==true) {
				// reading datadir from config file
				std::string datadir;
				bool rc;
				rc=root.lookupValue("datadir", datadir);
				if (rc==true) {
					GloVars.datadir=strdup(datadir.c_str());
				} else {
					GloVars.datadir=strdup(t);
				}
			} else {
				// datadir was not specified in config file
				GloVars.datadir=strdup(t);
			}
			if (root.exists("restart_on_missing_heartbeats")==true) {
				// restart_on_missing_heartbeats datadir from config file
				int restart_on_missing_heartbeats;
				bool rc;
				rc=root.lookupValue("restart_on_missing_heartbeats", restart_on_missing_heartbeats);
				if (rc==true) {
					GloVars.restart_on_missing_heartbeats=restart_on_missing_heartbeats;
				} else {
					GloVars.restart_on_missing_heartbeats = 10; // default
				}
			} else {
				// restart_on_missing_heartbeats was not specified in config file
				GloVars.restart_on_missing_heartbeats = 10; // default
			}
		} else {
			// config file not readable
			GloVars.datadir=strdup(t);
			std::cerr << "[Warning]: Cannot open any default config file . Using default datadir in current working directory " << GloVars.datadir << endl;
		}
	} else {
		GloVars.datadir=GloVars.__cmd_proxysql_datadir;
	}
	free(t);

	GloVars.admindb=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.db")+2);
	sprintf(GloVars.admindb,"%s/%s",GloVars.datadir, (char *)"proxysql.db");

	GloVars.sqlite3serverdb=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"sqlite3server.db")+2);
	sprintf(GloVars.sqlite3serverdb,"%s/%s",GloVars.datadir, (char *)"sqlite3server.db");

	GloVars.statsdb_disk=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql_stats.db")+2);
	sprintf(GloVars.statsdb_disk,"%s/%s",GloVars.datadir, (char *)"proxysql_stats.db");

	if (GloVars.errorlog == NULL) {
		GloVars.errorlog=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.log")+2);
		sprintf(GloVars.errorlog,"%s/%s",GloVars.datadir, (char *)"proxysql.log");
	}

	if (GloVars.pid == NULL) {
		GloVars.pid=(char *)malloc(strlen(GloVars.datadir)+strlen((char *)"proxysql.pid")+2);
		sprintf(GloVars.pid,"%s/%s",GloVars.datadir, (char *)"proxysql.pid");
	}

	if (GloVars.__cmd_proxysql_initial==true) {
		std::cerr << "Renaming database file " << GloVars.admindb << endl;
		char *newpath=(char *)malloc(strlen(GloVars.admindb)+8);
		sprintf(newpath,"%s.bak",GloVars.admindb);
		rename(GloVars.admindb,newpath);	// FIXME: should we check return value, or ignore whatever it successed or not?
	}

	GloVars.confFile->ReadGlobals();
	GloVars.process_opts_post();
}

void ProxySQL_Main_init_main_modules() {
	GloMyQC=NULL;
	GloPgQC=NULL;
	GloMyQPro=NULL;
	GloMTH=NULL;
	GloMyAuth=NULL;
	GloPgAuth=NULL;
	GloPTH=NULL;
#ifdef PROXYSQLGENAI
	GloMCPH=new MCP_Threads_Handler();
	GloGATH=new GenAI_Threads_Handler();
	GloAI=NULL;
#endif /* PROXYSQLGENAI */
#ifdef PROXYSQLCLICKHOUSE
	GloClickHouseAuth=NULL;
#endif /* PROXYSQLCLICKHOUSE */
	GloMyMon=NULL;
	GloMyLogger=NULL;
	GloPgSQL_Logger = NULL;
	GloMyStmt=NULL;
	GloPgStmt = NULL;
	// initialize libev
	if (!ev_default_loop (EVBACKEND_POLL | EVFLAG_NOENV)) {
		fprintf(stderr,"could not initialise libev");
		exit(EXIT_FAILURE);
	}

	MyHGM=new MySQL_HostGroups_Manager();
	MyHGM->init();

	MySQL_Threads_Handler * _tmp_GloMTH = NULL;
	_tmp_GloMTH=new MySQL_Threads_Handler();
	GloMTH = _tmp_GloMTH;
	GloMyLogger = new MySQL_Logger();
	GloMyLogger->print_version();
	GloPgSQL_Logger = new PgSQL_Logger();
	GloPgSQL_Logger->print_version();
	GloMyStmt=new MySQL_STMT_Manager_v14();
	GloPgStmt=new PgSQL_STMT_Manager();
	PgHGM = new PgSQL_HostGroups_Manager();
	PgHGM->init();
	PgSQL_Threads_Handler* _tmp_GloPTH = NULL;
	_tmp_GloPTH = new PgSQL_Threads_Handler();
	GloPTH = _tmp_GloPTH;
}

#ifdef PROXYSQLGENAI
void ProxySQL_Main_init_GenAI_module() {
	GloGATH->init();
	proxy_info("GenAI Threads Handler initialized\n");
	GloAI = new AI_Features_Manager();
	GloAI->init();
	proxy_info("AI Features module initialized\n");
}

void ProxySQL_Main_init_MCP_module() {
	GloMCPH->init();
	proxy_info("MCP module initialized\n");
}
#endif /* PROXYSQLGENAI */

void ProxySQL_Main_init_Admin_module(const bootstrap_info_t& bootstrap_info) {
	// cluster module needs to be initialized before
	GloProxyCluster = new ProxySQL_Cluster();
	GloProxyCluster->init();
	GloProxyCluster->print_version();
	GloProxyStats = new ProxySQL_Statistics();
	//GloProxyStats->init();
	GloProxyStats->print_version();
	GloAdmin = new ProxySQL_Admin();
	GloAdmin->init(bootstrap_info);
	GloAdmin->print_version();
	if (binary_sha1) {
		proxy_info("ProxySQL SHA1 checksum: %s\n", binary_sha1);
	}
}

void ProxySQL_Main_init_Auth_module() {
	GloMyAuth = new MySQL_Authentication();
	GloMyAuth->print_version();
	GloPgAuth = new PgSQL_Authentication();
	GloPgAuth->print_version();
	GloAdmin->init_users();
	GloAdmin->init_pgsql_users();
	//GloMyLdapAuth = create_MySQL_LDAP_Authentication();
	if (GloMyLdapAuth) {
		GloMyLdapAuth->print_version();
	}
}

void ProxySQL_Main_init_Query_module() {
	GloMyQPro = new MySQL_Query_Processor();
	GloMyQPro->print_version();
	GloPgQPro = new PgSQL_Query_Processor();
	GloPgQPro->print_version();
	GloAdmin->init_mysql_query_rules();
	GloAdmin->init_mysql_firewall();
	GloAdmin->init_pgsql_query_rules();
	GloAdmin->init_pgsql_firewall();
//	if (GloWebInterface) {
//		GloWebInterface->print_version();
//	}
}

void ProxySQL_Main_init_MySQL_Threads_Handler_module() {
	unsigned int i;
	GloMTH->init();
	load_ = 1;
	load_ += GloMTH->num_threads;
#ifdef IDLE_THREADS
	if (GloVars.global.idle_threads) {
		load_ += GloMTH->num_threads;
	} else {
		proxy_warning("proxysql instance running without --idle-threads : most workloads benefit from this option\n");
		proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n");
	}
#endif // IDLE_THREADS
	for (i=0; i<GloMTH->num_threads; i++) {
		GloMTH->create_thread(i,mysql_worker_thread_func, false);
#ifdef IDLE_THREADS
		if (GloVars.global.idle_threads) {
			GloMTH->create_thread(i,mysql_worker_thread_func_idles, true);
		}
#endif // IDLE_THREADS
	}
}

void ProxySQL_Main_init_PgSQL_Threads_Handler_module() {
	unsigned int i;
	GloPTH->init();
	//load_ = 1;
	load_ += GloPTH->num_threads;
#ifdef IDLE_THREADS
	if (GloVars.global.idle_threads) {
		load_ += GloPTH->num_threads;
	}
	else {
		proxy_warning("proxysql instance running without --idle-threads : most workloads benefit from this option\n");
		proxy_warning("proxysql instance running without --idle-threads : enabling it can potentially improve performance\n");
	}
#endif // IDLE_THREADS
	for (i = 0; i < GloPTH->num_threads; i++) {
		GloPTH->create_thread(i, pgsql_worker_thread_func, false);
#ifdef IDLE_THREADS
		if (GloVars.global.idle_threads) {
			GloPTH->create_thread(i, pgsql_worker_thread_func_idles, true);
		}
#endif // IDLE_THREADS
	}
}

void ProxySQL_Main_init_Query_Cache_module() {
	GloMyQC = new MySQL_Query_Cache();
	GloMyQC->print_version();
	GloPgQC = new PgSQL_Query_Cache();
	GloPgQC->print_version();

	pthread_t purge_thread_id;
	pthread_create(&purge_thread_id, NULL, unified_query_cache_purge_thread, NULL);
	GloMyQC->purge_thread_id = purge_thread_id;
	GloPgQC->purge_thread_id = purge_thread_id;
}

void ProxySQL_Main_init_MySQL_Monitor_module() {
	// start MySQL_Monitor
//	GloMyMon = new MySQL_Monitor();
	if (MyMon_thread == NULL) { // only if not created yet
		MyMon_thread = new std::thread(&MySQL_Monitor::run,GloMyMon);
		GloMyMon->print_version();
	}
}


void ProxySQL_Main_init_SQLite3Server() {
	// start SQLite3Server
	GloSQLite3Server = new SQLite3_Server();
	GloSQLite3Server->init();
	// NOTE: Always perform the 'load_*_to_runtime' after module start, otherwise values won't be properly
	// loaded from disk at ProxySQL startup.
	GloAdmin->load_sqliteserver_variables_to_runtime();
	GloAdmin->init_sqliteserver_variables();
	GloSQLite3Server->print_version();
}
#ifdef PROXYSQLCLICKHOUSE
void ProxySQL_Main_init_ClickHouseServer() {
	// start SQServer
	GloClickHouseServer = new ClickHouse_Server();
	GloClickHouseServer->init();
	// NOTE: Always perform the 'load_*_to_runtime' after module start, otherwise values won't be properly
	// loaded from disk at ProxySQL startup.
	GloAdmin->load_clickhouse_variables_to_runtime();
	GloAdmin->init_clickhouse_variables();
	GloClickHouseServer->print_version();
	GloClickHouseAuth = new ClickHouse_Authentication();
	GloClickHouseAuth->print_version();
	GloAdmin->init_clickhouse_users();
}
#endif /* PROXYSQLCLICKHOUSE */

void ProxySQL_Main_join_all_threads() {
	cpu_timer t;
	if (GloMTH) {
		cpu_timer t;
		GloMTH->shutdown_threads();
#ifdef DEBUG
		std::cerr << "GloMTH joined in ";
#endif
	}
	if (GloPTH) {
		cpu_timer t;
		GloPTH->shutdown_threads();
#ifdef DEBUG
		std::cerr << "GloPTH joined in ";
#endif
	}
	if (GloMyQC) {
		GloMyQC->shutting_down=true;
	}
	if (GloPgQC) {
		GloPgQC->shutting_down=true;
	}
	if (GloMyMon) {
		GloMyMon->shutdown=true;
	}
	if (GloPgMon) {
		GloPgMon->shutdown=true;
	}
	// join GloMyMon thread
	if (GloMyMon && MyMon_thread) {
		cpu_timer t;
		MyMon_thread->join();
		delete MyMon_thread;
		MyMon_thread = NULL;
#ifdef DEBUG
		std::cerr << "GloMyMon joined in ";
#endif
	}
	if (GloPgMon && pgsql_monitor_thread) {
		cpu_timer t;
		pgsql_monitor_thread->join();
		delete pgsql_monitor_thread;
		pgsql_monitor_thread = NULL;
#ifdef DEBUG
		std::cerr << "GloPgMon joined in ";
#endif
	}
	/* Unified QC Purge Thread for both MySQL and PgSQL query cache
	// join GloMyQC thread
	if (GloMyQC) {
		cpu_timer t;
		pthread_join(GloMyQC->purge_thread_id, NULL);
#ifdef DEBUG
		std::cerr << "GloMyQC joined in ";
#endif
	}
	// join GloPgQC thread
	if (GloPgQC) {
		cpu_timer t;
		pthread_join(GloPgQC->purge_thread_id, NULL);
#ifdef DEBUG
		std::cerr << "GloPgQC joined in ";
#endif
	}*/
	if (GloMyQC || GloPgQC) {
		cpu_timer t;
		// The purge_thread_id is shared by both MySQL and PgSQL.
		// use either one to join the thread.
		pthread_join(GloMyQC->purge_thread_id, NULL);
#ifdef DEBUG
		std::cerr << "GloMyQC and GloPgQC joined in ";
#endif
	}

#ifdef DEBUG
	std::cerr << "All threads joined in ";
#endif
}

void ProxySQL_Main_shutdown_all_modules() {
	if (GloMyMon) {
		cpu_timer t;
		delete GloMyMon;
		GloMyMon=NULL;
#ifdef DEBUG
		std::cerr << "GloMyMon shutdown in ";
#endif
	}
	if (GloPgMon) {
		cpu_timer t;
		delete GloPgMon;
		GloPgMon=NULL;
#ifdef DEBUG
		std::cerr << "GloPgMon shutdown in ";
#endif
	}
	if (GloMyQC) {
		cpu_timer t;
		delete GloMyQC;
		GloMyQC=NULL;
#ifdef DEBUG
		std::cerr << "GloMyQC shutdown in ";
#endif
	}
	if (GloPgQC) {
		cpu_timer t;
		delete GloPgQC;
		GloPgQC=NULL;
#ifdef DEBUG
		std::cerr << "GloPgQC shutdown in ";
#endif
	}
	if (GloMyQPro) {
		cpu_timer t;
		delete GloMyQPro;
		GloMyQPro=NULL;
#ifdef DEBUG
		std::cerr << "GloMyQPro shutdown in ";
#endif
	}
	if (GloPgQPro) {
		cpu_timer t;
		delete GloPgQPro;
		GloPgQPro=NULL;
#ifdef DEBUG
		std::cerr << "GloPgQPro shutdown in ";
#endif
	}
#ifdef PROXYSQLCLICKHOUSE
	if (GloClickHouseAuth) {
		cpu_timer t;
		delete GloClickHouseAuth;
		GloClickHouseAuth=NULL;
#ifdef DEBUG
		std::cerr << "GloClickHouseAuth shutdown in ";
#endif
	}
	if (GloClickHouseServer) {
		cpu_timer t;
		delete GloClickHouseServer;
		GloClickHouseServer=NULL;
#ifdef DEBUG
		std::cerr << "GloClickHouseServer shutdown in ";
#endif
	}
#endif /* PROXYSQLCLICKHOUSE */
	if (GloSQLite3Server) {
		cpu_timer t;
		delete GloSQLite3Server;
		GloSQLite3Server=NULL;
#ifdef DEBUG
		std::cerr << "GloSQLite3Server shutdown in ";
#endif
	}
	if (GloMyAuth) {
		cpu_timer t;
		delete GloMyAuth;
		GloMyAuth=NULL;
#ifdef DEBUG
		std::cerr << "GloMyAuth shutdown in ";
#endif
	}
	if (GloPgAuth) {
		cpu_timer t;
		delete GloPgAuth;
		GloPgAuth = NULL;
#ifdef DEBUG
		std::cerr << "GloPgAuth shutdown in ";
#endif
	}
	if (GloMTH) {
		cpu_timer t;
		pthread_mutex_lock(&GloVars.global.ext_glomth_mutex);
		delete GloMTH;
		GloMTH=NULL;
		pthread_mutex_unlock(&GloVars.global.ext_glomth_mutex);
#ifdef DEBUG
		std::cerr << "GloMTH shutdown in ";
#endif
	}
	if (GloPTH) {
		cpu_timer t;
		pthread_mutex_lock(&GloVars.global.ext_glopth_mutex);
		delete GloPTH;
		GloPTH = NULL;
		pthread_mutex_unlock(&GloVars.global.ext_glopth_mutex);
#ifdef DEBUG
		std::cerr << "GloPTH shutdown in ";
#endif
	}
#ifdef PROXYSQLGENAI
	if (GloMCPH) {
		cpu_timer t;
		delete GloMCPH;
		GloMCPH = NULL;
#ifdef DEBUG
		std::cerr << "GloMCPH shutdown in ";
#endif
	}
	if (GloGATH) {
		cpu_timer t;
		delete GloGATH;
		GloGATH = NULL;
#ifdef DEBUG
		std::cerr << "GloGATH shutdown in ";
#endif
	}
	if (GloAI) {
		cpu_timer t;
		delete GloAI;
		GloAI = NULL;
#ifdef DEBUG
		std::cerr << "GloAI shutdown in ";
#endif
	}
#endif /* PROXYSQLGENAI */
	if (GloMyLogger) {
		cpu_timer t;
		delete GloMyLogger;
		GloMyLogger=NULL;
#ifdef DEBUG
		std::cerr << "GloMyLogger shutdown in ";
#endif
	}
	if (GloPgSQL_Logger) {
		cpu_timer t;
		delete GloPgSQL_Logger;
		GloPgSQL_Logger = NULL;
#ifdef DEBUG
		std::cerr << "GloPgSQL_Logger shutdown in ";
#endif
	}

	{
#ifdef TEST_WITHASAN
		pthread_mutex_lock(&GloAdmin->sql_query_global_mutex);
#endif
		cpu_timer t;
		delete GloAdmin;
#ifdef DEBUG
		std::cerr << "GloAdmin shutdown in ";
#endif
	}
	if (MyHGM)
	{
		cpu_timer t;
		MyHGM->shutdown();
		delete MyHGM;
#ifdef DEBUG
		std::cerr << "GloMyHGM shutdown in ";
#endif
	}
	if (PgHGM)
	{
		cpu_timer t;
		PgHGM->shutdown();
		delete PgHGM;
#ifdef DEBUG
		std::cerr << "GloPgHGM shutdown in ";
#endif
	}
	if (GloMyStmt) {
		delete GloMyStmt;
		GloMyStmt=NULL;
	}
	if (GloPgStmt) {
		delete GloPgStmt;
		GloPgStmt = NULL;
	}
}

void ProxySQL_Main_init() {
#ifdef DEBUG
	GloVars.global.gdbg=false;
	glovars.has_debug=true;
#else
	glovars.has_debug=false;
#endif /* DEBUG */
//	__thr_sfp=l_mem_init();
	proxysql_init_debug_prometheus_metrics();
}





static void LoadPlugins() {
	GloMyLdapAuth = NULL;
	if (proxy_sqlite3_open_v2 == nullptr) {
		SQLite3DB::LoadPlugin(GloVars.sqlite3_plugin);
	} else {
		int i= (*proxy_sqlite3_config)(SQLITE_CONFIG_URI, 1);

		if (i != SQLITE_OK) {
			fprintf(stderr,"SQLITE: Error on (*proxy_sqlite3_config)(SQLITE_CONFIG_URI,1)\n");
			assert(i==SQLITE_OK);
			exit(EXIT_FAILURE);
		}
	}

	if (GloVars.web_interface_plugin) {
		dlerror();
		char * dlsym_error = NULL;
		dlerror();
		dlsym_error=NULL;
		__web_interface = dlopen(GloVars.web_interface_plugin, RTLD_NOW);
		if (!__web_interface) {
			cerr << "Cannot load library: " << dlerror() << '\n';
			exit(EXIT_FAILURE);
		} else {
			dlerror();
			create_Web_Interface = (create_Web_Interface_t *) dlsym(__web_interface, "create_Web_Interface_func");
			dlsym_error = dlerror();
			if (dlsym_error!=NULL) {
				cerr << "Cannot load symbol create_Web_Interface: " << dlsym_error << '\n';
				exit(EXIT_FAILURE);
			}
		}
		if (__web_interface==NULL || dlsym_error) {
			proxy_error("Unable to load Web_Interface from %s\n", GloVars.web_interface_plugin);
			exit(EXIT_FAILURE);
		} else {
			GloWebInterface = create_Web_Interface();
			if (GloWebInterface) {
				//GloAdmin->init_WebInterfacePlugin();
				//GloAdmin->load_ldap_variables_to_runtime();
			} else {
				proxy_error("Failed to load 'Web_Interface' plugin\n");
			}
		}
	}
	if (GloVars.ldap_auth_plugin) {
		dlerror();
		char * dlsym_error = NULL;
		dlerror();
		dlsym_error=NULL;
		__mysql_ldap_auth = dlopen(GloVars.ldap_auth_plugin, RTLD_NOW);
		if (!__mysql_ldap_auth) {
			cerr << "Cannot load library: " << dlerror() << '\n';
			exit(EXIT_FAILURE);
		} else {
			dlerror();
			create_MySQL_LDAP_Authentication = (create_MySQL_LDAP_Authentication_t *) dlsym(__mysql_ldap_auth, "create_MySQL_LDAP_Authentication_func");
			dlsym_error = dlerror();
			if (dlsym_error!=NULL) {
				cerr << "Cannot load symbol create_MySQL_LDAP_Authentication: " << dlsym_error << '\n';
				exit(EXIT_FAILURE);
			}
		}
		if (__mysql_ldap_auth==NULL || dlsym_error) {
			proxy_error("Unable to load MySQL_LDAP_Authentication from %s\n", GloVars.ldap_auth_plugin);
			exit(EXIT_FAILURE);
		} else {
			GloMyLdapAuth = create_MySQL_LDAP_Authentication();

			if (!GloMyLdapAuth) {
				proxy_error("Failed to load 'MySQL_LDAP_Authentication' plugin\n");
			}

			// we are removing this from here, and copying in
			//     ProxySQL_Main_init_phase2___not_started
			// the keep record of these two lines to make sure we don't
			// do a similar mistakes with other plugins
			//
			//if (GloMyLdapAuth) {
			//	GloAdmin->init_ldap();
			//	GloAdmin->load_ldap_variables_to_runtime();
			//}
		}
	}
}

/**
 * @brief Unloads all the plugins that hold some resources that
 *  need to be deallocated.
 */
void UnloadPlugins() {
	if (GloWebInterface) {
		GloWebInterface->stop();
	}
}

void ProxySQL_Main_init_phase2___not_started(const bootstrap_info_t& boostrap_info) {
	std::string msg;
	ProxySQL_create_or_load_TLS(false, msg);

	LoadPlugins();

	ProxySQL_Main_init_main_modules();

#ifdef PROXYSQLGENAI
	ProxySQL_Main_init_GenAI_module();
	ProxySQL_Main_init_MCP_module();
#endif /* PROXYSQLGENAI */

	ProxySQL_Main_init_Admin_module(boostrap_info);
	GloMTH->print_version();

	{
		cpu_timer t;
		GloMyLogger->events_set_datadir(GloVars.datadir);
		GloMyLogger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
		std::cerr << "Main phase3 : GloMyLogger initialized in ";
#endif
	}
	{
		cpu_timer t;
		GloPgSQL_Logger->events_set_datadir(GloVars.datadir);
		GloPgSQL_Logger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
		std::cerr << "Main phase3 : GloPgSQL_Logger initialized in ";
#endif
	}
	if (GloVars.configfile_open) {
		GloVars.confFile->CloseFile();
	}

	if (GloMyLdapAuth) {
		GloAdmin->load_ldap_variables_to_runtime();
	}

	ProxySQL_Main_init_Auth_module();

	if (GloVars.global.nostart) {
		pthread_mutex_lock(&GloVars.global.start_mutex);
	}
}

void ProxySQL_Main_init_phase3___start_all() {

	srandom((unsigned int)(time(NULL) ^ getpid()));
	{
		cpu_timer t;
		GloMyLogger->events_set_datadir(GloVars.datadir);
		GloMyLogger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
		std::cerr << "Main phase3 : GloMyLogger initialized in ";
#endif
	}
	{
		cpu_timer t;
		GloPgSQL_Logger->events_set_datadir(GloVars.datadir);
		GloPgSQL_Logger->audit_set_datadir(GloVars.datadir);
#ifdef DEBUG
		std::cerr << "Main phase3 : GloPgSQL_Logger initialized in ";
#endif
	}
	// Initialized monitor, no matter if it will be started or not
	GloMyMon = new MySQL_Monitor();
	GloPgMon = new PgSQL_Monitor();
	// load all mysql servers to GloHGH
	{
		cpu_timer t;
		GloAdmin->init_mysql_servers();
		GloAdmin->init_pgsql_servers();
		GloAdmin->init_proxysql_servers();
		GloAdmin->load_scheduler_to_runtime();
#ifdef DEBUG
		std::cerr << "Main phase3 : GloAdmin initialized in ";
#endif
	}
	{
		cpu_timer t;
		ProxySQL_Main_init_Query_module();
#ifdef DEBUG
		std::cerr << "Main phase3 : Query Processor initialized in ";
#endif
	}
	{
		cpu_timer t;
		ProxySQL_Main_init_MySQL_Threads_Handler_module();
#ifdef DEBUG
		std::cerr << "Main phase3 : MySQL Threads Handler initialized in ";
#endif
	}

	{
		cpu_timer t;
		ProxySQL_Main_init_PgSQL_Threads_Handler_module();
#ifdef DEBUG
		std::cerr << "Main phase3 : PgSQL Threads Handler initialized in ";
#endif
	}

	{
		cpu_timer t;
		ProxySQL_Main_init_Query_Cache_module();
#ifdef DEBUG
		std::cerr << "Main phase3 : Query Cache initialized in ";
#endif
	}

	unsigned int iter = 0;
	do { sleep_iter(++iter); } while (load_ != 1);
	load_ = 0;
	__sync_fetch_and_add(&GloMTH->status_variables.threads_initialized, 1);
	__sync_fetch_and_add(&GloPTH->status_variables.threads_initialized, 1);
	{
		cpu_timer t;
		GloMTH->start_listeners();
#ifdef DEBUG
		std::cerr << "Main phase3 : MySQL Threads Handler listeners started in ";
#endif
	}

	{
		cpu_timer t;
		GloPTH->start_listeners();
#ifdef DEBUG
		std::cerr << "Main phase3 : PgSQL Threads Handler listeners started in ";
#endif
	}

	if ( GloVars.global.sqlite3_server == true ) {
		cpu_timer t;
		ProxySQL_Main_init_SQLite3Server();
		sleep(1);
#ifdef DEBUG
		std::cerr << "Main phase3 : SQLite3 Server initialized in ";
#endif
	}
	if (GloVars.global.my_monitor==true)
		{
			cpu_timer t;
			ProxySQL_Main_init_MySQL_Monitor_module();
#ifdef DEBUG
			std::cerr << "Main phase3 : MySQL Monitor initialized in ";
#endif
		}
	if (GloVars.global.pg_monitor==true)
		{
			cpu_timer t;
			pgsql_monitor_thread = new std::thread(&PgSQL_monitor_scheduler_thread);
#ifdef DEBUG
			std::cerr << "Main phase3 : PgSQL Monitor initialized in ";
#endif
		}
#ifdef PROXYSQLCLICKHOUSE
	if ( GloVars.global.clickhouse_server == true ) {
		cpu_timer t;
		ProxySQL_Main_init_ClickHouseServer();
#ifdef DEBUG
		std::cerr << "Main phase3 : ClickHouse Server initialized in ";
#endif
	}
#endif /* PROXYSQLCLICKHOUSE */

	// LDAP
	if (GloMyLdapAuth) {
		GloAdmin->init_ldap_variables();
	}

#ifdef PROXYSQLTSDB
	GloAdmin->init_tsdb_variables();
#endif

#ifdef PROXYSQLGENAI
	// GenAI
	if (GloGATH)
		GloAdmin->init_genai_variables();
	if (GloMCPH) {
		GloAdmin->init_mcp_variables();
	}
#endif /* PROXYSQLGENAI */

	// HTTP Server should be initialized after other modules. See #4510
	GloAdmin->init_http_server();
	GloAdmin->proxysql_restapi().load_restapi_to_runtime();

	// Signal ProxySQL_Admin that all modules have been started
	GloAdmin->all_modules_started = true;

	// Load the config not previously loaded for these modules
	GloAdmin->load_http_server();
	GloAdmin->load_restapi_server();
}



void ProxySQL_Main_init_phase4___shutdown() {
	cpu_timer t;
	ProxySQL_Main_join_all_threads();

	//write(GloAdmin->pipefd[1], &GloAdmin->pipefd[1], 1);	// write a random byte
	if (GloVars.global.nostart) {
		pthread_mutex_unlock(&GloVars.global.start_mutex);
	}

	ProxySQL_Main_shutdown_all_modules();
#ifdef DEBUG
	std::cerr << "Main init phase4 shutdown completed in ";
#endif
}

/**
 * @brief Phase 1 of the daemonization process for ProxySQL.
 *
 * This function performs the first phase of the daemonization process for ProxySQL. It sets up essential parameters
 * and checks for conditions necessary for daemonization. If any of the conditions are not met or if an error occurs,
 * the function logs an error message and exits with a failure status code.
 *
 * @param argv0 The name of the executable file used to start ProxySQL.
 * @return void.
 * @note This function does not return if an error occurs; it exits the process.
 */
void ProxySQL_daemonize_phase1(char *argv0) {
	int rc; // Variable to store the return code of system calls

	// Set the PID file identification to the global PID variable
	daemon_pid_file_ident=GloVars.pid;

	// Set the log identification based on the executable file name
	daemon_log_ident=daemon_ident_from_argv0(argv0);

	// Change the current working directory to the data directory
	rc=chdir(GloVars.datadir);
	if (rc) {
		// Log an error message if changing the directory fails and exit with failure status
		daemon_log(LOG_ERR, "Could not chdir into datadir: %s . Error: %s", GloVars.datadir, strerror(errno));
		exit(EXIT_FAILURE);
	}

	// Set the PID file process to the ProxySQL PID file
	daemon_pid_file_proc = proxysql_pid_file;

	// Check if ProxySQL is already running by checking the PID file
	pid=daemon_pid_file_is_running();
	if (pid>=0) {
		// Log an error message if ProxySQL is already running and exit with failure status
		daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
		exit(EXIT_FAILURE);
	}
	if (daemon_retval_init() < 0) {
		// Initialize the return value for daemonization; log an error if initialization fails
		daemon_log(LOG_ERR, "Failed to create pipe.");
		exit(EXIT_FAILURE);
	}
}

/**
 * @brief Wait for the return value from the daemon process.
 *
 * This function waits for the return value from the daemon process for a specified duration. If the return value is
 * received within the specified time, the function logs the return value. If an error occurs during the waiting process,
 * the function logs an error message and exits with a failure status code.
 *
 * @return void.
 * @note This function does not return if an error occurs; it exits the process.
 */
void ProxySQL_daemonize_wait_daemon() {
	int ret; // Variable to store the return value from daemon_retval_wait()
	// Wait for 20 seconds for the return value passed from the daemon process
	if ((ret = daemon_retval_wait(20)) < 0) {
		// Log an error message if waiting for the return value fails and exit with failure status
		daemon_log(LOG_ERR, "Could not receive return value from daemon process: %s", strerror(errno));
		exit(EXIT_FAILURE);
	}

	// If a return value is received, log it
	if (ret) {
		daemon_log(LOG_ERR, "Daemon returned %i as return value.", ret);
	}

	// Exit with the return value received from the daemon process
	exit(ret);
}


bool ProxySQL_daemonize_phase2() {
	int rc;
/*
	// we DO NOT close FDs anymore. See:
	// https://github.com/sysown/proxysql/issues/2628
	//
	// Close FDs
	if (daemon_close_all(-1) < 0) {
		daemon_log(LOG_ERR, "Failed to close all file descriptors: %s", strerror(errno));

		// Send the error condition to the parent process
		daemon_retval_send(1);
		return false;
	}
*/

	rc=chdir(GloVars.datadir);
	if (rc) {
		daemon_log(LOG_ERR, "Could not chdir into datadir: %s . Error: %s", GloVars.datadir, strerror(errno));
		exit(EXIT_FAILURE);
	}
	/* Create the PID file */
	if (daemon_pid_file_create() < 0) {
		daemon_log(LOG_ERR, "Could not create PID file (%s).", strerror(errno));
		daemon_retval_send(2);
		return false;
	}

	/* Send OK to parent process */
	daemon_retval_send(0);
	GloAdmin->flush_error_log();
	//daemon_log(LOG_INFO, "Starting ProxySQL\n");
	//daemon_log(LOG_INFO, "Sucessfully started");
	proxy_info("Starting ProxySQL\n");
	proxy_info("Successfully started\n");
	return true;
}

/**
 * @brief Calls an external script upon exit failure.
 *
 * This function attempts to execute an external script specified in the global variable `GloVars.execute_on_exit_failure`
 * if the program exits due to failure. It first checks if the variable is set, and if not, returns without further action.
 * If the variable is set, it attempts to fork a child process to execute the script. If forking fails, the function exits
 * with failure status. If forking succeeds, the child process attempts to execute the script using the `system` function.
 * If the script execution fails, an error message is logged, and the child process exits with failure status. Otherwise, the
 * child process exits with success status. Additionally, the function creates a detached thread to wait for the child process
 * to exit, ensuring that the parent process does not block. If thread creation fails, the function logs an error message and
 * exits with failure status.
 *
 * @return void.
 * @note This function does not return if an error occurs; it exits the process.
 */
void call_execute_on_exit_failure() {
	// Log a message indicating the attempt to call the external script
	proxy_info("Trying to call external script after exit failure: %s\n", GloVars.execute_on_exit_failure ? GloVars.execute_on_exit_failure : "(null)");

	// Check if the global variable execute_on_exit_failure is NULL
	if (GloVars.execute_on_exit_failure == NULL) {
		// Exit the function if the variable is not set
		return;
	}

	// Fork a child process
	pid_t cpid;
	cpid = fork();

	// Check for fork failure
	if (cpid == -1) {
		// Exit with failure status if fork fails
		exit(EXIT_FAILURE);
	}

	// Child process
	if (cpid == 0) {
		int rc;
		// Execute the external script
		rc = system(GloVars.execute_on_exit_failure);

		// Check if script execution failed
		if (rc) {
			// Log an error message and exit with failure status if execution fails
			proxy_error("Execute on EXIT_FAILURE: Failed to run %s\n", GloVars.execute_on_exit_failure);
			perror("system()");
			exit(EXIT_FAILURE);
		} else {
			// Exit with success status if script execution succeeds
			exit(EXIT_SUCCESS);
		}
	}
	// Parent process
	else {
		pthread_attr_t attr;
		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
		pthread_attr_setstacksize (&attr, 64*1024);
		pid_t *cpid_ptr=(pid_t *)malloc(sizeof(pid_t));
		*cpid_ptr=cpid;
		pthread_t thr;

		// Create a detached thread to wait for the child process to exit
		if (pthread_create(&thr, &attr, waitpid_thread, (void *)cpid_ptr) !=0 ) {
			perror("Thread creation");
			exit(EXIT_FAILURE);
		}
	}
}


bool ProxySQL_daemonize_phase3() {
	int rc;
	int status;
	//daemon_log(LOG_INFO, "Angel process started ProxySQL process %d\n", pid);
	parent_open_error_log();
	proxy_info("Angel process started ProxySQL process %d\n", pid);
	parent_close_error_log();
	rc=waitpid(pid, &status, 0);
	if (rc==-1) {
		parent_open_error_log();
		perror("waitpid");
		//proxy_error("[FATAL]: waitpid: %s\n", perror("waitpid"));
		exit(EXIT_FAILURE);
	}
	rc=WIFEXITED(status);
	if (rc) { // client exit()ed
		rc=WEXITSTATUS(status);
		if (rc==0) {
			//daemon_log(LOG_INFO, "Shutdown angel process\n");
			parent_open_error_log();
			proxy_info("Shutdown angel process\n");
			exit(EXIT_SUCCESS);
		} else {
			//daemon_log(LOG_INFO, "ProxySQL exited with code %d . Restarting!\n", rc);
			parent_open_error_log();
			proxy_error("ProxySQL exited with code %d . Restarting!\n", rc);
			call_execute_on_exit_failure();
			parent_close_error_log();
			return false;
		}
	} else {
		//daemon_log(LOG_INFO, "ProxySQL crashed. Restarting!\n");
		parent_open_error_log();
		proxy_error("ProxySQL crashed. Restarting!\n");
		proxy_info("ProxySQL version %s\n", PROXYSQL_VERSION);
		if (binary_sha1) {
			proxy_info("ProxySQL SHA1 checksum: %s\n", binary_sha1);
		}
		call_execute_on_exit_failure();
		// automatic reload of TLS certificates after a crash , see #4658
		std::string msg;
		ProxySQL_create_or_load_TLS(false, msg);
		//  Honor --initial after a crash , see #4659
		if (GloVars.__cmd_proxysql_initial==true) {
			std::cerr << "Renaming database file " << GloVars.admindb << endl;
			char *newpath=(char *)malloc(strlen(GloVars.admindb)+8);
			sprintf(newpath,"%s.bak",GloVars.admindb);
			rename(GloVars.admindb,newpath);	// FIXME: should we check return value, or ignore whatever it successed or not?
		}
		parent_close_error_log();
		return false;
	}
	return true;
}

void my_terminate(void) {
	proxy_error("ProxySQL crashed due to exception\n");
	print_backtrace();
}

namespace {
	static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}

/**
 * @brief Regex for parsing URI connections.
 * @details Groups explanation:
 *  + HierPart doesn't hold '//' making previous non-capturing group optional. E.g:
 *     - '127.0.0.1:3306'
 *     - 'mysql-server-1:3306'
 *  + UserInfo is inside a non-capturing group to avoid matching '@'.
 *  + Host,Port groups are inside a non-capturing group to allow URI like: 'mysql://user:pass@'
 *  + RegName matches any valid Ipv4 or domain name.
 *  + Ipv6 matches Ipv6, it's NOT spec conforming, we don't verify the supplied Ip in the regex.
 *  + Port matches the supplied port.
 *  + Optionally match a supplied (/).
 *  + Ensure match termination in HierPart group, forcing conditional subgroups matching.
 */
const char CONN_URI_REGEX[] {
	"^(:?(?P<Scheme>[a-z][a-z0-9\\+\\-\\.]*):\\/\\/)?"
	"(?P<HierPart>"
		"(?:(?P<UserInfo>(?:\\%[0-9a-f][0-9a-f]|[a-z0-9\\-\\.\\_\\~]|[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]|\\:)*)\\@)?"
		"(:?"
			"(?P<Host>"
				"(?P<RegName>(?:\\%[0-9a-f][0-9a-f]|[a-z0-9\\-\\.\\_\\~]|[\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=]])*)|"
				"(?P<Ipv6>\\[(?:[0-9a-f]|[\\:])*\\]))"
			"(?:\\:(?P<Port>[0-9]+)?)?"
		")?"
		"(?:\\/)?"
	")?$"
};

/**
 * @brief Holds each of the groups matched in a string by 'CONN_URI_REGEX'.
 */
struct conn_uri_t {
	string scheme;
	string hierpart;
	string user;
	string pass;
	string host;
	uint32_t port;
};

/**
 * @brief Uses Regex 'CONN_URI_REGEX' to parse the supplied string.
 * @details Tries to perform a 'PartialMatchN' over the 'CONN_URI_REGEX'. Right now doesn't perform a *full*
 *   check on the validity of the semantics of the URI itself. It does perform some checks.
 * @param conn_uri A connection URI.
 * @return On success `{EXIT_SUCCESS, conn_uri_t}`, otherwise `{EXIT_FAILURE, conn_uri_t{}}`. Error cause is
 *   logged.
 */
pair<int,conn_uri_t> parse_conn_uri(const string& conn_uri) {
	re2::RE2::Options opts(RE2::Quiet);
	opts.set_case_sensitive(false);

	re2::RE2 re(CONN_URI_REGEX, opts);;
	if (re.error_code()) {
		proxy_error("Regex creation failed - %s\n", re.error().c_str());
		assert(0);
	}

	const int num_groups = re.NumberOfCapturingGroups();
	std::vector<std::string> str_args(num_groups, std::string {});
	std::vector<RE2::Arg> re2_args {};

	for (std::string& str_arg : str_args) {
		re2_args.push_back(RE2::Arg(&str_arg));
	}

	std::vector<const RE2::Arg*> matches {};
	for (RE2::Arg& re2_arg : re2_args) {
		matches.push_back(&re2_arg);
	}

	const re2::RE2::Arg* const* args = &matches[0];
	RE2::PartialMatchN(conn_uri, re, args, num_groups);

	const map<string, int>& groups = re.NamedCapturingGroups();
	map<string,int>::const_iterator group_it;

	string scheme {};
	string hierpart {};
	string userinfo {};
	string host {};
	uint32_t port = 0;

	if ((group_it = groups.find("Scheme")) != groups.end()) { scheme = str_args[group_it->second - 1]; }
	if ((group_it = groups.find("HierPart")) != groups.end()) { hierpart = str_args[group_it->second - 1]; }
	if ((group_it = groups.find("UserInfo")) != groups.end()) { userinfo = str_args[group_it->second - 1]; }
	if ((group_it = groups.find("Host")) != groups.end()) { host = str_args[group_it->second - 1]; }

	// Remove the enclosing(`[]`) from IPv6 addresses
	if (host.empty() == false && host.size() > 2) {
		if (host[0] == '[') {
			host = host.substr(1, host.size()-2);
		}
	}

	string user {};
	string pass {};

	int32_t match_err = EXIT_SUCCESS;

	// Extract supplied info for user:pass
	vector<string> v_userinfo = split_str(userinfo, ':');
	if (v_userinfo.size() == 1) {
		user = v_userinfo[0];
	} else if (v_userinfo.size() == 2) {
		user = v_userinfo[0];
		pass = v_userinfo[1];
	} else if (v_userinfo.size() > 2) {
		proxy_error(
			"Invalid UserInfo '%s' supplied in connection URI. UserInfo should contain at max two fields 'user:pass'\n",
			userinfo.c_str()
		);
		match_err = EXIT_FAILURE;
	}

	if ((group_it = groups.find("Port")) != groups.end()) {
		const string s_port { str_args[group_it->second - 1] };

		if (!s_port.empty()) {
			char* p_end = nullptr;
			port = std::strtol(s_port.c_str(), &p_end, 10);

			if (errno == ERANGE || p_end == s_port.c_str()) {
				proxy_error("Invalid Port '%s' supplied in connection URI.\n", s_port.c_str());
				match_err = EXIT_FAILURE;
			}
		}
	}

	struct conn_uri_t uri_data { scheme, hierpart, user, pass, host, port };

	return { match_err, uri_data };
}

/**
 * @brief Helper function to serialize 'conn_uri_t' for debugging purposes.
 */
string to_string(const conn_uri_t& conn_uri) {
	nlohmann::ordered_json j;

	j["scheme"] = conn_uri.scheme;
	j["user"] = conn_uri.user;
#ifdef DEBUG
	j["pass"] = conn_uri.pass;
#endif
	j["host"] = conn_uri.host;
	j["port"] = conn_uri.port;

	return j.dump();
}

/**
 * @brief Query for fetching MySQL users during bootstrapping.
 * @details For security reasons, users matching the following names are excluded:
 *   - `mysql.%`
 *   - `root`
 *   - `bt_proxysql_%`
 *   Users starting with `bt_proxysql_` are considered `ProxySQL` created used during `bootstrap` for
 *   monitoring purposes. A user, could create it's own monitoring accounts under this prefix, to avoid
 *   ProxySQL fetching them as regular users.
 */
const char BOOTSTRAP_SELECT_USERS[] {
	"SELECT DISTINCT user,ssl_type,authentication_string,plugin,password_expired FROM mysql.user"
		" WHERE user NOT LIKE 'mysql.%' AND user NOT LIKE 'bt_proxysql_%'"
#ifndef DEBUG
		" AND user != 'root'"
#endif
};

/**
 * @brief Query for fetching MySQL servers during bootstrapping.
 * @details As the regular GR monitoring queries, makes use of `replication_group_members` table.
 */
const char BOOTSTRAP_SELECT_SERVERS[] {
	"SELECT MEMBER_ID,MEMBER_HOST,MEMBER_PORT,MEMBER_STATE,MEMBER_ROLE,MEMBER_VERSION FROM"
		" performance_schema.replication_group_members"
};

/**
 * @brief Stores credentials for monitoring created accounts during bootstrap.
 */
struct acct_creds_t {
	string user;
	string pass;
};

/**
 * @brief Minimal set of permissions for a created GR monitoring account.
 * @details Right now we **do not grant** permissions to `mysql_innodb_cluster_metadata` tables, since for now
 *   we don't make any use of them.
 */
const vector<string> t_grant_perms_queries {
	"GRANT USAGE ON *.* TO `%s`@`%%`",
//  NOTE: For now, we don't make use of any `mysql_innodb_cluster_metadata` tables
//	"GRANT SELECT, EXECUTE ON `mysql_innodb_cluster_metadata`.* TO `%s`@`%%`",
//  NOTE: For now, we don't make use of 'routers' and 'v2_routers' table
//	"GRANT INSERT, UPDATE, DELETE ON `mysql_innodb_cluster_metadata`.`routers` TO `%s`@`%%`",
//	"GRANT INSERT, UPDATE, DELETE ON `mysql_innodb_cluster_metadata`.`v2_routers` TO `%s`@`%%`",
	"GRANT SELECT ON `performance_schema`.`global_variables` TO `%s`@`%%`",
	"GRANT SELECT ON `performance_schema`.`replication_group_member_stats` TO `%s`@`%%`",
	"GRANT SELECT ON `performance_schema`.`replication_group_members` TO `%s`@`%%`"
};

/**
 * @brief Grants the minimal set of permissions for GR monitoring to a supplies user.
 * @details All permissions will be granted for host `%`.
 * @param mysql An already opened MySQL connection.
 * @param user The username to grant permissions to.
 * @return Either `0` for success, or the corresponding `mysql_errno` for failure.
 */
int grant_user_perms(MYSQL* mysql, const string& user) {
	for (const string& t_query : t_grant_perms_queries) {
		const string query { cstr_format(t_query.c_str(), user.c_str()).str };

		proxy_info("GRANT permissions '%s' to user\n", query.c_str());
		int myerr = mysql_query(mysql, query.c_str());
		if (myerr) {
			return mysql_errno(mysql);
		}
	}

	return 0;
}

/**
 * @brief Generates a random password conforming with MySQL 'MEDIUM' policy.
 * @param size The target password size.
 * @return The random password generated.
 */
string gen_rand_password(std::size_t size) {
	const string lowercase { "abcdefghijklmnopqrstuvwxyz" };
	const string uppercase { "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
	const string digits { "0123456789" };
	const string symbols { "~@#$^&*]}[{()|-=+;:.>,</?" };
	string allphabet { lowercase + uppercase + digits + symbols };

	std::random_device rd {};
	std::mt19937 gen { rd() };

	string pass {};

	if (size == 0) {
		return pass;
	} else if (size <= 4) {
		std::shuffle(allphabet.begin(), allphabet.end(), gen);
		pass = allphabet.substr(0, size);
	} else {
		// 1 numeric character
		pass += digits[gen() % digits.size()];
		// 1 lowercase character
		pass += lowercase[gen() % lowercase.size()];
		// 1 uppercase character
		pass += toupper(lowercase[gen() % lowercase.size()]);
		// 1 special (nonalphanumeric) character
		pass += symbols[gen() % symbols.size()];

		std::shuffle(allphabet.begin(), allphabet.end(), gen);
		std::size_t remains = size - 4;

		if (remains < allphabet.size()) {
			pass += allphabet.substr(0, remains);
		} else {
			std::size_t req_modulus = static_cast<std::size_t>(remains / allphabet.size());
			std::size_t req_reminder = remains % allphabet.size();

			for (std::size_t i = 0; i < req_modulus; i++) {
				std::shuffle(allphabet.begin(), allphabet.end(), gen);
				pass += allphabet;
			}

			std::shuffle(allphabet.begin(), allphabet.end(), gen);
			pass += allphabet.substr(0, req_reminder);
		}
	}

	return pass;
}

/**
 * @brief Creates a random monitoring account for bootstrap with a random generated password.
 * @param mysql An already opened MySQL connection.
 * @param max_retries Maximum number of attempts for creating the user.
 * @return On success `{0, acct_creds_t}`, otherwise `{mysql_errno, acct_creds_t{}}`. Error cause is logged.
 */
pair<int32_t,acct_creds_t> create_random_bootstrap_account(MYSQL* mysql, uint32_t max_retries) {
	// Random username
	const string monitor_user { "bt_proxysql_" + rand_str(12) };
	string monitor_pass {};

	int myerr = ER_NOT_VALID_PASSWORD;
	uint32_t retries = 0;

	while (retries < max_retries && (myerr == ER_NOT_VALID_PASSWORD)) {
		monitor_pass = gen_rand_password(16);

		const string user_create {
			"CREATE USER IF NOT EXISTS '" + monitor_user + "'@'%' IDENTIFIED BY '" + monitor_pass + "'"
		};

		int myres = mysql_query(mysql, user_create.c_str());
		myerr = mysql_errno(mysql);

		if (myres || myerr) {
			if (myerr != ER_NOT_VALID_PASSWORD) {
				return { myerr, { "", "" } };
			} else {
				proxy_info(
					"Bootstrap config, failed to create password for user '%s'. Retrying...\n", monitor_user.c_str()
				);
				retries += 1;
			}
		} else {
			break;
		}
	}

	if (myerr == 0) {
		myerr = grant_user_perms(mysql, monitor_user);
	}

	return { myerr, { monitor_user, monitor_pass } };
}

/**
 * @brief Creates a monitoring account for bootstrap with the supplied parameters.
 * @param mysql An already opened MySQL connection.
 * @param user The username for the new account.
 * @param pass The password for the new account.
 * @return On success `{0,acct_creds_t}`, otherwise `{mysql_errno,acct_creds_t}`. Doesn't log errors.
 */
pair<int32_t,acct_creds_t> create_bootstrap_account(MYSQL* mysql, const string& user, const string& pass) {
	const string monitor_user { "'" + user + "'" };
	const string user_create {
		"CREATE USER IF NOT EXISTS " + monitor_user + "@'%' IDENTIFIED BY '" + pass + "'"
	};

	int myerr = mysql_query(mysql, user_create.c_str());

	if (myerr == 0) {
		myerr = grant_user_perms(mysql, user);
	}

	return { myerr, { user, pass } };
}


/**
 * @brief This function handles the restart of a process based on certain conditions.
 * 
 * If glovars.proxy_restart_on_error is true, the process restart logic is executed.
 * The function implements exponential backoff and terminates if the maximum number of restart attempts is reached.
 */
void handleProcessRestart() {
	// Maximum number of restart attempts allowed
	constexpr int MAX_RESTART_ATTEMPTS = 5;
	// Threshold in seconds for considering restarts too frequent
	constexpr int EXECUTION_THRESHOLD = 10;

	// Initialize restart attempts counter
	time_t laststart = 0;
	int restartAttempts = 0;
	do {
		// Check if laststart is set. It means that the process was already started at least once
		if (laststart) {
			// Get current time
			time_t currenttime = time(NULL);
			// Calculate elapsed time since laststart
			time_t elapsed_seconds = currenttime - laststart;

			// Check if elapsed time is less than threshold
			if (elapsed_seconds < EXECUTION_THRESHOLD) {
				// if restart is too frequent, something really bad is going on
				// Implement exponential backoff - wait exponentially longer after each failed attempt
				restartAttempts++;

				// Check if restart attempts exceed the maximum allowed
				if (restartAttempts > MAX_RESTART_ATTEMPTS) {
					// the parent exits before the fork
					parent_open_error_log();
					proxy_error("Angel process already attempted to restart ProxySQL %d times and has no retries left. exit() with failure.\n", MAX_RESTART_ATTEMPTS);
					exit(EXIT_FAILURE);
				}

				// Calculate wait time using exponential backoff
				int waitTime = 1 << restartAttempts;
				parent_open_error_log();
				proxy_info("ProxySQL exited after only %ld seconds , below the %d seconds threshold. Restarting attempt %d\n", elapsed_seconds, EXECUTION_THRESHOLD, restartAttempts);
				proxy_info("Angel process is waiting %d seconds before starting a new ProxySQL process\n", waitTime);
				parent_close_error_log();

				// Wait for the calculated time before next restart attempt
				sleep(waitTime);
			} else {
				// Reset restart attempts counter if elapsed time is greater than or equal to threshold
				restartAttempts = 0;
			}
		}

		// Update laststart time to current time
		laststart=time(NULL);

		// Fork the process
		pid = fork();

		// Check for fork error
		if (pid < 0) {
			parent_open_error_log();
			proxy_error("[FATAL]: Error in fork()\n");

			// Exit with failure
			exit(EXIT_FAILURE);
		}

		// Check if the process is the parent
		if (pid) { // The parent
			parent_close_error_log();
			if (ProxySQL_daemonize_phase3()==true) {
				break;
			}
		} else { // The daemon
			// we open the files also on the child process
			// this is required if the child process was created after a crash
			parent_open_error_log();
			GloVars.global.start_time=monotonic_time();
			GloVars.install_signal_handler();
		}
	} while (pid > 0);
}

#ifndef NOJEM
int print_jemalloc_conf() {
	int rc = 0;

	bool xmalloc = 0;
	bool prof_accum = 0;
	bool prof_leak = 0;

	size_t lg_cache_max = 0;
	size_t lg_prof_sample = 0;
	size_t lg_prof_interval = 0;

	size_t bool_sz = sizeof(bool);
	size_t size_sz = sizeof(size_t);
	size_t ssize_sz = sizeof(ssize_t);

	rc = mallctl("config.xmalloc", &xmalloc, &bool_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'config.xmalloc' with error %d", rc); return rc; }

	rc = mallctl("opt.lg_tcache_max", &lg_cache_max, &size_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'opt.lg_tcache_max' with error %d", rc);  return rc; }

	rc = mallctl("opt.prof_accum", &prof_accum, &bool_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'opt.prof_accum' with error %d", rc); return rc; }

	rc = mallctl("opt.prof_leak", &prof_leak, &bool_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'opt.prof_leak' with error %d", rc);  return rc; }

	rc = mallctl("opt.lg_prof_sample", &lg_prof_sample, &size_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_sample' with error %d", rc);  return rc; }

	rc = mallctl("opt.lg_prof_interval", &lg_prof_interval, &ssize_sz, NULL, 0);
	if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_interval' with error %d", rc);  return rc; }

	proxy_info(
		"Using jemalloc with MALLOC_CONF:"
			" config.xmalloc:%d, lg_tcache_max:%lu, opt.prof_accum:%d, opt.prof_leak:%d,"
			" opt.lg_prof_sample:%lu, opt.lg_prof_interval:%lu, rc:%d\n",
		xmalloc, lg_cache_max, prof_accum, prof_leak, lg_prof_sample, lg_prof_interval, rc
	);

	return 0;
}
#else
int print_jemalloc_conf() {
	return 0;
}
#endif

// Database type configuration
enum WatchDogEntityType { 
	ENTITY_TYPE_MYSQL, 
	ENTITY_TYPE_POSTGRESQL,
	ENTITY_TYPE_COUNT
};

struct WatchdogGroup {
	WatchDogEntityType type;
	const char* name;
	void* thread_handler;  // GloMTH or GloPTH
	unsigned int missed_heartbeats;
	unsigned long long prev_time;
};

template <typename ThreadType>
unsigned int check_heartbeats(ThreadType* threads, unsigned int num_threads,
	unsigned long long curtime, unsigned long long poll_timeout) {
	unsigned int missing = 0;
	if (!threads) return missing;

	for (unsigned int i = 0; i < num_threads; ++i) {
		if (threads[i].worker &&
			curtime > threads[i].worker->atomic_curtime + poll_timeout) {
			missing++;
#ifdef DEBUG
			// This block is only used for Watchdog unit tests:
			// Specifically for PROXYSQLTEST cases 55 0 and 55 1.
			if (i == 0 && threads[i].worker->watchdog_test__simulated_delay_ms) {
				// [Test-only] Break execution after missed heartbeat on thread 0.
				threads[i].worker->watchdog_test__missed_heartbeats++;
				break;  
			}
#endif
		}
	}
	return missing;
}

void watchdog_main_loop() {
#if 0
	{
		// the following commented code is here only to manually test handleProcessRestart()
		// DO NOT ENABLE
		//proxy_info("Service is up\n");
		//sleep(2);
		//assert(0);
	}
#endif

	WatchdogGroup groups[ENTITY_TYPE_COUNT] = {
		{ ENTITY_TYPE_MYSQL, "MySQL", GloMTH, 0, monotonic_time() },
		{ ENTITY_TYPE_POSTGRESQL, "PostgreSQL", GloPTH, 0, monotonic_time() }
	};

	unsigned int inner_loops = 0;
	unsigned long long time_next_version_check = 0;
	unsigned long long loop_prev_time = monotonic_time();
	while (glovars.shutdown == 0) {
		std::this_thread::sleep_for(std::chrono::microseconds(200000));
		
		if (disable_watchdog) continue;

		const unsigned long long curtime = monotonic_time();

		// Version check handling
		if (GloVars.global.version_check && curtime > time_next_version_check) {
			pthread_attr_t attr;
			pthread_attr_init(&attr);
			pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
			pthread_t thr;
			if (pthread_create(&thr, &attr, main_check_latest_version_thread, NULL) != 0) {
				perror("Thread creation");
				exit(EXIT_FAILURE);
			}
			if (time_next_version_check == 0)
				time_next_version_check = curtime;
			time_next_version_check += (24ULL * 3600 * 1000000); // 24h in microseconds
		}

		inner_loops++;
		if (curtime >= ((inner_loops * 300000) + loop_prev_time)) {
			//proxy_info("Watchdog: main loop is blocked for more than 300ms, resetting prev_time\n");
			// if this happens, it means that this very simple loop is blocked
			// probably we are running under gdb
			loop_prev_time = curtime;
			for (auto& group : groups) {
				group.prev_time = curtime;
			}
			inner_loops = 0;
			continue;
		}

		// Process all
		for (auto& group : groups) {
			if (!group.thread_handler) continue;

			// Get poll timeout from thread handler
			unsigned int poll_timeout_ms = 0;
			switch (group.type) {
			case ENTITY_TYPE_MYSQL:
				poll_timeout_ms = static_cast<MySQL_Threads_Handler*>(group.thread_handler)->variables.poll_timeout;
				break;
			case ENTITY_TYPE_POSTGRESQL:
				poll_timeout_ms = static_cast<PgSQL_Threads_Handler*>(group.thread_handler)->variables.poll_timeout;
				break;
			default:
				assert(0);
			}

			const unsigned long long poll_timeout = (static_cast<unsigned long long>(poll_timeout_ms) + 1000) * 1000;

			if (curtime < group.prev_time + poll_timeout) continue;
			loop_prev_time = curtime;
			group.prev_time = curtime;
			inner_loops = 0;

			// Check thread heartbeats
			unsigned int threads_missing_heartbeat = 0;
			switch (group.type) {
			case ENTITY_TYPE_MYSQL:
			{
				MySQL_Threads_Handler* mth = static_cast<MySQL_Threads_Handler*>(group.thread_handler);
				threads_missing_heartbeat += check_heartbeats(mth->mysql_threads, mth->num_threads, curtime, poll_timeout);
#ifdef IDLE_THREADS
				if (GloVars.global.idle_threads) {
					threads_missing_heartbeat += check_heartbeats(mth->mysql_threads_idles, mth->num_threads, curtime, poll_timeout);
				}
#endif
				break;
			}
			case ENTITY_TYPE_POSTGRESQL:
			{
				PgSQL_Threads_Handler* pth = static_cast<PgSQL_Threads_Handler*>(group.thread_handler);
				threads_missing_heartbeat += check_heartbeats(pth->pgsql_threads, pth->num_threads, curtime, poll_timeout);
#ifdef IDLE_THREADS
				if (GloVars.global.idle_threads) {
					threads_missing_heartbeat += check_heartbeats(pth->pgsql_threads_idles, pth->num_threads, curtime, poll_timeout);
				}
#endif
				break;
			}
			default:
				assert(0);
			}
			// Handle heartbeat results
			if (threads_missing_heartbeat > 0) {
				proxy_error("Watchdog: %u %s threads missed a heartbeat\n",
					threads_missing_heartbeat, group.name);
				group.missed_heartbeats++;

				if (group.missed_heartbeats >= static_cast<unsigned int>(GloVars.restart_on_missing_heartbeats)) {
#ifdef DEBUG
					// This block is only used for Watchdog unit tests:
					// Specifically for PROXYSQLTEST cases 55 0 and 55 1.
					bool is_test_mode = false;
					switch (group.type) {
					case ENTITY_TYPE_MYSQL: {
						MySQL_Threads_Handler* mth = static_cast<MySQL_Threads_Handler*>(group.thread_handler);
						if (mth && mth->num_threads && mth->mysql_threads[0].worker &&
							mth->mysql_threads[0].worker->watchdog_test__simulated_delay_ms) {
							is_test_mode = true;
						}
						break;
					}
					case ENTITY_TYPE_POSTGRESQL: {
						PgSQL_Threads_Handler* pth = static_cast<PgSQL_Threads_Handler*>(group.thread_handler);
						if (pth && pth->num_threads && pth->pgsql_threads[0].worker &&
							pth->pgsql_threads[0].worker->watchdog_test__simulated_delay_ms) {
							is_test_mode = true;
						}
						break;
					}
					default:
						assert(0);
					}

					if (is_test_mode) {
						proxy_error("Watchdog: reached %u missed %s thread heartbeats. Running in Watchdog test mode. Aborting suppressed.\n",
							group.missed_heartbeats, group.name);
						group.missed_heartbeats = 0;
						continue;
					}
#endif

#ifndef RUNNING_ON_VALGRIND
					if (GloVars.restart_on_missing_heartbeats) {
						proxy_error("Watchdog: reached %u missed %s thread heartbeats. Aborting!\n",
							group.missed_heartbeats, group.name);
						proxy_error("Watchdog: see details at https://github.com/sysown/proxysql/wiki/Watchdog\n");
						assert(0);
					}
#else
					proxy_error("Watchdog: reached %u missed %s thread heartbeats. Not aborting under Valgrind\n",
						group.missed_heartbeats, group.name);
#endif
				}
			} else {
				group.missed_heartbeats = 0;
			}
		}
	}
}

int main(int argc, const char * argv[]) {

	if (check_openssl_version() == false) {
		exit(EXIT_FAILURE);
	}

#ifdef DEBUG
	{
		// This run some ProxyProtocolInfo tests.
		// It will assert() if any test fails
		ProxyProtocolInfo ppi;
		ppi.run_tests();
	}
#endif // DEBUG

	{
		MYSQL *my = mysql_init(NULL);
		mysql_close(my);
//		cpu_timer t;
		ProxySQL_Main_init();
#ifdef DEBUG
//		std::cerr << "Main init phase0 completed in ";
#endif
	}
#ifdef DEBUG
	{
		// Automated testing
		MySQL_Set_Stmt_Parser parser("");
		parser.test_parse_USE_query();
	}
#endif // DEBUG
	{
		cpu_timer t;
		ProxySQL_Main_process_global_variables(argc, argv);
		GloVars.global.start_time=monotonic_time(); // always initialize it
		srand(GloVars.global.start_time*thread_id());
		randID = rand();
#ifdef DEBUG
		std::cerr << "Main init global variables completed in ";
#endif
	}

	// Output current jemalloc conf; no action taken when disabled
	{
		int rc = print_jemalloc_conf();
		if (rc) { exit(EXIT_FAILURE); }
	}

	struct rlimit nlimit;
	{
		int rc = getrlimit(RLIMIT_NOFILE, &nlimit);
		if (rc == 0) {
			proxy_info("Current RLIMIT_NOFILE: %lu\n", nlimit.rlim_cur);
			if (nlimit.rlim_cur <= 1024) {
				proxy_error("Current RLIMIT_NOFILE is very low: %lu .  Tune RLIMIT_NOFILE correctly before running ProxySQL\n", nlimit.rlim_cur);
				if (nlimit.rlim_max > nlimit.rlim_cur) {
					if (nlimit.rlim_max >= 102400) {
						nlimit.rlim_cur = 102400;
					} else {
						nlimit.rlim_cur = nlimit.rlim_max;
					}
					proxy_warning("Automatically setting RLIMIT_NOFILE to %lu\n", nlimit.rlim_cur);
					rc = setrlimit(RLIMIT_NOFILE, &nlimit);
					if (rc) {
						proxy_error("Unable to increase RLIMIT_NOFILE: %s: \n", strerror(errno));
					}
				} else {
					proxy_error("Unable to increase RLIMIT_NOFILE because rlim_max is low: %lu\n", nlimit.rlim_max);
				}
			}
		} else {
			proxy_error("Call to getrlimit failed: %s\n", strerror(errno));
		}
	}

	bootstrap_info_t bootstrap_info {};
	// Try to connect to MySQL for performing the bootstrapping process:
	//   - If data isn't found we perform the bootstrap process.
	//   - If non-empty datadir is present, reconfiguration should be performed.
	if (GloVars.global.gr_bootstrap_mode == 1) {
		// Check the other required arguments for performing the bootstrapping process:
		//  - Username
		//  - Password - asked by prompt or supplied
		//  - Connection string parsing is required
		const string conn_uri { GloVars.global.gr_bootstrap_uri };
		const pair<int32_t,conn_uri_t> parse_uri_res { parse_conn_uri(conn_uri) };
		const conn_uri_t uri_data = parse_uri_res.second;

		if (parse_uri_res.first == EXIT_FAILURE) {
			proxy_info("Aborting bootstrap due to failed to parse or match URI - `%s`\n", to_string(uri_data).c_str());
			exit(parse_uri_res.first);
		} else {
			proxy_info("Bootstrap connection data supplied via URI - `%s`\n", to_string(uri_data).c_str());
		}

		const char* c_host = uri_data.host.c_str();
		const char* c_user = uri_data.user.empty() ? "root" : uri_data.user.c_str();
		const char* c_pass = nullptr;
		uint32_t port = uri_data.port == 0 ? 3306 : uri_data.port;
		uint32_t flags = CLIENT_SSL;

		nlohmann::ordered_json conn_data { { "host", c_host }, { "user", c_user }, { "port", port } };
		proxy_info("Performing bootstrap connection using URI data and defaults - `%s`\n", conn_data.dump().c_str());

		if (uri_data.pass.empty()) {
			c_pass = getpass("Enter password: ");
		} else {
			c_pass = uri_data.pass.c_str();
		}

		MYSQL* mysql = mysql_init(NULL);

		// SSL explicitly disabled by user for backend connections
		if (GloVars.global.gr_bootstrap_ssl_mode) {
			if (!strcasecmp(GloVars.global.gr_bootstrap_ssl_mode, "DISABLED")) {
				flags = 0;
			}
		}

		if (flags == CLIENT_SSL) {
			mysql_ssl_set(
				mysql,
				GloVars.global.gr_bootstrap_ssl_key,
				GloVars.global.gr_bootstrap_ssl_cert,
				GloVars.global.gr_bootstrap_ssl_ca,
				GloVars.global.gr_bootstrap_ssl_capath,
				GloVars.global.gr_bootstrap_ssl_cipher
			);
		}

		if (!mysql_real_connect(mysql, c_host, c_user, c_pass, nullptr, port, NULL, flags)) {
			proxy_error("Bootstrap failed, connection error '%s'\n", mysql_error(mysql));
			exit(EXIT_FAILURE);
		}

		if (uri_data.pass.empty()) {
			uint32_t passlen = strlen(c_pass);
			memset(static_cast<void*>(const_cast<char*>(c_pass)), 0, passlen);
		}

		// Get server default collation and version directly from initial handshake
		bootstrap_info.server_language = mysql->server_language;
		bootstrap_info.server_version = mysql->server_version;

		// Fetch all required data for Bootstrap
		int myrc = mysql_query(mysql, BOOTSTRAP_SELECT_SERVERS);

		if (myrc) {
			proxy_error("Bootstrap failed, query failed with error - %s\n", mysql_error(mysql));
			exit(EXIT_FAILURE);
		}

		MYSQL_RES* myres_members = mysql_store_result(mysql);

		if (myres_members == nullptr || mysql_num_rows(myres_members) == 0) {
			proxy_error("Bootstrap failed, expected server %s:%d to have Group Replication configured\n", c_host, port);
			exit(EXIT_FAILURE);
		}

		myrc = mysql_query(mysql, BOOTSTRAP_SELECT_USERS);

		if (myrc) {
			proxy_error("Bootstrap failed, query failed with error - %s\n", mysql_error(mysql));
			exit(EXIT_FAILURE);
		}

		MYSQL_RES* myres_users = mysql_store_result(mysql);

		if (myres_users == nullptr) {
			proxy_error("Bootstrap failed, storing resultset failed with error - %s\n", mysql_error(mysql));
			exit(EXIT_FAILURE);
		}

		// TODO-NOTE: Maybe further data verification should be performed here; bootstrap-info holding final types
		bootstrap_info.servers = myres_members;
		bootstrap_info.users = myres_users;

		// Setup a bootstrap account - monitoring
		const string account_create_policy {
			GloVars.global.gr_bootstrap_account_create == nullptr ? "if-not-exists" :
				GloVars.global.gr_bootstrap_account_create
		};

		if (GloVars.global.gr_bootstrap_account == nullptr && GloVars.global.gr_bootstrap_account_create != nullptr) {
			proxy_error("Bootstrap failed, option '--account-create' can only be used in combination with '--account'\n");
			exit(EXIT_FAILURE);
		}

		const uint32_t password_retries = GloVars.global.gr_bootstrap_password_retries;
		string new_mon_user {};
		string new_mon_pass {};

		if (GloVars.global.gr_bootstrap_account != nullptr) {
			const vector<string> valid_policies { "if-not-exists", "always", "never" };
			if (std::find(valid_policies.begin(), valid_policies.end(), account_create_policy) == valid_policies.end()) {
				proxy_error("Bootstrap failed, unknown '--account-create' option '%s'\n", account_create_policy.c_str());
				exit(EXIT_FAILURE);
			}

			// Since an account has been specified, we require the password for the account
			const string mon_user { GloVars.global.gr_bootstrap_account };
			const string get_acc_pass_msg { "Please enter MySQL password for " + mon_user + ": " };

			// Get the account pass directly from user input
			const string mon_pass = getpass(get_acc_pass_msg.c_str());

			// 1. Check if account exists
			const string get_user_cnt { "SELECT COUNT(*) FROM mysql.user WHERE user='" + mon_user + "'" };
			int cnt_err = mysql_query(mysql, get_user_cnt.c_str());
			MYSQL_RES* myres = mysql_store_result(mysql);

			if (cnt_err || myres == nullptr) {
				proxy_error("Bootstrap failed, detecting count existence failed with error - %s\n", mysql_error(mysql));
				exit(EXIT_FAILURE);
			}

			MYSQL_ROW myrow = mysql_fetch_row(myres);
			uint32_t acc_exists = atoi(myrow[0]);
			mysql_free_result(myres);

			if (account_create_policy == "if-not-exists") {
				// 2. Account doesn't exists, create new account. Otherwise reuse current
				if (acc_exists == 0) {
					pair<int32_t,acct_creds_t> new_creds { create_bootstrap_account(mysql, mon_user, mon_pass) };

					if (new_creds.first) {
						proxy_error("Bootstrap failed, user creation failed with error - %s\n", mysql_error(mysql));
						exit(EXIT_FAILURE);
					} {
						// Store the credentials as the new 'monitor' ones.
						new_mon_user = mon_user;
						new_mon_pass = new_creds.second.pass;
					}
				} else {
					new_mon_user = mon_user;
					new_mon_pass = mon_pass;
				}
			} else if (account_create_policy == "always") {
				if (acc_exists == 0) {
					pair<int32_t,acct_creds_t> new_creds { create_bootstrap_account(mysql, mon_user, mon_pass) };

					if (new_creds.first) {
						proxy_error("Bootstrap failed, user creation failed with error - %s\n", mysql_error(mysql));
						exit(EXIT_FAILURE);
					}
				} else {
					proxy_error(
						"Bootstrap failed, account '%s' already exists but supplied option '--account-create=\"always\"'\n",
						mon_user.c_str()
					);
					exit(EXIT_FAILURE);
				}

				new_mon_user = mon_user;
				new_mon_pass = mon_pass;
			} else if (account_create_policy == "never") {
				if (acc_exists == 0) {
					proxy_error(
						"Bootstrap failed, account '%s' doesn't exists but supplied option '--account-create=\"never\"'\n",
						mon_user.c_str()
					);
					exit(EXIT_FAILURE);
				}

				new_mon_user = mon_user;
				new_mon_pass = mon_pass;
			} else {
				proxy_error("Bootstrap failed, unknown '--account-create' option '%s'\n", account_create_policy.c_str());
				exit(EXIT_FAILURE);
			}
		} else {
			string prev_bootstrap_user {};
			string prev_bootstrap_pass {};

			if (Proxy_file_exists(GloVars.admindb)) {
				SQLite3DB::LoadPlugin(GloVars.sqlite3_plugin);
				SQLite3DB* configdb = new SQLite3DB();
				configdb->open((char*)GloVars.admindb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX);

				{
					const char check_table[] {
						"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='bootstrap_variables'"
					};

					int table_exists = 0;

					char* error = nullptr;
					int cols = 0;
					int affected_rows = 0;
					SQLite3_result* resultset = NULL;

					configdb->execute_statement(check_table, &error , &cols , &affected_rows , &resultset);

					if (error == nullptr && resultset) {
						table_exists = atoi(resultset->rows[0]->fields[0]);
						delete resultset;
					} else {
						const char* err_msg = error != nullptr ? error : "Empty resultset";
						proxy_error("Bootstrap failed, query failed with error - %s", err_msg);
						exit(EXIT_FAILURE);
					}

					if (table_exists != 0) {
						const char query_user_pass[] {
							"SELECT variable_name,variable_value FROM bootstrap_variables"
								" WHERE variable_name='bootstrap_username' OR variable_name='bootstrap_password'"
								" ORDER BY variable_name"
						};
						configdb->execute_statement(query_user_pass, &error, &cols, &affected_rows, &resultset);

						if (resultset->rows.size() != 0) {
							prev_bootstrap_user = resultset->rows[1]->fields[1];
							prev_bootstrap_pass = resultset->rows[0]->fields[1];
						}

						if (resultset) {
							delete resultset;
						}
					}
				}

				delete configdb;
			}

			if (!prev_bootstrap_pass.empty() && !prev_bootstrap_user.empty()) {
				proxy_info(
					"Bootstrap info, detected previous bootstrap user '%s' reusing account...\n",
					prev_bootstrap_user.c_str()
				);

				new_mon_user = prev_bootstrap_user;
				new_mon_pass = prev_bootstrap_pass;
			} else {
				// Create random account with random password
				pair<int32_t,acct_creds_t> mon_creds { create_random_bootstrap_account(mysql, password_retries) };

				if (mon_creds.first) {
					proxy_error(
						"Bootstrap failed, user creation '%s' failed with error - %s\n",
						mon_creds.second.user.c_str(), mysql_error(mysql)
					);
					exit(EXIT_FAILURE);
				} else {
					new_mon_user = mon_creds.second.user;
					new_mon_pass = mon_creds.second.pass;
					bootstrap_info.rand_gen_user = true;
				}
			}
		}

		bootstrap_info.mon_user = new_mon_user;
		bootstrap_info.mon_pass = new_mon_pass;

		mysql_close(mysql);
	}

	{
		cpu_timer t;
		ProxySQL_Main_init_SSL_module();
#ifdef DEBUG
		std::cerr << "Main SSL init variables completed in ";
#endif
	}

	{
		cpu_timer t;
		int fd = -1;
		char buff[PATH_MAX+1];
		ssize_t len = -1;
#if defined(__APPLE__)
		uint32_t bufsize = sizeof(buff);
		if (_NSGetExecutablePath(buff, &bufsize) == 0) {
			// Resolve symlinks to get the real path
			char resolved[PATH_MAX];
			if (realpath(buff, resolved) != NULL) {
				strncpy(buff, resolved, sizeof(buff) - 1);
				buff[sizeof(buff) - 1] = '\0';
			}
			len = strlen(buff);
		}
#elif defined(__FreeBSD__)
		len = readlink("/proc/curproc/file", buff, sizeof(buff)-1);
#else
		len = readlink("/proc/self/exe", buff, sizeof(buff)-1);
#endif
		if (len != -1) {
			buff[len] = '\0';
			fd = open(buff, O_RDONLY);
		}
		if(fd >= 0) {
			struct stat statbuf;
			if(fstat(fd, &statbuf) == 0) {
				unsigned char *fb = (unsigned char *)mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
				if (fb != MAP_FAILED) {
					unsigned char temp[SHA_DIGEST_LENGTH];
					SHA1(fb, statbuf.st_size, temp);
					binary_sha1 = (char *)malloc(SHA_DIGEST_LENGTH*2+1);
					memset(binary_sha1, 0, SHA_DIGEST_LENGTH*2+1);
					char buf[SHA_DIGEST_LENGTH*2 + 1];
					for (int i=0; i < SHA_DIGEST_LENGTH; i++) {
						sprintf((char*)&(buf[i*2]), "%02x", temp[i]);
					}
					memcpy(binary_sha1, buf, SHA_DIGEST_LENGTH*2);
					munmap(fb,statbuf.st_size);
				} else {
					proxy_error("Unable to mmap %s: %s\n", buff, strerror(errno));
				}
			} else {
				proxy_error("Unable to fstat %s: %s\n", buff, strerror(errno));
			}
		} else {
			proxy_error("Unable to open %s: %s\n", argv[0], strerror(errno));
		}
#ifdef DEBUG
		std::cerr << "SHA1 generated in ";
#endif
	}
	if (GloVars.global.foreground==false) {
		{
			cpu_timer t;
			ProxySQL_daemonize_phase1((char *)argv[0]);
#ifdef DEBUG
			std::cerr << "Main daemonize phase1 completed in ";
#endif
		}
	/* Do the fork */
		if ((pid = daemon_fork()) < 0) {
			/* Exit on error */
			daemon_retval_done();
			exit(EXIT_FAILURE);

		} else if (pid) { /* The parent */

			ProxySQL_daemonize_wait_daemon();

		} else { /* The daemon */

			cpu_timer t;
			GloVars.global.start_time=monotonic_time();
			GloVars.install_signal_handler();
			if (ProxySQL_daemonize_phase2()==false) {
				goto finish;
			}

#ifdef DEBUG
			std::cerr << "Main daemonize phase1 completed in ";
#endif
		}

		if (glovars.proxy_restart_on_error) {
			handleProcessRestart();
		}

	} else {
		GloAdmin->flush_error_log();
		GloVars.install_signal_handler();
	}

__start_label:
	{
		cpu_timer t;
		ProxySQL_Main_init_phase2___not_started(bootstrap_info);
#ifdef DEBUG
		std::cerr << "Main init phase2 completed in ";
#endif
	}
	if (glovars.shutdown) {
		goto __shutdown;
	}

	{
		cpu_timer t;
		ProxySQL_Main_init_phase3___start_all();
#ifdef DEBUG
		std::cerr << "Main init phase3 completed in ";
#endif
	}
#ifdef DEBUG
		std::cerr << "WARNING: this is a DEBUG release and can be slow or perform poorly. Do not use it in production" << std::endl;
#endif
	proxy_info("For information about products and services visit: https://proxysql.com/\n");
	proxy_info("For online documentation visit: https://proxysql.com/documentation/\n");
	proxy_info("For support visit: https://proxysql.com/services/support/\n");
	proxy_info("For consultancy visit: https://proxysql.com/services/consulting/\n");

	watchdog_main_loop();

__shutdown:

	proxy_info("Starting shutdown...\n");

	// First shutdown step is to unload plugins
	UnloadPlugins();

	ProxySQL_Main_init_phase4___shutdown();

	proxy_info("Shutdown completed!\n");

	if (glovars.reload) {
		if (glovars.reload==2) {
			GloVars.global.nostart=true;
		}
		glovars.reload=0;
		glovars.shutdown=0;
		goto __start_label;
	}

finish:
	//daemon_log(LOG_INFO, "Exiting...");
	proxy_info("Exiting...\n");
	daemon_retval_send(255);
	daemon_signal_done();
	daemon_pid_file_remove();

//	l_mem_destroy(__thr_sfp);

#ifdef RUNNING_ON_VALGRIND
	if (RUNNING_ON_VALGRIND==0) {
		if (__web_interface) {
			dlclose(__web_interface);
		}
		if (__mysql_ldap_auth) {
			dlclose(__mysql_ldap_auth);
		}
	}
#endif
	return 0;
}
