]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/edsp.cc
edsp: try to read responses even if writing failed
[apt.git] / apt-pkg / edsp.cc
index e37ab04b4731497e0ec6668f8b2d01d793f03443..f56625feb5e9044e04ac5a07fd41bf38d5798731 100644 (file)
@@ -7,11 +7,14 @@
 // Include Files                                                       /*{{{*/
 #include <config.h>
 
+#include <apt-pkg/algorithms.h>
 #include <apt-pkg/error.h>
 #include <apt-pkg/cacheset.h>
 #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>
@@ -337,7 +340,7 @@ static bool WriteScenarioLimitedDependency(FileFd &output,
 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 choosen
+      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
@@ -437,7 +440,7 @@ bool EDSP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress *Progres
            Progress->Progress(p);
       }
    }
-   return true;
+   return Okay;
 }
                                                                        /*}}}*/
 // EDSP::WriteLimitedScenario - to the given file descriptor           /*{{{*/
@@ -659,8 +662,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) {
@@ -905,14 +912,14 @@ bool EDSP::WriteSolutionStanza(FileFd &output, char const * const Type, pkgCache
                                                                        /*}}}*/
 // EDSP::WriteProgess - pulse to the given file descriptor             /*{{{*/
 bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FILE* output) {
-       fprintf(output, "Progress: %s\n", TimeRFC1123(time(NULL)).c_str());
+       fprintf(output, "Progress: %s\n", TimeRFC1123(time(NULL), true).c_str());
        fprintf(output, "Percentage: %d\n", percent);
        fprintf(output, "Message: %s\n\n", message);
        fflush(output);
        return true;
 }
 bool EDSP::WriteProgress(unsigned short const percent, const char* const message, FileFd &output) {
-       return WriteOkay(output, "Progress: ", TimeRFC1123(time(NULL)), "\n",
+       return WriteOkay(output, "Progress: ", TimeRFC1123(time(NULL), true), "\n",
              "Percentage: ", percent, "\n",
              "Message: ", message, "\n\n") && output.Flush();
 }
@@ -1010,20 +1017,38 @@ bool EDSP::ExecuteSolver(const char* const solver, int *solver_in, int *solver_o
    return true;
 }
                                                                        /*}}}*/
+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)
        {
-               auto const dumpfile = _config->FindFile("Dir::Log::Solver");
-               if (dumpfile.empty())
-                       return false;
-               auto const dumpdir = flNotFile(dumpfile);
                FileFd output;
-               if (CreateAPTDirectoryIfNeeded(dumpdir, dumpdir) == false ||
-                     output.Open(dumpfile, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false)
-                       return _error->WarningE("EDSP::Resolve", _("Could not open file '%s'"), dumpfile.c_str());
-               bool Okay = EDSP::WriteRequest(Cache, output, flags, nullptr);
+               bool Okay = CreateDumpFile("EDSP::Resolve", "solver", output);
+               Okay &= EDSP::WriteRequest(Cache, output, flags, nullptr);
                return Okay && EDSP::WriteScenario(Cache, output, nullptr);
        }
        int solver_in, solver_out;
@@ -1036,20 +1061,21 @@ bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache,
                return _error->Errno("ResolveExternal", "Opening solver %s stdin on fd %d for writing failed", solver, solver_in);
 
        bool Okay = output.Failed() == false;
-       if (Progress != NULL)
+       if (Okay && Progress != NULL)
                Progress->OverallProgress(0, 100, 5, _("Execute external solver"));
        Okay &= EDSP::WriteRequest(Cache, output, flags, Progress);
-       if (Progress != NULL)
+       if (Okay && Progress != NULL)
                Progress->OverallProgress(5, 100, 20, _("Execute external solver"));
        Okay &= EDSP::WriteScenario(Cache, output, Progress);
        output.Close();
 
-       if (Progress != NULL)
+       if (Okay && Progress != NULL)
                Progress->OverallProgress(25, 100, 75, _("Execute external solver"));
        if (Okay && EDSP::ReadResponse(solver_out, Cache, Progress) == false)
                return false;
 
-       return ExecWait(solver_pid, solver);
+       bool const waited = ExecWait(solver_pid, solver);
+       return Okay && waited;
 }
 bool EDSP::ResolveExternal(const char* const solver, pkgDepCache &Cache,
                         bool const upgrade, bool const distUpgrade,
@@ -1065,55 +1091,81 @@ 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)
 {
+   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);
+   }
+
    int solver_in, solver_out;
-   pid_t const solver_pid = ExecuteExternal("planer", solver, "Dir::Bin::Planers", &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("OrderInstall", "Opening planer %s stdin on fd %d for writing failed", solver, solver_in);
+      return _error->Errno("EIPP::OrderInstall", "Opening planner %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);
+   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 (Progress != NULL)
-      Progress->OverallProgress(25, 100, 75, _("Execute external planer"));
-   if (Okay && EIPP::ReadResponse(solver_out, Cache, Progress) == false)
+   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);
+      }
+   }
+
+   if (EIPP::ReadResponse(solver_out, PM, Progress) == false)
       return false;
 
-   return ExecWait(solver_pid, solver);
+   bool const waited = ExecWait(solver_pid, solver);
+   return Okay && waited;
 }
                                                                        /*}}}*/
 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"));
+      Progress->SubProgress(Cache.Head().PackageCount, _("Send request to planner"));
    unsigned long p = 0;
-   string del, purge, inst, reinst;
+   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)
-        req = &purge;
+      if (P.Purge() == true && Pkg->CurrentState == pkgCache::State::ConfigFiles)
+        continue;
       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;
@@ -1131,15 +1183,19 @@ bool EIPP::WriteRequest(pkgDepCache &Cache, FileFd &output,             /*{{{*/
        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");
+   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");
 }
                                                                        /*}}}*/
@@ -1186,7 +1242,7 @@ template<typename forVersion> void forAllInterestingVersions(pkgDepCache &Cache,
 bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const Progress)
 {
    if (Progress != NULL)
-      Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planer"));
+      Progress->SubProgress(Cache.Head().PackageCount, _("Send scenario to planner"));
    unsigned long p = 0;
    bool Okay = output.Failed() == false;
    std::vector<std::string> archs = APT::Configuration::getArchitectures();
@@ -1206,6 +1262,35 @@ bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const
               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)
@@ -1224,31 +1309,29 @@ bool EIPP::WriteScenario(pkgDepCache &Cache, FileFd &output, OpProgress * const
         continue;
       forAllInterestingVersions(Cache, Pkg, WriteVersion);
    }
-   return true;
+   return Okay;
 }
                                                                        /*}}}*/
 // 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");
@@ -1260,18 +1343,146 @@ bool EIPP::ReadResponse(int const input, pkgDepCache &Cache, OpProgress *Progres
       } 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");
+           msg = _("External planner 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());
+           _error->Error("External planner 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 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 {
-        _error->Warning("Encountered an unexpected section with %d fields", section.Count());
+      } 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;
+      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, "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;