/*
 * rgzip.c - A wrapper for gzip to make it more rsync friendly
 * By Martijn van Oosterhout <kleptog@svana.org>
 * Created Fri Nov 19 1999
 * 
 * First version, a bit slow. 3% increase in file size.
 *
 * See report at http://svana.org/~kleptog/rgzip.html
 *
 * Compile using: gcc -Wall -O2 -o rgzip rgzip.c
 */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>

#define MIN_BLOCK_SIZE 10240
#define CHECKSUM_BITS  15

#define HASH_MASK (~((~0)<<CHECKSUM_BITS))

char *gzip_args[] = { "gzip", "-vcn", NULL };

int main( int argc, char *argv[] )
{
  int fds[2];
  int pid;
  int lasttime = 0;
  int offset = 0;

  if( pipe( fds ) < 0 )
  {
    perror("rgzip: pipe");
    return 1;
  }
  
  pid = fork();
  
  if( pid == -1 )
  {
    perror("rgzip: fork");
    return 1;
  }
  
  if( pid == 0 )
  {
    dup2( fds[0], 0 );
    close( fds[0] );
    close( fds[1] );
    execvp("gzip", gzip_args );
    perror("rgzip: exec");
    return 1;
  }
  close( fds[0] );

  for(;;)
  {    
    int rollinghash = 0;

    char buffer[4096];
    int len;
      
    int i, start;

    len = read( 0, buffer, 4096 );
    
    if( len == -1 )
    {
      perror("rgzip: Reading stdin");
      return 1;
    }
    
    if( len == 0 )
      break;
    
    for( i=0, start = 0; i<len; i++ )
    {
      rollinghash <<= 1;
      rollinghash ^= buffer[i];
      
      lasttime++;
      
      if( lasttime > MIN_BLOCK_SIZE && (rollinghash & HASH_MASK) == 0 ) 
      {
        lasttime = 0;
        write( fds[1], buffer+start, i-start );
        close( fds[1] );  // Signal end of file to gzip
        start = i;
        fprintf(stderr,"Restarted gzip at offset %d\n", offset+start);
        while( waitpid( pid, NULL, 0 ) < 0 )
          ;
        
        if( pipe( fds ) < 0 )
        {
          perror("rgzip: pipe");
          return 1;
        }

        pid = fork(); 

        if( pid == -1 )
        {
          perror("rgzip: fork");
          return 1;
        }

        if( pid == 0 )
        {
          dup2( fds[0], 0 );
          close( fds[0] );
          close( fds[1] );
          execvp("gzip", gzip_args );
          perror("rgzip: exec");
          return 1;
        }
        close( fds[0] );
      }
    }  
    write( fds[1], buffer+start, i-start );
    offset += len;
  }
  return 0;
}
