]> git.saurik.com Git - apt.git/blobdiff - apt-pkg/deb/dpkgpm.cc
Avoid wedging the entire system if recoverable :/.
[apt.git] / apt-pkg / deb / dpkgpm.cc
index 9c871d477616406220852a9531084249e80578d5..79c5f9bdf719cbf9483c51484aca7573c7d37f82 100644 (file)
@@ -61,6 +61,8 @@
 #include <apti18n.h>
                                                                        /*}}}*/
 
+extern char **environ;
+
 using namespace std;
 
 APT_PURE static string AptHistoryRequestingUser()                      /*{{{*/
@@ -141,6 +143,8 @@ namespace
   // of each array is the key, entry 1 is the value.
   const std::pair<const char *, const char *> PackageProcessingOps[] = {
     std::make_pair("install",   N_("Installing %s")),
+    // we don't care for the difference
+    std::make_pair("upgrade",   N_("Installing %s")),
     std::make_pair("configure", N_("Configuring %s")),
     std::make_pair("remove",    N_("Removing %s")),
     std::make_pair("purge",    N_("Completely removing %s")),
@@ -613,68 +617,154 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
    {
       pkgname = APT::String::Strip(list[2]);
       action = APT::String::Strip(list[1]);
-      // we don't care for the difference (as dpkg doesn't really either)
-      if (action == "upgrade")
-        action = "install";
    }
    // "status" has the form: "status: pkg: state"
-   // with state in ["half-installed", "unpacked", "half-configured", 
+   // with state in ["half-installed", "unpacked", "half-configured",
    //                "installed", "config-files", "not-installed"]
    else if (prefix == "status")
    {
       pkgname = APT::String::Strip(list[1]);
       action = APT::String::Strip(list[2]);
-   } else {
-      if (Debug == true)
-        std::clog << "unknown prefix '" << prefix << "'" << std::endl;
-      return;
-   }
 
+      /* handle the special cases first:
 
-   /* handle the special cases first:
-
-      errors look like this:
-      'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data 
-      and conffile-prompt like this
-      'status:/etc/compiz.conf/compiz.conf :  conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
-   */
-   if (prefix == "status")
-   {
+        errors look like this:
+        'status: /var/cache/apt/archives/krecipes_0.8.1-0ubuntu1_i386.deb : error : trying to overwrite `/usr/share/doc/kde/HTML/en/krecipes/krectip.png', which is also in package krecipes-data
+        and conffile-prompt like this
+        'status:/etc/compiz.conf/compiz.conf :  conffile-prompt: 'current-conffile' 'new-conffile' useredited distedited
+        */
       if(action == "error")
       {
-         d->progress->Error(pkgname, PackagesDone, PackagesTotal,
-                            list[3]);
-         pkgFailures++;
+         d->progress->Error(pkgname, PackagesDone, PackagesTotal, list[3]);
+         ++pkgFailures;
          WriteApportReport(pkgname.c_str(), list[3].c_str());
          return;
       }
       else if(action == "conffile-prompt")
       {
-         d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal,
-                                     list[3]);
+         d->progress->ConffilePrompt(pkgname, PackagesDone, PackagesTotal, list[3]);
          return;
       }
+   } else {
+      if (Debug == true)
+        std::clog << "unknown prefix '" << prefix << "'" << std::endl;
+      return;
    }
 
-   // at this point we know that we should have a valid pkgname, so build all 
-   // the info from it
-
-   // dpkg does not always send "pkgname:arch" so we add it here if needed
+   // At this point we have a pkgname, but it might not be arch-qualified !
    if (pkgname.find(":") == std::string::npos)
    {
-      // find the package in the group that is touched by dpkg
-      // if there are multiple pkgs dpkg would send us a full pkgname:arch
       pkgCache::GrpIterator Grp = Cache.FindGrp(pkgname);
-      if (Grp.end() == false)
-        for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
-           if(Cache[P].Keep() == false || Cache[P].ReInstall() == true)
+      /* No arch means that dpkg believes there can only be one package
+         this can refer to so lets see what could be candidates here: */
+      std::vector<pkgCache::PkgIterator> candset;
+      for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
+      {
+        if (PackageOps.find(P.FullName()) != PackageOps.end())
+           candset.push_back(P);
+        // packages can disappear without them having any interaction itself
+        // so we have to consider these as candidates, too
+        else if (P->CurrentVer != 0 && action == "disappear")
+           candset.push_back(P);
+      }
+      if (unlikely(candset.empty()))
+      {
+        if (Debug == true)
+           std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (1)" << std::endl;
+        return;
+      }
+      else if (candset.size() == 1) // we are lucky
+        pkgname = candset.cbegin()->FullName();
+      else
+      {
+        /* here be dragons^Wassumptions about dpkg:
+           - an M-A:same version is always arch-qualified
+           - a package from a foreign arch is (in newer versions) */
+        size_t installedInstances = 0, wannabeInstances = 0;
+        for (auto const &P: candset)
+        {
+           if (P->CurrentVer != 0)
            {
-              auto fullname = P.FullName();
-              if (Cache[P].Delete() && PackageOps[fullname].size() <= PackageOpsDone[fullname])
+              ++installedInstances;
+              if (Cache[P].Delete() == false)
+                 ++wannabeInstances;
+           }
+           else if (Cache[P].Install())
+              ++wannabeInstances;
+        }
+        // the package becomes M-A:same, so we are still talking about current
+        if (installedInstances == 1 && wannabeInstances >= 2)
+        {
+           for (auto const &P: candset)
+           {
+              if (P->CurrentVer == 0)
                  continue;
-              pkgname = std::move(fullname);
+              pkgname = P.FullName();
+              break;
+           }
+        }
+        // the package was M-A:same, it isn't now, so we can only talk about that
+        else if (installedInstances >= 2 && wannabeInstances == 1)
+        {
+           for (auto const &P: candset)
+           {
+              auto const IV = Cache[P].InstVerIter(Cache);
+              if (IV.end())
+                 continue;
+              pkgname = P.FullName();
               break;
            }
+        }
+        // that is a crossgrade
+        else if (installedInstances == 1 && wannabeInstances == 1 && candset.size() == 2)
+        {
+           auto const PkgHasCurrentVersion = [](pkgCache::PkgIterator const &P) { return P->CurrentVer != 0; };
+           auto const P = std::find_if(candset.begin(), candset.end(), PkgHasCurrentVersion);
+           if (unlikely(P == candset.end()))
+           {
+              if (Debug == true)
+                 std::clog << "situation for '" << pkgname << "' looked like a crossgrade, but no current version?!" << std::endl;
+              return;
+           }
+           auto fullname = P->FullName();
+           if (PackageOps[fullname].size() != PackageOpsDone[fullname])
+              pkgname = std::move(fullname);
+           else
+              pkgname = std::find_if_not(candset.begin(), candset.end(), PkgHasCurrentVersion)->FullName();
+        }
+        // we are desperate: so "just" take the native one, but that might change mid-air,
+        // so we have to ask dpkg what it believes native is at the moment… all the time
+        else
+        {
+           std::vector<std::string> sArgs = debSystem::GetDpkgBaseCommand();
+           sArgs.push_back("--print-architecture");
+           int outputFd = -1;
+           pid_t const dpkgNativeArch = debSystem::ExecDpkg(sArgs, nullptr, &outputFd, true);
+           if (unlikely(dpkgNativeArch == -1))
+           {
+              if (Debug == true)
+                 std::clog << "calling dpkg failed to ask it for its current native architecture to expand '" << pkgname << "'!" << std::endl;
+              return;
+           }
+           FILE *dpkg = fdopen(outputFd, "r");
+           if(dpkg != NULL)
+           {
+              char* buf = NULL;
+              size_t bufsize = 0;
+              if (getline(&buf, &bufsize, dpkg) != -1)
+                 pkgname += ':' + bufsize;
+              free(buf);
+              fclose(dpkg);
+           }
+           ExecWait(dpkgNativeArch, "dpkg --print-architecture", true);
+           if (pkgname.find(':') != std::string::npos)
+           {
+              if (Debug == true)
+                 std::clog << "unable to figure out which package is dpkg refering to with '" << pkgname << "'! (2)" << std::endl;
+              return;
+           }
+        }
+      }
    }
 
    std::string arch = "";
@@ -688,10 +778,7 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
    // 'processing: action: pkg'
    if(prefix == "processing")
    {
-      const std::pair<const char *, const char *> * const iter =
-       std::find_if(PackageProcessingOpsBegin,
-                    PackageProcessingOpsEnd,
-                    MatchProcessingOp(action.c_str()));
+      auto const iter = std::find_if(PackageProcessingOpsBegin, PackageProcessingOpsEnd, MatchProcessingOp(action.c_str()));
       if(iter == PackageProcessingOpsEnd)
       {
         if (Debug == true)
@@ -707,24 +794,15 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
       //         short pkgnames?
       if (action == "disappear")
         handleDisappearAction(pkgname);
+      else if (action == "upgrade")
+        handleCrossUpgradeAction(pkgname);
       return;
    }
 
    if (prefix == "status")
    {
       std::vector<struct DpkgState> &states = PackageOps[pkgname];
-      if (action == "triggers-pending")
-      {
-        if (Debug == true)
-           std::clog << "(parsed from dpkg) pkg: " << pkgname
-              << " action: " << action << " (prefix 2 to "
-              << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
-
-        states.insert(states.begin(), {"installed", N_("Installed %s")});
-        states.insert(states.begin(), {"half-configured", N_("Configuring %s")});
-        PackagesTotal += 2;
-      }
-      else if(PackageOpsDone[pkgname] < states.size())
+      if(PackageOpsDone[pkgname] < states.size())
       {
         char const * next_action = states[PackageOpsDone[pkgname]].state;
         if (next_action)
@@ -761,36 +839,19 @@ void pkgDPkgPM::ProcessDpkgStatusLine(char *line)
               strprintf(msg, translation, i18n_pkgname.c_str());
               d->progress->StatusChanged(pkgname, PackagesDone, PackagesTotal, msg);
            }
-           else if (action == "unpacked" && strcmp(next_action, "config-files") == 0)
-           {
-              // in a crossgrade what looked like a remove first is really an unpack over it
-              ++PackageOpsDone[pkgname];
-              ++PackagesDone;
-
-              auto const Pkg = Cache.FindPkg(pkgname);
-              if (likely(Pkg.end() == false))
-              {
-                 auto const Grp = Pkg.Group();
-                 if (likely(Grp.end() == false))
-                 {
-                    for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
-                       if(Cache[P].Install())
-                       {
-                          auto && Ops = PackageOps[P.FullName()];
-                          auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; });
-                          if (unpackOp != Ops.cend())
-                          {
-                             auto const skipped = std::distance(Ops.cbegin(), unpackOp);
-                             PackagesDone += skipped;
-                             PackageOpsDone[P.FullName()] += skipped;
-                             break;
-                          }
-                       }
-                 }
-              }
-           }
         }
       }
+      else if (action == "triggers-pending")
+      {
+        if (Debug == true)
+           std::clog << "(parsed from dpkg) pkg: " << pkgname
+              << " action: " << action << " (prefix 2 to "
+              << PackageOpsDone[pkgname] << " of " << states.size() << ")" << endl;
+
+        states.insert(states.begin(), {"installed", N_("Installed %s")});
+        states.insert(states.begin(), {"half-configured", N_("Configuring %s")});
+        PackagesTotal += 2;
+      }
    }
 }
                                                                        /*}}}*/
@@ -801,6 +862,12 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname)
    if (unlikely(Pkg.end() == true))
       return;
 
+   // a disappeared package has no further actions
+   auto const ROps = PackageOps[Pkg.FullName()].size();
+   auto && ROpsDone = PackageOpsDone[Pkg.FullName()];
+   PackagesDone += ROps - ROpsDone;
+   ROpsDone = ROps;
+
    // record the package name for display and stuff later
    disappearedPkgs.insert(Pkg.FullName(true));
 
@@ -842,6 +909,38 @@ void pkgDPkgPM::handleDisappearAction(string const &pkgname)
    }
 }
                                                                        /*}}}*/
+void pkgDPkgPM::handleCrossUpgradeAction(string const &pkgname)                /*{{{*/
+{
+   // in a crossgrade what looked like a remove first is really an unpack over it
+   auto const Pkg = Cache.FindPkg(pkgname);
+   if (likely(Pkg.end() == false) && Cache[Pkg].Delete())
+   {
+      auto const Grp = Pkg.Group();
+      if (likely(Grp.end() == false))
+      {
+        for (auto P = Grp.PackageList(); P.end() != true; P = Grp.NextPkg(P))
+           if(Cache[P].Install())
+           {
+              auto && Ops = PackageOps[P.FullName()];
+              auto const unpackOp = std::find_if(Ops.cbegin(), Ops.cend(), [](DpkgState const &s) { return strcmp(s.state, "unpacked") == 0; });
+              if (unpackOp != Ops.cend())
+              {
+                 // skip ahead in the crossgraded packages
+                 auto const skipped = std::distance(Ops.cbegin(), unpackOp);
+                 PackagesDone += skipped;
+                 PackageOpsDone[P.FullName()] += skipped;
+                 // finish the crossremoved package
+                 auto const ROps = PackageOps[Pkg.FullName()].size();
+                 auto && ROpsDone = PackageOpsDone[Pkg.FullName()];
+                 PackagesDone += ROps - ROpsDone;
+                 ROpsDone = ROps;
+                 break;
+              }
+           }
+      }
+   }
+}
+                                                                       /*}}}*/
 // DPkgPM::DoDpkgStatusFd                                              /*{{{*/
 void pkgDPkgPM::DoDpkgStatusFd(int statusfd)
 {
@@ -1085,6 +1184,18 @@ void pkgDPkgPM::BuildPackagesProgressMap()
         ++PackagesTotal;
         return true;
       });
+      if ((I.Op == Item::Remove || I.Op == Item::Purge) && I.Pkg->CurrentVer != 0)
+      {
+        if (I.Pkg->CurrentState == pkgCache::State::UnPacked ||
+              I.Pkg->CurrentState == pkgCache::State::HalfInstalled)
+        {
+           if (likely(strcmp(PackageOps[name][0].state, "half-configured") == 0))
+           {
+              ++PackageOpsDone[name];
+              --PackagesTotal;
+           }
+        }
+      }
    }
    /* one extra: We don't want the progress bar to reach 100%, especially not
       if we call dpkg --configure --pending and process a bunch of triggers
@@ -1274,7 +1385,9 @@ static void cleanUpTmpDir(char * const tmpdir)                            /*{{{*/
         if (unlikely(Ent->d_type != DT_LNK && Ent->d_type != DT_UNKNOWN))
            continue;
 #endif
-        if (unlikely(unlinkat(dfd, Ent->d_name, 0) != 0))
+        char path[strlen(tmpdir) + 1 + strlen(Ent->d_name) + 1];
+        sprintf(path, "%s/%s", tmpdir, Ent->d_name);
+        if (unlikely(unlink(path) != 0))
            break;
       }
       closedir(D);
@@ -1325,7 +1438,8 @@ bool pkgDPkgPM::ExpandPendingCalls(std::vector<Item> &List, pkgDepCache &Cache)
         if (I.Op == Item::Install && alreadyConfigured.insert(I.Pkg->ID).second == true)
            AppendList.emplace_back(Item::Configure, I.Pkg);
       for (auto Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
-        if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure && alreadyConfigured.insert(Pkg->ID).second == true)
+        if (Pkg.State() == pkgCache::PkgIterator::NeedsConfigure &&
+              Cache[Pkg].Delete() == false && alreadyConfigured.insert(Pkg->ID).second == true)
            AppendList.emplace_back(Item::Configure, Pkg);
       std::move(AppendList.begin(), AppendList.end(), std::back_inserter(List));
    }
@@ -1426,11 +1540,30 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
            continue;
 
         auto const Grp = I->Pkg.Group();
-        size_t installedInstances = 0;
+        size_t installedInstances = 0, wannabeInstances = 0;
+        bool multiArchInstances = false;
         for (auto Pkg = Grp.PackageList(); Pkg.end() == false; Pkg = Grp.NextPkg(Pkg))
-           if (Pkg->CurrentVer != 0 || Cache[Pkg].Install())
+        {
+           if (Pkg->CurrentVer != 0)
+           {
               ++installedInstances;
-        if (installedInstances == 2)
+              if (Cache[Pkg].Delete() == false)
+                 ++wannabeInstances;
+           }
+           else if (PackageOps.find(Pkg.FullName()) != PackageOps.end())
+              ++wannabeInstances;
+           if (multiArchInstances == false)
+           {
+              auto const V = Cache[Pkg].InstVerIter(Cache);
+              if (V.end() == false && (Pkg->CurrentVer == 0 || V != Pkg.CurrentVer()))
+                 multiArchInstances = ((V->MultiArch & pkgCache::Version::Same) == pkgCache::Version::Same);
+           }
+        }
+        /* theoretically the installed check would be enough as some wannabe will
+           be first and hence be the crossgrade we were looking for, but #844300
+           prevents this so we keep these situations explicit removes.
+           It is also the reason why neither of them can be a M-A:same package */
+        if (installedInstances == 1 && wannabeInstances == 1 && multiArchInstances == false)
         {
            auto const FirstInstall = std::find_if_not(I, List.end(),
                  [](Item const &i) { return i.Op == Item::Remove || i.Op == Item::Purge; });
@@ -1992,6 +2125,24 @@ bool pkgDPkgPM::Go(APT::Progress::PackageManager *progress)
 
    if (noopDPkgInvocation == false)
    {
+      if (d->dpkg_error.empty() && (PackagesDone + 1) != PackagesTotal)
+      {
+        std::string pkglist;
+        for (auto const &PO: PackageOps)
+           if (PO.second.size() != PackageOpsDone[PO.first])
+           {
+              if (pkglist.empty() == false)
+                 pkglist.append(" ");
+              pkglist.append(PO.first);
+           }
+        /* who cares about correct progress? As we depend on it for skipping actions
+           our parsing should be correct. People will no doubt be confused if they see
+           this message, but the dpkg warning about unknown packages isn't much better
+           from a user POV and combined we might have a chance to figure out what is wrong */
+        _error->Warning("APT had planned for dpkg to do more than it reported back (%u vs %u).\n"
+              "Affected packages: %s", PackagesDone, PackagesTotal, pkglist.c_str());
+      }
+
       std::string const oldpkgcache = _config->FindFile("Dir::cache::pkgcache");
       if (oldpkgcache.empty() == false && RealFileExists(oldpkgcache) == true &&
          RemoveFile("pkgDPkgPM::Go", oldpkgcache))