/********************************************************************
 * lindner
 * 3.6
 * 1994/03/09 02:11:49
 * /home/mudhoney/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.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

/*
 * Cache of used fios 
 */

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

/*
 * Pop a recently used item.
 */

static FileIO *FIOpopUsedfio()
{
     if (FIOnumusedfios > 0) {
	  FIOnumusedfios--;
	  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;
	  FIOnumusedfios++;
	  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->issocket = -1;
     temp->pid = -1;
     temp->fd = (pid_t) -1;
     temp->buf = NULL;

     temp->bufindex = 0;
     temp->bufdatasize = -1;

     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;

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

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

     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;
     char   findit[512];
     char  *path;
     char  *cp;

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

#ifdef unix
     if (*prog != '/') {
	  /* scan the path */
	  struct stat stbuf;
	  boolean     foundit = FALSE;

	  path = getenv("PATH");

	  if (path == NULL)
	       return(NULL);

	  do {
	       cp = strchr(path, ':');
	       if (cp != NULL)
		    *cp = '\0';
	       
	       strcpy(findit, path);
	       strcat(findit, "/");
	       strcat(findit, prog);
	       
	       if (stat(findit, &stbuf) == 0) {
		    foundit = TRUE;
		    break;	/* found it! */
	       }

	       path = cp+1;

	  } while (cp != NULL);
	  
	  if (foundit == TRUE) 
	       prog = findit;
     }
#endif

     fio = FIOnew();

     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]);
	  }
	  execv(prog, args);
	  _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]);
	  close(pfd[0]);
     }
     FIOsetPid(fio, pid);
     return(fio);
}


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

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

     if (FIOgetPid(fio) >= 0) {
	  
	  close(FIOgetfd(fio));
	  result = FIOwaitpid(fio);
	  FIOdestroy(fio);
	  
	  result &= 0xf;	/* Probably different on VMS */

	  return(result);
     }

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

     FIOdestroy(fio);

     return(result);
}

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

     result = waitpid(FIOgetPid(fio), &status, 0);
     
     return(result);
}

/*
 * 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) {
	  Debug("writestring: writen failed\n",0);
	  return(-1);
     }
     else
	  return(0);
}

/*
 * 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;

     if (FIOgetBufIndex(fio) == 0) {
	  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);
	  FIOsetBufDsize(fio, len);
	  FIOsetBufIndex(fio, 0);
	  
#if defined(FIO_NOMULTIEOF)
	  if (len < 0 && errno == EPIPE)
	       return(0);
#endif
	  if (len == 0)
	       return(0);

     }
     
     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 (len == 0) {
		    FIOsetBufIndex(fio,0);
		    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, 0);
     }
     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)
	       break;
#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("readline: %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++) {
	  if (FIOgetBufIndex(fio) == 0)
	       rc = FIOreadbuf(fio, &c, 1);
	  else {
	       rc = 1;
	       c = fio->buf[FIOgetBufIndex(fio)++];
	       if (FIOgetBufIndex(fio) >= FIOgetBufDsize(fio))
		    FIOsetBufIndex(fio, 0);
	  }

	  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, i = 0;
     char    buffer[1024];

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

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

	  strcat(buffer, args[i]);
	  strcat(buffer, " ");
     }
     result = DCLsystem(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 (buf[i+1] == ' ' || buf[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++] = buf;
     }
     argv[argc] = NULL;
     
     return(argv);
}


/*  
 *
 */

int
FIOsystem(cmd)
  char *cmd;
{
     char **argv;
#ifdef VMS
     return(system(cmd));
#else

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

     argv = FIOgetargv(cmd);

     return(FIOexecv(argv[0], argv));
#endif
}



/*
 * Similar to popen...
 */

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

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

     argv = FIOgetargv(cmd);
     
     fio = FIOopenProcess(argv[0], argv, rw);
     
     return(fio);
}

