Bladeren bron

Merge branch 'feature/apt-dpkg-comm'

David Kalnischkies 10 jaren geleden
bovenliggende
commit
0a7370ca91
28 gewijzigde bestanden met toevoegingen van 703 en 246 verwijderingen
  1. 63 2
      apt-pkg/algorithms.cc
  2. 9 1
      apt-pkg/algorithms.h
  3. 441 76
      apt-pkg/deb/dpkgpm.cc
  4. 8 4
      apt-pkg/deb/dpkgpm.h
  5. 9 4
      apt-pkg/packagemanager.cc
  6. 1 1
      apt-pkg/packagemanager.h
  7. 2 1
      apt-pkg/statechanges.cc
  8. 7 77
      doc/apt.conf.5.xml
  9. 3 1
      doc/external-installation-planner-protocol.txt
  10. 4 4
      test/integration/test-allow-scores-for-all-dependency-types
  11. 9 1
      test/integration/test-apt-get-autoremove
  12. 1 1
      test/integration/test-apt-progress-fd-error
  13. 1 1
      test/integration/test-bug-673536-pre-depends-breaks-loop
  14. 51 35
      test/integration/test-bug-712116-dpkg-pre-install-pkgs-hook-multiarch
  15. 3 3
      test/integration/test-bug-735967-lib32-to-i386-unavailable
  16. 8 8
      test/integration/test-bug-740843-versioned-up-down-breaks
  17. 2 2
      test/integration/test-bug-lp1562402-nomark-removals-as-keep
  18. 2 2
      test/integration/test-bug-multiarch-upgrade
  19. 49 0
      test/integration/test-crossgrades
  20. 1 1
      test/integration/test-dpkg-assert-multi-arch
  21. 3 0
      test/integration/test-essential-force-loopbreak
  22. 2 5
      test/integration/test-external-installation-planner-protocol
  23. 1 1
      test/integration/test-ignore-provides-if-versioned-breaks
  24. 1 1
      test/integration/test-ignore-provides-if-versioned-conflicts
  25. 6 2
      test/integration/test-no-fds-leaked-to-maintainer-scripts
  26. 4 4
      test/integration/test-prevent-markinstall-multiarch-same-versionscrew
  27. 4 0
      test/integration/test-ubuntu-bug-761175-remove-purge
  28. 8 8
      test/integration/test-very-tight-loop-configure-with-unpacking-new-packages

+ 63 - 2
apt-pkg/algorithms.cc

@@ -25,22 +25,29 @@
 #include <apt-pkg/pkgcache.h>
 #include <apt-pkg/cacheiterators.h>
 #include <apt-pkg/prettyprinters.h>
+#include <apt-pkg/dpkgpm.h>
 
 #include <string.h>
 #include <string>
 #include <cstdlib>
 #include <iostream>
+#include <utility>
 
 #include <apti18n.h>
 									/*}}}*/
 using namespace std;
 
+class APT_HIDDEN pkgSimulatePrivate
+{
+public:
+   std::vector<pkgDPkgPM::Item> List;
+};
 // Simulate::Simulate - Constructor					/*{{{*/
 // ---------------------------------------------------------------------
 /* The legacy translations here of input Pkg iterators is obsolete, 
    this is not necessary since the pkgCaches are fully shared now. */
 pkgSimulate::pkgSimulate(pkgDepCache *Cache) : pkgPackageManager(Cache),
-		            d(NULL), iPolicy(Cache),
+		            d(new pkgSimulatePrivate()), iPolicy(Cache),
 			    Sim(&Cache->GetCache(),&iPolicy),
 			    group(Sim)
 {
@@ -58,6 +65,7 @@ pkgSimulate::pkgSimulate(pkgDepCache *Cache) : pkgPackageManager(Cache),
 pkgSimulate::~pkgSimulate()
 {
    delete[] Flags;
+   delete d;
 }
 									/*}}}*/
 // Simulate::Describe - Describe a package				/*{{{*/
@@ -90,7 +98,14 @@ void pkgSimulate::Describe(PkgIterator Pkg,ostream &out,bool Current,bool Candid
 // Simulate::Install - Simulate unpacking of a package			/*{{{*/
 // ---------------------------------------------------------------------
 /* */
-bool pkgSimulate::Install(PkgIterator iPkg,string /*File*/)
+bool pkgSimulate::Install(PkgIterator iPkg,string File)
+{
+   if (iPkg.end() || File.empty())
+      return false;
+   d->List.emplace_back(pkgDPkgPM::Item::Install, iPkg, File);
+   return true;
+}
+bool pkgSimulate::RealInstall(PkgIterator iPkg,string /*File*/)
 {
    // Adapt the iterator
    PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch());
@@ -137,6 +152,13 @@ bool pkgSimulate::Install(PkgIterator iPkg,string /*File*/)
    install the package.. For some investigations it may be necessary 
    however. */
 bool pkgSimulate::Configure(PkgIterator iPkg)
+{
+   if (iPkg.end())
+      return false;
+   d->List.emplace_back(pkgDPkgPM::Item::Configure, iPkg);
+   return true;
+}
+bool pkgSimulate::RealConfigure(PkgIterator iPkg)
 {
    // Adapt the iterator
    PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch());
@@ -187,6 +209,13 @@ bool pkgSimulate::Configure(PkgIterator iPkg)
 // ---------------------------------------------------------------------
 /* */
 bool pkgSimulate::Remove(PkgIterator iPkg,bool Purge)
+{
+   if (iPkg.end())
+      return false;
+   d->List.emplace_back(Purge ? pkgDPkgPM::Item::Purge : pkgDPkgPM::Item::Remove, iPkg);
+   return true;
+}
+bool pkgSimulate::RealRemove(PkgIterator iPkg,bool Purge)
 {
    // Adapt the iterator
    PkgIterator Pkg = Sim.FindPkg(iPkg.Name(), iPkg.Arch());
@@ -232,6 +261,38 @@ void pkgSimulate::ShortBreaks()
    cout << ']' << endl;
 }
 									/*}}}*/
+bool pkgSimulate::Go2(APT::Progress::PackageManager *)			/*{{{*/
+{
+   if (pkgDPkgPM::ExpandPendingCalls(d->List, Cache) == false)
+      return false;
+   for (auto && I : d->List)
+      switch (I.Op)
+      {
+	 case pkgDPkgPM::Item::Install:
+	    if (RealInstall(I.Pkg, I.File) == false)
+	       return false;
+	    break;
+	 case pkgDPkgPM::Item::Configure:
+	    if (RealConfigure(I.Pkg) == false)
+	       return false;
+	    break;
+	 case pkgDPkgPM::Item::Remove:
+	    if (RealRemove(I.Pkg, false) == false)
+	       return false;
+	    break;
+	 case pkgDPkgPM::Item::Purge:
+	    if (RealRemove(I.Pkg, true) == false)
+	       return false;
+	    break;
+	 case pkgDPkgPM::Item::ConfigurePending:
+	 case pkgDPkgPM::Item::TriggersPending:
+	 case pkgDPkgPM::Item::RemovePending:
+	 case pkgDPkgPM::Item::PurgePending:
+	    return _error->Error("Internal error, simulation encountered unexpected pending item");
+      }
+   return true;
+}
+									/*}}}*/
 // ApplyStatus - Adjust for non-ok packages				/*{{{*/
 // ---------------------------------------------------------------------
 /* We attempt to change the state of the all packages that have failed

+ 9 - 1
apt-pkg/algorithms.h

@@ -52,9 +52,10 @@ using std::ostream;
 #endif
 
 
+class pkgSimulatePrivate;
 class pkgSimulate : public pkgPackageManager				/*{{{*/
 {
-   void * const d;
+   pkgSimulatePrivate * const d;
    protected:
 
    class Policy : public pkgDepCache::Policy
@@ -81,9 +82,16 @@ class pkgSimulate : public pkgPackageManager				/*{{{*/
    virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE;
    virtual bool Remove(PkgIterator Pkg,bool Purge) APT_OVERRIDE;
 
+   // FIXME: trick to avoid ABI break for virtual reimplementation; fix on next ABI break
+public:
+   APT_HIDDEN bool Go2(APT::Progress::PackageManager * progress);
+
 private:
    APT_HIDDEN void ShortBreaks();
    APT_HIDDEN void Describe(PkgIterator iPkg,std::ostream &out,bool Current,bool Candidate);
+   APT_HIDDEN bool RealInstall(PkgIterator Pkg,std::string File);
+   APT_HIDDEN bool RealConfigure(PkgIterator Pkg);
+   APT_HIDDEN bool RealRemove(PkgIterator Pkg,bool Purge);
 
    public:
 

+ 441 - 76
apt-pkg/deb/dpkgpm.cc

@@ -19,9 +19,11 @@
 #include <apt-pkg/install-progress.h>
 #include <apt-pkg/packagemanager.h>
 #include <apt-pkg/strutl.h>
+#include <apt-pkg/statechanges.h>
 #include <apt-pkg/cacheiterators.h>
 #include <apt-pkg/macros.h>
 #include <apt-pkg/pkgcache.h>
+#include <apt-pkg/version.h>
 
 #include <errno.h>
 #include <fcntl.h>
@@ -36,6 +38,8 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/wait.h>
+#include <sys/types.h>
+#include <dirent.h>
 #include <termios.h>
 #include <time.h>
 #include <unistd.h>
@@ -47,7 +51,9 @@
 #include <map>
 #include <set>
 #include <string>
+#include <type_traits>
 #include <utility>
+#include <unordered_set>
 #include <vector>
 #include <sstream>
 #include <numeric>
@@ -205,6 +211,14 @@ pkgCache::VerIterator FindNowVersion(const pkgCache::PkgIterator &Pkg)
    return Ver;
 }
 									/*}}}*/
+static pkgCache::VerIterator FindToBeRemovedVersion(pkgCache::PkgIterator const &Pkg)/*{{{*/
+{
+   auto const PV = Pkg.CurrentVer();
+   if (PV.end() == false)
+      return PV;
+   return FindNowVersion(Pkg);
+}
+									/*}}}*/
 
 // DPkgPM::pkgDPkgPM - Constructor					/*{{{*/
 // ---------------------------------------------------------------------
@@ -656,27 +670,23 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
       // if there are multiple pkgs dpkg would send us a full pkgname:arch
       pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname);
       if (Grp.end() == false)
-      {
-	 pkgCache::PkgIterator P = Grp.PackageList();
-	 for (; P.end() != true; P = Grp.NextPkg(P))
-	 {
+	 for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
 	    if(Cache[P].Keep() == false || Cache[P].ReInstall() == true)
 	    {
-	       pkgname = P.FullName();
+	       auto fullname = P.FullName();
+	       if (Cache[P].Delete() && PackageOps[fullname].size() <= PackageOpsDone[fullname])
+		  continue;
+	       pkgname = std::move(fullname);
 	       break;
 	    }
-	 }
-      }
    }
 
-   const char* const pkg = pkgname.c_str();
-   std::string short_pkgname = StringSplit(pkgname, ":")[0];
    std::string arch = "";
    if (pkgname.find(":") != string::npos)
       arch = StringSplit(pkgname, ":")[1];
    std::string i18n_pkgname = pkgname;
    if (arch.size() != 0)
-      strprintf(i18n_pkgname, "%s (%s)", short_pkgname.c_str(), arch.c_str());
+      strprintf(i18n_pkgname, "%s (%s)", StringSplit(pkgname, ":")[0].c_str(), arch.c_str());
 
    // 'processing' from dpkg looks like
    // 'processing: action: pkg'
@@ -706,21 +716,21 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
 
    if (prefix == "status")
    {
-      std::vector<struct DpkgState> &states = PackageOps[pkg];
+      std::vector<struct DpkgState> &states = PackageOps[pkgname];
       if (action == "triggers-pending")
       {
 	 if (Debug == true)
-	    std::clog << "(parsed from dpkg) pkg: " << short_pkgname
+	    std::clog << "(parsed from dpkg) pkg: " << pkgname
 	       << " action: " << action << " (prefix 2 to "
-	       << PackageOpsDone[pkg] << " of " << states.size() << ")" << endl;
+	       << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
 
 	 states.insert(states.begin(), {"installed", N_("Installed %s")});
 	 states.insert(states.begin(), {"half-configured", N_("Configuring %s")});
 	 PackagesTotal += 2;
       }
-      else if(PackageOpsDone[pkg] < states.size())
+      else if(PackageOpsDone[pkgname] < states.size())
       {
-	 char const * next_action = states[PackageOpsDone[pkg]].state;
+	 char const * next_action = states[PackageOpsDone[pkgname]].state;
 	 if (next_action)
 	 {
 	    /*
@@ -737,24 +747,52 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
 	    }
 	    */
 	    if (Debug == true)
-	       std::clog << "(parsed from dpkg) pkg: " << short_pkgname
+	       std::clog << "(parsed from dpkg) pkg: " << pkgname
 		  << " action: " << action << " (expected: '" << next_action << "' "
-		  << PackageOpsDone[pkg] << " of " << states.size() << ")" << endl;
+		  << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
 
 	    // check if the package moved to the next dpkg state
 	    if(action == next_action)
 	    {
 	       // only read the translation if there is actually a next action
-	       char const * const translation = _(states[PackageOpsDone[pkg]].str);
+	       char const * const translation = _(states[PackageOpsDone[pkgname]].str);
 
 	       // we moved from one dpkg state to a new one, report that
-	       ++PackageOpsDone[pkg];
+	       ++PackageOpsDone[pkgname];
 	       ++PackagesDone;
 
 	       std::string msg;
 	       strprintf(msg, translation, i18n_pkgname.c_str());
 	       d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
 	    }
+	    else if (action == "unpacked" && strcmp(next_action, "config-files") == 0)
+	    {
+	       // in a crossgrade what looked like a remove first is really an unpack over it
+	       ++PackageOpsDone[pkgname];
+	       ++PackagesDone;
+
+	       auto const Pkg = Cache.FindPkg(pkgname);
+	       if (likely(Pkg.end() == false))
+	       {
+		  auto const Grp = Pkg.Group();
+		  if (likely(Grp.end() == false))
+		  {
+		     for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
+			if(Cache[P].Install())
+			{
+			   auto && Ops = PackageOps[P.FullName()];
+			   auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; });
+			   if (unpackOp != Ops.cend())
+			   {
+			      auto const skipped = std::distance(Ops.cbegin(), unpackOp);
+			      PackagesDone += skipped;
+			      PackageOpsDone[P.FullName()] += skipped;
+			      break;
+			   }
+			}
+		  }
+	       }
+	    }
 	 }
       }
    }
@@ -1221,6 +1259,34 @@ void pkgDPkgPM::StopPtyMagic()						/*{{{*/
       d->master = -1;
    }
 }
+									/*}}}*/
+static void cleanUpTmpDir(char * const tmpdir)				/*{{{*/
+{
+   if (tmpdir == nullptr)
+      return;
+   DIR * const D = opendir(tmpdir);
+   if (D == nullptr)
+      _error->Errno("opendir", _("Unable to read %s"), tmpdir);
+   else
+   {
+      auto const dfd = dirfd(D);
+      for (struct dirent *Ent = readdir(D); Ent != nullptr; Ent = readdir(D))
+      {
+	 if (Ent->d_name[0] == '.')
+	    continue;
+#ifdef _DIRENT_HAVE_D_TYPE
+	 if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN))
+	    continue;
+#endif
+	 if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0))
+	    break;
+      }
+      closedir(D);
+      rmdir(tmpdir);
+   }
+   free(tmpdir);
+}
+									/*}}}*/
 
 // DPkgPM::Go - Run the sequence					/*{{{*/
 // ---------------------------------------------------------------------
@@ -1231,8 +1297,59 @@ void pkgDPkgPM::StopPtyMagic()						/*{{{*/
  * through to human readable (and i10n-able)
  * names and calculates a percentage for each step.
  */
+static bool ItemIsEssential(pkgDPkgPM::Item const &I)
+{
+   static auto const cachegen = _config->Find("pkgCacheGen::Essential");
+   if (cachegen == "none" || cachegen == "native")
+      return true;
+   if (unlikely(I.Pkg.end()))
+      return true;
+   return (I.Pkg->Flags & pkgCache::Flag::Essential) != 0;
+}
+bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache)
+{
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> alreadyRemoved;
+      for (auto && I : List)
+	 if (I.Op == Item::Remove || I.Op == Item::Purge)
+	    alreadyRemoved.insert(I.Pkg->ID);
+      std::remove_reference<decltype(List)>::type AppendList;
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+	 if (Cache[Pkg].Delete() && alreadyRemoved.insert(Pkg->ID).second == true)
+	    AppendList.emplace_back(Cache[Pkg].Purge() ? Item::Purge : Item::Remove, Pkg);
+      std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
+   }
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> alreadyConfigured;
+      for (auto && I : List)
+	 if (I.Op == Item::Configure)
+	    alreadyConfigured.insert(I.Pkg->ID);
+      std::remove_reference<decltype(List)>::type AppendList;
+      for (auto && I : List)
+	 if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true)
+	    AppendList.emplace_back(Item::Configure, I.Pkg);
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+	 if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && alreadyConfigured.insert(Pkg->ID).second == true)
+	    AppendList.emplace_back(Item::Configure, Pkg);
+      std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
+   }
+   return true;
+}
 bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 {
+   // explicitely remove&configure everything for hookscripts and progress building
+   // we need them only temporarily through, so keep the length and erase afterwards
+   decltype(List)::const_iterator::difference_type explicitIdx =
+      std::distance(List.cbegin(), List.cend());
+   ExpandPendingCalls(List, Cache);
+
+   auto const StripAlreadyDoneFromPending = [&](APT::VersionVector & Pending) {
+      Pending.erase(std::remove_if(Pending.begin(), Pending.end(), [&](pkgCache::VerIterator const &Ver) {
+	    auto const PN = Ver.ParentPkg().FullName();
+	    return PackageOps[PN].size() <= PackageOpsDone[PN];
+	 }), Pending.end());
+   };
+
    pkgPackageManager::SigINTStop = false;
    d->progress = progress;
 
@@ -1254,7 +1371,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       OSArgMax = 32*1024;
    OSArgMax -= EnvironmentSize() - 2*1024;
    unsigned int const MaxArgBytes = _config->FindI("Dpkg::MaxArgBytes", OSArgMax);
-   bool const NoTriggers = _config->FindB("DPkg::NoTriggers", false);
+   bool const NoTriggers = _config->FindB("DPkg::NoTriggers", true);
 
    if (RunScripts("DPkg::Pre-Invoke") == false)
       return false;
@@ -1267,28 +1384,173 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
    if (noopDPkgInvocation == false)
       Cache.writeStateFile(NULL);
 
-   decltype(List)::const_iterator::difference_type const notconfidx =
-      _config->FindB("Dpkg::ExplicitLastConfigure", false) ? std::numeric_limits<decltype(notconfidx)>::max() :
-      std::distance(List.cbegin(), std::find_if_not(List.crbegin(), List.crend(), [](Item const &i) { return i.Op == Item::Configure; }).base());
-
-   // support subpressing of triggers processing for special
-   // cases like d-i that runs the triggers handling manually
-   bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
-   bool const ConfigurePending = _config->FindB("DPkg::ConfigurePending", true);
-   if (ConfigurePending)
-      List.push_back(Item(Item::ConfigurePending, PkgIterator()));
+   bool dpkg_recursive_install = _config->FindB("dpkg::install::recursive", false);
+   if (_config->FindB("dpkg::install::recursive::force", false) == false)
+   {
+      // dpkg uses a sorted treewalk since that version which enables the workaround to work
+      auto const dpkgpkg = Cache.FindPkg("dpkg");
+      if (likely(dpkgpkg.end() == false && dpkgpkg->CurrentVer != 0))
+	 dpkg_recursive_install = Cache.VS().CmpVersion("1.18.5", dpkgpkg.CurrentVer().VerStr()) <= 0;
+   }
+   // no point in doing this dance for a handful of packages only
+   unsigned int const dpkg_recursive_install_min = _config->FindB("dpkg::install::recursive::minimum", 5);
+   // FIXME: workaround for dpkg bug, see our ./test-bug-740843-versioned-up-down-breaks test
+   bool const dpkg_recursive_install_numbered = _config->FindB("dpkg::install::recursive::numbered", true);
 
    // for the progress
    BuildPackagesProgressMap();
 
-   if (notconfidx != std::numeric_limits<decltype(notconfidx)>::max())
+   APT::StateChanges approvedStates;
+   if (_config->FindB("dpkg::selection::remove::approved", true))
    {
-      if (ConfigurePending)
-	 List.erase(std::next(List.begin(), notconfidx), std::prev(List.end()));
-      else
-	 List.erase(std::next(List.begin(), notconfidx), List.end());
+      for (auto && I : List)
+	 if (I.Op == Item::Purge)
+	    approvedStates.Purge(FindToBeRemovedVersion(I.Pkg));
+	 else if (I.Op == Item::Remove)
+	    approvedStates.Remove(FindToBeRemovedVersion(I.Pkg));
+   }
+
+   // Skip removes if we install another architecture of this package soon (crossgrade)
+   // We can't just skip them all the time as it could be an ordering requirement [of another package]
+   if ((approvedStates.Remove().empty() == false || approvedStates.Purge().empty() == false) &&
+	 _config->FindB("dpkg::remove::crossgrade::implicit", true) == true)
+   {
+      std::unordered_set<decltype(pkgCache::Package::ID)> crossgraded;
+      std::vector<std::pair<Item*, std::string>> toCrossgrade;
+      auto const PlanedEnd = std::next(List.begin(), explicitIdx);
+      for (auto I = List.begin(); I != PlanedEnd; ++I)
+      {
+	 if (I->Op != Item::Remove && I->Op != Item::Purge)
+	    continue;
+
+	 auto const Grp = I->Pkg.Group();
+	 size_t installedInstances = 0;
+	 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
+	    if (Pkg->CurrentVer != 0 || Cache[Pkg].Install())
+	       ++installedInstances;
+	 if (installedInstances == 2)
+	 {
+	    auto const FirstInstall = std::find_if_not(I, List.end(),
+		  [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; });
+	    auto const LastInstall = std::find_if_not(FirstInstall, List.end(),
+		  [](Item const &i) { return i.Op == Item::Install; });
+	    auto const crosser = std::find_if(FirstInstall, LastInstall,
+		  [&I](Item const &i) { return i.Pkg->Group == I->Pkg->Group; });
+	    if (crosser != LastInstall)
+	    {
+	       crossgraded.insert(I->Pkg->ID);
+	       toCrossgrade.emplace_back(&(*I), crosser->Pkg.FullName());
+	    }
+	 }
+      }
+      for (auto I = PlanedEnd; I != List.end(); ++I)
+      {
+	 if (I->Op != Item::Remove && I->Op != Item::Purge)
+	    continue;
+
+	 auto const Grp = I->Pkg.Group();
+	 for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
+	 {
+	    if (Pkg == I->Pkg || Cache[Pkg].Install() == false)
+	       continue;
+	    toCrossgrade.emplace_back(&(*I), Pkg.FullName());
+	    break;
+	 }
+      }
+      for (auto C : toCrossgrade)
+      {
+	 // we never do purges on packages which are crossgraded, even if "requested"
+	 if (C.first->Op == Item::Purge)
+	 {
+	    C.first->Op = Item::Remove; // crossgrades should never be purged
+	    auto && Purges = approvedStates.Purge();
+	    auto const Ver = std::find_if(
+#if __GNUC__ >= 5 || (__GNUC_MINOR__ >= 9 && __GNUC__ >= 4)
+	       Purges.cbegin(), Purges.cend(),
+#else
+	       Purges.begin(), Purges.end(),
+#endif
+	       [&C](pkgCache::VerIterator const &V) { return V.ParentPkg() == C.first->Pkg; });
+	    approvedStates.Remove(*Ver);
+	    Purges.erase(Ver);
+	    auto && RemOp = PackageOps[C.first->Pkg.FullName()];
+	    if (RemOp.size() == 5)
+	    {
+	       RemOp.erase(std::next(RemOp.begin(), 3), RemOp.end());
+	       PackagesTotal -= 2;
+	    }
+	    else
+	    _error->Warning("Unexpected amount of planned ops for package %s: %lu", C.first->Pkg.FullName().c_str(), RemOp.size());
+	 }
+      }
+      if (crossgraded.empty() == false)
+      {
+	 auto const oldsize = List.size();
+	 List.erase(std::remove_if(List.begin(), PlanedEnd,
+	       [&crossgraded](Item const &i){
+		  return (i.Op == Item::Remove || i.Op == Item::Purge) &&
+		     crossgraded.find(i.Pkg->ID) != crossgraded.end();
+	       }), PlanedEnd);
+	 explicitIdx -= (oldsize - List.size());
+      }
    }
 
+   APT::StateChanges currentStates;
+   if (_config->FindB("dpkg::selection::current::saveandrestore", true))
+   {
+      for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+	 if (Pkg->CurrentVer == 0)
+	    continue;
+	 else if (Pkg->SelectedState == pkgCache::State::Purge)
+	    currentStates.Purge(FindToBeRemovedVersion(Pkg));
+	 else if (Pkg->SelectedState == pkgCache::State::DeInstall)
+	    currentStates.Remove(FindToBeRemovedVersion(Pkg));
+      if (currentStates.empty() == false)
+      {
+	 APT::StateChanges cleanStates;
+	 for (auto && P: currentStates.Remove())
+	    cleanStates.Install(P);
+	 for (auto && P: currentStates.Purge())
+	    cleanStates.Install(P);
+	 if (cleanStates.Save(false) == false)
+	    return _error->Error("Couldn't clean the currently selected dpkg states");
+      }
+   }
+
+   if (_config->FindB("dpkg::selection::remove::approved", true))
+   {
+      if (approvedStates.Save(false) == false)
+      {
+	 _error->Error("Couldn't record the approved state changes as dpkg selection states");
+	 if (currentStates.Save(false) == false)
+	    _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
+	 return false;
+      }
+
+      List.erase(std::next(List.begin(), explicitIdx), List.end());
+
+      std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false);
+      for (auto && I: approvedStates.Remove())
+	 toBeRemoved[I.ParentPkg()->ID] = true;
+      for (auto && I: approvedStates.Purge())
+	 toBeRemoved[I.ParentPkg()->ID] = true;
+
+      for (auto && I: List)
+	 if (I.Op == Item::Remove || I.Op == Item::Purge)
+	    toBeRemoved[I.Pkg->ID] = false;
+
+      if (std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end())
+	 List.emplace_back(Item::RemovePending, pkgCache::PkgIterator());
+      if (approvedStates.Purge().empty() == false)
+	 List.emplace_back(Item::PurgePending, pkgCache::PkgIterator());
+
+      // support subpressing of triggers processing for special
+      // cases like d-i that runs the triggers handling manually
+      if (_config->FindB("DPkg::ConfigurePending", true))
+	 List.emplace_back(Item::ConfigurePending, pkgCache::PkgIterator());
+   }
+   bool const TriggersPending = _config->FindB("DPkg::TriggersPending", false);
+
    d->stdin_is_dev_null = false;
 
    // create log
@@ -1303,7 +1565,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
    d->progress->Start(d->master);
 
    // this loop is runs once per dpkg operation
-   vector<Item>::const_iterator I = List.begin();
+   vector<Item>::const_iterator I = List.cbegin();
    while (I != List.end())
    {
       // Do all actions with the same Op in one run
@@ -1320,9 +1582,10 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 	       continue;
 	    break;
 	 }
+      else if (J->Op == Item::Remove || J->Op == Item::Purge)
+	 J = std::find_if(J, List.cend(), [](Item const &I) { return I.Op != Item::Remove && I.Op != Item::Purge; });
       else
-	 for (; J != List.end() && J->Op == I->Op; ++J)
-	    /* nothing */;
+	 J = std::find_if(J, List.cend(), [&J](Item const &I) { return I.Op != J->Op; });
 
       auto const size = (J - I) + 10;
 
@@ -1346,18 +1609,20 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       ADDARG(status_fd_buf);
       unsigned long const Op = I->Op;
 
+      if (NoTriggers == true && I->Op != Item::TriggersPending &&
+	  I->Op != Item::ConfigurePending)
+      {
+	 ADDARGC("--no-triggers");
+      }
+
       switch (I->Op)
       {
 	 case Item::Remove:
-	 ADDARGC("--force-depends");
-	 ADDARGC("--force-remove-essential");
-	 ADDARGC("--remove");
-	 break;
-
 	 case Item::Purge:
 	 ADDARGC("--force-depends");
-	 ADDARGC("--force-remove-essential");
-	 ADDARGC("--purge");
+	 if (std::any_of(I, J, ItemIsEssential))
+	    ADDARGC("--force-remove-essential");
+	 ADDARGC("--remove");
 	 break;
 
 	 case Item::Configure:
@@ -1374,34 +1639,96 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 	 ADDARGC("--pending");
 	 break;
 
+	 case Item::RemovePending:
+	 ADDARGC("--remove");
+	 ADDARGC("--pending");
+	 break;
+
+	 case Item::PurgePending:
+	 ADDARGC("--purge");
+	 ADDARGC("--pending");
+	 break;
+
 	 case Item::Install:
 	 ADDARGC("--unpack");
 	 ADDARGC("--auto-deconfigure");
 	 break;
       }
 
-      if (NoTriggers == true && I->Op != Item::TriggersPending &&
-	  I->Op != Item::ConfigurePending)
-      {
-	 ADDARGC("--no-triggers");
-      }
-#undef ADDARGC
+      char * tmpdir_to_free = nullptr;
 
       // Write in the file or package names
       if (I->Op == Item::Install)
       {
-	 for (;I != J && Size < MaxArgBytes; ++I)
+	 auto const installsToDo = J - I;
+	 if (dpkg_recursive_install == true && dpkg_recursive_install_min < installsToDo)
 	 {
-	    if (I->File[0] != '/')
-	       return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
-	    Args.push_back(I->File.c_str());
-	    Size += I->File.length();
+	    std::string tmpdir;
+	    strprintf(tmpdir, "%s/apt-dpkg-install-XXXXXX", GetTempDir().c_str());
+	    tmpdir_to_free = strndup(tmpdir.data(), tmpdir.length());
+	    if (mkdtemp(tmpdir_to_free) == nullptr)
+	       return _error->Errno("DPkg::Go", "mkdtemp of %s failed in preparation of calling dpkg unpack", tmpdir_to_free);
+
+	    char p = 1;
+	    for (auto c = installsToDo - 1; (c = c/10) != 0; ++p);
+	    for (unsigned long n = 0; I != J; ++n, ++I)
+	    {
+	       if (I->File[0] != '/')
+		  return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
+	       auto const file = flNotDir(I->File);
+	       std::string linkpath;
+	       if (dpkg_recursive_install_numbered)
+		  strprintf(linkpath, "%s/%.*lu-%s", tmpdir_to_free, p, n, file.c_str());
+	       else
+		  strprintf(linkpath, "%s/%s", tmpdir_to_free, file.c_str());
+	       if (symlink(I->File.c_str(), linkpath.c_str()) != 0)
+		  return _error->Errno("DPkg::Go", "Symlinking %s to %s failed!", I->File.c_str(), linkpath.c_str());
+	    }
+	    ADDARGC("--recursive");
+	    ADDARG(tmpdir_to_free);
+	 }
+	 else
+	 {
+	    for (;I != J && Size < MaxArgBytes; ++I)
+	    {
+	       if (I->File[0] != '/')
+		  return _error->Error("Internal Error, Pathname to install is not absolute '%s'",I->File.c_str());
+	       Args.push_back(I->File.c_str());
+	       Size += I->File.length();
+	    }
+	 }
+      }
+      else if (I->Op == Item::RemovePending)
+      {
+	 ++I;
+	 StripAlreadyDoneFromPending(approvedStates.Remove());
+	 if (approvedStates.Remove().empty())
+	    continue;
+      }
+      else if (I->Op == Item::PurgePending)
+      {
+	 ++I;
+	 // explicit removes of packages without conffiles passthrough the purge states instantly, too.
+	 // Setting these non-installed packages up for purging generates 'unknown pkg' warnings from dpkg
+	 StripAlreadyDoneFromPending(approvedStates.Purge());
+	 if (approvedStates.Purge().empty())
+	    continue;
+	 std::remove_reference<decltype(approvedStates.Remove())>::type approvedRemoves;
+	 std::swap(approvedRemoves, approvedStates.Remove());
+	 // we apply it again here as an explicit remove in the ordering will have cleared the purge state
+	 if (approvedStates.Save(false) == false)
+	 {
+	    _error->Error("Couldn't record the approved purges as dpkg selection states");
+	    if (currentStates.Save(false) == false)
+	       _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
+	    return false;
 	 }
+	 std::swap(approvedRemoves, approvedStates.Remove());
       }
       else
       {
 	 string const nativeArch = _config->Find("APT::Architecture");
-	 unsigned long const oldSize = I->Op == Item::Configure ? Size : 0;
+	 unsigned long const oldSize = I->Pkg.end() == false ? Size : 0;
 	 for (;I != J && Size < MaxArgBytes; ++I)
 	 {
 	    if((*I).Pkg.end() == true)
@@ -1420,12 +1747,15 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 	    {
 	       pkgCache::VerIterator PkgVer;
 	       std::string name = I->Pkg.Name();
-	       if (Op == Item::Remove || Op == Item::Purge) 
-               {
+	       if (Op == Item::Remove)
+		  PkgVer = I->Pkg.CurrentVer();
+	       else if (Op == Item::Purge)
+	       {
+		  // we purge later with --purge --pending, so if it isn't installed (aka rc-only), skip it here
 		  PkgVer = I->Pkg.CurrentVer();
-                  if(PkgVer.end() == true)
-                     PkgVer = FindNowVersion(I->Pkg);
-               }
+		  if (PkgVer.end() == true)
+		     continue;
+	       }
 	       else
 		  PkgVer = Cache[I->Pkg].InstVerIter(Cache);
 	       if (strcmp(I->Pkg.Arch(), "none") == 0)
@@ -1443,6 +1773,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 	 if (oldSize == Size)
 	    continue;
       }
+#undef ADDARGC
 #undef ADDARG
 
       J = I;
@@ -1459,6 +1790,7 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 	 Packages.clear();
 	 close(fd[0]);
 	 close(fd[1]);
+	 cleanUpTmpDir(tmpdir_to_free);
 	 continue;
       }
       Args.push_back(NULL);
@@ -1594,6 +1926,8 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
       signal(SIGINT,old_SIGINT);
       signal(SIGHUP,old_SIGHUP);
 
+      cleanUpTmpDir(tmpdir_to_free);
+
       if (waitpid_failure == true)
       {
 	 strprintf(d->dpkg_error, "Sub-process %s couldn't be waited for.",Args[0]);
@@ -1625,6 +1959,23 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
    StopPtyMagic();
    CloseLog();
 
+   if (d->dpkg_error.empty() == false)
+   {
+      // no point in reseting packages we already completed removal for
+      StripAlreadyDoneFromPending(approvedStates.Remove());
+      StripAlreadyDoneFromPending(approvedStates.Purge());
+      APT::StateChanges undo;
+      auto && undoRem = approvedStates.Remove();
+      std::move(undoRem.begin(), undoRem.end(), std::back_inserter(undo.Install()));
+      auto && undoPur = approvedStates.Purge();
+      std::move(undoPur.begin(), undoPur.end(), std::back_inserter(undo.Install()));
+      approvedStates.clear();
+      if (undo.Save(false) == false)
+	 _error->Error("Couldn't revert dpkg selection for approved remove/purge after an error was encountered!");
+   }
+   if (currentStates.Save(false) == false)
+      _error->Error("Couldn't restore dpkg selection states which were present before this interaction!");
+
    if (pkgPackageManager::SigINTStop)
        _error->Warning(_("Operation was interrupted before it could finish"));
 
@@ -1764,7 +2115,17 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    // find the package version and source package name
    pkgCache::PkgIterator Pkg = Cache.FindPkg(pkgname);
    if (Pkg.end() == true)
-      return;
+   {
+      if (pos == std::string::npos || _config->FindB("dpkg::install::recursive::numbered", true) == false)
+	 return;
+      auto const dash = pkgname.find_first_not_of("0123456789");
+      if (dash == std::string::npos || pkgname[dash] != '-')
+	 return;
+      pkgname.erase(0, dash + 1);
+      Pkg = Cache.FindPkg(pkgname);
+      if (Pkg.end() == true)
+	 return;
+   }
    pkgCache::VerIterator Ver = Cache.GetCandidateVersion(Pkg);
    if (Ver.end() == true)
       return;
@@ -1862,20 +2223,24 @@ void pkgDPkgPM::WriteApportReport(const char *pkgpath, const char *errormsg)
    }
 
    // log the ordering, see dpkgpm.h and the "Ops" enum there
-   const char *ops_str[] = {
-      "Install",
-      "Configure",
-      "Remove",
-      "Purge",
-      "ConfigurePending",
-      "TriggersPending",
-   };
    fprintf(report, "AptOrdering:\n");
-   for (vector<Item>::iterator I = List.begin(); I != List.end(); ++I)
-      if ((*I).Pkg != NULL)
-         fprintf(report, " %s: %s\n", (*I).Pkg.Name(), ops_str[(*I).Op]);
-      else
-         fprintf(report, " %s: %s\n", "NULL", ops_str[(*I).Op]);
+   for (auto && I : List)
+   {
+      char const * opstr = nullptr;
+      switch (I.Op)
+      {
+	 case Item::Install: opstr = "Install"; break;
+	 case Item::Configure: opstr = "Configure"; break;
+	 case Item::Remove: opstr = "Remove"; break;
+	 case Item::Purge: opstr = "Purge"; break;
+	 case Item::ConfigurePending: opstr = "ConfigurePending"; break;
+	 case Item::TriggersPending: opstr = "TriggersPending"; break;
+	 case Item::RemovePending: opstr = "RemovePending"; break;
+	 case Item::PurgePending: opstr = "PurgePending"; break;
+      }
+      auto const pkgname = I.Pkg.end() ? "NULL" : I.Pkg.FullName();
+      fprintf(report, " %s: %s\n", pkgname.c_str(), opstr);
+   }
 
    // attach dmesg log (to learn about segfaults)
    if (FileExists("/bin/dmesg"))

+ 8 - 4
apt-pkg/deb/dpkgpm.h

@@ -76,17 +76,19 @@ class pkgDPkgPM : public pkgPackageManager
    // progress reporting
    unsigned int PackagesDone;
    unsigned int PackagesTotal;
-  
+
+   public:
    struct Item
    {
-      enum Ops {Install, Configure, Remove, Purge, ConfigurePending, TriggersPending} Op;
+      enum Ops {Install, Configure, Remove, Purge, ConfigurePending, TriggersPending,
+         RemovePending, PurgePending } Op;
       std::string File;
       PkgIterator Pkg;
       Item(Ops Op,PkgIterator Pkg,std::string File = "") : Op(Op),
             File(File), Pkg(Pkg) {};
       Item() {};
-      
    };
+   protected:
    std::vector<Item> List;
 
    // Helpers
@@ -125,7 +127,7 @@ class pkgDPkgPM : public pkgPackageManager
    virtual bool Remove(PkgIterator Pkg,bool Purge = false) APT_OVERRIDE;
 
    virtual bool Go(APT::Progress::PackageManager *progress) APT_OVERRIDE;
-   virtual bool Go(int StatusFd=-1) APT_OVERRIDE;
+   APT_DEPRECATED_MSG("Use overload with explicit progress manager") virtual bool Go(int StatusFd=-1) APT_OVERRIDE;
 
    virtual void Reset() APT_OVERRIDE;
    
@@ -133,6 +135,8 @@ class pkgDPkgPM : public pkgPackageManager
 
    pkgDPkgPM(pkgDepCache *Cache);
    virtual ~pkgDPkgPM();
+
+   APT_HIDDEN static bool ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache);
 };
 
 void SigINT(int sig);

+ 9 - 4
apt-pkg/packagemanager.cc

@@ -315,8 +315,8 @@ bool pkgPackageManager::ConfigureAll()
    if (OList.OrderConfigure() == false)
       return false;
 
-   std::string const conf = _config->Find("PackageManager::Configure","all");
-   bool const ConfigurePkgs = (conf == "all");
+   std::string const conf = _config->Find("PackageManager::Configure", "smart");
+   bool const ConfigurePkgs = (ImmConfigureAll || conf == "all");
 
    // Perform the configuring
    for (pkgOrderList::iterator I = OList.begin(); I != OList.end(); ++I)
@@ -600,7 +600,7 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
 
    if (PkgLoop) return true;
 
-   static std::string const conf = _config->Find("PackageManager::Configure","all");
+   static std::string const conf = _config->Find("PackageManager::Configure", "smart");
    static bool const ConfigurePkgs = (conf == "all" || conf == "smart");
 
    if (List->IsFlag(Pkg,pkgOrderList::Configured))
@@ -1148,7 +1148,12 @@ pkgPackageManager::DoInstallPostFork(int statusFd)
 pkgPackageManager::OrderResult 
 pkgPackageManager::DoInstallPostFork(APT::Progress::PackageManager *progress)
 {
-   bool goResult = Go(progress);
+   bool goResult;
+   auto simulation = dynamic_cast<pkgSimulate*>(this);
+   if (simulation == nullptr)
+      goResult = Go(progress);
+   else
+      goResult = simulation->Go2(progress);
    if(goResult == false) 
       return Failed;
    

+ 1 - 1
apt-pkg/packagemanager.h

@@ -99,7 +99,7 @@ class pkgPackageManager : protected pkgCache::Namespace
    virtual bool Configure(PkgIterator /*Pkg*/) {return false;};
    virtual bool Remove(PkgIterator /*Pkg*/,bool /*Purge*/=false) {return false;};
    virtual bool Go(APT::Progress::PackageManager * /*progress*/) {return true;};
-   virtual bool Go(int /*statusFd*/=-1) {return true;};
+   APT_DEPRECATED_MSG("Use overload with explicit progress manager") virtual bool Go(int /*statusFd*/=-1) {return true;};
 
    virtual void Reset() {};
 

+ 2 - 1
apt-pkg/statechanges.cc

@@ -25,7 +25,8 @@ public:
 #define APT_GETTERSETTER(Name, Container) \
 void StateChanges::Name(pkgCache::VerIterator const &Ver) \
 { \
-   Container.push_back(Ver); \
+   if (Ver.end() == false) \
+      Container.push_back(Ver); \
 }\
 APT::VersionVector& StateChanges::Name() \
 { \

+ 7 - 77
doc/apt.conf.5.xml

@@ -904,84 +904,14 @@ APT::Compressor::rev {
      <listitem><para>These options are passed to &dpkg-buildpackage; when compiling packages;
      the default is to disable signing and produce all binaries.</para></listitem>
      </varlistentry>
-   </variablelist>
 
-   <refsect2><title>dpkg trigger usage (and related options)</title>
-     <para>APT can call &dpkg; in such a way as to let it make aggressive use of triggers over
-     multiple calls of &dpkg;. Without further options &dpkg; will use triggers once each time it runs.
-     Activating these options can therefore decrease the time needed to perform the
-     install or upgrade. Note that it is intended to activate these options per default in the
-     future, but as it drastically changes the way APT calls &dpkg; it needs a lot more testing.
-     <emphasis>These options are therefore currently experimental and should not be used in
-     production environments.</emphasis> It also breaks progress reporting such that all front-ends will
-     currently stay around half (or more) of the time in the 100% state while it actually configures
-     all packages.</para>
-     <para>Note that it is not guaranteed that APT will support these options or that these options will
-     not cause (big) trouble in the future. If you have understand the current risks and problems with
-     these options, but are brave enough to help testing them, create a new configuration file and test a
-     combination of options. Please report any bugs, problems and improvements you encounter and make sure
-     to note which options you have used in your reports. Asking &dpkg; for help could also be useful for
-     debugging proposes, see e.g. <command>dpkg --audit</command>. A defensive option combination would be
-<literallayout>DPkg::NoTriggers "true";
-PackageManager::Configure "smart";
-DPkg::ConfigurePending "true";
-DPkg::TriggersPending "true";</literallayout></para>
-
-     <variablelist>
-       <varlistentry><term><option>DPkg::NoTriggers</option></term>
-       <listitem><para>Add the no triggers flag to all &dpkg; calls (except the ConfigurePending call).
-       See &dpkg; if you are interested in what this actually means. In short: &dpkg; will not run the
-       triggers when this flag is present unless it is explicitly called to do so in an extra call.
-       Note that this option exists (undocumented) also in older APT versions with a slightly different
-       meaning: Previously these option only append --no-triggers to the configure calls to &dpkg; -
-       now APT will also add this flag to the unpack and remove calls.</para></listitem>
-       </varlistentry>
-       <varlistentry><term><option>PackageManager::Configure</option></term>
-       <listitem><para>Valid values are "<literal>all</literal>",
-       "<literal>smart</literal>" and "<literal>no</literal>".
-       The default value is "<literal>all</literal>", which causes APT to
-       configure all packages. The "<literal>smart</literal>" way is to
-       configure only packages which need to be configured before another
-       package can be unpacked (Pre-Depends), and let the rest be configured
-       by &dpkg; with a call generated by the ConfigurePending option (see
-       below). On the other hand, "<literal>no</literal>" will not configure
-       anything, and totally relies on &dpkg; for configuration (which at the
-       moment will fail if a Pre-Depends is encountered). Setting this option
-       to any value other than <literal>all</literal> will implicitly also
-       activate the next option by default, as otherwise the system could end
-       in an unconfigured and potentially unbootable state.</para></listitem>
-       </varlistentry>
-       <varlistentry><term><option>DPkg::ConfigurePending</option></term>
-       <listitem><para>If this option is set APT will call <command>dpkg --configure --pending</command>
-       to let &dpkg; handle all required configurations and triggers. This option is activated automatically
-       per default if the previous option is not set to <literal>all</literal>, but deactivating it could be useful
-       if you want to run APT multiple times in a row - e.g. in an installer. In these sceneries you could
-       deactivate this option in all but the last run.</para></listitem>
-       </varlistentry>
-       <varlistentry><term><option>DPkg::TriggersPending</option></term>
-       <listitem><para>Useful for the <literal>smart</literal> configuration as a package which has pending
-       triggers is not considered as <literal>installed</literal>, and &dpkg; treats them as <literal>unpacked</literal>
-       currently which is a showstopper for Pre-Dependencies (see debbugs #526774). Note that this will
-       process all triggers, not only the triggers needed to configure this package.</para></listitem>
-       </varlistentry>
-       <varlistentry><term><option>OrderList::Score::Immediate</option></term>
-       <listitem><para>Essential packages (and their dependencies) should be configured immediately
-       after unpacking. It is a good idea to do this quite early in the upgrade process as these
-       configure calls also currently require <literal>DPkg::TriggersPending</literal> which
-       will run quite a few triggers (which may not be needed). Essentials get per default a high score
-       but the immediate flag is relatively low (a package which has a Pre-Depends is rated higher).
-       These option and the others in the same group can be used to change the scoring. The following
-       example shows the settings with their default values.
-       <literallayout>OrderList::Score {
-	Delete 500;
-	Essential 200;
-	Immediate 10;
-	PreDepends 50;
-};</literallayout>
-       </para></listitem>
-       </varlistentry>
-     </variablelist>
-   </refsect2>
+     <varlistentry><term><option>DPkg::ConfigurePending</option></term>
+     <listitem><para>If this option is set APT will call <command>dpkg --configure --pending</command>
+     to let &dpkg; handle all required configurations and triggers. This option is activated by default,
+     but deactivating it could be useful if you want to run APT multiple times in a row - e.g. in an installer.
+     In this scenario you could deactivate this option in all but the last run.</para></listitem>
+     </varlistentry>
+   </variablelist>
  </refsect1>
 
  <refsect1>

+ 3 - 1
doc/external-installation-planner-protocol.txt

@@ -248,7 +248,9 @@ before it was unpacked, dependency relations must be satisfied, …), but
 they don't need to be complete: A planner can and should expect that any
 package which wasn't explicitly configured will be configured at the end
 automatically. That also means through that a planner is not allowed to
-produce a solution in which a package remains unconfigured.
+produce a solution in which a package remains unconfigured. Also,
+packages which are requested to be removed will be automatically removed
+at the end if not marked for removal explicitly earlier.
 
 In terms of expressivity, all stanzas can carry one single field each, as
 APT-IDs are enough to pinpoint packages to be installed/removed.

+ 4 - 4
test/integration/test-allow-scores-for-all-dependency-types

@@ -55,8 +55,8 @@ The following packages will be upgraded:
 Remv libdb5.1-dev [5.1.29-7] [libdb-dev:amd64 ]
 Inst libdb-dev [5.1.7] (5.3.0 unversioned [amd64]) []
 Inst libdb5.3-dev (5.3.28-3 unversioned [amd64])
-Conf libdb5.3-dev (5.3.28-3 unversioned [amd64])
-Conf libdb-dev (5.3.0 unversioned [amd64])' aptget dist-upgrade -st unversioned
+Conf libdb-dev (5.3.0 unversioned [amd64])
+Conf libdb5.3-dev (5.3.28-3 unversioned [amd64])' aptget dist-upgrade -st unversioned
 testsuccessequal 'Reading package lists...
 Building dependency tree...
 Calculating upgrade...
@@ -70,8 +70,8 @@ The following packages will be upgraded:
 Remv libdb5.1-dev [5.1.29-7] [libdb-dev:amd64 ]
 Inst libdb-dev [5.1.7] (5.3.0 versioned [amd64]) []
 Inst libdb5.3-dev (5.3.28-3 versioned [amd64])
-Conf libdb5.3-dev (5.3.28-3 versioned [amd64])
-Conf libdb-dev (5.3.0 versioned [amd64])' aptget dist-upgrade -st versioned
+Conf libdb-dev (5.3.0 versioned [amd64])
+Conf libdb5.3-dev (5.3.28-3 versioned [amd64])' aptget dist-upgrade -st versioned
 
 cp -f rootdir/var/lib/dpkg/status-backup rootdir/var/lib/dpkg/status
 insertinstalledpackage 'foo' 'amd64' '1'

+ 9 - 1
test/integration/test-apt-get-autoremove

@@ -18,6 +18,8 @@ testmarkedauto 'po-debconf'
 testsuccess aptget remove debhelper -y
 testdpkgnotinstalled 'debhelper'
 testdpkginstalled 'po-debconf' 'unrelated'
+echo 'unrelated purge' | dpkg --set-selections
+testdpkgstatus 'pi' '1' 'unrelated'
 
 AUTOREMOVE='apt autoremove'
 if [ -n "$SUDO_USER" ]; then
@@ -49,14 +51,17 @@ testdpkginstalled 'po-debconf'
 echo 'APT::NeverAutoRemove { "^po-debconf$"; };' > rootdir/etc/apt/apt.conf.d/00autoremove
 testsuccess aptget autoremove -y
 testdpkginstalled 'po-debconf'
+testdpkgstatus 'pi' '1' 'unrelated'
 
 echo 'APT::NeverAutoRemove { "^po-.*$"; };' > rootdir/etc/apt/apt.conf.d/00autoremove
 testsuccess aptget autoremove -y
 testdpkginstalled "po-debconf"
+testdpkgstatus 'pi' '1' 'unrelated'
 
 rm rootdir/etc/apt/apt.conf.d/00autoremove
 testsuccess aptget autoremove -y
 testdpkgnotinstalled 'po-debconf'
+testdpkgstatus 'pi' '1' 'unrelated'
 testmarkedauto
 
 sed rootdir/var/log/apt/history.log -e '/^Commandline: / d' \
@@ -71,7 +76,8 @@ Remove: debhelper:i386 (8.0.0)
 Remove: po-debconf:i386 (1.0.16)'
 
 testsuccess aptget install debhelper -y
-testdpkginstalled 'unrelated' 'debhelper' 'po-debconf'
+testdpkgstatus 'pi' '1' 'unrelated'
+testdpkginstalled 'debhelper' 'po-debconf'
 testsuccess aptmark auto debhelper
 
 testmarkedauto 'debhelper' 'po-debconf'
@@ -105,9 +111,11 @@ Reading state information...
 
 testsuccess aptget autoremove debhelper -y --allow-change-held-packages
 testdpkgnotinstalled 'po-debconf' 'debhelper'
+testdpkgstatus 'pi' '1' 'unrelated'
 testmarkedauto
 testsuccess aptget install debhelper --solver apt -y -o Debug::pkgDepCache::Marker=1
 testmarkedauto 'po-debconf'
+testdpkgstatus 'pi' '1' 'unrelated'
 
 insertinstalledpackage 'bar' 'all' '1' 'Depends: foo-provider'
 insertinstalledpackage 'foo-multi1-1' 'all' '1' 'Provides: foo-provider

+ 1 - 1
test/integration/test-apt-progress-fd-error

@@ -19,7 +19,7 @@ setupaptarchive
 exec 3> apt-progress.log
 testfailure aptget install foo1 foo2 -y -o APT::Status-Fd=3
 msgtest 'Ensure correct error message'
-testsuccess --nomsg grep "aptarchive/pool/foo2_0.8.15_[^.]\+.deb:36.3636:trying to overwrite '/usr/bin/file-conflict', which is also in package foo1 0.8.15" apt-progress.log
+testsuccess --nomsg grep "foo2_0.8.15_[^.]\+.deb:36.3636:trying to overwrite '/usr/bin/file-conflict', which is also in package foo1 0.8.15" apt-progress.log
 
 testsuccess test -s rootdir/var/crash/foo2.0.crash
 testsuccess grep '^Package: foo2 0.8.15$' rootdir/var/crash/foo2.0.crash

+ 1 - 1
test/integration/test-bug-673536-pre-depends-breaks-loop

@@ -11,7 +11,7 @@ buildsimplenativepackage 'advanced' 'native' '2' 'unstable' 'Pre-Depends: basic'
 buildsimplenativepackage 'basic' 'native' '2' 'unstable' 'Pre-Depends: common'
 
 buildsimplenativepackage 'common' 'native' '2~conflict' 'unstable-conflict' 'Conflicts: advanced (<= 1)'
-buildsimplenativepackage 'common' 'native' '2~break' 'unstable-break' 'Conflicts: advanced (<= 1)'
+buildsimplenativepackage 'common' 'native' '2~break' 'unstable-break' 'Breaks: advanced (<= 1)'
 
 setupaptarchive
 

+ 51 - 35
test/integration/test-bug-712116-dpkg-pre-install-pkgs-hook-multiarch

@@ -48,33 +48,42 @@ DPkg::Tools::options::\"./${hook}-v${1}.sh\"::Version \"$1\";" > rootdir/etc/apt
 observehook() {
 	rm -f ${hook}-v2.list ${hook}-v3.list
 	msgtest 'Observe hooks while' "$*"
-	testsuccess --nomsg aptget "$@" -y --allow-downgrades
+	testsuccess --nomsg aptget "$@" -y --allow-downgrades --planner $planner
+	# different planners have different orders – we don't care in this test here
+	if [ -e ${hook}-v2.list ]; then
+		sort < ${hook}-v2.list > ${hook}-v2.list.new
+		mv ${hook}-v2.list.new ${hook}-v2.list
+	fi
+	if [ -e ${hook}-v3.list ]; then
+		sort < ${hook}-v3.list > ${hook}-v3.list.new
+		mv ${hook}-v3.list.new ${hook}-v3.list
+	fi
 }
 
 testrun() {
 	observehook install stuff -t stable
 	testfileequal "${hook}-v2.list" 'libsame - < 1 **CONFIGURE**
-toolkit - < 1 **CONFIGURE**
-stuff - < 1 **CONFIGURE**'
+stuff - < 1 **CONFIGURE**
+toolkit - < 1 **CONFIGURE**'
 	testfileequal "${hook}-v3.list" 'libsame - - none < 1 amd64 same **CONFIGURE**
-toolkit - - none < 1 all foreign **CONFIGURE**
-stuff - - none < 1 amd64 none **CONFIGURE**'
+stuff - - none < 1 amd64 none **CONFIGURE**
+toolkit - - none < 1 all foreign **CONFIGURE**'
 
 	observehook install stuff -t unstable
 	testfileequal "${hook}-v2.list" 'libsame 1 < 2 **CONFIGURE**
-toolkit 1 < 2 **CONFIGURE**
-stuff 1 < 2 **CONFIGURE**'
+stuff 1 < 2 **CONFIGURE**
+toolkit 1 < 2 **CONFIGURE**'
 	testfileequal "${hook}-v3.list" 'libsame 1 amd64 same < 2 amd64 same **CONFIGURE**
-toolkit 1 all foreign < 2 amd64 foreign **CONFIGURE**
-stuff 1 amd64 none < 2 amd64 none **CONFIGURE**'
+stuff 1 amd64 none < 2 amd64 none **CONFIGURE**
+toolkit 1 all foreign < 2 amd64 foreign **CONFIGURE**'
 
 	observehook install stuff:i386 -t unstable
-	testfileequal "${hook}-v2.list" 'stuff 2 > - **REMOVE**
-libsame - < 2 **CONFIGURE**
-stuff - < 2 **CONFIGURE**'
-	testfileequal "${hook}-v3.list" 'stuff 2 amd64 none > - - none **REMOVE**
-libsame - - none < 2 i386 same **CONFIGURE**
-stuff - - none < 2 i386 none **CONFIGURE**'
+	testfileequal "${hook}-v2.list" 'libsame - < 2 **CONFIGURE**
+stuff - < 2 **CONFIGURE**
+stuff 2 > - **REMOVE**'
+	testfileequal "${hook}-v3.list" 'libsame - - none < 2 i386 same **CONFIGURE**
+stuff - - none < 2 i386 none **CONFIGURE**
+stuff 2 amd64 none > - - none **REMOVE**'
 
 	observehook remove libsame
 	testfileequal "${hook}-v2.list" 'libsame 2 > - **REMOVE**'
@@ -82,30 +91,30 @@ stuff - - none < 2 i386 none **CONFIGURE**'
 
 	observehook install stuff:i386/stable libsame:i386/stable toolkit/stable
 	testfileequal "${hook}-v2.list" 'libsame 2 > 1 **CONFIGURE**
-toolkit 2 > 1 **CONFIGURE**
-stuff 2 > 1 **CONFIGURE**'
+stuff 2 > 1 **CONFIGURE**
+toolkit 2 > 1 **CONFIGURE**'
 	testfileequal "${hook}-v3.list" 'libsame 2 i386 same > 1 i386 same **CONFIGURE**
-toolkit 2 amd64 foreign > 1 all foreign **CONFIGURE**
-stuff 2 i386 none > 1 i386 none **CONFIGURE**'
+stuff 2 i386 none > 1 i386 none **CONFIGURE**
+toolkit 2 amd64 foreign > 1 all foreign **CONFIGURE**'
 
 	observehook install 'libsame:*'
-	testfileequal "${hook}-v2.list" 'libsame 1 < 2 **CONFIGURE**
-libsame - < 2 **CONFIGURE**
-toolkit 1 < 2 **CONFIGURE**
-stuff 1 < 2 **CONFIGURE**'
-	testfileequal "${hook}-v3.list" 'libsame 1 i386 same < 2 i386 same **CONFIGURE**
-libsame - - none < 2 amd64 same **CONFIGURE**
-toolkit 1 all foreign < 2 amd64 foreign **CONFIGURE**
-stuff 1 i386 none < 2 i386 none **CONFIGURE**'
+	testfileequal "${hook}-v2.list" 'libsame - < 2 **CONFIGURE**
+libsame 1 < 2 **CONFIGURE**
+stuff 1 < 2 **CONFIGURE**
+toolkit 1 < 2 **CONFIGURE**'
+	testfileequal "${hook}-v3.list" 'libsame - - none < 2 amd64 same **CONFIGURE**
+libsame 1 i386 same < 2 i386 same **CONFIGURE**
+stuff 1 i386 none < 2 i386 none **CONFIGURE**
+toolkit 1 all foreign < 2 amd64 foreign **CONFIGURE**'
 
 	observehook purge stuff:i386 'libsame:*' toolkit
 	testfileequal "${hook}-v2.list" 'libsame 2 > - **REMOVE**
-stuff 2 > - **REMOVE**
 libsame 2 > - **REMOVE**
+stuff 2 > - **REMOVE**
 toolkit 2 > - **REMOVE**'
 	testfileequal "${hook}-v3.list" 'libsame 2 amd64 same > - - none **REMOVE**
-stuff 2 i386 none > - - none **REMOVE**
 libsame 2 i386 same > - - none **REMOVE**
+stuff 2 i386 none > - - none **REMOVE**
 toolkit 2 amd64 foreign > - - none **REMOVE**'
 
 	observehook install confpkg
@@ -127,10 +136,17 @@ toolkit 2 amd64 foreign > - - none **REMOVE**'
 	dpkg -l confpkg 2>/dev/null | grep -q '^rc' && msgfail || msgpass
 }
 
-enablehookversion 2
-enablehookversion 3
-testrun
+runwithplanner()
+{
+	msgmsg 'Running with planner' "$1"
+	planner="$1"
+	enablehookversion 2
+	enablehookversion 3
+	testrun
+
+	enablehookversion 2 13
+	enablehookversion 3 13
+	testrun
+}
 
-enablehookversion 2 13
-enablehookversion 3 13
-testrun
+runwithplanner 'apt'

+ 3 - 3
test/integration/test-bug-735967-lib32-to-i386-unavailable

@@ -71,10 +71,10 @@ Inst lib32nss-mdns [0.9-1] (0.10-6 unstable [amd64]) []
 Inst libnss-mdns [0.9-1] (0.10-6 unstable [amd64]) []
 Inst libnss-mdns:i386 (0.10-6 unstable [i386]) []
 Inst libnss-mdns-i386:i386 (0.10-6 unstable [i386])
-Conf libnss-mdns:i386 (0.10-6 unstable [i386])
+Conf lib32nss-mdns (0.10-6 unstable [amd64])
 Conf libnss-mdns (0.10-6 unstable [amd64])
-Conf libnss-mdns-i386:i386 (0.10-6 unstable [i386])
-Conf lib32nss-mdns (0.10-6 unstable [amd64])' aptget dist-upgrade -s
+Conf libnss-mdns:i386 (0.10-6 unstable [i386])
+Conf libnss-mdns-i386:i386 (0.10-6 unstable [i386])' aptget dist-upgrade -s
 
 testsuccessequal 'Reading package lists...
 Building dependency tree...

+ 8 - 8
test/integration/test-bug-740843-versioned-up-down-breaks

@@ -33,11 +33,11 @@ Inst libgl1-foo-glx:i386 [1] (2 stable [i386]) [foo-driver:amd64 on libgl1-foo-g
 Inst foo-driver [1] (2 stable [amd64]) []
 Inst libfoo:i386 [1] (2 stable [i386]) [libfoo:amd64 on libfoo:i386] [libfoo:i386 on libfoo:amd64] [libfoo:amd64 ]
 Inst libfoo [1] (2 stable [amd64])
-Conf libfoo:i386 (2 stable [i386])
-Conf libfoo (2 stable [amd64])
-Conf libgl1-foo-glx:i386 (2 stable [i386])
 Conf libgl1-foo-glx (2 stable [amd64])
-Conf foo-driver (2 stable [amd64])' 'Reading package lists...
+Conf libgl1-foo-glx:i386 (2 stable [i386])
+Conf foo-driver (2 stable [amd64])
+Conf libfoo:i386 (2 stable [i386])
+Conf libfoo (2 stable [amd64])' 'Reading package lists...
 Building dependency tree...
 Calculating upgrade...
 The following packages will be upgraded:
@@ -48,10 +48,10 @@ Inst libgl1-foo-glx:i386 [1] (2 stable [i386]) [foo-driver:amd64 on libgl1-foo-g
 Inst foo-driver [1] (2 stable [amd64]) []
 Inst libfoo:i386 [1] (2 stable [i386]) [libfoo:amd64 on libfoo:i386] [libfoo:i386 on libfoo:amd64] [libfoo:amd64 ]
 Inst libfoo [1] (2 stable [amd64])
-Conf libfoo:i386 (2 stable [i386])
-Conf libfoo (2 stable [amd64])
-Conf libgl1-foo-glx:i386 (2 stable [i386])
 Conf libgl1-foo-glx (2 stable [amd64])
-Conf foo-driver (2 stable [amd64])' aptget dist-upgrade -s
+Conf libgl1-foo-glx:i386 (2 stable [i386])
+Conf foo-driver (2 stable [amd64])
+Conf libfoo:i386 (2 stable [i386])
+Conf libfoo (2 stable [amd64])' aptget dist-upgrade -s
 
 testsuccess aptget dist-upgrade -y -o Debug::pkgPackageManager=1 -o Debug::pkgOrderList=1

+ 2 - 2
test/integration/test-bug-lp1562402-nomark-removals-as-keep

@@ -33,7 +33,7 @@ Inst maas-region-controller [2.0.0~alpha3+bzr4810-0ubuntu1] (2.0.0~alpha4+bzr484
 Remv maas-region-controller-min [2.0.0~alpha3+bzr4810-0ubuntu1] []
 Inst maas-common [2.0.0~alpha3+bzr4810-0ubuntu1] (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [all]) []
 Inst maas-region-api (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [amd64])
+Conf maas-region-controller (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [all])
 Conf maas-common (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [all])
-Conf maas-region-api (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [amd64])
-Conf maas-region-controller (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [all])' \
+Conf maas-region-api (2.0.0~alpha4+bzr4843-0ubuntu1~xenial2 unstable [amd64])' \
 aptget dist-upgrade -s

+ 2 - 2
test/integration/test-bug-multiarch-upgrade

@@ -25,5 +25,5 @@ The following packages will be upgraded:
 2 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 Inst libcups2 [1] (2 unstable [amd64]) [libcups2:amd64 on libcups2:i386] [libcups2:i386 on libcups2:amd64] [libcups2:i386 ]
 Inst libcups2:i386 [1] (2 unstable [i386])
-Conf libcups2:i386 (2 unstable [i386])
-Conf libcups2 (2 unstable [amd64])' aptget install -s libcups2:i386
+Conf libcups2 (2 unstable [amd64])
+Conf libcups2:i386 (2 unstable [i386])' aptget install -s libcups2:i386

+ 49 - 0
test/integration/test-crossgrades

@@ -0,0 +1,49 @@
+#!/bin/sh
+set -e
+
+TESTDIR="$(readlink -f "$(dirname "$0")")"
+. "$TESTDIR/framework"
+
+setupenvironment
+configarchitecture 'i386' 'amd64' 'armel'
+configdpkgnoopchroot
+
+buildsimplenativepackage 'unrelated' 'amd64' '1' 'stable'
+buildsimplenativepackage 'crosser' 'i386,armel' '1' 'stable' 'Multi-Arch: same'
+buildsimplenativepackage 'crosser' 'amd64' '2' 'unstable'
+setupaptarchive
+
+singleinstance() {
+	testsuccess apt install crosser:i386=1 unrelated:amd64 -y --planner $1
+	testdpkginstalled 'crosser:i386' 'unrelated'
+
+	testsuccess apt install crosser:amd64 -y -o Debug::pkgDpkgPm=1 -o Dpkg::Use-Pty=0 --purge --planner $1
+	cp -a rootdir/tmp/testsuccess.output crosser.output
+	testfailure grep -- '--remove.*crosser.*' crosser.output
+	testfailure grep -- '--purge' crosser.output
+	testsuccess apt install crosser:amd64 unrelated:amd64- -y -o Dpkg::Use-Pty=0 --purge -o Debug::pkgDPkgProgressReporting=1 --planner $1
+	testdpkgnotinstalled 'crosser:i386' 'unrelated'
+	testdpkginstalled 'crosser:amd64'
+
+	testsuccess apt purge crosser:amd64 -y --planner $1
+	testdpkgnotinstalled 'crosser:amd64'
+}
+singleinstance 'internal'
+singleinstance 'apt'
+
+multiinstance() {
+	testsuccess apt install crosser:i386=1 crosser:armel=1 unrelated:amd64 -y --planner $1
+	testdpkginstalled 'crosser:i386' 'crosser:armel' 'unrelated'
+
+	testsuccess apt install crosser:amd64 -y -o Debug::pkgDpkgPm=1 -o Dpkg::Use-Pty=0 --purge --planner $1
+	cp -a rootdir/tmp/testsuccess.output crosser.output
+	testsuccess grep -- '--remove.*crosser.*' crosser.output
+	testsuccess grep -- '--purge' crosser.output
+	testsuccess apt install crosser:amd64 unrelated:amd64- -y -o Dpkg::Use-Pty=0 --purge -o Debug::pkgDPkgProgressReporting=1 --planner $1
+	testdpkgnotinstalled 'crosser:i386' 'crosser:armel' 'unrelated'
+	testdpkginstalled 'crosser:amd64'
+
+	testsuccess apt purge crosser:amd64 -y --planner $1
+	testdpkgnotinstalled 'crosser:amd64'
+}
+multiinstance 'internal'

+ 1 - 1
test/integration/test-dpkg-assert-multi-arch

@@ -17,7 +17,7 @@ setupaptarchive
 testqualifier() {
 	msgtest 'Test with' $1 'for correct qualifier mode' $2
 	#aptget install $1 -qq -o Debug::pkgDPkgPM=1 || true
-	aptget install $1 -qq -o Debug::pkgDPkgPM=1 -o Dpkg::ExplicitLastConfigure=1 > testqualifier.output 2>&1 || true
+	aptget install $1 -qq -o Debug::pkgDPkgPM=1 -o Dpkg::ExplicitLastConfigure=1 -o PackageManager::Configure=all > testqualifier.output 2>&1 || true
 	GIVEN="$(grep -v -- '--unpack' testqualifier.output | sed -ne 's/^.*--[rpc][^ ]* \([^ ]*\).*$/\1/p')"
 	if [ "$GIVEN" = "$2" ]; then
 		msgpass

+ 3 - 0
test/integration/test-essential-force-loopbreak

@@ -43,6 +43,9 @@ E: Internal Error, Could not early remove sysvinit:$(dpkg --print-architecture)
 
 	# with enough force however …
 	cp -a dpkg.status.backup rootdir/var/lib/dpkg/status
+	testsuccess aptget install systemd-sysv -y -t "$1" -o APT::Force-LoopBreak=1 -o Debug::pkgDpkgPm=1
+	cp rootdir/tmp/testsuccess.output apt.output
+	testsuccess grep -- '--force-remove-essential --remove sysvinit' apt.output
 	testsuccess aptget install systemd-sysv -y -t "$1" -o APT::Force-LoopBreak=1
 	testdpkginstalled 'sysvinit' 'systemd-sysv'
 }

+ 2 - 5
test/integration/test-external-installation-planner-protocol

@@ -45,9 +45,7 @@ Planner: internal' head -n 6 "$EIPPLOG"
 aptinternalplanner < "$EIPPLOG" > planner.log || true
 testsuccessequal 'Remove: 6
 Unpack: 2
-Unpack: 4
-Configure: 2
-Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log
+Unpack: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log
 
 rm -f "$EIPPLOG"
 testsuccess aptget install foo -s --reinstall
@@ -59,8 +57,7 @@ Architectures: amd64
 ReInstall: foo:amd64
 Planner: internal' head -n 5 "$EIPPLOG"
 aptinternalplanner < "$EIPPLOG" > planner.log || true
-testsuccessequal 'Unpack: 4
-Configure: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log
+testsuccessequal 'Unpack: 4' grep -e '^Unpack:' -e '^Install:' -e '^Configure:' -e '^Remove:' planner.log
 
 rm -f "$EIPPLOG"
 testsuccess aptget purge foo -s

+ 1 - 1
test/integration/test-ignore-provides-if-versioned-breaks

@@ -144,7 +144,7 @@ Inst foo-same [2.0] (4.0 unstable [i386]) [foo-same:i386 on foo-same:amd64] [foo
 Inst foo-same:amd64 [2.0] (4.0 unstable [amd64])
 Inst foo-same-breaker-3 (1.0 unstable [i386])
 Inst foo-same-provider (1.0 unstable [i386])
-Conf foo-same:amd64 (4.0 unstable [amd64])
 Conf foo-same (4.0 unstable [i386])
+Conf foo-same:amd64 (4.0 unstable [amd64])
 Conf foo-same-breaker-3 (1.0 unstable [i386])
 Conf foo-same-provider (1.0 unstable [i386])' aptget install foo-same-provider foo-same-breaker-3 -s

+ 1 - 1
test/integration/test-ignore-provides-if-versioned-conflicts

@@ -144,7 +144,7 @@ Inst foo-same [2.0] (4.0 unstable [i386]) [foo-same:i386 on foo-same:amd64] [foo
 Inst foo-same:amd64 [2.0] (4.0 unstable [amd64])
 Inst foo-same-breaker-3 (1.0 unstable [i386])
 Inst foo-same-provider (1.0 unstable [i386])
-Conf foo-same:amd64 (4.0 unstable [amd64])
 Conf foo-same (4.0 unstable [i386])
+Conf foo-same:amd64 (4.0 unstable [amd64])
 Conf foo-same-breaker-3 (1.0 unstable [i386])
 Conf foo-same-provider (1.0 unstable [i386])' aptget install foo-same-provider foo-same-breaker-3 -s

+ 6 - 2
test/integration/test-no-fds-leaked-to-maintainer-scripts

@@ -34,7 +34,7 @@ rm -f rootdir/var/log/dpkg.log rootdir/var/log/apt/term.log
 testsuccess aptget install -y fdleaks -qq < /dev/null
 
 checkfdleak() {
-	msgtest 'Check if fds were not' 'leaked'
+	msgtest 'Check if fds were not' "leaked: expect $1"
 	if [ "$(grep 'root root' rootdir/tmp/testsuccess.output | wc -l)" = "$1" ]; then
 		msgpass
 	else
@@ -72,12 +72,15 @@ checkpurge() {
 	tail -n +3 rootdir/var/log/apt/term.log | head -n -1 > terminal.log
 	testfileequal 'terminal.log' "$(cat terminal.output)"
 
-	testequal "startup packages purge
+	testequal "startup packages remove
 status installed $PKGNAME 1.0
 remove $PKGNAME 1.0 <none>
 status half-configured $PKGNAME 1.0
 status half-installed $PKGNAME 1.0
 status config-files $PKGNAME 1.0
+status config-files $PKGNAME 1.0
+startup packages purge
+remove $PKGNAME 1.0 <none>
 purge $PKGNAME 1.0 <none>
 status config-files $PKGNAME 1.0
 status config-files $PKGNAME 1.0
@@ -86,6 +89,7 @@ status config-files $PKGNAME 1.0
 status config-files $PKGNAME 1.0
 status not-installed $PKGNAME <none>
 startup packages configure" cut -f 3- -d' ' rootdir/var/log/dpkg.log
+	testequalor2 "dpkg-query: no packages found matching ${PKGNAME}" "No packages found matching ${PKGNAME}." dpkg -l "$PKGNAME"
 }
 checkpurge
 

+ 4 - 4
test/integration/test-prevent-markinstall-multiarch-same-versionscrew

@@ -59,10 +59,10 @@ Inst fine-installed [1] (2 unstable [amd64]) [fine-installed:amd64 on fine-insta
 Inst fine-installed:i386 [1] (2 unstable [i386])
 Inst out-of-sync-gone-foreign [1] (2 unstable [amd64])
 Inst out-of-sync-gone-native:i386 [1] (2 unstable [i386])
-Conf fine:i386 (2 unstable [i386])
 Conf fine (2 unstable [amd64])
-Conf fine-installed:i386 (2 unstable [i386])
+Conf fine:i386 (2 unstable [i386])
 Conf fine-installed (2 unstable [amd64])
+Conf fine-installed:i386 (2 unstable [i386])
 Conf out-of-sync-gone-foreign (2 unstable [amd64])
 Conf out-of-sync-gone-native:i386 (2 unstable [i386])' aptget dist-upgrade -s #-o Debug::pkgDepCache::Marker=1
 
@@ -89,10 +89,10 @@ Inst fine-installed [1] (3 experimental [amd64]) [fine-installed:amd64 on fine-i
 Inst fine-installed:i386 [1] (3 experimental [i386])
 Inst out-of-sync-gone-foreign [1] (2 unstable [amd64])
 Inst out-of-sync-gone-native:i386 [1] (2 unstable [i386])
-Conf fine:i386 (3 experimental [i386])
 Conf fine (3 experimental [amd64])
-Conf fine-installed:i386 (3 experimental [i386])
+Conf fine:i386 (3 experimental [i386])
 Conf fine-installed (3 experimental [amd64])
+Conf fine-installed:i386 (3 experimental [i386])
 Conf out-of-sync-gone-foreign (2 unstable [amd64])
 Conf out-of-sync-gone-native:i386 (2 unstable [i386])' aptget dist-upgrade -s #-o Debug::pkgDepCache::Marker=1
 

+ 4 - 0
test/integration/test-ubuntu-bug-761175-remove-purge

@@ -30,6 +30,8 @@ runtests() {
 	testsuccess aptget install compiz-core-${PKG} -t "${RELEASE}" "$@"
 	testdpkginstalled compiz-core-${PKG}
 
+	testsuccess aptget remove compiz-core-${PKG} -y "$@" -o Debug::pkgDpkgPm=1
+	testfailure grep -- '--force-remove-essential' rootdir/tmp/testsuccess.output
 	testsuccess aptget remove compiz-core-${PKG} -y "$@"
 	testdpkgnotinstalled compiz-core-${PKG}
 	testdpkgstatus 'rc' '1' "compiz-core-${PKG}"
@@ -61,6 +63,8 @@ The following packages will be REMOVED:
 0 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.
 Purg compiz-core-${PKG}" aptget purge compiz-core-${PKG} -s "$@"
 	fi
+	testsuccess aptget purge compiz-core-${PKG} -y "$@" -o Debug::pkgDpkgPm=1
+	testfailure grep -- '--force-remove-essential' rootdir/tmp/testsuccess.output
 	testsuccess aptget purge compiz-core-${PKG} -y "$@"
 	echo -n '' > rootdir/var/lib/dpkg/available # dpkg -l < 1.16.2 reads the available file by default, where the package can be found
 	testequalor2 "dpkg-query: no packages found matching compiz-core-${PKG}" "No packages found matching compiz-core-${PKG}." dpkg -l compiz-core-${PKG}

+ 8 - 8
test/integration/test-very-tight-loop-configure-with-unpacking-new-packages

@@ -39,11 +39,11 @@ Inst libreoffice-style-galaxy [3] (4 sid [amd64]) [libreoffice-common:amd64 on l
 Inst libreoffice-core [3] (4 sid [amd64]) [libreoffice-core:amd64 on libreoffice-common:amd64] [libreoffice-common:amd64 on libreoffice-core:amd64] [libreoffice-common:amd64 on libreoffice-style-galaxy:amd64] [libreoffice-common:amd64 ]
 Inst libreoffice-common [3] (4 sid [all]) []
 Inst ure (4 sid [amd64])
-Conf ure (4 sid [amd64])
-Conf libreoffice-common (4 sid [all])
-Conf libreoffice-core (4 sid [amd64])
+Conf libreoffice (4 sid [amd64])
 Conf libreoffice-style-galaxy (4 sid [amd64])
-Conf libreoffice (4 sid [amd64])' 'Reading package lists...
+Conf libreoffice-core (4 sid [amd64])
+Conf libreoffice-common (4 sid [all])
+Conf ure (4 sid [amd64])' 'Reading package lists...
 Building dependency tree...
 Calculating upgrade...
 The following NEW packages will be installed:
@@ -56,8 +56,8 @@ Inst libreoffice-style-galaxy [3] (4 sid [amd64]) [libreoffice-common:amd64 on l
 Inst libreoffice-core [3] (4 sid [amd64]) [libreoffice-common:amd64 on libreoffice-core:amd64] [libreoffice-common:amd64 on libreoffice-style-galaxy:amd64] [libreoffice-core:amd64 on libreoffice-common:amd64] [libreoffice-common:amd64 ]
 Inst libreoffice-common [3] (4 sid [all]) []
 Inst ure (4 sid [amd64])
-Conf ure (4 sid [amd64])
-Conf libreoffice-common (4 sid [all])
-Conf libreoffice-core (4 sid [amd64])
+Conf libreoffice (4 sid [amd64])
 Conf libreoffice-style-galaxy (4 sid [amd64])
-Conf libreoffice (4 sid [amd64])' aptget dist-upgrade -s
+Conf libreoffice-core (4 sid [amd64])
+Conf libreoffice-common (4 sid [all])
+Conf ure (4 sid [amd64])' aptget dist-upgrade -s