/*
 * Copyright (c) 1994 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * File I/O routines callable by users.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include "std_arg.h"
#include "calc.h"
#include "longbits.h"
#include "have_fpos.h"
#include "fposval.h"


#define	READSIZE	1024	/* buffer size for reading */

/*
 * Definition of opened files.
 */
typedef struct {
	FILEID id;		/* id to identify this file */
	FILE *fp;		/* real file structure for I/O */
	char *name;		/* file name */
	BOOL reading;		/* TRUE if opened for reading */
	BOOL writing;		/* TRUE if opened for writing */
	char *mode;		/* open mode */
} FILEIO;


/*
 * Table of opened files.
 * The first three entries always correspond to stdin, stdout, and stderr,
 * and cannot be closed.  Their file ids are always 0, 1, and 2.
 */
static FILEIO files[MAXFILES] = {
	{FILEID_STDIN,  NULL,  "(stdin)",  TRUE, FALSE, "reading"},
	{FILEID_STDOUT, NULL, "(stdout)", FALSE,  TRUE, "writing"},
	{FILEID_STDERR, NULL, "(stderr)", FALSE,  TRUE, "writing"}
};
static FILEID lastid = FILEID_STDERR;		/* last allocated file id */

/* forward static declarations */
static ZVALUE filepos2z MATH_PROTO((FILEPOS pos));
static FILEPOS z2filepos MATH_PROTO((ZVALUE pos));
static int set_open_pos MATH_PROTO((FILE *fp, ZVALUE zpos));
static int get_open_pos MATH_PROTO((FILE *fp, ZVALUE *res));
static ZVALUE stsize2z MATH_PROTO((off_t siz));
static int get_open_siz MATH_PROTO((FILE *fp, ZVALUE *res));


/*
 * file_init - perform needed initilization work
 *
 * On some systems, one cannot initialize a pointer to a FILE *.
 * This routine, called once at startup is a work-a-round for
 * systems with such bogons.
 */
void
file_init()
{
    static int done = 0;	/* 1 => routine already called */

    if (!done) {
	files[0].fp = stdin;
	files[1].fp = stdout;
	files[2].fp = stderr;
	done = 1;
    }
}


/*
 * Open the specified file name for reading or writing as determined by
 * the specified mode ("r", "w", or "a").  Returns a file id which can be
 * used to do I/O to the file, or else FILEID_NONE if the open failed.
 * Aborts with an error if too many files are opened or the mode is illegal.
 */
FILEID
openid(name, mode)
	char *name;		/* file name */
	char *mode;		/* open mode */
{
	FILEIO *fiop;		/* file structure */
	FILEID id;		/* new file id */
	int count;

	if (((*mode != 'r') && (*mode != 'w') && (*mode != 'a')) || mode[1])
		math_error("Illegal mode for fopen");

	count = MAXFILES;
	do {
		if (--count < 0)
			math_error("Too many open files");
		id = ++lastid;
		fiop = &files[id % MAXFILES];

	} while (fiop->reading || fiop->writing);

	fiop->name = (char *)malloc(strlen(name) + 1);
	if (fiop->name == NULL) {
		lastid--;
		math_error("No memory for filename");
	}
	strcpy(fiop->name, name);

	fiop->fp = f_open(name, mode);
	if (fiop->fp == NULL) {
		free(fiop->name);
		fiop->name = NULL;
		lastid--;
		return FILEID_NONE;
	}

	switch (*mode) {
		case 'r':
			fiop->mode = "reading";
			fiop->reading = TRUE;
			break;
		case 'w':
			fiop->mode = "writing";
			fiop->writing = TRUE;
			break;
		case 'a':
			fiop->mode = "appending";
			fiop->writing = TRUE;
			break;
	}

	fiop->id = id;

	return id;
}


/*
 * Find the file I/O structure for the specified file id, and verify that
 * it is opened in the required manner ('r' for reading or 'w' for writing).
 * If mode is 0, then no open checks are made at all, and NULL is then
 * returned if the id represents a closed file.
 */
static FILEIO *
findid(id, mode)
	int mode;
	FILEID id;
{
	FILEIO *fiop;		/* file structure */
	static char *msg;
	BOOL flag = 0;

	if ((id < 0) || (id > lastid))
		math_error("Illegal file id");

	fiop = &files[id % MAXFILES];

	switch (mode) {
		case 'r':
			msg = "Reading from";
			flag = fiop->reading;
			break;
		case 'w':
			msg = "Writing to";
			flag = fiop->writing;
			break;
		case 0:
			msg = NULL;
			break;
		default:
			math_error("Unknown findid mode");
	}

	if (fiop->id != id) {
		if (msg)
			math_error("%s closed file", msg);
		return NULL;
	}

	if (msg && !flag)
		math_error("%s file not opened that way", msg);

	return fiop;
}


/*
 * Return whether or not a file id is valid.  This is used for if tests.
 */
BOOL
validid(id)
	FILEID id;
{
	return (findid(id, 0) != NULL);
}


/*
 * Return the file id for the entry in the file table at the specified index.
 * Returns FILEID_NONE if the index is illegal or the file is closed.
 */
FILEID
indexid(index)
	long index;
{
	FILEIO *fiop;		/* file structure */

	if ((index < 0) || (index >= MAXFILES))
		return FILEID_NONE;

	fiop = &files[index];
	if (fiop->reading || fiop->writing)
		return fiop->id;

	return FILEID_NONE;
}


/*
 * Close the specified file id.  Returns TRUE if there was an error.
 * Closing of stdin, stdout, or stderr is illegal, but closing of already
 * closed files is allowed.
 */
BOOL
closeid(id)
	FILEID id;
{
	FILEIO *fiop;		/* file structure */
	int err;

	/* firewall */
	if ((id == FILEID_STDIN) || (id == FILEID_STDOUT) ||
	    (id == FILEID_STDERR)) {
		math_error("Cannot close stdin, stdout, or stderr");
	}

	/* get file structure */
	fiop = findid(id, 0);
	if (fiop == NULL)
		return FALSE;

	/* note that file is no longer for reading or writing */
	fiop->id = FILEID_NONE;
	if (!fiop->reading && !fiop->writing)
		math_error("Closing non-opened file");
	fiop->reading = FALSE;
	fiop->writing = FALSE;

	/* free filename */
	if (fiop->name)
		free(fiop->name);
	fiop->name = NULL;

	/* close file and note error state */
	err = ferror(fiop->fp);
	err |= fclose(fiop->fp);
	fiop->fp = NULL;

	/* return success or failure */
	return (err != 0);
}


/*
 * Return whether or not an error occurred to a file.
 */
BOOL
errorid(id)
	FILEID id;
{
	FILEIO *fiop;		/* file structure */

	fiop = findid(id, 0);
	if (fiop == NULL)
		math_error("Closed file for ferror");
	return (ferror(fiop->fp) != 0);
}


/*
 * Return whether or not end of file occurred to a file.
 */
BOOL
eofid(id)
	FILEID id;
{
	FILEIO *fiop;		/* file structure */

	fiop = findid(id, 0);
	if (fiop == NULL)
		math_error("Closed file for feof");
	return (feof(fiop->fp) != 0);
}


/*
 * Flush output to an opened file.
 */
void
flushid(id)
	FILEID id;
{
	FILEIO *fiop;		/* file structure */

	fiop = findid(id, 'w');
	fflush(fiop->fp);
}


/*
 * Read the next line from an opened file.
 * Returns a pointer to an allocated string holding the null-terminated
 * line, or else a NULL pointer on an end of file or error.  If remove_nl
 * is true, then the trailing newline is replaced with \0.
 */
void
readid(id, remove_nl, retptr)
	FILEID	id;		/* file to read from */
	int remove_nl;		/* 1 => remove retailing newline */
	char **retptr;		/* returned pointer to string */
{
	FILEIO *fiop;		/* file structure */
	char *str;		/* current string */
	int len;		/* current length of string */
	int totlen;		/* total length of string */
	char buf[READSIZE];	/* temporary buffer */

	totlen = 0;
	str = NULL;

	fiop = findid(id, 'r');

	while (fgets(buf, READSIZE, fiop->fp) && buf[0]) {
		len = strlen(buf);
		if (totlen)
			str = (char *)realloc(str, totlen + len + 1);
		else
			str = (char *)malloc(len + 1);
		if (str == NULL)
			math_error("No memory in freadline");
		strcpy(&str[totlen], buf);
		totlen += len;
		if (remove_nl && buf[len - 1] == '\n') {
			str[totlen - 1] = '\0';
			*retptr = str;
			return;
		}
	}
	if (totlen && ferror(fiop->fp)) {
		free(str);
		str = NULL;
	}
	*retptr = str;
}


/*
 * Return the next character from an opened file.
 * Returns EOF if there was an error or end of file.
 */
int
getcharid(id)
	FILEID id;
{
	return fgetc(findid(id, 'r')->fp);
}


/*
 * Print out the name of an opened file.
 * If the file has been closed, a null name is printed.
 * If flags contain PRINT_UNAMBIG then extra information is printed
 * identifying the output as a file and some data about it.
 */
void
printid(id, flags)
	int flags;
	FILEID id;
{
	FILEIO *fiop;		/* file structure */
	FILE *fp;
	ZVALUE pos;		/* file position */

	/*
	 * filewall - file is closed
	 */
	fiop = findid(id, 0);
	if (fiop == NULL) {
		math_str((flags & PRINT_UNAMBIG) ? "FILE (closed)" : "\"\"");
		return;
	}

	/*
	 * print quoted filename and mode
	 */
	if ((flags & PRINT_UNAMBIG) == 0) {
		math_chr('"');
		math_str(fiop->name);
		math_chr('"');
		return;
	}
	math_fmt("FILE %d \"%s\" (%s, pos ", id, fiop->name,  fiop->mode);

	/*
	 * print file position
	 */
	fp = fiop->fp;
	if (fp == NULL) {
	    math_error("\nBogus internal file pointer!");
	}
	if (get_open_pos(fp, &pos) < 0) {
	    math_error("\nError while determining file position!");
	}
	zprintval(pos, 0, 0);
	zfree(pos);

	/*
	 * report special status
	 */
	if (ferror(fp))
		math_str(", error");
	if (feof(fp))
		math_str(", eof");
	math_chr(')');
}


/*
 * Print a formatted string similar to printf.  Various formats of output
 * are possible, depending on the format string AND the actual types of the
 * values.  Mismatches do not cause errors, instead something reasonable is
 * printed instead.  The output goes to the file with the specified id.
 */
void
idprintf(id, fmt, count, vals)
	int count;
	FILEID id;			/* file id to print to */
	char *fmt;			/* standard format string */
	VALUE **vals;			/* table of values to print */
{
	FILEIO *fiop;
	VALUE *vp;
	char *str;
	int ch, len;
	int oldmode, newmode;
	long olddigits, newdigits;
	long width, precision;
	BOOL didneg, didprecision;

	fiop = findid(id, 'w');

	math_setfp(fiop->fp);

	while ((ch = *fmt++) != '\0') {
		if (ch == '\\') {
			ch = *fmt++;
			switch (ch) {
				case 'n': ch = '\n'; break;
				case 'r': ch = '\r'; break;
				case 't': ch = '\t'; break;
				case 'f': ch = '\f'; break;
				case 'v': ch = '\v'; break;
				case 'b': ch = '\b'; break;
				case 0:
					math_setfp(stdout);
					return;
			}
			math_chr(ch);
			continue;
		}

		if (ch != '%') {
			math_chr(ch);
			continue;
		}

		/*
		 * Here to handle formats.
		 */
		didneg = FALSE;
		didprecision = FALSE;
		width = 0;
		precision = 0;

		ch = *fmt++;
		if (ch == '-') {
			didneg = TRUE;
			ch = *fmt++;
		}
		while ((ch >= '0') && (ch <= '9')) {
			width = width * 10 + (ch - '0');
			ch = *fmt++;
		}
		if (ch == '.') {
			didprecision = TRUE;
			ch = *fmt++;
			while ((ch >= '0') && (ch <= '9')) {
				precision = precision * 10 + (ch - '0');
				ch = *fmt++;
			}
		}
		if (ch == 'l')
			ch = *fmt++;

		oldmode = _outmode_;
		newmode = oldmode;
		olddigits = _outdigits_;
		newdigits = olddigits;
		if (didprecision)
			newdigits = precision;

		switch (ch) {
			case 'd':
			case 's':
			case 'c':
				break;
			case 'f':
				newmode = MODE_REAL;
				break;
			case 'e':
				newmode = MODE_EXP;
				break;
			case 'r':
				newmode = MODE_FRAC;
				break;
			case 'o':
				newmode = MODE_OCTAL;
				break;
			case 'x':
				newmode = MODE_HEX;
				break;
			case 'b':
				newmode = MODE_BINARY;
				break;
			case 0:
				math_setfp(stdout);
				return;
			default:
				math_chr(ch);
				continue;
		}

		if (--count < 0)
			math_error("Not enough arguments for fprintf");
		vp = *vals++;

		math_setdigits(newdigits);
		math_setmode(newmode);

		/*
		 * If there is no width specification, or if the type of
		 * value requires multiple lines, then just output the
		 * value directly.
		 */
		if ((width == 0) ||
			(vp->v_type == V_MAT) || (vp->v_type == V_LIST))
		{
			printvalue(vp, PRINT_NORMAL);
			math_setmode(oldmode);
			math_setdigits(olddigits);
			continue;
		}

		/*
		 * There is a field width.  Collect the output in a string,
		 * print it padded appropriately with spaces, and free it.
		 * However, if the output contains a newline, then ignore
		 * the field width.
		 */
		math_divertio();
		printvalue(vp, PRINT_NORMAL);
		str = math_getdivertedio();
		if (strchr(str, '\n'))
			width = 0;
		len = strlen(str);
		while (!didneg && (width > len)) {
			width--;
			math_chr(' ');
		}
		math_str(str);
		free(str);
		while (didneg && (width > len)) {
			width--;
			math_chr(' ');
		}
		math_setmode(oldmode);
		math_setdigits(olddigits);
	}
	math_setfp(stdout);
}


/*
 * Write a character to a file.
 */
void
idfputc(id, ch)
	FILEID id;			/* file id to print to */
	int ch;				/* character to write */
{
	FILEIO *fiop;

	/* get the file info pointer */
	fiop = findid(id, 'w');

	/* set output to file */
	math_setfp(fiop->fp);

	/* write char */
	math_chr(ch);

	/* restore output to stdout */
	math_setfp(stdout);
}


/*
 * Write a string to a file.
 */
void
idfputs(id, str)
	FILEID id;			/* file id to print to */
	char *str;			/* string to write */
{
	FILEIO *fiop;

	/* get the file info pointer */
	fiop = findid(id, 'w');

	/* set output to file */
	math_setfp(fiop->fp);

	/* write the string */
	math_str(str);

	/* restore output to stdout */
	math_setfp(stdout);
}


/*
 * byterev - byte reverse a block of data
 *
 * Performs byte reversal on a chunk of data.
 *
 * NOTE: from and to must not overlap
 */
void
byterev(dest, src, len)
	char *dest;	/* where reversed 'src' is placed */
	char *src;	/* source of reversal */
	int len;	/* length of src in bytes */
{
	int i;

	/* reverse bytes */
	dest += len-1;
	for (i=0; i < len; ++i) {
		*dest-- = *src++;
	}
	return;
}


/*
 * filepos2z - convert a positive file position into a ZVALUE
 *
 * given:
 *	pos		file position
 *
 * returns:
 *	file position as a ZVALUE
 *
 * NOTE: Does not support negative file positions.
 */
static ZVALUE
filepos2z(pos)
	FILEPOS pos;		/* file position */
{
	FULL fval;		/* FULL value to convert */
#if FILEPOS_BITS > FULL_BITS
	ZVALUE tmp;		/* temporary ZVALUE value */
	ZVALUE tmp2;		/* temporary ZVALUE value */
#endif
	ZVALUE ret;		/* ZVALUE file position to return */

	/*
	 * convert file position to ZVALUE
	 *
	 * This is gross, but needed for portability to some systems that
	 * have 64 bit file systems or offsets that are not of a simple type.
	 */
	fval = 0;
#if FILEPOS_BITS <= FULL_BITS
#if defined(FILEPOS_00010002)

	/*
	 * 32 bit native order
	 */
	memcpy((void *)&fval, (void *)&pos, sizeof(pos));
	utoz(fval, &ret);

#elif defined(FILEPOS_02000100)

	/*
	 * 32 bit oppoiste order
	 */
	byterev((char *)&fval, (char *)&pos, sizeof(pos));
	utoz(fval, &ret);

#elif defined(FILEPOS_0000000000010002)

	/*
	 * 64 bit native order
	 */
	memcpy((void *)&fval, (void *)&pos, sizeof(pos));
	utoz(fval, &ret);

#elif defined(FILEPOS_0200010000000000)

	/*
	 * 64 bit oppoiste order
	 */
	byterev((char *)&fval, (char *)&pos, sizeof(pos));
	utoz(fval, &ret);

#elif defined(FILEPOS_0001000200000000)

	/*
	 * swapped 64 bit native order
	 */
	memcpy((void *)&fval, ((void *)&pos)+sizeof(pos)/2, sizeof(pos)/2);
	memcpy(((void *)&fval)+sizeof(pos)/2, (void *)&pos, sizeof(pos)/2);
	utoz(fval, &ret);

#elif defined(FILEPOS_0000000000020001)

	/* swapped 64 bit oppoiste order */
	byterev((void *)&fval, ((void *)&pos)+sizeof(pos)/2, sizeof(pos)/2);
	byterev(((void *)&fval)+sizeof(pos)/2, (void *)&pos, sizeof(pos)/2);
	utoz(fval, &ret);

#else /* unknown FILEPOS length and byte order */

	;%@*@%; /* can't make: unknown FILEPOS length & byte order #1 */ ;%@*@%;

#endif

#else /* file position is too long to fit into a FULL */

#if defined(FILEPOS_0000000000010002)

	/*
	 * 64 bit native order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	memcpy((void *)&fval, (void *)&pos, sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, FILEPOS_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	memcpy((void *)&fval, ((void *)&pos)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);


#elif defined(FILEPOS_0200010000000000)

	/*
	 * 64 bit oppoiste order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	byterev((void *)&fval, (void *)&pos, sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, FILEPOS_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	byterev((void *)&fval, ((void *)&pos)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#elif defined(FILEPOS_0001000200000000)

	/*
	 * swappped 64 bit native order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	memcpy((void *)&fval, ((void *)&pos)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, FILEPOS_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	memcpy((void *)&fval, (void *)&pos, sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#elif defined(FILEPOS_0000000000020001)

	/*
	 * swapped 64 bit reverse order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	byterev((void *)&fval, ((void *)&pos)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, FILEPOS_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	byterev((void *)&fval, (void *)&pos, sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#else /* unknown FILEPOS length and byte order */

	;%@*@%; /* can't make: unknown FILEPOS length & byte order #2 */ ;%@*@%;

#endif
#endif

	/*
	 * return our result
	 */
	return ret;
}


/*
 * z2filepos - convert a positive ZVALUE file position to a FILEPOS
 *
 * given:
 *	zpos		file position as a ZVALUE
 *
 * returns:
 *	file position as a FILEPOS
 *
 * NOTE: Does not support negative file positions.
 */
static FILEPOS
z2filepos(zpos)
	ZVALUE zpos;
{
	FULL lower;		/* lower bits of a file position */
#if FILEPOS_BITS > FULL_BITS
	FULL upper;		/* lower bits of a file position */
#endif
	FILEPOS ret;		/* file position as a FILEPOS */

	/*
	 * deal only with the absolue value
	 */
	zpos.sign = 0;

	/*
	 * convert lower FULL_BITS of the ZVALUE
	 */
	lower = ztofull(zpos);

	/*
	 * if needed, convert the next FULL_BITS of the ZVALUE
	 */
#if FILEPOS_BITS > FULL_BITS
#if FILEPOS_BITS == FULL_BITS*2
	if (zgtmaxufull(zpos)) {
		ZVALUE tmp; /* temporary ZVALUE */

		zcopy(zpos, &tmp);
		zshiftr(tmp, FULL_BITS);
		ztrim(&tmp);
		upper = ztofull(tmp);
		zfree(tmp);
	} else {
		upper = U(0);
	}
#else
	;%@*@%; /* can't make: FILEPOS_BITS not 1 or 2 * FULL_BITS */ ;%@*@%;
#endif
#endif

	/*
	 * convert byte order of FULL value(s)
	 *
	 * This is gross, but needed for portability to some systems that
	 * have 64 bit file systems or offsets that are not of a simple type.
	 */
	memset((void *)&ret, 0, sizeof(ret));
#if FILEPOS_BITS <= FULL_BITS
#if defined(FILEPOS_00010002)

	/*
	 * 32 bit filepos in native byte order
	 */
	memcpy((void *)&ret, (void *)&lower, sizeof(lower));

#elif defined(FILEPOS_02000100)

	/*
	 * 32 bit filepos in opposite byte order
	 */
	byterev((void *)&ret, (void *)&lower, sizeof(lower));

#elif defined(FILEPOS_0000000000010002)

	/*
	 * 64 bit filepos in native byte order
	 */
	memcpy((void *)&ret, (void *)&lower, sizeof(lower));

#elif defined(FILEPOS_0200010000000000)

	/*
	 * 64 bit filepos in opposite byte order
	 */
	byterev((void *)&ret, (void *)&lower, sizeof(lower));

#elif defined(FILEPOS_0001000200000000)

	/*
	 * swapped 64 bit filepos in native order
	 */
	memcpy((void *)&ret, (void *)&lower+sizeof(lower)/2, sizeof(lower)/2);
	memcpy((void *)&ret+sizeof(lower)/2, (void *)&lower, sizeof(lower)/2);

#elif defined(FILEPOS_0000000000020001)

	/*
	 * swapped 64 bit filepos in opposite order
	 */
	byterev((void *)&ret, (void *)&lower+sizeof(lower)/2, sizeof(lower)/2);
	byterev((void *)&ret+sizeof(lower)/2, (void *)&lower, sizeof(lower)/2);

#else /* unknown FILEPOS length and byte order */

	;%@*@%; /* can't make: unknown FILEPOS length & byte order #3 */ ;%@*@%;

#endif

#else /* FILEPOS is twice the size of a FULL */

#if defined(FILEPOS_0000000000010002)

	/*
	 * 64 bit filepos in native byte order with double FULL length
	 */
	memcpy((void *)&ret, (void *)&lower, sizeof(lower));
	memcpy((void *)&ret+sizeof(lower), (void *)&upper, sizeof(upper));

#elif defined(FILEPOS_0200010000000000)

	/*
	 * 64 bit filepos in opposite byte order with double FULL length
	 */
	byterev((void *)&ret, (void *)&lower, sizeof(lower));
	byterev((void *)&ret+sizeof(lower), (void *)&upper, sizeof(upper));

#elif defined(FILEPOS_0001000200000000)

	/*
	 * swapped 64 bit filepos in native byte order with double FULL length
	 */
	memcpy((void *)&ret, (void *)&upper, sizeof(upper));
	memcpy((void *)&ret+sizeof(upper), (void *)&lower, sizeof(lower));

#elif defined(FILEPOS_0000000000020001)

	/*
	 * swapped 64 bit filepos in opposite byte order with double FULL length
	 */
	byterev((void *)&ret, (void *)&upper, sizeof(upper));
	byterev((void *)&ret+sizeof(upper), (void *)&lower, sizeof(lower));

#else /* unknown FILEPOS length and byte order */

	;%@*@%; /* can't make: unknown FILEPOS length & byte order #4 */ ;%@*@%;

#endif
#endif

	/*
	 * return our result
	 */
	return ret;
}


/*
 * get_open_pos - get a an open file position
 *
 * given:
 *	fp		open file stream
 *	res		where to place the file position (ZVALUE)
 *
 * returns:
 *	0		res points to the file position
 *	-1		error
 */
static int
get_open_pos(fp, res)
	FILE *fp;		/* open file stream */
	ZVALUE *res;		/* ptr to file position ZVALUE result */
{
	FILEPOS pos;		/* current file position */

	/*
	 * get the file position
	 */
#if defined(HAVE_FPOS)
	if (fgetpos(fp, (fpos_t *)&pos) < 0) {
	    /* cannot get file position, return -1 */
	    return -1;
	}
#else
	pos = ftell(fp);
	if (pos < 0) {
	    /* cannot get file position, return -1 */
	    return -1;
	}
#endif

	/*
	 * update file position and return success
	 */
	*res = filepos2z(pos);
	return 0;
}


/*
 * getloc - get the current position of the file
 *
 * given:
 *	id	file id of the file
 *	loc	pointer to result
 *
 * returns:
 *	0	able to get file position
 *	-1	unable to get file position
 */
int
getloc(id, res)
	FILEID id;
	ZVALUE *res;
{
	FILEIO *fiop;		/* file structure */
	FILE *fp;

	/*
	 * convert id to stream
	 */
	fiop = findid(id, 0);
	if (fiop == NULL) {
	  	/* file not open */
		return -1;
	}
	fp = fiop->fp;
	if (fp == NULL) {
		math_error("Bogus internal file pointer!");
	}

	/*
	 * return result
	 */
	return get_open_pos(fp, res);
}


/*
 * set_open_pos - set a an open file position
 *
 * given:
 *	fp		open file stream
 *	zpos		file position (ZVALUE) to set
 *
 * returns:
 *	0		res points to the file position
 *	-1		error
 *
 * NOTE: Due to fsetpos limitation, position is set relative to only
 *	 the beginning of the file.
 */
static int
set_open_pos(fp, zpos)
	FILE *fp;		/* open file stream */
	ZVALUE zpos;		/* file position to set */
{
	FILEPOS pos;		/* current file position */

	/*
	 * convert ZVALUE to file position
	 */
	pos = z2filepos(zpos);

	/*
	 * set the file position
	 */
#if defined(HAVE_FPOS)
	if (fsetpos(fp, (fpos_t *)&pos) < 0) {
	    /* cannot set file position, return -1 */
	    return -1;
	}
#else
	if (fseek(fp, pos, 0) < 0) {
	    /* cannot set file position, return -1 */
	    return -1;
	}
#endif

	/*
	 * return sucess
	 */
	return 0;
}


/*
 * setloc - set the current position of the file
 *
 * given:
 *	id	file id of the file
 *	zpos	file position (ZVALUE) to set
 *
 * returns:
 *	0	able to set file position
 *	-1	unable to set file position
 *
 * NOTE: Due to fsetpos limitation, position is set relative to only
 *	 the beginning of the file.
 */
int
setloc(id, zpos)
	FILEID id;
	ZVALUE zpos;
{
	FILEIO *fiop;		/* file structure */
	FILE *fp;

	/*
	 * firewall
	 */
	if ((id == FILEID_STDIN) || (id == FILEID_STDOUT) ||
	    (id == FILEID_STDERR)) {
		math_error("Cannot fseek stdin, stdout, or stderr");
	}

	/*
	 * convert id to stream
	 */
	fiop = findid(id, 0);
	if (fiop == NULL) {
	  	/* file not open */
		return -1;
	}
	fp = fiop->fp;
	if (fp == NULL) {
		math_error("Bogus internal file pointer!");
	}

	/*
	 * return result
	 */
	return set_open_pos(fp, zpos);
}


/*
 * stsize2z - convert a file size into a ZVALUE
 *
 * given:
 *	siz		file size
 *
 * returns:
 *	file size as a ZVALUE
 */
static ZVALUE
stsize2z(siz)
	off_t siz;		/* file size */
{
	FULL fval;		/* FULL value to convert */
#if STSIZE_BITS > FULL_BITS
	ZVALUE tmp;		/* temporary ZVALUE value */
	ZVALUE tmp2;		/* temporary ZVALUE value */
#endif
	ZVALUE ret;		/* ZVALUE file size to return */

	/*
	 * convert file size to ZVALUE
	 *
	 * This is gross, but needed for portability to some systems that
	 * have 64 bit file systems or offsets that are not of a simple type.
	 */
	fval = 0;
#if STSIZE_BITS <= FULL_BITS
#if defined(STSIZE_00010002)

	/*
	 * 32 bit native order
	 */
	memcpy((void *)&fval, (void *)&siz, sizeof(siz));
	utoz(fval, &ret);

#elif defined(STSIZE_02000100)

	/*
	 * 32 bit oppoiste order
	 */
	byterev((char *)&fval, (char *)&siz, sizeof(siz));
	utoz(fval, &ret);

#elif defined(STSIZE_0000000000010002)

	/*
	 * 64 bit native order
	 */
	memcpy((void *)&fval, (void *)&siz, sizeof(siz));
	utoz(fval, &ret);

#elif defined(STSIZE_0200010000000000)

	/*
	 * 64 bit oppoiste order
	 */
	byterev((char *)&fval, (char *)&siz, sizeof(siz));
	utoz(fval, &ret);

#elif defined(STSIZE_0001000200000000)

	/*
	 * swapped 64 bit native order
	 */
	memcpy((void *)&fval, ((void *)&siz)+sizeof(siz)/2, sizeof(siz)/2);
	memcpy(((void *)&fval)+sizeof(siz)/2, (void *)&siz, sizeof(siz)/2);
	utoz(fval, &ret);

#elif defined(STSIZE_0000000000020001)

	/* swapped 64 bit oppoiste order */
	byterev((void *)&fval, ((void *)&siz)+sizeof(siz)/2, sizeof(siz)/2);
	byterev(((void *)&fval)+sizeof(siz)/2, (void *)&siz, sizeof(siz)/2);
	utoz(fval, &ret);

#else /* unknown off_t length and byte order */

	;%@*@%; /* can't make: unknown off_t length & byte order #1 */ ;%@*@%;

#endif

#else /* file size is too long to fit into a FULL */

#if defined(STSIZE_0000000000010002)

	/*
	 * 64 bit native order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	memcpy((void *)&fval, (void *)&siz, sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, STSIZE_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	memcpy((void *)&fval, ((void *)&siz)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#elif defined(STSIZE_0200010000000000)

	/*
	 * 64 bit oppoiste order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	byterev((void *)&fval, (void *)&siz, sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, STSIZE_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	byterev((void *)&fval, ((void *)&siz)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#elif defined(STSIZE_0001000200000000)

	/*
	 * swappped 64 bit native order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	memcpy((void *)&fval, ((void *)&siz)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, STSIZE_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	memcpy((void *)&fval, (void *)&siz, sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#elif defined(STSIZE_0000000000020001)

	/*
	 * swapped 64 bit reverse order with smaller FULL
	 */
	/* convert upper half to ZVALUE */
	byterev((void *)&fval, ((void *)&siz)+sizeof(FULL), sizeof(FULL));
	utoz(fval, &tmp);
	/* shift up ZVALUE into place */
	zshift(tmp, STSIZE_BITS/2, &tmp2);
	zfree(tmp);
	/* convert lower half to ZVALUE */
	byterev((void *)&fval, (void *)&siz, sizeof(FULL));
	utoz(fval, &tmp);
	/* add lower half to upper half */
	zadd(tmp, tmp2, &ret);
	zfree(tmp);
	zfree(tmp2);

#else /* unknown off_t length and byte order */

	;%@*@%; /* can't make: unknown off_t length & byte order #2 */ ;%@*@%;

#endif
#endif

	/*
	 * return our result
	 */
	return ret;
}


/*
 * get_open_siz - get a an open file size
 *
 * given:
 *	fp		open file stream
 *	res		where to place the file size (ZVALUE)
 *
 * returns:
 *	0		res points to the file size
 *	-1		error
 */
static int
get_open_siz(fp, res)
	FILE *fp;		/* open file stream */
	ZVALUE *res;		/* ptr to file size ZVALUE result */
{
	struct stat buf;	/* file status */

	/*
	 * get the file size
	 */
	if (fstat(fileno(fp), &buf) < 0) {
		/* stat error */
		return -1;
	}

	/*
	 * update file size and return success
	 */
	*res = stsize2z(buf.st_size);
	return 0;
}


/*
 * getsize - get the current size of the file
 *
 * given:
 *	id	file id of the file
 *	siz	pointer to result
 *
 * returns:
 *	0	able to get file size
 *	-1	unable to get file size
 */
int
getsize(id, res)
	FILEID id;
	ZVALUE *res;
{
	FILEIO *fiop;		/* file structure */
	FILE *fp;

	/*
	 * convert id to stream
	 */
	fiop = findid(id, 0);
	if (fiop == NULL) {
	  	/* file not open */
		return -1;
	}
	fp = fiop->fp;
	if (fp == NULL) {
		math_error("Bogus internal file pointer!");
	}

	/*
	 * return result
	 */
	return get_open_siz(fp, res);
}
