/*
 *____________________________________________________________
 *
 *		PROGRAM:	Alice (All Else) Utility
 *		MODULE:		alice_meta.epp
 *		DESCRIPTION:	Metadata lookup routines
 *
 *  The contents of this file are subject to the Interbase Public
 *  License Version 1.0 (the "License"); you may not use this file
 *  except in compliance with the License. You may obtain a copy
 *  of the License at http://www.Inprise.com/IPL.html
 *
 *  Software distributed under the License is distributed on an
 *  "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 *  or implied. See the License for the specific language governing
 *  rights and limitations under the License.
 *
 *  The Original Code was created by Inprise Corporation
 *  and its predecessors. Portions created by Inprise Corporation are
 *  Copyright (C) Inprise Corporation.
 *
 *  All Rights Reserved.
 *  Contributor(s): ______________________________________.
 *
 *
 *____________________________________________________________
 *
 */

#include "firebird.h"
#include <stdio.h>
#include "../jrd/ibase.h"
#include "../alice/alice.h"
#include "../alice/alice_meta.h"
#include "../yvalve/gds_proto.h"
#include "fb_exception.h"
#include "../common/classes/alloc.h"
#include "../common/classes/array.h"
#include "../common/classes/UserBlob.h"
#include "../alice/alice_proto.h"

#include <string.h>

//  For service APIs the follow DB handle is a value stored
//  in thread data.  This is also done for other statics generated by
//  GPRE.  This is to avoid multiple threading problems with module
//  level statics.

DATABASE DB = STATIC FILENAME "yachts.lnk";

#define DB			tdgbl->db_handle
#define gds_trans	tdgbl->tr_handle

typedef Firebird::HalfStaticArray<TEXT, 1024> TextBuffer;

static alice_str* alloc_string(const TEXT**);
static USHORT get_capabilities(ISC_STATUS*);
static tdr* get_description(ISC_QUAD*);
static void parse_fullpath(tdr*);
static bool snarf_blob(ISC_QUAD*, TextBuffer&);


// table used to determine capabilities, checking for specific
// fields in system relations

struct rfr_tab_t
{
	const TEXT* relation;
	const TEXT* field;
	int bit_mask;
};

static const rfr_tab_t rfr_table[] =
{
	{ "RDB$TRANSACTIONS", "RDB$TRANSACTION_DESCRIPTION", CAP_transactions },
	{ 0, 0, 0 }
};

static inline void return_error(const ISC_STATUS* /*user_status*/)
{
	ALICE_print_status(true, gds_status);
	Firebird::LongJump::raise();
}

/*____________________________________________________________
 *
 *		Disable WAL for the database forcibly.
 *		Drop all the entries from RDB$LOG_FILES
 */

void MET_disable_wal(ISC_STATUS* user_status, isc_db_handle handle)
{
	FB_API_HANDLE request = 0;
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	if (!(DB = handle))
		return;

	START_TRANSACTION
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	FOR(REQUEST_HANDLE request)
	X IN RDB$LOG_FILES
		ERASE X;
	END_FOR
	COMMIT
	ON_ERROR
		return_error(user_status);
	END_ERROR;
}


/*____________________________________________________________
 *
 *		Get the state of a transaction,
 *		assuming that the database has
 *		already been attached.
 */

void MET_get_state(ISC_STATUS* user_status, tdr* trans)
{
	FB_API_HANDLE request = 0;
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	if (!(DB = trans->tdr_db_handle) || !(trans->tdr_db_caps & CAP_transactions))
	{
		trans->tdr_state = TRA_unknown;
		return;
	}

	START_TRANSACTION
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	FOR(REQUEST_HANDLE request)
	TRA IN RDB$TRANSACTIONS WITH
		TRA.RDB$TRANSACTION_ID = trans->tdr_id
		trans->tdr_state = TRA.RDB$TRANSACTION_STATE;
	END_FOR
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	isc_release_request(gds_status, &request);
	if (gds_status[1]) {
		return_error(user_status);
	}

	ROLLBACK
	ON_ERROR
		return_error(user_status);
	END_ERROR;
}


/*____________________________________________________________
 *
 *		Get the description of a transaction in
 *		limbo, including all associated transactions
 *		in other databases.
 */

tdr* MET_get_transaction(ISC_STATUS* user_status, isc_db_handle handle, TraNumber id)
{
	FB_API_HANDLE request = 0;
	tdr* trans = NULL;
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	if (!(DB = handle))
		return 0;

	START_TRANSACTION
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	const USHORT capabilities = get_capabilities(user_status);

	if (capabilities & CAP_transactions)
	{
		FOR(REQUEST_HANDLE request)
		TRA IN RDB$TRANSACTIONS WITH
			TRA.RDB$TRANSACTION_ID = id AND
			TRA.RDB$TRANSACTION_DESCRIPTION NOT MISSING
			trans = get_description(&TRA.RDB$TRANSACTION_DESCRIPTION);
		END_FOR
		ON_ERROR
			return_error(user_status);
		END_ERROR;

		isc_release_request(gds_status, &request);
		if (gds_status[1]) {
			return_error(user_status);
		}
	}

	ROLLBACK
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	if (trans)
		trans->tdr_db_caps = capabilities;

	return trans;
}


/*____________________________________________________________
 *
 *		Get the capabilities associated with
 *		the database for a particular transaction.
 */

void MET_set_capabilities(ISC_STATUS* user_status, tdr* trans)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	if (!(DB = trans->tdr_db_handle))
		return;

	START_TRANSACTION
	ON_ERROR
		return_error(user_status);
	END_ERROR;

	trans->tdr_db_caps = get_capabilities(user_status);

	ROLLBACK
	ON_ERROR
		return_error(user_status);
	END_ERROR;
}


/*____________________________________________________________
 *
 *		Eat a string with a byte-encoded length.
 */

static alice_str* alloc_string(const TEXT** ptr)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	const TEXT* p = *ptr;

	const USHORT length = (USHORT) *p++;
	alice_str* string = FB_NEW_RPT(*tdgbl->getDefaultPool(), length + 1) alice_str;
	memcpy(string->str_data, p, length);
	string->str_data[length] = 0;
	*ptr = p + length;

	return string;
}



/*____________________________________________________________
 *
 *		Get the capabilities associated with
 *		the database for a particular transaction.
 */

static USHORT get_capabilities(ISC_STATUS* user_status)
{
	USHORT capabilities = CAP_none;
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	// Look for desired fields in system relations
	FB_API_HANDLE req = 0;

	for (const rfr_tab_t* rel_field_table = rfr_table; rel_field_table->relation; rel_field_table++)
	{
		FOR(REQUEST_HANDLE req) x IN DB.RDB$RELATION_FIELDS
			WITH x.RDB$RELATION_NAME = rel_field_table->relation
			AND x.RDB$FIELD_NAME = rel_field_table->field
			capabilities |= rel_field_table->bit_mask;
		END_FOR
		ON_ERROR
			return_error(user_status);
		END_ERROR;
	}

	isc_release_request(gds_status, &req);
	if (gds_status[1]) {
		return_error(user_status);
	}

	return capabilities;
}


/*____________________________________________________________
 *
 *		Get the description of a transaction in
 *		limbo, including all associated transactions
 *		in other databases.
 */

static tdr* get_description(ISC_QUAD* blob_id)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();
	TextBuffer buffer;

	if (UserBlob::blobIsNull(*blob_id))
		return NULL;

	if (!snarf_blob(blob_id, buffer))
	{
		ALICE_print_status(true, gds_status);
		return NULL;
	}

	if (buffer.getCount() < 2)
		return NULL;

	tdr* trans = NULL;
	alice_str* host_site = NULL;
	alice_str* database_path = NULL;

	const TEXT* p = buffer.begin();

	// skip version number
	++p;

	tdr* ptr = NULL; // silence uninitialized warning
	SLONG id_length;
	TraNumber id;

	while (*p)
	{
		switch (*p++)
		{
		case TDR_HOST_SITE:
			host_site = alloc_string(&p);
			break;

		case TDR_DATABASE_PATH:
			database_path = alloc_string(&p);
			break;

		case TDR_TRANSACTION_ID:
			id_length = *p++;
			id = isc_portable_integer(reinterpret_cast<const UCHAR*>(p), id_length);
			p += id_length;
			if (!trans) {
				trans = ptr = FB_NEW_POOL(*tdgbl->getDefaultPool()) tdr;
			}
			else
			{
				ptr->tdr_next = FB_NEW_POOL(*tdgbl->getDefaultPool()) tdr;
				ptr = ptr->tdr_next;
			}
			ptr->tdr_host_site = host_site;
			ptr->tdr_fullpath = database_path;
			parse_fullpath(ptr);
			ptr->tdr_id = id;
			database_path = NULL;
			break;

		default:
			ALICE_print(108);
			// msg 108: Transaction description item unknown.

			return NULL;
		}
	}

	return trans;
}


/*____________________________________________________________
 *
 *		Take apart a full remote path specification,
 *		finding the filename and the remote site.
 */

static void parse_fullpath(tdr* trans)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	// start at the end of the full pathname

 	const TEXT* p = (TEXT*) trans->tdr_fullpath->str_data;
	const TEXT* const start = p;
	while (*p)
		p++;
	const TEXT* const end = p;

	// Check for a named pipes name - \\node\path\db or //node/path/db
	while (p > start && !(*p == '/' && p[-1] == '/') && !(*p == '\\' && p[-1] == '\\'))
	{
		--p;
	}

	if (p > start)
	{
		// Increment p past slash, & search forward for end of node name
		p = p + 1;
		const TEXT* q = p;

		while (*q && *q != '/' && *q != '\\')
			q++;
		if (*q)
		{
			trans->tdr_filename = q + 1;

			trans->tdr_remote_site = FB_NEW_RPT(*tdgbl->getDefaultPool(), q - p + 1) alice_str;
			strncpy((char*) trans->tdr_remote_site->str_data, (char*) p, q - p);
			trans->tdr_remote_site->str_data[q - p] = '\0';
		}
	}
	else
	{
		p = end;

		// If not named pipes, check the other protocols
		// work backwards until we find a remote protocol specifier


		while (p >= start && (*p != '^' && *p != ':' && *p != '@'))
			p--;
		// dimitr:	make sure that the remote path is parsed correctly
		//			for win32 servers, i.e. the drive separator is taken into account
		if ((p - 2 >= start) && p[-2] == ':' && (p[0] == ':'))
			p -= 2;
		trans->tdr_filename = p + 1;

		// now find the last remote node in the chain

		while (p > start && (*p == ':' || *p == '^' || *p == '@'))
			p--;

		USHORT length = 0;
		for (; p >= start && (*p != '^' && *p != ':' && *p != '@'); ++length)
			--p;
		++p;

		if (length)
		{
			trans->tdr_remote_site = FB_NEW_RPT(*tdgbl->getDefaultPool(), length + 1) alice_str;
			TEXT* q = (TEXT *) trans->tdr_remote_site->str_data;
			while (length--)
				*q++ = *p++;
			*q = 0;
		}
	}
}


/*____________________________________________________________
 *
 *		Get a blob into a buffer, returning false if error happens
 */

static bool snarf_blob(ISC_QUAD* blob_id, TextBuffer& buffer)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	UserBlob blob(gds_status);
	if (!blob.open(DB, gds_trans, *blob_id))
		return false;

	SLONG blob_size;
	if (!getBlobSize(blob, &blob_size, NULL, NULL))
		return false;

	FB_SIZE_T len = blob_size;
	TEXT* ptr = buffer.getBuffer(blob_size + 1);
	while (len)
	{
		FB_SIZE_T real_len;
		if (len && blob.getSegment(len, ptr, real_len))
		{
			len -= real_len;
			ptr += real_len;
		}

		const ISC_STATUS err = blob.getCode();
		if (err == 0 || err == isc_segment)
			continue;

		if (err == isc_segstr_eof)
			break;

		return false;
	}

	blob.close();
	*ptr = 0;

	buffer.resize(ptr - buffer.begin() + 1);

	return true;
}
