/*
 * audit_handler.h
 *
 *  Created on: Feb 6, 2011
 *      Author: guyl
 */

#ifndef AUDIT_HANDLER_H_
#define AUDIT_HANDLER_H_

#include "mysql_inc.h"
#include <yajl/yajl_gen.h>

#ifndef PCRE_STATIC
#define PCRE_STATIC
#endif

#include <pcre.h>

#define AUDIT_LOG_PREFIX "Trellix Audit Plugin:"
#define AUDIT_PROTOCOL_VERSION "1.0"

#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
// For locking we use the native lock routines provided by MySQL.
// The data types and functions for native locking changed at 5.7.x.
// Try to hide this with macros.
#define rw_lock_t	native_rw_lock_t
#define rw_rdlock	native_rw_rdlock
#define rw_wrlock	native_rw_wrlock
#define rw_unlock	native_rw_unlock
#define rwlock_destroy	native_rw_destroy
#define my_rwlock_init(lock, unused)	native_rw_init(lock)
#endif

class THD;

#define MAX_NUM_QUERY_TABLE_ELEM 100
typedef struct _QueryTableInf {
	int num_of_elem;
	char *db[MAX_NUM_QUERY_TABLE_ELEM];
	char *table_name[MAX_NUM_QUERY_TABLE_ELEM];
	const char *object_type[MAX_NUM_QUERY_TABLE_ELEM];
} QueryTableInf;

#define MAX_NUM_QUEUE_ELEM 1024
typedef struct _THDPRINTED {
	size_t cur_index;
	char is_thd_printed_queue[MAX_NUM_QUEUE_ELEM];
} THDPRINTED;

struct PeerInfo {
	unsigned long pid;
	enum { MAX_APP_NAME_LEN = 128, MAX_USER_NAME_LEN = 128 };
	char appName[MAX_APP_NAME_LEN + 1];
	char osUser[MAX_USER_NAME_LEN + 1];	// allow lots, in case from LDAP or some such
	PeerInfo() : pid(0) {
		memset(appName, 0, sizeof appName);
		memset(osUser, 0, sizeof osUser);
	}
};

PeerInfo *retrieve_peerinfo(THD *thd);

const char *retrieve_command(THD *thd, bool& is_sql_cmd);
typedef size_t OFFSET;

#define MAX_COMMAND_CHAR_NUMBERS 40
#define MAX_COM_STATUS_VARS_RECORDS 512

// mysql max identifier is 64 so 2*64 + . and null
#define MAX_OBJECT_CHAR_NUMBERS 131
#define MAX_USER_CHAR_NUMBERS 32
#define MAX_NUM_OBJECT_ELEM 256
#define MAX_NUM_USER_ELEM 256

/**
 * The struct used to hold offsets. We should have one per version.
 */
typedef struct ThdOffsets {
	const char *version;
	const char *md5digest;
	OFFSET query_id;
	OFFSET thread_id;
	OFFSET main_security_ctx;
	OFFSET command;
	OFFSET lex;
	OFFSET lex_comment;
	OFFSET sec_ctx_user;
	OFFSET sec_ctx_host;
	OFFSET sec_ctx_ip;
	OFFSET sec_ctx_priv_user;
	OFFSET db;
	OFFSET killed;
	OFFSET client_capabilities;
	OFFSET pfs_connect_attrs;
	OFFSET pfs_connect_attrs_length;
	OFFSET pfs_connect_attrs_cs;
	OFFSET net;
	OFFSET lex_m_sql_command;
	OFFSET uninstall_cmd_comment;
	OFFSET found_rows;
	OFFSET sent_row_count;
	OFFSET row_count_func;
	OFFSET stmt_da;
	OFFSET da_status;
	OFFSET da_sql_errno;
	OFFSET view_tables;
} ThdOffsets;

/*
 * The offsets array
 */
extern const ThdOffsets thd_offsets_arr[];
extern const size_t thd_offsets_arr_size;

/*
 * On  success,  the  number of bytes written are returned (zero indicates nothing was written).  On error, -1 is returned,
 */
typedef ssize_t (*audit_write_func)(const char *, size_t);


/**
 * Interface for an io writer
 */
class IWriter {
public:
	virtual ~IWriter() {}
	// return negative on fail
	virtual ssize_t write(const char *data, size_t size) = 0;
	virtual ssize_t write_no_lock(const char *str, size_t size) = 0;
	// return 0 on success
	virtual int open(const char *io_dest, bool log_errors) = 0;
	virtual void close() = 0;
};

class ThdSesData {
public:
	// enum indicating from where the object list came from
	enum ObjectIterType { OBJ_NONE, OBJ_DB, OBJ_QUERY_CACHE, OBJ_TABLE_LIST };
	// enum indicating source of statement
	typedef enum { SOURCE_GENERAL, SOURCE_QUERY_CACHE } StatementSource;
	ThdSesData(THD *pTHD, StatementSource source = SOURCE_GENERAL);
	THD *getTHD() const { return m_pThd;}
	const char *getCmdName() const { return m_CmdName; }
	void setCmdName(const char *cmd) { m_CmdName = cmd; }
	const char *getUserName() { return m_UserName; }
	const unsigned long getPeerPid() const;
	const char *getAppName() const;
	const char *getOsUser() const;
	const int getPort() const { return m_port; }
	const StatementSource getStatementSource() const { return m_source; }
	void storeErrorCode();
	void setErrorCode(uint code) { m_errorCode = code; m_setErrorCode = true; }
	bool getErrorCode(uint & code) const { code = m_errorCode; return m_setErrorCode; }
	/**
	 * Start fetching objects. Return true if there are objects available.
	 */
	bool startGetObjects();
	/**
	 * Get next object. Return true if populated. False if there isn't an
	 * object available.
	 * Will point the passed pointers to point to db, name and type.
	 * obj_type is optional and may be null.
	 */
	bool getNextObject(const char **db_name, const char **obj_name, const char **obj_type);

private:
	THD *m_pThd;
	const char *m_CmdName;
	const char *m_UserName;
	bool m_isSqlCmd;
	enum ObjectIterType m_objIterType;
	// pointer for iterating tables
	TABLE_LIST *m_tables;
	// indicator if we are at the first table
	bool m_firstTable;
	// used for query cache iter
	QueryTableInf *m_tableInf;
	int m_index;

	// Statement source
	StatementSource m_source;

	PeerInfo *m_peerInfo;

	int m_port;	// TCP port of remote side

	uint m_errorCode;
	bool m_setErrorCode;

protected:
	ThdSesData(const ThdSesData&);
	ThdSesData &operator =(const ThdSesData&);
};

/**
 * Base for audit formatter
 */
class Audit_formatter {
public:
	virtual ~Audit_formatter() {}

	/**
	 * static offsets to use for fetching THD data.
	 * Set by the audit plugin during startup.
	 */
	static ThdOffsets thd_offsets;

	/**
	 * Format an audit event from the passed THD.
	 * Will write out its output using the audit_write_func.
	 *
	 * @return -1 on a failure
	 */
	virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer) = 0;
	/**
	 * Format a message when handler is started
	 * @return -1 on a failure
	 */
	virtual ssize_t start_msg_format(IWriter *writer) { return 0; }
	/**
	 * Format a message when handler is stopped
	 * @return -1 on a failure
	 */
	virtual ssize_t stop_msg_format(IWriter *writer) { return 0; }

	static const char *retrieve_object_type(TABLE_LIST *pObj);
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 80000
	static QueryTableInf *getQueryCacheTableList1(THD *thd);
#endif

	// utility functions for fetching thd stuff
	static int thd_client_port(THD *thd);

	static inline my_thread_id thd_inst_thread_id(THD *thd)
	{
		return *(my_thread_id *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.thread_id);
	}
	static inline query_id_t thd_inst_query_id(THD *thd)
	{
		return *(query_id_t *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.query_id);
	}
	static inline Security_context *thd_inst_main_security_ctx(THD *thd)
	{
		return (Security_context *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.main_security_ctx);
	}

	static inline const char *thd_db(THD *thd)
	{
		if (! Audit_formatter::thd_offsets.db) // no offsets use compiled in header
		{
#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID < 50709
			return thd->db;
#elif defined(MARIADB_BASE_VERSION)
#if MYSQL_VERSION_ID >= 100307
			return thd->db.str;
#else
			return thd->db;
#endif
#else
			return thd->db().str;
#endif
		}
		return *(const char **) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.db);
	}

	static inline int thd_killed(THD *thd)
	{
		if (! Audit_formatter::thd_offsets.killed) // no offsets use thd_killed function
		{
			return ::thd_killed(thd);
		}
		return *(int *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.killed);
	}

	static inline const char *thd_inst_main_security_ctx_user(THD *thd)
	{
		Security_context *sctx = thd_inst_main_security_ctx(thd);
		if (! Audit_formatter::thd_offsets.sec_ctx_user) // no offsets use compiled in header
		{
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
			return sctx->user;
#else
			return sctx->user().str;
#endif
		}
		return *(const char **) (((unsigned char *) sctx)
				+ Audit_formatter::thd_offsets.sec_ctx_user);
	}

	static inline const char *thd_inst_main_security_ctx_host(THD *thd)
	{
		Security_context *sctx = thd_inst_main_security_ctx(thd);
		// check ip to understand if set, as host is first in the struct and may actually be set to 0
		// we expect to have offsets for both ip and host or for neither of them
		if (! Audit_formatter::thd_offsets.sec_ctx_ip)
		{
			// interface changed in 5.5.34 and 5.6.14 and up host changed to get_host()
			// see: http://bazaar.launchpad.net/~mysql/mysql-server/5.5/revision/4407.1.1/sql/sql_class.h
#if defined(MARIADB_BASE_VERSION)
			return sctx->host;
#else
			// MySQL
#if  MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
			return sctx->host;
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
	|| (MYSQL_VERSION_ID >= 50614 &&  MYSQL_VERSION_ID < 50709)
			return sctx->get_host()->ptr();
#else
			// interface changed again in 5.7
			return sctx->host().str;
#endif
#endif // ! defined(MARIADB_BASE_VERSION)
		}
		return *(const char **) (((unsigned char *) sctx)
				+ Audit_formatter::thd_offsets.sec_ctx_host);
	}

	static inline const char *thd_inst_main_security_ctx_ip(THD *thd)
	{
		Security_context *sctx = thd_inst_main_security_ctx(thd);
		if (! Audit_formatter::thd_offsets.sec_ctx_ip) // no offsets use compiled in header
		{
			// interface changed in 5.5.34 and 5.6.14 and up host changed to get_ip()
#if defined(MARIADB_BASE_VERSION)
			return sctx->ip;
#else
			// MySQL
#if  MYSQL_VERSION_ID < 50534 || (MYSQL_VERSION_ID >= 50600 && MYSQL_VERSION_ID < 50614)
			return sctx->ip;
#elif (MYSQL_VERSION_ID >= 50534 && MYSQL_VERSION_ID < 50600) \
	|| (MYSQL_VERSION_ID >= 50614 &&  MYSQL_VERSION_ID < 50709)
			return sctx->get_ip()->ptr();
#else
			// interface changed again in 5.7
			return sctx->ip().str;
#endif
#endif // ! defined(MARIADB_BASE_VERSION)
		}
		return *(const char **) (((unsigned char *) sctx)
				+ Audit_formatter::thd_offsets.sec_ctx_ip);
	}

	static inline const char *thd_inst_main_security_ctx_priv_user(THD *thd)
	{
		Security_context *sctx = thd_inst_main_security_ctx(thd);
		if (! Audit_formatter::thd_offsets.sec_ctx_priv_user) // no offsets use compiled in header
		{
#if defined(MARIADB_BASE_VERSION) || MYSQL_VERSION_ID < 50709
			return sctx->priv_user;
#else
			return sctx->priv_user().str;
#endif
		}
		// in 5.5 and up priv_user is an array (char priv_user[USERNAME_LENGTH])
		return (const char *) (((unsigned char *) sctx)
				+ Audit_formatter::thd_offsets.sec_ctx_priv_user);
	}

	static inline int thd_inst_command(THD *thd)
	{
		return *(int *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.command);
	}

	static inline LEX *thd_lex(THD *thd)
	{
		return *(LEX **) (((unsigned char *) thd) + Audit_formatter::thd_offsets.lex);
	}

#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
	//in mysql 5.7 capabilities flag moved to protocol. We use the capabilities offset to point to m_protocol
	//and get from protocol the capabilities flag
	static inline ulong thd_client_capabilities(THD *thd)
	{
		if (! Audit_formatter::thd_offsets.client_capabilities)
		{
			//no offsets - return 0
			return 0;
		}
		Protocol * prot = *(Protocol **) (((unsigned char *) thd) + Audit_formatter::thd_offsets.client_capabilities);
		if(!prot)
		{
			return 0;
		}
		return prot->get_client_capabilities();
	}
#else
	static inline ulong thd_client_capabilities(THD *thd)
	{
		if (! Audit_formatter::thd_offsets.client_capabilities)
		{
			//no offsets - return 0
			return 0;
		}
		return *(ulong *) (((unsigned char *) thd) + Audit_formatter::thd_offsets.client_capabilities);
	}
#endif

	static inline const char * pfs_connect_attrs(const void * pfs)
	{
		if (! Audit_formatter::thd_offsets.pfs_connect_attrs || pfs == NULL)
		{
			//no offsets - return null
			return NULL;
		}
		const char **pfs_pointer = (const char **) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs);

		return *pfs_pointer;
	}

	static inline uint pfs_connect_attrs_length(const void * pfs)
	{
		if (! Audit_formatter::thd_offsets.pfs_connect_attrs_length || pfs == NULL)
		{
			//no offsets - return 0
			return 0;
		}
		return *(uint *) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_length);
	}
  
static inline const CHARSET_INFO * pfs_connect_attrs_cs(const void * pfs)
{
	if (! Audit_formatter::thd_offsets.pfs_connect_attrs_cs || pfs == NULL)
	{
		//no offsets - return null
		return NULL;
	}    
#if (!defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50600) || (defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100010)
	/**
	 * m_session_connect_attrs_cs changed to: m_session_connect_attrs_cs_number
	 * in 5.6.15 and up, 5.7 and mariadb 10.0.11 and up, and 10.1.
	 * see: storage/perfschema/table_session_connect.cc
	 * and: storage/perfschema/pfs_instr.h
	 */
	static bool first = true;
	static int major, minor, patch;

	if (first)
	{
		sscanf(server_version, "%d.%d.%d", & major, & minor, & patch);
		// sql_print_information("Audit_plugin: extracted version: %d.%d.%d",
		//			major, minor, patch);
	}

	if (   ( major == 5  && ( (minor == 6 && patch >= 15) || minor >= 7) ) || (major == 8)	// MySQL
	    || ( major == 10 && ( (minor == 0 && patch >= 11) || minor >= 1) ) )	// MariaDB
	{
		uint cs_number = *(uint *) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_cs);
		if (!cs_number)
		{
			return NULL;

		} 
		return get_charset(cs_number, MYF(0));
	}
	else
#endif
	{
		return *(const CHARSET_INFO **) (((unsigned char *) pfs) + Audit_formatter::thd_offsets.pfs_connect_attrs_cs);
	}
}

	static inline int thd_client_fd(THD *thd)
	{
		if (! Audit_formatter::thd_offsets.net)
		{
			return -1;
		}
		NET *net = ((NET *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.net));
		// get the socket for the peer
		int sock = -1;
		if (net->vio != NULL)	// MySQL 5.7.17 - this can happen. :-(
		{
#if MYSQL_VERSION_ID < 50600
			sock = net->vio->sd;
#else
			sock = net->vio->mysql_socket.fd;
#endif
		}

		return sock;
	}

	static inline ulonglong thd_found_rows(THD *thd)
	{
		if (Audit_formatter::thd_offsets.found_rows == 0)
		{
			return 0;
		}

		ulonglong *rows = ((ulonglong *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.found_rows));

		return  *rows;
	}

	static inline unsigned long thd_sent_row_count(THD *thd)
	{
		if (Audit_formatter::thd_offsets.sent_row_count == 0)
		{
			return 0;
		}

		ha_rows *rows = ((ha_rows *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.sent_row_count));

		return (unsigned long) *rows;
	}

	static inline longlong thd_row_count_func(THD *thd)
	{
		if (Audit_formatter::thd_offsets.row_count_func == 0)
		{
			return -1;
		}

		longlong *rows = ((longlong *) (((unsigned char *) thd)
				+ Audit_formatter::thd_offsets.row_count_func));

		return *rows;
	}

	static inline bool thd_error_code(THD *thd, uint & code)
	{
#if MYSQL_VERSION_ID >= 50534

		if ( Audit_formatter::thd_offsets.stmt_da == 0      ||
		     Audit_formatter::thd_offsets.da_status == 0    ||
		     Audit_formatter::thd_offsets.da_sql_errno == 0 )
		{
			return false;
		}

		Diagnostics_area **stmt_da = ((Diagnostics_area **) (((unsigned char *) thd)
						+ Audit_formatter::thd_offsets.stmt_da));

		enum Diagnostics_area::enum_diagnostics_status *status =
			((enum Diagnostics_area::enum_diagnostics_status *) (((unsigned char *) (*stmt_da))
						+ Audit_formatter::thd_offsets.da_status));

		uint *sql_errno = ((uint *) (((unsigned char *) (*stmt_da))
						+ Audit_formatter::thd_offsets.da_sql_errno));

		if (*status == Diagnostics_area::DA_OK  ||
			*status == Diagnostics_area::DA_EOF	)
		{
			code = 0;
			return true;
		}
		else if (*status == Diagnostics_area::DA_ERROR)
		{
			code = *sql_errno;
			return true;
		}
		else // DA_EMPTY, DA_DISABLE
		{
			return false;
		}
#else
		return false;
#endif
	}

#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709
	static inline Sql_cmd_uninstall_plugin* lex_sql_cmd(LEX *lex)
	{
		return *(Sql_cmd_uninstall_plugin **) (((unsigned char *) lex) + Audit_formatter::thd_offsets.lex_m_sql_command);
	}
#endif

	// we don't use get_db_name() as when we call it view may be not null
	// and it may return an invalid value for view_db
	static inline const char *table_get_db_name(TABLE_LIST *table)
	{
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100307
		return table->db.str;
#else
		return table->db;
#endif
	}

	static inline const char *table_get_name(TABLE_LIST *table)
	{
#if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100307
		return table->table_name.str;
#else
		return table->table_name;
#endif
	}

	static inline bool table_is_view(TABLE_LIST *table)
	{
		if (!Audit_formatter::thd_offsets.view_tables)
			return table->view_tables != 0;
		List<TABLE_LIST> **view_tables = (List<TABLE_LIST> **)((char*)table + Audit_formatter::thd_offsets.view_tables);
		return *view_tables;
	}
};


/**
 * Format the audit even in json format
 */
class Audit_json_formatter: public Audit_formatter {
public:
	static const char *DEF_MSG_DELIMITER;

	Audit_json_formatter()
		: m_write_start_msg(true),
		m_write_sess_connect_attrs(true),
		m_write_client_capabilities(false),
		m_write_socket_creds(true),
		m_perform_password_masking(NULL),
		m_msg_delimiter(NULL),
		m_password_mask_regex_compiled(false),
		m_password_mask_regex_preg(NULL)
	{

	}

	virtual ~Audit_json_formatter()
	{
		if (m_password_mask_regex_preg)
		{
			m_password_mask_regex_compiled = false;
			pcre_free(m_password_mask_regex_preg);
			m_password_mask_regex_preg = NULL;
		}
	}

	virtual ssize_t event_format(ThdSesData *pThdData, IWriter *writer);
	virtual ssize_t start_msg_format(IWriter *writer);

	/**
	 * Utility method used to compile a regex program.
	 * Will compile and log errors if necessary.
	 * Return null if fails
	 */
	static pcre *regex_compile(const char *str);

	/**
	 * Compile password masking regex
	 * Return true on success
	 */
	bool compile_password_masking_regex(const char *str);

	/**
	 * Boolean indicating if to log start msg.
	 * Public so sysvar can update.
	 */
	my_bool m_write_start_msg;
	
	/**
	 * include session oonnect attributes
	 * Public so sysvar can update
	 */
	my_bool m_write_sess_connect_attrs;

	/**
	* include client capabilities
	* Public for sysvar
	*/
	my_bool m_write_client_capabilities;

	/**
	 * include socket credentials from Unix Domain Socket
	 * Public for sysvar
	 */
	my_bool m_write_socket_creds;

	/**
	 * Callback function to determine if password masking should be performed
	 */
	my_bool (*m_perform_password_masking)(const char *cmd);

	/**
	 * Message delimiter. Should point to a valid json string
	 * (supporting the json escapping format).
	 * Will only be checked at the start. Public so can be set by sysvar.
	 *
	 * We only support a delimiter up to 32 chars
	 */
	char *m_msg_delimiter;

protected:

	Audit_json_formatter& operator =(const Audit_json_formatter& b);
	Audit_json_formatter(const Audit_json_formatter& );

	/**
	 * Boolean indicating if password masking regex is compiled
	 */
	my_bool m_password_mask_regex_compiled;

	/**
	 * Regex used for password masking
	 */
	pcre *m_password_mask_regex_preg;
};

/**
 * Base class for audit handlers. Provides basic locking setup.
 */
class Audit_handler {
public:
	static const size_t MAX_AUDIT_HANDLERS_NUM = 4;
	static const size_t JSON_FILE_HANDLER = 1;
	static const size_t JSON_SOCKET_HANDLER = 3;

	static Audit_handler *m_audit_handler_list[];

	/**
	 * Will iterate the handler list and log using each handler
	 */
	static void log_audit_all(ThdSesData *pThdData);

	/**
	 * Will iterate the handler list and stop all handlers
	 */
	static void stop_all();

	Audit_handler() :
		m_formatter()
		,m_initialized()
		,m_enabled()
		,m_failed()
		,m_log_io_errors(true)
		,m_print_offset_err(true)
	{
	}

	virtual ~Audit_handler()
	{
		if (m_initialized)
		{
			rwlock_destroy(&LOCK_audit);
			pthread_mutex_destroy(&LOCK_io);
		}
	}

	/**
	 * Should be called to initialize.
	 * We don't init in constructor in order to provide indication if
	 * pthread stuff failed init.
	 *
	 * @frmt the formatter to use in this handler (does not manage
	 * destruction of this object)
	 * @return 0 on success
	 */
	int init(Audit_formatter *frmt)
	{
		m_formatter = frmt;
		if (m_initialized)
		{
			return 0;
		}

		int res = my_rwlock_init(&LOCK_audit, NULL);
		if (res)
		{
			return res;
		}

		res = pthread_mutex_init(&LOCK_io, MY_MUTEX_INIT_SLOW);;
		if (res)
		{
			return res;
		}

		m_initialized = true;
		return res;
	}

	bool is_init()
	{
		return m_initialized;
	}

	void set_enable(bool val);

	bool is_enabled()
	{
		return m_enabled;
	}

	/**
	 * will close and start the handler
	 */
	void flush();

	/**
	 * Will get relevant shared lock and call internal method of handler
	 */
	void log_audit(ThdSesData *pThdData);

	/**
	 * Public so can be configured via sysvar
	 */
	unsigned int m_retry_interval;

protected:
	Audit_formatter *m_formatter;
	virtual void handler_start();
	// wiil call internal method and set failed as needed
	bool handler_start_nolock();
	virtual void handler_stop();
	virtual bool handler_start_internal() = 0;
	virtual void handler_stop_internal() = 0;
	virtual bool handler_log_audit(ThdSesData *pThdData) = 0;
	bool m_initialized;
	bool m_enabled;
	bool m_failed;
	bool m_log_io_errors;
	time_t m_last_retry_sec_ts;
	inline void set_failed()
	{
		time(&m_last_retry_sec_ts);
		m_failed = true;
		m_log_io_errors = false;
	}
	inline bool is_failed_now()
	{
		return m_failed && (m_retry_interval < 0 ||
				difftime(time(NULL), m_last_retry_sec_ts) > m_retry_interval);
	}
	// override default assignment and copy to protect against
	// creating additional instances
	Audit_handler & operator=(const Audit_handler&);
	Audit_handler(const Audit_handler&);
	// lock io 
	pthread_mutex_t LOCK_io;
private:
	// bool indicating if to print offset errors to log or not
	bool m_print_offset_err;	
	// audit (enable) lock
	rw_lock_t LOCK_audit;
	inline void lock_shared()
	{
		rw_rdlock(&LOCK_audit);
	}
	inline void lock_exclusive()
	{
		rw_wrlock(&LOCK_audit);
	}
	inline void unlock()
	{
		rw_unlock(&LOCK_audit);
	}
};

/**
 * Base class for handler which have io and need a lock
 */
class Audit_io_handler: public Audit_handler, public IWriter {
public:
	Audit_io_handler()
		: m_io_dest(NULL), m_io_type(NULL)
	{
	}

	virtual ~Audit_io_handler()
	{
	}


	/**
	 * target we write to (socket/file). Public so we update via sysvar
	 */
	char *m_io_dest;
	
	inline ssize_t write(const char *data, size_t size) 
	{
		pthread_mutex_lock(&LOCK_io);
		ssize_t res = write_no_lock(data, size);
		pthread_mutex_unlock(&LOCK_io);	//release the IO lock
		return res;
	}		

protected:
	/**
	 * Will format using the writer
	 */
	virtual bool handler_log_audit(ThdSesData *pThdData);
	virtual bool handler_start_internal();
	virtual void handler_stop_internal();
	// used for logging messages
	const char *m_io_type;
};

class Audit_file_handler: public Audit_io_handler {
public:

	Audit_file_handler() :
		m_sync_period(0)
		, m_bufsize(0)
		, m_log_file(NULL)
		, m_sync_counter(0)
	{
		m_io_type = "file";
	}

	virtual ~Audit_file_handler()
	{
	}

	/**
	 * The period to use for syncing to the file system. 0 means we don't sync.
	 * 1 means each write we sync. Larger than 1 means every sync_period we sync.
	 *
	 * We leave this public so the mysql sysvar function can update this variable directly.
	 */
	unsigned int m_sync_period;

	/**
	 * The buf size used by the file stream. 0 = use default,
	 * negative or 1 = no buffering
	 */
	long m_bufsize;

	/**
	 * Write function we pass to formatter
	 */
	ssize_t write_no_lock(const char *data, size_t size);

	void close();

	int open(const char *io_dest, bool m_log_errors);
	// static void print_sleep(THD *thd, int delay_ms);
protected:
	// override default assignment and copy to protect against creating
	// additional instances
	Audit_file_handler & operator=(const Audit_file_handler&);
	Audit_file_handler(const Audit_file_handler&);
	
	FILE *m_log_file;
	// the period to use for syncing
	unsigned int m_sync_counter;
};

class Audit_socket_handler: public Audit_io_handler {
public:

	Audit_socket_handler() :
		m_connect_timeout(1)
		, m_write_timeout()
		, m_vio()
		, m_log_with_error_severity()
	{
		m_io_type = "socket";
	}

	virtual ~Audit_socket_handler()
	{
	}


	/**
	 * Connect timeout in secconds
	 */
	unsigned int m_connect_timeout;

	/**
	 * Write function we pass to formatter
	 */
	ssize_t write_no_lock(const char *data, size_t size);

	void close();

	int open(const char *io_dest, bool log_errors);

	unsigned long m_write_timeout; // write timeout in microseconds
protected:
	// override default assignment and copy to protect against creating additional instances
	Audit_socket_handler & operator=(const Audit_socket_handler&);
	Audit_socket_handler(const Audit_socket_handler&);
	
	// Vio we write to
	// define as void* so we don't access members directly
	void *m_vio;

	// log using error severity only the second time same issue occurs
	bool m_log_with_error_severity;
};

#endif /* AUDIT_HANDLER_H_ */
