/*****************************************************************************
 * FILE: djio.c 							     *
 *									     *
 * DESC:								     *
 *	- io library for djgpp support					     *
 *									     *
 * Copyright (C) 1993,1994						     *
 *	Rainer Schnitker, Heeper Str. 283, 33607 Bielefeld		     *
 *	email: rainer@mathematik.uni-bielefeld.de			     *
 *									     *
 *****************************************************************************/

/*
** source: libc from emx/gcc (c) Eberhard Mattes
*/

#include "DPMI.H"
#include "RMLIB.H"
#include "DOSERRNO.H"
#include <stdarg.h>
#include <stddef.h>

#ifdef __EMX__

/* from djgpp include files */
#define O_RDONLY	0x0001
#define O_WRONLY	0x0002
#define O_RDWR		0x0004
#define O_CREAT 	0x0100
#define O_TRUNC 	0x0200
#define O_EXCL		0x0400
#define O_APPEND	0x0800
#define O_NDELAY	0x2000
#define O_TEXT		0x4000
#define O_BINARY	0x8000

#define O_ACCMODE	0x07

#define O_NOINHERIT	0x0080
#define O_DENYALL	0x0010
#define O_DENYWRITE	0x0020
#define O_DENYREAD	0x0030
#define O_DENYNONE	0x0040

#define F_EOF	    0x00000020
#define F_TERMIO    0x00000040
#define F_DEV	    0x00000080
#define F_NPIPE     0x00001000

#define S_IREAD 0x0100	/* owner may read */
#define S_IWRITE 0x0080 /* owner may write */
#define S_IEXEC 0x0040	/* owner may execute <directory search> */

#define SH_MASK 	  0x70

#define FALSE 0
#define TRUE  1

int _fmode_bin = O_BINARY;
int _nfiles = 40;
long _files[40];
int _lookahead[40];

typedef int (*OPENFUNC) (char *, WORD);

void djio_init(void)
{
    int j;
    int handle;

    for (handle = 0; handle < 5; ++handle) {
	_files[handle] = 0;
	_lookahead[handle] = -1;
	if ((j = rm_ioctl_getattr(handle)) != -1) {
	    _files[handle] |= O_TEXT;
	    if (j & 128)
		_files[handle] |= F_DEV;
	    if (handle == 0)
		_files[handle] |= O_RDONLY;
	    else if (handle == 1 || handle == 2)
		_files[handle] |= O_WRONLY;
	    else
		_files[handle] |= O_RDWR;
	}
    }
}

void djio_dup_fileattr(int from, int to)
{
    if (to > 40)
	return;
    _files[to] = _files[from];
    _lookahead[to] = _lookahead[from];
}

static void *my_memchr(const void *s, unsigned char c, size_t n)
{
    if (n != 0) {
	register const unsigned char *p = s;
	do {
	    if (*p++ == c)
		return ((void *) (p - 1));
	} while (--n != 0);
    }
    return (void *) 0;
}

static int do_ftruncate(int fh, long pos)
{
    long temp;

    if ((temp = rm_lseek(fh, 0, SEEK_END)) == -1)
	return -1;
    else if (temp > (long) pos)
	if (rm_lseek(fh, pos, SEEK_SET) == -1 || rm_write(fh, 0, 0) == -1)
	    return -1;
    return 0;
}

static int do_open(char *buf, long flags)
{
    int i, dosfile;
    OPENFUNC openfn;
    unsigned p2;

    i = (int) (flags >> 16);	/* 1 = O_CREAT, 2 = O_EXCL, 4 = O_TRUNC */

    if (i & 0x01) {
	if (i & 0x02)		/* O_CREAT | O_EXCL */
	    openfn = rm_creatnew;
	else if (i & 4) 	/* O_CREAT | O_TRUNC */
	    openfn = rm_creat;
	else if (rm_access(buf, 0) == -1)	/* old file there ? */
	    openfn = rm_creat;
	else
	    openfn = rm_open;
    } else			/* not O_CREAT */
	openfn = rm_open;

    if (openfn == rm_open) {
	int rdwr = flags & O_ACCMODE;
	rdwr >>= 1;
	p2 = flags & 0xff;
	p2 &= ~O_ACCMODE;
	p2 |= rdwr;
    }
    else
	p2 = (unsigned) flags >> 8;	/* creat attrib */

    dosfile = (*openfn) (buf, (WORD) p2);

    if (dosfile == -1)
	return -1;
    else {
	if ((i & 0x05) == 0x4) {/* O_TRUNC with open */
	    if (do_ftruncate(dosfile, 0) < 0)
		return -1;
	}
	return dosfile;
    }

}

static int dj_sopen(char *name, int oflag, int shflag, int pmode)
{
    int handle, saved_errno;
    long flags, bits, last;
    char dummy, ctrlz_kludge = FALSE;

    if ((oflag & O_ACCMODE) == O_RDONLY && (oflag & (O_TRUNC | O_CREAT))) {
	emx_errno = EMX_EINVAL;
	return (-1);
    }
    bits = oflag & (O_ACCMODE | O_NDELAY | O_APPEND);
    if (oflag & O_BINARY)
	 /* do nothing */ ;
    else if (oflag & O_TEXT)
	bits |= O_TEXT;
    else if (_fmode_bin == 0)	/* neither O_TEXT nor O_BINARY given */
	bits |= O_TEXT;

    if ((bits & O_TEXT) && (oflag & O_APPEND) && (oflag & O_ACCMODE) == O_WRONLY) {
	/*
	 * The caller requests to open a text file for appending in
	 * write-only.	To remove the Ctrl-Z (if there is any), we have to
	 * temporarily open the file in read/write mode.
	 */

	flags = O_RDWR | (shflag & SH_MASK);
	ctrlz_kludge = TRUE;
    } else
	flags = (oflag & O_ACCMODE) | (shflag & SH_MASK);

    if (oflag & O_CREAT) {
	int attr = 0;
	if (!(pmode & S_IWRITE))
	    attr |= _A_RDONLY;
	flags |= (attr << 8) | 0x10000;
	if (oflag & O_EXCL)
	    flags |= 0x20000;
    }
    if (oflag & O_TRUNC)
	flags |= 0x40000;

    saved_errno = emx_errno;
    handle = do_open(name, flags);
    if (handle < 0 && ctrlz_kludge && emx_errno == EMX_EACCES) {
	/* Perhaps read access is denied.  Try again. */
	emx_errno = saved_errno;
	ctrlz_kludge = FALSE;
	flags = (flags & ~O_ACCMODE) | (oflag & O_ACCMODE);
	handle = do_open(name, flags);
    }
    if (handle < 0)
	return (-1);

    if (handle >= _nfiles) {
	rm_close(handle);
	emx_errno = EMX_EMFILE;
	return (-1);
    }
    if (rm_ioctl_getattr(handle) & 0x80) {
	bits |= F_DEV;
	oflag &= ~O_APPEND;
    }
    if (!(bits & F_DEV) && (bits & O_TEXT)) {
	last = rm_lseek(handle, -1L, SEEK_END);
	if (last != -1 && rm_read(handle, &dummy, 1) == 1 && dummy == 0x1a)
	    do_ftruncate(handle, last); /* Remove Ctrl-Z) */
	rm_lseek(handle, 0L, SEEK_SET);
    }
    if (ctrlz_kludge) {
	/* Reopen the handle in write-only mode. */

	rm_close(handle);
	flags = (flags & ~O_ACCMODE) | (oflag & O_ACCMODE);
	flags &= ~0x20000;	/* Remove O_EXCL */
	handle = do_open(name, flags);
	if (handle < 0)
	    return (-1);
    }
    _files[handle] = bits;
    _lookahead[handle] = -1;

    /*
     * When opening a file for appending, move to the end of the file. This
     * is required for passing the handle to a child process.
     */

    if (!(bits & F_DEV) && (bits & O_APPEND))
	rm_lseek(handle, 0L, SEEK_END);
    emx_errno = saved_errno;

    return (handle);
}

int dj_creat(char *buf) {
    return dj_sopen(buf, O_CREAT | O_WRONLY | O_TRUNC,
		     O_DENYNONE, S_IREAD | S_IWRITE);
}

int dj_open(char *buf, int flags) {
    return dj_sopen(buf, flags, O_DENYNONE, S_IREAD | S_IWRITE);
}

#define CTRL_Z 0x1a

int dj_write(int handle, void *buf, size_t nbyte)
{
    int out_cnt, lf_cnt, i, n, buf_cnt;
    char *src, *p;
    char tmp[1024];

    if (handle < 0 || handle >= _nfiles) {
	emx_errno = EMX_EBADF;
	return (-1);
    }
    if (_files[handle] & O_APPEND)
	rm_lseek(handle, 0L, SEEK_END);
    if (nbyte == 0)		/* Avoid truncation of file */
	return (0);
    src = buf;
    if (_files[handle] & O_TEXT) {
	out_cnt = lf_cnt = 0;
	buf_cnt = 0;
	p = (char *) my_memchr(src, '\n', nbyte);
	if (p == NULL)
	    goto write_bin;
	for (i = 0; i < (int) nbyte; ++i) {
	    if (src[i] == '\n') {
		if (buf_cnt >= sizeof(tmp))
		    do {
			n = rm_write(handle, tmp, buf_cnt);
			if (n == -1)
			    goto error;
			out_cnt += n;
			if (n != buf_cnt) {
			    emx_errno = EMX_ENOSPC;
			    return (-1);
			}
			buf_cnt = 0;
		    } while (0);
		tmp[buf_cnt++] = '\r';
		++lf_cnt;
	    }
	    if (buf_cnt >= sizeof(tmp))
		do {
		    n = rm_write(handle, tmp, buf_cnt);
		    if (n == -1)
			goto error;
		    out_cnt += n;
		    if (n != buf_cnt) {
			emx_errno = EMX_ENOSPC;
			return (-1);
		    }
		    buf_cnt = 0;
		} while (0);
	    tmp[buf_cnt++] = src[i];
	}
	if (buf_cnt != 0)
	    do {
		n = rm_write(handle, tmp, buf_cnt);
		if (n == -1)
		    goto error;
		out_cnt += n;
		if (n != buf_cnt) {
		    emx_errno = EMX_ENOSPC;
		    return (-1);
		}
		buf_cnt = 0;
	    } while (0);
	return (out_cnt - lf_cnt);
    }
  write_bin:
    n = rm_write(handle, src, nbyte);
    if (n == -1)
	goto error;
    if (n == 0 && !((_files[handle] & F_DEV) && *src == CTRL_Z)) {
	emx_errno = EMX_ENOSPC;
	return (-1);
    }
    return (n);

  error:
    return (-1);
}

static int read_lookahead(int handle, void *buf, size_t nbyte)
{
    int i, n, saved_errno;
    char *dst;

    i = 0;
    dst = buf;
    saved_errno = emx_errno;
    if (nbyte > 0 && _lookahead[handle] != -1) {
	*dst = (char) _lookahead[handle];
	_lookahead[handle] = -1;
	++i;
	--nbyte;
    }
    n = rm_read(handle, dst + i, nbyte);
    if (n == -1) {
	if (emx_errno == EMX_EAGAIN && i > 0) { /* lookahead and O_NDELAY */
	    emx_errno = saved_errno;/* hide EAGAIN */
	    return (i); 	/* and be successful */
	}
	return (-1);
    }
    return (i + n);
}

static long filelength(int handle)
{
    long cur, n;

    cur = rm_lseek(handle, 0L, SEEK_CUR);
    if (cur == -1L)
	return (-1L);
    n = rm_lseek(handle, 0L, SEEK_END);
    rm_lseek(handle, cur, SEEK_SET);
    return (n);
}

static long tell(int handle)
{
    long n;

    if (handle < 0 || handle >= _nfiles) {
	emx_errno = EMX_EBADF;
	return (-1L);
    }
    n = (long) rm_lseek(handle, 0L, SEEK_CUR);
    if (n == -1)
	return (n);
    if (_lookahead[handle] >= 0)
	--n;
    return (n);
}

static int _crlf(char *buf, size_t size, size_t * new_size)
{
    size_t src, dst;
    char *p;

    p = my_memchr(buf, '\r', size);     /* Avoid copying until CR reached */
    if (p == NULL) {		/* This is the trivial case */
	*new_size = size;
	return (0);
    }
    src = dst = p - buf;	/* Start copying here */
    while (src < size) {
	if (buf[src] == '\r') { /* CR? */
	    ++src;		/* Skip the CR */
	    if (src >= size) {	/* Is it the final char? */
		*new_size = dst;/* Yes -> don't include in new_size, */
		return (1);	/* notify caller	     */
	    }
	    if (buf[src] != '\n')       /* CR not followed by LF? */
		--src;		/* Yes -> copy the CR */
	}
	buf[dst++] = buf[src++];/* Copy a character */
    }
    *new_size = dst;
    return (0);
}

static int eof(int handle)
{
    long cur, len;

    if (handle < 0 || handle >= _nfiles) {
	emx_errno = EMX_EBADF;
	return (-1);
    }
    if (_files[handle] & F_EOF) /* Ctrl-Z reached */
	return (1);
    cur = tell(handle);
    if (cur < 0)
	return (-1);
    len = filelength(handle);
    if (len < 0)
	return (-1);
    return (cur == len);
}

int dj_read(int handle, void *buf, size_t nbyte)
{
    int n;
    size_t j, k;
    char *dst, c;

    if (handle < 0 || handle >= _nfiles) {
	emx_errno = EMX_EBADF;
	return (-1);
    }
    if (nbyte > 0 && (_files[handle] & F_EOF))
	return (0);
    dst = buf;
    n = read_lookahead(handle, dst, nbyte);
    if (n == -1)
	return (-1);
    if ((_files[handle] & O_TEXT) && !(_files[handle] & F_TERMIO) && n > 0) {
	/* special processing for text mode */
	if (!(_files[handle] & (F_NPIPE | F_DEV)) && dst[n - 1] == 0x1a &&
	    eof(handle)) {
	    /* remove last Ctrl-Z in text files */
	    --n;
	    _files[handle] |= F_EOF;
	    if (n == 0)
		return (0);
	}
	if (n == 1 && dst[0] == '\r') {
	    /* This is the tricky case as we are not allowed to decrement n  */
	    /* by one as 0 indicates end of file. We have to use look ahead. */
	    int saved_errno = emx_errno;
	    j = read_lookahead(handle, &c, 1);	/* look ahead */
	    if (j == -1 && emx_errno == EMX_EAGAIN) {
		_lookahead[handle] = dst[0];
		return (-1);
	    }
	    emx_errno = saved_errno;/* hide error */
	    if (j == 1 && c == '\n')    /* CR/LF ? */
		dst[0] = '\n';  /* yes -> replace with LF */
	    else
		_lookahead[handle] = c; /* no -> save the 2nd char */
	} else {
	    if (_crlf(dst, n, &k))	/* CR/LF conversion */
		_lookahead[handle] = '\r';      /* buffer ends with CR */
	    n = k;
	}
    }
    return (n);
}

int dj_setmode(int handle, int mode)
{
    int old_mode;

    if (handle < 0 || handle >= _nfiles) {
	emx_errno = EMX_EBADF;
	return (-1);
    }
    old_mode = ((_files[handle] & O_TEXT) ? O_TEXT : O_BINARY);
    if (mode == O_BINARY)
	_files[handle] &= ~O_TEXT;
    else if (mode == O_TEXT)
	_files[handle] |= O_TEXT;
    else {
	emx_errno = EMX_EINVAL;
	return (-1);
    }
    return (old_mode);
}

int dj_chmod(char *name, int pmode)
{
    WORD attr;

    if (rm_getfattr(name, &attr) == -1)
	return (-1);
    if (pmode & S_IWRITE)
	attr &= ~1;
    else
	attr |= 1;
    if (rm_setfattr(name, attr) == -1)
	return -1;
    return (0);
}

#else

#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>

#define DJO_RDONLY	  0x0001
#define DJO_WRONLY	  0x0002
#define DJO_RDWR	  0x0004
#define DJO_CREAT	  0x0100
#define DJO_TRUNC	  0x0200
#define DJO_EXCL	  0x0400
#define DJO_APPEND	  0x0800
#define DJO_TEXT	  0x4000
#define DJO_BINARY	  0x8000

static int convert_dj_attr(int attr)
{
    int newattr = 0;
    if (attr & DJO_RDONLY) newattr |= O_RDONLY;
    if (attr & DJO_WRONLY) newattr |= O_WRONLY;
    if (attr & DJO_RDWR  ) newattr |= O_RDWR  ;
    if (attr & DJO_CREAT ) newattr |= O_CREAT ;
    if (attr & DJO_TRUNC ) newattr |= O_TRUNC ;
    if (attr & DJO_EXCL  ) newattr |= O_EXCL  ;
    if (attr & DJO_APPEND) newattr |= O_APPEND;
    if (attr & DJO_TEXT  ) newattr |= O_TEXT  ;
    if (attr & DJO_BINARY) newattr |= O_BINARY;
    return newattr;
}

void djio_init(void) {};

void djio_dup_fileattr(int from, int to) {};

int dj_creat(char *buf)
{
    return creat(buf, S_IREAD | S_IWRITE);
}
int dj_open(char *buf, int flags)
{
    return open(buf, convert_dj_attr(flags), S_IREAD | S_IWRITE);
}
int dj_write(int handle, void *buf, size_t nbyte)
{
    return write(handle, buf, nbyte);
}
int dj_read(int handle, void *buf, size_t nbyte)
{
    return read(handle, buf, nbyte);
}
int dj_setmode(int handle, int mode)
{
    return setmode(handle, convert_dj_attr(mode));
}
int dj_chmod(char *name, int pmode)
{
    return chmod(name, pmode);
}
#endif
