Files
apt/apt-pkg/statechanges.cc
T
David Kalnischkies b820fd59c4 save and restore selection states before/after calling dpkg
dpkg decides certain things on its own based on selections and
especially if we want to call --pending on purge/remove actions, we need
to ensure a clean slate or otherwise we surprise the user by removing
packages we weren't allowed to remove by the user in this run (the
selection might be an overarching plan for the not-yet "future").

Ideally dpkg would have some kind of temporal selection interface for
this case, but it hasn't, so we make it temporal with the risk of
loosing state if we don't manage to restore them.
2016-08-10 23:18:04 +02:00

228 lines
6.8 KiB
C++

#include <apt-pkg/pkgcache.h>
#include <apt-pkg/cacheset.h>
#include <apt-pkg/debsystem.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/statechanges.h>
#include <apt-pkg/prettyprinters.h>
#include <algorithm>
#include <memory>
namespace APT
{
class StateChanges::Private
{
public:
APT::VersionVector hold;
APT::VersionVector unhold;
APT::VersionVector install;
APT::VersionVector deinstall;
APT::VersionVector purge;
APT::VersionVector error;
};
#define APT_GETTERSETTER(Name, Container) \
void StateChanges::Name(pkgCache::VerIterator const &Ver) \
{ \
if (Ver.end() == false) \
Container.push_back(Ver); \
}\
APT::VersionVector& StateChanges::Name() \
{ \
return Container; \
}
APT_GETTERSETTER(Hold, d->hold)
APT_GETTERSETTER(Unhold, d->unhold)
APT_GETTERSETTER(Install, d->install)
APT_GETTERSETTER(Remove, d->deinstall)
APT_GETTERSETTER(Purge, d->purge)
#undef APT_GETTERSETTER
APT::VersionVector& StateChanges::Error()
{
return d->error;
}
void StateChanges::clear()
{
d->hold.clear();
d->unhold.clear();
d->install.clear();
d->deinstall.clear();
d->purge.clear();
d->error.clear();
}
bool StateChanges::empty() const
{
return d->hold.empty() &&
d->unhold.empty() &&
d->install.empty() &&
d->deinstall.empty() &&
d->purge.empty() &&
d->error.empty();
}
bool StateChanges::Save(bool const DiscardOutput)
{
bool const Debug = _config->FindB("Debug::pkgDpkgPm", false);
d->error.clear();
if (d->hold.empty() && d->unhold.empty() && d->install.empty() && d->deinstall.empty() && d->purge.empty())
return true;
std::vector<std::string> Args = debSystem::GetDpkgBaseCommand();
// ensure dpkg knows about the package so that it keeps the status we set
if (d->hold.empty() == false || d->install.empty() == false)
{
APT::VersionVector makeDpkgAvailable;
auto const notInstalled = [](pkgCache::VerIterator const &V) { return V.ParentPkg()->CurrentVer == 0; };
std::copy_if(d->hold.begin(), d->hold.end(), std::back_inserter(makeDpkgAvailable), notInstalled);
std::copy_if(d->install.begin(), d->install.end(), std::back_inserter(makeDpkgAvailable), notInstalled);
if (makeDpkgAvailable.empty() == false)
{
auto const BaseArgs = Args.size();
Args.push_back("--merge-avail");
// FIXME: supported only since 1.17.7 in dpkg
Args.push_back("-");
int dummyAvail = -1;
if (Debug)
{
for (auto const &V: makeDpkgAvailable)
{
std::clog << "echo 'Dummy record for " << V.ParentPkg().FullName(false) << "' | ";
std::copy(Args.begin(), Args.end(), std::ostream_iterator<std::string>(std::clog, " "));
std::clog << std::endl;
}
}
else
{
pid_t const dpkgMergeAvail = debSystem::ExecDpkg(Args, &dummyAvail, nullptr, true);
FILE* dpkg = fdopen(dummyAvail, "w");
for (auto const &V: makeDpkgAvailable)
fprintf(dpkg, "Package: %s\nVersion: 0~\nArchitecture: %s\nMaintainer: Dummy Example <dummy@example.org>\n"
"Description: dummy package record\n A record is needed to put a package on hold, so here it is.\n\n", V.ParentPkg().Name(), V.Arch());
fclose(dpkg);
ExecWait(dpkgMergeAvail, "dpkg --merge-avail", true);
}
Args.erase(Args.begin() + BaseArgs, Args.end());
}
}
bool const dpkgMultiArch = _system->MultiArchSupported();
Args.push_back("--set-selections");
if (Debug)
{
std::string state;
auto const dpkgName = [&](pkgCache::VerIterator const &V) {
pkgCache::PkgIterator P = V.ParentPkg();
if (strcmp(V.Arch(), "none") == 0)
ioprintf(std::clog, "echo '%s %s' | ", P.Name(), state.c_str());
else if (dpkgMultiArch == false)
ioprintf(std::clog, "echo '%s %s' | ", P.FullName(true).c_str(), state.c_str());
else
ioprintf(std::clog, "echo '%s:%s %s' | ", P.Name(), V.Arch(), state.c_str());
std::copy(Args.begin(), Args.end(), std::ostream_iterator<std::string>(std::clog, " "));
std::clog << std::endl;
};
for (auto const &V: d->unhold)
{
if (V.ParentPkg()->CurrentVer != 0)
state = "install";
else
state = "deinstall";
dpkgName(V);
}
if (d->purge.empty() == false)
{
state = "purge";
std::for_each(d->purge.begin(), d->purge.end(), dpkgName);
}
if (d->deinstall.empty() == false)
{
state = "deinstall";
std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName);
}
if (d->hold.empty() == false)
{
state = "hold";
std::for_each(d->hold.begin(), d->hold.end(), dpkgName);
}
if (d->install.empty() == false)
{
state = "install";
std::for_each(d->install.begin(), d->install.end(), dpkgName);
}
}
else
{
int selections = -1;
pid_t const dpkgSelections = debSystem::ExecDpkg(Args, &selections, nullptr, DiscardOutput);
FILE* dpkg = fdopen(selections, "w");
std::string state;
auto const dpkgName = [&](pkgCache::VerIterator const &V) {
pkgCache::PkgIterator P = V.ParentPkg();
if (strcmp(V.Arch(), "none") == 0)
fprintf(dpkg, "%s %s\n", P.Name(), state.c_str());
else if (dpkgMultiArch == false)
fprintf(dpkg, "%s %s\n", P.FullName(true).c_str(), state.c_str());
else
fprintf(dpkg, "%s:%s %s\n", P.Name(), V.Arch(), state.c_str());
};
for (auto const &V: d->unhold)
{
if (V.ParentPkg()->CurrentVer != 0)
state = "install";
else
state = "deinstall";
dpkgName(V);
}
if (d->purge.empty() == false)
{
state = "purge";
std::for_each(d->purge.begin(), d->purge.end(), dpkgName);
}
if (d->deinstall.empty() == false)
{
state = "deinstall";
std::for_each(d->deinstall.begin(), d->deinstall.end(), dpkgName);
}
if (d->hold.empty() == false)
{
state = "hold";
std::for_each(d->hold.begin(), d->hold.end(), dpkgName);
}
if (d->install.empty() == false)
{
state = "install";
std::for_each(d->install.begin(), d->install.end(), dpkgName);
}
fclose(dpkg);
if (ExecWait(dpkgSelections, "dpkg --set-selections") == false)
{
std::move(d->purge.begin(), d->purge.end(), std::back_inserter(d->error));
d->purge.clear();
std::move(d->deinstall.begin(), d->deinstall.end(), std::back_inserter(d->error));
d->deinstall.clear();
std::move(d->hold.begin(), d->hold.end(), std::back_inserter(d->error));
d->hold.clear();
std::move(d->unhold.begin(), d->unhold.end(), std::back_inserter(d->error));
d->unhold.clear();
std::move(d->install.begin(), d->install.end(), std::back_inserter(d->error));
d->install.clear();
}
}
return d->error.empty();
}
StateChanges::StateChanges() : d(new StateChanges::Private()) {}
StateChanges::StateChanges(StateChanges&&) = default;
StateChanges& StateChanges::operator=(StateChanges&&) = default;
StateChanges::~StateChanges() = default;
}