/* OS-specific IO functions for xcftools
 *
 * Copyright (C) 2006  Henning Makholm
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "xcftools.h"
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#if HAVE_MMAP
#include <sys/mman.h>
#endif

static FILE *xcfstream = 0 ;

void
free_or_close_xcf(void)
{
  if( xcf_file ) {
    if( xcfstream ) {
      munmap(xcf_file,xcf_length) ;
      fclose(xcfstream);
      xcf_file = 0 ;
      xcfstream = 0 ;
    } else {
      free(xcf_file) ;
      xcf_file = 0 ;
    }
  }
}

void
read_or_mmap_xcf(const char *filename,const char *unzipper)
{
  struct stat statbuf ;
  
  free_or_close_xcf() ;

  if( strcmp(filename,"-") != 0 ) {
    if( access(filename,R_OK) != 0 )
      FatalGeneric(21,"!%s",filename);
  }

  if( !unzipper ) {
    const char *pc ;
    pc = filename + strlen(filename) ;
    if( pc-filename > 2 && strcmp(pc-2,"gz") == 0 )
      unzipper = "zcat" ;
    else if ( pc-filename > 3 && strcmp(pc-3,"bz2") == 0 )
      unzipper = "bzcat" ;
    else
      unzipper = "" ;
  } else if( strcmp(unzipper,"cat") == 0 )
    unzipper = "" ;
  
  if( *unzipper ) {
    int pid, status, outfd ;
#if HAVE_MMAP
    xcfstream = tmpfile() ;
    if( !xcfstream )
      FatalUnexpected(_("!Cannot create temporary unzipped file"));
    outfd = fileno(xcfstream) ;
#else
    int fh[2] ;
    if( pipe(fh) < 0 )
      FatalUnexpected("!Cannot create pipe for %s",unzipper);
    xcfstream = fdopen(fh[1],"rb") ;
    if( !xcfstream )
      FatalUnexpected("!Cannot fdopen() unzipper pipe");
    outfd = fh[0] ;
#endif
    if( (pid = fork()) == 0 ) {
      /* We're the child */
      if( dup2(outfd,1) < 0 ) {
        perror("Cannot dup2 in unzip process");
        exit(127) ;
      }
      fclose(xcfstream) ;
      execlp(unzipper,unzipper,filename,NULL) ;
      fprintf(stderr,_("Cannot execute "));
      perror(unzipper);
      exit(126) ;
    }
#if HAVE_MMAP
    while( wait(&status) != pid )
      ;
    if( WIFEXITED(status) ) {
      status = WEXITSTATUS(status) ;
      if( status > 0 ) {
        fclose(xcfstream) ;
        xcfstream = 0 ;
        FatalGeneric(status,NULL);
      }
    } else {
      fclose(xcfstream) ;
      xcfstream = 0 ;
      FatalGeneric(126,_("%s terminated abnormally"),unzipper);
    }
#else
    close(fh[0]) ;
#endif
  } else if( strcmp(filename,"-") == 0 ) {
    xcfstream = fdopen(dup(0),"rb") ;
    if( !xcfstream )
      FatalUnexpected("!Cannot dup stdin for input") ;
  } else {
    xcfstream = fopen(filename,"rb") ;
    if( !xcfstream )
      FatalGeneric(21,_("!Cannot open %s"),filename);
  }
  /* OK, now we have an open stream ... */
  if( fstat(fileno(xcfstream),&statbuf) == 0 &&
      (statbuf.st_mode & S_IFMT) == S_IFREG ) {
    xcf_length = statbuf.st_size ;
#if HAVE_MMAP
    xcf_file = mmap(0,xcf_length,PROT_READ,MAP_SHARED,fileno(xcfstream),0);
    if( xcf_file != (void*)-1 )
      return ;
    if( errno != ENODEV ) {
      int saved = errno ;
      fclose(xcfstream) ;
      xcf_file = 0 ;
      errno = saved ;
      FatalUnexpected("!Could not mmap input");
    }
#endif
    xcf_file = malloc(xcf_length);
    if( xcf_file == 0 )
      FatalUnexpected(_("Out of memory for xcf data"));
    if( fread(xcf_file,1,xcf_length,xcfstream) != xcf_length ) {
      if( feof(xcfstream) )
        FatalUnexpected(_("XCF file shrunk while reading it"));
      else
        FatalUnexpected(_("!Could not read xcf data"));
    }
    fclose(xcfstream) ;
    xcfstream = 0 ;
  } else {
    size_t blocksize = 0x80000 ; /* 512 KB */
    xcf_length = 0 ;
    xcf_file = 0 ;
    while(1) {
      xcf_file = realloc(xcf_file,blocksize) ;
      if( xcf_file == 0 )
        FatalUnexpected(_("Out of memory for xcf data"));
      size_t actual = fread(xcf_file+xcf_length,1,blocksize-xcf_length,
                            xcfstream) ;
      xcf_length += actual ;
      if( feof(xcfstream) )
        break ;
      if( xcf_length < blocksize ) {
        FatalUnexpected(_("!Could not read xcf data")) ;
      }
      blocksize += (blocksize >> 1) & ~(size_t)0x3FFF ; /* 16 KB granularity */
    }
    fclose(xcfstream) ;
    xcfstream = 0 ;
  }
}
