/*
 *	PROGRAM:	JRD Access Method
 *	MODULE:		utl.c
 *	DESCRIPTION:	User callable 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): ______________________________________.
 *
 * 2001.06.14 Claudio Valderrama: isc_set_path() will append slash if missing,
 *            so ISC_PATH environment variable won't fail for this cause.
 * 2002.02.15 Sean Leyne - Code Cleanup is required of obsolete "EPSON", "XENIX" ports
 * 2002.02.15 Sean Leyne - Code Cleanup, removed obsolete "Apollo" port
 * 23-Feb-2002 Dmitry Yemanov - Events wildcarding
 *
 * 2002.10.27 Sean Leyne - Completed removal of obsolete "DG_X86" port
 * 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "UNIXWARE" port
 * 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix" port
 * 2002.10.27 Sean Leyne - Code Cleanup, removed obsolete "Ultrix/MIPS" port
 *
 * 2002.10.28 Sean Leyne - Code cleanup, removed obsolete "MPEXL" port
 *
 * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
 *
 * 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define
 *
 */

#ifdef SHLIB_DEFS
#define LOCAL_SHLIB_DEFS
#endif

#include "firebird.h"
#include <limits.h>
#include "../jrd/ib_stdio.h"
#include <stdlib.h>
#include <string.h>
#include "../jrd/common.h"
#include "../jrd/license.h"
#include <stdarg.h>
#include "../jrd/jrd_time.h"
#include "../jrd/misc.h"
#include "../jrd/gdsassert.h"

#include "../jrd/gds.h"
#include "../jrd/msg.h"
#include "../jrd/event.h"
#include "../jrd/gds_proto.h"
#include "../jrd/utl_proto.h"
#ifdef REPLAY_OSRI_API_CALLS_SUBSYSTEM
#include "../jrd/blb_proto.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef VMS
#include <file.h>
#include <ib_perror.h>
#include <descrip.h>
#include <types.h>
#include <stat.h>

#else /* !VMS */

#include <sys/types.h>
#include <sys/stat.h>

#if defined(WIN_NT)
#include <io.h>
#include <process.h>
#else
#include <sys/file.h>
#endif

#endif /* VMS */

#define statistics		stat

#ifdef UNIX
#define GDS_EDIT	gds__edit
#endif

#if defined(WIN_NT)
#define GDS_EDIT	gds__edit
#endif

#ifdef VMS
#ifdef __ALPHA
#define EDT_IMAGE	"TPUSHR"
#define EDT_SYMBOL	"TPU$EDIT"
#else
#define EDT_IMAGE	"EDTSHR"
#define EDT_SYMBOL	"EDT$EDIT"
#endif
#endif

/* Bug 7119 - BLOB_load will open external file for read in BINARY mode. */

#ifdef WIN_NT
#define FOPEN_READ_TYPE		"rb"
#define FOPEN_WRITE_TYPE	"wb"
#define FOPEN_READ_TYPE_TEXT	"rt"
#define FOPEN_WRITE_TYPE_TEXT	"wt"
#endif

#ifndef FOPEN_READ_TYPE
#define FOPEN_READ_TYPE		"r"
#endif

#ifndef FOPEN_WRITE_TYPE
#define FOPEN_WRITE_TYPE	"w"
#endif

#ifndef FOPEN_READ_TYPE_TEXT
#define FOPEN_READ_TYPE_TEXT	FOPEN_READ_TYPE
#endif

#ifndef FOPEN_WRITE_TYPE_TEXT
#define FOPEN_WRITE_TYPE_TEXT	FOPEN_WRITE_TYPE
#endif

#define LOWER7(c) ( (c >= 'A' && c<= 'Z') ? c + 'a' - 'A': c )


/* Blob stream stuff */

#define BSTR_input	0
#define BSTR_output	1
#define BSTR_alloc	2

extern "C" {

static int dump(GDS_QUAD *, FRBRD *, FRBRD *, IB_FILE *);
static int edit(GDS_QUAD *, FRBRD *, FRBRD *, SSHORT, SCHAR *);
static int get_ods_version(FRBRD **, USHORT *, USHORT *);
static int load(GDS_QUAD *, FRBRD *, FRBRD *, IB_FILE *);


#ifdef VMS
static int display(GDS_QUAD *, int *, int *);
#endif

/* Blob info stuff */

static const SCHAR blob_items[] =
	{ gds_info_blob_max_segment, gds_info_blob_num_segments,
	gds_info_blob_total_length
};


/* gds__version stuff */

static const UCHAR info[] =
	{ isc_info_firebird_version, isc_info_implementation, isc_info_end };

static const UCHAR ods_info[] =
	{ isc_info_ods_version, isc_info_ods_minor_version, isc_info_end };

static const TEXT *const impl_class[] = {
	NULL,						/* 0 */
	"access method",			/* 1 */
	"Y-valve",					/* 2 */
	"remote interface",			/* 3 */
	"remote server",			/* 4 */
	NULL,						/* 5 */
	NULL,						/* 6 */
	"pipe interface",			/* 7 */
	"pipe server",				/* 8 */
	"central interface",		/* 9 */
	"central server",			/* 10 */
	"gateway",					/* 11 */
	"classic server",		/* 12 */
	"super server"			/* 13 */
};

static const TEXT *const impl_implementation[] = {
	NULL,						/* 0 */
    "Rdb/VMS",				/* 1 */
    "Rdb/ELN target",		/* 2 */
    "Rdb/ELN development",	/* 3 */
    "Rdb/VMS Y",			/* 4 */
    "Rdb/ELN Y",			/* 5 */
    "JRI",					/* 6 */
    "JSV",					/* 7 */
    NULL,					/* 8 */
    NULL,					/* 9 */
    NULL, // "Firebird/apollo",     /* 10 */
    NULL, // "Firebird/ultrix",		/* 11 */
    "Firebird/vms",		/* 12 */
    "Firebird/sun",		/* 13 */
    NULL, // "Firebird/OS2",        /* 14 */
    NULL,					/* 15 */
    NULL,					/* 16 */
    NULL,					/* 17 */
    NULL,					/* 18 */
    NULL,					/* 19 */
    NULL,					/* 20 */
    NULL,					/* 21 */
    NULL,					/* 22 */
    NULL,					/* 23 */
    NULL,					/* 24 */
    NULL, // "Firebird/apollo",     /* 25 */
    NULL, // "Firebird/ultrix",		/* 26 */
    "Firebird/vms",		/* 27 */
    "Firebird/sun",		/* 28 */
    NULL, // "Firebird/OS2",        /* 29 */
    "Firebird/sun4",		/* 30 */
    NULL, // "Firebird/hpux800",	/* 31 */
    "Firebird/sun386",		/* 32 */
    "Firebird:ORACLE/vms",	/* 33 */
    NULL, // "Firebird/mac/aux",    /* 34 */
    "Firebird/ibm/aix",	/* 35 */
    NULL, // "Firebird/mips/ultrix",	/* 36 */
    NULL, // "Firebird/xenix",      /* 37 */
    NULL, // "Firebird/AViiON",		/* 38 */
    NULL, // "Firebird/hp/mpexl",	/* 39 */
    NULL, // "Firebird/hp/ux300",	/* 40 */
    NULL, // "Firebird/sgi",		/* 41 */
    "Firebird/sco/unix",	/* 42 */
    NULL, // "Firebird/Cray",       /* 43 */
    NULL, // "Firebird/imp",        /* 44 */
    NULL, // "Firebird/delta",      /* 45 */
    NULL, // "Firebird/NeXT",       /* 46 */
    NULL, // "Firebird/DOS",		/* 47 */
    NULL, // "Firebird/m88k",       /* 48 */
    NULL, // "Firebird/UNIXWARE",	/* 49 */
    "Firebird/x86/Windows NT",	/* 50 */
    NULL, // "Firebird/epson",      /* 51 */
    NULL, // "Firebird/DEC/OSF",	/* 52 */
    "Firebird/Alpha/OpenVMS",	/* 53 */
    NULL, // "Firebird/NetWare",	/* 54 */
    "Firebird/Windows",	/* 55 */
    NULL, // "Firebird/NCR3000",    /* 56 */
    NULL, // "Firebird/PPC/Windows NT", /* 57 */
    NULL, // "Firebird/DG_X86",		/* 58 */
    "Firebird/SCO_SV Intel",	/* 59 *//* 5.5 SCO Port */
    "Firebird/linux Intel",	/* 60 */
    "Firebird/FreeBSD/i386",	/* 61 */
    "Firebird/NetBSD/i386",	/* 62 */
    "Firebird/Darwin/PowerPC",	/* 63 */
    "Firebird/SINIX-Z",	/* 64 */
    "Firebird/linux Sparc",	/* 65 */
    "Firebird/linux AMD64"	/* 66 */
};



#ifdef SHLIB_DEFS
#define strlen		(*_libgds_strlen)
#define _iob		(*_libgds_iob)
#define ib_printf		(*_libgds_printf)
#define ib_fopen		(*_libgds_fopen)
#define ib_fclose		(*_libgds_fclose)
#define getenv		(*_libgds_getenv)
#define ib_fputc		(*_libgds_fputc)
#define mktemp		(*_libgds_mktemp)
#define unlink		(*_libgds_unlink)
#define statistics	(*_libgds_stat)
#define sprintf		(*_libgds_sprintf)
#define system		(*_libgds_system)
#define ib_fgetc		(*_libgds_fgetc)
#define ib_fgets		(*_libgds_fgets)
#define strcat		(*_libgds_strcat)
#define strcpy		(*_libgds_strcpy)
#define strncpy		(*_libgds_strncpy)
#define ib_fprintf		(*_libgds_fprintf)

extern int strlen();
extern IB_FILE _iob[];
extern int ib_printf();
extern IB_FILE *ib_fopen();
extern int ib_fclose();
extern SCHAR *getenv();
extern int ib_fputc();
extern SCHAR *mktemp();
extern int unlink();
extern int statistics();
extern int sprintf();
extern int system();
extern int ib_fgetc();
extern SCHAR *ib_fgets();
extern SCHAR *strcat();
extern SCHAR *strcpy();
extern SCHAR *strncpy();
extern int ib_fprintf();
#endif


#ifdef VMS
ISC_STATUS API_ROUTINE gds__attach_database_d(
										  ISC_STATUS * user_status,
										  struct dsc$descriptor_s *file_name,
										  void **handle,
										  SSHORT dpb_length,
SCHAR * dpb, SSHORT db_type)
{
/**************************************
 *
 *	g d s _ $ a t t a c h _ d a t a b a s e _ d
 *
 **************************************
 *
 * Functional description
 *	An attach database for COBOL to call
 *
 **************************************/

	return gds_attach_database(user_status, file_name->dsc$w_length,
							   file_name->dsc$a_pointer, handle, dpb_length,
							   dpb, db_type);
}
#endif


int API_ROUTINE gds__blob_size(
							   FRBRD ** b,
							   SLONG * size,
							   SLONG * seg_count, SLONG * max_seg)
{
/**************************************
 *
 *	g d s _ $ b l o b _ s i z e
 *
 **************************************
 *
 * Functional description
 *	Get the size, number of segments, and max
 *	segment length of a blob.  Return true
 *	if it happens to succeed.
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;
	SLONG n;
	SSHORT l;
	SCHAR *p, item, buffer[64];

#pragma FB_COMPILER_MESSAGE("Fix! Bad casts.")

	if (gds__blob_info(status_vector,
					   GDS_VAL(b),
					   sizeof(blob_items),
					   const_cast < char *>(blob_items),
					   sizeof(buffer), buffer)) {
		gds__print_status(status_vector);
		return FALSE;
	}

	p = buffer;

	while ((item = *p++) != gds_info_end) {
		l =
			static_cast < SSHORT >
			(gds__vax_integer(reinterpret_cast < UCHAR * >(p), 2));
		p += 2;
		n = gds__vax_integer(reinterpret_cast < UCHAR * >(p), l);
		p += l;
		switch (item) {
		case gds_info_blob_max_segment:
			if (max_seg)
				*max_seg = n;
			break;

		case gds_info_blob_num_segments:
			if (seg_count)
				*seg_count = n;
			break;

		case gds_info_blob_total_length:
			if (size)
				*size = n;
			break;

		default:
			return FALSE;
		}
	}

	return TRUE;
}


/* 17 May 2001 - isc_expand_dpb is DEPRECATED */
void API_ROUTINE_VARARG isc_expand_dpb(SCHAR ** dpb, SSHORT * dpb_size, ...)
{
/**************************************
 *
 *	i s c _ e x p a n d _ d p b
 *
 **************************************
 *
 * Functional description
 *	Extend a database parameter block dynamically
 *	to include runtime info.  Generated
 *	by gpre to provide host variable support for
 *	READY statement	options.
 *	This expects the list of variable args
 *	to be zero terminated.
 *
 *	Note: dpb_size is signed short only for compatibility
 *	with other calls (isc_attach_database) that take a dpb length.
 *
 * TMN: Note: According to Ann Harrison:
 * That routine should be deprecated.  It doesn't do what it
 * should, and does do things it shouldn't, and is harder to
 * use than the natural alternative.
 *
 **************************************/
	SSHORT	length;
	SSHORT	new_dpb_length;
	UCHAR*	new_dpb;
	char*	p;
	char*	q;
	va_list	args;
	USHORT	type;

/* calculate length of database parameter block,
   setting initial length to include version */

	if (!*dpb || !(new_dpb_length = *dpb_size))
	{
		new_dpb_length = 1;
	}

	VA_START(args, dpb_size);

	while (type = va_arg(args, int))
	{
		switch (type)
		{
		case gds_dpb_user_name:
		case gds_dpb_password:
		case isc_dpb_sql_role_name:
		case gds_dpb_lc_messages:
		case gds_dpb_lc_ctype:
		case isc_dpb_reserved:
			p = va_arg(args, char *);
			if (p)
			{
				length = strlen(p);
				new_dpb_length += 2 + length;
			}
			break;

		default:
			(void) va_arg(args, int);
			break;
		}
	}

/* if items have been added, allocate space
   for the new dpb and copy the old one over */

	if (new_dpb_length > *dpb_size)
	{
		/* Note: gds__free done by GPRE generated code */

		new_dpb = (UCHAR*)gds__alloc((SLONG)(sizeof(UCHAR) * new_dpb_length));
		p = reinterpret_cast < char *>(new_dpb);
		/* FREE: done by client process in GPRE generated code */
		if (!new_dpb)
		{			/* NOMEM: don't trash existing dpb */
			DEV_REPORT("isc_extend_dpb: out of memory");
			return;				/* NOMEM: not really handled */
		}

		q = *dpb;
		for (length = *dpb_size; length; length--)
		{
			*p++ = *q++;
		}

	}
	else
	{
		new_dpb = (UCHAR *) * dpb;
		p = reinterpret_cast<char*>(new_dpb + *dpb_size);
	}

	if (!*dpb_size)
		*p++ = gds_dpb_version1;

/* copy in the new runtime items */

	VA_START(args, dpb_size);

	while (type = va_arg(args, int))
	{
		switch (type)
		{
		case gds_dpb_user_name:
		case gds_dpb_password:
		case isc_dpb_sql_role_name:
		case gds_dpb_lc_messages:
		case gds_dpb_lc_ctype:
		case isc_dpb_reserved:
			if (q = va_arg(args, char *))
			{
				length = strlen(q);
				assert(type <= CHAR_MAX);
				*p++ = (char) type;
				assert(length <= CHAR_MAX);
				*p++ = (char) length;
				while (length--)
					*p++ = *q++;
			}
			break;

		default:
			(void) va_arg(args, int);
			break;
		}
	}

	*dpb_size = p - reinterpret_cast<char*>(new_dpb);
	*dpb = (SCHAR*) new_dpb;
}


int API_ROUTINE isc_modify_dpb(SCHAR**	dpb,
							   SSHORT*	dpb_size,
							   USHORT	type,
							   SCHAR*	str,
							   SSHORT	str_len)
{
/**************************************
 *
 *	i s c _ e x p a n d _ d p b
 *
 **************************************
 *
 * Functional description
 *	Extend a database parameter block dynamically
 *	to include runtime info.  Generated
 *	by gpre to provide host variable support for
 *	READY statement	options.
 *	This expects one arg at a time.
 *      the length of the string is passed by the caller and hence
 * 	is not expected to be null terminated.
 * 	this call is a variation of isc_expand_dpb without a variable
 * 	arg parameters.
 * 	Instead, this function is called recursively
 *	Alternatively, this can have a parameter list with all possible
 *	parameters either nulled or with proper value and type.
 *
 *  	**** This can be modified to be so at a later date, making sure
 *	**** all callers follow the same convention
 *
 *	Note: dpb_size is signed short only for compatibility
 *	with other calls (isc_attach_database) that take a dpb length.
 *
 **************************************/
	SSHORT length, new_dpb_length;
	UCHAR *new_dpb, *p, *q;

/* calculate length of database parameter block,
   setting initial length to include version */

	if (!*dpb || !(new_dpb_length = *dpb_size))
	{
		new_dpb_length = 1;
	}

	switch (type)
	{
	case gds_dpb_user_name:
	case gds_dpb_password:
	case isc_dpb_sql_role_name:
	case gds_dpb_lc_messages:
	case gds_dpb_lc_ctype:
	case isc_dpb_reserved:
		new_dpb_length += 2 + str_len;
		break;

	default:
		return FB_FAILURE;
	}

/* if items have been added, allocate space
   for the new dpb and copy the old one over */

	if (new_dpb_length > *dpb_size)
	{
		/* Note: gds__free done by GPRE generated code */

		new_dpb = (UCHAR*)gds__alloc((SLONG)(sizeof(UCHAR) * new_dpb_length));

		/* FREE: done by client process in GPRE generated code */
		if (!new_dpb)
		{			/* NOMEM: don't trash existing dpb */
			DEV_REPORT("isc_extend_dpb: out of memory");
			return FB_FAILURE;		/* NOMEM: not really handled */
		}

		p = new_dpb;
		q = (UCHAR*) *dpb;
		for (length = *dpb_size; length; length--)
		{
			*p++ = *q++;
		}

	}
	else
	{
		new_dpb = (UCHAR *) * dpb;
		p = new_dpb + *dpb_size;
	}

	if (!*dpb_size)
	{
		*p++ = gds_dpb_version1;
	}

/* copy in the new runtime items */

	switch (type)
	{
	case gds_dpb_user_name:
	case gds_dpb_password:
	case isc_dpb_sql_role_name:
	case gds_dpb_lc_messages:
	case gds_dpb_lc_ctype:
	case isc_dpb_reserved:
		if (q = (UCHAR *) str)
		{
			length = str_len;
			assert(type <= MAX_UCHAR);
			*p++ = (UCHAR) type;
			assert(length <= MAX_UCHAR);
			*p++ = (UCHAR) length;
			while (length--)
			{
				*p++ = *q++;
			}
		}
		break;

	default:
		return FB_FAILURE;
	}

	*dpb_size = p - new_dpb;
	*dpb = (SCHAR *) new_dpb;

	return FB_SUCCESS;
}


#ifdef GDS_EDIT
int API_ROUTINE GDS_EDIT(TEXT* file_name, USHORT type)
{
/**************************************
 *
 *	g d s _ $ e d i t
 *
 **************************************
 *
 * Functional description
 *	Edit a file.
 *
 **************************************/
	TEXT *editor, buffer[256];
	struct stat before, after;

#ifndef WIN_NT
	if (!(editor = getenv("VISUAL")) && !(editor = getenv("EDITOR")))
		editor = "vi";
#else
	if (!(editor = getenv("EDITOR")))
		editor = "Notepad";
#endif

	statistics(file_name, &before);
	sprintf(buffer, "%s %s", editor, file_name);

	system(buffer);

	statistics(file_name, &after);

	return (before.st_mtime != after.st_mtime ||
			before.st_size != after.st_size);
}
#endif


#ifdef VMS
int API_ROUTINE gds__edit(TEXT * file_name, USHORT type)
{
/**************************************
 *
 *	g d s _ $ e d i t
 *
 **************************************
 *
 * Functional description
 *	Open a blob, dump it to a file, allow the user to edit the
 *	window, and dump the data back into a blob.  If the user
 *	bails out, return FALSE, otherwise return TRUE.
 *
 **************************************/
	int status, (*editor) ();
	struct dsc$descriptor_s desc, symbol, image;
	struct stat before, after;

	stat(file_name, &before);
	ISC_make_desc(file_name, &desc, 0);
	ISC_make_desc(EDT_SYMBOL, &symbol, 0);
	ISC_make_desc(EDT_IMAGE, &image, 0);

	lib$find_image_symbol(&image, &symbol, &editor);
	status = (*editor) (&desc, &desc);
	stat(file_name, &after);

	return (before.st_ctime != after.st_ctime ||
			before.st_ino[0] != after.st_ino[0] ||
			before.st_ino[1] != after.st_ino[1] ||
			before.st_ino[2] != after.st_ino[2]);
}
#endif


SLONG API_ROUTINE gds__event_block(SCHAR ** event_buffer,
								   SCHAR ** result_buffer, USHORT count, ...)
{
/**************************************
 *
 *	g d s _ $ e v e n t _ b l o c k
 *
 **************************************
 *
 * Functional description
 *	Create an initialized event parameter block from a
 *	variable number of input arguments.
 *	Return the size of the block.
 *
 *	Return 0 as the size if the event parameter block cannot be
 *	created for any reason.
 *
 **************************************/
	SCHAR *p, *q;
	SCHAR *end;
	SLONG length;
	va_list ptr;
	USHORT i;

	VA_START(ptr, count);

/* calculate length of event parameter block, 
   setting initial length to include version
   and counts for each argument */

	length = 1;
	i = GDS_VAL(count);
	while (i--) {
		q = va_arg(ptr, SCHAR *);
		length += strlen(q) + 5;
	}

	p = *event_buffer =
		(SCHAR *) gds__alloc((SLONG) (sizeof(SCHAR) * length));
/* FREE: unknown */
	if (!*event_buffer)			/* NOMEM: */
		return 0;
	*result_buffer = (SCHAR *) gds__alloc((SLONG) (sizeof(SCHAR) * length));
/* FREE: unknown */
	if (!*result_buffer) {		/* NOMEM: */
		gds__free(*event_buffer);
		*event_buffer = NULL;
		return 0;
	}

#ifdef DEBUG_GDS_ALLOC
/* I can't find anywhere these items are freed */
/* 1994-October-25 David Schnepper  */
	gds_alloc_flag_unfreed((void *) *event_buffer);
	gds_alloc_flag_unfreed((void *) *result_buffer);
#endif /* DEBUG_GDS_ALLOC */

/* initialize the block with event names and counts */

	*p++ = EPB_version1;

	VA_START(ptr, count);

	i = GDS_VAL(count);
	while (i--) {
		q = va_arg(ptr, SCHAR *);

		/* Strip trailing blanks from string */

		for (end = q + strlen(q); --end >= q && *end == ' ';);
		*p++ = end - q + 1;
		while (q <= end)
			*p++ = *q++;
		*p++ = 0;
		*p++ = 0;
		*p++ = 0;
		*p++ = 0;
	}

	return p - *event_buffer;
}


USHORT API_ROUTINE gds__event_block_a(SCHAR ** event_buffer,
									  SCHAR ** result_buffer,
									  SSHORT GDS_VAL(count),
									  SCHAR ** name_buffer)
{
/**************************************
 *
 *	g d s _ $ e v e n t _ b l o c k _ a 
 *
 **************************************
 *
 * Functional description
 *	Create an initialized event parameter block from a
 *	vector of input arguments. (Ada needs this)
 *	Assume all strings are 31 characters long.
 *	Return the size of the block.
 *
 **************************************/
	SCHAR *p, *q;
	SCHAR *end, **nb;
	SLONG length;
	USHORT i;

/* calculate length of event parameter block, 
   setting initial length to include version
   and counts for each argument */

	i = GDS_VAL(count);
	nb = name_buffer;
	length = 0;
	while (i--) {
		q = *nb++;

		/* Strip trailing blanks from string */

		for (end = q + 31; --end >= q && *end == ' ';);
		length += end - q + 1 + 5;
	}

	i = GDS_VAL(count);
	p = *event_buffer =
		(SCHAR *) gds__alloc((SLONG) (sizeof(SCHAR) * length));
/* FREE: unknown */
	if (!*event_buffer)			/* NOMEM: */
		return 0;
	*result_buffer = (SCHAR *) gds__alloc((SLONG) (sizeof(SCHAR) * length));
/* FREE: unknown */
	if (!*result_buffer) {		/* NOMEM: */
		gds__free(*event_buffer);
		*event_buffer = NULL;
		return 0;
	}

#ifdef DEBUG_GDS_ALLOC
/* I can't find anywhere these items are freed */
/* 1994-October-25 David Schnepper  */
	gds_alloc_flag_unfreed((void *) *event_buffer);
	gds_alloc_flag_unfreed((void *) *result_buffer);
#endif /* DEBUG_GDS_ALLOC */

	*p++ = EPB_version1;

	nb = name_buffer;

	while (i--) {
		q = *nb++;

		/* Strip trailing blanks from string */

		for (end = q + 31; --end >= q && *end == ' ';);
		*p++ = end - q + 1;
		while (q <= end)
			*p++ = *q++;
		*p++ = 0;
		*p++ = 0;
		*p++ = 0;
		*p++ = 0;
	}

	return (p - *event_buffer);
}


void API_ROUTINE gds__event_block_s(
									SCHAR ** event_buffer,
									SCHAR ** result_buffer,
									SSHORT count,
SCHAR ** name_buffer, SSHORT * return_count)
{
/**************************************
 *
 *	g d s _ $ e v e n t _ b l o c k _ s
 *
 **************************************
 *
 * Functional description
 *	THIS IS THE COBOL VERSION of gds__event_block_a for Cobols
 *	that don't permit return values.
 *
 **************************************/

	*return_count =
		gds__event_block_a(event_buffer, result_buffer, count, name_buffer);
}


void API_ROUTINE gds__event_counts(
								   ULONG * result_vector,
								   SSHORT GDS_VAL(buffer_length),
								   SCHAR * event_buffer,
SCHAR * result_buffer)
{
/**************************************
 *
 *	g d s _ $ e v e n t _ c o u n t s
 *
 **************************************
 *
 * Functional description
 *	Get the delta between two events in an event
 *	parameter block.  Used to update gds_events
 *	for GPRE support of events.
 *
 **************************************/
	SCHAR *p, *q, *end;
	USHORT i, length;
	ULONG *vec;
	ULONG initial_count, new_count;

	vec = result_vector;
	p = event_buffer;
	q = result_buffer;
	length = GDS_VAL(buffer_length);
	end = p + length;

/* analyze the event blocks, getting the delta for each event */

	p++;
	q++;
	while (p < end) {
		/* skip over the event name */

		i = (USHORT) * p++;
		p += i;
		q += i + 1;

		/* get the change in count */

		initial_count =
			gds__vax_integer(reinterpret_cast < UCHAR * >(p), sizeof(SLONG));
		p += sizeof(SLONG);
		new_count =
			gds__vax_integer(reinterpret_cast < UCHAR * >(q), sizeof(SLONG));
		q += sizeof(SLONG);
		*vec++ = new_count - initial_count;
	}

/* copy over the result to the initial block to prepare
   for the next call to gds__event_wait */

	p = event_buffer;
	q = result_buffer;
	do
		*p++ = *q++;
	while (--length);
}


void API_ROUTINE gds__get_client_version(SCHAR *buffer)
{
/**************************************
 *
 *	g d s _ $ g e t _ c l i e n t _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *
 **************************************/

	if (buffer)
		strcpy(buffer, ISC_VERSION);
}


int API_ROUTINE gds__get_client_major_version()
{
/**************************************
 *
 *	g d s _ $ g e t _ c l i e n t _ m a j o r _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *
 **************************************/

	return atoi(ISC_MAJOR_VER);
}


int API_ROUTINE gds__get_client_minor_version()
{
/**************************************
 *
 *	g d s _ $ g e t _ c l i e n t _ m i n o r _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *
 **************************************/

	return atoi(ISC_MINOR_VER);
}


void API_ROUTINE gds__map_blobs(int *handle1, int *handle2)
{
/**************************************
 *
 *	g d s _ $ m a p _ b l o b s
 *
 **************************************
 *
 * Functional description
 *	Map an old blob to a new blob.
 *	This call is intended for use by REPLAY,
 *	and is probably not generally useful
 *	for anyone else.
 *
 **************************************/

#ifdef REPLAY_OSRI_API_CALLS_SUBSYSTEM
#ifndef SUPERCLIENT
/* Note: gds__map_blobs is almost like an API call,
   it needs a TDBB structure setup for it in order
   to function properly.  This currently does
   not function.
   1996-Nov-06 David Schnepper  */
	deliberate_compile_error++;
	BLB_map_blobs(NULL_TDBB, handle1, handle2);
#endif
#endif
}


#if !(defined REQUESTER)
void API_ROUTINE gds__set_debug(int GDS_VAL(value))
{
/**************************************
 *
 *	G D S _ S E T _ D E B U G
 *
 **************************************
 *
 * Functional description
 *	Set debugging mode, whatever that may mean.
 *
 **************************************/

#pragma FB_COMPILER_MESSAGE("Empty function?!")
}
#endif


void API_ROUTINE isc_set_login(UCHAR ** dpb, SSHORT * dpb_size)
{
/**************************************
 *
 *	i s c _ s e t _ l o g i n
 *
 **************************************
 *
 * Functional description
 *	Pickup any ISC_USER and ISC_PASSWORD
 *	environment variables, and stuff them
 *	into the dpb (if there is no user name
 *	or password already referenced).
 *
 **************************************/
#ifndef SUPERSERVER
	TEXT *username, *password;
	UCHAR *p, *end_dpb;
	BOOLEAN user_seen = FALSE, password_seen = FALSE;
	USHORT l;
	int item;

/* look for the environment variables */

	username = getenv("ISC_USER");
	password = getenv("ISC_PASSWORD");

	if (!username && !password)
		return;

/* figure out whether the username or 
   password have already been specified */

	if (*dpb && *dpb_size)
		for (p = *dpb, end_dpb = p + *dpb_size; p < end_dpb;) {
			item = *p++;

			if (item == gds_dpb_version1)
				continue;

			switch (item) {
			case gds_dpb_sys_user_name:
			case gds_dpb_user_name:
				user_seen = TRUE;
				break;

			case gds_dpb_password:
			case gds_dpb_password_enc:
				password_seen = TRUE;
				break;
			}

			/* get the length and increment past the parameter. */
			l = *p++;
			p += l;
		}

	if (username && !user_seen) {
		if (password && !password_seen)
			isc_expand_dpb(reinterpret_cast < char **>(dpb), dpb_size,
						   gds_dpb_user_name, username, gds_dpb_password,
						   password, 0);
		else
			isc_expand_dpb(reinterpret_cast < char **>(dpb), dpb_size,
						   gds_dpb_user_name, username, 0);
	}
	else if (password && !password_seen)
		isc_expand_dpb(reinterpret_cast < char **>(dpb), dpb_size,
					   gds_dpb_password, password, 0);
#endif
}


BOOLEAN API_ROUTINE isc_set_path(TEXT * file_name,
								 USHORT file_length, TEXT * expanded_name)
{
/**************************************
 *
 *	i s c _ s e t _ p a t h
 *
 **************************************
 *
 * Functional description
 *	Set a prefix to a filename based on 
 *	the ISC_PATH user variable.
 *
 **************************************/
	TEXT *pathname, *p;

/* look for the environment variables to tack 
   onto the beginning of the database path */

	if (!(pathname = getenv("ISC_PATH")))
		return FALSE;

	if (!file_length)
		file_length = strlen(file_name);
	file_name[file_length] = 0;

/* if the file already contains a remote node
   or any path at all forget it */

	for (p = file_name; *p; p++)
		if (*p == ':' || *p == '/' || *p == '\\')
			return FALSE;

/* concatenate the strings */

	strcpy(expanded_name, pathname);

    /* CVC: Make the concatenation work if no slash is present. */
    p = expanded_name + (strlen (expanded_name) - 1);
    if (*p != ':' && *p != '/' && *p != '\\') {
        strcat(expanded_name, "/");
    }

	strcat(expanded_name, file_name);

	return TRUE;
}

static void print_version(void* dummy, const char* version) {
	ib_printf("\t%s\n", version);
}


void API_ROUTINE isc_set_single_user(UCHAR ** dpb,
									 SSHORT * dpb_size, TEXT * single_user)
{
/****************************************
 *
 *      i s c _ s e t _ s i n g l e _ u s e r
 *
 ****************************************
 *
 * Functional description
 *      Stuff the single_user flag into the dpb
 *      if the flag doesn't already exist in the dpb.
 *
 ****************************************/

	UCHAR *p, *end_dpb;
	BOOLEAN single_user_seen = FALSE;
	USHORT l;
	int item;

/* Discover if single user access has already been specified */

	if ((*dpb) && (*dpb_size))
		for (p = *dpb, end_dpb = p + *dpb_size; p < end_dpb;) {

			item = *p++;

			if (item == gds_dpb_version1)
				continue;

			switch (item) {

			case isc_dpb_reserved:

				single_user_seen = TRUE;
				break;

			}

/* Get the length and increment past the parameter. */

			l = *p++;
			p += l;

		}

	if (!single_user_seen)
		isc_expand_dpb(reinterpret_cast < char **>(dpb), dpb_size,
					   isc_dpb_reserved, single_user, 0);

}


int API_ROUTINE gds__version(
							 FRBRD **handle,
							 FPTR_VOID routine, void *user_arg)
{
/**************************************
 *
 *	g d s _ $ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *	Obtain and print information about a database.
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;
	USHORT buf_len, len, implementation, class_, ods_version,
		ods_minor_version;
	UCHAR count, item, l, *buf, buffer[256], *p;
	TEXT *versions, *implementations,
		*class_string, *implementation_string, s[128];
	BOOLEAN redo;

 	if (!routine)
 		routine = reinterpret_cast<FPTR_VOID>(print_version);

	buf = buffer;
	buf_len = sizeof(buffer);

	do {
		if (isc_database_info(status_vector,
							  handle,
							  sizeof(info),
							  reinterpret_cast < char *>(const_cast <
														 UCHAR * >(info)),
							  buf_len, reinterpret_cast < char *>(buf))) {
			if (buf != buffer)
				gds__free((SLONG *) buf);
			return FB_FAILURE;
		}

		p = buf;
		redo = FALSE;

		while (!redo && *p != isc_info_end && p < buf + buf_len) {
			item = *p++;
			len = static_cast < USHORT > (gds__vax_integer(p, 2));
			p += 2;
			switch (item) {
			case isc_info_firebird_version:
				versions = (TEXT *) p;
				break;

			case isc_info_implementation:
				implementations = (TEXT *) p;
				break;

			case isc_info_truncated:
				redo = TRUE;
				break;

			default:
				if (buf != buffer)
					gds__free((SLONG *) buf);
				return FB_FAILURE;
			}
			p += len;
		}

		/* Our buffer wasn't large enough to hold all the information,
		 * make a larger one and try again.
		 */
		if (redo) {
			if (buf != buffer)
				gds__free((SLONG *) buf);
			buf_len += 1024;
			buf = (UCHAR *) gds__alloc((SLONG) (sizeof(UCHAR) * buf_len));
			/* FREE: freed within this module */
			if (!buf)			/* NOMEM: */
				return FB_FAILURE;
		}
	} while (redo);

	count = MIN(*versions, *implementations);
	++versions;
	++implementations;

	while (count-- > 0) {
		implementation = *implementations++;
		class_ = *implementations++;
		l = *versions++;
		if (implementation >= FB_NELEM(impl_implementation)
			|| !(implementation_string =
				 const_cast < char *>(impl_implementation[implementation])))
			implementation_string = "**unknown**";
		if (class_ >= FB_NELEM(impl_class) ||
			!(class_string = const_cast < char *>(impl_class[class_])))
			class_string = "**unknown**";
		sprintf(s, "%s (%s), version \"%.*s\"",
				implementation_string, class_string, l, versions);

#pragma FB_COMPILER_MESSAGE("Fix! Bad function ptr type cast!")
		reinterpret_cast < void (*) (...) > (*routine) (user_arg, s);
		versions += l;
	}

	if (buf != buffer)
		gds__free((SLONG *) buf);

	if (get_ods_version(handle, &ods_version, &ods_minor_version) == FB_FAILURE)
		return FB_FAILURE;

	sprintf(s, "on disk structure version %d.%d", ods_version,
			ods_minor_version);
	reinterpret_cast < void (*) (...) > (*routine) (user_arg, s);

	return FB_SUCCESS;
}


void API_ROUTINE isc_format_implementation(
										   USHORT implementation,
										   USHORT ibuflen,
										   TEXT * ibuf,
USHORT class_, USHORT cbuflen, TEXT * cbuf)
{
/**************************************
 *
 *	i s c _ f o r m a t _ i m p l e m e n t a t i o n
 *
 **************************************
 *
 * Functional description
 *	Convert the implementation and class codes to strings
 * 	by looking up their values in the internal tables.
 *
 **************************************/
	int len;

	if (ibuflen > 0) {
		if (implementation >= FB_NELEM(impl_implementation) ||
			!(impl_implementation[implementation])) {
			strncpy(ibuf, "**unknown**", ibuflen - 1);
			ibuf[MIN(11, ibuflen - 1)] = '\0';
		}
		else {
			strncpy(ibuf, impl_implementation[implementation], ibuflen - 1);
			len = strlen(impl_implementation[implementation]);
			ibuf[MIN(len, ibuflen - 1)] = '\0';
		}
	}

	if (cbuflen > 0) {
		if (class_ >= FB_NELEM(impl_class) || !(impl_class[class_])) {
			strncpy(cbuf, "**unknown**", cbuflen - 1);
			ibuf[MIN(11, cbuflen - 1)] = '\0';
		}
		else {
			strncpy(cbuf, impl_class[class_], cbuflen - 1);
			len = strlen(impl_class[class_]);
			ibuf[MIN(len, cbuflen - 1)] = '\0';
		}
	}

}


U_IPTR API_ROUTINE isc_baddress(SCHAR * object)
{
/**************************************
 *
 *        i s c _ b a d d r e s s
 *
 **************************************
 *
 * Functional description
 *      Return the address of whatever is passed in
 *
 **************************************/

	return (U_IPTR) object;
}


void API_ROUTINE isc_baddress_s(SCHAR * object, U_IPTR * address)
{
/**************************************
 *
 *        i s c _ b a d d r e s s _ s
 *
 **************************************
 *
 * Functional description
 *      Return the address of whatever is passed in
 *
 **************************************/

	*address = (U_IPTR) object;
}


#ifdef VMS
void API_ROUTINE gds__wake_init(void)
{
/**************************************
 *
 *	g d s _ $ w a k e _ i n i t
 *
 **************************************
 *
 * Functional description
 *	Set up to be awakened by another process thru a blocking AST.
 *
 **************************************/

	ISC_wake_init();
}
#endif


int API_ROUTINE BLOB_close(BSTREAM * bstream)
{
/**************************************
 *
 *	B L O B _ c l o s e
 *
 **************************************
 *
 * Functional description
 *	Close a blob stream.
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;
	USHORT l;

	if (!bstream->bstr_blob)
		return FALSE;

	if (bstream->bstr_mode & BSTR_output) {
		l = (bstream->bstr_ptr - bstream->bstr_buffer);
		if (l > 0)
			if (gds__put_segment(status_vector,
								 GDS_REF(bstream->bstr_blob),
								 l,
								 GDS_VAL(bstream->bstr_buffer))) return FALSE;
	}

	gds__close_blob(status_vector, GDS_REF(bstream->bstr_blob));

	if (bstream->bstr_mode & BSTR_alloc)
		gds__free(bstream->bstr_buffer);

	gds__free(bstream);

	return TRUE;
}


int API_ROUTINE blob__display(
							  SLONG blob_id[2],
							  FRBRD **database,
							  FRBRD **transaction,
							  TEXT * field_name, SSHORT * name_length)
{
/**************************************
 *
 *	b l o b _ $ d i s p l a y
 *
 **************************************
 *
 * Functional description
 *	PASCAL callable version of EDIT_blob.
 *
 **************************************/
	TEXT *p, *q, temp[32];
	USHORT l;

	if ((l = *name_length) != 0) {
		p = temp;
		q = field_name;
		do
			*p++ = *q++;
		while (--l);
		*p = 0;
	}

	return BLOB_display(reinterpret_cast < GDS_QUAD * >(blob_id), *database,
						*transaction, temp);
}


int API_ROUTINE BLOB_display(
							 GDS_QUAD * blob_id,
							 FRBRD *database,
							 FRBRD *transaction, TEXT * field_name)
{
/**************************************
 *
 *	B L O B _ d i s p l a y
 *
 **************************************
 *
 * Functional description
 *	Open a blob, dump it to a file, allow the user to read the
 *	window. 
 *
 **************************************/

/* On VMS use the system library routines to do the output */

#ifdef VMS
	return display(blob_id, database, transaction);
#else

/* On UNIX, just dump the file to ib_stdout */

	return dump(blob_id, database, transaction, ib_stdout);

#endif
}


int API_ROUTINE blob__dump(
						   SLONG blob_id[2],
						   FRBRD **database,
						   FRBRD **transaction,
						   TEXT * file_name, SSHORT * name_length)
{
/**************************************
 *
 *	b l o b _ $ d u m p
 *
 **************************************
 *
 * Functional description
 *	Translate a pascal callable dump
 *	into an internal dump call.
 *
 **************************************/
	TEXT *p, *q, temp[129];
	USHORT l;

	if ((l = *name_length) != 0) {
		p = temp;
		q = file_name;
		do
			*p++ = *q++;
		while (--l);
		*p = 0;
	}

	return BLOB_dump(reinterpret_cast < GDS_QUAD * >(blob_id), *database,
					 *transaction, temp);
}


int API_ROUTINE BLOB_text_dump(
							   GDS_QUAD * blob_id,
							   FRBRD *database,
							   FRBRD *transaction, SCHAR * file_name)
{
/**************************************
 *
 *	B L O B _ t e x t _ d u m p
 *
 **************************************
 *
 * Functional description
 *	Dump a blob into a file.
 *      This call does CR/LF translation on NT.
 *
 **************************************/
	IB_FILE *file;
	int ret;

	if (!(file = ib_fopen(file_name, FOPEN_WRITE_TYPE_TEXT)))
		return FALSE;

	ret = dump(blob_id, database, transaction, file);
	ib_fclose(file);

	return ret;
}


int API_ROUTINE BLOB_dump(
						  GDS_QUAD * blob_id,
						  FRBRD *database,
						  FRBRD *transaction, SCHAR * file_name)
{
/**************************************
 *
 *	B L O B _ d u m p
 *
 **************************************
 *
 * Functional description
 *	Dump a blob into a file.
 *
 **************************************/
	IB_FILE *file;
	int ret;

	if (!(file = ib_fopen(file_name, FOPEN_WRITE_TYPE)))
		return FALSE;

	ret = dump(blob_id, database, transaction, file);
	ib_fclose(file);

	return ret;
}


int API_ROUTINE blob__edit(
						   SLONG blob_id[2],
						   FRBRD **database,
						   FRBRD **transaction,
						   TEXT * field_name, SSHORT * name_length)
{
/**************************************
 *
 *	b l o b _ $ e d i t
 *
 **************************************
 *
 * Functional description
 *	Translate a pascal callable edit
 *	into an internal edit call.
 *
 **************************************/
	TEXT *p, *q, temp[32];
	USHORT l;

	if ((l = *name_length) != 0) {
		p = temp;
		q = field_name;
		do
			*p++ = *q++;
		while (--l);
		*p = 0;
	}

	return BLOB_edit(reinterpret_cast < GDS_QUAD * >(blob_id), *database,
					 *transaction, temp);
}


int API_ROUTINE BLOB_edit(
						  GDS_QUAD * blob_id,
						  FRBRD *database,
						  FRBRD *transaction, SCHAR * field_name)
{
/**************************************
 *
 *	B L O B _ e d i t
 *
 **************************************
 *
 * Functional description
 *	Open a blob, dump it to a file, allow the user to edit the
 *	window, and dump the data back into a blob.  If the user
 *	bails out, return FALSE, otherwise return TRUE.
 *
 **************************************/

	return edit(blob_id, database, transaction, TRUE, field_name);
}


int API_ROUTINE BLOB_get(BSTREAM * bstream)
{
/**************************************
 *
 *	B L O B _ g e t
 *
 **************************************
 *
 * Functional description
 *	Return the next byte of a blob.  If the blob is exhausted, return
 *	EOF.
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;

	if (!bstream->bstr_buffer)
		return EOF;

	while (1) {
		if (--bstream->bstr_cnt >= 0)
			return *bstream->bstr_ptr++ & 0377;

		gds__get_segment(status_vector,
						 GDS_REF(bstream->bstr_blob),
						 reinterpret_cast <
						 USHORT * >(GDS_REF(bstream->bstr_cnt)),
						 bstream->bstr_length, GDS_VAL(bstream->bstr_buffer));
		if (status_vector[1] && status_vector[1] != gds_segment) {
			bstream->bstr_ptr = 0;
			bstream->bstr_cnt = 0;
			if (status_vector[1] != gds_segstr_eof)
				gds__print_status(status_vector);
			return EOF;
		}
		bstream->bstr_ptr = bstream->bstr_buffer;
	}
}


int API_ROUTINE blob__load(
						   SLONG blob_id[2],
						   FRBRD **database,
						   FRBRD **transaction,
						   TEXT * file_name, SSHORT * name_length)
{
/**************************************
 *
 *	b l o b _ $ l o a d
 *
 **************************************
 *
 * Functional description
 *	Translate a pascal callable load
 *	into an internal load call.
 *
 **************************************/
	TEXT *p, *q, temp[129];
	USHORT l;

	if ((l = *name_length) != 0) {
		p = temp;
		q = file_name;
		do
			*p++ = *q++;
		while (--l);
		*p = 0;
	}

	return BLOB_load(reinterpret_cast < GDS_QUAD * >(blob_id), *database,
					 *transaction, temp);
}


int API_ROUTINE BLOB_text_load(
							   GDS_QUAD * blob_id,
							   FRBRD *database,
							   FRBRD *transaction, TEXT * file_name)
{
/**************************************
 *
 *	B L O B _ t e x t _ l o a d
 *
 **************************************
 *
 * Functional description
 *	Load a  blob with the contents of a file.  
 *      This call does CR/LF translation on NT.
 *      Return TRUE is successful.
 *
 **************************************/
	IB_FILE *file;
	int ret;

	if (!(file = ib_fopen(file_name, FOPEN_READ_TYPE_TEXT)))
		return FALSE;

	ret = load(blob_id, database, transaction, file);

	ib_fclose(file);

	return ret;
}


int API_ROUTINE BLOB_load(
						  GDS_QUAD * blob_id,
						  FRBRD *database, FRBRD *transaction, TEXT * file_name)
{
/**************************************
 *
 *	B L O B _ l o a d
 *
 **************************************
 *
 * Functional description
 *	Load a blob with the contents of a file.  Return TRUE is successful.
 *
 **************************************/
	IB_FILE *file;
	int ret;

	if (!(file = ib_fopen(file_name, FOPEN_READ_TYPE)))
		return FALSE;

	ret = load(blob_id, database, transaction, file);

	ib_fclose(file);

	return ret;
}


BSTREAM *API_ROUTINE Bopen(GDS_QUAD * blob_id,
						   FRBRD *database, FRBRD *transaction, SCHAR * mode)
{
/**************************************
 *
 *	B o p e n
 *
 **************************************
 *
 * Functional description
 *	Initialize a blob-stream block.
 *
 **************************************/
	FRBRD *blob;
	ISC_STATUS_ARRAY status_vector;
	BSTREAM *bstream;
	USHORT bpb_length;
	UCHAR *bpb;

	bpb_length = 0;
	bpb = NULL;

	blob = NULL;

	if (*mode == 'w' || *mode == 'W') {
		if (gds__create_blob2(status_vector,
							  GDS_REF(database),
							  GDS_REF(transaction),
							  GDS_REF(blob),
							  GDS_VAL(blob_id),
							  bpb_length,
							  reinterpret_cast < char *>(bpb))) return NULL;
	}
	else if (*mode == 'r' || *mode == 'R') {
		if (gds__open_blob2(status_vector,
							GDS_REF(database),
							GDS_REF(transaction),
							GDS_REF(blob),
							GDS_VAL(blob_id),
							bpb_length,
							reinterpret_cast < char *>(bpb))) return NULL;
	}
	else
		return NULL;

	bstream = BLOB_open(blob, (SCHAR *) 0, 0);

	if (*mode == 'w' || *mode == 'W') {
		bstream->bstr_mode |= BSTR_output;
		bstream->bstr_cnt = bstream->bstr_length;
		bstream->bstr_ptr = bstream->bstr_buffer;
	}
	else {
		bstream->bstr_cnt = 0;
		bstream->bstr_mode |= BSTR_input;
	}

	return bstream;
}


BSTREAM *API_ROUTINE BLOB_open(FRBRD *blob, SCHAR * buffer, int length)
{
/**************************************
 *
 *	B L O B _ o p e n
 *
 **************************************
 *
 * Functional description
 *	Initialize a blob-stream block.
 *
 **************************************/
	BSTREAM *bstream;

	if (!blob)
		return NULL;

	bstream = (BSTREAM *) gds__alloc((SLONG) sizeof(BSTREAM));
/* FREE: This structure is freed by BLOB_close */
	if (!bstream)				/* NOMEM: */
		return NULL;

#ifdef DEBUG_gds__alloc
/* This structure is handed to the user process, we depend on the client
 * to call BLOB_close() for it to be freed.
 */
	gds_alloc_flag_unfreed((void *) bstream);
#endif

	bstream->bstr_blob = blob;
	bstream->bstr_length = (length) ? length : 512;
	bstream->bstr_mode = 0;
	bstream->bstr_cnt = 0;
	bstream->bstr_ptr = 0;

	if (!(bstream->bstr_buffer = buffer)) {
		bstream->bstr_buffer = (SCHAR *)
			gds__alloc((SLONG) (sizeof(SCHAR) * bstream->bstr_length));
		/* FREE: This structure is freed in BLOB_close() */
		if (!bstream->bstr_buffer) {	/* NOMEM: */
			gds__free(bstream);
			return NULL;
		}
#ifdef DEBUG_gds__alloc
		/* This structure is handed to the user process, we depend on the client
		 * to call BLOB_close() for it to be freed.
		 */
		gds_alloc_flag_unfreed((void *) bstream->bstr_buffer);
#endif

		bstream->bstr_mode |= BSTR_alloc;
	}

	return bstream;
}


int API_ROUTINE BLOB_put(SCHAR x, BSTREAM * bstream)
{
/**************************************
 *
 *	B L O B _ p u t
 *
 **************************************
 *
 * Functional description
 *	Write a segment to a blob. First
 *	stick in the final character, then
 *	compute the length and send off the
 *	segment.  Finally, set up the buffer
 *	block and retun TRUE if all is well. 
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;
	USHORT l;

	if (!bstream->bstr_buffer)
		return FALSE;

	*bstream->bstr_ptr++ = (x & 0377);
	l = (bstream->bstr_ptr - bstream->bstr_buffer);
	if (gds__put_segment(status_vector,
						 GDS_REF(bstream->bstr_blob),
						 l, GDS_VAL(bstream->bstr_buffer))) {
		return FALSE;
	}
	bstream->bstr_cnt = bstream->bstr_length;
	bstream->bstr_ptr = bstream->bstr_buffer;
	return TRUE;
}


#ifdef VMS
static display(GDS_QUAD * blob_id, void *database, void *transaction)
{
/**************************************
 *
 *	d i s p l a y
 *
 **************************************
 *
 * Functional description
 *	Display a file on VMS
 *
 **************************************/
	SCHAR buffer[256], *p;
	SSHORT short_length, l;
	ISC_STATUS_ARRAY status_vector;
	int *blob;
	struct dsc$descriptor_s desc;

/* Open the blob.  If it failed, what the hell -- just return failure */

	blob = NULL;
	if (gds__open_blob(status_vector,
					   GDS_REF(database),
					   GDS_REF(transaction), GDS_REF(blob), GDS_VAL(blob_id))) {
		gds__print_status(status_vector);
		return FALSE;
	}

/* Copy data from blob to scratch file */

	short_length = sizeof(buffer);

	for (;;) {
		gds__get_segment(status_vector,
						 GDS_REF(blob), GDS_REF(l), short_length, buffer);
		if (status_vector[1] && status_vector[1] != gds_segment) {
			if (status_vector[1] != gds_segstr_eof)
				gds__print_status(status_vector);
			break;
		}
		buffer[l] = 0;
		ISC_make_desc(buffer, &desc, 0);
		lib$put_output(&desc);
	}

/* Close the blob */

	gds__close_blob(status_vector, GDS_REF(blob));

	return TRUE;
}
#endif /* VMS */


static int dump(
				GDS_QUAD * blob_id,
				FRBRD *database, FRBRD *transaction, IB_FILE * file)
{
/**************************************
 *
 *	d u m p
 *
 **************************************
 *
 * Functional description
 *	Dump a blob into a file.
 *
 **************************************/
	SCHAR buffer[256], *p;
	SSHORT short_length, l;
	ISC_STATUS_ARRAY status_vector;
	FRBRD *blob;
	USHORT bpb_length;
	UCHAR *bpb;

	bpb_length = 0;
	bpb = NULL;

/* Open the blob.  If it failed, what the hell -- just return failure */

	blob = NULL;
	if (gds__open_blob2(status_vector,
						GDS_REF(database),
						GDS_REF(transaction),
						GDS_REF(blob),
						GDS_VAL(blob_id),
						bpb_length, reinterpret_cast < char *>(bpb))) {
		gds__print_status(status_vector);
		return FALSE;
	}

/* Copy data from blob to scratch file */

	short_length = sizeof(buffer);

	for (;;) {
		gds__get_segment(status_vector,
						 GDS_REF(blob),
						 reinterpret_cast < USHORT * >(GDS_REF(l)),
						 short_length, buffer);
		if (status_vector[1] && status_vector[1] != gds_segment) {
			if (status_vector[1] != gds_segstr_eof)
				gds__print_status(status_vector);
			break;
		}
		p = buffer;
		if (l)
			do
				ib_fputc(*p++, file);
			while (--l);
	}

/* Close the blob */

	gds__close_blob(status_vector,
					GDS_REF(blob));

	return TRUE;
}


static int edit(
				GDS_QUAD * blob_id,
				FRBRD *database,
				FRBRD *transaction, SSHORT type, SCHAR * field_name)
{
/**************************************
 *
 *	e d i t
 *
 **************************************
 *
 * Functional description
 *	Open a blob, dump it to a file, allow the user to edit the
 *	window, and dump the data back into a blob.  If the user
 *	bails out, return FALSE, otherwise return TRUE.
 *
 *	If the field name coming in is too big, truncate it.
 *
 **************************************/
	TEXT file_name[50], *p, *q;
#if (defined WIN_NT)
	TEXT buffer[9];
#else
	TEXT buffer[25];
#endif
	IB_FILE *file;
#ifdef HAVE_MKSTEMP
	int fd;
#endif

	if (!(q = field_name))
		q = "gds_edit";

	for (p = buffer; *q && p < buffer + sizeof(buffer) - 1; q++)
		if (*q == '$')
			*p++ = '_';
		else
			*p++ = LOWER7(*q);
	*p = 0;

/* Moved this out of #ifndef mpexl to get mktemp/mkstemp to work for Linux
   This has been done in the inprise tree some days ago.
   Would have saved me a lot of time, if I had seen this earlier :-(
   FSG 15.Oct.2000
*/
	sprintf(file_name, "%sXXXXXX", buffer);

#ifdef HAVE_MKSTEMP
	fd = mkstemp(file_name);
	if (!(file = fdopen(fd, "w+"))) {
		close(fd);
		return FALSE;
	}
#else
	if (mktemp(file_name) == (char *)0)
		return FALSE;
	if (!(file = ib_fopen(file_name, FOPEN_WRITE_TYPE)))
		return FALSE;
	ib_fclose(file);

	if (!(file = ib_fopen(file_name, FOPEN_WRITE_TYPE_TEXT)))
		return FALSE;
#endif

	if (!dump(blob_id, database, transaction, file)) {
		ib_fclose(file);
		unlink(file_name);
		return FALSE;
	}

	ib_fclose(file);

	if (type = gds__edit(file_name, type)) {

		if (!(file = ib_fopen(file_name, FOPEN_READ_TYPE_TEXT))) {
			unlink(file_name);
			return FALSE;
		}

		load(blob_id, database, transaction, file);

		ib_fclose(file);

	}

	unlink(file_name);

	return type;
}


static int get_ods_version(
						   FRBRD **handle,
						   USHORT * ods_version, USHORT * ods_minor_version)
{
/**************************************
 *
 *	g e t _ o d s _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *	Obtain information about a on-disk structure (ods) versions
 *	of the database.
 *
 **************************************/
	ISC_STATUS_ARRAY status_vector;
	USHORT n, l;
	UCHAR item, buffer[16], *p;

	isc_database_info(status_vector,
					  handle,
					  sizeof(ods_info),
					  reinterpret_cast < char *>(const_cast <
												 UCHAR * >(ods_info)),
					  sizeof(buffer), reinterpret_cast < char *>(buffer));

	if (status_vector[1])
		return FB_FAILURE;

	p = buffer;

	while ((item = *p++) != gds_info_end) {
		l = static_cast < USHORT > (gds__vax_integer(p, 2));
		p += 2;
		n = static_cast < USHORT > (gds__vax_integer(p, l));
		p += l;
		switch (item) {
		case gds_info_ods_version:
			*ods_version = n;
			break;

		case gds_info_ods_minor_version:
			*ods_minor_version = n;
			break;

		default:
			return FB_FAILURE;
		}
	}

	return FB_SUCCESS;
}



static int load(
				GDS_QUAD * blob_id,
				FRBRD *database, FRBRD *transaction, IB_FILE * file)
{
/**************************************
 *
 *     l o a d  
 *
 **************************************
 *
 * Functional description
 *      Load a blob from a file.
 *
 **************************************/
	TEXT buffer[512], *p, *buffer_end;
	SSHORT l, c;
	ISC_STATUS_ARRAY status_vector;
	FRBRD *blob;

/* Open the blob.  If it failed, what the hell -- just return failure */

	blob = NULL;
	if (gds__create_blob(status_vector,
						 GDS_REF(database),
						 GDS_REF(transaction),
						 GDS_REF(blob),
						 GDS_VAL(blob_id))) {
		gds__print_status(status_vector);
		return FALSE;
	}

/* Copy data from file to blob.  Make up boundaries at end of of line. */

	p = buffer;
	buffer_end = buffer + sizeof(buffer);

	for (;;) {
		c = ib_fgetc(file);
		if (ib_feof(file))
			break;
		*p++ = static_cast < TEXT > (c);
		if ((c != '\n') && p < buffer_end)
			continue;
		l = p - buffer;
		if (gds__put_segment
			(status_vector, GDS_REF(blob), l,
			 buffer)) {
			gds__print_status(status_vector);
			gds__close_blob(status_vector,
							GDS_REF(blob));
			return FALSE;
		}
		p = buffer;
	}

	if ((l = p - buffer) != 0)
		if (gds__put_segment
			(status_vector, GDS_REF(blob), l,
			 buffer)) {
			gds__print_status(status_vector);
			gds__close_blob(status_vector, GDS_REF(blob));
			return FALSE;
		}

	gds__close_blob(status_vector,
					GDS_REF(blob));

	return TRUE;
}


} // extern "C"
