/* ,file-id archive://[lord]/404/vu/./vfdbuf.c/1998-05-18
*/
/*	Copyright (C) 1997 Tom Lord
 * 
 * This program is provided to you under the terms of the Liberty Software
 * License.  You are NOT permitted to redistribute, modify, or use it
 * except in very specific ways described by that license.
 *
 * This software comes with NO WARRANTY.
 * 
 * You should have received a copy of the Liberty Software License
 * along with this software; see the file =LICENSE.  If not, write to
 * the Tom Lord, 1810 Francisco St. #2, Berkeley CA, 94703, USA.  
 */



#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <utime.h>
#include <errno.h>
#include "rx/regex.h"
#include "xmalloc.h"
#include "vu.h"
#include "vu/ar.h"
#include "vfdbuf.h"



struct vfdbuf_record
{
  int sub_fd;
  struct vu_fs_discipline * sub_vtable;
  int flags;
  int eof;

  int bufpos;		/* input only */
  int buffered;
  int bufsize;
  char * buf;
};

static struct vfdbuf_record * bufs = 0;

static void
vfdbuf_add (int * errn, int fd, int sub_fd, int bufsize, int flags)
{
  if (fd >= asize (bufs))
    aref ((void **)&bufs, fd, sizeof (struct vfdbuf_record));

  if (bufs[fd].bufsize != bufsize)
    {
      bufs[fd].buf = (char *)xrealloc (bufs[fd].buf, bufsize);
      bufs[fd].bufsize = bufsize;
    }
  bufs[fd].sub_fd = sub_fd;
  bufs[fd].flags = flags;
  bufs[fd].eof = 0;
  bufs[fd].sub_vtable = vu_fd_dispatch (sub_fd);
  bufs[fd].bufpos = 0;
  bufs[fd].buffered = 0;
  vu_set_fd_handler (fd, &vu_fdbuf_vtable);
}

static void
vfdbuf_cancel (int fd)
{
  xfree (bufs[fd].buf);
  bufs[fd].bufsize = 0;
  bufs[fd].buf = 0;
}



/* pseudo-dup an fd, attaching a buffer to the new fd.
 */
int
vfdbuf_copy_fd (int * errn, int sub_fd, int bufsize, int acc_flags)
{
  int fd;
  fd = reserv (errn, acc_flags);
  if (fd < 0)
    return -1;
  vfdbuf_add (errn, fd, sub_fd, bufsize, acc_flags);
  return fd;
}

/* Discard amt leading characters from a buffer,
 * shifting any remaining characters to make room at the end
 * of the buffer.
 */
void
vfdbuf_shift (int fd, int amt)
{
  int remain;
  if (amt > bufs[fd].buffered)
    amt = bufs[fd].buffered;
  remain = bufs[fd].buffered - amt;
  memmove (bufs[fd].buf, bufs[fd].buf + amt, remain);
  bufs[fd].bufpos = 0;
  bufs[fd].buffered = remain;
}

/* Disgard amt (default: already read) leading characters from a buffer.
 */
void
vfdbuf_eat (int fd, int amt)
{
  if (bufs[fd].bufpos + amt > bufs[fd].buffered)
    {
      bufs[fd].buffered = 0;
      bufs[fd].bufpos = 0;
    }
  else
    bufs[fd].bufpos += amt;
}

/* Flush output from a buffer.
 */
int
vfdbuf_flush (int * errn, int fd)
{
  int wrote;

  if (!bufs[fd].buffered)
    return 0;

 retry:
  wrote = vu_write (errn, bufs[fd].sub_fd, bufs[fd].buf, bufs[fd].buffered);
  if (wrote != bufs[fd].buffered)
    {
      if (wrote > 0)
	{
	  vfdbuf_shift (fd, wrote);
	  goto retry;
	}
      return -1;
    }
  bufs[fd].buffered = 0;
  return 0;
}

/* Return the buffer and its size for a given descriptor.
 */
void
vfdbuf_getbuf (char ** buf, int * buffered, int fd)
{
  *buf = bufs[fd].buf + bufs[fd].bufpos;
  *buffered = bufs[fd].buffered - bufs[fd].bufpos;
}

/* Return the buffer and its size for a given descriptor, but first,
 * add (room for) AMT characters at the end.  The new characters are
 * not initialized, but are counted in the return value BUFFERED.
 */
void
vfdbuf_getbuf_space (char ** buf, int * buffered, int fd, int amt)
{
 retry:
  if ((bufs[fd].buffered + amt) > bufs[fd].bufsize)
    {
      if (bufs[fd].bufpos)
	{
	  vfdbuf_shift (fd, bufs[fd].bufpos);
	  goto retry;
	}
      else
	{
	  bufs[fd].buf = ((char *) xrealloc (bufs[fd].buf, bufs[fd].bufsize + amt));
	  bufs[fd].bufsize += amt;
	}
    }
  bufs[fd].buffered += amt;
  vfdbuf_getbuf (buf, buffered, fd);
}

/* Put some characters at the end of the buffer.
 */
void
vfdbuf_return (int fd, char * str, int len)
{
  char * buf;
  int buffered;

  vfdbuf_getbuf_space (&buf, &buffered, fd, len);
  memmove (buf + len, buf, buffered - len);
  memmove (buf, str, len);
}

int
vfdbuf_more (int * errn, char ** buf, int * buffered, int fd, int opt_amt)
{
  int buffered_before;

  if (!opt_amt)
    {
      opt_amt = bufs[fd].bufsize - bufs[fd].buffered;
      if (!opt_amt)
	opt_amt = bufs[fd].bufsize;
      if (!opt_amt)
	opt_amt = 4096;
    }

  buffered_before = bufs[fd].buffered - bufs[fd].bufpos;

  vfdbuf_getbuf_space (buf, buffered, fd, opt_amt);

  /* getbuf_space just increased the count of characters 
   * buffered.  Undo that:
   */
  bufs[fd].buffered = buffered_before + bufs[fd].bufpos;;

  {
    int got;
    got = vu_read (errn, bufs[fd].sub_fd, bufs[fd].buf + bufs[fd].buffered, opt_amt);
    if (got < 0)
      return -1;
    if (got == 0)
      bufs[fd].eof = 1;
    bufs[fd].buffered += got;
    *buf = bufs[fd].buf + bufs[fd].bufpos;
    *buffered = bufs[fd].buffered - bufs[fd].bufpos;
    return got;
  }
}

int
vfdbuf_eof (int * errn, int fd)
{
  return bufs[fd].eof;
}



#define vfdbuf_access vu_sys_access
#define vfdbuf_chdir vu_sys_chdir
#define vfdbuf_chmode vu_sys_chmode
#define vfdbuf_chown vu_sys_chown
#define vfdbuf_chroot vu_sys_chroot
#define vfdbuf_closedir vu_sys_closedir
#define vfdbuf_fchdir vu_sys_fchdir
#define vfdbuf_fstat vu_sys_fstat
#define vfdbuf_link vu_sys_link
#define vfdbuf_lstat vu_sys_lstat
#define vfdbuf_mkdir vu_sys_mkdir
#define vfdbuf_open vu_sys_open
#define vfdbuf_opendir vu_sys_opendir
#define vfdbuf_readdir vu_sys_readdir
#define vfdbuf_readlink vu_sys_readlink
#define vfdbuf_rename vu_sys_rename
#define vfdbuf_rmdir vu_sys_rmdir
#define vfdbuf_seekdir vu_sys_seekdir
#define vfdbuf_stat vu_sys_stat
#define vfdbuf_symlink vu_sys_symlink
#define vfdbuf_telldir vu_sys_telldir
#define vfdbuf_truncate vu_sys_truncate
#define vfdbuf_unlink vu_sys_unlink
#define vfdbuf_utimes vu_sys_utimes
#define vfdbuf_fcntl vu_sys_fcntl
#define vfdbuf_ioctl vu_sys_ioctl
#define vfdbuf_dup vu_sys_dup
#define vfdbuf_dup2 vu_sys_dup2

int
vfdbuf_close (int * errn, int fd)
{
  int got;

  unreserv (fd);
  got = bufs[fd].sub_vtable->close (errn, bufs[fd].sub_fd);
  vfdbuf_cancel (fd);
  return got;
}

int
vfdbuf_fsync (int * errn, int fd)
{
  if (vfdbuf_flush (errn, fd) < 0)
    return -1;
  return bufs[fd].sub_vtable->fsync (errn, fd);
}

int
vfdbuf_ftruncate (int * errn, int fd, int where)
{
  int offset;

  offset = vu_lseek (errn, fd, 0, SEEK_CUR);

  if (offset < 0)
    return -1;

  if ((offset + bufs[fd].buffered) >= where)
    {
      bufs[fd].buffered = where - offset;
      if (bufs[fd].buffered < 0)
	bufs[fd].buffered = 0;
    }

  return bufs[fd].sub_vtable->ftruncate (errn, fd, where);
}

int
vfdbuf_read (int * errn, int fd, char * buf, int count)
{
  int orig_count;
  int avail;
  int filled_in;

  orig_count = count;
  avail = bufs[fd].buffered - bufs[fd].bufpos;
  filled_in = (avail < count ? avail : count);
  memmove (buf,
	  bufs[fd].buf + bufs[fd].bufpos,
	  filled_in);
  bufs[fd].bufpos += filled_in;
  buf += filled_in;
  count -= filled_in;

  if (!count)
    return orig_count;
  else
    {
      int got;
      vfdbuf_shift (fd, bufs[fd].buffered);
      while (count > bufs[fd].bufsize)
	{
	  got = bufs[fd].sub_vtable->read (errn, fd, buf, bufs[fd].bufsize);
	  if (got < 0)
	    return -1;
	  buf += got;
	  count -= got;
	  if (got < bufs[fd].bufsize)
	    return orig_count - count;
	}

      if (count)
	{
	  char * ign;
	  int ign2;
	  if (vfdbuf_more (errn, &ign, &ign2, fd, 0) < 0)
	    return -1;

	  avail = bufs[fd].buffered - bufs[fd].bufpos;
	  filled_in = (avail < count ? avail : count);
	  memmove (buf,
		  bufs[fd].buf + bufs[fd].bufpos,
		  filled_in);
	  bufs[fd].bufpos += filled_in;
	  buf += filled_in;
	  count -= filled_in;
	}
      return orig_count - count;
    }
}

int
vfdbuf_write (int * errn, int fd, char * buf, int count)
{
  int orig_count;
  int avail;
  int filled_in;

  orig_count = count;
  avail = bufs[fd].bufsize - bufs[fd].buffered;
  filled_in = (avail < count ? avail : count);
  memmove (bufs[fd].buf + bufs[fd].buffered,
	  buf,
	  filled_in);
  bufs[fd].buffered += filled_in;
  buf += filled_in;
  count -= filled_in;

  if (!count)
    return filled_in;
  else
    {
      if (vfdbuf_flush (errn, fd) < 0)
	return -1;

      while (count > bufs[fd].bufsize)
	{
	  int wrote;
	  wrote = bufs[fd].sub_vtable->write (errn, fd, buf, bufs[fd].bufsize);
	  if (wrote < 0)
	    return -1;
	  buf += wrote;
	  count -= wrote;
	  if (wrote < bufs[fd].bufsize)
	    return orig_count - count;
	}
      memmove (bufs[fd].buf, buf, count);
      bufs[fd].buffered = count;
      return orig_count;
    }
}

int
vfdbuf_lseek (int * errn, int fd, int offset, int whence)
{
  int write_fd;
  int cur_fd_offset;
  int cur_offset;
  int goal;

  write_fd = ((bufs[fd].flags & O_ACCMODE) != O_RDONLY);

  cur_fd_offset = vu_lseek (errn, bufs[fd].sub_fd, 0, SEEK_CUR);

  if (cur_fd_offset < 0)
    return -1;

  cur_offset = (write_fd
		? cur_fd_offset + bufs[fd].buffered
		: (   cur_fd_offset
		   - bufs[fd].buffered
		   + bufs[fd].bufpos));

  if (whence == SEEK_SET)
    goal = offset;
  else if (whence == SEEK_CUR)
    goal = cur_offset + offset;
  else
    {
      struct stat sbuf;
      int eof;

      if (vu_fstat (errn, fd, &sbuf) < 0)
	return -1;

      if (!write_fd || (cur_offset <= sbuf.st_size))
	goal = sbuf.st_size + offset;
      else
	goal = cur_offset + offset;
    }

  if (write_fd && (vfdbuf_flush (errn, fd) < 0))
    return -1;
  else
    bufs[fd].buffered = bufs[fd].bufpos = 0;

  return bufs[fd].sub_vtable->lseek (errn, bufs[fd].sub_fd, goal, SEEK_SET);
}



struct vu_fs_discipline vu_fdbuf_vtable \
  = { VU_FS_DISCIPLINE_INITIALIZERS (vfdbuf_) };
