/* $Id: read.c,v 1.2 2009/01/04 15:42:14 kiesling Exp $ */

/*
  This file is part of Ctalk.
  Copyright  2005-2009 Robert Kiesling, ctalk@ctalklang.org.
  Permission is granted to copy this software provided that this copyright 
  notice is included in all source code modules.

  This program 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 of the License, or
  (at your option) any later version.

  This program 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 this program; if not, write to the Free Software Foundation, 
  Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include "object.h"
#include "message.h"
#include "ctalk.h"

/*
 *  DJGPP can perform line-ending translation, so the functions
 *  below need to rely on errno, not the number of bytes read,
 *  to determine if an error occurred.
 */
#ifdef __DJGPP__

unsigned long int __perform_read (FILE *f, char *path, char buf[], 
				  long long int _stat_size) {
  unsigned long int __chars_read;
  errno = 0;
  __chars_read = fread ((void *)buf, sizeof(char), (size_t) _stat_size, f);
  if (errno)
    _warning ("read_file: %s: %s.", path, strerror (errno));
  if (!memcmp ((const void *)&buf[1], (const void *)"ELF", 
	       (unsigned long int)3))
    _warning ("read_file: %s: Attempt to read a binary file.\n", path);
  return __chars_read;
}

#else

size_t __perform_read (FILE *f, char *path, char buf[], 
		       long long int _stat_size) {
  size_t __chars_read;
  if ((__chars_read = fread ((void *)buf, sizeof(char), (size_t) 
			     _stat_size, f)) 
      != _stat_size)
    _warning ("read_file: %s: %s.", path, strerror (errno));
  if (!memcmp ((const void *)&buf[1], (const void *)"ELF", (size_t)3))
    _warning ("read_file: %s: Attempt to read a binary file.\n", path);
  return __chars_read;
}

#endif  /* #defined __DJGPP__ */

/* 
 *  Return the number of bytes read, or -1.
 */
#ifdef __DJGPP__
unsigned long int read_file (char *buf, char *path) {
#else
size_t read_file (char *buf, char *path) {
#endif

  struct stat statbuf;
  int r_stat = -1;
#ifdef __DJGPP__
  size_t chars_read;
#else
  size_t chars_read;
#endif
  off_t stat_size;
  FILE *f;

  if ((r_stat = stat (path, &statbuf)) != 0) 
    return r_stat;
  stat_size = statbuf.st_size;

  /* Check for directories in cases where '.' is an overloaded operator,
     in which case it gets statted as a file during library searches. */
  if (S_ISDIR (statbuf.st_mode)) {
    _warning ("read_file: %s is a directory.\n", path);
    return ERROR;
  }
  if ((f = fopen (path, FILE_READ_MODE)) == NULL) 
    return ERROR;
  chars_read = __perform_read (f, path, buf, (long long int)stat_size);
  fclose (f);

  return chars_read;
}

/*
 *  Ctpp version of read_file (), which allows for input
 *  from stdin.  If we can stat () an input file, then the function
 *  uses calloc () to size the buffer.  If the input is from stdin,
 *  then the function reads the stream to a temporary file, and 
 *  then allocates the input buffer from the temporary file.
 *
 *  Here also the program should free () the buffer after the input is 
 *  processed, although the program only processes one input at a time.
 */

#ifdef __DJGPP__
unsigned long int p_read_file (char **buf, char *path) {
#else
size_t p_read_file (char **buf, char *path) {
#endif

  struct stat statbuf;
  int r_stat = -1;
  int stdin_fileno;
#ifdef __DJGPP__
  unsigned long int chars_read;
#else
  size_t chars_read;
#endif
  ssize_t bytes_read;
  off_t stat_size;
  char streambuf[IO_BLKSIZE+1];
  FILE *f;

  if (!strcmp (path, "-")) {
    if ((stdin_fileno = fileno (stdin)) == ERROR)
      _error ("stdin: %s.", strerror (errno));

    create_tmp ();

    memset ((void *)streambuf, 0, sizeof (char) * IO_BLKSIZE);

    while ((bytes_read = read(stdin_fileno, streambuf, IO_BLKSIZE)) > 0) {
      streambuf[bytes_read] = 0;
      write_tmp (streambuf);
      memset ((void *)streambuf, 0, sizeof (char) * IO_BLKSIZE);
    }

    close_tmp ();

    if ((r_stat = stat (get_tmpname (), &statbuf)) != 0)
      return r_stat;
    stat_size = statbuf.st_size;

    if ((*buf = (char *)calloc (stat_size + 1, sizeof(char))) == NULL)
      _error ("%s: %s.", path, strerror (errno));

    if ((f = fopen (get_tmpname (), FILE_READ_MODE)) == NULL) 
      return ERROR;
    chars_read = __perform_read (f, path, *buf, (long long int)stat_size);
    fclose (f);

    unlink_tmp ();

  } else {

    if ((r_stat = stat (path, &statbuf)) != 0) 
      return r_stat;
    stat_size = statbuf.st_size;

    /* Check for directories in cases where '.' is an overloaded operator,
       in which case it gets statted as a file during library searches. */
    if (S_ISDIR (statbuf.st_mode)) {
      _warning ("read_file: %s is a directory.\n", path);
      return ERROR;
    }

    if ((*buf = (char *)calloc (stat_size + 1, sizeof(char))) == NULL)
      _error ("%s: %s.", path, strerror (errno));

    if ((f = fopen (path, FILE_READ_MODE)) == NULL) 
      return ERROR;
    chars_read = __perform_read (f, path, *buf, (long long int) stat_size);
    fclose (f);
  }

  return chars_read;
}


int is_binary_file (char *path) {
  FILE *f;
  char buf[64+1];
  unsigned long int __chars_read;
  int r;
  if ((f = fopen (path, "r")) == NULL) {
    if (errno)
      _warning ("is_binary_file: %s: %s.", path, strerror (errno));
    return ERROR;
  }
  __chars_read = fread ((void *)buf, sizeof(char), (size_t) 64, f);
#ifdef __DJGPP__
  if (!memcmp ((const void *)&buf[0], (const void *)"M", 
	       (unsigned long int)1) &&
      !memcmp ((const void *)&buf[1], (const void *)"Z", 
	       (unsigned long int)1))
    r = TRUE;
  else
    r = FALSE;
#else
  if (!memcmp ((const void *)&buf[1], (const void *)"ELF", 
	       (unsigned long int)3))
    r = TRUE;
  else
    r = FALSE;
#endif
  fclose (f);
  return r;
}
