/* Implement Input/Output runtime actions for CHILL.
   Copyright (C) 1992,1993 Free Software Foundation, Inc.
   Author: Wilfried Moser, et al
   
   This file is part of GNU CC.
   
   GNU CC is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.
   
   GNU CC 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 General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with GNU CC; see the file COPYING.  If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* As a special exception, if you link this library with other files,
   some of which are compiled with GCC, to produce an executable,
   this library does not by itself cause the resulting executable
   to be covered by the GNU General Public License.
   This exception does not however invalidate any other reasons why
   the executable file might be covered by the GNU General Public License.  */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>

#include <string.h>
#include <stdlib.h>

#include "fileio.h"

#ifndef PATH_MAX
#define PATH_MAX _POSIX_PATH_MAX
#endif

static
void
GetSetAttributes( Association_Mode* the_assoc )
{
  struct stat statbuf;
  int retco;

  if( (retco = stat( the_assoc->pathname, &statbuf )) )
    return;

  if( S_ISREG(statbuf.st_mode) )
    {
      SET_FLAG( the_assoc, IO_EXISTING );
      if( !TEST_FLAG( the_assoc, IO_VARIABLE ) )
        SET_FLAG( the_assoc, IO_INDEXABLE );
    }
  else
    if( S_ISCHR(statbuf.st_mode) || S_ISFIFO(statbuf.st_mode) )
      {
	SET_FLAG( the_assoc, IO_EXISTING );
	CLR_FLAG( the_assoc, IO_INDEXABLE );
      }
  SET_FLAG( the_assoc, IO_SEQUENCIBLE );

  /* FIXME: File size and computation of number of records for outoffile ? */

  if( !access( the_assoc->pathname, R_OK ) )
    SET_FLAG( the_assoc, IO_READABLE );
  if( !access( the_assoc->pathname, W_OK ) )
    SET_FLAG( the_assoc, IO_WRITEABLE );
}

static
void 
makeName( Association_Mode* the_assoc, char* the_path, int the_path_len,
	 char* file, int line)
{
  int namlen;
  if( ! the_assoc->pathname && 
      ! (the_assoc->pathname = (char*)malloc( PATH_MAX )) )
    CHILLEXCEPTION( file, line, SPACEFAIL, PATHNAME_ALLOC );

  if( the_path[0] != DIRSEP )
    {
      if( !getcwd( the_assoc->pathname, PATH_MAX ) )
	{
	  the_assoc->syserrno = errno;
	  CHILLEXCEPTION( file, line, ASSOCIATEFAIL, GETCWD_FAILS );
	}
      namlen = strlen( the_assoc->pathname );
      the_assoc->pathname[namlen++] = DIRSEP;  
    }
  else
    namlen = 0;

  strncpy( the_assoc->pathname + namlen, the_path, the_path_len );
  the_assoc->pathname[namlen+the_path_len] = '\0';
}

/*
 * ASSOCIATE
 */
/* Caution: returns an Association mode location (!) */
Association_Mode*
__associate( Association_Mode* the_assoc,
	     char*             the_path,
	     int               the_path_len,
	     char*             the_mode,
	     int               the_mode_len,
	     char*             file,
	     int               line )
{
  if( !the_assoc )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ASSOCIATION );

  if( TEST_FLAG(the_assoc, IO_ISASSOCIATED) )
    CHILLEXCEPTION( file, line, ASSOCIATEFAIL, IS_ASSOCIATED );

  /* clear all flags */
  the_assoc->flags = 0;

  if( ! the_path_len )
    CHILLEXCEPTION( file, line, ASSOCIATEFAIL, NO_PATH_NAME );

  makeName( the_assoc, the_path, the_path_len, file, line );
  GetSetAttributes( the_assoc );

  CLR_FLAG( the_assoc, IO_VARIABLE );
  if ( the_mode )
    {
      if( !strncmp( the_mode, "VARIABLE", 8 ) )
	{
	  SET_FLAG( the_assoc, IO_VARIABLE );
	  CLR_FLAG( the_assoc, IO_INDEXABLE );
	}
      else
	if( strlen( the_mode ) )
	  CHILLEXCEPTION( file, line, ASSOCIATEFAIL, INVALID_ASSOCIATION_MODE );
    }

  SET_FLAG( the_assoc, IO_ISASSOCIATED );
  return the_assoc;
}

/*
 *  DISSOCIATE
 */
void
__dissociate( Association_Mode* the_assoc, char* file, int line )
{
  if( !the_assoc )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ASSOCIATION );

  if( !TEST_FLAG( the_assoc, IO_ISASSOCIATED ) )
    CHILLEXCEPTION( file, line, NOTASSOCIATED, IS_NOT_ASSOCIATED );

  if( the_assoc->access )
    __disconnect( the_assoc->access, file, line );

  the_assoc->access = NULL;
  CLR_FLAG( the_assoc, IO_ISASSOCIATED );

  /* free allocated memory */
  if (the_assoc->pathname)
    {
      free (the_assoc->pathname);
      the_assoc->pathname = 0;
    }
  if (the_assoc->bufptr)
    {
      free (the_assoc->bufptr);
      the_assoc->bufptr = 0;
    }
}

/*
 * CREATE
 */
void __create( Association_Mode* the_assoc, char* file, int line )
{
  if( !the_assoc )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ASSOCIATION );

  if( !TEST_FLAG( the_assoc, IO_ISASSOCIATED ) )
    CHILLEXCEPTION( file, line, NOTASSOCIATED, IS_NOT_ASSOCIATED );

  if( TEST_FLAG( the_assoc, IO_EXISTING ) )
    CHILLEXCEPTION( file, line, CREATEFAIL, FILE_EXISTING );

  if( (the_assoc->handle = open( the_assoc->pathname, O_CREAT+O_TRUNC+O_WRONLY, 0666 ))
      == -1 )
      CHILLEXCEPTION( file, line, CREATEFAIL, CREATE_FAILS );

  the_assoc->usage = ReadWrite;
  GetSetAttributes( the_assoc );

  close( the_assoc->handle );
}

/*
 * MODIFY
 */
void
__modify( Association_Mode* the_assoc,
	  char*             the_path,
	  int               the_path_len,
	  char*             the_mode,
	  int               the_mode_len,
	  char*             file,
	  int               line )
{
  if( !the_assoc )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ASSOCIATION );

  if( !TEST_FLAG( the_assoc, IO_ISASSOCIATED ) )
    CHILLEXCEPTION( file, line, NOTASSOCIATED, IS_NOT_ASSOCIATED );

  if( the_path_len )
    {
      char* oldname;

      if( ! (oldname = (char*)malloc( PATH_MAX )) )
	CHILLEXCEPTION( file, line, SPACEFAIL, PATHNAME_ALLOC );
      strcpy( oldname, the_assoc->pathname );

      makeName( the_assoc, the_path, the_path_len, file, line );

      if( rename( oldname, the_assoc->pathname ) )
	{
	  free( oldname );
	  CHILLEXCEPTION( file, line, MODIFYFAIL, RENAME_FAILS );
	}
      free( oldname );
    }
  else
    {
      /* FIXME: other options? */
    }
}

static
/*** char* DirMode[] = { "rb", "r+b", "r+b" }; ***/
int DirMode[] = { O_RDONLY, O_RDWR, O_RDWR };

static
/*** char* SeqMode [] = { "rb", "r+b", "r+b" }; ***/
int SeqMode[] = { O_RDONLY, O_RDWR, O_RDWR };

/*
 * CONNECT
 */
void
__connect( void*             the_transfer,
	   Association_Mode* the_assoc,
	   Usage_Mode        the_usage,
	   Where_Mode        the_where,
	   Boolean           with_index,
	   signed long       the_index,
	   char*             file,
	   int               line )
{
  Access_Mode*  the_access;
  off_t         filepos;
  off_t         savepos;
  char          dummy;
  unsigned long nbytes;
  int           oflag;

  if( !the_transfer )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ACCESS );
  if( !the_assoc )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ASSOCIATION );

  if( TEST_FLAG((Text_Mode*)the_transfer, IO_TEXTLOCATION ))
    {
      if( ! ((Text_Mode*)the_transfer)->access_sub )
	CHILLEXCEPTION( file, line, EMPTY, NO_ACCESS_SUBLOCATION );
      the_access = ((Text_Mode*)the_transfer)->access_sub;
      SET_FLAG( the_access, IO_TEXTIO );
    }
  else
    {
      the_access = (Access_Mode*)the_transfer;
      CLR_FLAG( the_access, IO_TEXTIO );
    }

  /* FIXME: This should be an (implementation-dependent) static check
     if( with_index && the_access->rectype > Fixed )
     CHILLEXCEPTION( file, line, CONNECTFAIL, IMPL_RESTRICTION );
     */

  if( ! TEST_FLAG(the_assoc, IO_ISASSOCIATED) )
    CHILLEXCEPTION( file, line, NOTASSOCIATED, IS_NOT_ASSOCIATED );

  if( ! TEST_FLAG( the_assoc, IO_EXISTING ) )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_EXISTING );

  if( ! TEST_FLAG( the_assoc, IO_READABLE ) &&
      ( the_usage = ReadOnly || the_usage == ReadWrite ) )    
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_READABLE );

  if( ! TEST_FLAG( the_assoc, IO_WRITEABLE ) &&
      ( the_usage = WriteOnly || the_usage == ReadWrite ) )    
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_WRITEABLE );

  if( ! TEST_FLAG( the_assoc, IO_INDEXABLE ) 
      && TEST_FLAG( the_access, IO_INDEXED ) )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_INDEXABLE );

  if( ! TEST_FLAG( the_assoc, IO_SEQUENCIBLE ) 
      && ! TEST_FLAG( the_access, IO_INDEXED ) )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_SEQUENCIBLE );

  if( the_where == Same && the_assoc->access == NULL )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NO_CURRENT_POS );

  /* This dynamic condition is not checked for text connections. */
  if( ! TEST_FLAG( the_access, IO_TEXTIO ) )
    if( ! TEST_FLAG( the_assoc, IO_VARIABLE ) 
	&& the_access->rectype > Fixed 
	&& ( the_usage == WriteOnly || the_usage == ReadWrite ) )
      CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_VARIABLE );
 
  if( TEST_FLAG( the_assoc, IO_VARIABLE )
      && the_access->rectype == Fixed 
      && ( the_usage == ReadOnly || the_usage == ReadWrite ) )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_FIXED );
 
  if( ! TEST_FLAG( the_access, IO_INDEXED ) && the_usage == ReadWrite )
    CHILLEXCEPTION( file, line, CONNECTFAIL, NOT_INDEXED );

  /* Access location may be connected to a different association. */
  if( the_access->association && the_access->association != the_assoc )
    __disconnect( the_access, file, line );

  /* Is the association location already connected? */
  if( the_assoc->access )
    {
      /* save position just in case we need it for the_where == Same */
      if( (savepos = lseek( the_assoc->handle, 0L, SEEK_CUR )) == -1L )
	CHILLEXCEPTION( file, line, CONNECTFAIL, LSEEK_FAILS );

      /* text: read correction, flush buffer */
      if( the_assoc->bufptr ){
	savepos -= the_assoc->bufptr->len - the_assoc->bufptr->cur;
	the_assoc->bufptr->len = the_assoc->bufptr->cur = 0;
      }

      /* implicit disconnect */
      __disconnect( the_assoc->access, file, line );
    }

  the_assoc->usage = the_usage;
  CLR_FLAG( the_access, IO_OUTOFFILE );
 
  if( TEST_FLAG( the_access, IO_INDEXED ) )
    {
      if( (the_assoc->handle = open( the_assoc->pathname, DirMode[the_usage] )) == -1 )
	CHILLEXCEPTION( file, line, CONNECTFAIL, OPEN_FAILS );

      /* Set base index. */
      switch( the_where )
	{
	case First: 
	  filepos = 0;
	  break;
	case Same: 
	  filepos = savepos;
	  break;
	case Last: 
	  if( lseek( the_assoc->handle, 0L, SEEK_END ) == -1L )
	    CHILLEXCEPTION( file, line, CONNECTFAIL, LSEEK_FAILS );
	  filepos = lseek( the_assoc->handle, 0L, SEEK_CUR );
	  break;
	}

      /* Set current index */
      if( with_index )
	{
	  if( the_index < the_access->lowindex
	      || the_access->highindex < the_index )
	    CHILLEXCEPTION( file, line, RANGEFAIL, BAD_INDEX );
	  filepos += (the_index - the_access->lowindex) * the_access->reclength;
	}
      if( lseek( the_assoc->handle, filepos, SEEK_SET ) == -1L )
	CHILLEXCEPTION( file, line, CONNECTFAIL, LSEEK_FAILS );
      the_access->base = filepos;
    }
  else
    {
      /* for association to text for reading: allocate buffer */
      if( TEST_FLAG((Text_Mode*)the_transfer, IO_TEXTLOCATION ) &&
	  the_usage == ReadOnly &&
	  !the_assoc->bufptr )
	{
	  if( ! (the_assoc->bufptr = (readbuf_t*)malloc( sizeof(readbuf_t) )) )
	    CHILLEXCEPTION( file, line, CONNECTFAIL, BUFFER_ALLOC ); 
	  memset (the_assoc->bufptr, 0, sizeof (readbuf_t));
	}
      if( (the_assoc->handle = open( the_assoc->pathname, SeqMode[the_usage] )) == -1 )
	CHILLEXCEPTION( file, line, CONNECTFAIL, OPEN_FAILS );

      /* Set base index. */
      switch( the_where )
	{
	case First: 
	  filepos = 0;
	  break;
	case Same: 
	  filepos = savepos;
	  break;
	case Last:
	  if( lseek( the_assoc->handle, 0L, SEEK_END ) == -1L )
	    CHILLEXCEPTION( file, line, CONNECTFAIL, LSEEK_FAILS );
	  filepos = lseek( the_assoc->handle, 0L, SEEK_CUR );
	  break;
	}

      /* file truncation for sequential, Write Only */
      /***************************** FIXME: cannot truncate at Same
	if( the_usage == WriteOnly )
	{
	if( fseek( the_assoc->file_ptr, filepos, SEEK_SET ) == -1L )
        CHILLEXCEPTION( file, line, CONNECTFAIL, FSEEK_FAILS );
	fclose( the_assoc->file_ptr );
	if( !(the_assoc->file_ptr = fopen( the_assoc->pathname, "ab" )) )
        CHILLEXCEPTION( file, line, CONNECTFAIL, OPEN_FAILS );
	}
	else
	***************************/
      if( (filepos = lseek( the_assoc->handle, filepos, SEEK_SET )) == -1L )
	CHILLEXCEPTION( file, line, CONNECTFAIL, LSEEK_FAILS );
    }

  the_access->association = the_assoc;
  the_assoc->access = the_access;
  /* for text: set carriage control default */
  if( TEST_FLAG((Text_Mode*)the_transfer, IO_TEXTLOCATION ) ){
    the_assoc->ctl_pre  = '\0';
    the_assoc->ctl_post = '\n';
  }
}

void
__disconnect( void* the_transfer, char* file, int line )
{
  Access_Mode* the_access;

  if( !the_transfer )
    CHILLEXCEPTION( file, line, EMPTY, NULL_ACCESS );

  if( TEST_FLAG((Text_Mode*)the_transfer, IO_TEXTLOCATION ))
    {
      the_access = ((Text_Mode*)the_transfer)->access_sub;
      CLR_FLAG( the_access, IO_TEXTIO );
    }
  else
    the_access = (Access_Mode*)the_transfer;

  if( !the_access->association )
    CHILLEXCEPTION( file, line, NOTCONNECTED, IS_NOT_CONNECTED );

  close( the_access->association->handle );
  /* FIXME: check result */

  if( the_access->store_loc )
    free( the_access->store_loc );
  the_access->store_loc           = NULL;
  the_access->association->access = NULL;
  the_access->association         = NULL;
}
