/* $Id: uncompress.c,v 1.2 2001/07/27 19:49:08 hpa Exp $ */
/* ----------------------------------------------------------------------- *
 *   
 *   Copyright 2001 H. Peter Anvin - All Rights Reserved
 *
 *   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, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <zlib.h>
#include "mkzftree.h"
#include "iso9660.h"

int block_uncompress_file(FILE *input, FILE *output, off_t size)
{
  struct compressed_file_header hdr;
  char *inbuf, *outbuf;
  int block_shift;
  char *pointer_block, *pptr;
  unsigned long nblocks;
  unsigned long fullsize, block_size, block_size2;
  size_t ptrblock_bytes;
  unsigned long cstart, cend, csize;
  uLong bytes;
  int zerr;
  int err = EX_SOFTWARE;

  if ( (bytes = fread(&hdr, 1, sizeof hdr, input)) != sizeof hdr ) {
    if ( bytes == (size_t)size ) {
      /* Very short file; not compressed */
      return ( fwrite(&hdr, 1, bytes, output) != bytes ) ? EX_CANTCREAT : 0;
    } else {
      return EX_IOERR;		/* Read error */
    }
  }

  if ( memcmp(&hdr.magic, zisofs_magic, sizeof zisofs_magic) ) {
    inbuf = xmalloc(CBLOCK_SIZE);
    /* Not compressed */
    memcpy(inbuf, &hdr, sizeof hdr);
    bytes = sizeof hdr;
    do {
      if ( fwrite(inbuf, 1, bytes, output) != bytes )
	return EX_CANTCREAT;
    } while ( (bytes = fread(inbuf, 1, CBLOCK_SIZE, input)) > 0 );
    free(inbuf);
    return ferror(input) ? EX_IOERR : 0;
  }

  /* Now we know the file must be compressed.  Get the pointer table. */
  if ( fseek(input, hdr.header_size << 2, SEEK_SET) == -1 )
    return EX_IOERR;

  fullsize    = get_731(hdr.uncompressed_len);
  block_shift = hdr.block_size;
  block_size  = 1UL << block_shift;
  block_size2 = block_size << 1;
  inbuf  = xmalloc(block_size2);
  outbuf = xmalloc(block_size);

  nblocks = (fullsize + block_size - 1) >> block_shift;

  ptrblock_bytes = (nblocks+1) * 4;
  pointer_block = xmalloc(ptrblock_bytes);

  if ( (bytes = fread(pointer_block, 1, ptrblock_bytes, input)) != ptrblock_bytes ) {
    err = EX_IOERR;
    goto free_ptr_bail;
  }
  
  pptr = pointer_block;
  while ( fullsize ) {
    cstart = get_731(pptr);
    pptr += 4;
    cend   = get_731(pptr);

    csize = cend-cstart;

    if ( csize == 0 ) {
      memset(outbuf, 0, block_size);
      bytes = block_size;
    } else {
      if ( csize > block_size2 ) {
	err = EX_DATAERR;
	goto free_ptr_bail;
      }
      
      if ( fseek(input, cstart, SEEK_SET) == -1 ||
	   (bytes = fread(inbuf, 1, csize, input)) != csize ) {
	err = EX_IOERR;
	goto free_ptr_bail;
      }
      
      bytes = block_size;		/* Max output buffer size */
      if ( (zerr = uncompress(outbuf, &bytes, inbuf, csize)) != Z_OK ) {
	err = (zerr = Z_MEM_ERROR) ? EX_OSERR : EX_DATAERR;
	goto free_ptr_bail;
      }
    }
      
    if ( ((fullsize > block_size) && (bytes != block_size))
	 || ((fullsize <= block_size) && (bytes < fullsize)) ) {
      err = EX_DATAERR;
      goto free_ptr_bail;
    }
    
    if ( bytes > fullsize )
      bytes = fullsize;
    
    if ( fwrite(outbuf, 1, bytes, output) != bytes ) {
      err = EX_CANTCREAT;
      goto free_ptr_bail;
    }

    fullsize -= bytes;
  }

  err = 0;

 free_ptr_bail:
  free(pointer_block);
  free(inbuf);
  free(outbuf);
  return err;
}


