/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *  Portions Copyright (C) 1994 Rafael W. Luebbert
 *
 *  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.
 *
 *  $Id: open.c,v 1.4 1994/06/19 15:14:07 rluebbert Exp $
 *
 *  $Log: open.c,v $
 *  Revision 1.4  1994/06/19  15:14:07  rluebbert
 *  *** empty log message ***
 *
 *  Revision 1.2  1992/07/28  00:32:04  mwild
 *  pass convert_dir the original signal mask, to check for pending signals
 *
 *  Revision 1.1  1992/05/14  19:55:40  mwild
 *  Initial revision
 *
 */

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

#include <string.h>

/* 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 (), __open ();

/* 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 late_stat;
  int omask, error;
  char ptymask = 0;
  int ptyindex = 0;
  int amode = 0, i;

  if (name == NULL)     /* sanity check */
    return EACCES;
  mode = FFLAGS(mode);

  /* inhibit signals */
  omask = syscall (SYS_sigsetmask, ~0);

  error = falloc (&f, &fd);
  if (error)
    {
      syscall (SYS_sigsetmask, omask);
      errno = error;
      KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
      return -1;
    }
  /* we now got the file, ie. since its count is > 0, no other process
   * will get it with falloc() */

  /* init those fields */
  f->f_stb_dirty = 0;
  late_stat = 0;
  if (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 ((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 & ~u.u_cmask);
	  f->f_stb_dirty = 1;
	}
    }

  f->f_flags = mode & FMASK;
  f->f_ttyflags = IXTTY_ICRNL | IXTTY_OPOST | IXTTY_ONLCR;

  /* 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);
  __init_std_packet (&f->f_select_sp);

  /* check for case-sensitive filename */
  if ((mode & O_CASE) && !late_stat && filenamecmp(name))
    {
      error = ENOENT;
      goto error;
    }

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

  /* do this *only* if the stat() above was successful !! */
  if (!late_stat && S_ISDIR (f->f_stb.st_mode) && !(mode & FWRITE))
    {
      if (convert_dir (f, name, omask))
        goto ret_ok;
      else
	goto error;
    }

  /* filter invalid modes */
  switch (mode & (O_CREAT|O_TRUNC|O_EXCL))
    {
    case O_EXCL:
    case O_EXCL|O_TRUNC:
      /* can never succeed ! */
      error = EINVAL;
      goto error;

    case O_CREAT|O_EXCL:
    case O_CREAT|O_EXCL|O_TRUNC:
      if (! late_stat)
        {
	  error = EEXIST;
	  goto error;
	}
      break;
    }

  amode = (mode & O_CREAT) ? MODE_READWRITE : MODE_OLDFILE;

  if (!strcmp(name, "/dev/tty"))
    name = "*";
  else if ((i = is_pseudoterminal(name)))
    {
      char *orig_name = name;
      char mask;

      name = "/fifo/ptyXX/rweksm";
      memcpy(name + 7, orig_name + i + 1, 4);
      name[17] = (orig_name[i] == 'p' ? 'm' : 'c');
      mask = (name[17] == 'm' ? IX_PTY_MASTER : IX_PTY_SLAVE);
      ptyindex = (name[9] - 'p') * 16 + name[10] - (name[10] >= 'a' ? 'a' - 10 : '0');
      ix_lock_base();
      if (ix.ix_ptys[ptyindex] & mask)
        {
          ix_unlock_base();
          error = EIO;
          goto error;
        }
      ptymask = mask;
      ix.ix_ptys[ptyindex] |= (mask & IX_PTY_OPEN); /* mark pty in use */
      ix_unlock_base();
    }
  do
  {
    if (!strcmp(name, "*") || !strcasecmp(name, "console:"))
      /* Temporary patch for KingCON 1.3, which seems to have problems with
	 ACTION_FINDINPUT of "*"/"console:" when "dp_Port" of the packet is
	 not set to sender's "pr_MsgPort" - that's what IXEmul makes on
	 clients' startup during initialization of "stderr". */
      fh = Open(name, amode);
    else
      fh = __open (name, amode);

    if (! fh)
      {
        int err = IoErr();

        /* For those handlers that do not understand MODE_READWRITE (e.g. PAR: ) */
        if (err == ERROR_ACTION_NOT_KNOWN && amode == MODE_READWRITE)
        {
          amode = MODE_NEWFILE;
        }
        else
        {
          error = __ioerr_to_errno (err);
          goto error;
        }
      }
  } while (!fh);

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

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

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

  /*
   * 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);
  if (f->f_name)
    strcpy (f->f_name, name);

ret_ok:
  /* 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_type == DTYPE_FILE 
      && f->f_stb.st_size < ix.ix_membuf_limit && mode == FREAD)
    {
      void *buf;
      
      /* 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)
	  {
	    __Close (CTOBPTR (f->f_fh));
	    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);

  if (error)
    {
      errno = error;
      KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
    }
  if (!error && (mode & O_TRUNC) && (amode != MODE_NEWFILE))
    syscall(SYS_ftruncate, fd, 0);

  /* return the descriptor */
  return fd;

error:
  /* free the file */
  u.u_ofile[fd] = 0;
  if (ptymask)
    {
      ix_lock_base();
      ix.ix_ptys[ptyindex] &= ~(ptymask & IX_PTY_OPEN);
      ix_unlock_base();
    }
  f->f_count--;
  syscall (SYS_sigsetmask, omask);
  errno = error;
  KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
  return -1;
}
