Переглянути джерело

eipp: provide the internal planer as an external one

Testing the current implementation can benefit from being able to be
feed an EIPP request and produce a fully compliant response. It is also
a great test for EIPP in general.
David Kalnischkies 10 роки тому
батько
коміт
f74d99c6a7

+ 140 - 14
apt-pkg/edsp.cc

@@ -12,6 +12,8 @@
 #include <apt-pkg/depcache.h>
 #include <apt-pkg/pkgcache.h>
 #include <apt-pkg/cacheiterators.h>
+#include <apt-pkg/prettyprinters.h>
+#include <apt-pkg/packagemanager.h>
 #include <apt-pkg/progress.h>
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/edsp.h>
@@ -659,8 +661,12 @@ bool EDSP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progres
 			return false;
 		} else if (section.Exists("Autoremove") == true)
 			type = "Autoremove";
-		else
+		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;
+		}
 
 		size_t const id = section.FindULL(type.c_str(), VersionCount);
 		if (id == VersionCount) {
@@ -1065,7 +1071,7 @@ bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache,
 }
 									/*}}}*/
 
-bool EIPP::OrderInstall(char const * const solver, pkgDepCache &Cache,	/*{{{*/
+bool EIPP::OrderInstall(char const * const solver, pkgPackageManager * const PM,	/*{{{*/
 			 unsigned int const flags, OpProgress * const Progress)
 {
    int solver_in, solver_out;
@@ -1080,15 +1086,15 @@ bool EIPP::OrderInstall(char const * const solver, pkgDepCache &Cache,	/*{{{*/
    bool Okay = output.Failed() == false;
    if (Progress != NULL)
       Progress->OverallProgress(0, 100, 5, _("Execute external planer"));
-   Okay &= EIPP::WriteRequest(Cache, output, flags, Progress);
+   Okay &= EIPP::WriteRequest(PM->Cache, output, flags, Progress);
    if (Progress != NULL)
       Progress->OverallProgress(5, 100, 20, _("Execute external planer"));
-   Okay &= EIPP::WriteScenario(Cache, output, Progress);
+   Okay &= EIPP::WriteScenario(PM->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)
+   if (Okay && EIPP::ReadResponse(solver_out, PM, Progress) == false)
       return false;
 
    return ExecWait(solver_pid, solver);
@@ -1113,7 +1119,7 @@ bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output,		/*{{{*/
 	 req = &purge;
       if (P.Delete() == true)
 	 req = &del;
-      else if (P.NewInstall() == true || P.Upgrade() == true)
+      else if (P.NewInstall() == true || P.Upgrade() == true || P.Downgrade() == true)
 	 req = &inst;
       else if (P.ReInstall() == true)
 	 req = &reinst;
@@ -1228,27 +1234,25 @@ bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const
 }
 									/*}}}*/
 // EIPP::ReadResponse - from the given file descriptor			/*{{{*/
-bool EIPP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progress) {
+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… */
-   /*
-   unsigned long long const VersionCount = Cache.Head().VersionCount;
+   unsigned long long const VersionCount = PM->Cache.Head().VersionCount;
    unsigned long VerIdx[VersionCount];
-   for (pkgCache::PkgIterator P = Cache.PkgBegin(); P.end() == false; ++P) {
+   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;
 
-   std::set<decltype(Cache.PkgBegin()->ID)> seenOnce;
    while (response.Step(section) == true) {
+      char const * type = nullptr;
       if (section.Exists("Progress") == true) {
 	 if (Progress != NULL) {
 	    string msg = section.FindS("Message");
@@ -1270,8 +1274,130 @@ bool EIPP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progres
 	 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());
+      } else if (section.Exists("Install") == true)
+	 type = "Install";
+      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;
+      size_t 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 > PM->Cache.Head().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, "Install") == 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 true;
+}
+									/*}}}*/
+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, "Planer:"))
+	    ; // purely informational line
+	 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;
+
+
+   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;

+ 18 - 3
apt-pkg/edsp.h

@@ -236,16 +236,31 @@ namespace EDSP								/*{{{*/
 				    bool const autoRemove, OpProgress *Progress = NULL);
 }
 									/*}}}*/
+class pkgPackageManager;
 namespace EIPP								/*{{{*/
 {
-   APT_HIDDEN bool OrderInstall(char const * const solver, pkgDepCache &Cache,
-	 unsigned int const version, OpProgress * const Progress);
    APT_HIDDEN bool WriteRequest(pkgDepCache &Cache, FileFd &output,
 	 unsigned int const version, OpProgress * const Progress);
    APT_HIDDEN bool WriteScenario(pkgDepCache &Cache, FileFd &output,
 	 OpProgress * const Progress);
-   APT_HIDDEN bool ReadResponse(int const input, pkgDepCache &Cache,
+
+   APT_HIDDEN bool OrderInstall(char const * const planer, 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
+   };
+   bool ReadRequest(int const input,
+	 std::list<std::pair<std::string,PKG_ACTION>> &actions,
+	 unsigned int &flags);
+   bool ApplyRequest(std::list<std::pair<std::string,PKG_ACTION>> &actions,
+	 pkgDepCache &Cache);
 }
 									/*}}}*/
 #endif

+ 36 - 1
apt-pkg/edsp/edspindexfile.cc

@@ -69,6 +69,25 @@ pkgCacheListParser * edspIndex::CreateListParser(FileFd &Pkg)
    return newError ? NULL : Parser;
 }
 									/*}}}*/
+// 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 NULL;
+   _error->PushToStack();
+   pkgCacheListParser * const Parser = new eippListParser(&Pkg);
+   bool const newError = _error->PendingError();
+   _error->MergeWithStack();
+   return newError ? NULL : Parser;
+}
+									/*}}}*/
 
 // Index File types for APT						/*{{{*/
 class APT_HIDDEN edspIFType: public pkgIndexFile::Type
@@ -82,12 +101,28 @@ class APT_HIDDEN edspIFType: public pkgIndexFile::Type
    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 presistent
+      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() {}

+ 13 - 0
apt-pkg/edsp/edspindexfile.h

@@ -46,4 +46,17 @@ public:
    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_CONST;
+
+   eippIndex(std::string const &File);
+   virtual ~eippIndex();
+};
+
 #endif

+ 54 - 0
apt-pkg/edsp/edsplistparser.cc

@@ -125,5 +125,59 @@ bool edspListParser::ParseStatus(pkgCache::PkgIterator &Pkg,
 }
 									/*}}}*/
 
+// 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("Status");
+   if (status.empty() == false)
+   {
+      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.Index();
+	       break;
+	 }
+	 break;
+      }
+   }
+
+   return true;
+}
+									/*}}}*/
+
 edspLikeListParser::~edspLikeListParser() {}
 edspListParser::~edspListParser() {}
+eippListParser::~eippListParser() {}

+ 10 - 0
apt-pkg/edsp/edsplistparser.h

@@ -51,4 +51,14 @@ public:
    edspListParser(FileFd *File);
    virtual ~edspListParser();
 };
+
+class APT_HIDDEN eippListParser : public edspLikeListParser
+{
+protected:
+   virtual bool ParseStatus(pkgCache::PkgIterator &Pkg,pkgCache::VerIterator &Ver) APT_OVERRIDE;
+
+public:
+   eippListParser(FileFd *File);
+   virtual ~eippListParser();
+};
 #endif

+ 18 - 0
apt-pkg/edsp/edspsystem.cc

@@ -34,6 +34,9 @@ edspLikeSystem::edspLikeSystem(char const * const Label) : pkgSystem(Label, &deb
 }
 edspSystem::edspSystem() : edspLikeSystem("Debian APT solver interface")
 {
+}
+eippSystem::eippSystem() : edspLikeSystem("Debian APT planer interface")
+{
 }
 									/*}}}*/
 // System::Lock - Get the lock						/*{{{*/
@@ -134,6 +137,19 @@ bool edspSystem::AddStatusFiles(std::vector<pkgIndexFile *> &List)	/*{{{*/
    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()
@@ -145,5 +161,7 @@ edspSystem::~edspSystem()
    RemoveFile("~edspSystem", tempPrefsFile);
    rmdir(tempDir.c_str());
 }
+eippSystem::~eippSystem() {}
 
 APT_HIDDEN edspSystem edspSys;
+APT_HIDDEN eippSystem eippSys;

+ 9 - 0
apt-pkg/edsp/edspsystem.h

@@ -57,4 +57,13 @@ public:
    virtual ~edspSystem();
 };
 
+class APT_HIDDEN eippSystem : public edspLikeSystem
+{
+   public:
+   virtual bool AddStatusFiles(std::vector<pkgIndexFile *> &List) APT_OVERRIDE;
+
+   eippSystem();
+   virtual ~eippSystem();
+};
+
 #endif

+ 5 - 1
apt-pkg/packagemanager.cc

@@ -1039,8 +1039,12 @@ pkgPackageManager::OrderResult pkgPackageManager::OrderInstall()
 
    std::string const planer = _config->Find("APT::Planer", "internal");
    if (planer != "internal")
-      if (EIPP::OrderInstall(planer.c_str(), Cache, 0, nullptr) == false)
+   {
+      if (EIPP::OrderInstall(planer.c_str(), this, 0, nullptr))
+	 return Completed;
+      else
 	 return Failed;
+   }
 
    bool const ordering =
 	_config->FindB("PackageManager::UnpackAll",true) ?

+ 8 - 1
apt-pkg/packagemanager.h

@@ -24,6 +24,7 @@
 
 #include <apt-pkg/pkgcache.h>
 #include <apt-pkg/init.h>
+#include <apt-pkg/edsp.h>
 #include <apt-pkg/macros.h>
 
 #include <string>
@@ -43,13 +44,14 @@ class pkgDepCache;
 class pkgSourceList;
 class pkgOrderList;
 class pkgRecords;
+class OpProgress;
+class pkgPackageManager;
 namespace APT {
    namespace Progress {
       class PackageManager;
    }
 }
 
-
 class pkgPackageManager : protected pkgCache::Namespace
 {
    public:
@@ -115,6 +117,11 @@ class pkgPackageManager : protected pkgCache::Namespace
    // compat
    APT_DEPRECATED_MSG("Use APT::Progress::PackageManager subclass instead of fd") OrderResult DoInstall(int statusFd=-1);
 
+   friend bool EIPP::OrderInstall(char const * const planer, pkgPackageManager * const PM,
+	 unsigned int const version, OpProgress * const Progress);
+   friend bool EIPP::ReadResponse(int const input, pkgPackageManager * const PM,
+	 OpProgress * const Progress);
+
    // stuff that needs to be done before the fork() of a library that
    // uses apt
    OrderResult DoInstallPreFork() {

+ 9 - 1
apt-private/private-cmndline.cc

@@ -160,6 +160,11 @@ static bool addArgumentsAPTFTPArchive(std::vector<CommandLine::Args> &Args, char
    return true;
 }
 									/*}}}*/
+static bool addArgumentsAPTInternalPlaner(std::vector<CommandLine::Args> &, char const * const)/*{{{*/
+{
+   return true;
+}
+									/*}}}*/
 static bool addArgumentsAPTInternalSolver(std::vector<CommandLine::Args> &, char const * const)/*{{{*/
 {
    return true;
@@ -356,6 +361,7 @@ std::vector<CommandLine::Args> getCommandArgs(APT_CMD const Program, char const
 	 case APT_CMD::APT_EXTRACTTEMPLATES: addArgumentsAPTExtractTemplates(Args, Cmd); break;
 	 case APT_CMD::APT_FTPARCHIVE: addArgumentsAPTFTPArchive(Args, Cmd); break;
 	 case APT_CMD::APT_HELPER: addArgumentsAPTHelper(Args, Cmd); break;
+	 case APT_CMD::APT_INTERNAL_PLANER: addArgumentsAPTInternalPlaner(Args, Cmd); break;
 	 case APT_CMD::APT_INTERNAL_SOLVER: addArgumentsAPTInternalSolver(Args, Cmd); break;
 	 case APT_CMD::APT_MARK: addArgumentsAPTMark(Args, Cmd); break;
 	 case APT_CMD::APT_SORTPKG: addArgumentsAPTSortPkgs(Args, Cmd); break;
@@ -412,13 +418,15 @@ static bool ShowCommonHelp(APT_CMD const Binary, CommandLine &CmdL, std::vector<
       case APT_CMD::APT_FTPARCHIVE: cmd = "apt-ftparchive(1)"; break;
       case APT_CMD::APT_GET: cmd = "apt-get(8)"; break;
       case APT_CMD::APT_HELPER: cmd = nullptr; break;
+      case APT_CMD::APT_INTERNAL_PLANER: cmd = nullptr; break;
       case APT_CMD::APT_INTERNAL_SOLVER: cmd = nullptr; break;
       case APT_CMD::APT_MARK: cmd = "apt-mark(8)"; break;
       case APT_CMD::APT_SORTPKG: cmd = "apt-sortpkgs(1)"; break;
    }
    if (cmd != nullptr)
       ioprintf(std::cout, _("See %s for more information about the available commands."), cmd);
-   if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER)
+   if (Binary != APT_CMD::APT_DUMP_SOLVER && Binary != APT_CMD::APT_INTERNAL_SOLVER &&
+	 Binary != APT_CMD::APT_INTERNAL_PLANER)
       std::cout << std::endl <<
 	 _("Configuration options and syntax is detailed in apt.conf(5).\n"
 	       "Information about how to configure sources can be found in sources.list(5).\n"

+ 1 - 0
apt-private/private-cmndline.h

@@ -22,6 +22,7 @@ enum class APT_CMD {
    APT_MARK,
    APT_SORTPKG,
    APT_DUMP_SOLVER,
+   APT_INTERNAL_PLANER,
 };
 struct aptDispatchWithHelp
 {

+ 1 - 0
apt-private/private-main.cc

@@ -38,6 +38,7 @@ void InitLocale(APT_CMD const binary)				/*{{{*/
 	 break;
       case APT_CMD::APT_EXTRACTTEMPLATES:
       case APT_CMD::APT_FTPARCHIVE:
+      case APT_CMD::APT_INTERNAL_PLANER:
       case APT_CMD::APT_INTERNAL_SOLVER:
       case APT_CMD::APT_SORTPKG:
 	 textdomain("apt-utils");

+ 187 - 0
cmdline/apt-internal-planer.cc

@@ -0,0 +1,187 @@
+// -*- mode: cpp; mode: fold -*-
+// Description								/*{{{*/
+/* #####################################################################
+
+   cover around the internal solver to be able to run it like an external
+
+   ##################################################################### */
+									/*}}}*/
+// Include Files							/*{{{*/
+#include <config.h>
+
+#include <apt-pkg/error.h>
+#include <apt-pkg/cmndline.h>
+#include <apt-pkg/init.h>
+#include <apt-pkg/cachefile.h>
+#include <apt-pkg/cacheset.h>
+#include <apt-pkg/strutl.h>
+#include <apt-pkg/edsp.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/pkgsystem.h>
+#include <apt-pkg/configuration.h>
+#include <apt-pkg/packagemanager.h>
+#include <apt-pkg/prettyprinters.h>
+#include <apt-pkg/depcache.h>
+#include <apt-pkg/pkgcache.h>
+#include <apt-pkg/cacheiterators.h>
+
+#include <apt-private/private-output.h>
+#include <apt-private/private-cmndline.h>
+#include <apt-private/private-main.h>
+
+#include <string.h>
+#include <iostream>
+#include <sstream>
+#include <list>
+#include <string>
+#include <unistd.h>
+#include <cstdio>
+#include <stdlib.h>
+
+#include <apti18n.h>
+									/*}}}*/
+
+static bool ShowHelp(CommandLine &)					/*{{{*/
+{
+	std::cout <<
+		_("Usage: apt-internal-planer\n"
+		"\n"
+		"apt-internal-planer is an interface to use the current internal\n"
+		"installation planer for the APT family like an external one,\n"
+		"for debugging or the like.\n");
+	return true;
+}
+									/*}}}*/
+APT_NORETURN static void DIE(std::string const &message) {		/*{{{*/
+	std::cerr << "ERROR: " << message << std::endl;
+	_error->DumpErrors(std::cerr);
+	exit(EXIT_FAILURE);
+}
+									/*}}}*/
+static std::vector<aptDispatchWithHelp> GetCommands()			/*{{{*/
+{
+   return {};
+}
+									/*}}}*/
+class PMOutput: public pkgPackageManager				/*{{{*/
+{
+   FileFd &output;
+   bool const Debug;
+
+protected:
+   virtual bool Install(PkgIterator Pkg,std::string) APT_OVERRIDE
+   {
+      //std::cerr << "INSTALL: " << APT::PrettyPkg(&Cache, Pkg) << std::endl;
+      return EDSP::WriteSolutionStanza(output, "Install", Cache[Pkg].InstVerIter(Cache));
+   }
+   virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE
+   {
+      //std::cerr << "CONFIGURE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl;
+      return EDSP::WriteSolutionStanza(output, "Configure", Cache[Pkg].InstVerIter(Cache));
+   }
+   virtual bool Remove(PkgIterator Pkg,bool) APT_OVERRIDE
+   {
+      //std::cerr << "REMOVE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl;
+      return EDSP::WriteSolutionStanza(output, "Remove", Pkg.CurrentVer());
+   }
+public:
+   PMOutput(pkgDepCache *Cache, FileFd &file) : pkgPackageManager(Cache), output(file),
+      Debug(_config->FindB("Debug::EDSP::WriteSolution", false))
+   {}
+
+   bool ApplyRequest(std::list<std::pair<std::string,EIPP::PKG_ACTION>> const &actions)
+   {
+      for (auto && a: actions)
+      {
+	 auto const Pkg = Cache.FindPkg(a.first);
+	 if (unlikely(Pkg.end() == true))
+	    continue;
+	 switch (a.second)
+	 {
+	    case EIPP::PKG_ACTION::NOOP:
+	       break;
+	    case EIPP::PKG_ACTION::INSTALL:
+	    case EIPP::PKG_ACTION::REINSTALL:
+	       FileNames[Pkg->ID] = "EIPP";
+	       break;
+	    case EIPP::PKG_ACTION::REMOVE:
+	       break;
+	 }
+      }
+      return true;
+   }
+};
+									/*}}}*/
+int main(int argc,const char *argv[])					/*{{{*/
+{
+	// we really don't need anything
+	DropPrivileges();
+
+	CommandLine CmdL;
+	ParseCommandLine(CmdL, APT_CMD::APT_INTERNAL_PLANER, &_config, NULL, argc, argv, &ShowHelp, &GetCommands);
+
+	// Deal with stdout not being a tty
+	if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1)
+		_config->Set("quiet","1");
+
+	if (_config->FindI("quiet", 0) < 1)
+		_config->Set("Debug::EIPP::WriteSolution", true);
+
+	_config->Set("APT::System", "Debian APT planer interface");
+	_config->Set("APT::Planer", "internal");
+	_config->Set("eipp::scenario", "/nonexistent/stdin");
+	FileFd output;
+	if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false)
+	   DIE("stdout couldn't be opened");
+	int const input = STDIN_FILENO;
+	SetNonBlock(input, false);
+
+	EDSP::WriteProgress(0, "Start up planer…", output);
+
+	if (pkgInitSystem(*_config,_system) == false)
+		DIE("System could not be initialized!");
+
+	EDSP::WriteProgress(1, "Read request…", output);
+
+	if (WaitFd(input, false, 5) == false)
+		DIE("WAIT timed out in the planer");
+
+	std::list<std::pair<std::string,EIPP::PKG_ACTION>> actions;
+	unsigned int flags;
+	if (EIPP::ReadRequest(input, actions, flags) == false)
+		DIE("Parsing the request failed!");
+
+	EDSP::WriteProgress(5, "Read scenario…", output);
+
+	pkgCacheFile CacheFile;
+	if (CacheFile.Open(NULL, false) == false)
+		DIE("Failed to open CacheFile!");
+
+	EDSP::WriteProgress(50, "Apply request on scenario…", output);
+
+	if (EIPP::ApplyRequest(actions, CacheFile) == false)
+		DIE("Failed to apply request to depcache!");
+
+	EDSP::WriteProgress(60, "Call orderinstall on current scenario…", output);
+
+	//_config->Set("Debug::pkgOrderList", true);
+	//_config->Set("Debug::pkgPackageManager", true);
+	PMOutput PM(CacheFile, output);
+	if (PM.ApplyRequest(actions) == false)
+		DIE("Failed to apply request to packagemanager!");
+	pkgPackageManager::OrderResult const Res = PM.DoInstallPreFork();
+	switch (Res)
+	{
+	   case pkgPackageManager::Completed:
+	      EDSP::WriteProgress(100, "Done", output);
+	      break;
+	   case pkgPackageManager::Incomplete:
+	      EDSP::WriteError("pm-incomplete", "Planer could only plan Incompletely", output);
+	   case pkgPackageManager::Failed:
+	      EDSP::WriteError("pm-failed", "Planer failed to find an order", output);
+	      break;
+	}
+
+	return DispatchCommandLine(CmdL, {});
+}
+									/*}}}*/

+ 7 - 1
cmdline/makefile

@@ -79,13 +79,19 @@ LIB_MAKES = apt-pkg/makefile apt-inst/makefile apt-private/makefile
 SOURCE = apt-extracttemplates.cc 
 include $(PROGRAM_H)
 
-# The internal solver acting as an external
+# The internal solver/planer acting as an external
 PROGRAM=apt-internal-solver
 SLIBS = -lapt-pkg -lapt-private $(INTLLIBS)
 LIB_MAKES = apt-pkg/makefile apt-private/makefile
 SOURCE = apt-internal-solver.cc
 include $(PROGRAM_H)
 
+PROGRAM=apt-internal-planer
+SLIBS = -lapt-pkg -lapt-private $(INTLLIBS)
+LIB_MAKES = apt-pkg/makefile apt-private/makefile
+SOURCE = apt-internal-planer.cc
+include $(PROGRAM_H)
+
 # This just dumps out the state
 PROGRAM=apt-dump-solver
 SLIBS = -lapt-pkg -lapt-private $(INTLLIBS)

+ 1 - 0
debian/apt-utils.dirs

@@ -1,2 +1,3 @@
 usr/lib/apt/solvers
+usr/lib/apt/planers
 usr/bin

+ 2 - 1
debian/apt.dirs

@@ -1,6 +1,7 @@
 usr/bin
 usr/lib/apt/methods
 usr/lib/apt/solvers
+usr/lib/apt/planers
 usr/lib/dpkg/methods/apt
 etc/apt
 etc/apt/apt.conf.d
@@ -16,4 +17,4 @@ var/lib/apt/periodic
 var/log/apt
 usr/share/bug/apt
 usr/share/bash-completion/completions/
-lib/systemd/system/
+lib/systemd/system/

+ 0 - 2
debian/apt.lintian-overrides

@@ -1,4 +1,2 @@
 # the private library is for internal sharing only
 apt: package-name-doesnt-match-sonames
-# external solvers are shipped in here:
-apt: package-contains-empty-directory

+ 3 - 1
debian/rules

@@ -189,8 +189,9 @@ apt: build-binary build-manpages debian/apt.install
 	dh_install -p$@ --sourcedir=$(BLD)
 
 	# Remove the bits that are in apt-utils
-	rm $(addprefix debian/$@/usr/bin/apt-,$(APT_UTILS) dump-solver internal-solver)
+	rm $(addprefix debian/$@/usr/bin/apt-,$(APT_UTILS) dump-solver internal-solver internal-planer)
 	cp $(BLD)/bin/apt-dump-solver debian/$@/usr/lib/apt/solvers/dump
+	ln -s ../solvers/dump debian/$@/usr/lib/apt/planers/dump
 
 	# https has its own package
 	rm debian/$@/usr/lib/apt/methods/https
@@ -252,6 +253,7 @@ apt-utils: build-binary build-manpages
 
 	cp $(addprefix $(BLD)/bin/apt-,$(APT_UTILS)) debian/$@/usr/bin/
 	cp $(BLD)/bin/apt-internal-solver debian/$@/usr/lib/apt/solvers/apt
+	cp $(BLD)/bin/apt-internal-planer debian/$@/usr/lib/apt/planers/apt
 
 	dh_install -p$@ --sourcedir=$(BLD)
 	dh_link -p$@

+ 1 - 0
debian/tests/run-tests

@@ -17,6 +17,7 @@ APT_INTEGRATION_TESTS_METHODS_DIR=/usr/lib/apt/methods \
 APT_INTEGRATION_TESTS_LIBEXEC_DIR=/usr/lib/apt/ \
 APT_INTEGRATION_TESTS_INTERNAL_SOLVER=/usr/lib/apt/solvers/apt \
 APT_INTEGRATION_TESTS_DUMP_SOLVER=/usr/lib/apt/solvers/dump \
+APT_INTEGRATION_TESTS_INTERNAL_PLANER=/usr/lib/apt/planers/apt \
 APT_INTEGRATION_TESTS_BUILD_DIR=/usr/bin \
 APT_INTEGRATION_TESTS_LIBRARY_PATH=/dev/null/does/not/exist \
 ./test/integration/run-tests -q

+ 3 - 0
test/integration/framework

@@ -191,6 +191,7 @@ aptitude() { runapt aptitude "$@"; }
 aptextracttemplates() { runapt apt-extracttemplates "$@"; }
 aptinternalsolver() { runapt "${APTINTERNALSOLVER}" "$@"; }
 aptdumpsolver() { runapt "${APTDUMPSOLVER}" "$@"; }
+aptinternalplaner() { runapt "${APTINTERNALPLANER}" "$@"; }
 
 dpkg() {
 	"${TMPWORKINGDIRECTORY}/rootdir/usr/bin/dpkg" "$@"
@@ -289,6 +290,7 @@ setupenvironment() {
         APTWEBSERVERBINDIR="${APT_INTEGRATION_TESTS_WEBSERVER_BIN_DIR:-"${BUILDDIRECTORY}"}"
         APTINTERNALSOLVER="${APT_INTEGRATION_TESTS_INTERNAL_SOLVER:-"${BUILDDIRECTORY}/apt-internal-solver"}"
 	APTDUMPSOLVER="${APT_INTEGRATION_TESTS_DUMP_SOLVER:-"${BUILDDIRECTORY}/apt-dump-solver"}"
+	APTINTERNALPLANER="${APT_INTEGRATION_TESTS_INTERNAL_PLANER:-"${BUILDDIRECTORY}/apt-internal-planer"}"
 	test -x "${BUILDDIRECTORY}/apt-get" || msgdie "You need to build tree first"
         # -----
 
@@ -306,6 +308,7 @@ setupenvironment() {
 		ln -s "${BUILDDIRECTORY}/apt-dump-solver" usr/lib/apt/solvers/dump
 		ln -s "${BUILDDIRECTORY}/apt-dump-solver" usr/lib/apt/planers/dump
 		ln -s "${BUILDDIRECTORY}/apt-internal-solver" usr/lib/apt/solvers/apt
+		ln -s "${BUILDDIRECTORY}/apt-internal-planer" usr/lib/apt/planers/apt
 		echo "Dir::Bin::Solvers \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/solvers\";" > etc/apt/apt.conf.d/externalsolver.conf
 		echo "Dir::Bin::Planers \"${TMPWORKINGDIRECTORY}/rootdir/usr/lib/apt/planers\";" > etc/apt/apt.conf.d/externalplaner.conf
 	fi

+ 1 - 1
test/integration/test-00-commands-have-help

@@ -49,7 +49,7 @@ for CMD in 'apt-cache' 'apt-cdrom' 'apt-config' \
 	checkoptions "$cmd"
 done
 
-for CMD in 'apt-dump-solver'  'apt-internal-solver'; do
+for CMD in 'apt-dump-solver'  'apt-internal-solver' 'apt-internal-planer'; do
 	checkoptions "$(echo "$CMD" | tr -d '-')"
 done
 

+ 3 - 0
test/integration/test-external-installation-planer-protocol

@@ -26,3 +26,6 @@ testfailure aptget install foo --planer dump -y
 testfailure grep 'unrelated-2' "$APT_EDSP_DUMP_FILENAME"
 testsuccessequal '2' grep -c '^Package: foo$' "$APT_EDSP_DUMP_FILENAME"
 testsuccessequal '1' grep -c '^Package: libfoo$' "$APT_EDSP_DUMP_FILENAME"
+#less "$APT_EDSP_DUMP_FILENAME"
+
+aptget install foo -ys #--planer apt