/* Copyright (C) 1991 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

#include <ansidecl.h>
#include <stdio.h>
#include <hurd.h>
#include <hurd/shared.h>

struct mapped
  {
    struct shared_io shared;

    /* The rest of the shared page is owned by the user (stdio).  */

    io_t file;			/* Port to the file.  */
    size_t blksize;		/* Optimal size for i/o.  */
    memory_object_t cntl;	/* Memory object for this page.  */
    /* Memory objects for reading and writing data.
       These two might be the same.  */
    memory_object_t rdmemobj, wrmemobj;
  };


/* Get IT.  */
static error_t
#ifdef	__GNUC__
__inline
#endif
DEFUN(get_it, (m), struct mapped *m)
{
 try:

  __spin_lock(m->shared.lock);

  switch (m->shared.it_status)
    {
    case USER_POTENTIALLY_IT:
      m->shared.it_status = USER_IT;
    case USER_RELEASE_IT:
    case USER_IT:
      __spin_unlock(m->shared.lock);
      return 0;

    case USER_NOT_IT:
      __spin_unlock(m->shared.lock);
      {
	error_t error = __io_get_it(m->file, m->cntl);
	if (error == 0)
	  return 0;
	else
	  /* Don't just tail-recurse because that might
	     make the function not get inlined.  Sigh.  */
	  goto try;
      }

    default:
      __libc_fatal("get_it: Unrecognized IT status!\n");
    }
}

/* Release IT.  */
static error_t
#ifdef	__GNUC__
__inline
#endif
DEFUN(release_it, (m), struct mapped *m)
{
  __spin_lock(m->shared.lock);
  switch (m->shared.it_status)
    {
    case USER_IT:
      m->shared.it_status = USER_POTENTIALLY_IT;
    case USER_NOT_IT:
      __spin_unlock(m->shared.lock);
      return 0;

    case USER_RELEASE_IT:
      __spin_unlock(m->shared.lock);
      return __io_release_it(m->file, m->cntl);

    default:
      __libc_fatal("release_it: Unrecognized IT status!\n");
    }
}

static int
DEFUN(mapped_close, (cookie), PTR cookie)
{
  struct mapped *CONST m = (struct mapped *) cookie;
  int am_it;
  error_t error = 0;

  __spin_lock(m->shared.lock);
  am_it = m->shared.it_status != USER_NOT_IT;
  __spin_unlock(m->shared.lock);
  if (am_it)
    error = __io_release_it(m->file, m->cntl);

#define	DO(foo)	if (error == 0) error = foo; else (void) foo
  DO(__vm_deallocate(__mach_task_self(), m, sizeof(*m)));
  DO(__port_deallocate(__mach_task_self(), m->file));
  DO(__port_deallocate(__mach_task_self(), m->cntl));
  DO(__port_deallocate(__mach_task_self(), m->rdmemobj));
  DO(__port_deallocate(__mach_task_self(), m->wrmemobj));
#undef	DO

  if (error != 0)
    {
      errno = error;
      return -1;
    }
  return 0;
}

static int
DEFUN(mapped_seek, (cookie, pos, whence),
      PTR cookie AND fpos_t *pos AND int whence)
{
  struct shared_io *CONST m = (struct shared_io *) cookie;
  int error;

  error = get_it(m);
  if (error == 0)
    {
      switch (whence)
	{
	case SEEK_SET:
	  if (!m->seekable && *pos > m->file_pointer)
	    error = ESPIPE;
	  else
	    {
	      m->file_pointer = *pos;
	      error = 0;
	    }
	  break;

	case SEEK_CUR:
	  if (!m->seekable && *pos < 0)
	    error = ESPIPE;
	  else
	    {
	      m->file_pointer += *pos;
	      *pos = m->file_pointer;
	      error = 0;
	    }
	  break;

	case SEEK_END:
	  if (!m->use_file_size)
	    error = ESPIPE;
	  else
	    {
	      off_t desired = m->file_size + *pos;
	      if (!m->seekable && desired < m->file_pointer)
		error = ESPIPE;
	      else
		{
		  *pos = m->file_pointer = desired;
		  error = 0;
		}
	    }
	  break;

	default:
	  error = EINVAL;
	  break;
	}

      if (error == 0)
	error = release_it(m);
      else
	(void) release_it(m);
    }

  if (error != 0)
    {
      errno = error;
      return -1;
    }
  return 0;
}

static int
DEFUN(mapped_input, (stream), FILE *stream)
{
  struct mapped *CONST m = (struct mapped *) cookie;
  struct shared_io *CONST sio = &m->shared;
  off_t pos, bufp;
  error_t error;
  off_t size;

  if (error = get_it(sio))
    {
      __error(stream) = 1;
      errno = error;
      return EOF;
    }

  if (__buffer(stream) != NULL && m->rdmemobj == m->wrmemobj &&
      __offset(stream) == __target(stream))
    {
      /* The right spot is already mapped.  */
      sio->accessed = 1;	/* Tell the FS we are reading it.  */
      __get_limit(stream) = __buffer(stream) + __bufsize(stream);
      if (!sio->written)
	/* Make the next writing operation call mapped_output,
	   so it can set the `written' bit at the right time.  */
	__put_limit(stream) = __buffer(stream);
      if (error = release_it(m))
	goto release_lost;
      __bufp(stream) = __buffer(stream);
      return *__bufp(stream)++;
    }

  if (__buffer(stream) != NULL)
    {
      /* Remove the old mapping.  */
      size_t mapping_size = ((__get_limit(stream) > __buffer(stream) ?
			      __get_limit(stream) : __put_limit(stream)) -
			     __buffer(stream));
      (void) __vm_deallocate(__mach_task_self(),
			     __buffer(stream), mapping_size);
      __buffer(stream) = NULL;
    }

  /* We're reading, so we're not at the end-of-file.  */
  __eof(stream) = 0;

  pos = __target(stream);
  while (sio->use_read_size && sio->read_size < pos)
    {
      if (sio->use_file_size && pos >= sio->file_size)
	{
	  /* Tried to read past the end of the file.  */
	  if (sio->eof_notify)
	    __io_eofnotify(m->file);
	  if (error = release_it(sio))
	    goto release_lost;
	  __eof(stream) = 1;
	  return EOF;
	}

      /* Block until there is more to read.  */
      error = __io_readsleep(m->file);
      if (error)
	{
	  (void) release_it(m);
	  errno = error;
	  return EOF;
	}
    }
  if (sio->use_read_size)
    size = sio->read_size;
  else if (sio->use_file_size)
    size = sio->file_size;
  else
    __libc_fatal("!use_read_size && !use_file_size\n");

  /* Round POS to a block boundary, leaving the excess in BUFP.  */
  bufp = pos % m->blksize;
  pos -= bufp;

  /* Decide how big a window on the file to use.  */
  size -= pos;
  if (size > m->blksize)
    size = m->blksize;

  /* Map the data.  */
  {
    vm_prot_t prot = VM_PROT_READ;
    if (stream->__mode.__write && m->rdmemobj == m->wrmemobj)
      prot |= VM_PROT_WRITE;
    error = __vm_map(__mach_task_self(),
		     &__buffer(stream), size, 0, 1,
		     m->rdmemobj, pos, 0, prot, prot, VM_INHERIT_NONE);
  }

  if (error == 0)
    /* Tell the FS that we have read some data.  */
    sio->accessed = 1;

  if (error == 0)
    (void) release_it(m);
  else
    error = release_it(m);

  if (error)
    {
    release_lost:
      __put_limit(stream) = __get_limit(stream) = __buffer(stream);
      errno = error;
      return EOF;
    }

  /* Set the offset to the position where our mapping begins.  */
  __offset(stream) = pos;
  /* Set the target position to just past the end of our mapping.  */
  __target(stream) = pos + size;

  /* Make the next output operation call __flshfp.  */
  __put_limit(stream) = __buffer(stream);

  __bufsize(stream) = size;
  __get_limit(stream) = __buffer(stream) + size;
  __bufp(stream) = __buffer(stream) + bufp;
  return (unsigned char) *__bufp(stream)++;
}

static void
DEFUN(mapped_output, (stream, c),
      FILE *stream AND int c)
{
  struct mapped *CONST m = (struct mapped *) cookie;
  struct shared_io *CONST sio = &m->shared;
  error_t error;
  off_t pos, bufp;
  size_t size;

  if (error = get_it(sio))
    {
      __error(stream) = 1;
      errno = error;
      return EOF;
    }

  if (__put_limit(stream) > __buffer(stream))
    {
      if (__bufp(stream) > __buffer(stream))
	/* Tell the FS that we have written some data.  */
	sio->written = 1;

      if (sio->use_postnotify_size &&
	  (__offset(stream) +
	   (__bufp(stream) - __buffer(stream))) > sio->postnotify_size)
	{
	  /* Notify the FS about what has been written.  */
	  if (error = __io_postnotify(m->file, m->cntl,
				      __offset(stream),
				      __bufp(stream) - __buffer(stream)))
	    goto end;
	}
    }

  if (__buffer(stream) != NULL)
    {
      /* Remove the old mapping.  */
      (void) __vm_deallocate(__mach_task_self(), __buffer(stream),
			     __get_limit(stream) - __buffer(stream));
      __buffer(stream) = NULL;
    }

  size = m->blksize;

  pos = __target(stream);
  /* Round POS to a block boundary, leaving the excess in BUFP.  */
  bufp = pos % size;
  pos -= bufp;

  if (sio->use_postnotify_size && size > sio->postnotify_size)
    /* %%% What if SIZE < BUFP after this?  */
    size = sio->postnotify_size;

  while (sio->use_prenotify_size && __target(stream) >= sio->prenotify_use)
    if (error = __io_prenotify(m->file, m->cntl, pos, size))
      goto end;

  /* Map the data.  */
  {
    vm_prot_t prot = VM_PROT_WRITE;
    if (stream->__mode.__read && m->wrmemobj == m->rdmemobj)
      prot |= VM_PROT_READ;
    error = __vm_map(__mach_task_self(),
		     &__buffer(stream), size, 0, 1,
		     m->memobj, pos, 0, prot, prot, VM_INHERIT_NONE);
  }

 end:
  release_it(m);

  if (error)
    {
      __put_limit(stream) = __get_limit(stream) = __buffer(stream);
      __error(stream) = 1;
      errno = error;
    }
  else
    {
      /* Set the offset to the position where our mapping begins.  */
      __target(stream) = __offset(stream) = pos;

      /* Make the next input operation call __fillbf.  */
      __get_limit(stream) = __buffer(stream);

      __bufsize(stream) = size;
      __put_limit(stream) = __buffer(stream) + size;
      __bufp(stream) = __buffer(stream) + bufp;
      *__bufp(stream)++ = (unsigned char) c;
    }
}

/* Initialize STREAM as necessary.
   This may change I/O functions, give a buffer, etc.
   If no buffer is allocated, but the bufsize is set,
   the bufsize will be used to allocate the buffer.  */
void
DEFUN(__stdio_init_stream, (stream), FILE *stream)
{
  CONST io_t file = (io_t) __cookie(stream);
  io_statbuf_t buf;
  memory_object_t cntl, xxmemobj, rdmemobj, wrmemobj;
  struct mapped *m;

  if (__io_stat(file, &buf))
    return;

  if (S_ISFIFO(buf.stb_mode))
    {
      /* It's named pipe (FIFO).  Make it unbuffered.  */
      __userbuf(stream) = 1;
      return;
    }

  if (sizeof(*m) > __vm_page_size)
    __libc_fatal("stdio: sizeof(struct mapped) > vm_page_size");

  /* Try to use mapped i/o.  */

  if (__io_map_cntl(m->file, &xxcntl, &rdcntl, &wtcntl))
    return;
  cntl = xxcntl; /* %%% ??? */

  if (__io_map(m->file, &xxmemobj, &rdmemobj, &wrmemobj))
    {
      (void) __port_deallocate(__mach_task_self(), cntl);
      return;
    }

  if (rdmemobj == MACH_PORT_NULL)
    rdmemobj = xxmemobj;
  if (wrmemobj == MACH_PORT_NULL)
    wrmemobj = xxmemobj;

  /* Lose if we can't do mapped i/o in the necessary direction(s).  */
  if ((stream->__mode.__read && rdmemobj == MACH_PORT_NULL) ||
      (stream->__mode.__write && wrmemobj == MACH_PORT_NULL) ||
      /* Map the shared page.  */
      __vm_map(__mach_task_self(), &m, sizeof(*m), NULL, 1, cntl, 0, 0,
	       VM_PROT_READ|VM_PROT_WRITE, VM_PROT_READ|VM_PROT_WRITE,
	       VM_INHERIT_NONE))
    {
      (void) __port_deallocate(__mach_task_self(), cntl);
      (void) __port_deallocate(__mach_task_self(), xxmemobj);
      (void) __port_deallocate(__mach_task_self(), rdmemobj);
      (void) __port_deallocate(__mach_task_self(), wrmemobj);
      return;
    }

  m->file = file;
  m->blksize = buf.stb_blksize;
  m->cntl = cntl;
  m->rdmemobj = rdmemobj;
  m->wrmemobj = wrmemobj;

  __io_funcs(stream).__close = mapped_close;
  __io_funcs(stream).__seek = mapped_seek;
  __room_funcs(stream).__input = mapped_input;
  __room_funcs(stream).__output = mapped_output;
  __cookie(stream) = (PTR) m;
  __userbuf(stream) = 1;	/* Tell stdio not to allocate a buffer.  */
}
