/* Copyright (C) 1996 Robert de Bath <rdebath@cix.compulink.co.uk>
 * This file is part of the Linux-8086 C library and is distributed
 * under the GNU Library General Public License.
 */

/* This is an implementation of the C standard IO package.
 *
 * Updates:
 * 29-Sep-2000 W. Greathouse    1. fgetc copying beyond end of buffer
 *                              2. stdout needs flushed when input requested on
 *                                 stdin.
 *                              3. bufend was set incorrectly to 4 bytes beyond
 *                                 bufin (sizeof a pointer) instead of BUFSIZ.
 *                                 This resulted in 4 byte buffers for full
 *                                 buffered stdin and stdout and an 8 byte
 *                                 buffer for the unbuffered stderr!
 */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <malloc.h>
#include <errno.h>
#include <string.h>

#undef STUB_FWRITE

extern FILE *__IO_list;			/* For fflush at exit */

#define FIXED_BUFFERS 2
struct fixed_buffer {
	unsigned char data[BUFSIZ];
	unsigned char used;
};

extern struct fixed_buffer _fixed_buffers[FIXED_BUFFERS];

extern unsigned char *_alloc_stdio_buffer(size_t size);
extern void _free_stdio_buffer(unsigned char *buf);

#ifdef L__alloc_stdio_buffer

unsigned char *_alloc_stdio_buffer(size_t size)
{
	if (size == BUFSIZ) {
		int i;

		for (i = 0; i < FIXED_BUFFERS; i++)
			if (!_fixed_buffers[i].used) {
				_fixed_buffers[i].used = 1;
				return _fixed_buffers[i].data;
			}
	}
	return malloc(size);
}
#endif

#ifdef L__free_stdio_buffer

void _free_stdio_buffer(unsigned char *buf)
{
	int i;

	for (i = 0; i < FIXED_BUFFERS; i++) {
		if (buf == _fixed_buffers[i].data) {
			_fixed_buffers[i].used = 0;
			return;
		}
	}
	free(buf);
}
#endif

#ifdef L__stdio_init

#if FIXED_BUFFERS < 2
#error FIXED_BUFFERS must be >= 2
#endif

#define bufin (_fixed_buffers[0].data)
#define bufout (_fixed_buffers[1].data)
#define buferr (_stdio_streams[2].unbuf)		/* Stderr is unbuffered */

struct fixed_buffer _fixed_buffers[FIXED_BUFFERS];

FILE _stdio_streams[3] = {
	{bufin, bufin, bufin, bufin, bufin + BUFSIZ,
	 0, _IOFBF | __MODE_READ | __MODE_IOTRAN | __MODE_FREEBUF},
	{bufout, bufout, bufout, bufout, bufout + BUFSIZ,
	 1, _IOFBF | __MODE_WRITE | __MODE_IOTRAN | __MODE_FREEBUF},
	{buferr, buferr, buferr, buferr, buferr + sizeof(buferr),
	 2, _IONBF | __MODE_WRITE | __MODE_IOTRAN}
};

/*
 * Note: the following forces linking of the __init_stdio function if
 * any of the stdio functions are used (except perror) since they all
 * call fflush directly or indirectly.
 */
FILE *__IO_list = 0;			/* For fflush at exit */

/* Call the stdio initiliser; it's main job it to call atexit */

void __stdio_close_all(void)
{
	FILE *fp;

	fflush(stdout);
	fflush(stderr);
	for (fp = __IO_list; fp; fp = fp->next) {
		fflush(fp);
		close(fp->fd);
		/* Note we're not de-allocating the memory */
		/* There doesn't seem to be much point :-) */
		fp->fd = -1;
	}
}

void __init_stdio(void)
{
	static int stdio_initialized = 0;
#if FIXED_BUFFERS > 2
	int i;
#endif

	if (stdio_initialized!=0)
	    return;
	stdio_initialized++;

#if FIXED_BUFFERS > 2
	for ( i = 2 ; i < FIXED_BUFFERS ; i++ ) {
		_fixed_buffers[i].used = 0;
	}
#endif

	_fixed_buffers[0].used = 1;
	_fixed_buffers[1].used = 1;

	if (isatty(1)) {
		stdout->mode |= _IOLBF;
	}
#if 1
	/* Taken care of in _start.S and atexit.c now. */
	atexit(__stdio_close_all);
#endif
}
#endif

#ifdef L_fputc
int fputc(ch, fp)
int ch;
FILE *fp;
{
	register int v;

	 __init_stdio();

	v = fp->mode;
	/* If last op was a read ... */
	if ((v & __MODE_READING) && fflush(fp))
		return EOF;

	/* Can't write or there's been an EOF or error then return EOF */
	if ((v & (__MODE_WRITE | __MODE_EOF | __MODE_ERR)) != __MODE_WRITE)
		return EOF;

	/* In MSDOS translation mode */
#if __MODE_IOTRAN
	if (ch == '\n' && (v & __MODE_IOTRAN) && fputc('\r', fp) == EOF)
		return EOF;
#endif

	/* Buffer is full */
	if (fp->bufpos >= fp->bufend && fflush(fp))
		return EOF;

	/* Right! Do it! */
	*(fp->bufpos++) = ch;
	fp->mode |= __MODE_WRITING;

	/* Unbuffered or Line buffered and end of line */
	if (((ch == '\n' && (v & _IOLBF)) || (v & _IONBF))
		&& fflush(fp))
		return EOF;

	/* Can the macro handle this by itself ? */
	if (v & (__MODE_IOTRAN | _IOLBF | _IONBF))
		fp->bufwrite = fp->bufstart;	/* Nope */
	else
		fp->bufwrite = fp->bufend;	/* Yup */

	/* Correct return val */
	return (unsigned char) ch;
}
#endif

#ifdef L_fgetc
int fgetc(fp)
FILE *fp;
{
	int ch;

	 __init_stdio();

	if (fp->mode & __MODE_WRITING)
		fflush(fp);

	if ( (fp == stdin) && (stdout->fd != -1) && (stdout->mode & __MODE_WRITING) ) 
	    fflush(stdout);

#if __MODE_IOTRAN
  try_again:
#endif
	/* Can't read or there's been an EOF or error then return EOF */
	if ((fp->mode & (__MODE_READ | __MODE_EOF | __MODE_ERR)) !=
		__MODE_READ) return EOF;

	/* Nothing in the buffer - fill it up */
	if (fp->bufpos >= fp->bufread) {
		fp->bufpos = fp->bufread = fp->bufstart;
		ch = fread(fp->bufpos, 1, fp->bufend - fp->bufstart, fp);
		if (ch == 0)
			return EOF;
		fp->bufread += ch;
		fp->mode |= __MODE_READING;
		fp->mode &= ~__MODE_UNGOT;
	}
	ch = *(fp->bufpos++);

#if __MODE_IOTRAN
	/* In MSDOS translation mode; WARN: Doesn't work with UNIX macro */
	if (ch == '\r' && (fp->mode & __MODE_IOTRAN))
		goto try_again;
#endif

	return ch;
}
#endif

#ifdef L_fflush
int fflush(fp)
FILE *fp;
{
	int len, cc, rv = 0;
	char *bstart;

	 __init_stdio();

	if (fp == NULL) {			/* On NULL flush the lot. */
		if (fflush(stdin))
			return EOF;
		if (fflush(stdout))
			return EOF;
		if (fflush(stderr))
			return EOF;

		for (fp = __IO_list; fp; fp = fp->next)
			if (fflush(fp))
				return EOF;

		return 0;
	}

	/* If there's output data pending */
	if (fp->mode & __MODE_WRITING) {
		len = fp->bufpos - fp->bufstart;

		if (len) {
			bstart = fp->bufstart;
			/*
			 * The loop is so we don't get upset by signals or partial writes.
			 */
			do {
				cc = write(fp->fd, bstart, len);
				if (cc > 0) {
					bstart += cc;
					len -= cc;
				}
			}
			while (cc > 0 || (cc == -1 && errno == EINTR));
			/*
			 * If we get here with len!=0 there was an error, exactly what to
			 * do about it is another matter ...
			 *
			 * I'll just clear the buffer.
			 */
			if (len) {
				fp->mode |= __MODE_ERR;
				rv = EOF;
			}
		}
	}
	/* If there's data in the buffer sychronise the file positions */
	else if (fp->mode & __MODE_READING) {
		/* Humm, I think this means sync the file like fpurge() ... */
		/* Anyway the user isn't supposed to call this function when reading */

		len = fp->bufread - fp->bufpos;	/* Bytes buffered but unread */
		/* If it's a file, make it good */
		if (len > 0 && lseek(fp->fd, (long) -len, 1) < 0) {
			/* Hummm - Not certain here, I don't think this is reported */
			/*
			 * fp->mode |= __MODE_ERR; return EOF;
			 */
		}
	}

	/* All done, no problem */
	fp->mode &=
		(~(__MODE_READING | __MODE_WRITING | __MODE_EOF | __MODE_UNGOT));
	fp->bufread = fp->bufwrite = fp->bufpos = fp->bufstart;
	return rv;
}
#endif

#ifdef L_fgets
/* Nothing special here ... */
char *fgets(s, count, f)
char *s;
int count;
FILE *f;
{
	char *ret;
	register size_t i;
	register int ch;

	 __init_stdio();

	ret = s;
	for (i = count-1; i > 0; i--) {
		ch = getc(f);
		if (ch == EOF) {
			if (s == ret)
				return 0;
			break;
		}
		*s++ = (char) ch;
		if (ch == '\n')
			break;
	}
	*s = 0;

	if (ferror(f))
		return 0;
	return ret;
}
#endif

#ifdef L_gets
char *gets(str) /* BAD function; DON'T use it! */
char *str;
{
	/* Auwlright it will work but of course _your_ program will crash */
	/* if it's given a too long line */
	register char *p = str;
	register int c;

	 __init_stdio();

	while (((c = getc(stdin)) != EOF) && (c != '\n'))
		*p++ = c;
	*p = '\0';
	return (((c == EOF) && (p == str)) ? NULL : str);	/* NULL == EOF */
}
#endif

#ifdef L_fputs
int fputs(str, fp)
const char *str;
FILE *fp;
{
	register int n = 0;

	 __init_stdio();

	while (*str) {
		if (putc(*str++, fp) == EOF)
			return (EOF);
		++n;
	}
	return (n);
}
#endif

#ifdef L_puts
int puts(str)
const char *str;
{
	register int n;

	 __init_stdio();

	if (((n = fputs(str, stdout)) == EOF)
		|| (putc('\n', stdout) == EOF))
		return (EOF);
	return (++n);
}
#endif

#ifdef L_fread
/*
 * fread will often be used to read in large chunks of data calling read()
 * directly can be a big win in this case. Beware also fgetc calls this
 * function to fill the buffer.
 * 
 * This ignores __MODE__IOTRAN; probably exactly what you want. (It _is_ what
 * fgetc wants)
 */
size_t fread(buf, size, nelm, fp)
void *buf;
size_t size;
size_t nelm;
FILE *fp;
{
	int len, v;
	unsigned bytes, got = 0;

	 __init_stdio();

	v = fp->mode;

	/* Want to do this to bring the file pointer up to date */
	if (v & __MODE_WRITING)
		fflush(fp);

	/* Can't read or there's been an EOF or error then return zero */
	if ((v & (__MODE_READ | __MODE_EOF | __MODE_ERR)) != __MODE_READ)
		return 0;

	/* This could be long, doesn't seem much point tho */
	bytes = size * nelm;

	len = fp->bufread - fp->bufpos;
	if (len >= bytes) {			/* Enough buffered */
		memcpy(buf, fp->bufpos, (unsigned) bytes);
		fp->bufpos += bytes;
		return bytes;
	} else if (len > 0) {		/* Some buffered */
		memcpy(buf, fp->bufpos, len);
		got = len;
	}

	/* Need more; do it with a direct read */
	len = read(fp->fd, buf + got, (unsigned) (bytes - got));

	/* Possibly for now _or_ later */
	if (len < 0) {
		fp->mode |= __MODE_ERR;
		len = 0;
	} else if (len == 0)
		fp->mode |= __MODE_EOF;

	return (got + len) / size;
}
#endif

#ifdef L_fwrite
/*
 * Like fread, fwrite will often be used to write out large chunks of
 * data; calling write() directly can be a big win in this case.
 * 
 * But first we check to see if there's space in the buffer.
 * 
 * Again this ignores __MODE__IOTRAN.
 */
size_t fwrite(buf, size, nelm, fp)
const void *buf;
size_t size;
size_t nelm;
FILE *fp;
{
	register int v;
	int len;
	unsigned bytes, put;

	 __init_stdio();

#ifdef STUB_FWRITE
	bytes = size * nelm;
	while (bytes > 0) {
		len = write(fp->fd, buf, bytes);
		if (len <= 0) {
			break;
		}
		bytes -= len;
		buf += len;
	}
	return nelm;
#else

	v = fp->mode;
	/* If last op was a read ... */
	if ((v & __MODE_READING) && fflush(fp))
		return 0;

	/* Can't write or there's been an EOF or error then return 0 */
	if ((v & (__MODE_WRITE | __MODE_EOF | __MODE_ERR)) != __MODE_WRITE)
		return 0;

	/* This could be long, doesn't seem much point tho */
	bytes = size * nelm;

	len = fp->bufend - fp->bufpos;

	/* Flush the buffer if not enough room */
	if (bytes > len)
		if (fflush(fp))
			return 0;

	len = fp->bufend - fp->bufpos;
	if (bytes <= len) {			/* It'll fit in the buffer ? */
		fp->mode |= __MODE_WRITING;
		memcpy(fp->bufpos, buf, bytes);
		fp->bufpos += bytes;

		/* If we're not fully buffered */
		if (v & (_IOLBF | _IONBF))
			fflush(fp);

		return nelm;
	} else
		/* Too big for the buffer */
	{
		put = bytes;
		do {
			len = write(fp->fd, buf, bytes);
			if (len > 0) {
				buf += len;
				bytes -= len;
			}
		}
		while (len > 0 || (len == -1 && errno == EINTR));

		if (len < 0)
			fp->mode |= __MODE_ERR;

		put -= bytes;
	}

	return put / size;
#endif
}
#endif

#ifdef L_rewind
void rewind(fp)
FILE *fp;
{
	 __init_stdio();

	fseek(fp, (long) 0, 0);
	clearerr(fp);
}
#endif

#ifdef L_fseek
int fseek(fp, offset, ref)
FILE *fp;
long offset;
int ref;
{
#if 0
	/* FIXME: this is broken!  BROKEN!!!! */
	/* if __MODE_READING and no ungetc ever done can just move pointer */
	/* This needs testing! */

	if ((fp->mode & (__MODE_READING | __MODE_UNGOT)) == __MODE_READING &&
		(ref == SEEK_SET || ref == SEEK_CUR)) {
		long fpos = lseek(fp->fd, 0L, SEEK_CUR);

		if (fpos == -1)
			return EOF;

		if (ref == SEEK_CUR) {
			ref = SEEK_SET;
			offset = fpos + offset + fp->bufpos - fp->bufread;
		}
		if (ref == SEEK_SET) {
			if (offset < fpos
				&& offset >= fpos + fp->bufstart - fp->bufread) {
				fp->bufpos = offset - fpos + fp->bufread;
				return 0;
			}
		}
	}
#endif

	/* Use fflush to sync the pointers */

	if (fflush(fp) == EOF)
		return EOF;
	if (lseek(fp->fd, offset, ref) < 0)
		return EOF;
	return 0;
}
#endif

#ifdef L_ftell
long ftell(fp)
FILE *fp;
{
	if (fflush(fp) == EOF)
		return EOF;
	return lseek(fp->fd, 0L, SEEK_CUR);
}
#endif

#ifdef L_fopen
/*
 * This Fopen is all three of fopen, fdopen and freopen. The macros in
 * stdio.h show the other names.
 */
FILE *__fopen(fname, fd, fp, mode)
const char *fname;
int fd;
FILE *fp;
const char *mode;
{
	int open_mode = 0;

#if __MODE_IOTRAN
	int do_iosense = 1;
#endif
	int fopen_mode = 0;
	FILE *nfp = 0;

	 __init_stdio();

	/* If we've got an fp close the old one (freopen) */
	if (fp) {
		/* Careful, don't de-allocate it */
		fopen_mode |=
			(fp->mode & (__MODE_BUF | __MODE_FREEFIL | __MODE_FREEBUF));
		fp->mode &= ~(__MODE_FREEFIL | __MODE_FREEBUF);
		fclose(fp);
	}

	/* decode the new open mode */
	while (*mode)
		switch (*mode++) {
		case 'r':
			fopen_mode |= __MODE_READ;
			break;
		case 'w':
			fopen_mode |= __MODE_WRITE;
			open_mode = (O_CREAT | O_TRUNC);
			break;
		case 'a':
			fopen_mode |= __MODE_WRITE;
			open_mode = (O_CREAT | O_APPEND);
			break;
		case '+':
			fopen_mode |= __MODE_RDWR;
			break;
#if __MODE_IOTRAN
		case 'b':				/* Binary */
			fopen_mode &= ~__MODE_IOTRAN;
			do_iosense = 0;
			break;
		case 't':				/* Text */
			fopen_mode |= __MODE_IOTRAN;
			do_iosense = 0;
			break;
#endif
		}

	/* Add in the read/write options to mode for open() */
	switch (fopen_mode & (__MODE_READ | __MODE_WRITE)) {
	case 0:
		return 0;
	case __MODE_READ:
		open_mode |= O_RDONLY;
		break;
	case __MODE_WRITE:
		open_mode |= O_WRONLY;
		break;
	default:
		open_mode |= O_RDWR;
		break;
	}

	/* Allocate the (FILE) before we do anything irreversable */
	if (fp == 0) {
		nfp = malloc(sizeof(FILE));
		if (nfp == 0)
			return 0;
	}

	/* Open the file itself */
	if (fname)
		fd = open(fname, open_mode, 0666);
	if (fd < 0) {				/* Grrrr */
		if (nfp)
			free(nfp);
		return 0;
	}

	/* If this isn't freopen create a (FILE) and buffer for it */
	if (fp == 0) {
		fp = nfp;
		fp->next = __IO_list;
		__IO_list = fp;

		fp->mode = __MODE_FREEFIL;
		if (isatty(fd)) {
			fp->mode |= _IOLBF;
#if __MODE_IOTRAN
			if (do_iosense)
				fopen_mode |= __MODE_IOTRAN;
#endif
		} else
			fp->mode |= _IOFBF;

		fp->bufstart = _alloc_stdio_buffer(BUFSIZ);

		if (fp->bufstart == 0) {	/* Oops, no mem *//* Humm, full buffering with a two(!) byte
									   * buffer. */
			fp->bufstart = fp->unbuf;
			fp->bufend = fp->unbuf + sizeof(fp->unbuf);
		} else {
			fp->bufend = fp->bufstart + BUFSIZ;
			fp->mode |= __MODE_FREEBUF;
		}
	}
	/* Ok, file's ready clear the buffer and save important bits */
	fp->bufpos = fp->bufread = fp->bufwrite = fp->bufstart;
	fp->mode |= fopen_mode;
	fp->fd = fd;
	return fp;
}
#endif

#ifdef L_fclose
int fclose(fp)
FILE *fp;
{
	int rv = 0;

	 __init_stdio();

	if (fp == 0) {
		errno = EINVAL;
		return EOF;
	}
	if (fflush(fp))
		return EOF;

	if (close(fp->fd))
		rv = EOF;
	fp->fd = -1;

	if (fp->mode & __MODE_FREEBUF) {
		_free_stdio_buffer(fp->bufstart);
		fp->mode &= ~__MODE_FREEBUF;
		fp->bufstart = fp->bufend = 0;
	}

	if (fp->mode & __MODE_FREEFIL) {
		FILE *prev = 0, *ptr;

		fp->mode = 0;

		for (ptr = __IO_list; ptr && ptr != fp; ptr = ptr->next);
		if (ptr == fp) {
			if (prev == 0)
				__IO_list = fp->next;
			else
				prev->next = fp->next;
		}
		free(fp);
	} else
		fp->mode = 0;

	return rv;
}
#endif

#ifdef L_setbuffer
void setbuffer(fp, buf, size)
FILE *fp;
char *buf;
size_t size;
{
	fflush(fp);

	if ((fp->bufstart == (unsigned char *) buf)
		&& (fp->bufend == ((unsigned char *) buf + size)))
		return;

	if (fp->mode & __MODE_FREEBUF) {
		_free_stdio_buffer(fp->bufstart);
	}
	fp->mode &= ~(__MODE_FREEBUF | __MODE_BUF);

	if (buf == 0) {
		fp->bufstart = fp->unbuf;
		fp->bufend = fp->unbuf + sizeof(fp->unbuf);
		fp->mode |= _IONBF;
	} else {
		fp->bufstart = buf;
		fp->bufend = buf + size;
		fp->mode |= _IOFBF;
	}
	fp->bufpos = fp->bufread = fp->bufwrite = fp->bufstart;
}
#endif

#ifdef L_setvbuf
int setvbuf(fp, buf, mode, size)
FILE *fp;
char *buf;
int mode;
size_t size;
{
	fflush(fp);
	if (fp->mode & __MODE_FREEBUF) {
		_free_stdio_buffer(fp->bufstart);
	}
	fp->mode &= ~(__MODE_FREEBUF | __MODE_BUF);
	fp->bufstart = fp->unbuf;
	fp->bufend = fp->unbuf + sizeof(fp->unbuf);
	fp->mode |= _IONBF;

	if (mode == _IOFBF || mode == _IOLBF) {
		if (size <= 0) {
			size = BUFSIZ;
		}
		if (buf == 0) {
			buf = _alloc_stdio_buffer(size);
			if (buf == 0)
				return EOF;
		}

		fp->bufstart = buf;
		fp->bufend = buf + size;
		fp->mode |= mode;
	}
	fp->bufpos = fp->bufread = fp->bufwrite = fp->bufstart;
	return 0;
}
#endif

#ifdef L_ungetc
int ungetc(c, fp)
int c;
FILE *fp;
{
	 __init_stdio();

	if (fp->mode & __MODE_WRITING)
		fflush(fp);

	/* Can't read or there's been an error then return EOF */
	if ((fp->mode & (__MODE_READ | __MODE_ERR)) != __MODE_READ)
		return EOF;

	/* Can't do fast fseeks */
	fp->mode |= __MODE_UNGOT;

	if (fp->bufpos > fp->bufstart)
		return *--fp->bufpos = (unsigned char) c;
	else if (fp->bufread == fp->bufstart)
		return *fp->bufread++ = (unsigned char) c;
	else
		return EOF;
}
#endif