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

#define KERNEL
#include "ixemul.h"

#undef DEBUG

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif

/* standard functions.. could get overridden in a network environment */
extern int __ioctl (), __fselect (), __close ();

/* "normal" functions, means do half-async writes & sync reads */
extern int __write (), __read ();

/* sync-write, waits for completion of write, this gets enabled by
 * O_FSYNC */
extern int __sync_write ();

extern void __fasync_handler ();

/* incore functions */
extern int __mread (), __mclose ();

extern int __ioerr_to_errno ();

int
open (char *name, int mode, int perms)
{
  int fd;
  struct file *f;
  BPTR fh;
  int len, late_stat;
  int omask, error;

  mode = FFLAGS(mode);

DP(("open1: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));

  if ((errno = falloc (&f, &fd)) != 0) return -1;
  /* we now got the file, ie. since its count is > 0, no other process
   * will get it with falloc() */

DP(("open2: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));

DP(("open3: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
  /* init those fields */
  f->f_stb_dirty = 0;
  late_stat = 0;
  if (syscall (SYS_stat, name, &f->f_stb) < 0)
    {
      /* there can mainly be two reasons for stat() to fail. Either the
       * file really doesn't exist (ENOENT), or then the filesystem/handler
       * doesn't support file locks. */

      /* if we should get out of here without an error, init the stat
       * buffer after having opened the file with __fstat, this sets some
       * reasonable parameters (see end of function). */
      late_stat = 1;

#if 0
      /* GRRR this sounded too good to be true, stat("*") returns
       * ENOENT, so this trick doesn't work */

      /* if the file doesn't exist, this is an error if not O_CREAT */
      if ((errno == ENOENT) && !(mode&O_CREAT)) return -1;
#endif

      if ((errno == ENOENT) && (mode&O_CREAT))
	{
	  /* can't set permissions on an open file, so this has to be done
	   * by 'close' */
	  f->f_stb.st_mode = perms;
	  f->f_stb_dirty = 1;
	}

      /* so the file exists, it just doesn't cope with stat */
    }

DP(("open4: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
  f->f_flags = mode & FMASK;

  /* initialise the packet. The only thing needed at this time is its
   * header, filling in of port, action & args will be done when it's
   * used */
  __init_std_packet (&f->f_sp);

  /* ok, so lets try to open the file... */

  /* since we may obtain a filehandle, that wouldn't be closed if we break
   * too early, inhibit signals */
  omask = syscall (SYS_sigsetmask, ~0);

  if (S_ISDIR (f->f_stb.st_mode) && !(mode & FWRITE))
    {
      if (convert_dir (f, name) == 0)
        goto ret_ok;
      else
	goto error;
    }

DP(("open5: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
  fh = __open (name, MODE_OLDFILE);

  if (!fh && !(mode & O_CREAT))
    {
      error = __ioerr_to_errno (IoErr ());
      goto error;
    }
  else if (fh && ((mode&(O_EXCL|O_CREAT))==(O_EXCL|O_CREAT)))
    {
      __Close (fh);
      error = EEXIST;
      goto error;
    }
  else if (!fh && (mode & O_CREAT))
    {
      fh = __open (name, MODE_NEWFILE);
    }
  else if (fh && (mode & O_TRUNC))
    {
      __Close (fh);
      fh = __open (name, MODE_NEWFILE);
    }

  if (! fh)
    {
      error = __ioerr_to_errno (IoErr ());
      goto error;
    }

  /* now.. we're lucky, we actually opened the file! */
  f->f_fh = (struct FileHandle *) BTOCPTR(fh);

  if (mode & FWRITE)
    f->f_write  = (mode & O_FSYNC) ? __sync_write : __write;
  if (mode & FREAD)
    f->f_read   = __read;

#if 0
  /* won't work that way, I'll think something up later.. */

  /* in this case, we have to setup a handler that is called when ever
   * something arrives at the rw-port, and then walk our filetable and
   * decide whether the packet belonged to a file that has FASYNC set */
  if (mode & FASYNC) signal (__rwport->mp_SigBit, __fasync_handler);
#endif

  f->f_ioctl  = __ioctl;
  f->f_select = __fselect;
  f->f_close  = __close;
  f->f_type   = DTYPE_FILE;

ret_ok:
  /*
   * have to use kmalloc() instead of malloc(), because this is no task-private
   * data, it could (in the future) be shared by other tasks 
   */
  f->f_name = (void *) kmalloc (strlen (name) + 1);
  strcpy (f->f_name, name);

  /* ok, we're almost done. If desired, init the stat buffer to the
   * information we can get from an open file descriptor */
  if (late_stat) __fstat (f);
  
  /* if the file qualifies, try to change it into a DTYPE_MEM file */
  if (!late_stat && f->f_stb.st_size < ix.ix_membuf_limit && mode == FREAD)
    {
      void *buf;
      
DP(("open6: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
      /* try to obtain the needed memory */
      buf = (void *) kmalloc (f->f_stb.st_size);
      if (buf)
	if (syscall (SYS_read, fd, buf, f->f_stb.st_size) == f->f_stb.st_size)
	  {
DP(("open7: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
	    __Close (CTOBPTR (f->f_fh));
DP(("open8: write (2,.. %s.\n", write (2, "WR2", 3) == 3 ? "works" : "doesn't work"));
	    f->f_type 		= DTYPE_MEM;
	    f->f_mf.mf_offset 	= 0;
	    f->f_mf.mf_buffer 	= buf;
	    f->f_read		= __mread;
	    f->f_close 		= __mclose;
	    f->f_ioctl		= 0;
	    f->f_select		= 0;
	  }
	else
	  kfree (buf);
    }

  syscall (SYS_sigsetmask, omask);

  errno = error;
  /* return the descriptor */
  return fd;

error:
  /* free the file */
  u.u_ofile[fd] = 0;
  f->f_count--;
  syscall (SYS_sigsetmask, omask);
  errno = error;
  return -1;
}
