summaryrefslogtreecommitdiffstats
path: root/apt-pkg/edsp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--apt-pkg/edsp.cc1195
-rw-r--r--apt-pkg/edsp.h252
-rw-r--r--apt-pkg/edsp/edspindexfile.cc129
-rw-r--r--apt-pkg/edsp/edspindexfile.h59
-rw-r--r--apt-pkg/edsp/edsplistparser.cc177
-rw-r--r--apt-pkg/edsp/edsplistparser.h58
-rw-r--r--apt-pkg/edsp/edspsystem.cc166
-rw-r--r--apt-pkg/edsp/edspsystem.h75
8 files changed, 2111 insertions, 0 deletions
diff --git a/apt-pkg/edsp.cc b/apt-pkg/edsp.cc
new file mode 100644
index 0000000..9d196ee
--- /dev/null
+++ b/apt-pkg/edsp.cc
@@ -0,0 +1,1195 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+ Set of methods to help writing and reading everything needed for EDSP
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/algorithms.h>
+#include <apt-pkg/aptconfiguration.h>
+#include <apt-pkg/cacheset.h>
+#include <apt-pkg/depcache.h>
+#include <apt-pkg/edsp.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/packagemanager.h>
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/pkgsystem.h>
+#include <apt-pkg/prettyprinters.h>
+#include <apt-pkg/progress.h>
+#include <apt-pkg/string_view.h>
+#include <apt-pkg/strutl.h>
+#include <apt-pkg/tagfile.h>
+
+#include <ctype.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
+#include <limits>
+#include <sstream>
+#include <string>
+
+#include <apti18n.h>
+ /*}}}*/
+
+using std::string;
+
+// we could use pkgCache::DepType and ::Priority, but these would be localized strings…
+constexpr char const * const PrioMap[] = {
+ nullptr, "important", "required", "standard",
+ "optional", "extra"
+};
+constexpr char const * const DepMap[] = {
+ nullptr, "Depends", "Pre-Depends", "Suggests",
+ "Recommends" , "Conflicts", "Replaces",
+ "Obsoletes", "Breaks", "Enhances"
+};
+
+// WriteOkay - varaidic helper to easily Write to a FileFd /*{{{*/
+static bool WriteOkay_fn(FileFd &) { return true; }
+template<typename... Tail> static bool WriteOkay_fn(FileFd &output, APT::StringView data, Tail... more_data)
+{
+ return likely(output.Write(data.data(), data.length()) && WriteOkay_fn(output, more_data...));
+}
+template<typename... Tail> static bool WriteOkay_fn(FileFd &output, unsigned int data, Tail... more_data)
+{
+ std::string number;
+ strprintf(number, "%d", data);
+ return likely(output.Write(number.data(), number.length()) && WriteOkay_fn(output, more_data...));
+}
+template<typename... Data> static bool WriteOkay(bool &Okay, FileFd &output, Data&&... data)
+{
+ Okay = likely(Okay && WriteOkay_fn(output, std::forward<Data>(data)...));
+ return Okay;
+}
+template<typename... Data> static bool WriteOkay(FileFd &output, Data&&... data)
+{
+ bool Okay = likely(output.Failed() == false);
+ return WriteOkay(Okay, output, std::forward<Data>(data)...);
+}
+ /*}}}*/
+// WriteScenarioVersion /*{{{*/
+static bool WriteScenarioVersion(FileFd &output, pkgCache::PkgIterator const &Pkg,
+ pkgCache::VerIterator const &Ver)
+{
+ bool Okay = WriteOkay(output, "Package: ", Pkg.Name(),
+ "\nArchitecture: ", Ver.Arch(),
+ "\nVersion: ", Ver.VerStr());
+ WriteOkay(Okay, output, "\nAPT-ID: ", Ver->ID);
+ if (Ver.PhasedUpdatePercentage() != 100)
+ WriteOkay(Okay, output, "\nPhased-Update-Percentage: ", Ver.PhasedUpdatePercentage());
+ if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential)
+ WriteOkay(Okay, output, "\nEssential: yes");
+ if ((Ver->MultiArch & pkgCache::Version::Allowed) == pkgCache::Version::Allowed)
+ WriteOkay(Okay, output, "\nMulti-Arch: allowed");
+ else if ((Ver->MultiArch & pkgCache::Version::Foreign) == pkgCache::Version::Foreign)
+ WriteOkay(Okay, output, "\nMulti-Arch: foreign");
+ else if ((Ver->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same)
+ WriteOkay(Okay, output, "\nMulti-Arch: same");
+ return Okay;
+}
+ /*}}}*/
+// WriteScenarioDependency /*{{{*/
+static bool WriteScenarioDependency(FileFd &output, pkgCache::VerIterator const &Ver, bool const OnlyCritical)
+{
+ std::array<std::string, APT_ARRAY_SIZE(DepMap)> dependencies;
+ bool orGroup = false;
+ for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep)
+ {
+ if (Dep.IsImplicit() == true)
+ continue;
+ if (OnlyCritical && Dep.IsCritical() == false)
+ continue;
+ if (orGroup == false && dependencies[Dep->Type].empty() == false)
+ dependencies[Dep->Type].append(", ");
+ dependencies[Dep->Type].append(Dep.TargetPkg().Name());
+ if (Dep->Version != 0)
+ dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")");
+ if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
+ {
+ dependencies[Dep->Type].append(" | ");
+ orGroup = true;
+ }
+ else
+ orGroup = false;
+ }
+ bool Okay = output.Failed() == false;
+ for (size_t i = 1; i < dependencies.size(); ++i)
+ if (dependencies[i].empty() == false)
+ WriteOkay(Okay, output, "\n", DepMap[i], ": ", dependencies[i]);
+ std::vector<std::string> provides;
+ for (auto Prv = Ver.ProvidesList(); not Prv.end(); ++Prv)
+ {
+ if (Prv.IsMultiArchImplicit())
+ continue;
+ std::string provide = Prv.Name();
+ if (Prv->ProvideVersion != 0)
+ provide.append(" (= ").append(Prv.ProvideVersion()).append(")");
+ if ((Ver->MultiArch & pkgCache::Version::Foreign) != 0 && std::find(provides.cbegin(), provides.cend(), provide) != provides.cend())
+ continue;
+ provides.emplace_back(std::move(provide));
+ }
+ if (not provides.empty())
+ {
+ std::ostringstream out;
+ std::copy(provides.begin(), provides.end() - 1, std::ostream_iterator<std::string>(out, ", "));
+ out << provides.back();
+ WriteOkay(Okay, output, "\nProvides: ", out.str());
+ }
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+// WriteScenarioLimitedDependency /*{{{*/
+static bool WriteScenarioLimitedDependency(FileFd &output,
+ pkgCache::VerIterator const &Ver,
+ std::vector<bool> const &pkgset,
+ bool const OnlyCritical)
+{
+ std::array<std::string, APT_ARRAY_SIZE(DepMap)> dependencies;
+ bool orGroup = false;
+ for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep)
+ {
+ if (Dep.IsImplicit() == true)
+ continue;
+ if (OnlyCritical && Dep.IsCritical() == false)
+ continue;
+ if (orGroup == false)
+ {
+ if (pkgset[Dep.TargetPkg()->ID] == false)
+ continue;
+ if (dependencies[Dep->Type].empty() == false)
+ dependencies[Dep->Type].append(", ");
+ }
+ else if (pkgset[Dep.TargetPkg()->ID] == false)
+ {
+ if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
+ continue;
+ dependencies[Dep->Type].erase(dependencies[Dep->Type].end()-3, dependencies[Dep->Type].end());
+ orGroup = false;
+ continue;
+ }
+ dependencies[Dep->Type].append(Dep.TargetPkg().Name());
+ if (Dep->Version != 0)
+ dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")");
+ if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
+ {
+ dependencies[Dep->Type].append(" | ");
+ orGroup = true;
+ }
+ else
+ orGroup = false;
+ }
+ bool Okay = output.Failed() == false;
+ for (size_t i = 1; i < dependencies.size(); ++i)
+ if (dependencies[i].empty() == false)
+ WriteOkay(Okay, output, "\n", DepMap[i], ": ", dependencies[i]);
+ string provides;
+ for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv)
+ {
+ if (Prv.IsMultiArchImplicit() == true)
+ continue;
+ if (pkgset[Prv.ParentPkg()->ID] == false)
+ continue;
+ if (provides.empty() == false)
+ provides.append(", ");
+ provides.append(Prv.Name());
+ if (Prv->ProvideVersion != 0)
+ provides.append(" (= ").append(Prv.ProvideVersion()).append(")");
+ }
+ if (provides.empty() == false)
+ WriteOkay(Okay, output, "\nProvides: ", provides);
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+static bool checkKnownArchitecture(std::string const &arch) /*{{{*/
+{
+ if (APT::Configuration::checkArchitecture(arch))
+ return true;
+ static auto const veryforeign = _config->FindVector("APT::BarbarianArchitectures");
+ return std::find(veryforeign.begin(), veryforeign.end(), arch) != veryforeign.end();
+}
+ /*}}}*/
+static bool WriteGenericRequestHeaders(FileFd &output, APT::StringView const head)/*{{{*/
+{
+ bool Okay = WriteOkay(output, head, "Architecture: ", _config->Find("APT::Architecture"), "\n",
+ "Architectures:");
+ for (auto const &a : APT::Configuration::getArchitectures())
+ WriteOkay(Okay, output, " ", a);
+ for (auto const &a : _config->FindVector("APT::BarbarianArchitectures"))
+ WriteOkay(Okay, output, " ", a);
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+static bool SkipUnavailableVersions(pkgDepCache &Cache, pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver)/*{{{*/
+{
+ /* versions which aren't current and aren't available in
+ any "online" source file are bad, expect if they are the chosen
+ candidate: The exception is for build-dep implementation as it creates
+ such pseudo (package) versions and removes them later on again.
+ We filter out versions at all so packages in 'rc' state only available
+ in dpkg/status aren't passed to solvers as they can't be installed. */
+ if (Pkg->CurrentVer != 0)
+ return false;
+ if (Cache.GetCandidateVersion(Pkg) == Ver)
+ return false;
+ for (pkgCache::VerFileIterator I = Ver.FileList(); I.end() == false; ++I)
+ if (I.File().Flagged(pkgCache::Flag::NotSource) == false)
+ return false;
+ return true;
+}
+ /*}}}*/
+static bool WriteScenarioEDSPVersion(pkgDepCache &Cache, FileFd &output, pkgCache::PkgIterator const &Pkg,/*{{{*/
+ pkgCache::VerIterator const &Ver)
+{
+ bool Okay = WriteOkay(output, "\nSource: ", Ver.SourcePkgName(),
+ "\nSource-Version: ", Ver.SourceVerStr());
+ if (PrioMap[Ver->Priority] != nullptr)
+ WriteOkay(Okay, output, "\nPriority: ", PrioMap[Ver->Priority]);
+ if (Ver->Section != 0)
+ WriteOkay(Okay, output, "\nSection: ", Ver.Section());
+ if (Pkg.CurrentVer() == Ver)
+ WriteOkay(Okay, output, "\nInstalled: yes");
+ if (Pkg->SelectedState == pkgCache::State::Hold ||
+ (Cache[Pkg].Keep() == true && Cache[Pkg].Protect() == true))
+ WriteOkay(Okay, output, "\nHold: yes");
+ std::set<string> Releases;
+ for (pkgCache::VerFileIterator I = Ver.FileList(); I.end() == false; ++I) {
+ pkgCache::PkgFileIterator File = I.File();
+ if (File.Flagged(pkgCache::Flag::NotSource) == false) {
+ string Release = File.RelStr();
+ if (!Release.empty())
+ Releases.insert(Release);
+ }
+ }
+ if (!Releases.empty()) {
+ WriteOkay(Okay, output, "\nAPT-Release:");
+ for (std::set<string>::iterator R = Releases.begin(); R != Releases.end(); ++R)
+ WriteOkay(Okay, output, "\n ", *R);
+ }
+ WriteOkay(Okay, output, "\nAPT-Pin: ", Cache.GetPolicy().GetPriority(Ver));
+ if (Cache.GetCandidateVersion(Pkg) == Ver)
+ WriteOkay(Okay, output, "\nAPT-Candidate: yes");
+ if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
+ WriteOkay(Okay, output, "\nAPT-Automatic: yes");
+ return Okay;
+}
+ /*}}}*/
+// EDSP::WriteScenario - to the given file descriptor /*{{{*/
+bool EDSP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress *Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver"));
+ decltype(Cache.Head().VersionCount) p = 0;
+ bool Okay = output.Failed() == false;
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg)
+ {
+ if (Pkg->CurrentVer == 0 && not checkKnownArchitecture(Pkg.Arch()))
+ continue;
+ for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false && likely(Okay); ++Ver, ++p)
+ {
+ if (SkipUnavailableVersions(Cache, Pkg, Ver))
+ continue;
+ Okay &= WriteScenarioVersion(output, Pkg, Ver);
+ Okay &= WriteScenarioEDSPVersion(Cache, output, Pkg, Ver);
+ Okay &= WriteScenarioDependency(output, Ver, false);
+ WriteOkay(Okay, output, "\n");
+ if (Progress != NULL && p % 100 == 0)
+ Progress->Progress(p);
+ }
+ }
+ return Okay;
+}
+ /*}}}*/
+// EDSP::WriteLimitedScenario - to the given file descriptor /*{{{*/
+bool EDSP::WriteLimitedScenario(pkgDepCache &Cache, FileFd &output,
+ std::vector<bool> const &pkgset,
+ OpProgress *Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to solver"));
+ decltype(Cache.Head().PackageCount) p = 0;
+ bool Okay = output.Failed() == false;
+ for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg, ++p)
+ {
+ if (pkgset[Pkg->ID] == false)
+ continue;
+ for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false && likely(Okay); ++Ver)
+ {
+ if (SkipUnavailableVersions(Cache, Pkg, Ver))
+ continue;
+ Okay &= WriteScenarioVersion(output, Pkg, Ver);
+ Okay &= WriteScenarioEDSPVersion(Cache, output, Pkg, Ver);
+ Okay &= WriteScenarioLimitedDependency(output, Ver, pkgset, false);
+ WriteOkay(Okay, output, "\n");
+ if (Progress != NULL && p % 100 == 0)
+ Progress->Progress(p);
+ }
+ }
+ if (Progress != NULL)
+ Progress->Done();
+ return Okay;
+}
+ /*}}}*/
+// EDSP::WriteRequest - to the given file descriptor /*{{{*/
+bool EDSP::WriteRequest(pkgDepCache &Cache, FileFd &output,
+ unsigned int const flags,
+ OpProgress *Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().PackageCount, _("Send request to solver"));
+ decltype(Cache.Head().PackageCount) p = 0;
+ string del, inst;
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p)
+ {
+ if (Progress != NULL && p % 100 == 0)
+ Progress->Progress(p);
+ string* req;
+ pkgDepCache::StateCache &P = Cache[Pkg];
+ if (P.Delete() == true)
+ req = &del;
+ else if (P.NewInstall() == true || P.Upgrade() == true || P.ReInstall() == true ||
+ (P.Mode == pkgDepCache::ModeKeep && (P.iFlags & pkgDepCache::Protected) == pkgDepCache::Protected))
+ req = &inst;
+ else
+ continue;
+ req->append(" ").append(Pkg.FullName());
+ }
+
+ bool Okay = WriteGenericRequestHeaders(output, "Request: EDSP 0.5\n");
+ string machineID = APT::Configuration::getMachineID();
+ if (not machineID.empty())
+ WriteOkay(Okay, output, "Machine-ID: ", machineID, "\n");
+ if (del.empty() == false)
+ WriteOkay(Okay, output, "Remove:", del, "\n");
+ if (inst.empty() == false)
+ WriteOkay(Okay, output, "Install:", inst, "\n");
+ if (flags & Request::AUTOREMOVE)
+ WriteOkay(Okay, output, "Autoremove: yes\n");
+ if (flags & Request::UPGRADE_ALL)
+ {
+ WriteOkay(Okay, output, "Upgrade-All: yes\n");
+ if (flags & (Request::FORBID_NEW_INSTALL | Request::FORBID_REMOVE))
+ WriteOkay(Okay, output, "Upgrade: yes\n");
+ else
+ WriteOkay(Okay, output, "Dist-Upgrade: yes\n");
+ }
+ if (flags & Request::FORBID_NEW_INSTALL)
+ WriteOkay(Okay, output, "Forbid-New-Install: yes\n");
+ if (flags & Request::FORBID_REMOVE)
+ WriteOkay(Okay, output, "Forbid-Remove: yes\n");
+ auto const solver = _config->Find("APT::Solver", "internal");
+ WriteOkay(Okay, output, "Solver: ", solver, "\n");
+ if (_config->FindB("APT::Solver::Strict-Pinning", true) == false)
+ WriteOkay(Okay, output, "Strict-Pinning: no\n");
+ string solverpref("APT::Solver::");
+ solverpref.append(solver).append("::Preferences");
+ if (_config->Exists(solverpref) == true)
+ WriteOkay(Okay, output, "Preferences: ", _config->Find(solverpref,""), "\n");
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+// EDSP::ReadResponse - from the given file descriptor /*{{{*/
+bool EDSP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progress) {
+ /* We build an map id to mmap offset here
+ In theory we could use the offset as ID, but then VersionCount
+ couldn't be used to create other versionmappings anymore and it
+ would be too easy for a (buggy) solver to segfault APT… */
+ auto VersionCount = Cache.Head().VersionCount;
+ decltype(VersionCount) VerIdx[VersionCount];
+ for (pkgCache::PkgIterator P = Cache.PkgBegin(); P.end() == false; ++P) {
+ for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V)
+ VerIdx[V->ID] = V.Index();
+ Cache[P].Marked = true;
+ Cache[P].Garbage = false;
+ }
+
+ FileFd in;
+ in.OpenDescriptor(input, FileFd::ReadOnly, true);
+ pkgTagFile response(&in, 100);
+ pkgTagSection section;
+
+ std::set<decltype(Cache.PkgBegin()->ID)> seenOnce;
+ while (response.Step(section) == true) {
+ std::string type;
+ if (section.Exists("Install") == true)
+ type = "Install";
+ else if (section.Exists("Remove") == true)
+ type = "Remove";
+ else if (section.Exists("Progress") == true) {
+ if (Progress != NULL) {
+ string msg = section.FindS("Message");
+ if (msg.empty() == true)
+ msg = _("Prepare for receiving solution");
+ Progress->SubProgress(100, msg, section.FindI("Percentage", 0));
+ }
+ continue;
+ } else if (section.Exists("Error") == true) {
+ if (_error->PendingError()) {
+ if (Progress != nullptr)
+ Progress->Done();
+ Progress = nullptr;
+ _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
+ }
+ std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n");
+ if (msg.empty() == true) {
+ msg = _("External solver failed without a proper error message");
+ _error->Error("%s", msg.c_str());
+ } else
+ _error->Error("External solver failed with: %s", msg.substr(0,msg.find('\n')).c_str());
+ if (Progress != nullptr)
+ Progress->Done();
+ std::cerr << "The solver encountered an error of type: " << section.FindS("Error") << std::endl;
+ std::cerr << "The following information might help you to understand what is wrong:" << std::endl;
+ std::cerr << msg << std::endl << std::endl;
+ return false;
+ } else if (section.Exists("Autoremove") == true)
+ type = "Autoremove";
+ else {
+ char const *Start, *End;
+ section.GetSection(Start, End);
+ _error->Warning("Encountered an unexpected section with %d fields: %s", section.Count(), std::string(Start, End).c_str());
+ continue;
+ }
+
+ decltype(VersionCount) const id = section.FindULL(type, VersionCount);
+ if (id == VersionCount) {
+ _error->Warning("Unable to parse %s request with id value '%s'!", type.c_str(), section.FindS(type.c_str()).c_str());
+ continue;
+ } else if (id > VersionCount) {
+ _error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type.c_str()).c_str(), type.c_str());
+ continue;
+ }
+
+ pkgCache::VerIterator Ver(Cache.GetCache(), Cache.GetCache().VerP + VerIdx[id]);
+ auto const Pkg = Ver.ParentPkg();
+ if (type == "Autoremove") {
+ Cache[Pkg].Marked = false;
+ Cache[Pkg].Garbage = true;
+ } else if (seenOnce.emplace(Pkg->ID).second == false) {
+ _error->Warning("Ignoring %s stanza received for package %s which already had a previous stanza effecting it!", type.c_str(), Pkg.FullName(false).c_str());
+ } else if (type == "Install") {
+ if (Pkg.CurrentVer() == Ver) {
+ _error->Warning("Ignoring Install stanza received for version %s of package %s which is already installed!",
+ Ver.VerStr(), Pkg.FullName(false).c_str());
+ } else {
+ Cache.SetCandidateVersion(Ver);
+ Cache.MarkInstall(Pkg, false, 0, false);
+ }
+ } else if (type == "Remove") {
+ if (Pkg->CurrentVer == 0)
+ _error->Warning("Ignoring Remove stanza received for version %s of package %s which isn't installed!",
+ Ver.VerStr(), Pkg.FullName(false).c_str());
+ else if (Pkg.CurrentVer() != Ver)
+ _error->Warning("Ignoring Remove stanza received for version %s of package %s which isn't the installed version %s!",
+ Ver.VerStr(), Pkg.FullName(false).c_str(), Pkg.CurrentVer().VerStr());
+ else
+ Cache.MarkDelete(Ver.ParentPkg(), false);
+ }
+ }
+ return true;
+}
+ /*}}}*/
+// ReadLine - first line from the given file descriptor /*{{{*/
+// ---------------------------------------------------------------------
+/* Little helper method to read a complete line into a string. Similar to
+ fgets but we need to use the low-level read() here as otherwise the
+ listparser will be confused later on as mixing of fgets and read isn't
+ a supported action according to the manpages and results are undefined */
+static bool ReadLine(int const input, std::string &line) {
+ char one;
+ ssize_t data = 0;
+ line.erase();
+ line.reserve(100);
+ while ((data = read(input, &one, sizeof(one))) != -1) {
+ if (data != 1)
+ continue;
+ if (one == '\n')
+ return true;
+ if (one == '\r')
+ continue;
+ if (line.empty() == true && isblank(one) != 0)
+ continue;
+ line += one;
+ }
+ return false;
+}
+ /*}}}*/
+// StringToBool - convert yes/no to bool /*{{{*/
+// ---------------------------------------------------------------------
+/* we are not as lazy as we are in the global StringToBool as we really
+ only accept yes/no here */
+static bool localStringToBool(std::string answer, bool const defValue) {
+ std::transform(answer.begin(), answer.end(), answer.begin(), ::tolower);
+ if (answer == "yes")
+ return true;
+ else if (answer == "no")
+ return false;
+ else
+ _error->Warning("Value '%s' is not a boolean 'yes' or 'no'!", answer.c_str());
+ return defValue;
+}
+ /*}}}*/
+static bool LineStartsWithAndStrip(std::string &line, APT::StringView const with)/*{{{*/
+{
+ if (line.compare(0, with.size(), with.data()) != 0)
+ return false;
+ line = APT::String::Strip(line.substr(with.length()));
+ return true;
+}
+ /*}}}*/
+static bool ReadFlag(unsigned int &flags, std::string &line, APT::StringView const name, unsigned int const setflag)/*{{{*/
+{
+ if (LineStartsWithAndStrip(line, name) == false)
+ return false;
+ if (localStringToBool(line, false))
+ flags |= setflag;
+ else
+ flags &= ~setflag;
+ return true;
+}
+ /*}}}*/
+// EDSP::ReadRequest - first stanza from the given file descriptor /*{{{*/
+bool EDSP::ReadRequest(int const input, std::list<std::string> &install,
+ std::list<std::string> &remove, unsigned int &flags)
+{
+ install.clear();
+ remove.clear();
+ flags = 0;
+ std::string line;
+ while (ReadLine(input, line) == true)
+ {
+ // Skip empty lines before request
+ if (line.empty() == true)
+ continue;
+ // The first Tag must be a request, so search for it
+ if (LineStartsWithAndStrip(line, "Request:"))
+ continue;
+
+ while (ReadLine(input, line) == true)
+ {
+ // empty lines are the end of the request
+ if (line.empty() == true)
+ return true;
+
+ std::list<std::string> *request = NULL;
+ if (LineStartsWithAndStrip(line, "Install:"))
+ request = &install;
+ else if (LineStartsWithAndStrip(line, "Remove:"))
+ request = &remove;
+ else if (ReadFlag(flags, line, "Upgrade:", (Request::UPGRADE_ALL | Request::FORBID_REMOVE | Request::FORBID_NEW_INSTALL)) ||
+ ReadFlag(flags, line, "Dist-Upgrade:", Request::UPGRADE_ALL) ||
+ ReadFlag(flags, line, "Upgrade-All:", Request::UPGRADE_ALL) ||
+ ReadFlag(flags, line, "Forbid-New-Install:", Request::FORBID_NEW_INSTALL) ||
+ ReadFlag(flags, line, "Forbid-Remove:", Request::FORBID_REMOVE) ||
+ ReadFlag(flags, line, "Autoremove:", Request::AUTOREMOVE))
+ ;
+ else if (LineStartsWithAndStrip(line, "Architecture:"))
+ _config->Set("APT::Architecture", line);
+ else if (LineStartsWithAndStrip(line, "Architectures:"))
+ _config->Set("APT::Architectures", SubstVar(line, " ", ","));
+ else if (LineStartsWithAndStrip(line, "Machine-ID"))
+ _config->Set("APT::Machine-ID", line);
+ else if (LineStartsWithAndStrip(line, "Solver:"))
+ ; // purely informational line
+ else
+ _error->Warning("Unknown line in EDSP Request stanza: %s", line.c_str());
+
+ if (request == NULL)
+ continue;
+ auto const pkgs = VectorizeString(line, ' ');
+ std::move(pkgs.begin(), pkgs.end(), std::back_inserter(*request));
+ }
+ }
+ return false;
+} /*}}}*/
+// EDSP::ApplyRequest - first stanza from the given file descriptor /*{{{*/
+bool EDSP::ApplyRequest(std::list<std::string> const &install,
+ std::list<std::string> const &remove,
+ pkgDepCache &Cache)
+{
+ for (std::list<std::string>::const_iterator i = install.begin();
+ i != install.end(); ++i) {
+ pkgCache::PkgIterator P = Cache.FindPkg(*i);
+ if (P.end() == true)
+ _error->Warning("Package %s is not known, so can't be installed", i->c_str());
+ else
+ Cache.MarkInstall(P, false);
+ }
+
+ for (std::list<std::string>::const_iterator i = remove.begin();
+ i != remove.end(); ++i) {
+ pkgCache::PkgIterator P = Cache.FindPkg(*i);
+ if (P.end() == true)
+ _error->Warning("Package %s is not known, so can't be installed", i->c_str());
+ else
+ Cache.MarkDelete(P);
+ }
+ return true;
+}
+ /*}}}*/
+// EDSP::WriteSolutionStanza - to the given file descriptor /*{{{*/
+bool EDSP::WriteSolutionStanza(FileFd &output, char const * const Type, pkgCache::VerIterator const &Ver)
+{
+ bool Okay = output.Failed() == false;
+ WriteOkay(Okay, output, Type, ": ", _system->GetVersionMapping(Ver->ID));
+ if (_config->FindB("Debug::EDSP::WriteSolution", false) == true)
+ WriteOkay(Okay, output, "\nPackage: ", Ver.ParentPkg().FullName(), "\nVersion: ", Ver.VerStr());
+ return WriteOkay(Okay, output, "\n\n");
+}
+ /*}}}*/
+// EDSP::WriteProgess - pulse to the given file descriptor /*{{{*/
+bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FileFd &output) {
+ return WriteOkay(output, "Progress: ", TimeRFC1123(time(NULL), true), "\n",
+ "Percentage: ", percent, "\n",
+ "Message: ", message, "\n\n") && output.Flush();
+}
+ /*}}}*/
+// EDSP::WriteError - format an error message to be send to file descriptor /*{{{*/
+static std::string formatMessage(std::string const &msg)
+{
+ return SubstVar(SubstVar(APT::String::Strip(msg), "\n\n", "\n.\n"), "\n", "\n ");
+}
+bool EDSP::WriteError(char const * const uuid, std::string const &message, FileFd &output) {
+ return WriteOkay(output, "Error: ", uuid, "\n",
+ "Message: ", formatMessage(message),
+ "\n\n");
+}
+ /*}}}*/
+static std::string findExecutable(std::vector<std::string> const &dirs, char const * const binary) {/*{{{*/
+ for (auto && dir : dirs) {
+ std::string const file = flCombine(dir, binary);
+ if (RealFileExists(file) == true)
+ return file;
+ }
+ return "";
+}
+ /*}}}*/
+static pid_t ExecuteExternal(char const* const type, char const * const binary, char const * const configdir, int * const solver_in, int * const solver_out) {/*{{{*/
+ auto const solverDirs = _config->FindVector(configdir);
+ auto const file = findExecutable(solverDirs, binary);
+ std::string dumper;
+ {
+ dumper = findExecutable(solverDirs, "apt-dump-solver");
+ if (dumper.empty())
+ dumper = findExecutable(solverDirs, "dump");
+ }
+
+ if (file.empty() == true)
+ {
+ _error->Error("Can't call external %s '%s' as it is not in a configured directory!", type, binary);
+ return 0;
+ }
+ int external[4] = {-1, -1, -1, -1};
+ if (pipe(external) != 0 || pipe(external + 2) != 0)
+ {
+ _error->Errno("Resolve", "Can't create needed IPC pipes for EDSP");
+ return 0;
+ }
+ for (int i = 0; i < 4; ++i)
+ SetCloseExec(external[i], true);
+
+ pid_t Solver = ExecFork();
+ if (Solver == 0) {
+ dup2(external[0], STDIN_FILENO);
+ dup2(external[3], STDOUT_FILENO);
+ auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str());
+ auto const dumpdir = flNotFile(dumpfile);
+ auto const runasuser = _config->Find(std::string("APT::") + type + "::" + binary + "::RunAsUser",
+ _config->Find(std::string("APT::") + type + "::RunAsUser",
+ _config->Find("APT::Sandbox::User")));
+ if (dumper.empty() || dumpfile.empty() || dumper == file || CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false)
+ {
+ _config->Set("APT::Sandbox::User", runasuser);
+ DropPrivileges();
+ char const * const calling[] = { file.c_str(), nullptr };
+ execv(calling[0], const_cast<char**>(calling));
+ }
+ else
+ {
+ char const * const calling[] = { dumper.c_str(), "--user", runasuser.c_str(), dumpfile.c_str(), file.c_str(), nullptr };
+ execv(calling[0], const_cast<char**>(calling));
+ }
+ std::cerr << "Failed to execute " << type << " '" << binary << "'!" << std::endl;
+ _exit(100);
+ }
+ close(external[0]);
+ close(external[3]);
+
+ if (WaitFd(external[1], true, 5) == false)
+ {
+ _error->Errno("Resolve", "Timed out while Waiting on availability of %s stdin", type);
+ return 0;
+ }
+
+ *solver_in = external[1];
+ *solver_out = external[2];
+ return Solver;
+}
+ /*}}}*/
+// EDSP::ExecuteSolver - fork requested solver and setup ipc pipes {{{*/
+pid_t EDSP::ExecuteSolver(const char* const solver, int * const solver_in, int * const solver_out, bool) {
+ return ExecuteExternal("solver", solver, "Dir::Bin::Solvers", solver_in, solver_out);
+}
+ /*}}}*/
+static bool CreateDumpFile(char const * const id, char const * const type, FileFd &output)/*{{{*/
+{
+ auto const dumpfile = _config->FindFile((std::string("Dir::Log::") + type).c_str());
+ if (dumpfile.empty())
+ return false;
+ auto const dumpdir = flNotFile(dumpfile);
+ _error->PushToStack();
+ bool errored_out = CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false ||
+ output.Open(dumpfile, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false;
+ std::vector<std::string> downgrademsgs;
+ while (_error->empty() == false)
+ {
+ std::string msg;
+ _error->PopMessage(msg);
+ downgrademsgs.emplace_back(std::move(msg));
+ }
+ _error->RevertToStack();
+ for (auto && msg : downgrademsgs)
+ _error->Warning("%s", msg.c_str());
+ if (errored_out)
+ return _error->WarningE(id, _("Could not open file '%s'"), dumpfile.c_str());
+ return true;
+}
+ /*}}}*/
+// EDSP::ResolveExternal - resolve problems by asking external for help {{{*/
+bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache,
+ unsigned int const flags, OpProgress *Progress) {
+ if (strcmp(solver, "internal") == 0)
+ {
+ FileFd output;
+ bool Okay = CreateDumpFile("EDSP::Resolve", "solver", output);
+ Okay &= EDSP::WriteRequest(Cache, output, flags, nullptr);
+ return Okay && EDSP::WriteScenario(Cache, output, nullptr);
+ }
+ _error->PushToStack();
+ int solver_in, solver_out;
+ pid_t const solver_pid = ExecuteSolver(solver, &solver_in, &solver_out, true);
+ if (solver_pid == 0)
+ return false;
+
+ FileFd output;
+ if (output.OpenDescriptor(solver_in, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
+ return _error->Errno("ResolveExternal", "Opening solver %s stdin on fd %d for writing failed", solver, solver_in);
+
+ bool Okay = output.Failed() == false;
+ if (Okay && Progress != NULL)
+ Progress->OverallProgress(0, 100, 5, _("Execute external solver"));
+ Okay &= EDSP::WriteRequest(Cache, output, flags, Progress);
+ if (Okay && Progress != NULL)
+ Progress->OverallProgress(5, 100, 20, _("Execute external solver"));
+ Okay &= EDSP::WriteScenario(Cache, output, Progress);
+ output.Close();
+
+ if (Okay && Progress != NULL)
+ Progress->OverallProgress(25, 100, 75, _("Execute external solver"));
+ bool const ret = EDSP::ReadResponse(solver_out, Cache, Progress);
+ _error->MergeWithStack();
+ if (ExecWait(solver_pid, solver))
+ return ret;
+ return false;
+} /*}}}*/
+
+bool EIPP::OrderInstall(char const * const solver, pkgPackageManager * const PM, /*{{{*/
+ unsigned int const flags, OpProgress * const Progress)
+{
+ if (strcmp(solver, "internal") == 0)
+ {
+ FileFd output;
+ _error->PushToStack();
+ bool Okay = CreateDumpFile("EIPP::OrderInstall", "planner", output);
+ if (Okay == false && dynamic_cast<pkgSimulate*>(PM) != nullptr)
+ {
+ _error->RevertToStack();
+ return false;
+ }
+ _error->MergeWithStack();
+ Okay &= EIPP::WriteRequest(PM->Cache, output, flags, nullptr);
+ return Okay && EIPP::WriteScenario(PM->Cache, output, nullptr);
+ }
+ _error->PushToStack();
+ int solver_in, solver_out;
+ pid_t const solver_pid = ExecuteExternal("planner", solver, "Dir::Bin::Planners", &solver_in, &solver_out);
+ if (solver_pid == 0)
+ return false;
+
+ FileFd output;
+ if (output.OpenDescriptor(solver_in, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
+ return _error->Errno("EIPP::OrderInstall", "Opening planner %s stdin on fd %d for writing failed", solver, solver_in);
+
+ bool Okay = output.Failed() == false;
+ if (Okay && Progress != NULL)
+ Progress->OverallProgress(0, 100, 5, _("Execute external planner"));
+ Okay &= EIPP::WriteRequest(PM->Cache, output, flags, Progress);
+ if (Okay && Progress != NULL)
+ Progress->OverallProgress(5, 100, 20, _("Execute external planner"));
+ Okay &= EIPP::WriteScenario(PM->Cache, output, Progress);
+ output.Close();
+
+ if (Okay)
+ {
+ if (Progress != nullptr)
+ Progress->OverallProgress(25, 100, 75, _("Execute external planner"));
+
+ // we don't tell the external planners about boring things
+ for (auto Pkg = PM->Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+ {
+ if (Pkg->CurrentState == pkgCache::State::ConfigFiles && PM->Cache[Pkg].Purge() == true)
+ PM->Remove(Pkg, true);
+ }
+ }
+ bool const ret = EIPP::ReadResponse(solver_out, PM, Progress);
+ _error->MergeWithStack();
+ if (ExecWait(solver_pid, solver))
+ return ret;
+ return false;
+}
+ /*}}}*/
+bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output, /*{{{*/
+ unsigned int const flags,
+ OpProgress * const Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planner"));
+ decltype(Cache.Head().PackageCount) p = 0;
+ string del, inst, reinst;
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg, ++p)
+ {
+ if (Progress != NULL && p % 100 == 0)
+ Progress->Progress(p);
+ string* req;
+ pkgDepCache::StateCache &P = Cache[Pkg];
+ if (P.Purge() == true && Pkg->CurrentState == pkgCache::State::ConfigFiles)
+ continue;
+ if (P.Delete() == true)
+ req = &del;
+ else if (P.NewInstall() == true || P.Upgrade() == true || P.Downgrade() == true)
+ req = &inst;
+ else if (P.ReInstall() == true)
+ req = &reinst;
+ else
+ continue;
+ req->append(" ").append(Pkg.FullName());
+ }
+
+ bool Okay = WriteGenericRequestHeaders(output, "Request: EIPP 0.1\n");
+ if (del.empty() == false)
+ WriteOkay(Okay, output, "Remove:", del, "\n");
+ if (inst.empty() == false)
+ WriteOkay(Okay, output, "Install:", inst, "\n");
+ if (reinst.empty() == false)
+ WriteOkay(Okay, output, "ReInstall:", reinst, "\n");
+ WriteOkay(Okay, output, "Planner: ", _config->Find("APT::Planner", "internal"), "\n");
+ if ((flags & Request::IMMEDIATE_CONFIGURATION_ALL) != 0)
+ WriteOkay(Okay, output, "Immediate-Configuration: yes\n");
+ else if ((flags & Request::NO_IMMEDIATE_CONFIGURATION) != 0)
+ WriteOkay(Okay, output, "Immediate-Configuration: no\n");
+ else if ((flags & Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS) != 0)
+ WriteOkay(Okay, output, "Allow-Temporary-Remove-of-Essentials: yes\n");
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+static bool WriteScenarioEIPPVersion(pkgDepCache &, FileFd &output, pkgCache::PkgIterator const &Pkg,/*{{{*/
+ pkgCache::VerIterator const &Ver)
+{
+ bool Okay = true;
+ if (Pkg.CurrentVer() == Ver)
+ switch (Pkg->CurrentState)
+ {
+ case pkgCache::State::NotInstalled: WriteOkay(Okay, output, "\nStatus: not-installed"); break;
+ case pkgCache::State::ConfigFiles: WriteOkay(Okay, output, "\nStatus: config-files"); break;
+ case pkgCache::State::HalfInstalled: WriteOkay(Okay, output, "\nStatus: half-installed"); break;
+ case pkgCache::State::UnPacked: WriteOkay(Okay, output, "\nStatus: unpacked"); break;
+ case pkgCache::State::HalfConfigured: WriteOkay(Okay, output, "\nStatus: half-configured"); break;
+ case pkgCache::State::TriggersAwaited: WriteOkay(Okay, output, "\nStatus: triggers-awaited"); break;
+ case pkgCache::State::TriggersPending: WriteOkay(Okay, output, "\nStatus: triggers-pending"); break;
+ case pkgCache::State::Installed: WriteOkay(Okay, output, "\nStatus: installed"); break;
+ }
+ return Okay;
+}
+ /*}}}*/
+// EIPP::WriteScenario - to the given file descriptor /*{{{*/
+template<typename forVersion> void forAllInterestingVersions(pkgDepCache &Cache, pkgCache::PkgIterator const &Pkg, forVersion const &func)
+{
+ if (Pkg->CurrentState == pkgCache::State::NotInstalled)
+ {
+ auto P = Cache[Pkg];
+ if (P.Install() == false)
+ return;
+ func(Pkg, P.InstVerIter(Cache));
+ }
+ else
+ {
+ if (Pkg->CurrentVer != 0)
+ func(Pkg, Pkg.CurrentVer());
+ auto P = Cache[Pkg];
+ auto const V = P.InstVerIter(Cache);
+ if (P.Delete() == false && Pkg.CurrentVer() != V)
+ func(Pkg, V);
+ }
+}
+
+bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planner"));
+ decltype(Cache.Head().PackageCount) p = 0;
+ bool Okay = output.Failed() == false;
+ std::vector<bool> pkgset(Cache.Head().PackageCount, false);
+ auto const MarkVersion = [&](pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver) {
+ pkgset[Pkg->ID] = true;
+ for (auto D = Ver.DependsList(); D.end() == false; ++D)
+ {
+ if (D.IsCritical() == false)
+ continue;
+ auto const P = D.TargetPkg();
+ for (auto Prv = P.ProvidesList(); Prv.end() == false; ++Prv)
+ {
+ auto const V = Prv.OwnerVer();
+ auto const PV = V.ParentPkg();
+ if (V == PV.CurrentVer() || V == Cache[PV].InstVerIter(Cache))
+ pkgset[PV->ID] = true;
+ }
+ pkgset[P->ID] = true;
+ if (strcmp(P.Arch(), "any") == 0)
+ {
+ APT::StringView const pkgname(P.Name());
+ auto const idxColon = pkgname.find(':');
+ if (idxColon != APT::StringView::npos)
+ {
+ pkgCache::PkgIterator PA;
+ if (pkgname.substr(idxColon + 1) == "any")
+ {
+ auto const GA = Cache.FindGrp(pkgname.substr(0, idxColon).to_string());
+ for (auto PA = GA.PackageList(); PA.end() == false; PA = GA.NextPkg(PA))
+ {
+ pkgset[PA->ID] = true;
+ }
+ }
+ else
+ {
+ auto const PA = Cache.FindPkg(pkgname.to_string());
+ if (PA.end() == false)
+ pkgset[PA->ID] = true;
+ }
+ }
+ }
+ else
+ {
+ auto const PA = Cache.FindPkg(P.FullName(false), "any");
+ if (PA.end() == false)
+ pkgset[PA->ID] = true;
+ }
+ }
+ };
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+ forAllInterestingVersions(Cache, Pkg, MarkVersion);
+ auto const WriteVersion = [&](pkgCache::PkgIterator const &Pkg, pkgCache::VerIterator const &Ver) {
+ Okay &= WriteScenarioVersion(output, Pkg, Ver);
+ Okay &= WriteScenarioEIPPVersion(Cache, output, Pkg, Ver);
+ Okay &= WriteScenarioLimitedDependency(output, Ver, pkgset, true);
+ WriteOkay(Okay, output, "\n");
+ if (Progress != NULL && p % 100 == 0)
+ Progress->Progress(p);
+ };
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg, ++p)
+ {
+ if (pkgset[Pkg->ID] == false || Pkg->VersionList == 0)
+ continue;
+ forAllInterestingVersions(Cache, Pkg, WriteVersion);
+ }
+ return Okay;
+}
+ /*}}}*/
+// EIPP::ReadResponse - from the given file descriptor /*{{{*/
+bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM, OpProgress *Progress) {
+ /* We build an map id to mmap offset here
+ In theory we could use the offset as ID, but then VersionCount
+ couldn't be used to create other versionmappings anymore and it
+ would be too easy for a (buggy) solver to segfault APT… */
+ auto VersionCount = PM->Cache.Head().VersionCount;
+ decltype(VersionCount) VerIdx[VersionCount];
+ for (pkgCache::PkgIterator P = PM->Cache.PkgBegin(); P.end() == false; ++P) {
+ for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V)
+ VerIdx[V->ID] = V.Index();
+ }
+
+ FileFd in;
+ in.OpenDescriptor(input, FileFd::ReadOnly);
+ pkgTagFile response(&in, 100);
+ pkgTagSection section;
+
+ while (response.Step(section) == true) {
+ char const * type = nullptr;
+ if (section.Exists("Progress") == true) {
+ if (Progress != NULL) {
+ string msg = section.FindS("Message");
+ if (msg.empty() == true)
+ msg = _("Prepare for receiving solution");
+ Progress->SubProgress(100, msg, section.FindI("Percentage", 0));
+ }
+ continue;
+ } else if (section.Exists("Error") == true) {
+ if (_error->PendingError()) {
+ if (Progress != nullptr)
+ Progress->Done();
+ Progress = nullptr;
+ _error->DumpErrors(std::cerr, GlobalError::DEBUG, false);
+ }
+ std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n");
+ if (msg.empty() == true) {
+ msg = _("External planner failed without a proper error message");
+ _error->Error("%s", msg.c_str());
+ } else
+ _error->Error("External planner failed with: %s", msg.substr(0,msg.find('\n')).c_str());
+ if (Progress != nullptr)
+ Progress->Done();
+ std::cerr << "The planner encountered an error of type: " << section.FindS("Error") << std::endl;
+ std::cerr << "The following information might help you to understand what is wrong:" << std::endl;
+ std::cerr << msg << std::endl << std::endl;
+ return false;
+ } else if (section.Exists("Unpack") == true)
+ type = "Unpack";
+ else if (section.Exists("Configure") == true)
+ type = "Configure";
+ else if (section.Exists("Remove") == true)
+ type = "Remove";
+ else {
+ char const *Start, *End;
+ section.GetSection(Start, End);
+ _error->Warning("Encountered an unexpected section with %d fields: %s", section.Count(), std::string(Start, End).c_str());
+ continue;
+ }
+
+ if (type == nullptr)
+ continue;
+ decltype(VersionCount) const id = section.FindULL(type, VersionCount);
+ if (id == VersionCount) {
+ _error->Warning("Unable to parse %s request with id value '%s'!", type, section.FindS(type).c_str());
+ continue;
+ } else if (id > VersionCount) {
+ _error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type).c_str(), type);
+ continue;
+ }
+
+ pkgCache::VerIterator Ver(PM->Cache.GetCache(), PM->Cache.GetCache().VerP + VerIdx[id]);
+ auto const Pkg = Ver.ParentPkg();
+ if (strcmp(type, "Unpack") == 0)
+ PM->Install(Pkg, PM->FileNames[Pkg->ID]);
+ else if (strcmp(type, "Configure") == 0)
+ PM->Configure(Pkg);
+ else if (strcmp(type, "Remove") == 0)
+ PM->Remove(Pkg, PM->Cache[Pkg].Purge());
+ }
+ return in.Failed() == false;
+}
+ /*}}}*/
+bool EIPP::ReadRequest(int const input, std::list<std::pair<std::string,PKG_ACTION>> &actions,/*{{{*/
+ unsigned int &flags)
+{
+ actions.clear();
+ flags = 0;
+ std::string line;
+ while (ReadLine(input, line) == true)
+ {
+ // Skip empty lines before request
+ if (line.empty() == true)
+ continue;
+ // The first Tag must be a request, so search for it
+ if (line.compare(0, 8, "Request:") != 0)
+ continue;
+
+ while (ReadLine(input, line) == true)
+ {
+ // empty lines are the end of the request
+ if (line.empty() == true)
+ return true;
+
+ PKG_ACTION pkgact = PKG_ACTION::NOOP;
+ if (LineStartsWithAndStrip(line, "Install:"))
+ pkgact = PKG_ACTION::INSTALL;
+ else if (LineStartsWithAndStrip(line, "ReInstall:"))
+ pkgact = PKG_ACTION::REINSTALL;
+ else if (LineStartsWithAndStrip(line, "Remove:"))
+ pkgact = PKG_ACTION::REMOVE;
+ else if (LineStartsWithAndStrip(line, "Architecture:"))
+ _config->Set("APT::Architecture", line);
+ else if (LineStartsWithAndStrip(line, "Architectures:"))
+ _config->Set("APT::Architectures", SubstVar(line, " ", ","));
+ else if (LineStartsWithAndStrip(line, "Planner:"))
+ ; // purely informational line
+ else if (LineStartsWithAndStrip(line, "Immediate-Configuration:"))
+ {
+ if (localStringToBool(line, true))
+ flags |= Request::IMMEDIATE_CONFIGURATION_ALL;
+ else
+ flags |= Request::NO_IMMEDIATE_CONFIGURATION;
+ }
+ else if (ReadFlag(flags, line, "Allow-Temporary-Remove-of-Essentials:", Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS))
+ ;
+ else
+ _error->Warning("Unknown line in EIPP Request stanza: %s", line.c_str());
+
+ if (pkgact == PKG_ACTION::NOOP)
+ continue;
+ for (auto && p: VectorizeString(line, ' '))
+ actions.emplace_back(std::move(p), pkgact);
+ }
+ }
+ return false;
+}
+ /*}}}*/
+bool EIPP::ApplyRequest(std::list<std::pair<std::string,PKG_ACTION>> &actions,/*{{{*/
+ pkgDepCache &Cache)
+{
+ for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
+ {
+ short versions = 0;
+ for (auto Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
+ {
+ ++versions;
+ if (Pkg.CurrentVer() == Ver)
+ continue;
+ Cache.SetCandidateVersion(Ver);
+ }
+ if (unlikely(versions > 2))
+ _error->Warning("Package %s has %d versions, but should have at most 2!", Pkg.FullName().c_str(), versions);
+ }
+ for (auto && a: actions)
+ {
+ pkgCache::PkgIterator P = Cache.FindPkg(a.first);
+ if (P.end() == true)
+ {
+ _error->Warning("Package %s is not known, so can't be acted on", a.first.c_str());
+ continue;
+ }
+ switch (a.second)
+ {
+ case PKG_ACTION::NOOP:
+ _error->Warning("Package %s has NOOP as action?!?", a.first.c_str());
+ break;
+ case PKG_ACTION::INSTALL:
+ Cache.MarkInstall(P, false);
+ break;
+ case PKG_ACTION::REINSTALL:
+ Cache.MarkInstall(P, false);
+ Cache.SetReInstall(P, true);
+ break;
+ case PKG_ACTION::REMOVE:
+ Cache.MarkDelete(P);
+ break;
+ }
+ }
+ return true;
+}
+ /*}}}*/
diff --git a/apt-pkg/edsp.h b/apt-pkg/edsp.h
new file mode 100644
index 0000000..434010d
--- /dev/null
+++ b/apt-pkg/edsp.h
@@ -0,0 +1,252 @@
+// -*- mode: cpp; mode: fold -*-
+/** Description \file edsp.h {{{
+ ######################################################################
+ Set of methods to help writing and reading everything needed for EDSP
+ with the notable exception of reading a scenario for conversion into
+ a Cache as this is handled by edsp interface for listparser and friends
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EDSP_H
+#define PKGLIB_EDSP_H
+
+#include <apt-pkg/cacheset.h>
+#include <apt-pkg/macros.h>
+#include <apt-pkg/pkgcache.h>
+
+#include <stdio.h>
+
+#include <list>
+#include <string>
+#include <vector>
+
+
+class pkgDepCache;
+class OpProgress;
+
+namespace EDSP /*{{{*/
+{
+ namespace Request
+ {
+ enum Flags
+ {
+ AUTOREMOVE = (1 << 0), /*!< removal of unneeded packages should be performed */
+ UPGRADE_ALL = (1 << 1), /*!< upgrade all installed packages, like 'apt-get full-upgrade' without forbid flags */
+ FORBID_NEW_INSTALL = (1 << 2), /*!< forbid the resolver to install new packages */
+ FORBID_REMOVE = (1 << 3), /*!< forbid the resolver to remove packages */
+ };
+ }
+ /** \brief creates the EDSP request stanza
+ *
+ * In the EDSP protocol the first thing send to the resolver is a stanza
+ * encoding the request. This method will write this stanza by looking at
+ * the given Cache and requests the installation of all packages which were
+ * marked for installation in it (equally for remove).
+ *
+ * \param Cache in which the request is encoded
+ * \param output is written to this "file"
+ * \param flags effecting the request documented in #EDSP::Request::Flags
+ * \param Progress is an instance to report progress to
+ *
+ * \return true if request was composed successfully, otherwise false
+ */
+ APT_PUBLIC bool WriteRequest(pkgDepCache &Cache, FileFd &output,
+ unsigned int const flags = 0,
+ OpProgress *Progress = NULL);
+
+ /** \brief creates the scenario representing the package universe
+ *
+ * After the request all known information about a package are send
+ * to the solver. The output looks similar to a Packages or status file
+ *
+ * All packages and version included in this Cache are send, even if
+ * it doesn't make sense from an APT resolver point of view like versions
+ * with a negative pin to enable the solver to propose even that as a
+ * solution or at least to be able to give a hint what can be done to
+ * satisfy a request.
+ *
+ * \param Cache is the known package universe
+ * \param output is written to this "file"
+ * \param Progress is an instance to report progress to
+ *
+ * \return true if universe was composed successfully, otherwise false
+ */
+ APT_PUBLIC bool WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress *Progress = NULL);
+
+ /** \brief creates a limited scenario representing the package universe
+ *
+ * This method works similar to #WriteScenario as it works in the same
+ * way but doesn't send the complete universe to the solver but only
+ * packages included in the pkgset which will have only dependencies
+ * on packages which are in the given set. All other dependencies will
+ * be removed, so that this method can be used to create testcases
+ *
+ * \param Cache is the known package universe
+ * \param output is written to this "file"
+ * \param pkgset is a set of packages the universe should be limited to
+ * \param Progress is an instance to report progress to
+ *
+ * \return true if universe was composed successfully, otherwise false
+ */
+ APT_PUBLIC bool WriteLimitedScenario(pkgDepCache &Cache, FileFd &output,
+ std::vector<bool> const &pkgset,
+ OpProgress *Progress = NULL);
+
+ /** \brief waits and acts on the information returned from the solver
+ *
+ * This method takes care of interpreting whatever the solver sends
+ * through the standard output like a solution, progress or an error.
+ * The main thread should hand his control over to this method to
+ * wait for the solver to finish the given task. The file descriptor
+ * used as input is completely consumed and closed by the method.
+ *
+ * \param input file descriptor with the response from the solver
+ * \param Cache the solution should be applied on if any
+ * \param Progress is an instance to report progress to
+ *
+ * \return true if a solution is found and applied correctly, otherwise false
+ */
+ APT_PUBLIC bool ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progress = NULL);
+
+ /** \brief search and read the request stanza for action later
+ *
+ * This method while ignore the input up to the point it finds the
+ * Request: line as an indicator for the Request stanza.
+ * The request is stored in the parameters install and remove then,
+ * as the cache isn't build yet as the scenario follows the request.
+ *
+ * \param input file descriptor with the edsp input for the solver
+ * \param[out] install is a list which gets populated with requested installs
+ * \param[out] remove is a list which gets populated with requested removals
+ * \param[out] upgrade is true if it is a request like apt-get upgrade
+ * \param[out] distUpgrade is true if it is a request like apt-get dist-upgrade
+ * \param[out] autoRemove is true if removal of unneeded packages should be performed
+ *
+ * \return true if the request could be found and worked on, otherwise false
+ */
+ APT_PUBLIC bool ReadRequest(int const input, std::list<std::string> &install,
+ std::list<std::string> &remove, unsigned int &flags);
+
+ /** \brief takes the request lists and applies it on the cache
+ *
+ * The lists as created by #ReadRequest will be used to find the
+ * packages in question and mark them for install/remove.
+ * No solving is done and no auto-install/-remove.
+ *
+ * \param install is a list of packages to mark for installation
+ * \param remove is a list of packages to mark for removal
+ * \param Cache is there the markers should be set
+ *
+ * \return false if the request couldn't be applied, true otherwise
+ */
+ APT_PUBLIC bool ApplyRequest(std::list<std::string> const &install,
+ std::list<std::string> const &remove,
+ pkgDepCache &Cache);
+
+ /** \brief formats a solution stanza for the given version
+ *
+ * EDSP uses a simple format for reporting solutions:
+ * A single required field name with an ID as value.
+ * Additional fields might appear as debug aids.
+ *
+ * \param output to write the stanza forming the solution to
+ * \param Type of the stanza, used as field name
+ * \param Ver this stanza applies to
+ *
+ * \return true if stanza could be written, otherwise false
+ */
+ APT_PUBLIC bool WriteSolutionStanza(FileFd &output, char const * const Type, pkgCache::VerIterator const &Ver);
+
+ /** \brief sends a progress report
+ *
+ * \param percent of the solving completed
+ * \param message the solver wants the user to see
+ * \param output the front-end listens for progress report
+ */
+ APT_PUBLIC bool WriteProgress(unsigned short const percent, const char* const message, FileFd &output);
+
+ /** \brief sends an error report
+ *
+ * Solvers are expected to execute successfully even if
+ * they were unable to calculate a solution for a given task.
+ * Obviously they can't send a solution through, so this
+ * methods deals with formatting an error message correctly
+ * so that the front-ends can receive and display it.
+ *
+ * The first line of the message should be a short description
+ * of the error so it can be used for dialog titles or alike
+ *
+ * \param uuid of this error message
+ * \param message is free form text to describe the error
+ * \param output the front-end listens for error messages
+ */
+ APT_PUBLIC bool WriteError(char const * const uuid, std::string const &message, FileFd &output);
+
+
+ /** \brief executes the given solver and returns the pipe ends
+ *
+ * The given solver is executed if it can be found in one of the
+ * configured directories and setup for it is performed.
+ *
+ * \param solver to execute
+ * \param[out] solver_in will be the stdin of the solver
+ * \param[out] solver_out will be the stdout of the solver
+ *
+ * \return PID of the started solver or 0 if failure occurred
+ */
+ APT_PUBLIC pid_t ExecuteSolver(const char* const solver, int * const solver_in, int * const solver_out, bool /*overload*/);
+
+ /** \brief call an external resolver to handle the request
+ *
+ * This method wraps all the methods above to call an external solver
+ *
+ * \param solver to execute
+ * \param Cache with the problem and as universe to work in
+ * \param flags effecting the request documented in #EDSP::Request::Flags
+ * \param Progress is an instance to report progress to
+ *
+ * \return true if the solver has successfully solved the problem,
+ * otherwise false
+ */
+ APT_PUBLIC bool ResolveExternal(const char* const solver, pkgDepCache &Cache,
+ unsigned int const flags = 0,
+ OpProgress *Progress = NULL);
+}
+ /*}}}*/
+class pkgPackageManager;
+namespace EIPP /*{{{*/
+{
+ namespace Request
+ {
+ enum Flags
+ {
+ IMMEDIATE_CONFIGURATION_ALL = (1 << 0), /*!< try to keep the least amount of packages unconfigured as possible at all times */
+ NO_IMMEDIATE_CONFIGURATION = (1 << 1), /*!< do not perform immediate configuration at all */
+ ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS = (1 << 2), /*!< just as the name suggests, very special case and dangerous! */
+ };
+ }
+
+ APT_HIDDEN bool WriteRequest(pkgDepCache &Cache, FileFd &output,
+ unsigned int const flags, OpProgress * const Progress);
+ APT_HIDDEN bool WriteScenario(pkgDepCache &Cache, FileFd &output,
+ OpProgress * const Progress);
+
+ APT_HIDDEN bool OrderInstall(char const * const planner, pkgPackageManager * const PM,
+ unsigned int const version, OpProgress * const Progress);
+ APT_HIDDEN bool ReadResponse(int const input, pkgPackageManager * const PM,
+ OpProgress * const Progress);
+
+ enum class PKG_ACTION
+ {
+ NOOP,
+ INSTALL,
+ REINSTALL,
+ REMOVE
+ };
+ APT_PUBLIC bool ReadRequest(int const input,
+ std::list<std::pair<std::string,PKG_ACTION>> &actions,
+ unsigned int &flags);
+ APT_PUBLIC bool ApplyRequest(std::list<std::pair<std::string,PKG_ACTION>> &actions,
+ pkgDepCache &Cache);
+}
+ /*}}}*/
+#endif
diff --git a/apt-pkg/edsp/edspindexfile.cc b/apt-pkg/edsp/edspindexfile.cc
new file mode 100644
index 0000000..faade6e
--- /dev/null
+++ b/apt-pkg/edsp/edspindexfile.cc
@@ -0,0 +1,129 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+ The scenario file is designed to work as an intermediate file between
+ APT and the resolver. Its on propose very similar to a dpkg status file
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/edspindexfile.h>
+#include <apt-pkg/edsplistparser.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/indexfile.h>
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/pkgrecords.h>
+
+#include <memory>
+#include <string>
+#include <stddef.h>
+#include <unistd.h>
+ /*}}}*/
+
+// EDSP-like Index /*{{{*/
+edspLikeIndex::edspLikeIndex(std::string const &File) : pkgDebianIndexRealFile(File, true)
+{
+}
+std::string edspLikeIndex::GetArchitecture() const
+{
+ return std::string();
+}
+bool edspLikeIndex::HasPackages() const
+{
+ return true;
+}
+bool edspLikeIndex::Exists() const
+{
+ return true;
+}
+uint8_t edspLikeIndex::GetIndexFlags() const
+{
+ return 0;
+}
+bool edspLikeIndex::OpenListFile(FileFd &Pkg, std::string const &FileName)
+{
+ if (FileName.empty() == false && FileName != "/nonexistent/stdin")
+ return pkgDebianIndexRealFile::OpenListFile(Pkg, FileName);
+ if (Pkg.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false)
+ return _error->Error("Problem opening %s",FileName.c_str());
+ return true;
+}
+ /*}}}*/
+// EDSP Index /*{{{*/
+edspIndex::edspIndex(std::string const &File) : edspLikeIndex(File)
+{
+}
+std::string edspIndex::GetComponent() const
+{
+ return "edsp";
+}
+pkgCacheListParser * edspIndex::CreateListParser(FileFd &Pkg)
+{
+ if (Pkg.IsOpen() == false)
+ return nullptr;
+ _error->PushToStack();
+ std::unique_ptr<pkgCacheListParser> Parser(new edspListParser(&Pkg));
+ bool const newError = _error->PendingError();
+ _error->MergeWithStack();
+ return newError ? nullptr : Parser.release();
+}
+ /*}}}*/
+// EIPP Index /*{{{*/
+eippIndex::eippIndex(std::string const &File) : edspLikeIndex(File)
+{
+}
+std::string eippIndex::GetComponent() const
+{
+ return "eipp";
+}
+pkgCacheListParser * eippIndex::CreateListParser(FileFd &Pkg)
+{
+ if (Pkg.IsOpen() == false)
+ return nullptr;
+ _error->PushToStack();
+ std::unique_ptr<pkgCacheListParser> Parser(new eippListParser(&Pkg));
+ bool const newError = _error->PendingError();
+ _error->MergeWithStack();
+ return newError ? nullptr : Parser.release();
+}
+ /*}}}*/
+
+// Index File types for APT /*{{{*/
+class APT_HIDDEN edspIFType: public pkgIndexFile::Type
+{
+ public:
+ virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &) const APT_OVERRIDE
+ {
+ // we don't have a record parser for this type as the file is not persistent
+ return NULL;
+ };
+ edspIFType() {Label = "EDSP scenario file";};
+};
+APT_HIDDEN edspIFType _apt_Edsp;
+const pkgIndexFile::Type *edspIndex::GetType() const
+{
+ return &_apt_Edsp;
+}
+
+class APT_HIDDEN eippIFType: public pkgIndexFile::Type
+{
+ public:
+ virtual pkgRecords::Parser *CreatePkgParser(pkgCache::PkgFileIterator const &) const APT_OVERRIDE
+ {
+ // we don't have a record parser for this type as the file is not persistent
+ return NULL;
+ };
+ eippIFType() {Label = "EIPP scenario file";};
+};
+APT_HIDDEN eippIFType _apt_Eipp;
+const pkgIndexFile::Type *eippIndex::GetType() const
+{
+ return &_apt_Eipp;
+}
+ /*}}}*/
+
+edspLikeIndex::~edspLikeIndex() {}
+edspIndex::~edspIndex() {}
+eippIndex::~eippIndex() {}
diff --git a/apt-pkg/edsp/edspindexfile.h b/apt-pkg/edsp/edspindexfile.h
new file mode 100644
index 0000000..42ef3fe
--- /dev/null
+++ b/apt-pkg/edsp/edspindexfile.h
@@ -0,0 +1,59 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+ The scenario file is designed to work as an intermediate file between
+ APT and the resolver. Its on propose very similar to a dpkg status file
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EDSPINDEXFILE_H
+#define PKGLIB_EDSPINDEXFILE_H
+
+#include <apt-pkg/debindexfile.h>
+#include <string>
+
+
+class OpProgress;
+class pkgCacheGenerator;
+
+class APT_HIDDEN edspLikeIndex : public pkgDebianIndexRealFile
+{
+protected:
+ virtual bool OpenListFile(FileFd &Pkg, std::string const &File) APT_OVERRIDE;
+ virtual uint8_t GetIndexFlags() const APT_OVERRIDE;
+ virtual std::string GetArchitecture() const APT_OVERRIDE;
+
+public:
+ virtual bool Exists() const APT_OVERRIDE;
+ virtual bool HasPackages() const APT_OVERRIDE;
+
+ explicit edspLikeIndex(std::string const &File);
+ virtual ~edspLikeIndex();
+};
+
+class APT_HIDDEN edspIndex : public edspLikeIndex
+{
+protected:
+ APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE;
+ virtual std::string GetComponent() const APT_OVERRIDE;
+
+public:
+ virtual const Type *GetType() const APT_OVERRIDE APT_PURE;
+
+ explicit edspIndex(std::string const &File);
+ virtual ~edspIndex();
+};
+
+class APT_HIDDEN eippIndex : public edspLikeIndex
+{
+protected:
+ APT_HIDDEN virtual pkgCacheListParser * CreateListParser(FileFd &Pkg) APT_OVERRIDE;
+ virtual std::string GetComponent() const APT_OVERRIDE;
+
+public:
+ virtual const Type *GetType() const APT_OVERRIDE APT_PURE;
+
+ explicit eippIndex(std::string const &File);
+ virtual ~eippIndex();
+};
+
+#endif
diff --git a/apt-pkg/edsp/edsplistparser.cc b/apt-pkg/edsp/edsplistparser.cc
new file mode 100644
index 0000000..5419069
--- /dev/null
+++ b/apt-pkg/edsp/edsplistparser.cc
@@ -0,0 +1,177 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ Package Cache Generator - Generator for the cache structure.
+
+ This builds the cache structure from the abstract package list parser.
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/configuration.h>
+#include <apt-pkg/deblistparser.h>
+#include <apt-pkg/edsplistparser.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/pkgsystem.h>
+#include <apt-pkg/string_view.h>
+#include <apt-pkg/strutl.h>
+#include <apt-pkg/tagfile-keys.h>
+#include <apt-pkg/tagfile.h>
+
+#include <array>
+
+ /*}}}*/
+
+// ListParser::edspListParser - Constructor /*{{{*/
+edspLikeListParser::edspLikeListParser(FileFd * const File) : debListParser(File)
+{
+}
+edspListParser::edspListParser(FileFd * const File) : edspLikeListParser(File)
+{
+ std::string const states = _config->FindFile("Dir::State::extended_states");
+ RemoveFile("edspListParserPrivate", states);
+ extendedstates.Open(states, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, 0600);
+ std::string const prefs = _config->FindFile("Dir::Etc::preferences");
+ RemoveFile("edspListParserPrivate", prefs);
+ preferences.Open(prefs, FileFd::WriteOnly | FileFd::Create | FileFd::Exclusive, 0600);
+}
+ /*}}}*/
+// ListParser::NewVersion - Fill in the version structure /*{{{*/
+bool edspLikeListParser::NewVersion(pkgCache::VerIterator &Ver)
+{
+ _system->SetVersionMapping(Ver->ID, Section.FindI("APT-ID", Ver->ID));
+ return debListParser::NewVersion(Ver);
+}
+ /*}}}*/
+// ListParser::Description - Return the description string /*{{{*/
+// ---------------------------------------------------------------------
+/* Sorry, no description for the resolvers… */
+std::vector<std::string> edspLikeListParser::AvailableDescriptionLanguages()
+{
+ return {};
+}
+APT::StringView edspLikeListParser::Description_md5()
+{
+ return APT::StringView();
+}
+ /*}}}*/
+// ListParser::VersionHash - Compute a unique hash for this version /*{{{*/
+uint32_t edspLikeListParser::VersionHash()
+{
+ if (Section.Exists("APT-ID") == true)
+ return Section.FindI("APT-ID");
+ return 0;
+}
+ /*}}}*/
+// ListParser::ParseStatus - Parse the status field /*{{{*/
+// ---------------------------------------------------------------------
+/* The Status: line here is not a normal dpkg one but just one which tells
+ use if the package is installed or not, where missing means not. */
+bool edspListParser::ParseStatus(pkgCache::PkgIterator &Pkg,
+ pkgCache::VerIterator &Ver)
+{
+ unsigned long state = 0;
+ if (Section.FindFlag("Hold",state,pkgCache::State::Hold) == false)
+ return false;
+ if (state != 0)
+ Pkg->SelectedState = pkgCache::State::Hold;
+
+ state = 0;
+ if (Section.FindFlag("Installed",state,pkgCache::State::Installed) == false)
+ return false;
+ if (state != 0)
+ {
+ Pkg->CurrentState = pkgCache::State::Installed;
+ Pkg->CurrentVer = Ver.MapPointer();
+ }
+
+ if (Section.FindB("APT-Automatic", false))
+ {
+ std::string out;
+ strprintf(out, "Package: %s\nArchitecture: %s\nAuto-Installed: 1\n\n", Pkg.Name(), Pkg.Arch());
+ if (extendedstates.Write(out.c_str(), out.length()) == false)
+ return false;
+ }
+
+ // FIXME: Using an overriding pin is wrong.
+ if (Section.FindB("APT-Candidate", false))
+ {
+ std::string out;
+ strprintf(out, "Package: %s\nPin: version %s\nPin-Priority: 9999\n\n", Pkg.FullName().c_str(), Ver.VerStr());
+ if (preferences.Write(out.c_str(), out.length()) == false)
+ return false;
+ }
+
+ signed short const pinvalue = Section.FindI("APT-Pin", 500);
+ if (pinvalue != 500)
+ {
+ std::string out;
+ strprintf(out, "Package: %s\nPin: version %s\nPin-Priority: %d\n\n", Pkg.FullName().c_str(), Ver.VerStr(), pinvalue);
+ if (preferences.Write(out.c_str(), out.length()) == false)
+ return false;
+ }
+
+ return true;
+}
+ /*}}}*/
+
+// ListParser::eippListParser - Constructor /*{{{*/
+eippListParser::eippListParser(FileFd *File) : edspLikeListParser(File)
+{
+}
+ /*}}}*/
+// ListParser::ParseStatus - Parse the status field /*{{{*/
+// ---------------------------------------------------------------------
+/* The Status: line here is not a normal dpkg one but just one which tells
+ use if the package is installed or not, where missing means not. */
+bool eippListParser::ParseStatus(pkgCache::PkgIterator &Pkg,
+ pkgCache::VerIterator &Ver)
+{
+ // Process the flag field
+ static std::array<WordList, 8> const statusvalues = {{
+ {"not-installed",pkgCache::State::NotInstalled},
+ {"config-files",pkgCache::State::ConfigFiles},
+ {"half-installed",pkgCache::State::HalfInstalled},
+ {"unpacked",pkgCache::State::UnPacked},
+ {"half-configured",pkgCache::State::HalfConfigured},
+ {"triggers-awaited",pkgCache::State::TriggersAwaited},
+ {"triggers-pending",pkgCache::State::TriggersPending},
+ {"installed",pkgCache::State::Installed},
+ }};
+ auto const status = Section.Find(pkgTagSection::Key::Status);
+ if (not status.empty())
+ {
+ for (auto && sv: statusvalues)
+ {
+ if (status != sv.Str)
+ continue;
+ Pkg->CurrentState = sv.Val;
+ switch (Pkg->CurrentState)
+ {
+ case pkgCache::State::NotInstalled:
+ case pkgCache::State::ConfigFiles:
+ break;
+ case pkgCache::State::HalfInstalled:
+ case pkgCache::State::UnPacked:
+ case pkgCache::State::HalfConfigured:
+ case pkgCache::State::TriggersAwaited:
+ case pkgCache::State::TriggersPending:
+ case pkgCache::State::Installed:
+ Pkg->CurrentVer = Ver.MapPointer();
+ break;
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+ /*}}}*/
+
+edspLikeListParser::~edspLikeListParser() {}
+edspListParser::~edspListParser() {}
+eippListParser::~eippListParser() {}
diff --git a/apt-pkg/edsp/edsplistparser.h b/apt-pkg/edsp/edsplistparser.h
new file mode 100644
index 0000000..41bfd1f
--- /dev/null
+++ b/apt-pkg/edsp/edsplistparser.h
@@ -0,0 +1,58 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ EDSP Package List Parser - This implements the abstract parser
+ interface for the APT specific intermediate format which is passed
+ to external resolvers
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EDSPLISTPARSER_H
+#define PKGLIB_EDSPLISTPARSER_H
+
+#include <apt-pkg/deblistparser.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/pkgcache.h>
+
+#include <string>
+
+
+namespace APT {
+ class StringView;
+}
+class APT_HIDDEN edspLikeListParser : public debListParser
+{
+ public:
+ virtual bool NewVersion(pkgCache::VerIterator &Ver) APT_OVERRIDE;
+ virtual std::vector<std::string> AvailableDescriptionLanguages() APT_OVERRIDE;
+ virtual APT::StringView Description_md5() APT_OVERRIDE;
+ virtual uint32_t VersionHash() APT_OVERRIDE;
+
+ explicit edspLikeListParser(FileFd *File);
+ virtual ~edspLikeListParser();
+};
+
+class APT_HIDDEN edspListParser : public edspLikeListParser
+{
+ FileFd extendedstates;
+ FileFd preferences;
+
+protected:
+ virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver) APT_OVERRIDE;
+
+public:
+ explicit edspListParser(FileFd *File);
+ virtual ~edspListParser();
+};
+
+class APT_HIDDEN eippListParser : public edspLikeListParser
+{
+protected:
+ virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver) APT_OVERRIDE;
+
+public:
+ explicit eippListParser(FileFd *File);
+ virtual ~eippListParser();
+};
+#endif
diff --git a/apt-pkg/edsp/edspsystem.cc b/apt-pkg/edsp/edspsystem.cc
new file mode 100644
index 0000000..c86f1ed
--- /dev/null
+++ b/apt-pkg/edsp/edspsystem.cc
@@ -0,0 +1,166 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ This system provides the abstraction to use the scenario file as the
+ only source of package information to be able to feed the created file
+ back to APT for its own consumption (eat your own dogfood).
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <config.h>
+
+#include <apt-pkg/configuration.h>
+#include <apt-pkg/debversion.h>
+#include <apt-pkg/edspindexfile.h>
+#include <apt-pkg/edspsystem.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/pkgcache.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+ /*}}}*/
+
+// System::System - Constructor /*{{{*/
+edspLikeSystem::edspLikeSystem(char const * const Label) : pkgSystem(Label, &debVS)
+{
+}
+edspSystem::edspSystem() : edspLikeSystem("Debian APT solver interface")
+{
+}
+eippSystem::eippSystem() : edspLikeSystem("Debian APT planner interface")
+{
+}
+ /*}}}*/
+// System::Lock - Get the lock /*{{{*/
+bool edspLikeSystem::Lock(OpProgress *)
+{
+ return true;
+}
+ /*}}}*/
+// System::UnLock - Drop a lock /*{{{*/
+bool edspLikeSystem::UnLock(bool /*NoErrors*/)
+{
+ return true;
+}
+ /*}}}*/
+// System::CreatePM - Create the underlying package manager /*{{{*/
+// ---------------------------------------------------------------------
+/* we can't use edsp input as input for real installations - just a
+ simulation can work, but everything else will fail bigtime */
+pkgPackageManager *edspLikeSystem::CreatePM(pkgDepCache * /*Cache*/) const
+{
+ return nullptr;
+}
+ /*}}}*/
+// System::Initialize - Setup the configuration space.. /*{{{*/
+bool edspLikeSystem::Initialize(Configuration &Cnf)
+{
+ Cnf.Set("Dir::Log", "/dev/null");
+ // state is included completely in the input files
+ Cnf.Set("Dir::Etc::preferences", "/dev/null");
+ Cnf.Set("Dir::Etc::preferencesparts", "/dev/null");
+ Cnf.Set("Dir::State::status","/dev/null");
+ Cnf.Set("Dir::State::extended_states","/dev/null");
+ Cnf.Set("Dir::State::lists","/dev/null");
+ // do not store an mmap cache
+ Cnf.Set("Dir::Cache::pkgcache", "");
+ Cnf.Set("Dir::Cache::srcpkgcache", "");
+ // the protocols only propose actions, not do them
+ Cnf.Set("Debug::NoLocking", "true");
+ Cnf.Set("APT::Get::Simulate", "true");
+
+ StatusFile.reset(nullptr);
+ return true;
+}
+bool edspSystem::Initialize(Configuration &Cnf)
+{
+ if (edspLikeSystem::Initialize(Cnf) == false)
+ return false;
+ std::string const tmp = GetTempDir();
+ char tmpname[300];
+ snprintf(tmpname, sizeof(tmpname), "%s/apt-edsp-solver-XXXXXX", tmp.c_str());
+ if (nullptr == mkdtemp(tmpname))
+ return false;
+ tempDir = tmpname;
+ tempStatesFile = flCombine(tempDir, "extended_states");
+ Cnf.Set("Dir::State::extended_states", tempStatesFile);
+ tempPrefsFile = flCombine(tempDir, "apt_preferences");
+ Cnf.Set("Dir::Etc::preferences", tempPrefsFile);
+ return true;
+}
+ /*}}}*/
+// System::ArchiveSupported - Is a file format supported /*{{{*/
+bool edspLikeSystem::ArchiveSupported(const char * /*Type*/)
+{
+ return false;
+}
+ /*}}}*/
+// System::Score - Never use the EDSP system automatically /*{{{*/
+signed edspLikeSystem::Score(Configuration const &)
+{
+ return -1000;
+}
+ /*}}}*/
+// System::FindIndex - Get an index file for status files /*{{{*/
+bool edspLikeSystem::FindIndex(pkgCache::PkgFileIterator File,
+ pkgIndexFile *&Found) const
+{
+ if (StatusFile == 0)
+ return false;
+ if (StatusFile->FindInCache(*File.Cache()) == File)
+ {
+ Found = StatusFile.get();
+ return true;
+ }
+
+ return false;
+}
+ /*}}}*/
+bool edspSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List) /*{{{*/
+{
+ if (StatusFile == nullptr)
+ {
+ if (_config->Find("edsp::scenario", "") == "/nonexistent/stdin")
+ StatusFile.reset(new edspIndex("/nonexistent/stdin"));
+ else
+ StatusFile.reset(new edspIndex(_config->FindFile("edsp::scenario")));
+ }
+ List.push_back(StatusFile.get());
+ return true;
+}
+ /*}}}*/
+bool eippSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List) /*{{{*/
+{
+ if (StatusFile == nullptr)
+ {
+ if (_config->Find("eipp::scenario", "") == "/nonexistent/stdin")
+ StatusFile.reset(new eippIndex("/nonexistent/stdin"));
+ else
+ StatusFile.reset(new eippIndex(_config->FindFile("eipp::scenario")));
+ }
+ List.push_back(StatusFile.get());
+ return true;
+}
+ /*}}}*/
+
+edspLikeSystem::~edspLikeSystem() {}
+edspSystem::~edspSystem()
+{
+ if (tempDir.empty())
+ return;
+
+ RemoveFile("~edspSystem", tempStatesFile);
+ RemoveFile("~edspSystem", tempPrefsFile);
+ rmdir(tempDir.c_str());
+}
+eippSystem::~eippSystem() {}
+
+APT_HIDDEN edspSystem edspSys;
+APT_HIDDEN eippSystem eippSys;
diff --git a/apt-pkg/edsp/edspsystem.h b/apt-pkg/edsp/edspsystem.h
new file mode 100644
index 0000000..97c2d66
--- /dev/null
+++ b/apt-pkg/edsp/edspsystem.h
@@ -0,0 +1,75 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+/* ######################################################################
+
+ System - Debian version of the System Class
+
+ ##################################################################### */
+ /*}}}*/
+#ifndef PKGLIB_EDSPSYSTEM_H
+#define PKGLIB_EDSPSYSTEM_H
+
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/pkgsystem.h>
+#include <apt-pkg/error.h>
+
+#include <memory>
+#include <vector>
+
+#include <apt-pkg/macros.h>
+
+class Configuration;
+class pkgDepCache;
+class pkgIndexFile;
+class pkgPackageManager;
+
+class APT_HIDDEN edspLikeSystem : public pkgSystem
+{
+protected:
+ std::unique_ptr<pkgIndexFile> StatusFile;
+
+public:
+ virtual bool Lock(OpProgress * const Progress) APT_OVERRIDE APT_PURE;
+ virtual bool UnLock(bool NoErrors = false) APT_OVERRIDE APT_PURE;
+ virtual pkgPackageManager *CreatePM(pkgDepCache *Cache) const APT_OVERRIDE APT_PURE;
+ virtual bool Initialize(Configuration &Cnf) APT_OVERRIDE;
+ virtual bool ArchiveSupported(const char *Type) APT_OVERRIDE APT_PURE;
+ virtual signed Score(Configuration const &Cnf) APT_OVERRIDE;
+ virtual bool FindIndex(pkgCache::PkgFileIterator File,
+ pkgIndexFile *&Found) const APT_OVERRIDE;
+
+ bool MultiArchSupported() const override { return true; }
+ std::vector<std::string> ArchitecturesSupported() const override { return {}; };
+
+ bool LockInner(OpProgress * const, int) override { return _error->Error("LockInner is not implemented"); };
+ bool UnLockInner(bool) override { return _error->Error("UnLockInner is not implemented"); };
+ bool IsLocked() override { return true; };
+
+ explicit edspLikeSystem(char const * const Label);
+ virtual ~edspLikeSystem();
+};
+
+class APT_HIDDEN edspSystem : public edspLikeSystem
+{
+ std::string tempDir;
+ std::string tempStatesFile;
+ std::string tempPrefsFile;
+
+public:
+ virtual bool Initialize(Configuration &Cnf) APT_OVERRIDE;
+ virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE;
+
+ edspSystem();
+ virtual ~edspSystem();
+};
+
+class APT_HIDDEN eippSystem : public edspLikeSystem
+{
+ public:
+ virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE;
+
+ eippSystem();
+ virtual ~eippSystem();
+};
+
+#endif