]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/deb/dpkgpm.cc
Merge branch 'feature/apt-dpkg-comm'
[apt.git] / apt-pkg / deb / dpkgpm.cc
index bc34e50e03a4b8faddd458c0cfd2e84cb036f09b..54a8dffd71b9520769d826df150223b5df41ac62 100644 (file)
@@ -51,7 +51,9 @@
 #include <map>
 #include <set>
 #include <string>
+#include <type_traits>
 #include <utility>
+#include <unordered_set>
 #include <vector>
 #include <sstream>
 #include <numeric>
@@ -668,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'
@@ -718,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)
         {
            /*
@@ -749,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;
+                          }
+                       }
+                 }
+              }
+           }
         }
       }
    }
@@ -1271,15 +1297,57 @@ static void cleanUpTmpDir(char * const tmpdir)                          /*{{{*/
  * 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)
 {
-   auto const 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;
+   // 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;
@@ -1303,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;
@@ -1329,26 +1397,102 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
    // 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);
 
-   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()));
-
    // 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;
@@ -1372,14 +1516,9 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
            return _error->Error("Couldn't clean the currently selected dpkg states");
       }
    }
-   APT::StateChanges approvedStates;
+
    if (_config->FindB("dpkg::selection::remove::approved", true))
    {
-      for (auto && I: List)
-        if (I.Op == Item::Remove && Cache[I.Pkg].Delete())
-           approvedStates.Remove(FindToBeRemovedVersion(I.Pkg));
-        else if (I.Op == Item::Purge && Cache[I.Pkg].Purge())
-           approvedStates.Purge(FindToBeRemovedVersion(I.Pkg));
       if (approvedStates.Save(false) == false)
       {
         _error->Error("Couldn't record the approved state changes as dpkg selection states");
@@ -1388,34 +1527,29 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
         return false;
       }
 
-      {
-        std::vector<bool> toBeRemoved(Cache.Head().PackageCount, false);
-        std::vector<bool> toBePurged(Cache.Head().PackageCount, false);
-        for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
-           if (Cache[Pkg].Purge())
-              toBePurged[Pkg->ID] = true;
-           else if (Cache[Pkg].Delete())
-              toBeRemoved[Pkg->ID] = true;
-        for (auto && I: approvedStates.Remove())
-           toBeRemoved[I.ParentPkg()->ID] = false;
-        for (auto && I: approvedStates.Purge())
-           toBePurged[I.ParentPkg()->ID] = false;
-        if (std::find(toBeRemoved.begin(), toBeRemoved.end(), true) != toBeRemoved.end())
-        {
-           if (ConfigurePending)
-              List.emplace(std::prev(List.end()), Item::RemovePending, pkgCache::PkgIterator());
-           else
-              List.emplace_back(Item::RemovePending, pkgCache::PkgIterator());
-        }
-        if (std::find(toBePurged.begin(), toBePurged.end(), true) != toBePurged.end())
-        {
-           if (ConfigurePending)
-              List.emplace(std::prev(List.end()), Item::PurgePending, pkgCache::PkgIterator());
-           else
-              List.emplace_back(Item::PurgePending, pkgCache::PkgIterator());
-        }
-      }
+      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;
 
@@ -1431,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
@@ -1448,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;
 
@@ -1474,20 +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");
-        if (std::any_of(I, J, ItemIsEssential))
-           ADDARGC("--force-remove-essential");
-        ADDARGC("--remove");
-        break;
-
         case Item::Purge:
         ADDARGC("--force-depends");
         if (std::any_of(I, J, ItemIsEssential))
            ADDARGC("--force-remove-essential");
-        ADDARGC("--purge");
+        ADDARGC("--remove");
         break;
 
         case Item::Configure:
@@ -1520,11 +1655,6 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
         break;
       }
 
-      if (NoTriggers == true && I->Op != Item::TriggersPending &&
-         I->Op != Item::ConfigurePending)
-      {
-        ADDARGC("--no-triggers");
-      }
       char * tmpdir_to_free = nullptr;
 
       // Write in the file or package names
@@ -1568,10 +1698,37 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
            }
         }
       }
+      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)
@@ -1590,8 +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)
-                 PkgVer = FindToBeRemovedVersion(I->Pkg);
+              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)
+                    continue;
+              }
               else
                  PkgVer = Cache[I->Pkg].InstVerIter(Cache);
               if (strcmp(I->Pkg.Arch(), "none") == 0)
@@ -1797,11 +1961,14 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 
    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.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.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!");