/* #define	COMPILE_MAIN */
/*
 * Library I/O routines for Vax-11 C
 *
 * Martin Minow
 *
 * This software is in the public-domain.
 *
 * Restrictions:
 *	We only know about text libraries.
 *	Only one library may be manipulated at at time.
 *	Little attention was paid to performance.
 */

#ifdef	COMPILE_MAIN
#define	TESTING
#endif

#include	<stdio.h>
#include	<ctype.h>
#define	EOS	0

/*
 * Define vms specific structures and definitions:
 *	descrip		String descriptors
 *	rms		File system
 *	ssdef		Return status codes
 */
#include	rms
#include	descrip
#include	ssdef

/*
 * Home made library definitions
 */
#include	"lbrdef.h"

/*
 * Library status values
 */
globalvalue	LBR$_NORMAL;		/* Normal return		*/

/*
 * The following functions are defined/used
 *
 *	int
 *	lbr_update(filename)		-- initialize existing file for update
 *	char	*filename;		-- file name to open
 *	    Opens an existing file for update.  Returns SS$_NORMAL if ok,
 *	    else the error code.
 *
 *	int
 *	lbr_create(filename, keylen, nindexes)
 *	char	*filename;
 *	int	keylen;			-- maximum key length
 *	int	nindex;			-- number of library indexes
 *	    Open the library for update.  Create it if it wasn't found.
 *	    Return SS$_NORMAL if ok, else a vms error code.
 *
 *	int
 *	lbr_open(filename)		-- initialize for read only
 *	char	*filename;
 *	    Open an existing library for input.  Return SS$_NORMAL
 *	    if opened ok, else a vms error code.
 *
 *	int
 *	lbr_close()
 *	    Close the library.  Returns the VMS error code.
 *
 *	int
 *	lbr_lookup(key)
 *	char	*key;
 *	    Setup to read the data associated with a key.
 *	    Return SS$_NORMAL if ok, else a vms error code.
 *
 *	int
 *	lbr_read(buffer, bufferlen)
 *	char	*buffer;
 *	int	bufferlen;
 *	    Read a record from the library -- after calling lbr_lookup.
 *	    Returns the number of bytes read for each record, or EOF
 *	    when at "end of data".
 *
 *	int
 *	lbr_write(buffer, bufferlen, command)
 *	char	*buffer;		-- Data buffer or key.
 *	int	bufferlen;		-- Length if data buffer.
 *	int	command;		-- Request type:
 *	  0  New entry, buffer has the key.
 *	  1  A record to be stored under this key.
 *	  2  Terminate input for this key.
 *	  3  An additional key for the record just entered.
 *	    Returns SS$_NORMAL if ok.  Exits to the operating system
 *	    if your calls are in the wrong order.
 */

typedef struct	rfa {
	long	rfa[2];
} RFA;

/*
 * Static database for library interface
 */

static	int	lbr_index;		/* VMS routines need this	*/
static	struct lbr$credef lbr_options;	/* Create options		*/

typedef struct dsc$descriptor_s STRING;	/* VMS string descriptor	*/

int
lbr_update(filename)
char	*filename;
/*
 * Initialize an existing file for update.
 */
{
	register int	errorcode;
	int		function;
	int		type_text;
	STRING		string;

	descrip(filename, -1, &string);
	function = LBR$C_UPDATE;
	type_text = LBR$C_TYP_TXT;
	if (((errorcode = lbr$ini_control(
		&lbr_index,			/* For other references	*/
		&function,			/* Assume it's there	*/
		&type_text,			/* Text library		*/
		NULL))				/* No name block	*/
	  		== LBR$_NORMAL)
	  && ((errorcode = lbr$open(
 		&lbr_index,			/* Library index	*/
		&string,			/* File name descriptor	*/
		&lbr_options,			/* Options array	*/
		NULL,				/* No default filename	*/
		NULL,				/* No NAM block		*/
		NULL,				/* No result file name	*/
		NULL))				/* No result file len	*/
			== LBR$_NORMAL)) {
		return (SS$_NORMAL);		/* Gotcha		*/
	}
#ifdef	TESTING
	message(errorcode, "Opening for update");
#endif
	return (errorcode);
}

int
lbr_create(filename, keylen, nindex)
char	*filename;
int	keylen;
int	nindex;
{

	register int	errorcode;
	int		function;
	int		type_text;
	STRING		string;

	if (lbr_update(filename) == SS$_NORMAL)
	    return (SS$_NORMAL);
	/*
	 * Couldn't open the file for update.  Try to create it.
	 */
	descrip(filename, -1, &string);
	lbr_options.cre$l_type = LBR$C_TYP_TXT;
	lbr_options.cre$l_keylen = keylen;	/* max key length	*/
	lbr_options.cre$l_alloc = 100;		/* initial allocation	*/
	lbr_options.cre$l_idxmax = nindex;	/* number of indices	*/
	lbr_options.cre$l_uhdmax = 0;		/* extra module header	*/
	lbr_options.cre$l_entall = 0;		/* Index to preallocate	*/
	function = LBR$C_CREATE;
	type_text = LBR$C_TYP_TXT;
	if (((errorcode = lbr$ini_control(
		&lbr_index,			/* For other references	*/
		&function,			/* Create a new library	*/
		&type_text,			/* Text library		*/
		NULL))				/* No name block	*/
	  		== LBR$_NORMAL)
	  && ((errorcode = lbr$open(
		    &lbr_index,			/* Library index	*/
		    &string,			/* File name descriptor	*/
		    &lbr_options,		/* Create options array	*/
		    NULL,			/* No default filename	*/
		    NULL,			/* No NAM block		*/
		    NULL,			/* No result file name	*/
		    NULL))			/* No result file len	*/
			== LBR$_NORMAL)
	  && (errorcode = lbr_close()) == SS$_NORMAL
	  && (errorcode = lbr_update(filename)) == SS$_NORMAL) {
		return (SS$_NORMAL);		/* Gotcha		*/
	}
#ifdef	TESTING
	message(errorcode, "Can't create library %s", filename);
#endif
	return (errorcode);
}

int
lbr_open(filename)
char		*filename;
/*
 * Open an existing library
 */
{
	register int	errorcode;
	int		function;
	int		type_text;
	STRING		string;

	descrip(filename, -1, &string);
	function = LBR$C_READ;
	type_text = LBR$C_TYP_TXT;
	if (((errorcode = lbr$ini_control(
		&lbr_index,			/* For other references	*/
		&function,			/* Assume it's there	*/
		&type_text,			/* Text library		*/
		NULL))				/* No name block	*/
	  		== LBR$_NORMAL)
	  && ((errorcode = lbr$open(
		&lbr_index,			/* Library index	*/
		&string,			/* File name descriptor	*/
		NULL,				/* No options array	*/
		NULL,				/* No default filename	*/
		NULL,				/* No NAM block		*/
		NULL,				/* No result file name	*/
		NULL))				/* No result file len	*/
			== LBR$_NORMAL)) {
		return (SS$_NORMAL);		/* Gotcha		*/
	}
#ifdef	TESTING
	message(errorcode, "Opening library %s", filename);
#endif
	return (errorcode);
}

int
lbr_close()
/*
 * Close the library
 */
{
	register int	errorcode;

	if ((errorcode = lbr$close(&lbr_index)) == LBR$_NORMAL)
	    return (SS$_NORMAL);
	else {
#ifdef	TESTING
	    message(errorcode, "Closing library", NULL);
#endif
	    return (errorcode);
	}
}

int
lbr_lookup(key)
char		*key;
/*
 * Setup to read data associated with a key.
 */
{
	register int	errorcode;
	RFA		textrfa;		/* Actaully unused	*/
	STRING		string;

	descrip(key, -1, &string);
	if (((errorcode = lbr$lookup_key(&lbr_index,
		&string,			/* Key descriptor	*/
		&textrfa))			/* Store rfa here	*/
			& 01) != 0) {
	    return (SS$_NORMAL);
	}
	else {
#ifdef	TESTING
	    message(errorcode, "Looking up key \"%s\"", key);
#endif
	    return (errorcode);
	}
}
	
int
lbr_read(buffer, bufflen)
char		*buffer;
int		bufflen;
/*
 * Read a record. return actual record length or EOF.
 */
{
	register int	errorcode;
	struct dsc$descriptor_s bufdes = {
	    bufflen,			/* dsc$w_length = strlen(text)	*/
	    DSC$K_DTYPE_T,		/* Text type			*/
	    DSC$K_CLASS_S,		/* String			*/
	    buffer,			/* dsc$a_pointer -> text	*/
	};
	struct dsc$descriptor_s outbufdes = {
	    0,				/* dsc$w_length = strlen(text)	*/
	    DSC$K_DTYPE_T,		/* Text type			*/
	    DSC$K_CLASS_S,		/* String			*/
	    NULL,			/* dsc$a_pointer -> text	*/
	};

	if (((errorcode = lbr$get_record(&lbr_index,
		&bufdes,		/* Where to store data		*/
		&outbufdes))		/* Gets actual datum length	*/
			& 01) != 0) {
	    return (outbufdes.dsc$w_length);
	}
	else if (errorcode == RMS$_EOF)
	    return (EOF);
	else {
#ifdef	TESTING
	    message(errorcode, "unexpected error reading text", NULL);
#endif
	    return (EOF);
	}
}

int
lbr_write(buffer, bufflen, command)
char		*buffer;
int		bufflen;
int		command;
/*
 * Manage update/replace of a module.
 * Calling sequence:
 *	lbr_write("module", 0, 0);		-- setup to enter "module"
 *	while (more_data)			-- write all data
 *	    lbr_write(datum, strlen(datum), 1);
 *	lbr_write(NULL, 0, 2);			-- terminate.
 *	while (more_alises_for_this_module)
 *	    lbr_write(alies, 0, 3);		-- write other names
 */
{
	register int	errorcode;
	struct dsc$descriptor_s bufdes = {
	    bufflen,			/* Actual data length		*/
	    DSC$K_DTYPE_T,		/* Text type			*/
	    DSC$K_CLASS_S,		/* String datum			*/
	    buffer,			/* Datum location		*/
	};
	/*
	 * The following static database maintains communication between
	 * calls of lbr_write.
 	 */
#define	INIT_STATE	0
#define	ENTER_STATE	1
#define	END_STATE	2
	static int		state = INIT_STATE;
	int			replacing;
	int			first;
	static RFA		old_module_rfa;
	static RFA		new_module_rfa;
	static RFA		first_record_rfa;
	static struct dsc$descriptor_s module_name = {
	    0,				/* Actual data length		*/
	    DSC$K_DTYPE_T,		/* Text type			*/
	    DSC$K_CLASS_S,		/* String datum			*/
	    NULL,			/* Datum location		*/
	};

	switch (command) {
	case 0:				/* Initialize			*/
	    if (state == ENTER_STATE) {
		fprintf(stderr, "Bug calling lbr_write:");
		fprintf(stderr, "  you must terminate previous");
		fprintf(stderr, " entry before entering a new module\n");
		fprintf(stderr, "old library key is \"%s\",",
		    module_name.dsc$a_pointer);
		fprintf(stderr, " new key is \"%s\"\n", buffer);
		exit(SS$_BADPARAM);
	    }
	    module_name.dsc$a_pointer = malloc(strlen(buffer) + 1);
	    strcpy(module_name.dsc$a_pointer, buffer);
	    module_name.dsc$w_length = strlen(buffer);
	    replacing = lbr$lookup_key(&lbr_index,
		&module_name,		/* Key to search for		*/
		&old_module_rfa);	/* Old module for deletion	*/
	    state = ENTER_STATE;
	    first = TRUE;
	    return (SS$_NORMAL);	/* Always successful		*/

	case 1:				/* Put a record			*/
	    if (state != ENTER_STATE) {
		fprintf(stderr, "Bug -- lbr_write calls out of order\n");
		exit(SS$_BADPARAM);
	    }
	    if ((errorcode = lbr$put_record(&lbr_index,
		    &bufdes,
		    &new_module_rfa)) & 01) {
		if (first) {
		    first_record_rfa = new_module_rfa;
		    first = FALSE;
		}
		return (SS$_NORMAL);
	    }
	    else {
#ifdef	TESTING
		message(errorcode, "putting a module record", NULL);
#endif
		return (errorcode);
	    }

	case 2:
	    if (state != ENTER_STATE) {
		fprintf(stderr, "Bug: lbr_write calls out of order.\n");
		exit(SS$_BADPARAM);
	    }
	    if (((errorcode = lbr$put_end(&lbr_index)) & 01) != 0
	     && ((errorcode = lbr$replace_key(&lbr_index,
			&module_name,
			&old_module_rfa,
			&new_module_rfa)) & 01) != 0
	     && ((replacing & 01) == 0
	      || ((errorcode = lbr$delete_data(&lbr_index,
			&old_module_rfa)) & 01) != 0)) {
		free(module_name.dsc$a_pointer);
		module_name.dsc$a_pointer = NULL;
		state = END_STATE;
		return (SS$_NORMAL);
	    }
	    else {
#ifdef	TESTING
		message(errorcode, "cleanup after write of \"%s\"",
		    module_name.dsc$a_pointer);
#endif
		return (errorcode);
	    }

	case 3:
	    if (state != END_STATE) {
		fprintf(stderr, "Bug: lbr_write calls out of order\n");
		exit(SS$_BADPARAM);
	    }
	    descrip(buffer, -1, &bufdes);
	    replacing = lbr$lookup_key(&lbr_index, &bufdes, &old_module_rfa);
	    if (replacing & 01) {
		if (((errorcode = lbr$delete_data(&lbr_index,
				&old_module_rfa)) & 01) == 0) {
#ifdef	TESTING
		    message(errorcode, "delete_data for \"%s\"", buffer);
#endif
		    return (errorcode);
		}
		if (((errorcode = lbr$delete_key(&lbr_index, &bufdes))
			& 01) == 0) {
#ifdef	TESTING
		    message(errorcode, "delete_key for \"%s\"", buffer);
#endif
		    return (errorcode);
		}
	    }
	    if (((errorcode = lbr$insert_key(&lbr_index,
			&bufdes, &first_record_rfa)) & 01) == 0) {
#ifdef	TESTING
		message(errorcode, "alternate key \"%s\"\n", buffer);
#endif
		return (errorcode);
	    }
	    return (SS$_NORMAL);
	}
}

static
descrip(text, len, stringp)
char		*text;
int		len;
STRING		*stringp;
/*
 * Load string descriptor
 */
{
	if (len == -1)
	    len = strlen(text);
	stringp->dsc$a_pointer = text;
	stringp->dsc$w_length = len;
	stringp->dsc$b_dtype = DSC$K_DTYPE_T;
	stringp->dsc$b_class = DSC$K_CLASS_S;
}

static
message(errorcode, why, arg)
int		errorcode;
char		*why;
char		*arg;
/*
 * Print error message
 */
{
	extern char	*vms_etext();

	if (why == NULL) {
	    fprintf(stderr, "\n%s\n", vms_etext(errorcode));
	}
	else if (arg == NULL) {
	    fprintf(stderr, "\n%s: %s\n", why, vms_etext(errorcode));
	}
	else {
	    fprintf(stderr, "\n");
	    fprintf(stderr, why, arg);
	    fprintf(stderr, ": %s\n", vms_etext(errorcode));
	}
}

#ifdef	COMPILE_MAIN

char	line[514];
char	filename[514];

main()
{
	register int	readcount;

	while (prompt("Library file name")) {
	    strcpy(filename, line);
	    read_module();
	    enter_module();
	}
}

read_module()
{
	register int	readcount;

	lbr_open(filename);
	fprintf(stderr, "open, index = %d\n", lbr_index);
	while (prompt("Module name")) {
	    if (lbr_lookup(line) == SS$_NORMAL) {
		while ((readcount = lbr_read(line, sizeof line)) >= 0) {
		    line[readcount] = EOS;
		    fprintf(stderr, "%6d\t%s\n", readcount, line);
		}
	    }
	    else fprintf(stderr, "Couldn't find \"%s\"\n", line);
	}
	lbr_close();
}

enter_module()
{
	lbr_create(filename, 32, 1);
	fprintf(stderr, "open for update, index = %d\n", lbr_index);
	while (prompt("Module name to enter")) {
	    if (lbr_lookup(line) == SS$_NORMAL)
		fprintf(stderr, "Warning, \"%s\" in library\n", line);
	    if (lbr_write(line, 0, 0) != SS$_NORMAL)
		fprintf(stderr, "Entry failed\n");
	    else {
		while (prompt("data")) {
		    if (lbr_write(line, strlen(line), 1) != SS$_NORMAL)
			fprintf(stderr, "Error entering data\n");
		}
		if (lbr_write(NULL, 0, 2) != SS$_NORMAL)
		    fprintf(stderr, "Error closing entry\n");
		while (prompt("Additional module names")) {
		    if (lbr_write(line, 0, 3) != SS$_NORMAL)
			fprintf(stderr, "Error entering additional name\n");
		}
	    }
	}
	lbr_close();
}

prompt(text)
char		*text;
{
	clearerr(stdin);
	fprintf(stderr, "%s: ", text);
	fflush(stdin);
	if (gets(line) == NULL || line[0] == EOS)
	    return (FALSE);
	return (TRUE);
}
#endif

