/* _vsopen.c (emx+gcc) -- Copyright (c) 1990-1996 by Eberhard Mattes */

#include <stdarg.h>
#include <io.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <emx/io.h>
#include <emx/syscalls.h>

/* Bugs: O_TRUNC|O_RDONLY not implemented */
/*       O_TEXT|O_WRONLY  does/can not overwrite Ctrl-Z */

#define FALSE   0
#define TRUE    1

int _fmode_bin;                 /* Set non-zero to make binary mode default */

#define SH_MASK           0x70

/* This function may be called as follows (assuming that oflag does
   not include O_CREAT nor O_SIZE):

   _vsopen (name, oflag, shflag)
   _vsopen (name, oflag | O_CREAT, shflag, pmode)
   _vsopen (name, oflag | O_SIZE, shflag, size)
   _vsopen (name, oflag | O_CREAT | O_SIZE, shflag, pmode, size) */

int _vsopen (const char *name, int oflag, int shflag, va_list va)
{
  int handle, flags, *pflags, saved_errno;
  int bits;
  char dummy, ctrlz_kludge = FALSE;
  unsigned long initial_size;

  if ((oflag & O_ACCMODE) == O_RDONLY && (oflag & (O_TRUNC|O_CREAT)))
    {
      errno = EINVAL;
      return -1;
    }

  bits = oflag & (O_ACCMODE|O_NDELAY|O_APPEND);
  if (oflag & O_BINARY)
    /* do nothing */;
  else if (oflag & O_TEXT)
    bits |= O_TEXT;
  else if (_fmode_bin == 0)          /* neither O_TEXT nor O_BINARY given */
    bits |= O_TEXT;

  if ((bits & O_TEXT) && (oflag & O_APPEND) && (oflag & O_ACCMODE) == O_WRONLY)
    {
      /* The caller requests to open a text file for appending in
         write-only.  To remove the Ctrl-Z (if there is any), we have
         to temporarily open the file in read/write mode. */

      flags = O_RDWR | (shflag & SH_MASK);
      ctrlz_kludge = TRUE;
    }
  else
    flags = (oflag & O_ACCMODE) | (shflag & SH_MASK);

  if (oflag & O_CREAT)
    {
      int attr, pmode;

      attr = 0;
      pmode = va_arg (va, int);
      if (!(pmode & S_IWRITE))
        attr |= _A_RDONLY;
      flags |= (attr << 8) | _SO_CREAT;
      if (oflag & O_EXCL)
        flags |= _SO_EXCL;
    }

  if (oflag & O_TRUNC)
    flags |= _SO_TRUNC;

  if (oflag & O_NOINHERIT)
    flags |= _SO_NOINHERIT;

  if (oflag & O_SYNC)
    flags |= _SO_SYNC;

  initial_size = 0;
  if (oflag & O_SIZE)
    {
      initial_size = va_arg (va, unsigned long);
      flags |= _SO_SIZE;
    }
    
  saved_errno = errno;
  handle = __open (name, flags, initial_size);
  if (handle < 0 && ctrlz_kludge && errno == EACCES)
    {
      /* Perhaps read access is denied.  Try again. */
      errno = saved_errno;
      ctrlz_kludge = FALSE;
      flags = (flags & ~O_ACCMODE) | (oflag & O_ACCMODE);
      handle = __open (name, flags, initial_size);
    }
  if (handle < 0)
    return -1;

  if (__ioctl1 (handle, 0) & 0x80)
    {
      bits |= F_DEV;
      oflag &= ~O_APPEND;
    }

  if (!(bits & F_DEV) && (bits & O_TEXT))
    {
      long size;

      /* Remove the last character of the file if it is a Ctrl-Z.  Do
         this even if O_SIZE is given as we don't know whether the
         file existed before; removing a spurious Ctrl-Z due to O_SIZE
         should be harmless. */

      /* DEC Pathworks seems to treat the offset as unsigned number.
         Therefore we do not use __lseek (handle, -1L, SEEK_END). */

      size = __lseek (handle, 0L, SEEK_END);
      if (size > 0 && __lseek (handle, size - 1, SEEK_SET) != -1L
          && __read (handle, &dummy, 1) == 1 && dummy == 0x1a)
        __ftruncate (handle, size - 1); /* Remove Ctrl-Z */
      __lseek (handle, 0L, SEEK_SET);
    }

  if (ctrlz_kludge)
    {
      /* Reopen the handle in write-only mode. */

      __close (handle);
      flags = (flags & ~O_ACCMODE) | (oflag & O_ACCMODE);
      flags &= ~_SO_EXCL;       /* Ignore O_EXCL */
      handle = __open (name, flags, initial_size);
      if (handle < 0)
        return -1;
    }

  pflags = _fd_init (handle);
  if (pflags == NULL)
    {
      __close (handle);
      errno = EMFILE;
      return -1;
    }

  *pflags = bits;

  /* When opening a file for appending, move to the end of the file.
     This is required for passing the handle to a child process. */

  if (!(bits & F_DEV) && (bits & O_APPEND))
    __lseek (handle, 0L, SEEK_END);
  errno = saved_errno;

  return handle;
}
