/*
 *____________________________________________________________
 *
 *		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/common.h"
#include "../jrd/ibase.h"
//#include "../jrd/license.h"
#include "../alice/alice.h"
#include "../alice/alice_meta.h"
#include "../jrd/gds_proto.h"
#include "../include/fb_exception.h"
#include "../common/classes/alloc.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

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 USHORT snarf_blob(ISC_QUAD*, USHORT, TEXT*);


// 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, SLONG 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)
{
	TEXT buffer[1024];
	TEXT* bigger_buffer = 0;
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	const TEXT* p = buffer;
	const USHORT length = snarf_blob(blob_id, (USHORT) sizeof(buffer), buffer);
	if (length)
	{
		p = bigger_buffer = (TEXT *) gds__alloc((SLONG) length);
		if (!p)
		{
			tdgbl->status[0] = isc_arg_gds;
			tdgbl->status[1] = isc_virmemexh;
			tdgbl->status[2] = isc_arg_end;

			ALICE_print_status(true, tdgbl->status);
			return NULL;
		}
		snarf_blob(blob_id, length, bigger_buffer);
	}

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

	// skip version number
	++p;

	tdr* ptr = NULL; // silence uninitialized warning
	SLONG id_length, 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 = gds__vax_integer(reinterpret_cast<const UCHAR*>(p), id_length);
			p += id_length;
			if (!trans) {
				trans = ptr = FB_NEW(*tdgbl->getDefaultPool()) tdr;
			}
			else
			{
				ptr->tdr_next = FB_NEW(*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.

			if (length) {
				gds__free(bigger_buffer);
			}
			return NULL;
		}
	}

	if (length) {
		gds__free(bigger_buffer);
	}

	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 the
 *		size of the blob if the passed buffer
 *		is not big enough.
 */

static USHORT snarf_blob(ISC_QUAD* blob_id, USHORT buffer_length, TEXT* buffer)
{
	AliceGlobals* tdgbl = AliceGlobals::getSpecific();

	if (buffer_length)
		buffer[0] = 0;
	if (buffer_length > 1)
		buffer[1] = 0;

	FB_API_HANDLE blob = 0;
	if (isc_open_blob(gds_status, &DB, &gds_trans, &blob, blob_id))
	{
		ALICE_print_status(true, gds_status);
		return 0;
	}

	// get the blob into the buffer, if it fits

	TEXT* ptr = buffer;
	const TEXT* const end = buffer + buffer_length;
	for (;;)
	{
		if (ptr >= end)
			break;
		if (!(buffer_length = end - ptr))
			break;
		USHORT returned_length;
		ISC_STATUS status = isc_get_segment(gds_status, &blob, &returned_length, buffer_length, ptr);
		if (status && status != isc_segment)
			break;
		ptr += returned_length;
	}

	// snarf failed, get length of blob for retry

	if (!buffer_length)
	{
		for (;;)
		{
			USHORT returned_length;
			ISC_STATUS status =
				isc_get_segment(gds_status, &blob, &returned_length, buffer_length, buffer);
			if (status && status != isc_segment)
				break;
			buffer_length += returned_length;
		}
	}
	else
		buffer_length = 0;

	isc_close_blob(gds_status, &blob);

	*ptr = 0;

	return buffer_length;
}

