/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* stdio.c: ANSI draft (X3J11 Oct 86) library code, section 4.9 */
/* Copyright (C) Codemist Ltd, 1988         */
/* Copyright (C) Acorn Computers Ltd., 1988 */
/* version 0.17e */

/* Incorporate _sys_tmpnam_ idea from WGD */

/* __pos of a file is now a long, and we avoid warnings by two casts        */
/* of this to (int) in the calls to _sys_seek().                            */
/* N.B. I am trying to factor out machine dependence via calls to           */
/* routines like _sys_read_ which can be implemented as _osgbpb or          */
/* NIOP as required.  This file SHOULD therefore be machine independent     */

/* the #include <stdio.h> imports macros getc/putc etc.  Note that we
   must keep the two files in step (more details in ctype.c).
*/

#define __system_io 1      /* makes stdio.h declare more */

#include "hostsys.h"   /* _sys_alloc() etc */
#include <stdio.h>     /* macros for putc, getc, putchar, getchar */
#include <string.h>    /* for memcpy etc */
#include <stddef.h>    /* for size_t etc */
#include <stdlib.h>    /* for free() */
#include <time.h>      /* for time() for tmpfil() */
#include <errno.h>

#include "kernel.h"    /* debug */
#include "hostsys.h"

extern char *_kernel_getmessage(char *msg, char *tag);
extern int _fprintf_lf(FILE *fp, const char *fmt, ...);
extern int _sprintf_lf(char *buff, const char *fmt, ...);

/* HIDDEN EXPORTS */
int __dup(int new, int old);
int _fisatty(FILE *stream);
int __backspace(FILE *stream);  /* strict right inverse of getc() */

#ifndef _SYS_OPEN
/* Temp feature to cope with upgrade path of compiler */
#define _SYS_OPEN SYS_OPEN
#endif

#ifndef EDOM
#define EDOM 1
#endif

#define NO_DEBUG

/* in the shared library world, __iob and _errno are generated by s.clib, */
/* in order to make it easier to keep their position fixed               */

/*FILE __iob[_SYS_OPEN];*/
/* Note that the real name for errno is _errno, and the macro errno is   */
/* defined in errno.h                                                    */
/*volatile int errno;*/

typedef struct __extradata {
  /*
   * BODGE BODGE BODGE BODGE
   * sscanf needs to know the size of this structure, so if you change
   * it, alter sscanf's view of it.
   */
  unsigned char __lilbuf[2]; /* single byte buffer for them that want it  */
                            /* plus an unget char is put in __lilbuf[1]   */
  long _lspos;              /* what __pos should be (set after lazy seek) */
  unsigned char *__extent;   /* extent of writes into the current buffer  */
  int __buflim;              /* used size of buffer                       */
  int __savedicnt;           /* after unget contains old icnt             */
  int __savedocnt;           /* after unget contains old ocnt             */
} _extradata, *_extradatap;

static _extradatap _extra;

#define EXTENT(stream) ((stream)->__extrap->__extent > (stream)->__ptr \
                       ? (stream)->__extrap->__extent : (stream)->__ptr)

/* Explanation of the _IOxxx bits:                                       */
/* IONOWRITES says that the file is positioned at the end of the current */
/* buffer (as when performing sequential reads), while IODIRTIED         */
/* indicates that its position is at the start of the current buffer (as */
/* in the usual case when writing a file). When the relevant bit is not  */
/* set and a transfer is needed _sys_seek() is used to reposition the    */
/* file. Now extra bit _IOSEEK indicating that repositioning of the file */
/* is required before performing I/O                                     */
/* Extra bit _IOLAZY added to indicate that a seek has been requested    */
/* but has been deferred until the next fill or flush buffer             */
/* I use IONOWRITES and IONOREADS to check the restriction               */
/* that read and write operations must be separated by fseek/fflush.     */

/* N.B. bits up to (and including 0xfff) in <stdio.h>, as below:         */
#ifdef never
#define _IOREAD     0x00000001     /* open for input */
#define _IOWRITE    0x00000002     /* open for output */
#define _IOBIN      0x00000004     /* binary stream */
#define _IOSTRG     0x00000008     /* string stream */
#define _IOSEEK     0x00000010     /* physical seek required before IO */
#define _IOLAZY     0x00000020     /* possible seek pending */
#define _IOEOF      0x00000040     /* end-of-file reached */
#define _IOERR      0x00000080     /* error occurred on stream */
#define _IOFBF      0x00000100     /* fully buffered IO */
#define _IOLBF      0x00000200     /* line buffered IO */
#define _IONBF      0x00000400     /* unbuffered IO */
#define _IOSBF      0x00000800     /* system allocated buffer */
#define _IOAPPEND   0x00008000     /* must seek to eof before write */
#endif
#define _IONOWRITES 0x00001000     /* last op was a read                 */
#define _IONOREADS  0x00002000     /* last op was a write                */
#define _IOPEOF     0x00004000     /* 'pending' EOF                      */
#define _IODIRTIED  0x00010000     /* buffer has been written to         */
#define _IOBUFREAD  0x00020000     /* indicates that the buffer being    */
                                   /* written was read from file, so a   */
                                   /* seek to start of buffer is required*/
#define _IOADFSBUG  0x00040000     /* indicates that the underlying      */
                                   /* system buffer was last written to  */
                                   /* and an ensure should be done if    */
                                   /* a seek is about to happen before   */
                                   /* the next read is performed         */
#define _IOUNGET    0x00080000     /* last op was an unget               */
#define _IOSHARED   0x00100000     /* 2 streams share the same file      */
#define _IOUSERFILE 0x00200000     /* set if should be closed by user    */
                                   /* terminateio in a module            */
#define _IOACTIVE   0x00400000     /* some IO operation already performed*/
#define _IODEL      0xad800000     /* for safety check 9 bits            */
#define _IODELMSK   0xff800000

/* first functions for macros in <stdio.h>  */
int (fgetc)(FILE *stream) { return getc(stream); }
int (fputc)(int ch, FILE *stream) { return putc(ch, stream); }
int (getc)(FILE *stream) { return getc(stream); }
int (getchar)() { return getchar(); }
int (putc)(int ch, FILE *stream) { return putc(ch, stream); }
int (putchar)(int ch) { return putchar(ch); }
int (feof)(FILE *stream) { return feof(stream); }
int (ferror)(FILE *stream) { return ferror(stream); }
#define STDOUT stderr
#ifdef DEBUG
#define dbmsg(m, m2) if (stream > STDOUT || stream == stdin) {int last ;int iw; char v[128]; \
        sprintf(v, m, m2); \
        for(iw=0;v[iw];_kernel_oswrch(last=v[iw++])); \
        if (last = 10)_kernel_oswrch(13);}
#define dbmsg_noNL(m, m2) if (stream > STDOUT || stream == stdin) {int last ;int iw; char v[128]; \
        sprintf(v, m, m2); \
        for(iw=0;v[iw];_kernel_oswrch(last=v[iw++]));}
#else
#define dbmsg(m, m2)
#define dbmsg_noNL(m, m2)
#endif
/* put this here too */
void clearerr(FILE *stream)
{   /* we should do more in 'clearerr' resetting __pos __ptr and _cnt      */
    if (!stream) return;
    stream->__flag &= ~(_IOEOF+_IOERR+_IOPEOF);
}

#define seterr(stream) ((stream)->__flag |= _IOERR, \
                        (stream)->__icnt = (stream)->__ocnt = 0)

int setvbuf(FILE *stream, char *buf, int type, size_t size)
{   int flags = stream -> __flag;
    unsigned char *ubuf = (unsigned char *) buf;   /* costs nothing */
    if ((!(flags & _IOREAD+_IOWRITE)) || (flags & _IOACTIVE))
        return 1;             /* failure - not open || already active */
    switch (type)
    {   default: return 1;    /* failure */
        case _IONBF:
            ubuf = stream->__extrap->__lilbuf;
            size = 1;
            break;
        case _IOLBF:
        case _IOFBF:
            if (size-1 >= 0xffffff) return 1;  /* unsigned! */
            break;
    }
    stream->__ptr = stream->__base = ubuf;
    stream->__bufsiz = size;
    stream->__flag = (stream->__flag & ~(_IOSBF+_IOFBF+_IOLBF+_IONBF)) | type;
    return 0;                 /* success */
}

void setbuf(FILE *stream, char *buf)
{
   (void) setvbuf(stream, buf, (buf!=0 ? _IOFBF : _IONBF), BUFSIZ);
}

int __backspace(FILE *stream)
{
    if (!(stream->__flag & _IOEOF) &&
         (stream->__ptr > stream->__base))
    {   ++(stream->__icnt);
        --(stream->__ptr);
        return 0;
    }
    return EOF;
}

int ungetc(int c,FILE *stream)
{   /* made into a fn to evaluate each arg once. */
    if (c==EOF || (stream->__flag & (_IOUNGET+_IONOREADS))) return EOF;
    /* put char into unget buffer */
    stream->__extrap->__lilbuf[1] = c;
    stream->__flag = (stream->__flag | _IOUNGET | _IONOWRITES | _IOACTIVE) & ~(_IOEOF);
    stream->__extrap->__savedocnt = stream->__ocnt;
    stream->__extrap->__savedicnt = stream->__icnt;
    stream->__icnt = stream->__ocnt = 0;
    return c;
}

static int _writebuf(unsigned char *buf, int len, FILE *stream)
{   int w;
    FILEHANDLE fh = stream->__file;
    int flag = stream->__flag;
    if (flag & _IOSHARED) /* this is really gross */
    {   flag |= _IOSEEK;
        stream->__pos = _sys_flen(fh);
dbmsg("_IOSHARED so zoom to end %d\n", (int)stream->__pos);
    }
    if (flag & _IOSEEK+_IOBUFREAD)
    {
dbmsg("_writebuf seeking to %d\n", (int)stream->__pos);
        if (_sys_seek(fh, (int)stream->__pos) < 0)
        {   seterr(stream);
            return EOF;
        }
        stream->__flag = (flag &= ~(_IOSEEK+_IOBUFREAD));
    }
dbmsg_noNL("_writebuf pop goes the stoat %i, ", fh);
dbmsg_noNL("%X, ", buf);
dbmsg_noNL("%d, ", len);
dbmsg("%X\n", flag);
    w = _sys_write(fh, buf, len, flag);
    stream->__flag |= _IOADFSBUG;
dbmsg("_sys_write_ returned %d\n", w);
    stream->__pos += len - (w & 0x7fffffffL);
    if (w!=0)    /* AM: was (w<0) but trap unwritten chars as error too */
    {   seterr(stream);
        return(EOF);
    }
dbmsg("filelen = %d\n",_sys_flen(fh));           /* length of this file      */
    return 0;
}

static int _fflush(FILE *stream)
{
    unsigned char *buff = stream->__base;
    unsigned char *extent = EXTENT(stream);
dbmsg("%s\n", "_fflush");
    stream->__flag &= ~_IOUNGET;
    if ((stream->__flag & _IOREAD+_IOWRITE) == _IOREAD) return 0;
    if ((stream->__flag & _IOERR+_IOWRITE) != _IOWRITE) return EOF;
    /* N.B. really more to do here for ANSI input stream */
    if (stream->__flag & _IODIRTIED)
    {   /* only write if dirty buffer - this avoids problems with
           writing to a file opened in append (or read+write) mode
           when only input has been done since the last fflush/fseek.
        */
dbmsg("%s\n", "dirty buffer");
        if (extent != buff)       /* something to do */
        {
dbmsg("%s\n", "call _writebuf");
          if (_writebuf(buff, extent - buff, stream)) return EOF;
        }
        stream->__ptr = stream->__extrap->__extent = extent = buff;
/* the next line forces a call to __filbuf/__flsbuf on next putc/getc -  */
/* this is necessary since change of direction may happen.               */
        stream->__ocnt = 0;
        stream->__flag &= ~_IODIRTIED;
dbmsg("%s\n", "clear IODIRTIED");
    }
else dbmsg("%s\n", "not a dirty buffer");

/* now do quick frig to fix I/O streams: essentially fseek(s, 0, SEEK_CUR).
    if ((stream->__flag & (_IOREAD+_IOWRITE)) == (_IOREAD+_IOWRITE))
    {
      stream->__flag = stream->__flag & ~(_IOEOF|_IONOWRITES|_IOREADS|_IOPEOF)
                                                                   |_IOSEEK;
      stream->__icnt = stream->__ocnt = 0;
      stream->__pos += (extent - buff);
      stream->__ptr = buff;
    }
*/

    return 0;
}

static void _deferredlazyseek(FILE *stream)
{
dbmsg("deferredlazyseek to %d\n", (int)stream->__extrap->_lspos);
    /* only here because of a seek */
    stream->__flag &= ~_IOLAZY;
    if (stream->__pos != stream->__extrap->_lspos) {
      _fflush(stream);
      /* clear EOF condition */
dbmsg("%s\n", "clear IODIRTIED");
      stream->__flag = stream->__flag & ~(_IONOWRITES | _IONOREADS) | _IOSEEK;
      stream->__pos = stream->__extrap->_lspos;
      stream->__ptr = stream->__extrap->__extent = stream->__base;
    }
else dbmsg("%s\n", ".....already there");
    stream->__flag &= ~(_IOEOF | _IOPEOF);
}

extern int __flsbuf(int ch, FILE *stream)
{   int flag;
dbmsg_noNL("%s ", "__flsbuf");
dbmsg("%c\n", ch);
    stream->__flag = (stream->__flag & ~_IOUNGET) | _IOACTIVE;

    if (stream->__flag & _IOLAZY) _deferredlazyseek(stream);

    if ((stream->__ocnt < -1) && !(stream->__flag & _IOLBF)) {
      /* buffer ~empty, sequence of events that lead to here are :          */
      /* put seek get seek put                                              */
dbmsg("flushbuf negative ocnt = %d\n", stream->__ocnt);
      stream->__ocnt = (-stream->__ocnt) - 2;
      stream->__flag |= (_IODIRTIED+_IONOREADS);           /* we are writing */
      return *(stream->__ptr)++ = (ch);
    }

    flag = stream->__flag;
    if ((flag & _IOERR+_IOSTRG+_IOWRITE+_IONOWRITES) != _IOWRITE)
    {   seterr(stream);
dbmsg("%s\n", "!= _IOWRITE");
        return EOF;
    }
/* the next conditional code is ACN's view of that APPEND means seek to     */
/* EOF after EVERY fflush, not just initially.  Hmm, ANSI really should     */
/* clarify - the problem is perhaps that we wish to seek to EOF after       */
/* fflush after read, but not after fflush after write?                     */
/* The line "if ((flag & (_IOSEEK+_IOAPPEND)) == (_IOSEEK+_IOAPPEND))"      */
/* which PH10 suggested does not work, try the sequence:                    */
/* create file with something in it; close file; open the file (append mode)*/
/* seek to start; write a char; THE CHAR WRITTEN IS NOT WRITTEN TO EOF      */
/* The following line has been reinstated cos it works.                     */
    if ((flag & (_IONOREADS+_IOSEEK+_IOAPPEND)) == _IOAPPEND)
/* Will somebody please help ACN remember/understand what was going on here!*/
    {   /* first write to APPEND file after FFLUSH, but not FSEEK nor       */
        /* fopen (does its own FSEEK)                                       */
        fseek(stream, 0L, SEEK_END);
        if (stream->__flag & _IOLAZY) _deferredlazyseek(stream);
        flag = stream->__flag;
    }
dbmsg("%s\n", "set IODIRTIED");
    stream->__flag = (flag |= (_IODIRTIED+_IONOREADS));    /* we are writing */
    if (stream->__base == NULL)
    {   if (_sys_istty(stream)) {         /* terminal - unbuffered  */
            stream->__ptr = stream->__base = stream->__extrap->__lilbuf;
            stream->__bufsiz = 1;
            stream->__flag = (flag |= _IONBF);
        } else {
            /* allocate default system buffer */
            stream->__ptr = stream->__base = _sys_alloc(stream->__bufsiz);
            stream->__flag |= (flag |= _IOSBF);
            if ((flag & _IOLBF+_IOFBF) == 0) stream->__flag |= (flag |= _IOFBF);
        }
    }
    if (flag & _IOFBF)               /* system or user buffer */
    {   unsigned char *buff = stream->__base;
        int count = EXTENT(stream) - buff;
        if (count != 0) {
            if (_writebuf(buff, count, stream)) return EOF;
        }
        stream->__ptr = stream->__extrap->__extent = buff+1;
        stream->__ocnt = stream->__bufsiz - 1;
        stream->__extrap->__buflim = stream->__bufsiz;
        return (*buff = ch);
    }
    else     /* no buffer (i.e. 1 char private one) or line buffer */
    {   unsigned char *buff = stream->__base;
        int count;
        *stream->__ptr++ = ch;   /* always room */
        count = EXTENT(stream) - buff;
        stream->__extrap->__buflim = stream->__bufsiz;
        if ((flag & _IONBF) ||
               (unsigned char)ch == '\n' || count >= stream->__bufsiz)
        {   stream->__ptr = stream->__extrap->__extent = buff;
            stream->__ocnt = 0;                 /* 2^31 is big but finite */
            return _writebuf(buff, count, stream) ? EOF : (unsigned char)ch;
        }
        return (unsigned char)ch;
    }
}

extern int __filbuf(FILE *stream)
{   int w;
    unsigned char *buff;
    FILEHANDLE fh;
    int request;
dbmsg("%s\n", "__filbuf");
    stream->__flag |= _IOACTIVE;
    if (stream->__flag & _IOUNGET) {
      stream->__icnt = stream->__extrap->__savedicnt;
      stream->__ocnt = stream->__extrap->__savedocnt;
      stream->__flag &= ~_IOUNGET;
      return stream->__extrap->__lilbuf[1];
    }

    if (stream->__flag & _IOLAZY) _deferredlazyseek(stream);

    /* note that sscanf (q.v.) requires this next line to yield EOF */
    if ((stream->__flag & (_IOEOF+_IOERR+_IOSTRG+_IOREAD+_IOPEOF+_IONOREADS))
                                                                    != _IOREAD)
    {   stream->__icnt = 0;                      /* 2^31 is big but finite */
        if (stream->__flag & _IOEOF+_IOPEOF+_IOSTRG)
            /* writing ok after EOF read according to ansi */
            stream->__flag = stream->__flag & ~(_IONOWRITES+_IOPEOF) | _IOEOF;
        else seterr(stream);
        return(EOF);
    }

    if ((stream->__flag & _IOSBF+_IONBF+_IODIRTIED) == 0)
    if ((stream->__base == NULL) && ((stream->__flag & _IODIRTIED) == 0))
    {
            /* allocate default system buffer */
            stream->__ptr = stream->__base = _sys_alloc(stream->__bufsiz);
            stream->__flag |= _IOSBF;
            if ((stream->__flag & _IOLBF+_IOFBF) == 0) stream->__flag |= _IOFBF;
    }

    if (stream->__icnt < -1) {
      /* buffer ~empty, came here cos seek wanted to to tidy up flags etc */
dbmsg("fillbuf negative icnt = %d\n", stream->__icnt);
      stream->__icnt = (-stream->__icnt) - 2;
      stream->__flag |= _IONOWRITES;           /* we are reading */
      return *(stream->__ptr)++;
    }

    fh = stream->__file;
    if (stream->__flag & _IOSEEK) {
      if (stream->__flag & _IODIRTIED) _fflush(stream);
      else {
dbmsg("fillbuf seeking to %d\n", (int)stream->__pos);
        if (stream->__flag & _IOADFSBUG) {
          _sys_ensure(fh);
          stream->__flag &= ~_IOADFSBUG;
        }
        if (_sys_seek(fh, (int)stream->__pos) < 0)
        {   seterr(stream);
            return EOF;
        }
      }
    }

    stream->__flag |= _IONOWRITES;           /* we are reading */

    if (stream->__flag & _IODIRTIED) {
      int extent = (int) (EXTENT(stream) - stream->__base);
      request = stream->__bufsiz - extent;
      if (request == 0) {
        _fflush(stream);
        request = stream->__bufsiz;
      } else {
dbmsg("fillbuf flag %X\n", stream->__flag);
        if ((stream->__flag & (_IOBUFREAD+_IOSEEK)) == 0) {
dbmsg("fillbuf dirty buffer, read into end,seeking to %d\n",(int) stream->__pos+extent);
          if (stream->__flag & _IOADFSBUG) {
            _sys_ensure(fh);
            stream->__flag &= ~_IOADFSBUG;
          }
          if (_sys_seek(fh, (int) stream->__pos + extent) < 0)
          {   seterr(stream);
              return EOF;
          } else stream->__flag |= _IOBUFREAD;
        }
      }
      buff = stream->__base + (stream->__bufsiz - request);
dbmsg_noNL("fillbuf not splatting out writ part of file request %d ", request);
dbmsg("at buff %X\n", (int)buff);
    } else {
      request = stream->__bufsiz;
      buff = stream->__base;
      stream->__pos += stream->__ptr - buff;     /* add buf size for ftell() */
    }
    stream->__flag &= ~_IOSEEK;


dbmsg("READING FROM FILE REQUEST = %d\n", request);
dbmsg("filelen = %d\n",_sys_flen(fh));             /* length of this file */
    stream->__icnt = 0;
    w = _sys_read(fh, buff, request, stream->__flag);
    stream->__flag &= ~_IOADFSBUG;
dbmsg("_sys_read_ returned %d\n", w);
    if (w<0) {
      if (w == _kernel_ERROR) { seterr(stream); return EOF; }
      /* this deals with operating systems with 'early' eof */
      stream->__flag |= _IOPEOF;
      w = w & 0x7fffffff;
    }
    w = request - w;
dbmsg("read %d bytes\n", w);
    stream->__extrap->__extent = buff + w;
    stream->__extrap->__buflim = stream->__bufsiz;
    if (w==0)    /* this deals with operating systems with 'late' eof  */
    {   stream->__flag |= _IOEOF;                /* is this case independent? */
        stream->__flag &= ~_IONOWRITES;          /* writing OK after EOF read */
        stream->__icnt = 0;
        stream->__ptr = buff;  /* just for fun - NB affects ftell() - check */
        return(EOF);
    }
    else
    {   stream->__icnt = w-1;
        stream->__ptr = buff+1;
        stream->__flag |= _IOBUFREAD;
        return(buff[0]);
    }
}

static int _fillb2(FILE *stream)
{   if (__filbuf(stream) == EOF) return EOF;
    stream->__icnt++;
    stream->__ptr--;
    return 0;
}

int fclose(FILE *stream)
{   /* MUST be callable on a closed file - if stream clr then no-op. */
    FILEHANDLE fh;
    unsigned char *buff;
    int flag;
    _extradatap extrap;
    int res = 0;

    /* Deal with programs calling fclose(0), ECN 21-09-93 */
    if (!stream) return EOF;
    fh = stream->__file;
    buff = stream->__base;
    flag = stream->__flag;
    extrap = stream->__extrap;
    if (!(flag & _IOREAD+_IOWRITE)) return EOF;   /* already closed    */
    if (!(flag & _IOSTRG))                    /* from _fopen_string    */
    {   int fd = _SYS_OPEN;
        res = fflush(stream);
        if (flag & _IOSHARED)
        {   for (fd =  0;  fd < _SYS_OPEN;  ++fd)
            {   FILE *f = &__iob[fd];
                if ((f != stream) &&
                    (f->__flag & _IOREAD+_IOWRITE) &&     /* f is open */
                    (f->__file == fh) && (f->__flag & _IOSHARED))
                {   f->__flag &= ~_IOSHARED;       /* no longer shared */
                    stream->__flag &= ~_IOSHARED;
                    break;
                }
            }
        }
        /* Assert: fd != SYS_OPEN => fh is shared and mustn't be closed */
        if (fd == _SYS_OPEN &&
            _sys_close(fh) < 0) res = EOF;          /* close real file */
        if (flag & _IOSBF) free(buff);         /* free buffer if system */
        if ((flag & _IODELMSK) == _IODEL)
        {   char name[L_tmpnam];
            _sys_tmpnam_(name, stream->__signature);
            if (remove(name) != 0) res = EOF; /* delete the file if pos */
        }
    }
    memclr(stream->__extrap, sizeof(_extradata));
    memclr(stream, sizeof(FILE));
    stream->__extrap = extrap;
    return res;
}

FILE *freopen(const char *name, const char *mode, FILE *iob)
{
/* The use of modes "r+", "w+" and "a+" is not fully thought out   */
/* yet, in that calls to __flsbuf may write back stuff that was    */
/* loaded by __filbuf and thereby corrupt the file.                */
/* This is now just about fixed given the ANSI restriction that    */
/* calls to getc/putc must be fflush/fseek separated.              */
    FILEHANDLE fh;
    int flag, openmode;        /* nasty magic numbers for openmode */
    fclose(iob);
    switch (*mode++)
    {   default:  return(NULL);               /* mode is incorrect */
        case 'r': flag = _IOREAD;  openmode = OPEN_R; break;
        case 'w': flag = _IOWRITE; openmode = OPEN_W; break;
        case 'a': flag = _IOWRITE | _IOAPPEND;
                                   openmode = OPEN_A; break;
    }
    for (;;)
    {   switch (*mode++)
        {
    case '+':   flag |= _IOREAD+_IOWRITE, openmode |= OPEN_PLUS;
                continue;
    case 'b':   flag |= _IOBIN, openmode |= OPEN_B;
                continue;
        }
        if (*(mode-1) == 't') openmode |= OPEN_T;
        break;
    }
    if ((fh = _sys_open(name, openmode)) == NONHANDLE) return NULL;
    if (_kernel_client_is_module()) flag |= _IOUSERFILE;
    iob->__ptr = iob->__base = NULL; iob->__bufsiz = BUFSIZ;
    iob->__flag = flag;
    iob->__file = fh;
    if (openmode & OPEN_A) fseek(iob, 0L, SEEK_END);  /* a or a+             */
    return iob;
}

FILE *fopen(const char *name, const char *mode)
{   int i;
    for (i=3; i<_SYS_OPEN; i++)
    {   FILE *stream = &__iob[i];
        if (!(stream->__flag & _IOREAD+_IOWRITE))  /* if not open then try it */
            return (freopen(name, mode, stream));
    }
    return 0;   /* no more i/o channels allowed for */
}

FILEHANDLE __dup(int new, int old)
{   FILE *s_new, *s_old;
    (void) fclose(&__iob[new]);
    s_new = &__iob[new];
    s_old = &__iob[old];
    s_old->__flag |= _IOSHARED;
    s_new->__flag = s_old->__flag & (_IOREAD+_IOWRITE+_IOAPPEND+_IOSHARED+
                                              _IOSBF+_IOFBF+_IOLBF+_IONBF);
    s_new->__file = s_old->__file;
    return s_new->__file;
}

FILE *_fopen_string_file(const char *data, int length)
{
/* open a file that will read data from the given string argument        */
/* The declaration of this function in #include "hostsys.h" suggests     */
/* that this function is of type (void *), so this definition will lead  */
/* to a warning message.                                                 */
    int i;
    for (i=3; i<_SYS_OPEN; i++)
    {   FILE *stream = &__iob[i];
        if (!(stream->__flag & _IOREAD+_IOWRITE))  /* if not open then try it */
        {
            fclose(stream);
            stream->__flag = _IOSTRG+_IOREAD+_IOACTIVE;
            stream->__ptr = stream->__base = (unsigned char *)data;
            stream->__icnt = length;
            return stream;
        }
    }
    return 0;   /* no more i/o channels allowed for */
}

int _fisatty(FILE *stream)  /* not in ANSI, but related needed for ML */
{   if ((stream->__flag & _IOREAD) && _sys_istty(stream)) return 1;
    return 0;
}

/* initialisation/termination code... */


void _initio(char *f1,char *f2,char *f3)
{
    int i;
    char v[128];
    memclr(__iob, _SYS_OPEN*sizeof(FILE));
/*  _extra = calloc(_SYS_OPEN, sizeof(_extradata)); */
    _extra = _sys_alloc(_SYS_OPEN * sizeof(_extradata));
    memclr(_extra, _SYS_OPEN * sizeof(_extradata));
    for (i=0; i<_SYS_OPEN; i++) __iob[i].__extrap = _extra+i;
    /* In the next lines DO NOT use standard I/O for error msgs (not open yet)
       Moreover, open in this order so we do not create/overwrite output if
       input does not exist. */
    if (freopen(f3, "w", stderr) == 0)
        _sprintf_lf(v,_kernel_getmessage("Couldn't write %s", "C46"), f3), _sys_msg(v), exit(1);
    if (freopen(f1, "r", stdin) == 0)
        _sprintf_lf(v,_kernel_getmessage("Couldn't read %s", "C47"), f1), _sys_msg(v), exit(1);
    if (freopen(f2, "w", stdout) == 0)
        _sprintf_lf(v,_kernel_getmessage("Couldn't write %s", "C46"), f2), _sys_msg(v), exit(1);
}

void _terminateio()
{   int i;
    int closeall = !_kernel_client_is_module();
    for (i=3; i<_SYS_OPEN; i++) {
        FILE *f = &__iob[i];
        if (closeall || (f->__flag & _IOUSERFILE)) fclose(f);
    }
    /* for cowardice do stdin, stdout, stderr last (in that order) */
    for (i=0; i<3; i++) {
        FILE *f = &__iob[i];
        if (closeall || (f->__flag & _IOUSERFILE)) {
            fclose(f);
            if (!closeall) freopen(TTYFILENAME, (f == stdin ? "r" : "w"), f);
        }
    }
    if (_extra) {
        free(_extra);
        _extra = 0;
    }
}


/* now the less machine dependent functions ... */

char *fgets(char *s, int n, FILE *stream)
{   char *a = s;
    if (n <= 1) return NULL;                  /* best of a bad deal */
    do { int ch = getc(stream);
         if (ch == EOF)                       /* error or EOF       */
         {   if (s == a) return NULL;         /* no chars -> leave  */
             if (ferror(stdin)) a = NULL;
             break; /* add NULL even if ferror(), spec says 'indeterminate' */
         }
         if ((*s++ = ch) == '\n') break;
       }
       while (--n > 1);
    *s = 0;
    return a;
}

char *gets(char *s)
{   char *a = s;
    for (;;)
    {    int ch = getc(stdin);
         if (ch == EOF)                       /* error or EOF       */
         {   if (s == a) return NULL;         /* no chars -> leave  */
             if (ferror(stdin)) a = NULL;
             break; /* add NULL even if ferror(), spec says 'indeterminate' */
         }
         if (ch == '\n') break;
         *s++ = ch;
    }
    *s = 0;
    return a;
}

int fputs(const char *s, FILE *stream)
{
    char c;
    while((c = *s++) != 0)
        if(putc(c, stream)==EOF) return(EOF);
    return(0);
}

int puts(const char *s)
{
    char c;
    while ((c = *s++) != 0)
       if (putchar(c) == EOF) return EOF;
    return putchar('\n');
}

/* _read improved to use __filbuf and block moves.  Optimisation
   to memcpy too if word move.  Still possible improvments avoiding copy
   but I don't want to do these yet because of interactions
   (e.g. __pos of a file).   N.B.  _read is not far from unix 'read' */
static int _read(char *ptr, int nbytes, FILE *stream)
{   int i = nbytes;
dbmsg("%s\n", "_read");
    if (i == 0) return 0;
    if (stream->__flag & _IOUNGET) {
      *ptr++ = __filbuf(stream); i--;
    }
    if (stream->__flag & _IOLAZY) _deferredlazyseek(stream);
    do
    {
dbmsg("in _read, icnt = %d\n", stream->__icnt);
        if (i <= stream->__icnt)
        {   memcpy(ptr, stream->__ptr, i);
            stream->__icnt -= i; stream->__ptr += i;
            return nbytes;
        }
        else if (stream->__icnt >= 0)
        {   memcpy(ptr, stream->__ptr, stream->__icnt);
            ptr += stream->__icnt, i -= stream->__icnt;
            stream->__ptr += stream->__icnt, stream->__icnt = -1; /* for __pos */
        }
        else stream->__icnt--;
    } while (_fillb2(stream) != EOF);
    return nbytes-i;
/*
    for (i=0; i<nbytes; i++)
    {   if ((ch = getc(stream)) == EOF) return i;
        *ptr++ = ch;
    }
    return nbytes;
*/
}

size_t fread(void *ptr, size_t itemsize, size_t count, FILE *stream)
{    /* ANSI spec says EOF and ERR treated the same as far as fread
      * is concerned and that the number of WHOLE items read is returned.
      */
dbmsg("fread %d\n", count);
    return itemsize == 0 ? 0   /* slight ansi irrationality */
                         : _read(ptr, itemsize*count, stream) / itemsize;
}

static int _write(const char *ptr, int nbytes, FILE *stream)
{   int i;
    for(i=0; i<nbytes; i++)
        if (putc(*ptr++, stream) == EOF) return 0;
        /* H&S say 0 on error */
    return nbytes;
}

size_t fwrite(const void *ptr, size_t itemsize, size_t count, FILE *stream)
{
/* The comments made about fread apply here too */
dbmsg("fwrite %d\n", count);
    return itemsize == 0 ? count
                         : _write(ptr, itemsize*count, stream) / itemsize;
}

/* back to machine dependent functions */

#define _ftell(stream) (  (stream)->__flag & _IOLAZY \
                        ? (stream)->__extrap->_lspos \
                        : (stream)->__pos + (stream)->__ptr - (stream)->__base)

long int ftell(FILE *stream)
{  long pos;
   if (!(stream->__flag & _IOREAD+_IOWRITE))     /* already closed        */
   { errno = EDOM;
     return -1L;
   }
   pos = _ftell(stream);
   if (stream->__flag & _IOUNGET && pos > 0) --pos;
   return pos;
}

/* The treatment of files that can be written to seems complicated in fseek */

int fseek(FILE *stream, long int offset, int whence)
{
    FILEHANDLE fh = stream->__file;
    int flag = stream->__flag;
dbmsg_noNL("%s ", "SEEK ENTRY");

    if (!(flag & _IOREAD+_IOWRITE+_IOSHARED) || _sys_istty(stream))
        return(2);                              /* fseek impossible  */

    switch(whence)
    {
case SEEK_SET:
        break;                                  /* relative to file start */
case SEEK_CUR:
        offset += ftell(stream);                /* relative seek */
        break;
case SEEK_END:
        {   long int filelen, filepos;
            filelen = _sys_flen(fh);           /* length of this file      */
dbmsg("filelen in seek = %d\n", (int)filelen);
            if (filelen<0)                      /* failed to read length    */
            {   seterr(stream);
                return 1;
            }
            filepos = stream->__pos + EXTENT(stream) - stream->__base;
            if (stream->__flag & _IOLAZY && filepos < stream->__extrap->_lspos)
              filepos = stream->__extrap->_lspos;
            if (filepos>filelen)                /* only possible on write   */
                filelen = filepos;              /* allow for stuff buffered */
            offset += filelen;                  /* relative to end of file  */
        }
        break;
default:
        seterr(stream);
        return(2);                              /* illegal operation code   */
    }

    if (offset < 0) { seterr(stream); return 2; } /* fseek impossible  */

    if ((flag & _IONOREADS) && stream->__extrap->__extent < stream->__ptr)
      stream->__extrap->__extent = stream->__ptr;

dbmsg_noNL("%s ", "SEEK");
dbmsg_noNL("__pos %d", (int)stream->__pos);
dbmsg_noNL(" offset %d", (int)offset);
dbmsg_noNL(" buflim %d", stream->__extrap->__buflim);
dbmsg_noNL(" __ptr %X", (int)stream->__ptr);
dbmsg_noNL(" __icnt %d", stream->__icnt);
dbmsg_noNL(" __ocnt %d", stream->__ocnt);
dbmsg_noNL(" __base %X", (int)stream->__base);

    if (offset < stream->__pos ||
      offset > stream->__pos + EXTENT(stream) - stream->__base ||
      offset >= stream->__pos + stream->__extrap->__buflim)

      { /* outside buffer */
dbmsg("%s\n", " outside buffer");
      flag |= _IOLAZY;
      stream->__icnt = stream->__ocnt = 0;
      stream->__extrap->_lspos = offset;
    } else { /* inside buffer */
dbmsg("%s\n", " inside buffer");
      offset -= stream->__pos;
      if (flag & _IOWRITE)
        stream->__ocnt = -(stream->__extrap->__buflim - (int)offset);
      if (flag & _IOREAD)
        stream->__icnt = -((int)(EXTENT(stream) - stream->__base) - (int)offset);
      stream->__ptr = stream->__base + offset;
      flag &= ~_IOLAZY;
    }
dbmsg_noNL("AFTER SEEK __ptr %X", (int)stream->__ptr);
dbmsg_noNL(" __icnt %d", stream->__icnt);
dbmsg(" __ocnt %d\n", stream->__ocnt);
    stream->__flag = flag & ~(_IOEOF+_IONOWRITES+_IONOREADS+_IOUNGET);

    return 0;
}

static int _do_fflush(FILE *stream)
{ /* ignore the effect of a previous unget on the file position indicator by
     using _ftell rather than ftell */
  if (stream->__flag & _IOREAD+_IOWRITE)   /* not open */
  { long offset = _ftell(stream);
    int res;
dbmsg("%s\n", "fflush");
    if (stream->__flag & _IOLAZY) _deferredlazyseek(stream);
    stream->__flag &= ~(_IONOREADS+_IONOWRITES);
    res =_fflush(stream);
    fseek(stream, offset, SEEK_SET);
    return res;
  } else return 0;
}

int fflush(FILE *stream)
{ /* new definition (ANSI May 88) says that if stream == NULL, apply fflush to
     all applicable streams */
   int res = 0;
   if (stream != NULL)
     res =_do_fflush(stream);
   else
   { int i;
     for (i=0; i<_SYS_OPEN; i++)
       if (_do_fflush(&__iob[i]) != 0) res = EOF;
   }
   return res;
}

void rewind(FILE *stream)
{
   fseek(stream, 0L, SEEK_SET);
   clearerr(stream);
}

/* the following routines need to become the main entry I suppose          */
int fgetpos(FILE *stream, fpos_t *pos)
{  pos->__lo = ftell(stream);
   return 0;
}

int fsetpos(FILE *stream, const fpos_t *pos)
{  int res = fseek(stream, pos->__lo, SEEK_SET);
   if (res) errno = EDOM;
   return res;
}

static char _tmp_file_name[L_tmpnam] = "";
static int _tmp_file_sig;

char *tmpnam(char *a)
{
/* Obtaining an unique name is tolerably nasty - what I do here is       */
/* derive the name (via _sys_tmpnam_())                                  */
/* from an integer that is constructed out of a serial number combined   */
/* with the current clock setting. An effect of this is that the file    */
/* name can be reconstructed from a 32-bit integer for when I want to    */
/* delete the file. The serial number is held in zero page and is        */
/* returned and incremented by __counter(). This ensures uniqueness      */
/* if tmpnam is called from two applications within the same second.     */
    _kernel_osfile_block fb;

    if (a==NULL) a = _tmp_file_name;
    do {
      _tmp_file_sig = ((int)time(NULL) << 8) | __counter();
      _sys_tmpnam_(a, _tmp_file_sig);
    } while (_kernel_osfile(17, a, &fb) != 0);

    return a;
}

char *__old_tmpnam(char *a)
{
    return tmpnam(a);
}

FILE *tmpfile()
{
    char name[L_tmpnam];
    FILE *f;
    f = fopen(tmpnam(name), "w+b");
    if (f)
    {   f->__flag |= _IODEL;
        f->__signature = _tmp_file_sig;
    }
    return f;
}

void perror(const char *s)
{   char b[256];
    if (s != 0 && *s != 0) fprintf(stderr, "%s: ", s);
    fprintf(stderr, "%s\n", _strerror(errno, b));
}

/* end of stdio.c */