/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This 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.
 *
 *  This 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 this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  lseek.c,v 1.1.1.1 1994/04/04 04:30:29 amiga Exp
 *
 *  lseek.c,v
 * Revision 1.1.1.1  1994/04/04  04:30:29  amiga
 * Initial CVS check in.
 *
 *  Revision 1.1  1992/05/14  19:55:40  mwild
 *  Initial revision
 *
 */

#define _KERNEL
#include "ixemul.h"
#include "kprintf.h"

#include <string.h>

static inline int
__extend_file (struct file *f, int add_to_eof, int *err)
{
  int res = 0;
  int buf_size, written;
  char *buf;

  /* now you know perhaps, that starting with OS2.0 there's a packet that
     sets the size of a file (used in [f]truncate()). Too bad, that the 
     following statement from the autodocs render this packet useless... :

     `` Do NOT count on any specific values to be in the extended area. ''
   
     Since **IX requires the new area to be zero'd, we can just as well
     write zeros without the use of the new packet ;-(( */
  
      
  buf_size = add_to_eof > 32*1024 ? 32*1024 : add_to_eof;
  while (! (buf = (char *) kmalloc (buf_size)) && buf_size)
    buf_size >>= 1;
  if (buf)
    {
      bzero (buf, buf_size);

      for (written = 0; written < add_to_eof; )
        {
	  SendPacket3(f,__srwport,ACTION_WRITE,f->f_fh->fh_Arg1,(long)buf,buf_size);
	  __wait_sync_packet(&f->f_sp);
	  res = LastResult (f);
          if (res < 0)
            {
              *err = LastError (f);
              break;
            }
          written += res;
          buf_size = add_to_eof - written > buf_size ? 
		       buf_size : add_to_eof - written;
	}
      kfree (buf);
    }
  else
    {
      *err = ENOMEM;
      res = -1;
    }

  if (res >= 0)
    {
      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_END);
     __wait_sync_packet (&f->f_sp);
      LastError(f) = 0;
      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_END);
     __wait_sync_packet (&f->f_sp);
     res = LastError (f) ? -1 : LastResult (f);
     *err = __ioerr_to_errno (LastError (f));
   }

  return res;
}


off_t
lseek (int fd, off_t off, int dir)
{
  struct file *f = u.u_ofile[fd];
  int omask;
  int err, res;
  int previous_pos = 0, shouldbe_pos;

  /* if this is an open fd */
  if (fd >= 0 && fd < NOFILE && f)
    {
      if (f->f_type == DTYPE_FILE)
	{
          if (HANDLER_NIL(f))
	    {
	      /* This is always possible with /dev/null */
	      if (off == 0)
	        return 0;
	      errno = ESPIPE;
	      KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
	      return -1;
	    }

	  omask = syscall (SYS_sigsetmask, ~0);
	  __get_file (f);

	  /* there's a subtle difference between Unix lseek() and AmigaDOS
	   * Seek(): lseek() returns the *current* position of the `pointer',
	   * Seek() returns the *previous* position. So in some cases, we
	   * have to do another Seek() to find out where we are.
	   * Thanks Mike for pointing me at this! */

	  switch (dir)
	    {
	    case SEEK_SET:
	      /* previous doesn't matter, don't need to seek */
	      previous_pos = 0;
	      break;

	    case SEEK_CUR:
	      /* first find out current position */
	      LastError(f) = 0;
	      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_CURRENT);
	      __wait_sync_packet (&f->f_sp);
	      if (LastError(f) || LastResult(f) < 0)
	        {
	          err = __ioerr_to_errno(LastError(f));
	          previous_pos = -1;
	        }
	      else
	        previous_pos = LastResult(f);
	      break;
	      
	    case SEEK_END:
	      /* first find out end position (have to do twice.. argl) */
	      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_END);
	      __wait_sync_packet (&f->f_sp);
	      LastError(f) = 0;
	      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_CURRENT);
	      __wait_sync_packet (&f->f_sp);
	      if (LastError(f) || LastResult(f) < 0)
	        {
	          err = __ioerr_to_errno(LastError(f));
	          previous_pos = -1;
	        }
	      else
	        previous_pos = LastResult(f);
	      break;
	    }
	  
	  shouldbe_pos = previous_pos + off;
	  if (shouldbe_pos < 0)
	    {
	      /* that way we make sure that invalid seek errors later result
	         from seeking past eof, so we can enlarge the file */

	      err = EINVAL;
	      res = -1;
	    }
	  else if (previous_pos >= 0)
	    {
	      /* reset the error field */
	      LastError(f) = 0;
	      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,off,dir-1);
	      __wait_sync_packet(&f->f_sp);

	      if (LastError (f) == ERROR_SEEK_ERROR)
	        {
	          /* in this case, assume the user wanted to seek past eof.
	             Thus get the current eof position, so that we can
	             tell __extend_file how much to enlarge the file */
		  
		  LastError(f) = 0;
		  SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_END);
		  __wait_sync_packet (&f->f_sp);
	        }
	  
	      if (LastError (f) || LastResult (f) < 0)
	        {
	          err = __ioerr_to_errno(LastError(f));
	          res = -1;
	        }
	      else
	        {
	          err = 0;

	          if (previous_pos != shouldbe_pos)
		    {
		      SendPacket3(f,__srwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_CURRENT);
		      __wait_sync_packet (&f->f_sp);
		      if (LastError (f) || LastResult(f) < 0)
		        {
	                  err = __ioerr_to_errno(LastError(f));
	                  res = -1;
	                }
		      else
	                res = LastResult(f);

	              if (res >= 0 && res < shouldbe_pos && (f->f_flags & FWRITE))
	                {
	                  /* extend the file... */
		          res = __extend_file (f, shouldbe_pos - res, &err);
		        }
		    }
		  else
		    res = shouldbe_pos;
	        }
	    }
	  else
	    res = -1;
	  
	  LastResult(f) = 0;
	  __release_file (f);
	  syscall (SYS_sigsetmask, omask);
	  errno = err;
	  KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
	  return res;
	}
      else if (f->f_type == DTYPE_MEM)
	{
	  int real_off;
	  int old_off;

	  omask = syscall (SYS_sigsetmask, ~0);
	  __get_file (f);
	  old_off = f->f_mf.mf_offset;
	  
	  real_off = (dir == L_SET ? off : 
		      (dir == L_INCR ? 
		       old_off + off : f->f_stb.st_size + off));
	  if (real_off < 0) real_off = 0;
	  else if (real_off > f->f_stb.st_size) real_off = f->f_stb.st_size;
	  f->f_mf.mf_offset = real_off;
	  __release_file (f);
	  syscall (SYS_sigsetmask, omask);
	  return old_off;
	}
      else
	{
	  errno = ESPIPE;
	  KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
	}
    }
  else
    {
      errno = EBADF;
      KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    }

  return -1;
}

