X-Git-Url: https://git.saurik.com/apt.git/blobdiff_plain/16d7341fce96b089aa2a1c241acd0a72209bcd7f..6aef1942f441e6e667982b92802907026d8cc7c6:/methods/rred.cc

diff --git a/methods/rred.cc b/methods/rred.cc
index 27d95bdde..bb801cb4e 100644
--- a/methods/rred.cc
+++ b/methods/rred.cc
@@ -1,262 +1,730 @@
+// Copyright (c) 2014 Anthony Towns
+//
+// 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.
+
+#include <config.h>
+
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/acquire-method.h>
 #include <apt-pkg/strutl.h>
 #include <apt-pkg/hashes.h>
+#include <apt-pkg/configuration.h>
+#include "aptmethod.h"
 
-#include <sys/stat.h>
-#include <unistd.h>
-#include <utime.h>
-#include <stdio.h>
+#include <stddef.h>
+#include <iostream>
+#include <string>
+#include <list>
+#include <vector>
+
+#include <assert.h>
 #include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
 #include <apti18n.h>
 
-/* this method implements a patch functionality similar to "patch --ed" that is
- * used by the "tiffany" incremental packages download stuff. it differs from 
- * "ed" insofar that it is way more restricted (and therefore secure). in the
- * moment only the "c", "a" and "d" commands of ed are implemented (diff 
- * doesn't output any other). additionally the records must be reverse sorted 
- * by line number and may not overlap (diff *seems* to produce this kind of 
- * output). 
- * */
+#define BLOCK_SIZE (512*1024)
 
-const char *Prog;
+class MemBlock {
+   char *start;
+   size_t size;
+   char *free;
+   MemBlock *next;
+
+   explicit MemBlock(size_t size) : size(size), next(NULL)
+   {
+      free = start = new char[size];
+   }
+
+   size_t avail(void) { return size - (free - start); }
 
-class RredMethod : public pkgAcqMethod
-{
-   bool Debug;
-   // the size of this doesn't really matter (except for performance)    
-   const static int BUF_SIZE = 1024;
-   // the ed commands
-   enum Mode {MODE_CHANGED, MODE_DELETED, MODE_ADDED};
-   // return values
-   enum State {ED_OK, ED_ORDERING, ED_PARSER, ED_FAILURE};
-   // this applies a single hunk, it uses a tail recursion to 
-   // reverse the hunks in the file
-   int ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line, 
-      char *buffer, unsigned int bufsize, Hashes *hash);
-   // apply a patch file
-   int ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file, Hashes *hash);
-   // the methods main method
-   virtual bool Fetch(FetchItem *Itm);
-   
    public:
-   
-   RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {};
+
+   MemBlock(void) {
+      free = start = new char[BLOCK_SIZE];
+      size = BLOCK_SIZE;
+      next = NULL;
+   }
+
+   ~MemBlock() {
+      delete [] start;
+      delete next;
+   }
+
+   void clear(void) {
+      free = start;
+      if (next)
+	 next->clear();
+   }
+
+   char *add_easy(char *src, size_t len, char *last)
+   {
+      if (last) {
+	 for (MemBlock *k = this; k; k = k->next) {
+	    if (k->free == last) {
+	       if (len <= k->avail()) {
+		  char *n = k->add(src, len);
+		  assert(last == n);
+		  if (last == n)
+		     return NULL;
+		  return n;
+	       } else {
+		  break;
+	       }
+	    } else if (last >= start && last < free) {
+	       break;
+	    }
+	 }
+      }
+      return add(src, len);
+   }
+
+   char *add(char *src, size_t len) {
+      if (len > avail()) {
+	 if (!next) {
+	    if (len > BLOCK_SIZE)  {
+	       next = new MemBlock(len);
+	    } else {
+	       next = new MemBlock;
+	    }
+	 }
+	 return next->add(src, len);
+      }
+      char *dst = free;
+      free += len;
+      memcpy(dst, src, len);
+      return dst;
+   }
 };
 
-int RredMethod::ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line, 
-      char *buffer, unsigned int bufsize, Hashes *hash) {
-   int pos;
-   int startline;
-   int stopline;
-   int mode;
-   int written;
-   char *idx;
-
-   /* get the current command and parse it*/
-   if (fgets(buffer, bufsize, ed_cmds) == NULL) {
-      return line;
+struct Change {
+   /* Ordering:
+    *
+    *   1. write out <offset> lines unchanged
+    *   2. skip <del_cnt> lines from source
+    *   3. write out <add_cnt> lines (<add>/<add_len>)
+    */
+   size_t offset;
+   size_t del_cnt;
+   size_t add_cnt; /* lines */
+   size_t add_len; /* bytes */
+   char *add;
+
+   explicit Change(size_t off)
+   {
+      offset = off;
+      del_cnt = add_cnt = add_len = 0;
+      add = NULL;
    }
-   startline = strtol(buffer, &idx, 10);
-   if (startline < line) {
-      return ED_ORDERING;
+
+   /* actually, don't write <lines> lines from <add> */
+   void skip_lines(size_t lines)
+   {
+      while (lines > 0) {
+	 char *s = (char*) memchr(add, '\n', add_len);
+	 assert(s != NULL);
+	 s++;
+	 add_len -= (s - add);
+	 add_cnt--;
+	 lines--;
+	 if (add_len == 0) {
+	    add = NULL;
+	    assert(add_cnt == 0);
+	    assert(lines == 0);
+	 } else {
+	    add = s;
+	    assert(add_cnt > 0);
+	 }
+      }
    }
-   if (*idx == ',') {
-      idx++;
-      stopline = strtol(idx, &idx, 10);
+};
+
+class FileChanges {
+   std::list<struct Change> changes;
+   std::list<struct Change>::iterator where;
+   size_t pos; // line number is as far left of iterator as possible
+
+   bool pos_is_okay(void) const
+   {
+#ifdef POSDEBUG
+      size_t cpos = 0;
+      std::list<struct Change>::const_iterator x;
+      for (x = changes.begin(); x != where; ++x) {
+	 assert(x != changes.end());
+	 cpos += x->offset + x->add_cnt;
+      }
+      return cpos == pos;
+#else
+      return true;
+#endif
    }
-   else {
-      stopline = startline;
+
+   public:
+   FileChanges() {
+      where = changes.end();
+      pos = 0;
    }
-   if (*idx == 'c') {
-      mode = MODE_CHANGED;
-	   if (Debug == true) {
-		   std::clog << "changing from line " << startline 
-			     << " to " << stopline << std::endl;
-	   }
+
+   std::list<struct Change>::iterator begin(void) { return changes.begin(); }
+   std::list<struct Change>::iterator end(void) { return changes.end(); }
+
+   std::list<struct Change>::reverse_iterator rbegin(void) { return changes.rbegin(); }
+   std::list<struct Change>::reverse_iterator rend(void) { return changes.rend(); }
+
+   void add_change(Change c) {
+      assert(pos_is_okay());
+      go_to_change_for(c.offset);
+      assert(pos + where->offset == c.offset);
+      if (c.del_cnt > 0)
+	 delete_lines(c.del_cnt);
+      assert(pos + where->offset == c.offset);
+      if (c.add_len > 0) {
+	 assert(pos_is_okay());
+	 if (where->add_len > 0)
+	    new_change();
+	 assert(where->add_len == 0 && where->add_cnt == 0);
+
+	 where->add_len = c.add_len;
+	 where->add_cnt = c.add_cnt;
+	 where->add = c.add;
+      }
+      assert(pos_is_okay());
+      merge();
+      assert(pos_is_okay());
    }
-   else if (*idx == 'a') {
-      mode = MODE_ADDED;
-	   if (Debug == true) {
-		   std::clog << "adding after line " << startline << std::endl;
-	   }
+
+   private:
+   void merge(void)
+   {
+      while (where->offset == 0 && where != changes.begin()) {
+	 left();
+      }
+      std::list<struct Change>::iterator next = where;
+      ++next;
+
+      while (next != changes.end() && next->offset == 0) {
+	 where->del_cnt += next->del_cnt;
+	 next->del_cnt = 0;
+	 if (next->add == NULL) {
+	    next = changes.erase(next);
+	 } else if (where->add == NULL) {
+	    where->add = next->add;
+	    where->add_len = next->add_len;
+	    where->add_cnt = next->add_cnt;
+	    next = changes.erase(next);
+	 } else {
+	    ++next;
+	 }
+      }
    }
-   else if (*idx == 'd') {
-      mode = MODE_DELETED;
-	   if (Debug == true) {
-		   std::clog << "deleting from line " << startline 
-			     <<  " to " << stopline << std::endl;
-	   }
+
+   void go_to_change_for(size_t line)
+   {
+      while(where != changes.end()) {
+	 if (line < pos) {
+	    left();
+	    continue;
+	 }
+	 if (pos + where->offset + where->add_cnt <= line) {
+	    right();
+	    continue;
+	 }
+	 // line is somewhere in this slot
+	 if (line < pos + where->offset) {
+	    break;
+	 } else if (line == pos + where->offset) {
+	    return;
+	 } else {
+	    split(line - pos);
+	    right();
+	    return;
+	 }
+      }
+      /* it goes before this patch */
+      insert(line-pos);
    }
-   else {
-      return ED_PARSER;
+
+   void new_change(void) { insert(where->offset); }
+
+   void insert(size_t offset)
+   {
+      assert(pos_is_okay());
+      assert(where == changes.end() || offset <= where->offset);
+      if (where != changes.end())
+	 where->offset -= offset;
+      changes.insert(where, Change(offset));
+      --where;
+      assert(pos_is_okay());
    }
-   /* get the current position */
-   pos = ftell(ed_cmds);
-   /* if this is add or change then go to the next full stop */
-   if ((mode == MODE_CHANGED) || (mode == MODE_ADDED)) {
-      do {
-         fgets(buffer, bufsize, ed_cmds);
-         while ((strlen(buffer) == (bufsize - 1)) 
-               && (buffer[bufsize - 2] != '\n')) {
-            fgets(buffer, bufsize, ed_cmds);
-            buffer[0] = ' ';
-         }
-      } while (strncmp(buffer, ".", 1) != 0);
+
+   void split(size_t offset)
+   {
+      assert(pos_is_okay());
+
+      assert(where->offset < offset);
+      assert(offset < where->offset + where->add_cnt);
+
+      size_t keep_lines = offset - where->offset;
+
+      Change before(*where);
+
+      where->del_cnt = 0;
+      where->offset = 0;
+      where->skip_lines(keep_lines);
+
+      before.add_cnt = keep_lines;
+      before.add_len -= where->add_len;
+
+      changes.insert(where, before);
+      --where;
+      assert(pos_is_okay());
+   }
+
+   void delete_lines(size_t cnt)
+   {
+      std::list<struct Change>::iterator x = where;
+      assert(pos_is_okay());
+      while (cnt > 0)
+      {
+	 size_t del;
+	 del = x->add_cnt;
+	 if (del > cnt)
+	    del = cnt;
+	 x->skip_lines(del);
+	 cnt -= del;
+
+	 ++x;
+	 if (x == changes.end()) {
+	    del = cnt;
+	 } else {
+	    del = x->offset;
+	    if (del > cnt)
+	       del = cnt;
+	    x->offset -= del;
+	 }
+	 where->del_cnt += del;
+	 cnt -= del;
+      }
+      assert(pos_is_okay());
+   }
+
+   void left(void) {
+      assert(pos_is_okay());
+      --where;
+      pos -= where->offset + where->add_cnt;
+      assert(pos_is_okay());
    }
-   /* do the recursive call */
-   line = ed_rec(ed_cmds, in_file, out_file, line, buffer, bufsize, 
-         hash);
-   /* pass on errors */
-   if (line < 0) {
-      return line;
+
+   void right(void) {
+      assert(pos_is_okay());
+      pos += where->offset + where->add_cnt;
+      ++where;
+      assert(pos_is_okay());
    }
-   /* apply our hunk */
-   fseek(ed_cmds, pos, SEEK_SET); 
-   /* first wind to the current position */
-   if (mode != MODE_ADDED) {
-      startline -= 1;
+};
+
+class Patch {
+   FileChanges filechanges;
+   MemBlock add_text;
+
+   static bool retry_fwrite(char *b, size_t l, FileFd &f, Hashes *hash)
+   {
+      if (f.Write(b, l) == false)
+	 return false;
+      if (hash)
+	 hash->Add((unsigned char*)b, l);
+      return true;
    }
-   while (line < startline) {
-      fgets(buffer, bufsize, in_file);
-      written = fwrite(buffer, 1, strlen(buffer), out_file);
-      hash->Add((unsigned char*)buffer, written);
-      while ((strlen(buffer) == (bufsize - 1)) 
-            && (buffer[bufsize - 2] != '\n')) {
-         fgets(buffer, bufsize, in_file);
-         written = fwrite(buffer, 1, strlen(buffer), out_file);
-         hash->Add((unsigned char*)buffer, written);
+
+   static void dump_rest(FileFd &o, FileFd &i, Hashes *hash)
+   {
+      char buffer[BLOCK_SIZE];
+      unsigned long long l = 0;
+      while (i.Read(buffer, sizeof(buffer), &l)) {
+	 if (l ==0  || !retry_fwrite(buffer, l, o, hash))
+	    break;
       }
-      line++;
    }
-   /* include from ed script */
-   if ((mode == MODE_ADDED) || (mode == MODE_CHANGED)) {
-      do {
-         fgets(buffer, bufsize, ed_cmds);
-         if (strncmp(buffer, ".", 1) != 0) {
-            written = fwrite(buffer, 1, strlen(buffer), out_file);
-            hash->Add((unsigned char*)buffer, written);
-            while ((strlen(buffer) == (bufsize - 1)) 
-                  && (buffer[bufsize - 2] != '\n')) {
-               fgets(buffer, bufsize, ed_cmds);
-               written = fwrite(buffer, 1, strlen(buffer), out_file);
-               hash->Add((unsigned char*)buffer, written);
-            }
-         }
-         else {
-            break;
-         }
-      } while (1);
+
+   static void dump_lines(FileFd &o, FileFd &i, size_t n, Hashes *hash)
+   {
+      char buffer[BLOCK_SIZE];
+      while (n > 0) {
+	 if (i.ReadLine(buffer, sizeof(buffer)) == NULL)
+	    buffer[0] = '\0';
+	 size_t const l = strlen(buffer);
+	 if (l == 0 || buffer[l-1] == '\n')
+	    n--;
+	 retry_fwrite(buffer, l, o, hash);
+      }
    }
-   /* ignore the corresponding number of lines from input */
-   if ((mode == MODE_DELETED) || (mode == MODE_CHANGED)) {
-      while (line < stopline) {
-         fgets(buffer, bufsize, in_file);
-         while ((strlen(buffer) == (bufsize - 1)) 
-               && (buffer[bufsize - 2] != '\n')) {
-            fgets(buffer, bufsize, in_file);
-         }
-         line++;
+
+   static void skip_lines(FileFd &i, int n)
+   {
+      char buffer[BLOCK_SIZE];
+      while (n > 0) {
+	 if (i.ReadLine(buffer, sizeof(buffer)) == NULL)
+	    buffer[0] = '\0';
+	 size_t const l = strlen(buffer);
+	 if (l == 0 || buffer[l-1] == '\n')
+	    n--;
       }
    }
-   return line;
-}
 
-int RredMethod::ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file, 
-      Hashes *hash) {
-   char buffer[BUF_SIZE];
-   int result;
-   int written;
-   
-   /* we do a tail recursion to read the commands in the right order */
-   result = ed_rec(ed_cmds, in_file, out_file, 0, buffer, BUF_SIZE, 
-         hash);
-   
-   /* read the rest from infile */
-   if (result >= 0) {
-      while (fgets(buffer, BUF_SIZE, in_file) != NULL) {
-         written = fwrite(buffer, 1, strlen(buffer), out_file);
-         hash->Add((unsigned char*)buffer, written);
+   static void dump_mem(FileFd &o, char *p, size_t s, Hashes *hash) {
+      retry_fwrite(p, s, o, hash);
+   }
+
+   public:
+
+   bool read_diff(FileFd &f, Hashes * const h)
+   {
+      char buffer[BLOCK_SIZE];
+      bool cmdwanted = true;
+
+      Change ch(std::numeric_limits<size_t>::max());
+      if (f.ReadLine(buffer, sizeof(buffer)) == NULL)
+	 return _error->Error("Reading first line of patchfile %s failed", f.Name().c_str());
+      do {
+	 if (h != NULL)
+	    h->Add(buffer);
+	 if (cmdwanted) {
+	    char *m, *c;
+	    size_t s, e;
+	    errno = 0;
+	    s = strtoul(buffer, &m, 10);
+	    if (unlikely(m == buffer || s == std::numeric_limits<unsigned long>::max() || errno != 0))
+	       return _error->Error("Parsing patchfile %s failed: Expected an effected line start", f.Name().c_str());
+	    else if (*m == ',') {
+	       ++m;
+	       e = strtol(m, &c, 10);
+	       if (unlikely(m == c || e == std::numeric_limits<unsigned long>::max() || errno != 0))
+		  return _error->Error("Parsing patchfile %s failed: Expected an effected line end", f.Name().c_str());
+	       if (unlikely(e < s))
+		  return _error->Error("Parsing patchfile %s failed: Effected lines end %lu is before start %lu", f.Name().c_str(), e, s);
+	    } else {
+	       e = s;
+	       c = m;
+	    }
+	    if (s > ch.offset)
+	       return _error->Error("Parsing patchfile %s failed: Effected line is after previous effected line", f.Name().c_str());
+	    switch(*c) {
+	       case 'a':
+		  cmdwanted = false;
+		  ch.add = NULL;
+		  ch.add_cnt = 0;
+		  ch.add_len = 0;
+		  ch.offset = s;
+		  ch.del_cnt = 0;
+		  break;
+	       case 'c':
+		  if (unlikely(s == 0))
+		     return _error->Error("Parsing patchfile %s failed: Change command can't effect line zero", f.Name().c_str());
+		  cmdwanted = false;
+		  ch.add = NULL;
+		  ch.add_cnt = 0;
+		  ch.add_len = 0;
+		  ch.offset = s - 1;
+		  ch.del_cnt = e - s + 1;
+		  break;
+	       case 'd':
+		  if (unlikely(s == 0))
+		     return _error->Error("Parsing patchfile %s failed: Delete command can't effect line zero", f.Name().c_str());
+		  ch.offset = s - 1;
+		  ch.del_cnt = e - s + 1;
+		  ch.add = NULL;
+		  ch.add_cnt = 0;
+		  ch.add_len = 0;
+		  filechanges.add_change(ch);
+		  break;
+	       default:
+		  return _error->Error("Parsing patchfile %s failed: Unknown command", f.Name().c_str());
+	    }
+	 } else { /* !cmdwanted */
+	    if (strcmp(buffer, ".\n") == 0) {
+	       cmdwanted = true;
+	       filechanges.add_change(ch);
+	    } else {
+	       char *last = NULL;
+	       char *add;
+	       size_t l;
+	       if (ch.add)
+		  last = ch.add + ch.add_len;
+	       l = strlen(buffer);
+	       add = add_text.add_easy(buffer, l, last);
+	       if (!add) {
+		  ch.add_len += l;
+		  ch.add_cnt++;
+	       } else {
+		  if (ch.add) {
+		     filechanges.add_change(ch);
+		     ch.del_cnt = 0;
+		  }
+		  ch.offset += ch.add_cnt;
+		  ch.add = add;
+		  ch.add_len = l;
+		  ch.add_cnt = 1;
+	       }
+	    }
+	 }
+      } while(f.ReadLine(buffer, sizeof(buffer)));
+      return true;
+   }
+
+   void write_diff(FileFd &f)
+   {
+      unsigned long long line = 0;
+      std::list<struct Change>::reverse_iterator ch;
+      for (ch = filechanges.rbegin(); ch != filechanges.rend(); ++ch) {
+	 line += ch->offset + ch->del_cnt;
+      }
+
+      for (ch = filechanges.rbegin(); ch != filechanges.rend(); ++ch) {
+	 std::list<struct Change>::reverse_iterator mg_i, mg_e = ch;
+	 while (ch->del_cnt == 0 && ch->offset == 0)
+	    ++ch;
+	 line -= ch->del_cnt;
+	 std::string buf;
+	 if (ch->add_cnt > 0) {
+	    if (ch->del_cnt == 0) {
+	       strprintf(buf, "%llua\n", line);
+	    } else if (ch->del_cnt == 1) {
+	       strprintf(buf, "%lluc\n", line+1);
+	    } else {
+	       strprintf(buf, "%llu,%lluc\n", line+1, line+ch->del_cnt);
+	    }
+	    f.Write(buf.c_str(), buf.length());
+
+	    mg_i = ch;
+	    do {
+	       dump_mem(f, mg_i->add, mg_i->add_len, NULL);
+	    } while (mg_i-- != mg_e);
+
+	    buf = ".\n";
+	    f.Write(buf.c_str(), buf.length());
+	 } else if (ch->del_cnt == 1) {
+	    strprintf(buf, "%llud\n", line+1);
+	    f.Write(buf.c_str(), buf.length());
+	 } else if (ch->del_cnt > 1) {
+	    strprintf(buf, "%llu,%llud\n", line+1, line+ch->del_cnt);
+	    f.Write(buf.c_str(), buf.length());
+	 }
+	 line -= ch->offset;
       }
    }
-   else {
-      return ED_FAILURE;
+
+   void apply_against_file(FileFd &out, FileFd &in, Hashes *hash = NULL)
+   {
+      std::list<struct Change>::iterator ch;
+      for (ch = filechanges.begin(); ch != filechanges.end(); ++ch) {
+	 dump_lines(out, in, ch->offset, hash);
+	 skip_lines(in, ch->del_cnt);
+	 dump_mem(out, ch->add, ch->add_len, hash);
+      }
+      dump_rest(out, in, hash);
    }
-   return ED_OK;
-}
+};
 
+class RredMethod : public aptMethod {
+   private:
+      bool Debug;
+
+      struct PDiffFile {
+	 std::string FileName;
+	 HashStringList ExpectedHashes;
+	 PDiffFile(std::string const &FileName, HashStringList const &ExpectedHashes) :
+	    FileName(FileName), ExpectedHashes(ExpectedHashes) {}
+      };
+
+      HashStringList ReadExpectedHashesForPatch(unsigned int const patch, std::string const &Message)
+      {
+	 HashStringList ExpectedHashes;
+	 for (char const * const * type = HashString::SupportedHashes(); *type != NULL; ++type)
+	 {
+	    std::string tagname;
+	    strprintf(tagname, "Patch-%d-%s-Hash", patch, *type);
+	    std::string const hashsum = LookupTag(Message, tagname.c_str());
+	    if (hashsum.empty() == false)
+	       ExpectedHashes.push_back(HashString(*type, hashsum));
+	 }
+	 return ExpectedHashes;
+      }
 
-bool RredMethod::Fetch(FetchItem *Itm)
+   protected:
+      virtual bool URIAcquire(std::string const &Message, FetchItem *Itm) APT_OVERRIDE {
+	 Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
+	 URI Get = Itm->Uri;
+	 std::string Path = Get.Host + Get.Path; // rred:/path - no host
+
+	 FetchResult Res;
+	 Res.Filename = Itm->DestFile;
+	 if (Itm->Uri.empty())
+	 {
+	    Path = Itm->DestFile;
+	    Itm->DestFile.append(".result");
+	 } else
+	    URIStart(Res);
+
+	 std::vector<PDiffFile> patchfiles;
+	 Patch patch;
+
+	 if (FileExists(Path + ".ed") == true)
+	 {
+	    HashStringList const ExpectedHashes = ReadExpectedHashesForPatch(0, Message);
+	    std::string const FileName = Path + ".ed";
+	    if (ExpectedHashes.usable() == false)
+	       return _error->Error("No hashes found for uncompressed patch: %s", FileName.c_str());
+	    patchfiles.push_back(PDiffFile(FileName, ExpectedHashes));
+	 }
+	 else
+	 {
+	    _error->PushToStack();
+	    std::vector<std::string> patches = GetListOfFilesInDir(flNotFile(Path), "gz", true, false);
+	    _error->RevertToStack();
+
+	    std::string const baseName = Path + ".ed.";
+	    unsigned int seen_patches = 0;
+	    for (std::vector<std::string>::const_iterator p = patches.begin();
+		  p != patches.end(); ++p)
+	    {
+	       if (p->compare(0, baseName.length(), baseName) == 0)
+	       {
+		  HashStringList const ExpectedHashes = ReadExpectedHashesForPatch(seen_patches, Message);
+		  if (ExpectedHashes.usable() == false)
+		     return _error->Error("No hashes found for uncompressed patch %d: %s", seen_patches, p->c_str());
+		  patchfiles.push_back(PDiffFile(*p, ExpectedHashes));
+		  ++seen_patches;
+	       }
+	    }
+	 }
+
+	 std::string patch_name;
+	 for (std::vector<PDiffFile>::iterator I = patchfiles.begin();
+	       I != patchfiles.end();
+	       ++I)
+	 {
+	    patch_name = I->FileName;
+	    if (Debug == true)
+	       std::clog << "Patching " << Path << " with " << patch_name
+		  << std::endl;
+
+	    FileFd p;
+	    Hashes patch_hash(I->ExpectedHashes);
+	    // all patches are compressed, even if the name doesn't reflect it
+	    if (p.Open(patch_name, FileFd::ReadOnly, FileFd::Gzip) == false ||
+		  patch.read_diff(p, &patch_hash) == false)
+	    {
+	       _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
+	       return false;
+	    }
+	    p.Close();
+	    HashStringList const hsl = patch_hash.GetHashStringList();
+	    if (hsl != I->ExpectedHashes)
+	       return _error->Error("Hash Sum mismatch for uncompressed patch %s", patch_name.c_str());
+	 }
+
+	 if (Debug == true)
+	    std::clog << "Applying patches against " << Path
+	       << " and writing results to " << Itm->DestFile
+	       << std::endl;
+
+	 FileFd inp, out;
+	 if (inp.Open(Path, FileFd::ReadOnly, FileFd::Extension) == false)
+	 {
+	    std::cerr << "FAILED to open inp " << Path << std::endl;
+	    return _error->Error("Failed to open inp %s", Path.c_str());
+	 }
+	 if (out.Open(Itm->DestFile, FileFd::WriteOnly | FileFd::Create, FileFd::Extension) == false)
+	 {
+	    std::cerr << "FAILED to open out " << Itm->DestFile << std::endl;
+	    return _error->Error("Failed to open out %s", Itm->DestFile.c_str());
+	 }
+
+	 Hashes hash(Itm->ExpectedHashes);
+	 patch.apply_against_file(out, inp, &hash);
+
+	 out.Close();
+	 inp.Close();
+
+	 if (Debug == true) {
+	    std::clog << "rred: finished file patching of " << Path  << "." << std::endl;
+	 }
+
+	 struct stat bufbase, bufpatch;
+	 if (stat(Path.c_str(), &bufbase) != 0 ||
+	       stat(patch_name.c_str(), &bufpatch) != 0)
+	    return _error->Errno("stat", _("Failed to stat"));
+
+	 struct timeval times[2];
+	 times[0].tv_sec = bufbase.st_atime;
+	 times[1].tv_sec = bufpatch.st_mtime;
+	 times[0].tv_usec = times[1].tv_usec = 0;
+	 if (utimes(Itm->DestFile.c_str(), times) != 0)
+	    return _error->Errno("utimes",_("Failed to set modification time"));
+
+	 if (stat(Itm->DestFile.c_str(), &bufbase) != 0)
+	    return _error->Errno("stat", _("Failed to stat"));
+
+	 Res.LastModified = bufbase.st_mtime;
+	 Res.Size = bufbase.st_size;
+	 Res.TakeHashes(hash);
+	 URIDone(Res);
+
+	 return true;
+      }
+
+   public:
+      RredMethod() : aptMethod("rred", "2.0",SingleInstance | SendConfig), Debug(false) {}
+};
+
+int main(int argc, char **argv)
 {
-   Debug = _config->FindB("Debug::pkgAcquire::RRed",false);
-   URI Get = Itm->Uri;
-   string Path = Get.Host + Get.Path; // To account for relative paths
-   // Path contains the filename to patch
-   FetchResult Res;
-   Res.Filename = Itm->DestFile;
-   URIStart(Res);
-   // Res.Filename the destination filename
-
-   if (Debug == true) 
-      std::clog << "Patching " << Path << " with " << Path 
-         << ".ed and putting result into " << Itm->DestFile << std::endl;
-   // Open the source and destination files (the d'tor of FileFd will do 
-   // the cleanup/closing of the fds)
-   FileFd From(Path,FileFd::ReadOnly);
-   FileFd Patch(Path+".ed",FileFd::ReadOnly);
-   FileFd To(Itm->DestFile,FileFd::WriteEmpty);   
-   To.EraseOnFailure();
-   if (_error->PendingError() == true)
-      return false;
-   
-   Hashes Hash;
-   FILE* fFrom = fdopen(From.Fd(), "r");
-   FILE* fPatch = fdopen(Patch.Fd(), "r");
-   FILE* fTo = fdopen(To.Fd(), "w");
-   // now do the actual patching
-   if (ed_file(fPatch, fFrom, fTo, &Hash) != ED_OK) {
-     _error->Errno("rred", _("Could not patch file"));  
-      return false;
+   int i;
+   bool just_diff = true;
+   Patch patch;
+
+   if (argc <= 1) {
+      RredMethod Mth;
+      return Mth.Run();
    }
 
-   // write out the result
-   fflush(fFrom);
-   fflush(fPatch);
-   fflush(fTo);
-   From.Close();
-   Patch.Close();
-   To.Close();
-
-   // Transfer the modification times
-   struct stat Buf;
-   if (stat(Path.c_str(),&Buf) != 0)
-      return _error->Errno("stat",_("Failed to stat"));
-
-   struct utimbuf TimeBuf;
-   TimeBuf.actime = Buf.st_atime;
-   TimeBuf.modtime = Buf.st_mtime;
-   if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0)
-      return _error->Errno("utime",_("Failed to set modification time"));
-
-   if (stat(Itm->DestFile.c_str(),&Buf) != 0)
-      return _error->Errno("stat",_("Failed to stat"));
-
-   // return done
-   Res.LastModified = Buf.st_mtime;
-   Res.Size = Buf.st_size;
-   Res.TakeHashes(Hash);
-   URIDone(Res);
-
-   return true;
-}
+   if (argc > 1 && strcmp(argv[1], "-f") == 0) {
+      just_diff = false;
+      i = 2;
+   } else {
+      i = 1;
+   }
 
-int main(int argc, char *argv[])
-{
-   RredMethod Mth;
+   for (; i < argc; i++) {
+      FileFd p;
+      if (p.Open(argv[i], FileFd::ReadOnly) == false) {
+	 _error->DumpErrors(std::cerr);
+	 exit(1);
+      }
+      if (patch.read_diff(p, NULL) == false)
+      {
+	 _error->DumpErrors(std::cerr);
+	 exit(2);
+      }
+   }
 
-   Prog = strrchr(argv[0],'/');
-   Prog++;
-   
-   return Mth.Run();
+   if (just_diff) {
+      FileFd out;
+      out.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::Create);
+      patch.write_diff(out);
+   } else {
+      FileFd out, inp;
+      out.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::Create);
+      inp.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly);
+      patch.apply_against_file(out, inp);
+   }
+   return 0;
 }