// 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>

#ifndef APT_EXCLUDE_RRED_METHOD_CODE
#include "aptmethod.h"
#include <apt-pkg/configuration.h>
#include <apt-pkg/init.h>
#endif

#include <apt-pkg/error.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/hashes.h>
#include <apt-pkg/strutl.h>

#include <apt-private/private-cmndline.h>

#include <cstddef>
#include <iostream>
#include <list>
#include <string>
#include <vector>

#include <cassert>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
#include <sys/time.h>

#include <apti18n.h>

#ifndef APT_MEMBLOCK_SIZE
#define APT_MEMBLOCK_SIZE (512*1024)
#endif

static bool ShowHelp(CommandLine &)
{
   std::cout <<
      "Usage: rred [options] -t input output patch-1 … patch-N\n"
      "       rred [options] -f patch-1 … patch-N < input > output\n"
      "       rred [options] patch-1 … patch-N > merged-patch\n"
      "\n"
      "The main use of this binary is by APTs acquire system, a mode reached\n"
      "by calling it without any arguments and driven via messages on stdin.\n"
      "\n"
      "For the propose of testing as well as simpler direct usage the above\n"
      "mentioned modes to work with \"reversed restricted ed\" patches as well.\n"
      "\n"
      "The arguments used above are:\n"
      "* input: denotes a file you want to patch.\n"
      "* output: a file you want to store the patched content in.\n"
      "* patch-1 … patch-N: One or more files containing a patch.\n"
      "* merged-patch: All changes by patch-1 … patch-N in one patch.\n"
      "\n"
      "This rred supports the commands 'a', 'c' and 'd', both single as well\n"
      "as multi line. Other commands are not supported (hence 'restricted').\n"
      "The command to patch the last line must appear first in the patch\n"
      "(hence 'reversed'). Such a patch can e.g. be produced with 'diff --ed'.\n"
      ;
   return true;
}

class MemBlock {
   char *start;
   size_t size;
   char *free;
   MemBlock *next = nullptr;

   explicit MemBlock(size_t size) : size(size)
   {
      free = start = new char[size];
   }

   size_t avail(void) { return size - (free - start); }

   public:

   MemBlock() : MemBlock(APT_MEMBLOCK_SIZE) {}

   ~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 * const n = k->add(src, len);
		  assert(last == n); // we checked already that the block is big enough, so a new one shouldn't be used
		  return (last == n) ? nullptr : 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 > APT_MEMBLOCK_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;
   }
};

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;
   }

   /* actually, don't write <lines> lines from <add> */
   bool skip_lines(size_t lines)
   {
      while (lines > 0) {
	 char *s = (char*) memchr(add, '\n', add_len);
	 if (s == nullptr)
	    return _error->Error("No line left in add_len data to skip (1)");
	 s++;
	 add_len -= (s - add);
	 add_cnt--;
	 lines--;
	 if (add_len == 0) {
	    add = nullptr;
	    if (add_cnt != 0 || lines != 0)
	       return _error->Error("No line left in add_len data to skip (2)");
	 } else {
	    add = s;
	    if (add_cnt == 0)
	       return _error->Error("No line left in add_len data to skip (3)");
	 }
      }
      return true;
   }
};

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 FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
      // this isn't unsafe, it is just a moderately expensive check we want to avoid normally
      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
   }

   public:
   FileChanges() {
      where = changes.end();
      pos = 0;
   }

   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(); }

   bool add_change(Change c) {
      assert(pos_is_okay());
      if (not go_to_change_for(c.offset) ||
	  pos + where->offset != c.offset)
	 return false;
      if (c.del_cnt > 0)
	 if (not delete_lines(c.del_cnt))
	    return false;
      if (pos + where->offset != c.offset)
	 return false;
      if (c.add_len > 0) {
	 assert(pos_is_okay());
	 if (where->add_len > 0)
	    if (not new_change())
	       return false;
	 if (where->add_len != 0 || where->add_cnt != 0)
	    return false;

	 where->add_len = c.add_len;
	 where->add_cnt = c.add_cnt;
	 where->add = c.add;
      }
      assert(pos_is_okay());
      if (not merge())
	 return false;
      return pos_is_okay();
   }

   private:
   bool merge(void)
   {
      while (where->offset == 0 && where != changes.begin()) {
	 if (not left())
	    return false;
      }
      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;
	 }
      }
      return true;
   }

   bool go_to_change_for(size_t line)
   {
      while(where != changes.end()) {
	 if (line < pos) {
	    if (not left())
	       return false;
	    continue;
	 }
	 if (pos + where->offset + where->add_cnt <= line) {
	    if (not right())
	       return false;
	    continue;
	 }
	 // line is somewhere in this slot
	 if (line < pos + where->offset) {
	    break;
	 } else if (line == pos + where->offset) {
	    return true;
	 } else {
	    if (not split(line - pos))
	       return false;
	    return right();
	 }
      }
      /* it goes before this patch */
      return insert(line-pos);
   }

   bool new_change(void) { return insert(where->offset); }

   bool insert(size_t offset)
   {
      assert(pos_is_okay());
      if (where != changes.end() && offset > where->offset)
	 return false;
      if (where != changes.end())
	 where->offset -= offset;
      changes.insert(where, Change(offset));
      --where;
      return pos_is_okay();
   }

   bool split(size_t offset)
   {
      assert(pos_is_okay());
      if (where->offset >= offset || offset >= where->offset + where->add_cnt)
	 return false;

      size_t keep_lines = offset - where->offset;

      Change before(*where);

      where->del_cnt = 0;
      where->offset = 0;
      if (not where->skip_lines(keep_lines))
	 return false;

      before.add_cnt = keep_lines;
      before.add_len -= where->add_len;

      changes.insert(where, before);
      --where;
      return pos_is_okay();
   }

   bool delete_lines(size_t cnt)
   {
      assert(pos_is_okay());
      std::list<struct Change>::iterator x = where;
      while (cnt > 0)
      {
	 size_t del;
	 del = x->add_cnt;
	 if (del > cnt)
	    del = cnt;
	 if (not x->skip_lines(del))
	    return false;
	 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;
      }
      return pos_is_okay();
   }

   bool left(void) {
      assert(pos_is_okay());
      --where;
      pos -= where->offset + where->add_cnt;
      return pos_is_okay();
   }

   bool right(void) {
      assert(pos_is_okay());
      pos += where->offset + where->add_cnt;
      ++where;
      return pos_is_okay();
   }
};

class Patch {
   FileChanges filechanges;
   MemBlock add_text;

   static bool retry_fwrite(char *b, size_t l, FileFd &f, Hashes * const start_hash, Hashes * const end_hash = nullptr) APT_NONNULL(1)
   {
      if (f.Write(b, l) == false)
	 return false;
      if (start_hash)
	 start_hash->Add((unsigned char*)b, l);
      if (end_hash)
	 end_hash->Add((unsigned char*)b, l);
      return true;
   }

   static void dump_rest(FileFd &o, FileFd &i,
	 Hashes * const start_hash, Hashes * const end_hash)
   {
      char buffer[APT_MEMBLOCK_SIZE];
      unsigned long long l = 0;
      while (i.Read(buffer, sizeof(buffer), &l)) {
	 if (l ==0  || !retry_fwrite(buffer, l, o, start_hash, end_hash))
	    break;
      }
   }

   static void dump_lines(FileFd &o, FileFd &i, size_t n,
	 Hashes * const start_hash, Hashes * const end_hash)
   {
      char buffer[APT_MEMBLOCK_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, start_hash, end_hash);
      }
   }

   static void skip_lines(FileFd &i, int n, Hashes * const start_hash)
   {
      char buffer[APT_MEMBLOCK_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--;
	 if (start_hash)
	    start_hash->Add((unsigned char*)buffer, l);
      }
   }

   static void dump_mem(FileFd &o, char *p, size_t s, Hashes *hash) APT_NONNULL(2) {
      retry_fwrite(p, s, o, nullptr, hash);
   }

   public:

   bool read_diff(FileFd &f, Hashes * const h)
   {
      char buffer[APT_MEMBLOCK_SIZE];
      bool cmdwanted = true;

      Change ch(std::numeric_limits<size_t>::max());
      if (f.ReadLine(buffer, sizeof(buffer)) == nullptr)
      {
	 if (f.Eof())
	    return true;
	 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;
		  if (not filechanges.add_change(ch))
		     return _error->Error("Parsing patchfile %s failed: Delete command could not be added to changes", f.Name().c_str());
		  break;
	       default:
		  return _error->Error("Parsing patchfile %s failed: Unknown command", f.Name().c_str());
	    }
	 } else { /* !cmdwanted */
	    if (strcmp(buffer, ".\n") == 0) {
	       cmdwanted = true;
	       if (not filechanges.add_change(ch))
		  return _error->Error("Parsing patchfile %s failed: Data couldn't be added for command (1)", f.Name().c_str());
	    } 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) {
		     if (not filechanges.add_change(ch))
			return _error->Error("Parsing patchfile %s failed: Data couldn't be added for command (2)", f.Name().c_str());
		     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;
	    if (unlikely(ch == filechanges.rend()))
	       return;
	 }
	 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;
      }
   }

   void apply_against_file(FileFd &out, FileFd &in,
	 Hashes * const start_hash = nullptr, Hashes * const end_hash = nullptr)
   {
      std::list<struct Change>::iterator ch;
      for (ch = filechanges.begin(); ch != filechanges.end(); ++ch) {
	 dump_lines(out, in, ch->offset, start_hash, end_hash);
	 skip_lines(in, ch->del_cnt, start_hash);
	 if (ch->add_len != 0)
	    dump_mem(out, ch->add, ch->add_len, end_hash);
      }
      dump_rest(out, in, start_hash, end_hash);
      out.Flush();
   }
};

#ifndef APT_EXCLUDE_RRED_METHOD_CODE
class RredMethod final : 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;
      }

   protected:
      bool URIAcquire(std::string const &Message, FetchItem *Itm) override {
	 Debug = DebugEnabled();
	 URI Get(Itm->Uri);
	 std::string Path = DecodeSendURI(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;

	 HashStringList StartHashes;
	 for (char const * const * type = HashString::SupportedHashes(); *type != nullptr; ++type)
	 {
	    std::string tagname;
	    strprintf(tagname, "Start-%s-Hash", *type);
	    std::string const hashsum = LookupTag(Message, tagname.c_str());
	    if (hashsum.empty() == false)
	       StartHashes.push_back(HashString(*type, hashsum));
	 }

	 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.emplace_back(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.emplace_back(*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)
	 {
	    if (Debug == true)
	       std::clog << "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::Empty | FileFd::BufferedWrite, FileFd::Extension) == false)
	 {
	    if (Debug == true)
	       std::clog << "FAILED to open out " << Itm->DestFile << std::endl;
	    return _error->Error("Failed to open out %s", Itm->DestFile.c_str());
	 }

	 Hashes end_hash(Itm->ExpectedHashes);
	 if (StartHashes.usable())
	 {
	    Hashes start_hash(StartHashes);
	    patch.apply_against_file(out, inp, &start_hash, &end_hash);
	    if (start_hash.GetHashStringList() != StartHashes)
	       _error->Error("The input file hadn't the expected hash!");
	 }
	 else
	    patch.apply_against_file(out, inp, nullptr, &end_hash);

	 out.Close();
	 inp.Close();

	 if (_error->PendingError() == true) {
	    if (Debug == true)
	       std::clog << "FAILED to read or write files" << std::endl;
	    return false;
	 }

	 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 %s"), Path.c_str());

	 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 %s"), Itm->DestFile.c_str());

	 Res.LastModified = bufbase.st_mtime;
	 Res.Size = bufbase.st_size;
	 Res.TakeHashes(end_hash);
	 URIDone(Res);

	 return true;
      }

   public:
   RredMethod() : aptMethod("rred", "2.0", SendConfig | SendURIEncoded), Debug(false)
   {
      SeccompFlags = aptMethod::BASE | aptMethod::DIRECTORY;
   }
};

static const APT::Configuration::Compressor *FindCompressor(std::vector<APT::Configuration::Compressor> const &compressors, std::string const &name) /*{{{*/
{
   APT::Configuration::Compressor const * compressor = nullptr;
   for (auto const & c : compressors)
   {
      if (compressor != nullptr && c.Cost >= compressor->Cost)
         continue;
      if (c.Name == name || c.Extension == name || (!c.Extension.empty() && c.Extension.substr(1) == name))
         compressor = &c;
   }
   return compressor;
}
									/*}}}*/
static std::vector<aptDispatchWithHelp> GetCommands()
{
   return {{nullptr, nullptr, nullptr}};
}
int main(int argc, const char *argv[])
{
   if (argc <= 1)
      return RredMethod().Run();

   CommandLine CmdL;
   auto const Cmds = ParseCommandLine(CmdL, APT_CMD::RRED, &_config, nullptr, argc, argv, &ShowHelp, &GetCommands);

   FileFd input, output;
   unsigned int argi = 0;
   auto const argmax = CmdL.FileSize();
   bool const quiet = _config->FindI("quiet", 0) >= 2;

   std::string const compressorName = _config->Find("Rred::Compress", "");
   auto const compressors = APT::Configuration::getCompressors();
   APT::Configuration::Compressor const * compressor = nullptr;
   if (not compressorName.empty())
   {
      compressor = FindCompressor(compressors, compressorName);
      if (compressor == nullptr)
      {
	 std::cerr << "E: Could not find compressor: " << compressorName << '\n';
	 return 101;
      }
   }

   bool just_diff = false;
   if (_config->FindB("Rred::T", false))
   {
      if (argmax < 3)
      {
	 std::cerr << "E: Not enough filenames given on the command line for mode 't'\n";
	 return 101;
      }
      if (not quiet)
	 std::clog << "Patching " << CmdL.FileList[0] << " into " << CmdL.FileList[1] << "\n";
      input.Open(CmdL.FileList[0], FileFd::ReadOnly,FileFd::Extension);
      if (compressor == nullptr)
	 output.Open(CmdL.FileList[1], FileFd::WriteOnly | FileFd::Create | FileFd::Empty | FileFd::BufferedWrite, FileFd::Extension);
      else
	 output.Open(CmdL.FileList[1], FileFd::WriteOnly | FileFd::Create | FileFd::Empty | FileFd::BufferedWrite, *compressor);
      argi = 2;
   }
   else
   {
      if (compressor == nullptr)
	 output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::Create | FileFd::BufferedWrite);
      else
	 output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::Create | FileFd::BufferedWrite, *compressor);
      if (_config->FindB("Rred::F", false))
	 input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly);
      else
	 just_diff = true;
   }

   if (argi + 1 > argmax)
   {
	 std::cerr << "E: At least one patch needs to be given on the command line\n";
	 return 101;
   }

   Patch merged_patch;
   for (; argi < argmax; ++argi)
   {
      FileFd patch;
      if (not patch.Open(CmdL.FileList[argi], FileFd::ReadOnly, FileFd::Extension))
      {
	 _error->DumpErrors(std::cerr);
	 return 1;
      }
      if (not merged_patch.read_diff(patch, nullptr))
      {
	 _error->DumpErrors(std::cerr);
	 return 2;
      }
   }

   if (just_diff)
      merged_patch.write_diff(output);
   else
      merged_patch.apply_against_file(output, input);

   output.Close();
   input.Close();
   return DispatchCommandLine(CmdL, {});
}
#endif
