]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/acquire-item.cc
ensure outdated files are dropped without lists-cleanup
[apt.git] / apt-pkg / acquire-item.cc
index 6b0debb44fd7b84011cb61b9cc25343b21c6a98f..874539625452f1a0c4f166e1e8eab902bce91a70 100644 (file)
 #include <stdio.h>
 #include <ctime>
 #include <sstream>
+#include <numeric>
 
 #include <apti18n.h>
                                                                        /*}}}*/
 
 using namespace std;
 
-static void printHashSumComparision(std::string const &URI, HashStringList const &Expected, HashStringList const &Actual) /*{{{*/
+static void printHashSumComparison(std::string const &URI, HashStringList const &Expected, HashStringList const &Actual) /*{{{*/
 {
    if (_config->FindB("Debug::Acquire::HashSumMismatch", false) == false)
       return;
@@ -85,32 +86,16 @@ static std::string GetKeepCompressedFileName(std::string file, IndexTarget const
    if (Target.KeepCompressed == false)
       return file;
 
-   std::string const CompressionTypes = Target.Option(IndexTarget::COMPRESSIONTYPES);
-   if (CompressionTypes.empty() == false)
+   std::string const KeepCompressedAs = Target.Option(IndexTarget::KEEPCOMPRESSEDAS);
+   if (KeepCompressedAs.empty() == false)
    {
-      std::string const ext = CompressionTypes.substr(0, CompressionTypes.find(' '));
+      std::string const ext = KeepCompressedAs.substr(0, KeepCompressedAs.find(' '));
       if (ext != "uncompressed")
         file.append(".").append(ext);
    }
    return file;
 }
                                                                        /*}}}*/
-static std::string GetCompressedFileName(IndexTarget const &Target, std::string const &Name, std::string const &Ext) /*{{{*/
-{
-   if (Ext.empty() || Ext == "uncompressed")
-      return Name;
-
-   // do not reverify cdrom sources as apt-cdrom may rewrite the Packages
-   // file when its doing the indexcopy
-   if (Target.URI.substr(0,6) == "cdrom:")
-      return Name;
-
-   // adjust DestFile if its compressed on disk
-   if (Target.KeepCompressed == true)
-      return Name + '.' + Ext;
-   return Name;
-}
-                                                                       /*}}}*/
 static std::string GetMergeDiffsPatchFileName(std::string const &Final, std::string const &Patch)/*{{{*/
 {
    // rred expects the patch as $FinalFile.ed.$patchname.gz
@@ -123,28 +108,27 @@ static std::string GetDiffsPatchFileName(std::string const &Final)        /*{{{*/
    return Final + ".ed";
 }
                                                                        /*}}}*/
-static bool BootstrapPDiffWith(std::string const &PartialFile, std::string const &FinalFile, IndexTarget const &Target)/*{{{*/
+static std::string GetExistingFilename(std::string const &File)                /*{{{*/
 {
-   // patching needs to be bootstrapped with the 'old' version
-   std::vector<std::string> types = VectorizeString(Target.Option(IndexTarget::COMPRESSIONTYPES), ' ');
-   auto typeItr = types.cbegin();
-   for (; typeItr != types.cend(); ++typeItr)
+   if (RealFileExists(File))
+      return File;
+   for (auto const &type : APT::Configuration::getCompressorExtensions())
    {
-      std::string Final = FinalFile;
-      if (*typeItr != "uncompressed")
-        Final.append(".").append(*typeItr);
-      if (RealFileExists(Final) == false)
-        continue;
-      std::string Partial = PartialFile;
-      if (*typeItr != "uncompressed")
-        Partial.append(".").append(*typeItr);
-      if (FileExists(Partial.c_str()) == true)
-        return true;
-      if (symlink(Final.c_str(), Partial.c_str()) != 0)
-        return false;
-      break;
+      std::string const Final = File + type;
+      if (RealFileExists(Final))
+        return Final;
    }
-   return typeItr != types.cend();
+   return "";
+}
+                                                                       /*}}}*/
+static std::string GetDiffIndexFileName(std::string const &Name)       /*{{{*/
+{
+   return Name + ".diff/Index";
+}
+                                                                       /*}}}*/
+static std::string GetDiffIndexURI(IndexTarget const &Target)          /*{{{*/
+{
+   return Target.URI + ".diff/Index";
 }
                                                                        /*}}}*/
 
@@ -232,12 +216,12 @@ HashStringList pkgAcqMetaBase::GetExpectedHashes() const
 
 APT_CONST bool pkgAcqIndexDiffs::HashesRequired() const
 {
-   /* We don't always have the diff of the downloaded pdiff file.
-      What we have for sure is hashes for the uncompressed file,
-      but rred uncompresses them on the fly while parsing, so not handled here.
-      Hashes are (also) checked while searching for (next) patch to apply. */
+   /* We can't check hashes of rred result as we don't know what the
+      hash of the file will be. We just know the hash of the patch(es),
+      the hash of the file they will apply on and the hash of the resulting
+      file. */
    if (State == StateFetchDiff)
-      return available_patches[0].download_hashes.empty() == false;
+      return true;
    return false;
 }
 HashStringList pkgAcqIndexDiffs::GetExpectedHashes() const
@@ -253,7 +237,7 @@ APT_CONST bool pkgAcqIndexMergeDiffs::HashesRequired() const
       we can check the rred result after all patches are applied as
       we know the expected result rather than potentially apply more patches */
    if (State == StateFetchDiff)
-      return patch.download_hashes.empty() == false;
+      return true;
    return State == StateApplyDiff;
 }
 HashStringList pkgAcqIndexMergeDiffs::GetExpectedHashes() const
@@ -297,6 +281,12 @@ bool pkgAcquire::Item::QueueURI(pkgAcquire::ItemDesc &Item)
    for a hashsum mismatch to happen which helps nobody) */
 bool pkgAcqTransactionItem::QueueURI(pkgAcquire::ItemDesc &Item)
 {
+   if (TransactionManager->State != TransactionStarted)
+   {
+      if (_config->FindB("Debug::Acquire::Transaction", false))
+        std::clog << "Skip " << Target.URI << " as transaction was already dealt with!" << std::endl;
+      return false;
+   }
    std::string const FinalFile = GetFinalFilename();
    if (TransactionManager != NULL && TransactionManager->IMSHit == true &&
         FileExists(FinalFile) == true)
@@ -327,17 +317,17 @@ bool pkgAcqDiffIndex::QueueURI(pkgAcquire::ItemDesc &Item)
 // Acquire::Item::GetFinalFilename and specialisations for child classes       /*{{{*/
 std::string pkgAcquire::Item::GetFinalFilename() const
 {
+   // Beware: Desc.URI is modified by redirections
    return GetFinalFileNameFromURI(Desc.URI);
 }
 std::string pkgAcqDiffIndex::GetFinalFilename() const
 {
-   // the logic we inherent from pkgAcqBaseIndex isn't what we need here
-   return pkgAcquire::Item::GetFinalFilename();
+   return GetFinalFileNameFromURI(GetDiffIndexURI(Target));
 }
 std::string pkgAcqIndex::GetFinalFilename() const
 {
    std::string const FinalFile = GetFinalFileNameFromURI(Target.URI);
-   return GetCompressedFileName(Target, FinalFile, CurrentCompressionExtension);
+   return GetKeepCompressedFileName(FinalFile, Target);
 }
 std::string pkgAcqMetaSig::GetFinalFilename() const
 {
@@ -369,7 +359,7 @@ std::string pkgAcqIndex::GetMetaKey() const
 }
 std::string pkgAcqDiffIndex::GetMetaKey() const
 {
-   return Target.MetaKey + ".diff/Index";
+   return GetDiffIndexFileName(Target.MetaKey);
 }
                                                                        /*}}}*/
 //pkgAcqTransactionItem::TransactionState and specialisations for child classes        /*{{{*/
@@ -378,6 +368,7 @@ bool pkgAcqTransactionItem::TransactionState(TransactionStates const state)
    bool const Debug = _config->FindB("Debug::Acquire::Transaction", false);
    switch(state)
    {
+      case TransactionStarted: _error->Fatal("Item %s changed to invalid transaction start state!", Target.URI.c_str()); break;
       case TransactionAbort:
         if(Debug == true)
            std::clog << "  Cancel: " << DestFile << std::endl;
@@ -388,16 +379,66 @@ bool pkgAcqTransactionItem::TransactionState(TransactionStates const state)
         }
         break;
       case TransactionCommit:
-        if(PartialFile != "")
+        if(PartialFile.empty() == false)
         {
-           if(Debug == true)
-              std::clog << "mv " << PartialFile << " -> "<< DestFile << " # " << DescURI() << std::endl;
+           bool sameFile = (PartialFile == DestFile);
+           // we use symlinks on IMS-Hit to avoid copies
+           if (RealFileExists(DestFile))
+           {
+              struct stat Buf;
+              if (lstat(PartialFile.c_str(), &Buf) != -1)
+              {
+                 if (S_ISLNK(Buf.st_mode) && Buf.st_size > 0)
+                 {
+                    char partial[Buf.st_size + 1];
+                    ssize_t const sp = readlink(PartialFile.c_str(), partial, Buf.st_size);
+                    if (sp == -1)
+                       _error->Errno("pkgAcqTransactionItem::TransactionState-sp", _("Failed to readlink %s"), PartialFile.c_str());
+                    else
+                    {
+                       partial[sp] = '\0';
+                       sameFile = (DestFile == partial);
+                    }
+                 }
+              }
+              else
+                 _error->Errno("pkgAcqTransactionItem::TransactionState-stat", _("Failed to stat %s"), PartialFile.c_str());
+           }
+           if (sameFile == false)
+           {
+              // ensure that even without lists-cleanup all compressions are nuked
+              std::string FinalFile = GetFinalFileNameFromURI(Target.URI);
+              if (FileExists(FinalFile))
+              {
+                 if(Debug == true)
+                    std::clog << "rm " << FinalFile << " # " << DescURI() << std::endl;
+                 if (RemoveFile("TransactionStates-Cleanup", FinalFile) == false)
+                    return false;
+              }
+              for (auto const &ext: APT::Configuration::getCompressorExtensions())
+              {
+                 auto const Final = FinalFile + ext;
+                 if (FileExists(Final))
+                 {
+                    if(Debug == true)
+                       std::clog << "rm " << Final << " # " << DescURI() << std::endl;
+                    if (RemoveFile("TransactionStates-Cleanup", Final) == false)
+                       return false;
+                 }
+              }
+              if(Debug == true)
+                 std::clog << "mv " << PartialFile << " -> "<< DestFile << " # " << DescURI() << std::endl;
+              if (Rename(PartialFile, DestFile) == false)
+                 return false;
+           }
+           else if(Debug == true)
+              std::clog << "keep " << PartialFile << " # " << DescURI() << std::endl;
 
-           Rename(PartialFile, DestFile);
         } else {
            if(Debug == true)
               std::clog << "rm " << DestFile << " # " << DescURI() << std::endl;
-           RemoveFile("TransactionCommit", DestFile);
+           if (RemoveFile("TransItem::TransactionCommit", DestFile) == false)
+              return false;
         }
         break;
    }
@@ -417,6 +458,7 @@ bool pkgAcqIndex::TransactionState(TransactionStates const state)
 
    switch (state)
    {
+      case TransactionStarted: _error->Fatal("AcqIndex %s changed to invalid transaction start state!", Target.URI.c_str()); break;
       case TransactionAbort:
         if (Stage == STAGE_DECOMPRESS_AND_VERIFY)
         {
@@ -428,7 +470,7 @@ bool pkgAcqIndex::TransactionState(TransactionStates const state)
         break;
       case TransactionCommit:
         if (EraseFileName.empty() == false)
-           RemoveFile("TransactionCommit", EraseFileName);
+           RemoveFile("AcqIndex::TransactionCommit", EraseFileName);
         break;
    }
    return true;
@@ -440,6 +482,7 @@ bool pkgAcqDiffIndex::TransactionState(TransactionStates const state)
 
    switch (state)
    {
+      case TransactionStarted: _error->Fatal("Item %s changed to invalid transaction start state!", Target.URI.c_str()); break;
       case TransactionCommit:
         break;
       case TransactionAbort:
@@ -476,6 +519,41 @@ class APT_HIDDEN NoActionItem : public pkgAcquire::Item                    /*{{{*/
    }
 };
                                                                        /*}}}*/
+class APT_HIDDEN CleanupItem : public pkgAcqTransactionItem            /*{{{*/
+/* This class ensures that a file which was configured but isn't downloaded
+   for various reasons isn't kept in an old version in the lists directory.
+   In a way its the reverse of NoActionItem as it helps with removing files
+   even if the lists-cleanup is deactivated. */
+{
+   public:
+   virtual std::string DescURI() const APT_OVERRIDE {return Target.URI;};
+   virtual HashStringList GetExpectedHashes()  const APT_OVERRIDE {return HashStringList();};
+
+   CleanupItem(pkgAcquire * const Owner, pkgAcqMetaClearSig * const TransactionManager, IndexTarget const &Target) :
+      pkgAcqTransactionItem(Owner, TransactionManager, Target)
+   {
+      Status = StatDone;
+      DestFile = GetFinalFileNameFromURI(Target.URI);
+   }
+   bool TransactionState(TransactionStates const state) APT_OVERRIDE
+   {
+      switch (state)
+      {
+        case TransactionStarted:
+           break;
+        case TransactionAbort:
+           break;
+        case TransactionCommit:
+           if (_config->FindB("Debug::Acquire::Transaction", false) == true)
+              std::clog << "rm " << DestFile << " # " << DescURI() << std::endl;
+           if (RemoveFile("TransItem::TransactionCommit", DestFile) == false)
+              return false;
+           break;
+      }
+      return true;
+   }
+};
+                                                                       /*}}}*/
 
 // Acquire::Item::Item - Constructor                                   /*{{{*/
 APT_IGNORE_DEPRECATED_PUSH
@@ -768,6 +846,32 @@ HashStringList pkgAcqTransactionItem::GetExpectedHashesFor(std::string const &Me
 }
                                                                        /*}}}*/
 
+static void LoadLastMetaIndexParser(pkgAcqMetaClearSig * const TransactionManager, std::string const &FinalRelease, std::string const &FinalInRelease)/*{{{*/
+{
+   if (TransactionManager->IMSHit == true)
+      return;
+   if (RealFileExists(FinalInRelease) || RealFileExists(FinalRelease))
+   {
+      TransactionManager->LastMetaIndexParser = TransactionManager->MetaIndexParser->UnloadedClone();
+      if (TransactionManager->LastMetaIndexParser != NULL)
+      {
+        _error->PushToStack();
+        if (RealFileExists(FinalInRelease))
+           TransactionManager->LastMetaIndexParser->Load(FinalInRelease, NULL);
+        else
+           TransactionManager->LastMetaIndexParser->Load(FinalRelease, NULL);
+        // its unlikely to happen, but if what we have is bad ignore it
+        if (_error->PendingError())
+        {
+           delete TransactionManager->LastMetaIndexParser;
+           TransactionManager->LastMetaIndexParser = NULL;
+        }
+        _error->RevertToStack();
+      }
+   }
+}
+                                                                       /*}}}*/
+
 // AcqMetaBase - Constructor                                           /*{{{*/
 pkgAcqMetaBase::pkgAcqMetaBase(pkgAcquire * const Owner,
       pkgAcqMetaClearSig * const TransactionManager,
@@ -775,7 +879,7 @@ pkgAcqMetaBase::pkgAcqMetaBase(pkgAcquire * const Owner,
       IndexTarget const &DataTarget)
 : pkgAcqTransactionItem(Owner, TransactionManager, DataTarget), d(NULL),
    IndexTargets(IndexTargets),
-   AuthPass(false), IMSHit(false)
+   AuthPass(false), IMSHit(false), State(TransactionStarted)
 {
 }
                                                                        /*}}}*/
@@ -791,10 +895,20 @@ void pkgAcqMetaBase::AbortTransaction()
    if(_config->FindB("Debug::Acquire::Transaction", false) == true)
       std::clog << "AbortTransaction: " << TransactionManager << std::endl;
 
+   switch (TransactionManager->State)
+   {
+      case TransactionStarted: break;
+      case TransactionAbort: _error->Fatal("Transaction %s was already aborted and is aborted again", TransactionManager->Target.URI.c_str()); return;
+      case TransactionCommit: _error->Fatal("Transaction %s was already aborted and is now commited", TransactionManager->Target.URI.c_str()); return;
+   }
+   TransactionManager->State = TransactionAbort;
+
    // ensure the toplevel is in error state too
    for (std::vector<pkgAcqTransactionItem*>::iterator I = Transaction.begin();
         I != Transaction.end(); ++I)
    {
+      if ((*I)->Status != pkgAcquire::Item::StatFetching)
+        Owner->Dequeue(*I);
       (*I)->TransactionState(TransactionAbort);
    }
    Transaction.clear();
@@ -824,6 +938,14 @@ void pkgAcqMetaBase::CommitTransaction()
    if(_config->FindB("Debug::Acquire::Transaction", false) == true)
       std::clog << "CommitTransaction: " << this << std::endl;
 
+   switch (TransactionManager->State)
+   {
+      case TransactionStarted: break;
+      case TransactionAbort: _error->Fatal("Transaction %s was already commited and is now aborted", TransactionManager->Target.URI.c_str()); return;
+      case TransactionCommit: _error->Fatal("Transaction %s was already commited and is again commited", TransactionManager->Target.URI.c_str()); return;
+   }
+   TransactionManager->State = TransactionCommit;
+
    // move new files into place *and* remove files that are not
    // part of the transaction but are still on disk
    for (std::vector<pkgAcqTransactionItem*>::iterator I = Transaction.begin();
@@ -980,25 +1102,7 @@ bool pkgAcqMetaBase::CheckAuthDone(string const &Message)         /*{{{*/
         FinalInRelease = FinalFile.substr(0, FinalFile.length() - strlen("Release")) + "InRelease";
         FinalRelease = FinalFile;
       }
-      if (RealFileExists(FinalInRelease) || RealFileExists(FinalRelease))
-      {
-        TransactionManager->LastMetaIndexParser = TransactionManager->MetaIndexParser->UnloadedClone();
-        if (TransactionManager->LastMetaIndexParser != NULL)
-        {
-           _error->PushToStack();
-           if (RealFileExists(FinalInRelease))
-              TransactionManager->LastMetaIndexParser->Load(FinalInRelease, NULL);
-           else
-              TransactionManager->LastMetaIndexParser->Load(FinalRelease, NULL);
-           // its unlikely to happen, but if what we have is bad ignore it
-           if (_error->PendingError())
-           {
-              delete TransactionManager->LastMetaIndexParser;
-              TransactionManager->LastMetaIndexParser = NULL;
-           }
-           _error->RevertToStack();
-        }
-      }
+      LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease);
    }
 
    if (TransactionManager->MetaIndexParser->Load(DestFile, &ErrorText) == false)
@@ -1044,10 +1148,12 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                    /*{{{*/
       // than invent an entirely new flag we would need to carry for all of eternity.
       if (Target->Option(IndexTarget::ARCHITECTURE) == "all")
       {
-        if (TransactionManager->MetaIndexParser->IsArchitectureSupported("all") == false)
-           continue;
-        if (TransactionManager->MetaIndexParser->IsArchitectureAllSupportedFor(*Target) == false)
+        if (TransactionManager->MetaIndexParser->IsArchitectureSupported("all") == false ||
+              TransactionManager->MetaIndexParser->IsArchitectureAllSupportedFor(*Target) == false)
+        {
+           new CleanupItem(Owner, TransactionManager, *Target);
            continue;
+        }
       }
 
       bool trypdiff = Target->OptionBool(IndexTarget::PDIFFS);
@@ -1057,13 +1163,17 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                    /*{{{*/
         {
            // optional targets that we do not have in the Release file are skipped
            if (Target->IsOptional)
+           {
+              new CleanupItem(Owner, TransactionManager, *Target);
               continue;
+           }
 
            std::string const &arch = Target->Option(IndexTarget::ARCHITECTURE);
            if (arch.empty() == false)
            {
               if (TransactionManager->MetaIndexParser->IsArchitectureSupported(arch) == false)
               {
+                 new CleanupItem(Owner, TransactionManager, *Target);
                  _error->Notice(_("Skipping acquire of configured file '%s' as repository '%s' doesn't support architecture '%s'"),
                        Target->MetaKey.c_str(), TransactionManager->Target.Description.c_str(), arch.c_str());
                  continue;
@@ -1072,7 +1182,10 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                     /*{{{*/
               // ignore silently as this is pretty much the same as just shipping an empty file.
               // if we don't know which architectures are supported, we do NOT ignore it to notify user about this
               if (TransactionManager->MetaIndexParser->IsArchitectureSupported("*undefined*") == false)
+              {
+                 new CleanupItem(Owner, TransactionManager, *Target);
                  continue;
+              }
            }
 
            Status = StatAuthError;
@@ -1082,11 +1195,21 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                    /*{{{*/
         else
         {
            auto const hashes = GetExpectedHashesFor(Target->MetaKey);
-           if (hashes.usable() == false && hashes.empty() == false)
+           if (hashes.empty() == false)
            {
-              _error->Warning(_("Skipping acquire of configured file '%s' as repository '%s' provides only weak security information for it"),
+              if (hashes.usable() == false)
+              {
+                 new CleanupItem(Owner, TransactionManager, *Target);
+                 _error->Warning(_("Skipping acquire of configured file '%s' as repository '%s' provides only weak security information for it"),
                        Target->MetaKey.c_str(), TransactionManager->Target.Description.c_str());
-              continue;
+                 continue;
+              }
+              // empty files are skipped as acquiring the very small compressed files is a waste of time
+              else if (hashes.FileSize() == 0)
+              {
+                 new CleanupItem(Owner, TransactionManager, *Target);
+                 continue;
+              }
            }
         }
 
@@ -1117,19 +1240,7 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                     /*{{{*/
         else
            Target->Options["COMPRESSIONTYPES"].clear();
 
-        std::string filename = GetFinalFileNameFromURI(Target->URI);
-        if (RealFileExists(filename) == false)
-        {
-           if (Target->KeepCompressed)
-           {
-              filename = GetKeepCompressedFileName(filename, *Target);
-              if (RealFileExists(filename) == false)
-                 filename.clear();
-           }
-           else
-              filename.clear();
-        }
-
+        std::string filename = GetExistingFilename(GetFinalFileNameFromURI(Target->URI));
         if (filename.empty() == false)
         {
            // if the Release file is a hit and we have an index it must be the current one
@@ -1154,31 +1265,19 @@ void pkgAcqMetaBase::QueueIndexes(bool const verify)                    /*{{{*/
         if (filename.empty() == false)
         {
            new NoActionItem(Owner, *Target, filename);
-           std::string const idxfilename = GetFinalFileNameFromURI(Target->URI + ".diff/Index");
+           std::string const idxfilename = GetFinalFileNameFromURI(GetDiffIndexURI(*Target));
            if (FileExists(idxfilename))
               new NoActionItem(Owner, *Target, idxfilename);
            continue;
         }
 
         // check if we have patches available
-        trypdiff &= TransactionManager->MetaIndexParser->Exists(Target->MetaKey + ".diff/Index");
+        trypdiff &= TransactionManager->MetaIndexParser->Exists(GetDiffIndexFileName(Target->MetaKey));
       }
       else
       {
         // if we have no file to patch, no point in trying
-        std::string filename = GetFinalFileNameFromURI(Target->URI);
-        if (RealFileExists(filename) == false)
-        {
-           if (Target->KeepCompressed)
-           {
-              filename = GetKeepCompressedFileName(filename, *Target);
-              if (RealFileExists(filename) == false)
-                 filename.clear();
-           }
-           else
-              filename.clear();
-        }
-        trypdiff &= (filename.empty() == false);
+        trypdiff &= (GetExistingFilename(GetFinalFileNameFromURI(Target->URI)).empty() == false);
       }
 
       // no point in patching from local sources
@@ -1332,6 +1431,15 @@ string pkgAcqMetaClearSig::Custom600Headers() const
    return Header;
 }
                                                                        /*}}}*/
+void pkgAcqMetaClearSig::Finished()                                    /*{{{*/
+{
+   if(_config->FindB("Debug::Acquire::Transaction", false) == true)
+      std::clog << "Finished: " << DestFile <<std::endl;
+   if(TransactionManager != NULL && TransactionManager->State == TransactionStarted &&
+      TransactionManager->TransactionHasError() == false)
+      TransactionManager->CommitTransaction();
+}
+                                                                       /*}}}*/
 bool pkgAcqMetaClearSig::VerifyDone(std::string const &Message,                /*{{{*/
         pkgAcquire::MethodConfig const * const Cnf)
 {
@@ -1418,30 +1526,7 @@ void pkgAcqMetaClearSig::Failed(string const &Message,pkgAcquire::MethodConfig c
         string const FinalInRelease = GetFinalFilename();
         Rename(DestFile, PartialRelease);
         TransactionManager->TransactionStageCopy(this, PartialRelease, FinalRelease);
-
-        if (RealFileExists(FinalReleasegpg) || RealFileExists(FinalInRelease))
-        {
-           // open the last Release if we have it
-           if (TransactionManager->IMSHit == false)
-           {
-              TransactionManager->LastMetaIndexParser = TransactionManager->MetaIndexParser->UnloadedClone();
-              if (TransactionManager->LastMetaIndexParser != NULL)
-              {
-                 _error->PushToStack();
-                 if (RealFileExists(FinalInRelease))
-                    TransactionManager->LastMetaIndexParser->Load(FinalInRelease, NULL);
-                 else
-                    TransactionManager->LastMetaIndexParser->Load(FinalRelease, NULL);
-                 // its unlikely to happen, but if what we have is bad ignore it
-                 if (_error->PendingError())
-                 {
-                    delete TransactionManager->LastMetaIndexParser;
-                    TransactionManager->LastMetaIndexParser = NULL;
-                 }
-                 _error->RevertToStack();
-              }
-           }
-        }
+        LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease);
 
         // we parse the indexes here because at this point the user wanted
         // a repository that may potentially harm him
@@ -1514,15 +1599,6 @@ void pkgAcqMetaIndex::Failed(string const &Message,
    }
 }
                                                                        /*}}}*/
-void pkgAcqMetaIndex::Finished()                                       /*{{{*/
-{
-   if(_config->FindB("Debug::Acquire::Transaction", false) == true)
-      std::clog << "Finished: " << DestFile <<std::endl;
-   if(TransactionManager != NULL &&
-      TransactionManager->TransactionHasError() == false)
-      TransactionManager->CommitTransaction();
-}
-                                                                       /*}}}*/
 std::string pkgAcqMetaIndex::DescURI() const                           /*{{{*/
 {
    return Target.URI;
@@ -1657,29 +1733,7 @@ void pkgAcqMetaSig::Failed(string const &Message,pkgAcquire::MethodConfig const
    // only allow going further if the user explicitly wants it
    if (AllowInsecureRepositories(_("The repository '%s' is not signed."), MetaIndex->Target.Description, TransactionManager->MetaIndexParser, TransactionManager, this) == true)
    {
-      if (RealFileExists(FinalReleasegpg) || RealFileExists(FinalInRelease))
-      {
-        // open the last Release if we have it
-        if (TransactionManager->IMSHit == false)
-        {
-           TransactionManager->LastMetaIndexParser = TransactionManager->MetaIndexParser->UnloadedClone();
-           if (TransactionManager->LastMetaIndexParser != NULL)
-           {
-              _error->PushToStack();
-              if (RealFileExists(FinalInRelease))
-                 TransactionManager->LastMetaIndexParser->Load(FinalInRelease, NULL);
-              else
-                 TransactionManager->LastMetaIndexParser->Load(FinalRelease, NULL);
-              // its unlikely to happen, but if what we have is bad ignore it
-              if (_error->PendingError())
-              {
-                 delete TransactionManager->LastMetaIndexParser;
-                 TransactionManager->LastMetaIndexParser = NULL;
-              }
-              _error->RevertToStack();
-           }
-        }
-      }
+      LoadLastMetaIndexParser(TransactionManager, FinalRelease, FinalInRelease);
 
       // we parse the indexes here because at this point the user wanted
       // a repository that may potentially harm him
@@ -1728,9 +1782,9 @@ pkgAcqDiffIndex::pkgAcqDiffIndex(pkgAcquire * const Owner,
    Debug = _config->FindB("Debug::pkgAcquire::Diffs",false);
 
    Desc.Owner = this;
-   Desc.Description = Target.Description + ".diff/Index";
+   Desc.Description = GetDiffIndexFileName(Target.Description);
    Desc.ShortDesc = Target.ShortDesc;
-   Desc.URI = Target.URI + ".diff/Index";
+   Desc.URI = GetDiffIndexURI(Target);
 
    DestFile = GetPartialFileNameFromURI(Desc.URI);
 
@@ -1821,7 +1875,7 @@ bool pkgAcqDiffIndex::ParseDiffIndex(string const &IndexDiffFile) /*{{{*/
       if (Debug == true)
       {
         std::clog << "pkgAcqDiffIndex: " << IndexDiffFile << ": Index has different hashes than parser, probably older, so fail pdiffing" << std::endl;
-         printHashSumComparision(CurrentPackagesFile, ServerHashes, TargetFileHashes);
+         printHashSumComparison(CurrentPackagesFile, ServerHashes, TargetFileHashes);
       }
       return false;
    }
@@ -2022,6 +2076,17 @@ bool pkgAcqDiffIndex::ParseDiffIndex(string const &IndexDiffFile)        /*{{{*/
       return false;
    }
 
+   for (auto const &patch: available_patches)
+      if (patch.result_hashes.usable() == false ||
+           patch.patch_hashes.usable() == false ||
+           patch.download_hashes.usable() == false)
+      {
+        if (Debug)
+           std::clog << "pkgAcqDiffIndex: " << IndexDiffFile << ": provides no usable hashes for " << patch.file
+              << " so fallback to complete download" << std::endl;
+        return false;
+      }
+
    // patching with too many files is rather slow compared to a fast download
    unsigned long const fileLimit = _config->FindI("Acquire::PDiffs::FileLimit", 0);
    if (fileLimit != 0 && fileLimit < available_patches.size())
@@ -2033,18 +2098,37 @@ bool pkgAcqDiffIndex::ParseDiffIndex(string const &IndexDiffFile)       /*{{{*/
    }
 
    // calculate the size of all patches we have to get
-   // note that all sizes are uncompressed, while we download compressed files
-   unsigned long long patchesSize = 0;
-   for (std::vector<DiffInfo>::const_iterator cur = available_patches.begin();
-        cur != available_patches.end(); ++cur)
-      patchesSize += cur->patch_hashes.FileSize();
-   unsigned long long const sizeLimit = ServerSize * _config->FindI("Acquire::PDiffs::SizeLimit", 100);
-   if (sizeLimit > 0 && (sizeLimit/100) < patchesSize)
-   {
-      if (Debug)
-        std::clog << "Need " << patchesSize << " bytes (Limit is " << sizeLimit/100
-           << ") so fallback to complete download" << std::endl;
-      return false;
+   unsigned short const sizeLimitPercent = _config->FindI("Acquire::PDiffs::SizeLimit", 100);
+   if (sizeLimitPercent > 0 && TransactionManager->MetaIndexParser != nullptr)
+   {
+      unsigned long long downloadSize = std::accumulate(available_patches.begin(),
+           available_patches.end(), 0llu, [](unsigned long long const T, DiffInfo const &I) {
+           return T + I.download_hashes.FileSize();
+           });
+      if (downloadSize != 0)
+      {
+        unsigned long long downloadSizeIdx = 0;
+        auto const types = VectorizeString(Target.Option(IndexTarget::COMPRESSIONTYPES), ' ');
+        for (auto const &t : types)
+        {
+           std::string MetaKey = Target.MetaKey;
+           if (t != "uncompressed")
+              MetaKey += '.' + t;
+           HashStringList const hsl = GetExpectedHashesFor(MetaKey);
+           if (unlikely(hsl.usable() == false))
+              continue;
+           downloadSizeIdx = hsl.FileSize();
+           break;
+        }
+        unsigned long long const sizeLimit = downloadSizeIdx * sizeLimitPercent;
+        if ((sizeLimit/100) < downloadSize)
+        {
+           if (Debug)
+              std::clog << "Need " << downloadSize << " compressed bytes (Limit is " << (sizeLimit/100) << ", "
+                 << "original is " << downloadSizeIdx << ") so fallback to complete download" << std::endl;
+           return false;
+        }
+      }
    }
 
    // we have something, queue the diffs
@@ -2065,6 +2149,41 @@ bool pkgAcqDiffIndex::ParseDiffIndex(string const &IndexDiffFile)        /*{{{*/
       pdiff_merge = (precedence != "merged");
    }
 
+   // clean the plate
+   {
+      std::string const Final = GetExistingFilename(CurrentPackagesFile);
+      if (unlikely(Final.empty())) // because we wouldn't be called in such a case
+        return false;
+      std::string const PartialFile = GetPartialFileNameFromURI(Target.URI);
+      if (FileExists(PartialFile) && RemoveFile("Bootstrap-linking", PartialFile) == false)
+      {
+        if (Debug)
+           std::clog << "Bootstrap-linking for patching " << CurrentPackagesFile
+              << " by removing stale " << PartialFile << " failed!" << std::endl;
+        return false;
+      }
+      for (auto const &ext : APT::Configuration::getCompressorExtensions())
+      {
+        std::string const Partial = PartialFile + ext;
+        if (FileExists(Partial) && RemoveFile("Bootstrap-linking", Partial) == false)
+        {
+           if (Debug)
+              std::clog << "Bootstrap-linking for patching " << CurrentPackagesFile
+                 << " by removing stale " << Partial << " failed!" << std::endl;
+           return false;
+        }
+      }
+      std::string const Ext = Final.substr(CurrentPackagesFile.length());
+      std::string const Partial = PartialFile + Ext;
+      if (symlink(Final.c_str(), Partial.c_str()) != 0)
+      {
+        if (Debug)
+           std::clog << "Bootstrap-linking for patching " << CurrentPackagesFile
+              << " by linking " << Final << " to " << Partial << " failed!" << std::endl;
+        return false;
+      }
+   }
+
    if (pdiff_merge == false)
       new pkgAcqIndexDiffs(Owner, TransactionManager, Target, available_patches);
    else
@@ -2159,13 +2278,6 @@ pkgAcqIndexDiffs::pkgAcqIndexDiffs(pkgAcquire * const Owner,
    }
    else
    {
-      if (BootstrapPDiffWith(GetPartialFileNameFromURI(Target.URI), GetFinalFilename(), Target) == false)
-      {
-        Failed("Bootstrapping of " + DestFile + " failed", NULL);
-        return;
-      }
-
-      // get the next diff
       State = StateFetchDiff;
       QueueNextDiff();
    }
@@ -2183,7 +2295,10 @@ void pkgAcqIndexDiffs::Failed(string const &Message,pkgAcquire::MethodConfig con
    RenameOnError(PDiffError);
    std::string const patchname = GetDiffsPatchFileName(DestFile);
    if (RealFileExists(patchname))
-      rename(patchname.c_str(), std::string(patchname + ".FAILED").c_str());
+      Rename(patchname, patchname + ".FAILED");
+   std::string const UnpatchedFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI));
+   if (UnpatchedFile.empty() == false && FileExists(UnpatchedFile))
+      Rename(UnpatchedFile, UnpatchedFile + ".FAILED");
    new pkgAcqIndex(Owner, TransactionManager, Target);
    Finish();
 }
@@ -2200,13 +2315,7 @@ void pkgAcqIndexDiffs::Finish(bool allDone)
    // the file will be cleaned
    if(allDone)
    {
-      std::string Final = GetFinalFilename();
-      if (Target.KeepCompressed)
-      {
-        std::string const ext = flExtension(DestFile);
-        if (ext.empty() == false)
-           Final.append(".").append(ext);
-      }
+      std::string const Final = GetKeepCompressedFileName(GetFinalFilename(), Target);
       TransactionManager->TransactionStageCopy(this, DestFile, Final);
 
       // this is for the "real" finish
@@ -2231,29 +2340,28 @@ void pkgAcqIndexDiffs::Finish(bool allDone)
 bool pkgAcqIndexDiffs::QueueNextDiff()                                 /*{{{*/
 {
    // calc sha1 of the just patched file
-   std::string const FinalFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
-   if(!FileExists(FinalFile))
+   std::string const PartialFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI));
+   if(unlikely(PartialFile.empty()))
    {
-      Failed("Message: No FinalFile " + FinalFile + " available", NULL);
+      Failed("Message: The file " + GetPartialFileNameFromURI(Target.URI) + " isn't available", NULL);
       return false;
    }
 
-   FileFd fd(FinalFile, FileFd::ReadOnly, FileFd::Extension);
+   FileFd fd(PartialFile, FileFd::ReadOnly, FileFd::Extension);
    Hashes LocalHashesCalc;
    LocalHashesCalc.AddFD(fd);
    HashStringList const LocalHashes = LocalHashesCalc.GetHashStringList();
 
    if(Debug)
-      std::clog << "QueueNextDiff: " << FinalFile << " (" << LocalHashes.find(NULL)->toStr() << ")" << std::endl;
+      std::clog << "QueueNextDiff: " << PartialFile << " (" << LocalHashes.find(NULL)->toStr() << ")" << std::endl;
 
    HashStringList const TargetFileHashes = GetExpectedHashesFor(Target.MetaKey);
    if (unlikely(LocalHashes.usable() == false || TargetFileHashes.usable() == false))
    {
-      Failed("Local/Expected hashes are not usable", NULL);
+      Failed("Local/Expected hashes are not usable for " + PartialFile, NULL);
       return false;
    }
 
-
    // final file reached before all patches are applied
    if(LocalHashes == TargetFileHashes)
    {
@@ -2271,7 +2379,7 @@ bool pkgAcqIndexDiffs::QueueNextDiff()                                    /*{{{*/
    // error checking and falling back if no patch was found
    if(available_patches.empty() == true)
    {
-      Failed("No patches left to reach target", NULL);
+      Failed("No patches left to reach target for " + PartialFile, NULL);
       return false;
    }
 
@@ -2291,55 +2399,51 @@ bool pkgAcqIndexDiffs::QueueNextDiff()                                  /*{{{*/
 void pkgAcqIndexDiffs::Done(string const &Message, HashStringList const &Hashes,       /*{{{*/
                            pkgAcquire::MethodConfig const * const Cnf)
 {
-   if(Debug)
+   if (Debug)
       std::clog << "pkgAcqIndexDiffs::Done(): " << Desc.URI << std::endl;
 
    Item::Done(Message, Hashes, Cnf);
 
-   std::string const FinalFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
-   std::string const PatchFile = GetDiffsPatchFileName(FinalFile);
+   std::string const UncompressedUnpatchedFile = GetPartialFileNameFromURI(Target.URI);
+   std::string const UnpatchedFile = GetExistingFilename(UncompressedUnpatchedFile);
+   std::string const PatchFile = GetDiffsPatchFileName(UnpatchedFile);
+   std::string const PatchedFile = GetKeepCompressedFileName(UncompressedUnpatchedFile, Target);
 
-   // success in downloading a diff, enter ApplyDiff state
-   if(State == StateFetchDiff)
+   switch (State)
    {
-      Rename(DestFile, PatchFile);
-
-      if(Debug)
-        std::clog << "Sending to rred method: " << FinalFile << std::endl;
-
-      State = StateApplyDiff;
-      Local = true;
-      Desc.URI = "rred:" + FinalFile;
-      QueueURI(Desc);
-      SetActiveSubprocess("rred");
-      return;
-   }
-
-   // success in download/apply a diff, queue next (if needed)
-   if(State == StateApplyDiff)
-   {
-      // remove the just applied patch
-      available_patches.erase(available_patches.begin());
-      RemoveFile("pkgAcqIndexDiffs::Done", PatchFile);
-
-      // move into place
-      if(Debug)
-      {
-        std::clog << "Moving patched file in place: " << std::endl
-                  << DestFile << " -> " << FinalFile << std::endl;
-      }
-      Rename(DestFile,FinalFile);
-      chmod(FinalFile.c_str(),0644);
-
-      // see if there is more to download
-      if(available_patches.empty() == false) {
-        new pkgAcqIndexDiffs(Owner, TransactionManager, Target,
-                             available_patches);
-        return Finish();
-      } else 
-         // update
-         DestFile = FinalFile;
-        return Finish(true);
+      // success in downloading a diff, enter ApplyDiff state
+      case StateFetchDiff:
+        Rename(DestFile, PatchFile);
+        DestFile = GetKeepCompressedFileName(UncompressedUnpatchedFile + "-patched", Target);
+        if(Debug)
+           std::clog << "Sending to rred method: " << UnpatchedFile << std::endl;
+        State = StateApplyDiff;
+        Local = true;
+        Desc.URI = "rred:" + UnpatchedFile;
+        QueueURI(Desc);
+        SetActiveSubprocess("rred");
+        return;
+      // success in download/apply a diff, queue next (if needed)
+      case StateApplyDiff:
+        // remove the just applied patch and base file
+        available_patches.erase(available_patches.begin());
+        RemoveFile("pkgAcqIndexDiffs::Done", PatchFile);
+        RemoveFile("pkgAcqIndexDiffs::Done", UnpatchedFile);
+        if(Debug)
+           std::clog << "Moving patched file in place: " << std::endl
+              << DestFile << " -> " << PatchedFile << std::endl;
+        Rename(DestFile, PatchedFile);
+
+        // see if there is more to download
+        if(available_patches.empty() == false)
+        {
+           new pkgAcqIndexDiffs(Owner, TransactionManager, Target, available_patches);
+           Finish();
+        } else {
+           DestFile = PatchedFile;
+           Finish(true);
+        }
+        return;
    }
 }
                                                                        /*}}}*/
@@ -2371,11 +2475,9 @@ pkgAcqIndexMergeDiffs::pkgAcqIndexMergeDiffs(pkgAcquire * const Owner,
    Desc.Owner = this;
    Description = Target.Description;
    Desc.ShortDesc = Target.ShortDesc;
-
    Desc.URI = Target.URI + ".diff/" + patch.file + ".gz";
-   Desc.Description = Description + " " + patch.file + string(".pdiff");
-
-   DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI + ".diff/" + patch.file), Target);
+   Desc.Description = Description + " " + patch.file + ".pdiff";
+   DestFile = GetPartialFileNameFromURI(Desc.URI);
 
    if(Debug)
       std::clog << "pkgAcqIndexMergeDiffs: " << Desc.URI << std::endl;
@@ -2396,19 +2498,23 @@ void pkgAcqIndexMergeDiffs::Failed(string const &Message,pkgAcquire::MethodConfi
    for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin();
         I != allPatches->end(); ++I)
       if ((*I)->State == StateErrorDiff)
+      {
+        State = StateErrorDiff;
         return;
+      }
 
    // first failure means we should fallback
    State = StateErrorDiff;
    if (Debug)
       std::clog << "Falling back to normal index file acquire" << std::endl;
-   DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
    RenameOnError(PDiffError);
-   std::string const patchname = GetMergeDiffsPatchFileName(DestFile, patch.file);
-   if (RealFileExists(patchname))
-      rename(patchname.c_str(), std::string(patchname + ".FAILED").c_str());
-   new pkgAcqIndex(Owner, TransactionManager, Target);
+   if (RealFileExists(DestFile))
+      Rename(DestFile, DestFile + ".FAILED");
+   std::string const UnpatchedFile = GetExistingFilename(GetPartialFileNameFromURI(Target.URI));
+   if (UnpatchedFile.empty() == false && FileExists(UnpatchedFile))
+      Rename(UnpatchedFile, UnpatchedFile + ".FAILED");
    DestFile.clear();
+   new pkgAcqIndex(Owner, TransactionManager, Target);
 }
                                                                        /*}}}*/
 void pkgAcqIndexMergeDiffs::Done(string const &Message, HashStringList const &Hashes,  /*{{{*/
@@ -2419,67 +2525,73 @@ void pkgAcqIndexMergeDiffs::Done(string const &Message, HashStringList const &Ha
 
    Item::Done(Message, Hashes, Cnf);
 
-   std::string const UncompressedFinalFile = GetPartialFileNameFromURI(Target.URI);
-   std::string const FinalFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
-   if (State == StateFetchDiff)
+   if (std::any_of(allPatches->begin(), allPatches->end(),
+           [](pkgAcqIndexMergeDiffs const * const P) { return P->State == StateErrorDiff; }))
    {
-      Rename(DestFile, GetMergeDiffsPatchFileName(FinalFile, patch.file));
-
-      // check if this is the last completed diff
-      State = StateDoneDiff;
-      for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin();
-           I != allPatches->end(); ++I)
-        if ((*I)->State != StateDoneDiff)
-        {
-           if(Debug)
-              std::clog << "Not the last done diff in the batch: " << Desc.URI << std::endl;
-           return;
-        }
-
-      // this is the last completed diff, so we are ready to apply now
-      State = StateApplyDiff;
-
-      if (BootstrapPDiffWith(UncompressedFinalFile, GetFinalFilename(), Target) == false)
-      {
-        Failed("Bootstrapping of " + DestFile + " failed", NULL);
-        return;
-      }
-
       if(Debug)
-        std::clog << "Sending to rred method: " << FinalFile << std::endl;
-
-      Local = true;
-      Desc.URI = "rred:" + FinalFile;
-      QueueURI(Desc);
-      SetActiveSubprocess("rred");
+        std::clog << "Another patch failed already, no point in processing this one." << std::endl;
+      State = StateErrorDiff;
       return;
    }
-   // success in download/apply all diffs, clean up
-   else if (State == StateApplyDiff)
-   {
-      // move the result into place
-      std::string const Final = GetKeepCompressedFileName(GetFinalFilename(), Target);
-      if(Debug)
-        std::clog << "Queue patched file in place: " << std::endl
-                  << DestFile << " -> " << Final << std::endl;
 
-      // queue for copy by the transaction manager
-      TransactionManager->TransactionStageCopy(this, DestFile, Final);
+   std::string const UncompressedUnpatchedFile = GetPartialFileNameFromURI(Target.URI);
+   std::string const UnpatchedFile = GetExistingFilename(UncompressedUnpatchedFile);
+   if (UnpatchedFile.empty())
+   {
+      _error->Fatal("Unpatched file %s doesn't exist (anymore)!", UncompressedUnpatchedFile.c_str());
+      State = StateErrorDiff;
+      return;
+   }
+   std::string const PatchFile = GetMergeDiffsPatchFileName(UnpatchedFile, patch.file);
+   std::string const PatchedFile = GetKeepCompressedFileName(UncompressedUnpatchedFile, Target);
 
-      // ensure the ed's are gone regardless of list-cleanup
-      for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin();
-           I != allPatches->end(); ++I)
-      {
-        std::string const PartialFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
-        std::string const patch = GetMergeDiffsPatchFileName(PartialFile, (*I)->patch.file);
-        RemoveFile("pkgAcqIndexMergeDiffs::Done", patch);
-      }
-      RemoveFile("pkgAcqIndexMergeDiffs::Done", FinalFile);
+   switch (State)
+   {
+      case StateFetchDiff:
+        Rename(DestFile, PatchFile);
 
-      // all set and done
-      Complete = true;
-      if(Debug)
-        std::clog << "allDone: " << DestFile << "\n" << std::endl;
+        // check if this is the last completed diff
+        State = StateDoneDiff;
+        for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin();
+              I != allPatches->end(); ++I)
+           if ((*I)->State != StateDoneDiff)
+           {
+              if(Debug)
+                 std::clog << "Not the last done diff in the batch: " << Desc.URI << std::endl;
+              return;
+           }
+        // this is the last completed diff, so we are ready to apply now
+        DestFile = GetKeepCompressedFileName(UncompressedUnpatchedFile + "-patched", Target);
+        if(Debug)
+           std::clog << "Sending to rred method: " << UnpatchedFile << std::endl;
+        State = StateApplyDiff;
+        Local = true;
+        Desc.URI = "rred:" + UnpatchedFile;
+        QueueURI(Desc);
+        SetActiveSubprocess("rred");
+        return;
+      case StateApplyDiff:
+        // success in download & apply all diffs, finialize and clean up
+        if(Debug)
+           std::clog << "Queue patched file in place: " << std::endl
+              << DestFile << " -> " << PatchedFile << std::endl;
+
+        // queue for copy by the transaction manager
+        TransactionManager->TransactionStageCopy(this, DestFile, GetKeepCompressedFileName(GetFinalFilename(), Target));
+
+        // ensure the ed's are gone regardless of list-cleanup
+        for (std::vector<pkgAcqIndexMergeDiffs *>::const_iterator I = allPatches->begin();
+              I != allPatches->end(); ++I)
+           RemoveFile("pkgAcqIndexMergeDiffs::Done", GetMergeDiffsPatchFileName(UnpatchedFile, (*I)->patch.file));
+        RemoveFile("pkgAcqIndexMergeDiffs::Done", UnpatchedFile);
+
+        // all set and done
+        Complete = true;
+        if(Debug)
+           std::clog << "allDone: " << DestFile << "\n" << std::endl;
+        return;
+      case StateDoneDiff: _error->Fatal("Done called for %s which is in an invalid Done state", PatchFile.c_str()); break;
+      case StateErrorDiff: _error->Fatal("Done called for %s which is in an invalid Error state", PatchFile.c_str()); break;
    }
 }
                                                                        /*}}}*/
@@ -2542,6 +2654,10 @@ void pkgAcqIndex::Init(string const &URI, string const &URIDesc,
    DestFile = GetPartialFileNameFromURI(URI);
    NextCompressionExtension(CurrentCompressionExtension, CompressionExtensions, false);
 
+   // store file size of the download to ensure the fetcher gives
+   // accurate progress reporting
+   FileSize = GetExpectedHashes().FileSize();
+
    if (CurrentCompressionExtension == "uncompressed")
    {
       Desc.URI = URI;
@@ -2631,20 +2747,6 @@ void pkgAcqIndex::Failed(string const &Message,pkgAcquire::MethodConfig const *
       TransactionManager->AbortTransaction();
 }
                                                                        /*}}}*/
-// AcqIndex::ReverifyAfterIMS - Reverify index after an ims-hit                /*{{{*/
-void pkgAcqIndex::ReverifyAfterIMS()
-{
-   // update destfile to *not* include the compression extension when doing
-   // a reverify (as its uncompressed on disk already)
-   DestFile = GetCompressedFileName(Target, GetPartialFileNameFromURI(Target.URI), CurrentCompressionExtension);
-
-   // copy FinalFile into partial/ so that we check the hash again
-   string FinalFile = GetFinalFilename();
-   Stage = STAGE_DECOMPRESS_AND_VERIFY;
-   Desc.URI = "copy:" + FinalFile;
-   QueueURI(Desc);
-}
-                                                                       /*}}}*/
 // AcqIndex::Done - Finished a fetch                                   /*{{{*/
 // ---------------------------------------------------------------------
 /* This goes through a number of states.. On the initial fetch the
@@ -2661,107 +2763,92 @@ void pkgAcqIndex::Done(string const &Message,
    switch(Stage) 
    {
       case STAGE_DOWNLOAD:
-         StageDownloadDone(Message, Hashes, Cfg);
+         StageDownloadDone(Message);
          break;
       case STAGE_DECOMPRESS_AND_VERIFY:
-         StageDecompressDone(Message, Hashes, Cfg);
+         StageDecompressDone();
          break;
    }
 }
                                                                        /*}}}*/
 // AcqIndex::StageDownloadDone - Queue for decompress and verify       /*{{{*/
-void pkgAcqIndex::StageDownloadDone(string const &Message, HashStringList const &,
-                                    pkgAcquire::MethodConfig const * const)
+void pkgAcqIndex::StageDownloadDone(string const &Message)
 {
+   Local = true;
    Complete = true;
 
-   // Handle the unzipd case
-   std::string FileName = LookupTag(Message,"Alt-Filename");
-   if (FileName.empty() == false)
+   std::string const AltFilename = LookupTag(Message,"Alt-Filename");
+   std::string Filename = LookupTag(Message,"Filename");
+
+   // we need to verify the file against the current Release file again
+   // on if-modfied-since hit to avoid a stale attack against us
+   if(StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
    {
+      // copy FinalFile into partial/ so that we check the hash again
+      string const FinalFile = GetExistingFilename(GetFinalFileNameFromURI(Target.URI));
+      if (symlink(FinalFile.c_str(), DestFile.c_str()) != 0)
+        _error->WarningE("pkgAcqIndex::StageDownloadDone", "Symlinking final file %s back to %s failed", FinalFile.c_str(), DestFile.c_str());
+      else
+      {
+        EraseFileName = DestFile;
+        Filename = DestFile;
+      }
       Stage = STAGE_DECOMPRESS_AND_VERIFY;
-      Local = true;
-      if (CurrentCompressionExtension != "uncompressed")
-        DestFile.erase(DestFile.length() - (CurrentCompressionExtension.length() + 1));
-      Desc.URI = "copy:" + FileName;
+      Desc.URI = "store:" + Filename;
       QueueURI(Desc);
-      SetActiveSubprocess("copy");
+      SetActiveSubprocess(::URI(Desc.URI).Access);
       return;
    }
-   FileName = LookupTag(Message,"Filename");
-
+   // methods like file:// give us an alternative (uncompressed) file
+   else if (Target.KeepCompressed == false && AltFilename.empty() == false)
+   {
+      Filename = AltFilename;
+      EraseFileName.clear();
+   }
    // Methods like e.g. "file:" will give us a (compressed) FileName that is
    // not the "DestFile" we set, in this case we uncompress from the local file
-   if (FileName != DestFile && RealFileExists(DestFile) == false)
+   else if (Filename != DestFile && RealFileExists(DestFile) == false)
    {
-      Local = true;
-      if (Target.KeepCompressed == true)
-      {
-        // but if we don't keep the uncompress we copy the compressed file first
-        Stage = STAGE_DOWNLOAD;
-        Desc.URI = "copy:" + FileName;
-        QueueURI(Desc);
-        SetActiveSubprocess("copy");
-        return;
-      }
+      // symlinking ensures that the filename can be used for compression detection
+      // that is e.g. needed for by-hash which has no extension over file
+      if (symlink(Filename.c_str(),DestFile.c_str()) != 0)
+        _error->WarningE("pkgAcqIndex::StageDownloadDone", "Symlinking file %s to %s failed", Filename.c_str(), DestFile.c_str());
       else
       {
-        // symlinking ensures that the filename can be used for compression detection
-        // that is e.g. needed for by-hash over file
-        if (symlink(FileName.c_str(),DestFile.c_str()) != 0)
-           _error->WarningE("pkgAcqIndex::StageDownloadDone", "Symlinking file %s to %s failed", FileName.c_str(), DestFile.c_str());
-        else
-        {
-           EraseFileName = DestFile;
-           FileName = DestFile;
-        }
+        EraseFileName = DestFile;
+        Filename = DestFile;
       }
    }
-   else
-      EraseFileName = FileName;
 
-   // we need to verify the file against the current Release file again
-   // on if-modfied-since hit to avoid a stale attack against us
-   if(StringToBool(LookupTag(Message,"IMS-Hit"),false) == true)
-   {
-      // The files timestamp matches, reverify by copy into partial/
-      EraseFileName = "";
-      ReverifyAfterIMS();
-      return;
-   }
-
-   string decompProg = "store";
-   if (Target.KeepCompressed == true)
-   {
-      DestFile = "/dev/null";
-      EraseFileName.clear();
-   }
+   Stage = STAGE_DECOMPRESS_AND_VERIFY;
+   DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
+   if (Filename != DestFile && flExtension(Filename) == flExtension(DestFile))
+      Desc.URI = "copy:" + Filename;
    else
+      Desc.URI = "store:" + Filename;
+   if (DestFile == Filename)
    {
       if (CurrentCompressionExtension == "uncompressed")
-        decompProg = "copy";
-      else
-        DestFile.erase(DestFile.length() - (CurrentCompressionExtension.length() + 1));
+        return StageDecompressDone();
+      DestFile = "/dev/null";
    }
 
+   if (EraseFileName.empty() && Filename != AltFilename)
+      EraseFileName = Filename;
+
    // queue uri for the next stage
-   Stage = STAGE_DECOMPRESS_AND_VERIFY;
-   Desc.URI = decompProg + ":" + FileName;
    QueueURI(Desc);
-   SetActiveSubprocess(decompProg);
+   SetActiveSubprocess(::URI(Desc.URI).Access);
 }
                                                                        /*}}}*/
 // AcqIndex::StageDecompressDone - Final verification                  /*{{{*/
-void pkgAcqIndex::StageDecompressDone(string const &,
-                                      HashStringList const &,
-                                      pkgAcquire::MethodConfig const * const)
+void pkgAcqIndex::StageDecompressDone()
 {
-   if (Target.KeepCompressed == true && DestFile == "/dev/null")
-      DestFile = GetPartialFileNameFromURI(Target.URI + '.' + CurrentCompressionExtension);
+   if (DestFile == "/dev/null")
+      DestFile = GetKeepCompressedFileName(GetPartialFileNameFromURI(Target.URI), Target);
 
    // Done, queue for rename on transaction finished
    TransactionManager->TransactionStageCopy(this, DestFile, GetFinalFilename());
-   return;
 }
                                                                        /*}}}*/
 pkgAcqIndex::~pkgAcqIndex() {}
@@ -3057,9 +3144,14 @@ std::string pkgAcqArchive::ShortDesc() const                             /*{{{*/
 pkgAcqArchive::~pkgAcqArchive() {}
 
 // AcqChangelog::pkgAcqChangelog - Constructors                                /*{{{*/
+class pkgAcqChangelog::Private
+{
+   public:
+   std::string FinalFile;
+};
 pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::VerIterator const &Ver,
       std::string const &DestDir, std::string const &DestFilename) :
-   pkgAcquire::Item(Owner), d(NULL), SrcName(Ver.SourcePkgName()), SrcVersion(Ver.SourceVerStr())
+   pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(Ver.SourcePkgName()), SrcVersion(Ver.SourceVerStr())
 {
    Desc.URI = URI(Ver);
    Init(DestDir, DestFilename);
@@ -3068,7 +3160,7 @@ pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::VerIterator
 pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::RlsFileIterator const &RlsFile,
       char const * const Component, char const * const SrcName, char const * const SrcVersion,
       const string &DestDir, const string &DestFilename) :
-   pkgAcquire::Item(Owner), d(NULL), SrcName(SrcName), SrcVersion(SrcVersion)
+   pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(SrcName), SrcVersion(SrcVersion)
 {
    Desc.URI = URI(RlsFile, Component, SrcName, SrcVersion);
    Init(DestDir, DestFilename);
@@ -3076,7 +3168,7 @@ pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner, pkgCache::RlsFileIter
 pkgAcqChangelog::pkgAcqChangelog(pkgAcquire * const Owner,
       std::string const &URI, char const * const SrcName, char const * const SrcVersion,
       const string &DestDir, const string &DestFilename) :
-   pkgAcquire::Item(Owner), d(NULL), SrcName(SrcName), SrcVersion(SrcVersion)
+   pkgAcquire::Item(Owner), d(new pkgAcqChangelog::Private()), SrcName(SrcName), SrcVersion(SrcVersion)
 {
    Desc.URI = URI;
    Init(DestDir, DestFilename);
@@ -3097,30 +3189,44 @@ void pkgAcqChangelog::Init(std::string const &DestDir, std::string const &DestFi
       return;
    }
 
-   if (DestDir.empty())
+   std::string DestFileName;
+   if (DestFilename.empty())
+      DestFileName = flCombine(DestFile, SrcName + ".changelog");
+   else
+      DestFileName = flCombine(DestFile, DestFilename);
+
+   std::string const SandboxUser = _config->Find("APT::Sandbox::User");
+   std::string const systemTemp = GetTempDir(SandboxUser);
+   char tmpname[1000];
+   snprintf(tmpname, sizeof(tmpname), "%s/apt-changelog-XXXXXX", systemTemp.c_str());
+   if (NULL == mkdtemp(tmpname))
+   {
+      _error->Errno("mkdtemp", "mkdtemp failed in changelog acquire of %s %s", SrcName.c_str(), SrcVersion.c_str());
+      Status = StatError;
+      return;
+   }
+   TemporaryDirectory = tmpname;
+
+   ChangeOwnerAndPermissionOfFile("Item::QueueURI", TemporaryDirectory.c_str(),
+        SandboxUser.c_str(), "root", 0700);
+
+   DestFile = flCombine(TemporaryDirectory, DestFileName);
+   if (DestDir.empty() == false)
    {
-      std::string const SandboxUser = _config->Find("APT::Sandbox::User");
-      std::string const systemTemp = GetTempDir(SandboxUser);
-      char tmpname[100];
-      snprintf(tmpname, sizeof(tmpname), "%s/apt-changelog-XXXXXX", systemTemp.c_str());
-      if (NULL == mkdtemp(tmpname))
+      d->FinalFile = flCombine(DestDir, DestFileName);
+      if (RealFileExists(d->FinalFile))
       {
-        _error->Errno("mkdtemp", "mkdtemp failed in changelog acquire of %s %s", SrcName.c_str(), SrcVersion.c_str());
-        Status = StatError;
-        return;
+        FileFd file1, file2;
+        if (file1.Open(DestFile, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive) &&
+              file2.Open(d->FinalFile, FileFd::ReadOnly) && CopyFile(file2, file1))
+        {
+           struct timeval times[2];
+           times[0].tv_sec = times[1].tv_sec = file2.ModificationTime();
+           times[0].tv_usec = times[1].tv_usec = 0;
+           utimes(DestFile.c_str(), times);
+        }
       }
-      DestFile = TemporaryDirectory = tmpname;
-
-      ChangeOwnerAndPermissionOfFile("Item::QueueURI", DestFile.c_str(),
-                                     SandboxUser.c_str(), "root", 0700);
    }
-   else
-      DestFile = DestDir;
-
-   if (DestFilename.empty())
-      DestFile = flCombine(DestFile, SrcName + ".changelog");
-   else
-      DestFile = flCombine(DestFile, DestFilename);
 
    Desc.ShortDesc = "Changelog";
    strprintf(Desc.Description, "%s %s %s Changelog", URI::SiteOnly(Desc.URI).c_str(), SrcName.c_str(), SrcVersion.c_str());
@@ -3130,16 +3236,47 @@ void pkgAcqChangelog::Init(std::string const &DestDir, std::string const &DestFi
                                                                        /*}}}*/
 std::string pkgAcqChangelog::URI(pkgCache::VerIterator const &Ver)     /*{{{*/
 {
+   std::string const confOnline = "Acquire::Changelogs::AlwaysOnline";
+   bool AlwaysOnline = _config->FindB(confOnline, false);
+   if (AlwaysOnline == false)
+      for (pkgCache::VerFileIterator VF = Ver.FileList(); VF.end() == false; ++VF)
+      {
+        pkgCache::PkgFileIterator const PF = VF.File();
+        if (PF.Flagged(pkgCache::Flag::NotSource) || PF->Release == 0)
+           continue;
+        pkgCache::RlsFileIterator const RF = PF.ReleaseFile();
+        if (RF->Origin != 0 && _config->FindB(confOnline + "::Origin::" + RF.Origin(), false))
+        {
+           AlwaysOnline = true;
+           break;
+        }
+      }
+   if (AlwaysOnline == false)
+   {
+      pkgCache::PkgIterator const Pkg = Ver.ParentPkg();
+      if (Pkg->CurrentVer != 0 && Pkg.CurrentVer() == Ver)
+      {
+        std::string const basename = std::string("/usr/share/doc/") + Pkg.Name() + "/changelog";
+        std::string const debianname = basename + ".Debian";
+        if (FileExists(debianname))
+           return "copy://" + debianname;
+        else if (FileExists(debianname + ".gz"))
+           return "gzip://" + debianname + ".gz";
+        else if (FileExists(basename))
+           return "copy://" + basename;
+        else if (FileExists(basename + ".gz"))
+           return "gzip://" + basename + ".gz";
+      }
+   }
+
    char const * const SrcName = Ver.SourcePkgName();
    char const * const SrcVersion = Ver.SourceVerStr();
-   pkgCache::PkgFileIterator PkgFile;
    // find the first source for this version which promises a changelog
    for (pkgCache::VerFileIterator VF = Ver.FileList(); VF.end() == false; ++VF)
    {
       pkgCache::PkgFileIterator const PF = VF.File();
       if (PF.Flagged(pkgCache::Flag::NotSource) || PF->Release == 0)
         continue;
-      PkgFile = PF;
       pkgCache::RlsFileIterator const RF = PF.ReleaseFile();
       std::string const uri = URI(RF, PF.Component(), SrcName, SrcVersion);
       if (uri.empty())
@@ -3236,7 +3373,6 @@ void pkgAcqChangelog::Failed(string const &Message, pkgAcquire::MethodConfig con
       ErrorText = errText;
    else
       ErrorText = errText + " (" + ErrorText + ")";
-   return;
 }
                                                                        /*}}}*/
 // AcqChangelog::Done - Item downloaded OK                             /*{{{*/
@@ -3244,6 +3380,12 @@ void pkgAcqChangelog::Done(string const &Message,HashStringList const &CalcHashe
                      pkgAcquire::MethodConfig const * const Cnf)
 {
    Item::Done(Message,CalcHashes,Cnf);
+   if (d->FinalFile.empty() == false)
+   {
+      if (RemoveFile("pkgAcqChangelog::Done", d->FinalFile) == false ||
+           Rename(DestFile, d->FinalFile) == false)
+        Status = StatError;
+   }
 
    Complete = true;
 }
@@ -3255,6 +3397,7 @@ pkgAcqChangelog::~pkgAcqChangelog()                                       /*{{{*/
       RemoveFile("~pkgAcqChangelog", DestFile);
       rmdir(TemporaryDirectory.c_str());
    }
+   delete d;
 }
                                                                        /*}}}*/