#include <map>
#include <set>
#include <string>
+#include <type_traits>
#include <utility>
+#include <unordered_set>
#include <vector>
#include <sstream>
#include <numeric>
// 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'
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)
{
/*
}
*/
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;
+ }
+ }
+ }
+ }
+ }
}
}
}
* 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;
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;
// 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;
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");
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;
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
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;
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:
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
}
}
}
+ 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)
{
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)
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!");