summaryrefslogtreecommitdiff
path: root/apt-pkg/edsp.cc
diff options
context:
space:
mode:
authorDavid Kalnischkies <david@kalnischkies.de>2016-05-14 18:07:12 +0200
committerDavid Kalnischkies <david@kalnischkies.de>2016-06-27 11:43:09 +0200
commit7b197262212f49b3b355b1124edf2ba9adb73411 (patch)
treec8ab09827d1e96915d07e127177253dd1aa7c329 /apt-pkg/edsp.cc
parenta523cb7f26e0eae7df8e7ea4c386f954049480aa (diff)
eipp: implement version 0.1 of the protocol
The very first step in introducing the "external installation planer protocol" (short: EIPP) as part of my GSoC2016 project. The description reads: APT-based tools like apt-get, aptitude, synaptic, … work with the user to figure out how their system should look like after they are done installing/removing packages and their dependencies. The actual installation/removal of packages is done by dpkg with the constrain that dependencies must be fulfilled at any point in time (e.g. to run maintainer scripts). Historically APT has a super micro-management approach to this task which hasn't aged that well over the years mostly ignoring changes in dpkg and growing into an unmaintainable mess hardly anyone can debug and everyone fears to touch – especially as more and more requirements are tacked onto it like handling cycles and triggers, dealing with "important" packages first, package sources on removable media, touch minimal groups to be able to interrupt the process if needed (e.g. unattended-upgrades) which not only sky-rocket complexity but also can be mutually exclusive as you e.g. can't have minimal groups and minimal trigger executions at the same time.
Diffstat (limited to 'apt-pkg/edsp.cc')
-rw-r--r--apt-pkg/edsp.cc180
1 files changed, 180 insertions, 0 deletions
diff --git a/apt-pkg/edsp.cc b/apt-pkg/edsp.cc
index 6e0a0fc2f..58982ade2 100644
--- a/apt-pkg/edsp.cc
+++ b/apt-pkg/edsp.cc
@@ -1064,3 +1064,183 @@ bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache,
return ResolveExternal(solver, Cache, flags, Progress);
}
/*}}}*/
+
+bool EIPP::OrderInstall(char const * const solver, pkgDepCache &Cache, /*{{{*/
+ unsigned int const flags, OpProgress * const Progress)
+{
+ int solver_in, solver_out;
+ pid_t const solver_pid = ExecuteExternal("planer", solver, "Dir::Bin::Planers", &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("OrderInstall", "Opening planer %s stdin on fd %d for writing failed", solver, solver_in);
+
+ bool Okay = output.Failed() == false;
+ if (Progress != NULL)
+ Progress->OverallProgress(0, 100, 5, _("Execute external planer"));
+ Okay &= EIPP::WriteRequest(Cache, output, flags, Progress);
+ if (Progress != NULL)
+ Progress->OverallProgress(5, 100, 20, _("Execute external planer"));
+ Okay &= EIPP::WriteScenario(Cache, output, Progress);
+ output.Close();
+
+ if (Progress != NULL)
+ Progress->OverallProgress(25, 100, 75, _("Execute external planer"));
+ if (Okay && EIPP::ReadResponse(solver_out, Cache, Progress) == false)
+ return false;
+
+ return ExecWait(solver_pid, solver);
+}
+ /*}}}*/
+bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output, /*{{{*/
+ unsigned int const flags,
+ OpProgress * const Progress)
+{
+ (void)(flags);
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planer"));
+ unsigned long p = 0;
+ string del, purge, 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)
+ req = &purge;
+ if (P.Delete() == true)
+ req = &del;
+ else if (P.NewInstall() == true || P.Upgrade() == true)
+ req = &inst;
+ else if (P.ReInstall() == true)
+ req = &reinst;
+ else
+ continue;
+ req->append(" ").append(Pkg.FullName());
+ }
+ bool Okay = WriteOkay(output, "Request: EIPP 0.1\n");
+
+ const char *arch = _config->Find("APT::Architecture").c_str();
+ std::vector<string> archs = APT::Configuration::getArchitectures();
+ WriteOkay(Okay, output, "Architecture: ", arch, "\n",
+ "Architectures:");
+ for (std::vector<string>::const_iterator a = archs.begin(); a != archs.end(); ++a)
+ WriteOkay(Okay, output, " ", *a);
+ WriteOkay(Okay, output, "\n");
+
+ if (purge.empty() == false)
+ WriteOkay(Okay, output, "Purge:", purge, "\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, "Planer: ", _config->Find("APT::Planer", "internal"), "\n");
+ return WriteOkay(Okay, output, "\n");
+}
+ /*}}}*/
+static bool WriteScenarioEIPPVersion(pkgDepCache &Cache, 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;
+ }
+ if (Pkg->SelectedState == pkgCache::State::Hold)
+ WriteOkay(Okay, output, "\nHold: yes");
+ // FIXME: Ideally, an EIPP request contains at most two versions (installed and to install)
+ if (Cache.GetCandidateVersion(Pkg) == Ver)
+ WriteOkay(Okay, output, "\nAPT-Candidate: yes");
+ return Okay;
+}
+ /*}}}*/
+// EIPP::WriteScenario - to the given file descriptor /*{{{*/
+bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress)
+{
+ if (Progress != NULL)
+ Progress->SubProgress(Cache.Head().VersionCount, _("Send scenario to planer"));
+ unsigned long p = 0;
+ bool Okay = output.Failed() == false;
+ std::vector<std::string> archs = APT::Configuration::getArchitectures();
+ std::vector<bool> pkgset(Cache.Head().VersionCount, true);
+ for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg)
+ {
+ std::string const arch = Pkg.Arch();
+ if (std::find(archs.begin(), archs.end(), arch) == archs.end())
+ continue;
+ for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false && likely(Okay); ++Ver, ++p)
+ {
+ 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);
+ }
+ }
+ return true;
+}
+ /*}}}*/
+// EIPP::ReadResponse - from the given file descriptor /*{{{*/
+bool EIPP::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… */
+ /*
+ unsigned long long const VersionCount = Cache.Head().VersionCount;
+ unsigned long 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();
+ }
+ */
+
+ FileFd in;
+ in.OpenDescriptor(input, FileFd::ReadOnly);
+ pkgTagFile response(&in, 100);
+ pkgTagSection section;
+
+ std::set<decltype(Cache.PkgBegin()->ID)> seenOnce;
+ while (response.Step(section) == true) {
+ 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) {
+ std::string msg = SubstVar(SubstVar(section.FindS("Message"), "\n .\n", "\n\n"), "\n ", "\n");
+ if (msg.empty() == true) {
+ msg = _("External planer failed without a proper error message");
+ _error->Error("%s", msg.c_str());
+ } else
+ _error->Error("External planer failed with: %s", msg.substr(0,msg.find('\n')).c_str());
+ if (Progress != NULL)
+ Progress->Done();
+ std::cerr << "The planer 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 {
+ _error->Warning("Encountered an unexpected section with %d fields", section.Count());
+ }
+ }
+ return true;
+}
+ /*}}}*/