/* Copyright (C) 2003  John Whitney
 *
 * 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.
 *
 * Author: John Whitney <jjw@linuxmail.org>
 */

#include <sys/types.h> 
#include <sys/wait.h>
#include <unistd.h> 
#include <sys/signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include "linklist.h"
#include "file.h"
#include <ctype.h>
#include <string>
#include <openssl/md5.h>
using namespace std;
bool force_overwrite = false, verbose = false, remove_intermediate = false,
     info_mode = false, ensure_md5sum = false, use_bdelta = false;
     
unsigned patch_compression_type = 0;
char *patch_compression = "0";
char *tmpdir;
unsigned numinputfname = 0;
char *inputfname[128];
unsigned numoldrepositories = 0;
string oldrepositories[128];
string newrepository;

//define recognized filetypes
const unsigned
  UNKNOWN_FMT	= 0, 
  BZIP2		= 1, 
  GZIP		= 2, 
  ZIP		= 3, 
  COMPRESS	= 4,
  TARBALL	= 5,
  DTU		= 6,
  BZIP2_OLD	= 7,
  XDELTA	= 8,
  BDELTA	= 9; //openoffice 1.0.2 & 1.0.3 use this format
const unsigned
  MAKEPATCH      = 1,
  PATCHFILE      = 2;
  
  
void showHelp() {
  printf("deltup version 0.4.2\n");
  printf("Bad usage.  Try one of:\n");
  printf("deltup -m[fvej] [bgz #] [-[dD] <repository>] package1 package2 patchfile #make patch\n");
  printf("deltup -p[fvir] [-[dD] <repository>] <patchfiles> #apply patches\n");
  printf("\n");
  printf("Flags: f = force overwrite output files\n");
  printf("       v = verbose\n");
  printf("       e = ensure md5sum is correct (for bzip2 < v. 1.0.0)\n");
  printf("       j = use new bdelta algorithm\n");
  printf("   b,g,z = compress package with bzip2, gzip, or internal zlib\n");
  printf("       i = don't actually do anything.  Just show info\n");
  printf("       r = if several patches to one source are given, merge\n");
  printf("           patches so they will apply in one step and don't\n"); 
  printf("           create intermediate files\n"); 
  printf("       d = new tarball belongs in <repository>\n");
  printf("       D = old tarball(s) can be found in <repository>\n");
  
  printf("See README in /usr/share/doc/deltup<ver> for examples and info\n");
  exit(0);
}


DList<char> tempfiles;
char tempname[8] = "/000000";
char *getTmpFilename() {
  string n = string(tmpdir)+tempname;
  char *name = new char[n.size()+1];
  strcpy(name, n.c_str());
  tempfiles.push_back(name);
  int i;
  for (i = 6; tempname[i]=='9'; --i)
    tempname[i]='0';
  tempname[i]++;
  return name;
}

int invoke_system(string prog, string args);
void doneTmpFile(char *c) {
  invoke_system("rm", string("-f ") + c);
  tempfiles.erase(tempfiles.find_first(c));
}


void cleanup() {
  if (verbose) printf("cleaning up\n");
  while (!tempfiles.empty()) {
    doneTmpFile(tempfiles.first->obj);
  }
  rmdir(tmpdir);
}



void error(string message) {
  fprintf(stderr, "error: %s\n", message.c_str());
  exit(1);
}

int invoke_system(string prog, string args) {
//  printf("%s\n", ret);
  int ret = system((prog + " " + args).c_str());
  if (WIFSIGNALED(ret) && 
      (WTERMSIG(ret) == SIGINT || WTERMSIG(ret) == SIGQUIT))
    error("Caught signal");
  if (ret==0x7F00) error(string("A required executable, ") + prog + ", was not found");
  return ret;
}

bool endsWith(const char *f, char *end) {
  return !strcmp(f+strlen(f)-strlen(end), end);
}

bool compressionTypeSupported(unsigned c) {
 return (c==UNKNOWN_FMT||c==BZIP2||c==GZIP||c==BZIP2_OLD);
}
unsigned read_ascii_octal(char *s) {
  int num = 0;
  for (char *i = s; i < s+12; ++i) 
    if (*i>='0'&&*i<='7') {
      num<<=3;
      num+=*i-'0';
    }
  return num;
}

bool unzipFileAs(const char *f, unsigned packageformat, char *unzippedfname) {

  char *command, *extra = "-c ";
  switch (packageformat) {
    case UNKNOWN_FMT: command = "cat"; extra=""; break;
    case BZIP2: command = "bunzip2"; break;
    case GZIP: command = "gunzip"; break;
//    case ZIP:
//     char path[256];
//     strcpy(path, tmpdir);
//     strcat(path, "/ziptemp");
//     mkdir(path, 700);
//     invoke_system(6, "zip 
//     command, f, " > ", unzipdir, "/", unzippedfname);
     
//     strcpy(command, "cat "); 
//     break;
  }
  return !invoke_system(command, string(extra)+f+" > "+unzippedfname+" 2> /dev/null");
}

bool unzipFile(const char *f, char *unzippedfname, unsigned &packageformat) {
  if (endsWith(f, "bz2")) packageformat = BZIP2; else 
  if (endsWith(f, "gz")) packageformat = GZIP; else
  if (endsWith(f, "zip")) error("zip files are unzipported (pardon the pun!)"); else
  if (endsWith(f, "lzw")) error("lzw files are not supported"); else
  if (endsWith(f, ".Z")) error(".Z compression not supported"); else
    packageformat = UNKNOWN_FMT; // assume no compression
  return unzipFileAs(f, packageformat, unzippedfname);
}

int determine_filetype(char *c) {
  if (!strncmp(c, "DTU", 3)) return DTU;
  if (!strncmp(c, "\037\213", 2) ||
      !strncmp(c, "\037\236", 2)) return GZIP;
  if (!strncmp(c, "BZ", 2)) return BZIP2;
  if (!strncmp(c, "PK\003\004", 2)) return ZIP;
  if (!strncmp(c, "%XDZ", 4)) return XDELTA;
  if (!strncmp(c, "BDT", 3)) return BDELTA;
  return UNKNOWN_FMT;
}

int determine_filetype(Injectable_IStream &f) {
  char c[4];
  f.read(c, 4);
  f.inject(c, 4);
  int fmt = determine_filetype(c);
  if (fmt!=UNKNOWN_FMT) return fmt;
    
  char tarheader[512];
  f.read(tarheader, 512);
  f.inject(tarheader, 512);
  if (strncmp(tarheader+257, "ustar", 5)==0) return TARBALL;
  return UNKNOWN_FMT;
}

int determine_filetype_fname(const char *fname) {
  FILE *f = fopen(fname, "rb");
  char header[4];
  fread(header, 1, 4, f);
  fclose(f);
  return determine_filetype(header);
}

unsigned getLenOfFile(const char *fname) {
  FILE *f = fopen(fname, "rb");
  fseek(f, 0, SEEK_END);
  unsigned len = ftell(f);
  fclose(f);
  return len;
}

bool fileExists(string fname) {
  FILE *f = fopen(fname.c_str(), "rb");
  bool exists = (f!=NULL);
  if (exists) fclose(f);
  return exists;
}

char *read_filename(IStream &f) {
    unsigned fnamelen = read_word(f);
    char *f1 = (char*)malloc(fnamelen+1);
    f.read(f1, fnamelen);
    f1[fnamelen] = 0;
    return f1;
}
void write_filename(FILE *f, string fname) {
  unsigned lenName = fname.size();
  write_word(f, lenName);
  fwrite(fname.c_str(), 1, lenName, f);
}

void read_md5sum(unsigned char *md, string fname) {
  FILE *f = fopen(fname.c_str(), "rb");
  
  unsigned char buf[4096];
  MD5_CTX c;
  MD5_Init(&c);
  unsigned numread;
  do {
    numread = fread(buf, 1, 4096, f);
    if (numread) MD5_Update(&c, buf, numread);
  } while (numread==4096);
  fclose(f);
  MD5_Final(md, &c);
}

bool md5_equal(unsigned char *md1, string fname) {
  unsigned char md2[16];
  read_md5sum(md2, fname);
  for (int i = 0; i < 16; ++i)
    if (md1[i]!=md2[i]) return false;
  return true;
}

void write_header(FILE *f) {
    fprintf(f, "DTU");
    write_word(f, 4); //FormatVersion
}
void read_header(IStream &f, unsigned &version, unsigned &numpatches) {
  char magic[3];
  f.read(magic, 3);
  if (strncmp(magic, "DTU", 3) != 0) error("Invalid patch file");
  version = read_word(f);
  if (version > 4) error("Cannot read package format.  Upgrade deltup");
  if (version==2) numpatches = read_word(f); else numpatches = 1;

}

int find_bz2_compression(const char *complete) {
//  char *complete = combineStrings(2, repository, fname);
  FILE *f = fopen(complete, "rb");

  char header[4];
  fread(header, 1, 4, f);
  fclose(f);
  return (header[3])-'0'; //assumes ASCII char set
}

bool old_bzip2_exists() {
      return !(system("bzip2_old 2> /dev/null")==0x7F00);
}


bool copy_bytes_to_file(IStream &infile, OStream &outfile, unsigned numleft);

void gzip_without_header(string in, string out, char *compression) {
/*
  invoke_system("cat", in + " | gzip -"+compression+
	              " > " + out);
  return;
*/
  
/*
  IFStream infile(in.c_str());
  OFStream outfile(out.c_str());
  unsigned char i_buf[4096];
  unsigned char o_buf[4096];
  z_stream z;
  z.zalloc = 0; 
  z.zfree = 0;
  z.opaque = 0;
  z.avail_in = 0;
  deflateInit(&z, *compression-'0');
  int b;
  do {
    if (!z.avail_in) {
      z.avail_in = infile.read(i_buf, 4096);
      z.next_in = i_buf;
    }
    z.avail_out = 4096;
    z.next_out = o_buf;
    if (!z.avail_in) 
      b = deflate(&z, Z_FINISH);
    else
      b = deflate(&z, Z_NO_FLUSH);
    if (b != Z_OK && b != Z_STREAM_END) {
        printf("here %i\n", b);
       error("error during zlib compression");
     }
    outfile.write(o_buf, 4096-z.avail_out);  
  } while (b != Z_STREAM_END);

  printf("filesize %i\n", getLenOfFile(out.c_str()));
*/  

  char *tempfile = getTmpFilename();
  
  invoke_system("cat", in + " | gzip -"+compression+
	              " > " + tempfile);
  
  unsigned filesize = getLenOfFile(tempfile);
  unsigned numtocrop = 10;
  char inbuf[12];
  IFStream *f = new IFStream(tempfile);
  char c = f->read(inbuf, 10);
  char flags = inbuf[3];
  
  if (flags & 2) {
    f->read(inbuf, 2);
    numtocrop+=2;
  }
  if (flags & 4) {
    unsigned extrafieldsize = read_word(*f);
    numtocrop+=2+extrafieldsize;
    while (extrafieldsize) 
      extrafieldsize -= f->read(inbuf, extrafieldsize<10?extrafieldsize:10);
  }
  if (flags & 8) {
    do {
      f->read(inbuf, 1); ++numtocrop;
    } while (inbuf[0]);
  }
  if (flags & 16) {
    do {
      f->read(inbuf, 1); ++numtocrop;
    } while (inbuf[0]);
  }
  if (flags & 32) {
    f->read(inbuf, 2);
    numtocrop += 2;
  }
  OFStream o(out.c_str());
  copy_bytes_to_file(*f, o, filesize-numtocrop);
  delete f;
  doneTmpFile(tempfile);

}



void createDelta(const char *oldfile, const char *newfile, char *patchfname) {
  if (numoldrepositories!=1) 
    error("multiple dirs not supported with the -m option");
  
  if (strchr(oldfile, '/') || strchr(newfile, '/'))
    printf("Warning: don't prefix packages with a path.  Use the -d and -D options\n");
        
  string oldrepository=oldrepositories[0];
  string file1 = oldrepository+oldfile, file2 = newrepository+newfile;
  if (!fileExists(file1)) error("cannot access first input file");
  if (!fileExists(file2)) error("cannot access second input file");
  if (!force_overwrite && fileExists(patchfname)) error("output file already exists");
  FILE *testaccess = fopen(patchfname, "wb");
  if (!testaccess) error("Access denied to output file");
  fclose(testaccess);
  invoke_system("rm", patchfname);

  unsigned compression_level, flags = 0;
  if (verbose) printf("Looking at package contents\n");
  char *f1Name = getTmpFilename(), *f2Name = getTmpFilename(),
       *deltaName = getTmpFilename(), *pristineName = getTmpFilename();
    
  unsigned f1Type, f2Type;
  if (!unzipFile(file1.c_str(), f1Name, f1Type)) 
    error("Cannot read first input file.  Error encountered during decompression");
  if (!unzipFile(file2.c_str(), f2Name, f2Type))
    error("Cannot read second input file.  Error encountered during decompression");

  unsigned char md5[16];
  read_md5sum(md5, string(f1Name));

  if (verbose) printf("Making package delta\n");
//    int errorcode = 
  char *zlibCompression = "0";
  if (patch_compression_type == 0) {
    zlibCompression = patch_compression;
  }
  if (use_bdelta)
    invoke_system("bdelta", string(f1Name)+" " + f2Name+" " + deltaName);
  else
    invoke_system("xdelta", string("delta -")+zlibCompression+" --pristine "+
                  f1Name+" "+
                  f2Name+" "+
                  deltaName+" 2> /dev/null");
//    printf("%i\n", errorcode);
  doneTmpFile(f1Name);
  if (verbose) printf("Ensuring MD5sum will be correct\n");
  if (f2Type==GZIP) {
    flags |= 1;
    char *compression_try = "968712534";
    char *gzip_temp = getTmpFilename();
    do {
      compression_level=(*compression_try-'0'); //assumes ASCII char set
      char compression_str[2] = {0,0};
      strncpy(compression_str, compression_try, 1);
      
      gzip_without_header(string(f2Name), gzip_temp, compression_str);
//	invoke_system("cat", string(f2Name)+" | gzip -"+compression_str+
//	              " > "+gzip_temp);
      if (use_bdelta)
        invoke_system("bdelta", string(gzip_temp)+" "+
	              file2+" "+pristineName);
      else
        invoke_system("xdelta", string("delta -")+zlibCompression+" --pristine "+ 
	                gzip_temp+" "+
	                file2+" "+pristineName+" 2> /dev/null");
      ++compression_try;
    } while (*compression_try && getLenOfFile(pristineName)>1024);
    if (!*compression_try) error("Unknown compression format");
    doneTmpFile(gzip_temp);
  } else if (f2Type==BZIP2) {
    compression_level=find_bz2_compression(file2.c_str());
    if (ensure_md5sum) {
      char *bzip_temp = getTmpFilename();
      char compression_str[4] = " -x";
	compression_str[2] = '0' + compression_level;
	invoke_system("bzip2", string(f2Name)+compression_str+
	              " -c > "+bzip_temp);
      if (invoke_system("cmp", string(bzip_temp)+" "+file2+
	                  " > /dev/null")) {
        f2Type=BZIP2_OLD;
        if (old_bzip2_exists()) {
	    invoke_system("bzip2_old", string(f2Name)+compression_str+
	                  " -c > "+bzip_temp);
          if (invoke_system("cmp", string(bzip_temp)+" "+file2+ 
                            " > /dev/null")) 
	      error("Cannot make correct patch.  Please report this to the Deltup maintainer");
	    if (verbose) printf("marking as old bzip2 format\n");  
	  } else {
          printf("This package was compressed using a old version of bzip2.  Please install a .9 version of bzip2 named bzip2_old in your path to verify md5sum is correct");
	  }
	} else
	  if (verbose) printf("bzip2 format is reliable\n");
      doneTmpFile(bzip_temp);
    }
  } else if (f2Type==UNKNOWN_FMT) {}
  doneTmpFile(f2Name);

  if (verbose) printf("Output package to: %s\n", patchfname);
  
  char *finalName = getTmpFilename();
  FILE *f = fopen(finalName, "wb");
  write_header(f);
  write_filename(f, oldfile);
  fwrite(md5, 1, 16, f);
  write_filename(f, newfile);
  read_md5sum(md5, file2);
  fwrite(md5, 1, 16, f);
  write_word(f, f1Type);
  write_word(f, f2Type);
  write_dword(f, compression_level);
  flags|=2; //md5 is after decompression
  write_dword(f, flags);

  unsigned len = getLenOfFile(deltaName);
  write_dword(f, len);
  fclose(f);
  invoke_system("cat", string(deltaName)+" >> "+finalName);
  doneTmpFile(deltaName);
  if (flags&1) {
    f = fopen(finalName, "ab");
    write_dword(f, getLenOfFile(pristineName));
    fclose(f);
    invoke_system("cat", string(pristineName)+" >> "+finalName);
    doneTmpFile(pristineName);
  }
  char *command;
  switch (patch_compression_type) {
    case GZIP: 
      invoke_system("gzip", string("-")+patch_compression+" -c "+finalName+" > "+patchfname);
    break;
    case BZIP2: 
      invoke_system("bzip2", string("-")+patch_compression+" -c "+finalName+" > "+patchfname);
    break;
    case UNKNOWN_FMT: 
      invoke_system("cat", string(finalName)+" > "+patchfname);
    break;
  }
  doneTmpFile(finalName);
  if (fileExists(patchfname)) {
    if (verbose) printf("All done\n");
    printf("Patch is %f times smaller.\n", 
      (double)(getLenOfFile(file2.c_str()))/getLenOfFile(patchfname));
  } else
    fprintf(stderr, "Couldn't output patch\n");
  
  
}

//returns true if all bytes were successfully copied
bool copy_bytes_to_file(IStream &infile, OStream &outfile, unsigned numleft) {
    size_t numread;
    do {
      char buf[1024];
      numread = infile.read(buf, numleft>1024?1024:numleft);
      if (outfile.write(buf, numread) != numread) 
        error("Could not write temporary data.  Possibly out of space");
      numleft-=numread;
    } while (numleft && !(numread < 1024 && numleft));
    return (numleft==0);
}

struct UncompressedFile {
  char *fname;
  char *tmpfname;
  unsigned type;
  unsigned compression_level;
  char *pristineName;
  bool has_md5;
  unsigned char md5[16];
};

void finalize_package(UncompressedFile &f) {
  char *finalName = getTmpFilename();
  char *command;
  switch (f.type) {
    case BZIP2_OLD: command = "bzip2_old"; break;
    case BZIP2: command = "bzip2"; break;
    case GZIP: command = "gzip"; break;
    case UNKNOWN_FMT: invoke_system("cat", string(f.tmpfname)+" | cat > "+tmpdir+finalName);

  }
  if (f.type==BZIP2 || f.type==BZIP2_OLD || f.type==GZIP) {
    char compress_str[16];
    if (f.compression_level) {
      compress_str[0] = '-';
      sprintf(compress_str+1, "%i", f.compression_level);
    }
    if (f.type==GZIP) 
      gzip_without_header(string(f.tmpfname), finalName, compress_str+1);
//      invoke_system("cat", string(f.tmpfname)+" | "+command+" "+compress_str+" -c > "+finalName);
    else
      invoke_system(command, string(compress_str)+" "+f.tmpfname+" -c > "+finalName);
  }
    if (f.pristineName) {
      int patchfiletype = determine_filetype_fname(f.pristineName);
      bool failure;
      if (patchfiletype == BDELTA)
        failure = invoke_system("bpatch", 
                                string(finalName)+" "+
                                newrepository+f.fname+" "+
					  f.pristineName);
      else if (patchfiletype == XDELTA) {
        failure = invoke_system("xdelta", 
          string("patch --pristine ")+f.pristineName+" "+
	    finalName+" "+newrepository+f.fname+" ");
	}
      else
        fprintf(stderr, "Unknown delta format.  Try upgrading deltup\n");
      if (failure) 
        fprintf(stderr, "permission denied on file: %s%s\n", newrepository.c_str(), f.fname);
      else 
        if (f.has_md5 && !md5_equal(f.md5, newrepository+f.fname)) 
	  fprintf(stderr, "MD5 check failed!!!\n");
    } else {
      invoke_system("mv", string(finalName)+" "+newrepository+f.fname);
      if (fileExists(finalName)) {
	fprintf(stderr, "Access denied\n");
	doneTmpFile(finalName); //rm PKGfinal only
      }
    }
  if (f.pristineName) {
    doneTmpFile(finalName);
//    f.pristineName=0;
  }
  doneTmpFile(f.tmpfname);
}
      
DList<UncompressedFile> unfinished;
void patchPackage(IStream &f) {
  unsigned version, numpatches;
  read_header(f, version, numpatches);
  
  if (version==1) error("Old patch.  Can be read by deltup <= 0.2.1.  Support removed before popuralized.");
  bool check_md5sums = version==4;
//  if (version<3) error("Old patch.  Cannot apply.  Try 0.2 series of deltup");
  for (unsigned patch = 0; patch < numpatches; ++patch) {
    UncompressedFile *oldpackage,
                     *newpackage;
    oldpackage = new UncompressedFile;
    newpackage = new UncompressedFile;
    oldpackage->has_md5=newpackage->has_md5=check_md5sums;
    oldpackage->fname = read_filename(f); //TODO: free later
    if (check_md5sums) f.read(oldpackage->md5, 16);
    newpackage->fname = read_filename(f);
    if (check_md5sums) f.read(newpackage->md5, 16);

    oldpackage->tmpfname = getTmpFilename();
    newpackage->tmpfname = getTmpFilename();
    oldpackage->type=read_word(f);
    newpackage->type=read_word(f);
    oldpackage->compression_level=9;
    newpackage->compression_level=read_dword(f)&15;
    unsigned flags = read_dword(f);
    oldpackage->pristineName=0;
    newpackage->pristineName=(flags&1)?getTmpFilename():0;
    char *deltaName = getTmpFilename();
    bool success;
    {
      OFStream o(deltaName);
      unsigned sizeofpatch = read_dword(f);
      success = sizeofpatch && copy_bytes_to_file(f, o, sizeofpatch); //read numleft and copy bytes
    }
    if (flags&1) {
      OFStream o(newpackage->pristineName);
      unsigned sizeofpatch = read_dword(f);
      success = success && sizeofpatch && copy_bytes_to_file(f, o, sizeofpatch); //same as above
    }
    if (version<4 && oldpackage->type==GZIP) //3 had a bug with gzip
      newpackage->pristineName=0;
    
    DLink<UncompressedFile> *s;
    for (s = unfinished.first; 
         s && strcmp(oldpackage->fname, s->obj->fname); 
         s = s->next) ;

    printf("%s -> %s: ", oldpackage->fname, newpackage->fname);
    fflush(stdout);
    if (!success) {printf("patch is truncated\n"); continue;}
    if (info_mode) {printf("\n"); continue;}
    
    string oldrepository=" ";
    for (int i = 0; i < numoldrepositories; ++i) {
      if (fileExists((oldrepositories[i]+oldpackage->fname).c_str())) {
        oldrepository = oldrepositories[i];
      }
    }

//    if (!fileExists((newrepository+oldpackage->fname).c_str()) && !s) {
    if (oldrepository==" " && !s) {
      printf("no source to patch.\n");
      continue;
    }
    if (!force_overwrite && fileExists((newrepository+newpackage->fname).c_str())) {// && !remove_intermediate) {
      printf("patch already applied.\n");
      continue;
    }

    if (!compressionTypeSupported(oldpackage->type)) {
        printf("Can't uncompress old package.  Upgrade deltup.\n");
	continue;
    } else if (!compressionTypeSupported(newpackage->type)) {
      printf("Can't re-compress package.  Upgrade deltup.\n");
      continue;
    } else if (newpackage->type==BZIP2_OLD) {
      if (!old_bzip2_exists()) {
        fprintf(stderr, "This package was compressed using a old version of bzip2. Please install a .9 version of bzip2 named bzip2_old in your path");
	continue;
      }
    }

    if (s) {
      delete oldpackage;
      oldpackage = s->obj;
    } else {
      if ((!(flags&2) && oldpackage->has_md5 && !md5_equal(oldpackage->md5, oldrepository+oldpackage->fname)) ||
          !unzipFileAs((oldrepository+oldpackage->fname).c_str(), oldpackage->type, oldpackage->tmpfname)) {
        printf("previous package is corrupt\n");
        continue;
      }
      if ((flags&2) && oldpackage->has_md5 && !md5_equal(oldpackage->md5, oldpackage->tmpfname)) {
        printf("previous package is corrupt\n");
        continue;
      }
    }
    int patchfiletype = determine_filetype_fname(deltaName);
    bool failure;
    if (patchfiletype == BDELTA)
      failure = invoke_system("bpatch", string(oldpackage->tmpfname)+" "+
					       newpackage->tmpfname+" "+
				               deltaName);
    else if (patchfiletype == XDELTA)
      failure =  invoke_system("xdelta", string("patch --pristine ")+deltaName+" "+
                      oldpackage->tmpfname+" "+
		          newpackage->tmpfname+" 2> /dev/null");
    else {
      fprintf(stderr, "Unknown delta format used.  Try upgrading deltup\n");
      continue;
    }
    if (failure) {
      printf("Error applying patch\n");
      continue;
    }
    doneTmpFile(deltaName);
    doneTmpFile(oldpackage->tmpfname);
//    if (oldpackage->pristineName) doneTmpFile(oldpackage->pristineName);

    if (remove_intermediate) {
//      invoke_system(4, "mv", f2Name, " ", f1Name, " -f");
      UncompressedFile *f = newpackage;
      if (s) {
        delete s->obj;
	  unfinished.erase(s);
      }
      unfinished.push_back(f);
    } else finalize_package(*newpackage);

    printf("OK\n");
  }

}

void applyPatchfile(char *fname);
void applyPatchfile(IStream &f) {
  char *fileName = getTmpFilename();
  {
    OFStream o(fileName);
    copy_bytes_to_file(f, o, unsigned(-1));
  }
  applyPatchfile(fileName);
}

void applyPatchfile(char *fname) {
  IStream *f = new IFStream(fname);
  Injectable_IStream f2(*f);
  if (((IFStream*)f)->bad()) {fprintf(stderr, "file is missing: %s\n", fname); exit(1);}
  int type = determine_filetype(f2);
  delete f;
  switch (type) {
    case GZIP: f = new GZ_IFStream(fname); break;
    case BZIP2: f = new BZ_IFStream(fname); break;
    case DTU: f = new IFStream(fname); break;
    case UNKNOWN_FMT: fprintf(stderr, "cannot read file %s\n", fname); exit(1);
    case TARBALL :
      f = new IFStream(fname);
      unsigned zero_count;
      zero_count = 0;
      char tarheader[512];
      do {
	f->read(tarheader, 512);
	if (tarheader[0]==0) ++zero_count;
	int size = read_ascii_octal(tarheader+124);
	if (size) {
          char *fileName = getTmpFilename();
          {OFStream o(fileName);
           copy_bytes_to_file(*f, o, size);
	  } 
	  applyPatchfile(fileName);
	  doneTmpFile(fileName);

          unsigned lastblocksize = size % 512;
	  if (lastblocksize==0) lastblocksize=512;
	  f->read(tarheader, 512 - lastblocksize);
	}
      } while (zero_count < 2);
      return;
  }
  Injectable_IStream infile(*f);
  type = determine_filetype(infile);
  if (type==DTU) patchPackage(infile); else 
	         applyPatchfile(infile);
}

int parse_args(int argc, char **argv) {
  unsigned commandtype = 0;
  bool nextNewRepository=false;
  bool nextOldRepositories=false;
  for (int i = 1; i < argc; ++i) {
    if (argv[i][0]=='-') {
      for (int j = 1; j < strlen(argv[i]); ++j) {
        unsigned oldcommandtype = commandtype; 
        switch (argv[i][j]) {
          case 'm' : commandtype = MAKEPATCH; break;
          case 'p' : commandtype = PATCHFILE; break;
          case 'i' : info_mode = true; break;
          case 'f' : force_overwrite = true; break;
          case 'v' : verbose = true; break;
          case 'r' : remove_intermediate = true; break;
          case 'd' : nextNewRepository=true; break;
          case 'D' : nextOldRepositories=true; break;
          case 'z' : patch_compression_type=128; break;
          case 'g' : patch_compression_type=GZIP; break;
          case 'b' : patch_compression_type=BZIP2; break;
	    case 'e' : ensure_md5sum=true; break;
	    case 'j' : use_bdelta=true; break;
	    default : printf("unknown option: %c\n", argv[i][j]); exit(1);
	  }
	  if (oldcommandtype&&commandtype!=oldcommandtype) 
	    error("cannot specify more than one of -mpc options");
      }
    } else if (patch_compression_type && strcmp(patch_compression, "0")==0) {
      if (strlen(argv[i]) > 1 || argv[i][0] > '9' || argv[i][0] < '1')
        error("compression option must precede a digit from 1-9");
      patch_compression = argv[i];
      if (patch_compression_type==128) patch_compression_type=0;
    } else if (nextNewRepository) {
      newrepository = string(argv[i])+"/";
      nextNewRepository=false;
    } else if (nextOldRepositories) {
      char *list = argv[i];
      while (*list) {
        while (isspace(*list)) ++list;
	char *p;
	for (p = list; *p && !isspace(*p); ++p) ;
	int num = p-list;
	if (num) {
	  char *oldrepository = (char*)calloc(num+2, 1);
	  strncpy(oldrepository, list, num);
	  strcat(oldrepository, "/");
	  oldrepositories[numoldrepositories] = oldrepository;
	  ++numoldrepositories;
	  free(oldrepository);
	  list = p;
	}
        nextOldRepositories=false;
      }
    } else { //assume filename
      if (numinputfname == 128)
        error("Maximum of 128 input files allowed in this version");
      inputfname[numinputfname] = argv[i];
      ++numinputfname;
    }
  }
  if (numoldrepositories==0) {
    oldrepositories[0]=newrepository;
    ++numoldrepositories;
  }
  if (nextNewRepository) error("must supply directory after -d option");
  if (nextOldRepositories) error("must supply directory after -D option");
  return commandtype;
}

int main(int argc, char *argv[]) {
  char *tmpenv =  getenv("TMPDIR");
  if (!tmpenv) tmpenv = "/tmp";
  char *tmptemplate = new char[strlen(tmpenv)+9];
  strcpy(tmptemplate, tmpenv);
  strcat(tmptemplate, "/.XXXXXX");

  tmpdir = mkdtemp(tmptemplate);
//  free(tmptemplate);
//  printf("%s\n", tmpdir);
  atexit(cleanup);

  int commandtype = parse_args(argc, argv);
  if (commandtype == MAKEPATCH) {
    if (numinputfname!=3) 
      showHelp();
    else
      createDelta(inputfname[0], inputfname[1], inputfname[2]);
  } else if (commandtype == PATCHFILE) {
    for (int filenum = 0; filenum < numinputfname; ++filenum)
      applyPatchfile(inputfname[filenum]);
    while (!unfinished.empty()) {
      finalize_package(*unfinished.first->obj);
      unfinished.erase(unfinished.first);
    }
  } else showHelp();
}


syntax highlighted by Code2HTML, v. 0.9.1