/* $Id: compress.c,v 1.3 2001/07/31 03:20:52 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 <stdlib.h>
#include <stdio.h>
#include <utime.h>
#include <unistd.h>
#include <zlib.h>
#include "mkzftree.h"
#include "iso9660.h"


int block_compress_file(FILE *input, FILE *output, off_t size)
{
  struct compressed_file_header hdr;
  char inbuf[CBLOCK_SIZE], outbuf[2*CBLOCK_SIZE];
  size_t bytes, pointer_bytes, nblocks, block;
  uLong cbytes;			/* uLong is a zlib datatype */
  char *pointer_block, *curptr;
  off_t position;
  int i;
  int force_compress = opt.force;
  int zerr;
  int err = EX_SOFTWARE;
  
  if ( (sizeof hdr) & 3 ) {
    fputs("INTERNAL ERROR: header is not a multiple of 4\n", stderr);
    abort();
  }

  memset(&hdr, 0, sizeof hdr);
  memcpy(&hdr.magic, zisofs_magic, sizeof zisofs_magic);
  hdr.header_size = (sizeof hdr) >> 2;
  hdr.block_size = CBLOCK_SIZE_LG2;
  set_731(&hdr.uncompressed_len, size);

  if ( fwrite(&hdr, sizeof hdr, 1, output) != 1 )
    return EX_CANTCREAT;

  nblocks = (size+CBLOCK_SIZE-1) >> CBLOCK_SIZE_LG2;
  pointer_bytes = 4*(nblocks+1);
  pointer_block = xmalloc(pointer_bytes);
  memset(pointer_block, 0, pointer_bytes);

  if ( fseek(output, pointer_bytes, SEEK_CUR) == -1 ) {
    err = EX_CANTCREAT;
    goto free_ptr_bail;
  }

  curptr = pointer_block;
  position = sizeof hdr + pointer_bytes;
  
  block = 0;
  while ( (bytes = fread(inbuf, 1, CBLOCK_SIZE, input)) > 0 ) {
    if ( bytes < CBLOCK_SIZE && block < nblocks-1 ) {
      err = EX_IOERR;
      goto free_ptr_bail;
    }

    /* HACK: If the file has our magic number, always compress */
    if ( block == 0 && bytes >= sizeof zisofs_magic ) {
      if ( !memcmp(inbuf, zisofs_magic, sizeof zisofs_magic) )
	force_compress = 1;
    }

    set_731(curptr, position); curptr += 4;
    
    /* We have two special cases: a zero-length block is defined as all zero,
       and a block the length of which is equal to the block size is unencoded. */

    for ( i = 0 ; i < (int)CBLOCK_SIZE ; i++ ) {
      if ( inbuf[i] ) break;
    }

    if ( i == CBLOCK_SIZE ) {
      /* All-zero block.  No output */
    } else {
      cbytes = 2*CBLOCK_SIZE;
      if ( (zerr = compress2(outbuf, &cbytes, inbuf, bytes, opt.level))
	   != Z_OK ) {
	err = (zerr == Z_MEM_ERROR) ? EX_OSERR : EX_SOFTWARE;
	goto free_ptr_bail;	/* Compression failure */
      }
      if ( fwrite(outbuf, 1, cbytes, output) != cbytes ) {
	err = EX_CANTCREAT;
	goto free_ptr_bail;
      }
      position += cbytes;
    }
    block++;
  }

  /* Set pointer to the end of the final block */
  set_731(curptr, position);

  /* Now write the pointer table */
  if ( fseek(output, sizeof hdr, SEEK_SET) == -1 ||
       fwrite(pointer_block, 1, pointer_bytes, output) != pointer_bytes ) {
    err = EX_CANTCREAT;
    goto free_ptr_bail;
  }

  free(pointer_block);

  /* Now make sure that this was actually the right thing to do */
  if ( !force_compress && position >= size ) {
    /* Incompressible file, just copy it */
    rewind(input);
    rewind(output);

    position = 0;
    while ( (bytes = fread(inbuf, 1, CBLOCK_SIZE, input)) > 0 ) {
      if ( fwrite(inbuf, 1, bytes, output) != bytes )
	return EX_CANTCREAT;
      position += bytes;
    }

    /* Truncate the file to the correct size */
    fflush(output);
    ftruncate(fileno(output), position);
  }

  /* If we get here, we're done! */
  return 0;

  /* Common bailout code */
 free_ptr_bail:
  free(pointer_block);
  return err;
}

