#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; }