#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)
{
pkgSimulate::~pkgSimulate()
{
delete[] Flags;
+ delete d;
}
/*}}}*/
// Simulate::Describe - Describe a package /*{{{*/
// 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());
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());
// ---------------------------------------------------------------------
/* */
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());
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
#endif
+class pkgSimulatePrivate;
class pkgSimulate : public pkgPackageManager /*{{{*/
{
- void * const d;
+ pkgSimulatePrivate * const d;
protected:
class Policy : public pkgDepCache::Policy
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:
#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>
#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>
#include <map>
#include <set>
#include <string>
+#include <type_traits>
#include <utility>
+#include <unordered_set>
#include <vector>
#include <sstream>
#include <numeric>
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 /*{{{*/
// ---------------------------------------------------------------------
// 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;
+ }
+ }
+ }
+ }
+ }
}
}
}
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 /*{{{*/
// ---------------------------------------------------------------------
* 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;
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;
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
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");
- 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:
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)
{
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)
if (oldSize == Size)
continue;
}
+#undef ADDARGC
#undef ADDARG
J = I;
Packages.clear();
close(fd[0]);
close(fd[1]);
+ cleanUpTmpDir(tmpdir_to_free);
continue;
}
Args.push_back(NULL);
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]);
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"));
// 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;
}
// 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"))
// 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
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;
pkgDPkgPM(pkgDepCache *Cache);
virtual ~pkgDPkgPM();
+
+ APT_HIDDEN static bool ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache);
};
void SigINT(int sig);
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)
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))
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;
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() {};
#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() \
{ \
<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>
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.
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...
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'
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
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' \
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'
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
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
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
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**'
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
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'
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...
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:
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
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 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
--- /dev/null
+#!/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'
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
# 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'
}
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
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
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
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
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
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
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
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
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
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}"
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}
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:
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