mirror of
https://github.com/ukui/apt.git
synced 2026-03-09 09:35:45 -07:00
b820fd59c4
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.
228 lines
6.8 KiB
C++
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;
|
|
|
|
}
|