/*****************************************************************************
* JLWilkinson 21-Sep-1995  Use DCLsystem() for client, VMS$system() for server.
* JLWilkinson 25-Sep-1995  Use __VMS instead of VMS as trigger for VMS items
* F.Macrides 12-May-1995   Deal with UCX's funny EPIPE return value on EOF
*                           immediately after each socket_read().
* F.Macrides 03-Mar-1995   Ensure that file is closed on fstat() error in
*                           FIOopenUFS().
*/
/********************************************************************
* lindner
* 3.33
* 1995/01/02 21:09:46
* /home/arcwelder/GopherSrc/CVS/gopher+/object/fileio.c,v
* Exp
*
* Paul Lindner, University of Minnesota CIS.
*
* Copyright 1991, 92, 93, 94 by the Regents of the University of Minnesota
* see the file "Copyright" in the distribution for conditions of use.
*********************************************************************
* MODULE: fileio.c
* Socket/file input output routines.
*********************************************************************
* Revision History:
* fileio.c,v
* Revision 3.33  1995/01/02  21:09:46  lindner
* Fix for UCX menu ordering
*
* Revision 3.32  1994/12/20  17:09:46  lindner
* Fix for binary file retrievals
*
* Revision 3.31  1994/12/03  02:14:13  lindner
* Correct fix for waitpid() code
*
* Revision 3.30  1994/12/02  00:37:05  lindner
* Fix for mail and misc process execution
*
* Revision 3.29  1994/12/01  00:30:13  lindner
* Remove EINTR wait for FIOwaitpid.. sigh..
*
* Revision 3.28  1994/11/29  06:30:04  lindner
* Make waitpid keep going after an interrupted system call
*
* Revision 3.27  1994/11/29  04:59:38  lindner
* Fix for broken shell scripts with Shell Index type
*
* Revision 3.26  1994/10/21  02:42:47  lindner
* fix for 0 length files
*
* Revision 3.25  1994/10/19  03:33:57  lindner
* Fix nasty bug, combine more code...
*
* Revision 3.24  1994/10/13  05:26:16  lindner
* Compiler complaint fixes
*
* Revision 3.23  1994/09/29  19:54:57  lindner
* Routines to use memory mapped I/O
*
* Revision 3.22  1994/07/21  22:19:37  lindner
* Add filename new and change debug msg
*
* Revision 3.21  1994/06/29  06:43:04  lindner
* Remove VMS message about reversed values, initialize filename
*
* Revision 3.20  1994/05/25  20:57:20  lindner
* Fix for piped play commands
*
* Revision 3.19  1994/04/25  03:44:42  lindner
* Don't block empty FIOsystem() command on VMS.  That's what's used for
* the '!' or '$' commands, and they've already be checked for permission
* in Gopher.c.
*
* Reversed DCLsystem() return values in CURCurses.c, and eliminated the
* reversing kludge in FIOsystem().
*
* Fixed break of open() for Alpha.  (all From F.Macrides)
*
* Revision 3.18  1994/04/25  03:37:00  lindner
* Modifications for Debug() and mismatched NULL arguments, added Debugmsg
*
* Revision 3.17  1994/04/25  02:18:46  lindner
* Fix for gcc-Wall
*
* Revision 3.16  1994/04/14  18:14:49  lindner
* Fix for closing null files
*
* Revision 3.15  1994/04/14  15:44:55  lindner
* Remove duplicate variable
*
* Revision 3.14  1994/04/08  21:07:34  lindner
* Put back a variable
*
* Revision 3.13  1994/04/08  20:05:57  lindner
* gcc -Wall fixes
*
* Revision 3.12  1994/04/08  19:15:47  lindner
* Fix for old union wait stuff
*
* Revision 3.11  1994/04/07  22:51:24  lindner
* Fix for typecast
*
* Revision 3.10  1994/04/01  04:43:31  lindner
* Fixes for memory leak in argv, and exit status
*
* Revision 3.9  1994/03/30  21:37:48  lindner
* Fix for AIX that doesn't #define unix..
*
* Revision 3.8  1994/03/11  00:10:52  lindner
* Fix for FIOopenUFS()
*
* Revision 3.7  1994/03/09  16:57:56  lindner
* Fix for other vms system() call
*
* Revision 3.6  1994/03/09  02:11:49  lindner
* Use DCLsystem for VMS
*
* Revision 3.5  1994/03/08  17:23:17  lindner
* Fix for return vals
*
* Revision 3.4  1994/03/08  06:17:15  lindner
* Nuke compiler warnings
*
* Revision 3.3  1994/03/08  03:22:39  lindner
* Additions for process management
*
* Revision 3.2  1994/03/04  17:43:36  lindner
* Fix for weird error
*
* Revision 3.1  1994/02/20  16:20:48  lindner
* New object based versions of buffered io routines
*
*
*********************************************************************/

#include "fileio.h"
#include "Malloc.h"

#include <errno.h>
#include "Debug.h"
#include "Wait.h"
#include "String.h"
#include "Stdlib.h"

#ifdef __VMS
#  include <stat.h>
#else
#  include <sys/stat.h>
#endif

#ifdef MMAP_IO
#  include <sys/mman.h>
#  ifndef MAP_FILE
#    define MAP_FILE 0
#  endif
#endif

/*
* Cache of used fios
*/

static FileIO* FIOusedfios[FIOMAXOFILES];
static int  FIOnumusedfios = -1;

/*
* Pop a recently used item.
*/

static FileIO *FIOpopUsedfio()
{
    if (FIOnumusedfios > 0) {
         return(FIOusedfios[--FIOnumusedfios]);
    } else
         return(NULL);
}

/*
* Push an item on our recently used stack.
*/

static boolean FIOpushUsedfio(oldfio)
 FileIO *oldfio;
{
    if (FIOnumusedfios < FIOMAXOFILES) {
         FIOusedfios[FIOnumusedfios++] = oldfio;
         return(0);
    } else
         return(-1);
}

static FileIO *
FIOnew()
{
    FileIO *temp;

    if (FIOnumusedfios == -1) {
         int i;
         /* Initialize the usedfios struct the first time through */
         for (i=0; i < FIOMAXOFILES; i++) {
              FIOusedfios[i] = NULL;
         }
         FIOnumusedfios=0;
    }

    temp = FIOpopUsedfio();
    if (temp == NULL) {
         temp = (FileIO *) malloc(sizeof(FileIO));
         temp->filename = STRnew();
    } else {
         STRinit(temp->filename);
    }


    temp->issocket = -1;
    temp->pid = -1;
    temp->fd = (pid_t) -1;
    temp->buf = NULL;

    temp->bufindex = -1;
    temp->bufdatasize = -1;
    temp->filename = STRnew();


    return(temp);
}


/*
* Should only be called by FIOclose* functions really..
*/

void
FIOdestroy(fio)
 FileIO *fio;
{
    if (FIOpushUsedfio(fio)) {
         /** No space in cache. **/
         free(fio);
    }
}

/*
* Open a file, initialize data structures.
*/

FileIO *
FIOopenUFS(fname, flags, mode)
 char   *fname;
 int    flags;
 int    mode;
{
    int    fd;
    FileIO *fio;
    struct stat buf;

    if (fname == NULL)
         return(NULL);

    /* Okay, try and open up the file */
    fd = open(fname, flags, mode );

    if (fd < 0)
         return(NULL); /* Couldn't open file */

    if (fstat(fd, &buf)) {
         close(fd);
         return(NULL); /* Couldn't open anything */
    }

    fio = FIOnew();

    FIOsetSocket(fio, FALSE);
    FIOsetFilename(fio, fname);
    FIOsetfd(fio,fd);

    return(fio);
}


/*
* Start FIO routines on an already open file descriptor
*/

FileIO*
FIOopenfd(fd, issocket)
 int     fd;
 boolean issocket;
{
    FileIO *fio;

    fio = FIOnew();

    FIOsetfd(fio, fd);
    FIOsetSocket(fio, issocket);
    return(fio);
}

FileIO*
FIOopenProcess(prog, args, rw)
 char   *prog;
 char   **args;
 char   *rw;
{
    int    pfd[2];
    int    pid;
    FileIO *fio;

    if (prog == NULL)
         return(NULL);

    fio = FIOnew();

    dup(0);  /*** Arghh!!  pipe doesn't work right when all fds are closed! */

    if (pipe(pfd) < 0)
         return(NULL);


    switch (pid = vfork()) {
    case -1:                   /* Error */
         (void) close(pfd[0]);
         (void) close(pfd[1]);
         break;
    case 0:                    /* Child */
         if (rw == NULL || *rw == '\0') {
              /** mimic system(), don't do anything **/
              (void) close(pfd[0]);
              (void) close(pfd[1]);
         }
         else if (*rw == 'r') {
              if (pfd[1] != 1) {
                   dup2(pfd[1], 1);
                   (void) close(pfd[1]);
              }

              (void) close(pfd[0]);
         } else {
              if (pfd[0] != 0) {
                   dup2(pfd[0], 0);
                   (void) close(pfd[0]);
              }
              (void) close(pfd[1]);
         }
#ifdef __VMS
         execv(prog, args);
#else
         /** Unix **/

         if (*prog == '/')
              execv(prog, args);
         else
              execvp(prog, args); /* search the path for the command */
#endif

         _exit(1);
    }

    /* parent.. */
    if (rw == NULL || *rw == '\0') {
         /** Don't do anything, mimic system() **/
         FIOsetfd(fio, -1);
         (void) close(pfd[0]);
         (void) close(pfd[1]);
    } else if (*rw == 'r') {
         FIOsetfd(fio, pfd[0]);
         (void) close(pfd[1]);
    } else {
         FIOsetfd(fio, pfd[1]);
         (void) close(pfd[0]);
    }
    FIOsetPid(fio, pid);
    FIOsetSocket(fio, FALSE);
    return(fio);
}


/*
* Close a file/socket/process
*/

int
FIOclose(fio)
 FileIO *fio;
{
    int result;

    if (fio == NULL)
         return(0);

    if (FIOgetPid(fio) >= 0) {

         close(FIOgetfd(fio));
         result = FIOwaitpid(fio);
         FIOdestroy(fio);

         return(result);
    }


#ifdef MMAP_IO
    /** Unmap memory mapped I/O stuff here.. **/
    if (FIOgetFilename(fio) != NULL && fio->buf != NULL)
         munmap(fio->buf, FIOgetBufDsize(fio));

#endif

    result = FIOisSocket(fio) ? socket_close(FIOgetfd(fio)) :
         close(FIOgetfd(fio));

    FIOdestroy(fio);

    return(result);
}

/*
* A portable waitpid fcn that returns the exit value of the child process
*
* Should be better about stopped and signaled processes....
*/

int
FIOwaitpid(fio)
 FileIO *fio;
{
    Portawait   status;
    pid_t       result;


    do {
         result = waitpid(FIOgetPid(fio), &status, 0);
    } while (result != FIOgetPid(fio) && (errno == EINTR));

    return(Gwaitstatus(status) & 0xf);
}

/*
* write n bytes to an fd..
*
* returns -1 on error.
*/

int
FIOwriten(fio, ptr, nbytes)
 FileIO *fio;
 char   *ptr;
 int    nbytes;
{
    int nleft, nwritten;
    int fd = FIOgetfd(fio);

    nleft = nbytes;
    while(nleft > 0) {
         nwritten = FIOisSocket(fio) ? socket_write(fd, ptr, nleft) :
              write(fd, ptr, nleft);

         if (nwritten <= 0)
              return(nwritten);        /* error */

         nleft -= nwritten;
         ptr   += nwritten;
    }
    return(nbytes - nleft);
}

/*
* write a string to a FileDescriptor, eventually buffer outgoing input
*
* If write fails a -1 is returned. Otherwise zero is returned.
*/

int
FIOwritestring(fio, str)
 FileIO *fio;
 char   *str;
{
    int length;

    Debug("writing: %s\n",str);

    if (str == NULL)
         return(0);

    length = strlen(str);
    if (FIOwriten(fio, str, length) != length) {
         Debugmsg("writestring: writen failed\n");
         return(-1);
    }
    else
         return(0);
}

#ifdef MMAP_IO
int
FIOreadbuf_mmap(fio, newbuf, newbuflen)
 FileIO *fio;
 char   *newbuf;
 int     newbuflen;
{
    struct stat buf;
    char        *bytes;

    if (FIOgetFilename(fio) != NULL) {
         /** Do MMAP IO if we can... **/

         if (fio->buf == NULL) {
              if (fstat(FIOgetfd(fio), &buf))
                   return(-1);

              if (buf.st_size == 0)
                   return(0);

              FIOsetBufDsize(fio, buf.st_size);
              FIOsetBufIndex(fio, 0);

              bytes = mmap(0, buf.st_size, PROT_READ,
                           MAP_SHARED | MAP_FILE,
                           FIOgetfd(fio), 0);
              madvise(bytes, buf.st_size, MADV_SEQUENTIAL);

              if (bytes == (caddr_t) -1)
                   return(-1);
              fio->buf = bytes;
         }

         if (FIOgetBufIndex(fio) == -1)
              return(0);

         /** Okay, lets return some data... **/
         if (FIOgetBufIndex(fio) >= FIOgetBufDsize(fio))
              return(0);

         if ((FIOgetBufIndex(fio) + newbuflen) > FIOgetBufDsize(fio)) {
              newbuflen = FIOgetBufDsize(fio) - FIOgetBufIndex(fio);
         }
         memcpy(newbuf, fio->buf+FIOgetBufIndex(fio), newbuflen);
         FIOsetBufIndex(fio, FIOgetBufIndex(fio) + newbuflen);

         return(newbuflen);
    }
    /** This is an error condition **/
    return(-1);

}
#endif

/*
* Read through a buffer, more efficient for character at a time
* processing.  Not so good for block binary transfers
*/

int
FIOreadbuf(fio, newbuf, newbuflen)
 FileIO *fio;
 char   *newbuf;
 int     newbuflen;
{
    int len;
    int fd = FIOgetfd(fio);
    char *recvbuf;
    int  bytesread = 0;

#ifdef MMAP_IO
    if (FIOisMMAPable(fio)) {
         return(FIOreadbuf_mmap(fio,newbuf, newbuflen));
    }
#endif

    if (FIOgetBufIndex(fio) == -1) {

         if (fio->buf == NULL)
              fio->buf = (char *) malloc(sizeof(char) * FIOBUFSIZE);

         len = FIOisSocket(fio) ? socket_read(fd, fio->buf, FIOBUFSIZE) :
              read(fd, fio->buf, FIOBUFSIZE);
#if defined(FIO_NOMULTIEOF)
         if (len < 0 && errno == EPIPE) { /** EOF **/
              len = 0;
              errno = 0;
         }
#endif
         FIOsetBufDsize(fio, len);
         FIOsetBufIndex(fio, 0);

         if (len == 0) {
              FIOsetBufIndex(fio,-1);
              return(bytesread); /** EOF **/
         }

    }
    recvbuf = fio->buf;

    while (newbuflen--) {
         *newbuf++ = recvbuf[FIOgetBufIndex(fio)++];
         bytesread++;

         if (FIOgetBufIndex(fio) == FIOgetBufDsize(fio) && newbuflen != 0) {
              /** Read another buffer **/
              len = FIOisSocket(fio) ? socket_read(fd, fio->buf, FIOBUFSIZE) :
                   read(fd, fio->buf, FIOBUFSIZE);
#if defined(FIO_NOMULTIEOF)
              if (len < 0 && errno == EPIPE) { /** EOF **/
                   len = 0;
                   errno = 0;
              }
#endif

              if (len == 0) {
                   FIOsetBufIndex(fio,-1);
                   return(bytesread); /** EOF **/
              }
              if (len < 0)
                   return(len);       /** Error **/

              FIOsetBufDsize(fio, len);
              FIOsetBufIndex(fio, 0);
         } else if (FIOgetBufIndex(fio) >= FIOgetBufDsize(fio))
              /* Read a new buffer next time through */
              FIOsetBufIndex(fio, -1);
    }
    return(bytesread);

}


/* Read 'n' bytes from a descriptor, non buffered direct into the storage. */
int
FIOreadn(fio, ptr, nbytes)
 FileIO *fio;
 char   *ptr;
 int    nbytes;
{
    int nleft, nread;
    int fd = FIOgetfd(fio);

    nleft = nbytes;
    while (nleft > 0) {
         nread = FIOisSocket(fio) ? socket_read(fd, ptr, nleft) :
              read(fd, ptr, nleft);
#if defined(FIO_NOMULTIEOF)
         if (nread < 0 && errno == EPIPE) { /* EOF */
              nread = 0;
              errno = 0;
         }
#endif

         if (nread < 0)
              return(nread);  /* error, return < 0 */
         else if (nread == 0) /* EOF */
              break;

         nleft -= nread;
         ptr   += nread;
    }
    return(nbytes - nleft);

}


/*
* Read a line from the file/socket, Read the line one byte at a time,
* looking for the newline.  We store the newline in the buffer,
* then follow it with a null (the same as fgets(3)).
* We return the number of characters up to, but not including,
* the null (the same as strlen(3))
*/

int
FIOreadline(fio, ptr, maxlen)
 FileIO *fio;
 char   *ptr;
 int    maxlen;
{
    int bytesread;
    int rc;
    char c;

    for (bytesread=1; bytesread < maxlen; bytesread++) {
         if ( (rc = FIOreadbuf(fio, &c, 1)) == 1) {
              *ptr++ = c;
              if (c == '\n')
                   break;
         }
         else if (rc == 0) {
              if (bytesread == 1)
                   return(0);  /* EOF, no data read */
              else
                   break;              /* EOF, some data was read */
         }
         else
              return(-1);              /* error */
    }

    *ptr = 0;                          /* Tack a NULL on the end */
    Debug("FIOreadline: %s\n", (ptr-bytesread));

    return(bytesread);
}


/*
* This does the same as readline, except that all newlines and
* carriage returns are automatically zapped.
*
* More efficient than doing a readline and a ZapCRLF
*/

int
FIOreadlinezap(fio, ptr, maxlen)
 FileIO *fio;
 char   *ptr;
 int    maxlen;
{
    int len;

    len = FIOreadtoken(fio, ptr, maxlen, '\n');
    ptr += len;
    ptr --;

    if (*ptr == '\r') {
         ptr[len] = '\0';
         len--;
    }
    return(len);
}


/*
* Read a line from the file/socket, Read the line one byte at a time,
* looking for the token.  We nuke the token from the returned string.
* We return the number of characters up to, but not including,
* the null (the same as strlen(3))
*/

int
FIOreadtoken(fio, ptr, maxlen, zechar)
 FileIO *fio;
 char   *ptr;
 int    maxlen;
 char   zechar;
{
    int bytesread;
    int rc;
    char c;

    for (bytesread=1; bytesread < maxlen; bytesread++) {
         rc = FIOreadbuf(fio, &c, 1);
         if (rc == 1) {
              *ptr++ = c;
              if (c == zechar) {
                   *(ptr - 1) = '\0';
                   break;
              }
         }
         else if (rc == 0) {
              if (bytesread == 1)
                   return(0);  /* EOF, no data read */
              else
                   break;              /* EOF, some data was read */
         }
         else
              return(-1);              /* error */
    }

    *ptr = 0;                          /* Tack a NULL on the end */
    Debug("readtoken: %s\n", (ptr-bytesread));
    return(bytesread);
}

int
FIOexecv(prog, args)
 char *prog;
 char **args;
{
    FileIO *fio;
    int    result;

#ifdef __VMS
    int     i = 0;
    char    buffer[1024];

    /* DCL hog heaven */
    strcpy(buffer, prog);
    strcat(buffer, " ");

    while (i++) {
         if (args[i] == NULL)
              break;

         strcat(buffer, args[i]);
         strcat(buffer, " ");
    }
    result = system(buffer);

#else
    fio = FIOopenProcess(prog, args, NULL);
    result = FIOclose(fio);
#endif /* VMS */

    return(result);
}


/*
* Do the minimal shell/dcl processing
*/

char **
FIOgetargv(cmd)
 char *cmd;
{
    int           inquote = 0;
    int           insquote = 0;
    int           i;
    static char  *argv[128];           /* Sufficient for now.. */
    int           argc = 0;
    char          buf[256];
    char         *cp = buf;

    if (cmd == NULL)
         return(NULL);

    for (i=0; cmd[i] != '\0'; i++) {

         switch (cmd[i]) {

         case ' ': case '\t':
              /* Separators */
              if (insquote || inquote) {
                   *cp = cmd[i]; cp++;
                   break;
              } else {
                   *cp = '\0';
                   argv[argc++] = strdup(buf);
                   cp = buf;

                   /** Get rid of any other whitespace **/
                   while (cmd[i+1] == ' ' || cmd[i+1] == '\t')
                        i++;
              }
              break;

         case '"':
              if (!insquote)
                   inquote = 1-inquote;
              break;

         case '\'':
              if (!inquote)
                   insquote = 1-insquote;
              break;

         case '\\':
              /* Quote next character if not in quotes */
              if (insquote || inquote) {
                   *cp = cmd[i]; cp++;
              } else {
                   *cp = cmd[i+1]; cp++; i++;
              }

              break;

         default:
              *cp = cmd[i]; cp++;
              break;
         }
    }
    if (buf != cp) {
         *cp = '\0';
         argv[argc++] = strdup(buf);
    }
    argv[argc] = NULL;

    return(argv);
}


/*
*  An emulation of the system() call without the shell
*  returns the exit status of the child
*/

int
FIOsystem(cmd)
 char *cmd;
{
    char **argv, i;
    int    result;

#ifdef __VMS
    return(system(cmd));
#else

    if (cmd == NULL || *cmd == '\0')
         return(-1);

    argv = FIOgetargv(cmd);

    result = FIOexecv(argv[0], argv);

    for (i=0; argv[i] != NULL; i++)
         free(argv[i]);

    return(result);
#endif
}



/*
* Similar to popen...
*/

FileIO
*FIOopenCmdline(cmd, rw)
 char *cmd;
 char *rw;
{
    char   **argv;
    FileIO *fio;

    if (cmd == NULL)
         return(NULL);

    if (*cmd == '|')
         cmd++;

    argv = FIOgetargv(cmd);

    fio = FIOopenProcess(argv[0], argv, rw);

    return(fio);
}

#ifdef VMS_SERVER
#include <stdio.h>
int
FIOtell(fio)
   FileIO *fio;
{
   return(lseek(fio->fd, 0, SEEK_CUR));
}

void
FIOseek(fio, offset)
   FileIO *fio;
   int offset;
{
   lseek(fio->fd, offset, SEEK_SET);
}
#endif