/*	$Id$
 *	Author:	Roger A. Cole
 *	Date:	12-04-90
 *
 *	Experimental Physics and Industrial Control System (EPICS)
 *
 *	Copyright 1991-92, the Regents of the University of California,
 *	and the University of Chicago Board of Governors.
 *
 *	This software was produced under  U.S. Government contracts:
 *	(W-7405-ENG-36) at the Los Alamos National Laboratory,
 *	and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *	Initial development by:
 *		The Controls and Automation Group (AT-8)
 *		Ground Test Accelerator
 *		Accelerator Technology Division
 *		Los Alamos National Laboratory
 *
 *	Co-developed with
 *		The Controls and Computing Group
 *		Accelerator Systems Division
 *		Advanced Photon Source
 *		Argonne National Laboratory
 *
 * Modification Log:
 * -----------------
 *  .00 12-04-90 rac	initial version
 *  .01 06-18-91 rac	installed in SCCS
 *  .02 06-19-91 rac	replace <fields.h> with <alarm.h>
 *  .03 08-14-91 rac	
 *  .04 02-27-92 rac	do ts rounding here instead of sydSubr.c
 *  .05 08-18-92 rac	add SYD_FC_STOP function code
 *  .06 09-09-92 rac	allow reading text values such as `no data'; look at
 *			the _end_ of the file to do name searches; handle
 *			channels which are missing and/or disconnected;
 *			discontinue use of special malloc routines
 *
 * make options
 *	-DvxWorks	makes a version for VxWorks
 *	-DNDEBUG	don't compile assert() checking
 *      -DDEBUG         compile various debug code
 */
/*+/mod***********************************************************************
* TITLE	sydSubrSSF.c - acquire synchronous samples from AR `sample set' files
*
* DESCRIPTION
*	
*-***************************************************************************/
#include <genDefs.h>
#define SYD_PRIVATE
#include <sydDefs.h>
#include <alarm.h>
#ifndef INC_tsDefs_h
#   include <tsDefs.h>
#endif
#ifndef INCLcadefh
#   include <cadef.h>
#endif

#ifdef vxWorks
#   include <vxWorks.h>
#   include <ioLib.h>		/* for O_RDWR and O_RDONLY definitions */
#   include <stdioLib.h>
#   include <strLib.h>
#else
#   include <stdio.h>
#   include <sys/file.h>	/* for O_RDWR and O_RDONLY definitions */
#   include <string.h>
#   include <strings.h>
#endif

long sydSSFFunc();
static long sydSSFFuncGetGR();
static long sydSSFFuncReadData();
static long sydSSFFuncSeekSample();
static long sydSSFFuncSeekStamp();

/*+/macro*********************************************************************
* NAME	posXxx - text file search and positioning routines
*
* DESCRIPTION
*	These routines allow setting the position for text files.  The
*	file may be positioned at its beginning or its end.  It is
*	supported to search, either forward or backward, for a string,
*	leaving the file positioned at the beginning of the string.
*
*	long	pos;
*	FILE	*pStream;
*
*	pos = posBOF(pStream);
*	pos = posEOF(pStream);
*	pos = posKeyFwd("key", pStream);
*	pos = posKeyRev("key", pStream, pos);
*	pos = posPos(pStream, pos);
*
* RETURNS
*	position, or
*	<0 if an error occurs
*
* BUGS
* o	the use of fseek and ftell doesn't match the Standard C specification
*	and may be non-portable.  They have been verified to function
*	properly under SunOS 4.1 and VxWorks 4.0.2.
*
*-*/
static long posBOF();
static long posEOF();
static long posKeyFwd();
static long posKeyRev();
static long posPos();

/*+/internal******************************************************************
* NAME	posBOF - position file at the beginning
*
*-*/
static long
posBOF(pStream)
FILE	*pStream;
{
    if (fseek(pStream, 0L, 0) < 0) {
	perror("posBOF: fseek error");
	return -1;
    }
    return ftell(pStream);
}
/*+/internal******************************************************************
* NAME	posEOF - position file at the end
*
*-*/
static long
posEOF(pStream)
FILE	*pStream;
{
    if (fseek(pStream, 0L, 2) < 0) {
	perror("posEOF: fseek error");
	return -1;
    }
    return ftell(pStream);
}
/*+/internal******************************************************************
* NAME	posKeyFwd - search forward for a string
*
* RETURNS
*	position of key (its first character), or
*	-1 if search fails
*
*-*/
static long
posKeyFwd(key, pStream)
char	*key;
FILE	*pStream;
{
    int		i, lastChar;
    int		c;
    long	pos;

    lastChar = strlen(key) - 1;
    i = 0;

    if ((pos = ftell(pStream)) < 0) {
	if (!feof(pStream))
	    perror("posKeyFwd: ftell error");
	return -1;
    }

    while (1) {
	if ((c = fgetc(pStream)) == EOF)
	    return -1;
	if ((char)c == key[i]) {
	    if (i == lastChar)
		break;
	    i++;
	}
	else {
	    if (i == 0) {
		if ((pos = ftell(pStream)) < 0) {
		    if (!feof(pStream))
			perror("posKeyFwd: ftell error");
		    return -1;
		}
	    }
	    else if (i == 1) {
		pos++;
		if ((char)c != key[0])
		    i = 0;
	    }
	    else {
		pos++;
		if (fseek(pStream, pos, 0) != 0) {
		    if (!feof(pStream))
			perror("posKeyFwd: fseek error");
		    return -1;
		}
		i = 0;
	    }
	}
    }
    if (fseek(pStream, pos, 0) != 0) {
	if (!feof(pStream))
	    perror("posKeyFwd: fseek error");
	return -1;
    }
    return pos;
}
/*+/internal******************************************************************
* NAME	posKeyRev - search backward for a string
*
* RETURNS
*	position of key (its first character), or
*	-1 if search fails
*
*-*/
static long
posKeyRev(key, pStream, lastPos)
char	*key;
FILE	*pStream;
long	lastPos;	/* I position to start search; -1 for EOF */
{
    int		i, lastChar;
    int		c;
    long	pos;

    lastChar = i = strlen(key) - 1;

    if (lastPos >= 0) {
	if (fseek(pStream, lastPos, 0) != 0) {
	    if (!feof(pStream))
		perror("posKeyRev: fseek error");
	    return -1;
	}
    }
    else {
	if (fseek(pStream, 0L, 2) != 0) {
	    if (!feof(pStream))
		perror("posKeyRev: fseek error");
	    return -1;
	}
    }
    while (1) {
	if (ftell(pStream) <= 0)
	    return -1;
	if (fseek(pStream, -1L, 1) != 0) {
	    if (!feof(pStream))
		perror("posKeyRev: fseek error");
	    return -1;
	}
	if ((c = fgetc(pStream)) == EOF)
	    return -1;
	if (fseek(pStream, -1L, 1) != 0) {
	    if (!feof(pStream))
		perror("posKeyRev: fseek error");
	    return -1;
	}
	if ((char)c == key[i]) {
	    if (i == 0)
		break;
	    i--;
	}
	else
	    i = lastChar;
    }
    if ((pos = ftell(pStream)) < 0)
	perror("posKeyRev: ftell error");
    return pos;
}
/*+/internal******************************************************************
* NAME	posPos - set file to desired position
*
*-*/
static long
posPos(pStream, pos)
FILE	*pStream;
long	pos;
{
    if (fseek(pStream, pos, 0) < 0) {
	perror("posPos: fseek error");
	return -1;
    }
    return ftell(pStream);
}

/*+/subr**********************************************************************
* NAME	sydOpenSSF
*
*-*/
long
sydOpenSSF(ppSspec, filePath)
SYD_SPEC **ppSspec;	/* O pointer to synchronous set spec pointer */
char	*filePath;	/* I path name for `sample set' archive file */
{
    long	stat;

    assert(ppSspec != NULL);

    if ((*ppSspec = (SYD_SPEC *)malloc(sizeof(SYD_SPEC))) == NULL)
	return S_syd_noMem;
    (*ppSspec)->pFunc = sydSSFFunc;
    (*ppSspec)->type = SYD_TY_SSF;
    if ((stat = sydSSFFunc(*ppSspec, NULL,SYD_FC_INIT,filePath)) != S_syd_OK){
	free((char *)*ppSspec);
	*ppSspec = NULL;
	return stat;
    }
    (*ppSspec)->nInBufs = 2;
    return sydOpen(ppSspec);
}
/*+/subr**********************************************************************
* NAME	sydSSFFunc - handle "sample set" data file interactions
*
* DESCRIPTION
*
* sydSSFFunc(pSspec, NULL, SYD_FC_INIT, filePath)  open "sample set" file
* sydSSFFunc(pSspec, pSChan, SYD_FC_OPEN, NULL)  chanName already in pSChan
* sydSSFFunc(pSspec, pSChan, SYD_FC_READ, NULL)
* sydSSFFunc(pSspec, pSChan, SYD_FC_POSITION, &stamp)
* sydSSFFunc(pSspec, pSChan, SYD_FC_CLOSE, NULL)
* sydSSFFunc(pSspec, NULL, SYD_FC_FILEINFO, outStream)
* sydSSFFunc(pSspec, NULL, SYD_FC_WRAPUP, NULL)	close "sample set" file
*
* RETURNS
*	S_syd_OK, or
*	S_syd_EOF, or
*	other code indicating error
*
* BUGS
* o	doesn't detect or report EOF
* o	doesn't do anything (or even detect) overwriting un-sampled
*	buffers (for SYD_FC_READ)
* o	needs a "get graphics information" function and function code
* o	if a channel wasn't connected when the last snapshot in the file
*	was written, its elCount is assumed to be 1
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
long
sydSSFFunc(pSspec, pStruct, funcCode, pArg)
SYD_SPEC *pSspec;	/* IO pointer to synchronous set spec */
void	*pStruct;	/* IO pointer to data struct used by funcCode */
enum sydFuncCode funcCode;/* I function code */
void	*pArg;		/* I pointer to arg, as required by funcCode */
{
    SYD_CHAN	*pSChan;	/* pointer to syncSet channel descriptor */
    FILE	*ssFile;	/* file pointer for sample set data file */
    long	retStat=S_syd_OK;
    long	stat;
    int		i;
    long	pos;
    char	record[120];
    char	recDelim;
    char	*pRecord;
    char	*pField;
    char	delim;
    char	chanName[db_name_dim+2];
    chtype	type;
    int		elCount;

    pSChan = (SYD_CHAN *)pStruct;
    if (pSspec != NULL)
	ssFile = (FILE *)pSspec->pHandle;

    if (funcCode ==					SYD_FC_INIT) {
	if ((ssFile = fopen((char *)pArg, "r")) == NULL) {
	    (void)printf("couldn't open %s\n", (char *)pArg);
	    perror(NULL);
	    retStat = S_syd_ERROR;
	}
	pSspec->pHandle = (void *)ssFile;
    }
    else if (funcCode ==				SYD_FC_OPEN) {
	if ((pos = posKeyRev("%endHeader%", ssFile, -1L)) < 0) {
	    (void)printf("couldn't find %%endHeader%%\n");
	    (void)posBOF(ssFile);
	    return S_syd_chanNotFound;
	}
	strcpy(chanName, "\n");
	strcat(chanName, pSChan->name);
	strcat(chanName, " ");
	if ((pos = posKeyFwd(chanName, ssFile)) < 0)
		return S_syd_chanNotFound;
	assert(sizeof(record) == 120);
	fgetc(ssFile);		/* skip over the \n */
	if (fgets(record, 120, ssFile) == NULL)
	    assertAlways(0);
	pRecord = record;
	recDelim = record[strlen(record)-1];
	if (nextChanNameField(&pRecord, &pField, &delim) <= 1)
	    assertAlways(0);
	if (strcmp(pField, pSChan->name) != 0)
	    assertAlways(0);
	pSChan->pHandle = NULL;
	if (nextAlphField(&pRecord, &pField, &delim) <= 1)
	    assertAlways(0);
	dbf_text_to_type(pField, type);
	if (type >= 0) {
	    pSChan->dbfType = type;
	    pSChan->dbrType = dbf_type_to_DBR_TIME(type);
	    pSChan->dbrGrType = dbf_type_to_DBR_GR(type);
	    stat = sscanf(pRecord, "%*d %*d %d", &elCount);
	    assert(stat == 1);
	    pSChan->elCount = elCount;
	}
	else {
	    pSChan->dbfType = TYPENOTCONN;
	    pSChan->dbrType = TYPENOTCONN;
	    pSChan->dbrGrType = TYPENOTCONN;
	    pSChan->elCount = 1;
	}
	sydSSFFuncGetGR(pSspec, pSChan);
	pSChan->sync = SYD_SY_NONF;	/* force channel to be sync */
	(void)posBOF(ssFile);
    }
    else if (funcCode ==				SYD_FC_READ) {
	TS_STAMP timeStamp;
	enum sydBStatus bufStat, newStat;
	int	bufNum, oldBufNum, first=1;
	char	typeText[80];

/*-----------------------------------------------------------------------------
*    find the next line with SAMPLE: and get the time stamp.  When done:
*    o  bufStat will be one of:
*		SYD_B_RESTART	this sample starts a new "run" of data
*		SYD_B_FULL	this is an ordinary sample
*		SYD_B_EOF	end of file
*		SYD_B_MISSING	no value for this channel i nthis sample
*    o  the file will be positioned following the %endHeader% line
*    o  if time stamp rounding has been requested, it will be done
*----------------------------------------------------------------------------*/
	while (1) {
	    newStat = SYD_B_EMPTY;
	    bufStat = sydSSFFuncSeekSample(ssFile, &timeStamp);
	    if (pSspec->roundNsec > 0)
		sydTsRound(&timeStamp, pSspec->roundNsec);

/*-----------------------------------------------------------------------------
*    now, read the actual data.  This is done by processing all the data
*    lines in the file, storing those which match a channel in the synchronous
*    set.  (This is somewhat contrary to the "advertising" for this function
*    code, which implies that only a single channel is read.)  When done:
*    o  all channels in the set will have an appropriate buffer status
*    o  all channels will have the same time stamp
*    o  all channels with data will have the data in the buffer
*    o  all channels will have .lastInBuf updated
*----------------------------------------------------------------------------*/
	    pSChan = pSspec->pChanHead;
	    if (first) {
		oldBufNum = pSChan->lastInBuf;
		bufNum = NEXT_INBUF(pSChan, oldBufNum);
		first = 0;
	    }
	    while (pSChan != NULL) {
		if (bufStat != SYD_B_EOF)
		    pSChan->inStatus[bufNum] = SYD_B_MISSING;
		else
		    pSChan->inStatus[bufNum] = bufStat;
		pSChan->pInBuf[bufNum]->tfltval.stamp = timeStamp;
		pSChan->lastInBuf = bufNum;
		if (pSChan->firstInBuf < 0)
		    pSChan->firstInBuf = bufNum;
		pSChan = pSChan->pNext;
	    }
	    if (bufStat == SYD_B_EOF)
		return S_syd_EOF;
	    while (1) {
		stat = fscanf(ssFile, "%s", chanName);
		assert(stat == 1);
		if ((pSChan = sydChanFind(pSspec, chanName)) != NULL) {
		    (void)fscanf(ssFile, "%s", typeText);
		    dbf_text_to_type(typeText, type);
		    if (pSChan->dbfType == TYPENOTCONN && type >= 0) {
			pSChan->dbfType = type;
			pSChan->dbrType = dbf_type_to_DBR_TIME(type);
			pSChan->dbrGrType = dbf_type_to_DBR_GR(type);
			sydSSFFuncGetGR(pSspec, pSChan);
		    }
		    stat = sydSSFFuncReadData(pSChan, ssFile, 
					bufStat, bufNum, oldBufNum, type);
		    if (pSChan->inStatus[bufNum] != SYD_B_MISSING &&
					newStat == SYD_B_EMPTY) {
			newStat = pSChan->inStatus[bufNum];
		    }
		}
		else if (strcmp(chanName, "%endData%") == 0)
		    break;
		else {
		    while ((delim = fgetc(ssFile)) != EOF && delim != '\n')
			;	/* keep skipping until end of line */
		}
	    }
	    if (newStat != SYD_B_EMPTY)
		break;
	}
    }
    else if (funcCode ==				SYD_FC_STOP) {
    }
    else if (funcCode ==				SYD_FC_POSITION) {
	TS_STAMP *pStamp;
	TS_STAMP stamp, earlyStamp;
	long	earlyPos;

	pStamp = (TS_STAMP *)pArg;
	if (pStamp == NULL) {
	    (void)posBOF(ssFile);
	    return retStat;
	}
/*-----------------------------------------------------------------------------
*    find 2 adjacent samples to make it easy to determine which way to
*    search.  There are several cases:
*    1. the file is at least 2 away from EOF and both stamps can be found
*	forward
*    2.	the file is 1 away from EOF--1 stamp backward and 1 forward
*    3.	the file contains no stamps
*    4.	the file is at EOF and both stamps must be found backward
*    5. the file only contains one stamp
*
*    Cases 2 and 4 are treated by rewinding the file to avoid any backward
*    searching.  This transforms those cases into one of the other cases.
*
*    When this section is done, `earlyStamp' and `stamp' will contain two
*    adjacent stamps.  If they are equal, then case 4 exists.  The file
*    will be positioned following `stamp'.
*----------------------------------------------------------------------------*/
	earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L);
	pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L);
	if (earlyPos < 0 || pos < 0) {
	    (void)posBOF(ssFile);
	    earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L);
	    if ((pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L)) < 0) {
		stamp = earlyStamp;
		pos = earlyPos;
	    }
	}
	if (earlyPos < 0)
	    return S_syd_ERROR;
	if (TsCmpStampsLT(pStamp, &earlyStamp)) {
	    (void)posBOF(ssFile);
	    earlyPos = sydSSFFuncSeekStamp(ssFile, &earlyStamp, 0, 0L);
	    if ((pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, 0L)) < 0) {
		stamp = earlyStamp;
		pos = earlyPos;
	    }
	}
	if (earlyPos < 0)
	    return S_syd_ERROR;
/*-----------------------------------------------------------------------------
*    now, find the record with the proper time stamp, going forward
*----------------------------------------------------------------------------*/
	while (1) {
	    if (TsCmpStampsLE(pStamp, &earlyStamp))
		break;
	    else if (TsCmpStampsLE(pStamp, &stamp) &&
	    				TsCmpStampsGE(pStamp, &earlyStamp)) {
		if (TsCmpStampsEQ(pStamp, &earlyStamp)) {
		    stamp = earlyStamp;
		    pos = earlyPos;
		}
		break;
	    }
	    else if (TsCmpStampsEQ(&earlyStamp, &stamp))
		break;
	    else if (TsCmpStampsGT(pStamp, &stamp)) {
		earlyStamp = stamp;
		earlyPos = pos;
		pos = sydSSFFuncSeekStamp(ssFile, &stamp, 0, earlyPos);
		if (pos < 0) {
		    stamp = earlyStamp;
		    pos = earlyPos;
		}
	    }
	}
/*-----------------------------------------------------------------------------
*    finally, position the file at the proper record
*----------------------------------------------------------------------------*/
	if (TsCmpStampsGT(pStamp, &stamp))
	    return S_syd_EOF;
	if (TsCmpStampsLE(pStamp, &earlyStamp))
	    (void)posPos(ssFile, earlyPos);
	else
	    (void)posPos(ssFile, pos);
    }
    else if (funcCode ==				SYD_FC_CLOSE) {
	;	/* no action to close a channel in sample set files */
    }
    else if (funcCode ==				SYD_FC_FILEINFO) {
	TS_STAMP    oldestStamp;
	char        oldestStampText[28];
	TS_STAMP    newestStamp;
	char        newestStampText[28];
	int		j;

	if (posBOF(ssFile) < 0) {
	    (void)fprintf((FILE *)pArg, "error positioning file\n");
	    return S_syd_ERROR;
	}
	if (sydSSFFuncSeekStamp(ssFile, &oldestStamp, 0, 0L) < 0) {
	    (void)fprintf((FILE *)pArg, "error finding oldest time stamp\n");
	    return S_syd_ERROR;
	}
	if (sydSSFFuncSeekStamp(ssFile, &newestStamp, 1, -1L) < 0) {
	    (void)fprintf((FILE *)pArg, "error finding newest time stamp\n");
	    return S_syd_ERROR;
	}
	(void)fprintf((FILE *)pArg, "data from %s through %s\n",
	       tsStampToText(&oldestStamp, TS_TEXT_MMDDYY, oldestStampText),
               tsStampToText(&newestStamp, TS_TEXT_MMDDYY, newestStampText) );
	if (posKeyRev("%endHeader%", ssFile, -1L) < 0) {
	    (void)fprintf((FILE *)pArg,
				"error locating %%endHeader%% in file\n");
	    return S_syd_ERROR;
	}
	(void)fgets(record, 120, ssFile);	/* skip over endHeader */
	while (1) {
	    if (fgets(record, 120, ssFile) == NULL) {
		(void)fprintf((FILE *)pArg, "error reading channel name\n");
		return S_syd_ERROR;
	    }
	    pRecord = record;
	    recDelim = record[strlen(record)-1];
	    if (nextChanNameField(&pRecord, &pField, &delim) <= 1) {
		(void)fprintf((FILE *)pArg, "error reading channel type\n");
		return S_syd_ERROR;
	    }
	    if (strcmp(pField, "%endData%") == 0)
		break;
	    (void)strcpy(chanName, pField);
	    if (nextAlphField(&pRecord, &pField, &delim) <= 1) {
		(void)fprintf((FILE *)pArg, "error reading channel type\n");
		return S_syd_ERROR;
	    }
	    if (sscanf(pRecord, "%*d %*d %d", &elCount) != 1) {
		(void)fprintf((FILE *)pArg, "error reading element count\n");
		return S_syd_ERROR;
	    }
	    j = strlen(chanName);
	    if (j > 4) {
		if (strncmp(&chanName[j-4], ".VAL", 4) == 0)
		    chanName[j-4] = '\0';
	    }
	
	    (void)fprintf((FILE *)pArg, "%s %d %s\n",
						chanName, elCount, pField);
	    if (recDelim != '\n') {
		while ((delim = fgetc(ssFile)) != EOF && delim != '\n')
		    ;	/* keep skipping until end of line */
	    }
	}
	if (posBOF(ssFile) < 0) {
	    (void)fprintf((FILE *)pArg, "error positioning file\n");
	    return S_syd_ERROR;
	}
    }
    else if (funcCode ==				SYD_FC_WRAPUP) {
	if (ssFile != NULL)
	    (void)close(ssFile);
	pSspec->pHandle = NULL;
    }

    return retStat;
}

/*+/internal******************************************************************
* NAME	sydSSFFuncReadData
*
* DESCRIPTION
*	Read data from the file into the sync set.  An entire snapshot is
*	read.  For each channel in the snapshot, the data is stored in the
*	sync set only if the channel is in the set.
*
* BUGS
* o	if a channel has a smaller elCount in the file than in the sync set,
*	then the `extra' values in the sync set will be garbage
*
*-*/
static long
sydSSFFuncReadData(pSChan, ssFile, bufStat, bufNum, oldBufNum, type)
SYD_CHAN *pSChan;	/* pointer to syncSet channel descriptor */
FILE	*ssFile;
int	bufStat, bufNum, oldBufNum;
chtype	type;
{
    long	stat;
    int		alStat, alSev, elCount;
    int		i;

/*-----------------------------------------------------------------------------
*    first, get and store the alarm status and severity .
*    (This is done using the "float" form of the buffer, which is
*    possible because for these items all types of buffers look the same.)
*----------------------------------------------------------------------------*/
    stat = fscanf(ssFile, "%d%d%d", &alSev, &alStat, &elCount);
    assert(stat == 3);
    pSChan->pInBuf[bufNum]->tfltval.status = alStat;
    pSChan->pInBuf[bufNum]->tfltval.severity = alSev;
/*-----------------------------------------------------------------------------
*    now get the actual data
*----------------------------------------------------------------------------*/
    if (pSChan->dbrType == TYPENOTCONN || type == TYPENOTCONN) {
	stat = fscanf(ssFile, "%*s");
	bufStat = SYD_B_MISSING;
    }
    else if (pSChan->dbrType == DBR_TIME_FLOAT) {
	float	*pFl, val;
	pFl = &pSChan->pInBuf[bufNum]->tfltval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%f", &val);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    else if (i < (int) pSChan->elCount)
		*pFl++ = val;
	}
    }
    else if (pSChan->dbrType == DBR_TIME_SHORT) {
	short	*pSh, val;
	pSh = &pSChan->pInBuf[bufNum]->tshrtval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%hd", &val);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    else if (i < (int) pSChan->elCount)
		*pSh++ = val;
	}
    }
    else if (pSChan->dbrType == DBR_TIME_DOUBLE) {
	double	*pDbl, val;
	pDbl = &pSChan->pInBuf[bufNum]->tdblval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%lf", &val);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    else if (i < (int) pSChan->elCount)
		*pDbl++ = val;
	}
    }
    else if (pSChan->dbrType == DBR_TIME_LONG) {
	long	*pL, val;
	pL = &pSChan->pInBuf[bufNum]->tlngval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%ld", &val);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    else if (i < (int) pSChan->elCount)
		*pL++ = val;
	}
    }
    else if (pSChan->dbrType == DBR_TIME_STRING) {
	char	*pC, val[db_strval_dim];
	int	nChar;
	pC = pSChan->pInBuf[bufNum]->tstrval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%d", &nChar);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    assert(nChar > 0 && nChar < db_strval_dim);
	    if (fgets(val, nChar, ssFile) == NULL)
		assertAlways(0);
	    if (i < (int) pSChan->elCount) {
		strcpy(pC, val);
		pC += db_strval_dim;
	    }
	}
    }
    else if (pSChan->dbrType == DBR_TIME_CHAR) {
	unsigned char	*pC, val;
	pC = &pSChan->pInBuf[bufNum]->tchrval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%c", &val);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    else if (i < (int) pSChan->elCount)
		*pC++ = val;
	}
    }
    else if (pSChan->dbrType == DBR_TIME_ENUM) {
	unsigned short	*pSh;
	char	state[80];
	int	iState;
	pSh = &pSChan->pInBuf[bufNum]->tenmval.value;
	for (i=0; i<elCount; i++) {
	    stat = fscanf(ssFile, "%s", state);
	    if (stat != 1) {
		bufStat = SYD_B_MISSING;
		break;
	    }
	    assert(strlen(state) < db_state_text_dim);
	    iState = 0;
	    while (1) {
		if (iState >= pSChan->grBuf.genmval.no_str) {
		    pSChan->grBuf.genmval.no_str++;
		    (void)strcpy(pSChan->grBuf.genmval.strs[iState], state);
		    break;
		}
		else if (strcmp(pSChan->grBuf.genmval.strs[iState],state) == 0)
		    break;
		iState++;
		assert(iState < db_state_dim);
	    }
	    if (i < (int) pSChan->elCount)
		*pSh++ = iState;
	}
    }
/*-----------------------------------------------------------------------------
*    set the buffer status.  If the previous record had a status of MISSING,
*    then the buffer status will be SYD_B_RESTART; otherwise, the caller's
*    status will be used.  (Unless, of course, this routine discovered that
*    the status needs to be something else.)
*----------------------------------------------------------------------------*/
    if (bufStat == SYD_B_MISSING)
	pSChan->inStatus[bufNum] = bufStat;
    else if (oldBufNum >= 0 && pSChan->inStatus[oldBufNum] == SYD_B_MISSING)
	pSChan->inStatus[bufNum] = SYD_B_RESTART;
    else
	pSChan->inStatus[bufNum] = bufStat;

    return S_syd_OK;
}

/*+/internal******************************************************************
* NAME	sydSSFFuncGetGR - simulate getting graphics information
*
*-*/
static long
sydSSFFuncGetGR(pSspec, pSChan)
SYD_SPEC *pSspec;	/* pointer to syncSet specification */
SYD_CHAN *pSChan;	/* pointer to syncSet channel descriptor */
{
    int		i;

    if (pSChan->dbrType == DBR_TIME_FLOAT) {
#define FLT_DEST pSChan->grBuf.gfltval
	FLT_DEST.status = NO_ALARM;
	FLT_DEST.severity = NO_ALARM;
#if 1
	FLT_DEST.precision = 3;
	(void)strcpy(FLT_DEST.units, " ");
	FLT_DEST.upper_disp_limit = 0.;
	FLT_DEST.lower_disp_limit = 0.;
	FLT_DEST.upper_alarm_limit = 0.;
	FLT_DEST.lower_alarm_limit = 0.;
	FLT_DEST.upper_warning_limit = 0.;
	FLT_DEST.lower_warning_limit = 0.;
#else
#define FLT_SRC ArCDChanHdr(pChanDesc).graphics.floatGr
	(void)strcpy(FLT_DEST.units, FLT_SRC.units);
	FLT_DEST.upper_disp_limit = FLT_SRC.upDispLim;
	FLT_DEST.lower_disp_limit = FLT_SRC.lowDispLim;
	FLT_DEST.upper_alarm_limit = FLT_SRC.upAlmLim;
	FLT_DEST.lower_alarm_limit = FLT_SRC.lowAlmLim;
	FLT_DEST.upper_warning_limit = FLT_SRC.upWarnLim;
	FLT_DEST.lower_warning_limit = FLT_SRC.lowWarnLim;
#endif
    }
    else if (pSChan->dbrType == DBR_TIME_SHORT) {
#define SHRT_DEST pSChan->grBuf.gshrtval
	SHRT_DEST.status = NO_ALARM;
	SHRT_DEST.severity = NO_ALARM;
#if 1
	(void)strcpy(SHRT_DEST.units, " ");
	SHRT_DEST.upper_disp_limit = 0;
	SHRT_DEST.lower_disp_limit = 0;
	SHRT_DEST.upper_alarm_limit = 0;
	SHRT_DEST.lower_alarm_limit = 0;
	SHRT_DEST.upper_warning_limit = 0;
	SHRT_DEST.lower_warning_limit = 0;
#else
#define SHRT_SRC ArCDChanHdr(pChanDesc).graphics.shortGr
	(void)strcpy(SHRT_DEST.units, SHRT_SRC.units);
	SHRT_DEST.upper_disp_limit = SHRT_SRC.upDispLim;
	SHRT_DEST.lower_disp_limit = SHRT_SRC.lowDispLim;
	SHRT_DEST.upper_alarm_limit = SHRT_SRC.upAlmLim;
	SHRT_DEST.lower_alarm_limit = SHRT_SRC.lowAlmLim;
	SHRT_DEST.upper_warning_limit = SHRT_SRC.upWarnLim;
	SHRT_DEST.lower_warning_limit = SHRT_SRC.lowWarnLim;
#endif
    }
    else if (pSChan->dbrType == DBR_TIME_DOUBLE) {
#define DBL_DEST pSChan->grBuf.gdblval
	DBL_DEST.status = NO_ALARM;
	DBL_DEST.severity = NO_ALARM;
#if 1
	DBL_DEST.precision = 3;
	(void)strcpy(DBL_DEST.units, " ");
	DBL_DEST.upper_disp_limit = 0.;
	DBL_DEST.lower_disp_limit = 0.;
	DBL_DEST.upper_alarm_limit = 0.;
	DBL_DEST.lower_alarm_limit = 0.;
	DBL_DEST.upper_warning_limit = 0.;
	DBL_DEST.lower_warning_limit = 0.;
#else
#define DBL_SRC ArCDChanHdr(pChanDesc).graphics.doubleGr
	(void)strcpy(DBL_DEST.units, DBL_SRC.units);
	DBL_DEST.upper_disp_limit = DBL_SRC.upDispLim;
	DBL_DEST.lower_disp_limit = DBL_SRC.lowDispLim;
	DBL_DEST.upper_alarm_limit = DBL_SRC.upAlmLim;
	DBL_DEST.lower_alarm_limit = DBL_SRC.lowAlmLim;
	DBL_DEST.upper_warning_limit = DBL_SRC.upWarnLim;
	DBL_DEST.lower_warning_limit = DBL_SRC.lowWarnLim;
#endif
    }
    else if (pSChan->dbrType == DBR_TIME_LONG) {
#define LNG_DEST pSChan->grBuf.glngval
	LNG_DEST.status = NO_ALARM;
	LNG_DEST.severity = NO_ALARM;
#if 1
	(void)strcpy(LNG_DEST.units, " ");
	LNG_DEST.upper_disp_limit = 0;
	LNG_DEST.lower_disp_limit = 0;
	LNG_DEST.upper_alarm_limit = 0;
	LNG_DEST.lower_alarm_limit = 0;
	LNG_DEST.upper_warning_limit = 0;
	LNG_DEST.lower_warning_limit = 0;
#else
#define LNG_SRC ArCDChanHdr(pChanDesc).graphics.longGr
	(void)strcpy(LNG_DEST.units, LNG_SRC.units);
	LNG_DEST.upper_disp_limit = LNG_SRC.upDispLim;
	LNG_DEST.lower_disp_limit = LNG_SRC.lowDispLim;
	LNG_DEST.upper_alarm_limit = LNG_SRC.upAlmLim;
	LNG_DEST.lower_alarm_limit = LNG_SRC.lowAlmLim;
	LNG_DEST.upper_warning_limit = LNG_SRC.upWarnLim;
	LNG_DEST.lower_warning_limit = LNG_SRC.lowWarnLim;
#endif
    }
    else if (pSChan->dbrType == DBR_TIME_STRING) {
#define STR_DEST pSChan->grBuf.gstrval
	STR_DEST.status = NO_ALARM;
	STR_DEST.severity = NO_ALARM;
    }
    else if (pSChan->dbrType == DBR_TIME_ENUM) {
#define ENM_DEST pSChan->grBuf.genmval
	ENM_DEST.status = NO_ALARM;
	ENM_DEST.severity = NO_ALARM;
#if 1
	ENM_DEST.no_str = 0;
#else
#define ENM_SRC ArCDChanHdr(pChanDesc).graphics.enumGr
	ENM_DEST.no_str = ArCFChanNStates(pChanDesc);
	for (i=0; i<ENM_DEST.no_str; i++)
	    (void)strcpy(ENM_DEST.strs[i], ArCFChanStates(pChanDesc)[i]);
#endif
    }
    else if (pSChan->dbrType == DBR_TIME_CHAR) {
#define CHR_DEST pSChan->grBuf.gchrval
	CHR_DEST.status = NO_ALARM;
	CHR_DEST.severity = NO_ALARM;
	(void)strcpy(CHR_DEST.units, " ");
#if 1
	CHR_DEST.upper_disp_limit = 0;
	CHR_DEST.lower_disp_limit = 0;
	CHR_DEST.upper_alarm_limit = 0;
	CHR_DEST.lower_alarm_limit = 0;
	CHR_DEST.upper_warning_limit = 0;
	CHR_DEST.lower_warning_limit = 0;
#else
#define CHR_SRC ArCDChanHdr(pChanDesc).graphics.charGr
	(void)strcpy(CHR_DEST.units, CHR_SRC.units);
	CHR_DEST.upper_disp_limit = CHR_SRC.upDispLim;
	CHR_DEST.lower_disp_limit = CHR_SRC.lowDispLim;
	CHR_DEST.upper_alarm_limit = CHR_SRC.upAlmLim;
	CHR_DEST.lower_alarm_limit = CHR_SRC.lowAlmLim;
	CHR_DEST.upper_warning_limit = CHR_SRC.upWarnLim;
	CHR_DEST.lower_warning_limit = CHR_SRC.lowWarnLim;
#endif
    }
    return S_syd_OK;
}

/*+/internal******************************************************************
* NAME	sydSSFFuncSeekStamp - find a `SAMPLE: at' line
*
* DESCRIPTION
*	Find a `SAMPLE: at' line and get the time stamp from it.
*
*	If the search is forward, the file is left positioned following
*	the stamp.  If backward, the position is at the beginning of the
*	line.
*
* RETURNS
*	position of the line
*
*-*/
static long
sydSSFFuncSeekStamp(ssFile, pStamp, revFlag, pos)
FILE	*ssFile;
TS_STAMP *pStamp;
int	revFlag;	/* I 1 says seek in reverse direction */
long	pos;		/* I position to start search if reverse; -1
				says to start at EOF */
{
    long	bufStat;
    char	record[120], recDelim;
    char	*pRecord, *pField, delim;
    long	stat;

    if (revFlag)
	pos = posKeyRev("SAMPLE: at", ssFile, pos);
    else 
	pos = posKeyFwd("SAMPLE: at", ssFile);
    if (pos < 0)
	return pos;
    assert(sizeof(record) == 120);
    if (fgets(record, 120, ssFile) == NULL)
	return -1;

    pRecord = record;
    recDelim = record[strlen(record)-1];
    if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1)
	assertAlways(0);
    if (strcmp(pField, "SAMPLE:") != 0)
	assertAlways(0);
    if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1)
	assertAlways(0);
    if (strcmp(pField, "at") != 0)
	assertAlways(0);
    stat = tsTextToStamp(pStamp, &pRecord);
    assert(stat == S_ts_OK);
    if (revFlag) {
	if (posPos(ssFile, pos) < 0)
	    assertAlways(0);
    }
    return pos;
}

/*+/internal******************************************************************
* NAME	sydSSFFuncSeekSample - find the next sample
*
* DESCRIPTION
*	Finds the next `SAMPLE: at' line, gets the time stamp for the
*	sample, and gets the `status' for the snapshot.  The file is
*	positioned following the %endHeader%, ready for reading the data.
*
* RETURNS
*	SYD_B_RESTART if `initial' was specified on the `SAMPLE: at' line, or
*	SYD_B_FULL
*
*-*/
static long
sydSSFFuncSeekSample(ssFile, pStamp)
FILE	*ssFile;
TS_STAMP *pStamp;
{
    long	pos;
    long	bufStat;
    char	record[120], recDelim;
    char	*pRecord, *pField, delim;
    long	stat;

    if ((pos = posKeyFwd("SAMPLE: at", ssFile)) < 0)
	return SYD_B_EOF;
    assert(sizeof(record) == 120);
    if (fgets(record, 120, ssFile) == NULL)
	return SYD_B_EOF;

    pRecord = record;
    recDelim = record[strlen(record)-1];
    if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1)
	assertAlways(0);
    if (strcmp(pField, "SAMPLE:") != 0)
	assertAlways(0);
    if (nextNonSpaceField(&pRecord, &pField, &delim) <= 1)
	assertAlways(0);
    if (strcmp(pField, "at") != 0)
	assertAlways(0);
    stat = tsTextToStamp(pStamp, &pRecord);
    assert(stat == S_ts_OK);
    while (*pRecord == '-')
	pRecord++;
    if (strncmp(pRecord, "initial", 7) == 0)
	bufStat = SYD_B_RESTART;
    else
	bufStat = SYD_B_FULL;
    if (recDelim != '\n') {
	while ((delim = fgetc(ssFile)) != EOF && delim != '\n')
	    ;	/* keep skipping until end of line */
    }
    if (posKeyFwd("%endHeader%", ssFile) < 0)
	return SYD_B_EOF;
    else if (fgets(record, 120, ssFile) == NULL)
	return SYD_B_EOF;
    return bufStat;
}
