/********************************************************************/
/*                                                                  */
/*   MQCAPTURE has 2 parameters                                     */
/*                                                                  */
/*      - name of the parameters file                               */
/*      - name of the file to store the captured messages           */
/*                                                                  */
/*    if no queue manager is specified, the default queue manager   */
/*    is used.                                                      */
/*                                                                  */
/********************************************************************/

/********************************************************************/
/*                                                                  */
/* Changes in V1.1                                                  */
/*                                                                  */
/* 1) Added support for Sun Solaris.                                */
/* 2) Use keyword rather than positional arguments.                 */
/* 3) Removed spurious remote qm name paramter.                     */
/*                                                                  */
/********************************************************************/

/********************************************************************/
/*                                                                  */
/* Changes in V1.2                                                  */
/*                                                                  */
/* 1) Added support for groups.                                     */
/*                                                                  */
/********************************************************************/

/********************************************************************/
/*                                                                  */
/* Changes in V1.3                                                  */
/*                                                                  */
/* 1) Added support for non-destructive gets.                       */
/* 2) Added support for capture of MQMD as well as message data     */
/* 3) Added maximum message length parameter override.              */
/*                                                                  */
/********************************************************************/

/********************************************************************/
/*                                                                  */
/* Changes in V1.3.1                                                */
/*                                                                  */
/* 1) Fixed bug where maxMsgLen was too large for client            */
/*    connections (2010 reason code on MQGET).                      */
/*                                                                  */
/********************************************************************/

/********************************************************************/
/*                                                                  */
/* Changes in V1.3.2                                                */
/*                                                                  */
/* 1) Added support for get by msgid, correlid and groupid          */
/* 2) Added check of completion code after MQINQ for max length.    */
/*                                                                  */
/********************************************************************/
/********************************************************************/
/*                                                                  */
/* Changes in V1.3.3                                                */
/*                                                                  */
/* 1) Added display of maximum message length.                      */
/* 2) Do not replace maximum message size if inq returns 0.         */
/*                                                                  */
/********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef SOLARIS
#include <ctype.h>
#include <unistd.h>
#endif

/* includes for MQI */
#include <cmqc.h>

/* common subroutines include */
#include "int64defs.h"
#include "comsubs.h"

/* MQ subroutines include */
#include "qsubs.h"

/* default buffer size is 16MB but will be adjusted to maximum length supported */
#define MAX_MESSAGE_LENGTH	256 * 65536
#define DEFAULT_DELIMITER	"#@#@#"

#define QNAME				"QNAME"
#define QMGR				"QMGR"
#define DELIMITER			"DELIMITER"
#define DELIMITERX			"DELIMITERX"
#define MSGID				"MSGID"
#define MSGIDX				"MSGIDX"
#define CORRELID			"CORRELID"
#define CORRELIDX			"CORRELIDX"
#define GROUPID				"GROUPID"
#define GROUPIDX			"GROUPIDX"
#define STRIPRFH			"STRIPRFH"
#define MAXMSGCOUNT			"MSGCOUNT"
#define READONLY			"READONLY"
#define SAVEMQMD			"SAVEMQMD"
#define MAXMSGLEN			"MAXMSGLEN"
#define INDIVFILES			"INDIVFILES"

#define RFHSTRIP			1
#define RFHNO				0

/**************************************************************/
/*                                                            */
/* Parameter values from parameter file.                      */
/*                                                            */
/**************************************************************/

typedef struct {
	/* global error switch */
	int		err;

	/* Universal parameters */
	int		logicalOrder;
	char	qname[MQ_Q_NAME_LENGTH + 1];
	char	saveQname[MQ_Q_NAME_LENGTH + 1];
#ifdef MQCLIENT
	char	qmname[512];
	char	saveQMname[512];
#else
	char	qmname[MQ_Q_MGR_NAME_LENGTH + 1];
	char	saveQMname[MQ_Q_MGR_NAME_LENGTH + 1];
#endif
	int		saveMQMD;
	int		saveSaveMQMD;
	int		readOnly;
	int		indivFiles;
	int		saveReadOnly;
	int		maxMsgLen;
	int		msgidSet;
	int		correlidSet;
	int		groupidSet;

	int		delimiterLen;		/* length of the delimiter in bytes */
	char	delimiter[513];		/* delimiter to be used between messages */
	int		maxmsgcount;		/* 0 is unlimited */
	int		striprfh;			/* whether to strip MQ headers before saving the data */

	/* MQMD parameters - per message */
	char	msgid[MQ_GROUP_ID_LENGTH + 1];
	char	correlid[MQ_CORREL_ID_LENGTH + 1];
	char	groupid[MQ_GROUP_ID_LENGTH + 1];

	/* name of the parameters file */
	char	parmFilename[512];
	char	outputFilename[512];
} capParms;

static char Copyright[] = "(C) Copyright IBM Corp, 2001/2002/2003/2004\n";
static char Version[]=\
"@(#)MQCapture V1.33 - MQSI V2.0 MQ data capture tool  - Jim MacNair ";

#ifdef _DEBUG
	#ifdef MQCLIENT
		static char Level[]="mqcapture.c V1.33 Client Debug version ("__DATE__" "__TIME__")\n";
	#else
		static char Level[]="mqcapture.c V1.33 Debug version ("__DATE__" "__TIME__")\n";
	#endif
#else
	#ifdef MQCLIENT
		static char Level[]="mqcapture.c V1.33 Client Release version ("__DATE__" "__TIME__")\n";
	#else
		static char Level[]="mqcapture.c V1.33 Release version ("__DATE__" "__TIME__")\n";
	#endif
#endif

void captureString(char * value, char * arg, const int maxlen, capParms * parms)

{
	if ((int) strlen(arg) < maxlen)
	{
		strcpy(value, arg);
	}
	else
	{
		/* issue an error message */
		printf("*****Value of constant exceeds maximum length\n");

		/* Set the return code based on the message number */
		parms->err = 94;
	}
}

int getArgPointer(int argc,
				  char *argv[],
				  int index,
				  char ** parmData,
				  const char * parmDesc,
				  capParms * parms)

{
	if (strlen(argv[index]) > 2)
	{
		/* point to the argument data */
		(*parmData) = argv[index] + 2;
	}
	else
	{
		index++;

		if (index < argc)
		{
			/* point to the argument data */
			(*parmData) = argv[index];
		}
		else
		{
			/* issue an error message */
			printf("*****%s parameter is missing value\n", parmDesc);
			
			/* Set the return code to indicate the error */
			parms->err = 95;

			/*point to something valid */
			(*parmData) = argv[index - 1] + 2;
		}
	}


	return index;
}

int processIndArg(int argc, 
				  char *argv[], 
				  int index, 
				  char * parm, 
				  int parmsize, 
				  const char * parmDesc,
				  capParms * parms)

{
	char *argData;

	index = getArgPointer(argc, argv, index, &argData, parmDesc, parms);

	if (index < argc)
	{
		/* value is included with parameter */
		captureString(parm, argData, parmsize, parms);
	}

	return index;
}

void processArgs(int argc, char **argv, capParms * parms)

{
	int		i;
	int		foundit;
	char	ch;
	char	logOrd[32];

	i = 1;
	while ((i < argc) && (0 == parms->err))
	{
		foundit = 0;

		if ('-' == argv[i][0])
		{
			ch = toupper(argv[i][1]);

			/* check for option */
			if ((0 == foundit) && ('Q' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  parms->saveQname, 
								  sizeof(parms->saveQname), 
								  "queue name (-q)",
								  parms);
			}

			/* check for option */
			if ((0 == foundit) && ('M' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  parms->saveQMname, 
								  sizeof(parms->saveQMname), 
								  "queue manager name (-m)",
								  parms);
			}

			/* check for option */
			if ((0 == foundit) && ('F' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  parms->parmFilename, 
								  sizeof(parms->parmFilename), 
								  "name of parameters file (-f)",
								  parms);
			}

			/* check for option */
			if ((0 == foundit) && ('O' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  parms->outputFilename, 
								  sizeof(parms->outputFilename), 
								  "name of output data file (-o)",
								  parms);
			}

			/* check for logical order option */
			if ((0 == foundit) && ('L' == ch))
			{
				foundit = 1;

				i = processIndArg(argc, 
								  argv, 
								  i,
								  logOrd, 
								  sizeof(logOrd), 
								  "use logical order (-l)",
								  parms);

				if (logOrd[1] == '1')
				{
					parms->logicalOrder = 1;
				}
			}

			/* check for option */
			if ((0 == foundit) && ('D' == ch))
			{
				foundit = 1;
				parms->saveSaveMQMD = 1;
			}

			/* check for option */
			if ((0 == foundit) && ('R' == ch))
			{
				foundit = 1;
				parms->saveReadOnly = 1;
			}
		}

		/* did we recognize the parameter? */
		if (0 == foundit)
		{
			/* issue an error message */
			printf("*****Unrecognized command line parameter found %c\n", argv[i][0]);

			/* Set the return code based on the message number */
			parms->err = 93;
		}

		i++;
	}
}

/**************************************************************/
/*                                                            */
/* Process a line in the parameters file.                     */
/*                                                            */
/**************************************************************/

int evaluateParm(char * ptr, char * value, capParms * parms)

{
	int foundit=0;
	int len;
	char *valueptr;

	/* remove any quotes from the value */
	valueptr = removeQuotes(value);
	len = strlen(valueptr);

	if (strcmp(ptr, QNAME) == 0)
	{
		foundit = 1;
		if (len > MQ_Q_NAME_LENGTH)
		{
			/* parameter too long, issue warning and truncate */
			printf("***** Queue name - parameter too long (%d) - data truncated - (%s)\n", 
					len, value);

			len = MQ_Q_NAME_LENGTH;
		}

		/* check for previous setting */
		if (parms->qname[0] > ' ')
		{
			/* issue warning message */
			printf("***** output queue name has been specified previously, new value (%d) used\n",
					valueptr);
		}

		/* get the output queue name */
		memset(parms->qname, 0, sizeof(parms->qname));
		memcpy(parms->qname, valueptr, len);
	}

	if (strcmp(ptr, QMGR) == 0)
	{
		foundit = 1;
		if (len > sizeof(parms->qmname) - 1)
		{
			/* parameter too long, issue warning and truncate */
			printf("***** Queue manager name - parameter too long (%d) - data truncated - (%s)\n", 
					len, value);

			len = sizeof(parms->qmname) - 1;
		}

		/* check for previous setting */
		if (parms->qmname[0] > ' ')
		{
			/* issue warning message */
			printf("***** output queue manager has been specified previously, new value (%d) used\n",
					valueptr);
		}

		/* get the output queue manager name to connect to */
		memset(parms->qmname, 0, sizeof(parms->qmname));
		memcpy(parms->qmname, valueptr, len);
	}

	if (strcmp(ptr, DELIMITER) == 0)
	{
		foundit = 1;

		if (len > sizeof(parms->delimiter))
		{
			len = sizeof(parms->delimiter);

			/* delimiter is too long */
			printf("***** delimiter too long - value was truncated to %d\n", len);
		}

		strcpy(parms->delimiter, valueptr);
		parms->delimiterLen = len;
	}

	if (strcmp(ptr, DELIMITERX) == 0)
	{
		foundit = 1;

		if (len > (2 * (sizeof(parms->delimiter) - 1)))
		{
			len = 2 * (sizeof(parms->delimiter) - 1);

			/* delimiter is too long */
			printf("***** delimiter too long - value was truncated to length %d\n", len);
		}

		/* make sure the length is an even number of bytes */
		if ((len & 1) > 0)
		{
			/* odd number of bytes, issue error and return */
			printf("***** delimiterx value has odd number of bytes (%d) for hex value - %s\n", 
					len, value);
			printf("***** last character was ignored\n");

			/* truncate the last character */
			len--;
		}

		parms->delimiterLen = len / 2;
		memset(parms->delimiter, 0, sizeof(parms->delimiter));
		HexToAscii(valueptr, len, parms->delimiter);
	}

	if (strcmp(ptr, MSGID) == 0)
	{
		foundit = 1;
		parms->msgidSet = 1;
		editCharParm(MSGID, parms->msgid, valueptr, sizeof(parms->msgid));
	}

	if (strcmp(ptr, MSGIDX) == 0)
	{
		foundit = 1;
		parms->msgidSet = 1;
		editHexParm(MSGIDX, parms->msgid, valueptr, sizeof(parms->msgid));
	}

	if (strcmp(ptr, CORRELID) == 0)
	{
		foundit = 1;
		parms->correlidSet = 1;
		editCharParm(CORRELID, parms->correlid, valueptr, sizeof(parms->correlid));
	}

	if (strcmp(ptr, CORRELIDX) == 0)
	{
		foundit = 1;
		parms->correlidSet = 1;
		editHexParm(CORRELIDX, parms->correlid, valueptr, sizeof(parms->correlid));
	}

	if (strcmp(ptr, GROUPID) == 0)
	{
		foundit = 1;
		parms->groupidSet = 1;
		editCharParm(GROUPID, parms->groupid, valueptr, sizeof(parms->groupid));
	}

	if (strcmp(ptr, GROUPIDX) == 0)
	{
		foundit = 1;
		parms->groupidSet = 1;
		editHexParm(GROUPIDX, parms->groupid, valueptr, sizeof(parms->groupid));
	}

	if (strcmp(ptr, STRIPRFH) == 0)
	{
		foundit = 1;

		if (('y' == valueptr[0]) || ('Y' == valueptr[0]))
		{
			parms->striprfh = RFHSTRIP;
		}
		else
		{
			parms->striprfh = RFHNO;
		}
	}

	if (strcmp(ptr, MAXMSGCOUNT) == 0)
	{
		foundit = 1;

		parms->maxmsgcount = atoi(valueptr);
		if (parms->maxmsgcount < 1)
		{
			printf("***** invalid value for maxMsgCount %d *****\n", parms->maxmsgcount);
			parms->maxmsgcount = 1;
		}
	}

	if (strcmp(ptr, MAXMSGLEN) == 0)
	{
		foundit = 1;

		parms->maxMsgLen = atoi(valueptr) + 1;
		if (parms->maxMsgLen < 2)
		{
			printf("***** invalid value for MaxMsgLen %d *****\n", parms->maxMsgLen);
			parms->maxMsgLen = MAX_MESSAGE_LENGTH;
		}
	}

	if (strcmp(ptr, READONLY) == 0)
	{
		foundit = 1;

		if (('y' == valueptr[0]) || ('Y' == valueptr[0]) || ('1' == valueptr[0]))
		{
			parms->readOnly = 1;
		}
		else
		{
			parms->readOnly = 0;
		}
	}

	if (strcmp(ptr, INDIVFILES) == 0)
	{
		foundit = 1;

		if (('y' == valueptr[0]) || ('Y' == valueptr[0]) || ('1' == valueptr[0]))
		{
			parms->indivFiles = 1;
		}
		else
		{
			parms->indivFiles = 0;
		}
	}

	if (strcmp(ptr, SAVEMQMD) == 0)
	{
		foundit = 1;

		if (('y' == valueptr[0]) || ('Y' == valueptr[0]))
		{
			parms->saveMQMD = 1;
		}
		else
		{
			parms->saveMQMD = 0;
		}
	}

	return foundit;
}

/**************************************************************/
/*                                                            */
/* Find the parameter and the value.                          */
/*                                                            */
/**************************************************************/

void processParmLine(char * ptr, capParms * parms)

{
	int		foundit=0;
	char *	valueptr;
	char *	blankptr;

	/* find the equal equal sign */
	valueptr = strchr(ptr, '=');

	/* did we find one? */
	if (valueptr != NULL)
	{
		/* break the line into name and value parts */
		valueptr[0] = 0;
		valueptr++;

		/* find the beginning of the value */
		valueptr = skipBlanks(valueptr);
	}

	/* strip trailing blanks and translate the name to upper case */
	blankptr = findBlank(ptr);
	blankptr[0] = 0;
	strupper(ptr);

	/* is this a name value pair? */
	if (valueptr != NULL)
	{
		/* check that we have a value */
		if (valueptr[0] > 0)
		{
			foundit = evaluateParm(ptr, valueptr, parms);

			if (foundit == 0)
			{
				printf("***** unrecognized parameter %s, value %s\n", ptr, valueptr);
			}
		}
		else
		{
			printf("***** missing or blank value for parameter %s\n", ptr);
		}
	}
}

void processParms(FILE * parmfile, capParms * parms)

{
	int		len;
	char *	ptr;
	char	parmline[512];

	/* read the parameter file */
	while (fgets(parmline, sizeof(parmline) - 1, parmfile) != NULL)
	{
		/* check for a new line character at the end of the line */
		while ((strlen(parmline) > 0) && (parmline[strlen(parmline) - 1] < ' '))
		{
			/* get rid of the new line character */
			parmline[strlen(parmline) - 1] = 0;
		}

		/* point to the beginning of the line */
		/* skip any leading blanks */
		ptr = skipBlanks(parmline);

		/* truncate any trailing blanks */
		len = strlen(ptr);
		while ((len > 0) && (ptr[len] == ' '))
		{
			ptr[len] = 0;
			len--;
		}

		/* check for a comment or blank line */
		if ((ptr[0] != 0) && (ptr[0] != ';') && (ptr[0] != '#') && (ptr[0] != '*'))
		{
			/* process this line */
			processParmLine(ptr, parms);

			/* re-initialize the parameter input area */
			memset(parmline, 0, sizeof(parmline));
		}
	}
}

int processParmFile(capParms * parms)

{
	int		rc=0;
	int		len=0;
	FILE	*parmfile;

	parmfile = fopen(parms->parmFilename, "r");

	if (NULL == parmfile)
	{
		rc = 1;
		printf("unable to open parameters file %s\n", parms->parmFilename);
	}
	else
	{
		/* process the parameters file */
		processParms(parmfile, parms);

		/* close the parameters file */
		fclose(parmfile);
	}

	return rc;
}

/********************************************************************/
/*                                                                  */
/* Routine to create a file name from a template file name and      */
/* a message number.  The message number will be inserted before    */
/* any file extension.  A file extension is considered to be        */
/* any characters that follow a period on the end of the file name. */
/*                                                                  */
/********************************************************************/

void createNextFileName(const char *fileName, char *newFileName, int fileCount)

{
	int		rc=0;
	char	*ptr;
	char	tempCount[16];
	char	fileExt[1024];

	/* make sure we have a file name to use as a template */
	if (strlen(fileName) == 0)
	{
		/* no file name - get out as gracefully as possible */
		newFileName[0] = 0;
		return;
	}

	/* get the file name into a work area */
	strcpy(newFileName, fileName);

	/* check if we have a file extension */
	ptr = newFileName + strlen(newFileName);

	/* look for a period before we find a directory                                 */
	/* the check for a slash is necessary to handle a file name with no extension   */
	/* and a directory name with a period                                           */
	while ((ptr > newFileName) && ('.' != ptr[0]) && ('\\' != ptr[0]) && ('/' != ptr[0]))
	{
		ptr--;
	}

	/* did we find a period? */
	if ((ptr >= newFileName) && ('.' == ptr[0]))
	{
		/* save the file extension */
		strcpy(fileExt, ptr);

		/* get rid of the period */
		ptr[0] = 0;
	}
	else
	{
		/* no file extension */
		fileExt[0] = 0;

		/* point to the end of the file name, so we can append */
		ptr = newFileName + strlen(newFileName);
	}

	/* insert a file count on the end of the file name */
	memset(tempCount, 0, sizeof(tempCount));
	sprintf(tempCount, "%d", fileCount);
	strcat(ptr, tempCount);

	/* restore the file extension */
	strcat(ptr, fileExt);
}

int checkRFH(const char * msgdata, const int datalen, MQMD *mqmd, capParms * parms)

{
	MQRFH2	tempRFH;
	char	tempEbcdic[32];
	int		ccsid;
	int		encoding;
	int		rfhlength=0;
	int		rfhversion;
	char	tempFormat[9];

	/* check if the message is long enough to have an RFH */
	if (datalen < sizeof(tempRFH))
	{
		return 0;
	}

	/* get a copy of the first 32 bytes of the message */
	memcpy(&tempRFH, msgdata, sizeof(tempRFH));
	memcpy(tempFormat, tempRFH.Format, 8);
	tempFormat[8] = 0;

	if (memcmp(tempRFH.StrucId, MQRFH_STRUC_ID, sizeof(tempRFH.StrucId)) != 0)
	{
		/* try converting the StrucId and format fields to EBCDIC */
		EbcdicToAscii((unsigned char *) &tempRFH.StrucId, sizeof(tempRFH.StrucId), (unsigned char *)tempEbcdic);
		EbcdicToAscii((unsigned char *) &tempRFH.Format, sizeof(tempRFH.Format), (unsigned char *)tempFormat);
		memcpy(&tempRFH.StrucId, tempEbcdic, sizeof(tempRFH.StrucId));

		/* see if we have a structure identifier in EBCDIC */
		if (memcmp(tempRFH.StrucId, MQRFH_STRUC_ID, sizeof(tempRFH.StrucId)) != 0)
		{
			/* no RFH found */
			return 0;
		}
	}

	/* see if we can recognize the version and length */
	rfhversion = tempRFH.Version;
	rfhlength = tempRFH.StrucLength;
	ccsid = tempRFH.CodedCharSetId;
	encoding = tempRFH.Encoding;

	/* check if we have the right encoding */
	if ((MQRFH_VERSION_1 != rfhversion) && (MQRFH_VERSION_2 != rfhversion))
	{
		/* reverse the bytes and see if we recognize the version */
		rfhversion = reverseBytes4(rfhversion);
		rfhlength = reverseBytes4(rfhlength);
		ccsid = reverseBytes4(ccsid);
		encoding = reverseBytes4(encoding);

		if ((MQRFH_VERSION_1 != rfhversion) && (MQRFH_VERSION_2 != rfhversion))
		{
			return 0;
		}
	}

	/* finally check that the length makes sense */
	if ((rfhlength < MQRFH_STRUC_LENGTH_FIXED) || (rfhlength > datalen))
	{
		/* length doesn't make sense */
		return 0;
	}

	/* check if we are removing the RFH header and saving the MQMD */
	if ((1 == parms->saveMQMD) && (RFHSTRIP == parms->striprfh))
	{
		/* update the format, encoding and codepage fields in the MQMD */
		memcpy(mqmd->Format, tempRFH.Format, sizeof(mqmd->Format));
		if (ccsid > 0)
		{
			mqmd->CodedCharSetId = ccsid;
		}

		if (encoding > 0)
		{
			mqmd->Encoding = encoding;
		}
	}

	return rfhlength;
}

void printHelp(char *pgmName)

{
	printf("format is:\n");
	printf("   %s -f parm_filename -o data_filename -l 0|1 -m QMname -q queue -r -d\n", pgmName);
	printf("         -r indicates that non-destructive reads will be used.\n");
	printf("         -d will save the MQMD with the data\n");
	printf("         -l is logical order and is used with MQ message groups\n");
	printf("           it can be 1 (use logical order) or 0 (default - do not use)\n");
	printf("         -m will override the queue manager and -q will override the queue name\n");
#ifdef MQCLIENT
	printf("         -m can be in the form of ChannelName/TCP/hostname(port)\n");
#endif
}

int main(int argc, char **argv)
{
	int			rc;
	int			msgcount=0;
	int			maxrate=0;
	int			firstsec=0;
	int			secondcount=0;
	int			totalbytes=0;
	int			avgbytes;
	int			firstTime=0;
	int			rfhlength=0;
	int			fileCount=0;				/* count of individual files used */
	MQLONG		qm=0;
	MQLONG		q=0;
	MQLONG		compcode;
	MQLONG		reason;
	MQLONG		Select[1];					/* attribute selectors           */
	MQLONG		IAV[1];						/* integer attribute values      */
	MQOD		objdesc = {MQOD_DEFAULT};
	MQMD		msgdesc = {MQMD_DEFAULT};
	MQLONG		openopt = 0;				/* MQ open options */
	MQGMO		mqgmo = {MQGMO_DEFAULT};	/* get message options structure used in MQGET */
	MQLONG		datalen=0;					/* length of the message that was read */
	FILE		*outFile;					/* output file */
	char		*msgdata;					/* pointer to message data */
	char		newFileName[512];			/* file name to open - needed when more than one message per file */
	MQOD		od = {MQOD_DEFAULT};	    /* Object Descriptor             */
	capParms	parms;						/* input parameters and global variables */

	/* print the copyright statement */
	printf(Copyright);
	printf(Level);

	/* initialize the work areas */
	memset(&parms, 0, sizeof(parms));
	parms.saveSaveMQMD = -1;
	parms.saveReadOnly = -1;
	parms.maxMsgLen = MAX_MESSAGE_LENGTH;

	/* check for too few input parameters */
	if (argc < 3)
	{
		printHelp(argv[0]);
		exit(99);
	}

	/* process any command line arguments */
	processArgs(argc, argv, &parms);

	if (parms.err != 0)
	{
		printHelp(argv[0]);
		exit(99);
	}

	if ((0 == parms.parmFilename[0]) || (0 == parms.outputFilename[0]))
	{
		printHelp(argv[0]);
		exit(99);
	}

	/* set defaults for the delimiter value and length */
	strcpy(parms.delimiter, DEFAULT_DELIMITER);
	parms.delimiterLen = sizeof(DEFAULT_DELIMITER) - 1;

	/* tell what parameters file we are using */
	printf("Reading parameters from file %s\n", parms.parmFilename);

	/* read the parameters file to get the delimiter string */
	rc = processParmFile(&parms);

	/* check for overrides */
	if (parms.saveQMname[0] != 0)
	{
		strcpy(parms.qmname, parms.saveQMname);
	}

	/* check for overrides */
	if (parms.saveQname[0] != 0)
	{
		strcpy(parms.qname, parms.saveQname);
	}

	/* check for overrides */
	if (parms.saveReadOnly != -1)
	{
		parms.readOnly = parms.saveReadOnly;
	}

	/* check for overrides */
	if (parms.saveSaveMQMD != -1)
	{
		parms.saveMQMD = parms.saveSaveMQMD;
	}

	/* exit if queue name not found */
	if (0 == parms.qname[0])
	{
		printf("***** Queue name not found in parameters file\n");
		return 2;
	}

	/* return if unable to find parameters file */
	if (rc != 0)
	{
		return rc;
	}

	/* check if we are using logical order */
	if (1 == parms.logicalOrder)
	{
		printf("Logical order option selected\n");
	}

	if (RFHSTRIP == parms.striprfh)
	{
		printf("StripRFH option selected - RFH headers will be discarded if present\n");
	}

	/* tell if we are in browse mode vs normal get mode */
	if (1 == parms.readOnly)
	{
		printf("Readonly option selected - using browse - messages will be left on the queue\n");
	}

	/* tell if we are saving the MQMD with the data */
	if (1 == parms.saveMQMD)
	{
		printf("SaveMQMD option selected - Message descriptors will be saved with the message data\n");
	}

	/* tell if we are each message in its own file */
	if (1 == parms.indivFiles)
	{
		printf("indivFiles option selected - Each file will be saved in a seperate file\n");
	}

	/* Tell what queue and qm will be used */
	if (0 == parms.qmname[0])
	{
		printf("Reading messages from Queue(%s) on default Qmgr\n", parms.qname);
	}
	else
	{
		printf("Reading messages from Queue(%s) on Qmgr(%s)\n", parms.qname, parms.qmname);
	}

	/* Tell what delimiter we are using */
	if ((0 == parms.delimiterLen) && (0 == parms.indivFiles))
	{
		/* print a warning message */
		printf("***** WARNING - delimiter length is zero\n");
	}
	else if (0 == parms.indivFiles)
	{
		/* print the delimiter */
		printf("Delimiter length is %d  value(%s)\n", parms.delimiterLen, parms.delimiter);
	}

	/* check for help request */
	if ((parms.outputFilename[0] == '?') || (parms.outputFilename[1] == '?'))
	{
		printHelp(argv[0]);
		exit(0);
	}

	/* open output file */
	printf("opening output file %s\n", parms.outputFilename);
	outFile = fopen(parms.outputFilename, "wb");

	if (NULL == outFile)
	{
		printf("unable to open output file %s for output\n", parms.outputFilename);
		return 100;
	}

	/* Connect to the queue manager */
#ifdef MQCLIENT
	clientConnect2QM(parms.qmname, &qm, &(parms.maxMsgLen), &compcode, &reason);
#else
	connect2QM(parms.qmname, &qm, &compcode, &reason);
#endif

	/* check for errors */
	if (compcode != MQCC_OK)
	{
		return 98;
	}

	/* set the queue open options */
	strncpy(objdesc.ObjectName, parms.qname, MQ_Q_NAME_LENGTH);

	/* check if should use non-destructive gets */
	if (0 == parms.readOnly)
	{
		openopt = MQOO_INPUT_SHARED + MQOO_FAIL_IF_QUIESCING + MQOO_INQUIRE;
	}
	else
	{
		openopt = MQOO_BROWSE + MQOO_FAIL_IF_QUIESCING + MQOO_INQUIRE;
	}

	/* open the queue for input */
	printf("opening queue %s for input\n", parms.qname);
	MQOPEN(qm, &objdesc, openopt, &q, &compcode, &reason);

	/* check for errors */
	checkerror("MQOPEN", compcode, reason, parms.qname);
	if (compcode != MQCC_OK)
	{
		return 97;
	}

	/* check if our maximum message length is too large         */
	/* This is to avoid 2010 return codes on client connections */
	Select[0] = MQIA_MAX_MSG_LENGTH;
	IAV[0]=0;
	MQINQ(qm, q, 1L, Select, 1L, IAV, 0L, NULL, &compcode, &reason);

	if ((0 == compcode) && (IAV[0] < parms.maxMsgLen) && (IAV[0] > 0))
	{
		/* change the maximum message length to the maximum allowed */
		parms.maxMsgLen = IAV[0];
	}
	else
	{
		checkerror("MQINQ", compcode, reason, parms.qname);
	}

	/* allocate a buffer for the message */
	msgdata = (char *)malloc(parms.maxMsgLen + 1);
	memset(msgdata, 0, parms.maxMsgLen +1);

	/* display the maximum message length that can be read */
	printf("Maximum message size that can be read is %d\n", parms.maxMsgLen);

	/* enter get message loop */
	while ((compcode == MQCC_OK) && ((0 == parms.maxmsgcount) || (msgcount < parms.maxmsgcount)))
	{
		/* set the get message options */
		mqgmo.Options = MQGMO_NO_WAIT | MQGMO_FAIL_IF_QUIESCING;
		mqgmo.MatchOptions = MQGMO_NONE;

		/* check if we are using logical order */
		if (1 == parms.logicalOrder)
		{
			mqgmo.Options |= MQGMO_LOGICAL_ORDER;
		}

		/* check if we are using non-destructive gets */
		if (1 == parms.readOnly)
		{
			/* use browse */
			mqgmo.Options |= MQGMO_BROWSE_NEXT;
		}

		/* reset the msgid and correlid */
		memcpy(msgdesc.MsgId, MQMI_NONE, sizeof(msgdesc.MsgId));
		memcpy(msgdesc.CorrelId, MQCI_NONE, sizeof(msgdesc.CorrelId));
		memcpy(msgdesc.GroupId, MQGI_NONE, sizeof(msgdesc.GroupId));

		/* check if a msg id was specified */
		if (1 == parms.msgidSet)
		{
			memcpy(msgdesc.MsgId, parms.msgid, MQ_MSG_ID_LENGTH);
			mqgmo.MatchOptions |= MQMO_MATCH_MSG_ID;
 		}

		/* check if a correl id was specified */
		if (1 == parms.correlidSet)
		{
			memcpy(msgdesc.CorrelId, parms.correlid, MQ_CORREL_ID_LENGTH);
			mqgmo.MatchOptions |= MQMO_MATCH_CORREL_ID;
		}

		/* check if a group id was specified */
		if (1 == parms.groupidSet)
		{
			memcpy(msgdesc.GroupId, parms.groupid, MQ_GROUP_ID_LENGTH);
			mqgmo.MatchOptions |= MQMO_MATCH_GROUP_ID;
		}

		/* perform the MQGET */
		MQGET(qm, q, &msgdesc, &mqgmo, parms.maxMsgLen, msgdata, &datalen, &compcode, &reason);

		/* check if we got to the end of the queue */
		/* check for errors, except for no more messages in queue */
		if (reason != 2033)
		{
			checkerror("MQGET", compcode, reason, parms.qname);
		}

		if (compcode == MQCC_OK)
		{
			/* count the total number of messages read */
			msgcount++;

			/* calculate the total bytes in the message */
			totalbytes += datalen;

			/* check if a delimiter should be added to the file */
			if ((0 == firstTime) || (1 == parms.indivFiles))
			{
				firstTime = 1;
			}
			else
			{
				/* insert a delimiter string */
				fwrite(parms.delimiter, parms.delimiterLen, 1, outFile);
			}


			/* do we need to check for an RFH at the front of the data? */
			rfhlength = 0;
			if (RFHSTRIP == parms.striprfh)
			{
				/* check the message format for an RFH */
				if ((memcmp(msgdesc.Format, MQFMT_RF_HEADER, sizeof(MQFMT_RF_HEADER) - 1) == 0) ||
					(memcmp(msgdesc.Format, MQFMT_RF_HEADER_2, sizeof(MQFMT_RF_HEADER_2) - 1) == 0))
				{
					/* get the length of the RFH and update the MQMD format, encoding and code page */
					rfhlength = checkRFH(msgdata, datalen, &msgdesc, &parms);
				}
			}

			/* should we include the MQMD in the file data? */
			if (1 == parms.saveMQMD)
			{
				/* write the MQMD to the file */
				fwrite(&msgdesc, sizeof(msgdesc), 1, outFile);
			}

			/* append the data to the file */
			fwrite(msgdata + rfhlength, datalen - rfhlength, 1, outFile);

			/* check if individual files are to be used for each message */
			if (1 == parms.indivFiles)
			{
				/* using one file per message */
				if (outFile != NULL)
				{
					/* close the current file */
					fclose(outFile);
					outFile = NULL;
				}

				/* increment the file counter */
				fileCount++;

				/* build the next file name to use */
				createNextFileName(parms.outputFilename, newFileName, fileCount);

				/* try to open the new output file */
				outFile = fopen(newFileName, "wb");

				/* check if the file open failed */
				if (NULL == outFile)
				{
					/* tell what happened */
					printf("***** Open failed for file %s\n", newFileName);

					/* break out of the loop */
					compcode = MQCC_FAILED;
				}
			}
		} 
		else if ((compcode == MQCC_WARNING) && (2080 == reason))
		{
			/* provide some additional details about the truncation */
			printf("***** Input buffer too short maxMsgLen=%d but messageLength=%d\n", parms.maxMsgLen, datalen);
		}
	}

	/* close the file */
	fclose(outFile);

	/* files are created before reading messages  */
	/* delete the last file since it was not used */
	if (1 == parms.indivFiles)
	{
		/* delete the last file that was created */
		remove(newFileName);
	}

	/* close the input queue */
	printf("closing the input queue\n");
	MQCLOSE(qm, &q, MQCO_NONE, &compcode, &reason);

	checkerror("MQCLOSE", compcode, reason, parms.qname);

	/* Disconnect from the queue manager */
	printf("disconnecting from the queue manager\n");
	MQDISC(&qm, &compcode, &reason);

	checkerror("MQDISC", compcode, reason, parms.qmname);

	/* dump out the total message count */
	printf("Total messages %d\n", msgcount);

	/* were inidividual files used for each message? */
	if (1 == parms.indivFiles)
	{
		/* display the number of files used */
		printf("Total files written %d\n", fileCount);
	}

	/* give the total and average message size */
	if (msgcount > 0)
	{
		avgbytes = totalbytes / msgcount;
		printf("total bytes in all messages %d\n", totalbytes);
		printf("average message size %d\n", avgbytes);
	}

	/******************************************************************/
	/*                                                                */
	/* END OF PROGRAM                                                 */
	/*                                                                */
	/******************************************************************/

	printf("mqcapture program ended\n");
	return 0;
}
