fix tight loop detection and temporary removes
authorDavid Kalnischkies <david@kalnischkies.de>
Sat, 17 May 2014 10:37:13 +0000 (12:37 +0200)
committerDavid Kalnischkies <david@kalnischkies.de>
Thu, 22 May 2014 15:43:48 +0000 (17:43 +0200)
As outlined in #748355 apt segfaulted if it encountered a loop between a
package pre-depending on a package conflicting with the previous as it
ended up in an endless loop trying to unpack 'the other package'.

In this specific case as an essential package is involved a lot of force
needs to be applied, but can also be caused by 'normal' tight loops and
highlights a problem in how we handle breaks which we want to avoid.

The fix comes in multiple entangled changes:
1. All Smart* calls are guarded with loop detection. Some already had it,
   some had parts of it, some did it incorrect, and some didn't even try.
2. temporary removes to avoid a loop (which is done if a loop is
   detected) prevent the unpack of this looping package (we tried to unpack
   it to avoid the conflict/breaks, but due to a loop we couldn't, so we
   remove/deconfigure it instead which means we can't unpack it now)
3. handle conflicts and breaks very similar instead of duplicating most
   of the code. The only remaining difference is, as it should:
   deconfigure is enough for breaks, for conflicts we need the big hammer

apt-pkg/packagemanager.cc
apt-pkg/packagemanager.h
test/integration/test-bug-618288-multiarch-same-lockstep
test/integration/test-bug-673536-pre-depends-breaks-loop
test/integration/test-conflicts-loop
test/integration/test-essential-force-loopbreak [new file with mode: 0755]

index 56f5c59..393f836 100644 (file)
@@ -261,7 +261,7 @@ bool pkgPackageManager::CheckRConflicts(PkgIterator Pkg,DepIterator D,
       if (Cache.VS().CheckDep(Ver,D->CompareOp,D.TargetVer()) == false)
         continue;
 
-      if (EarlyRemove(D.ParentPkg()) == false)
+      if (EarlyRemove(D.ParentPkg(), &D) == false)
         return _error->Error("Reverse conflicts early remove for package '%s' failed",
                              Pkg.FullName().c_str());
    }
@@ -313,18 +313,41 @@ bool pkgPackageManager::ConfigureAll()
    return true;
 }
                                                                        /*}}}*/
+// PM::NonLoopingSmart - helper to avoid loops while calling Smart methods /*{{{*/
+// -----------------------------------------------------------------------
+/* ensures that a loop of the form A depends B, B depends A (and similar)
+   is not leading us down into infinite recursion segfault land */
+bool pkgPackageManager::NonLoopingSmart(SmartAction const action, pkgCache::PkgIterator &Pkg,
+      pkgCache::PkgIterator DepPkg, int const Depth, bool const PkgLoop,
+      bool * const Bad, bool * const Changed)
+{
+   if (PkgLoop == false)
+      List->Flag(Pkg,pkgOrderList::Loop);
+   bool success = false;
+   switch(action)
+   {
+      case UNPACK_IMMEDIATE: success = SmartUnPack(DepPkg, true, Depth + 1); break;
+      case UNPACK: success = SmartUnPack(DepPkg, false, Depth + 1); break;
+      case CONFIGURE: success = SmartConfigure(DepPkg, Depth + 1); break;
+   }
+   if (PkgLoop == false)
+      List->RmFlag(Pkg,pkgOrderList::Loop);
+
+   if (success == false)
+      return false;
+
+   if (Bad != NULL)
+      *Bad = false;
+   if (Changed != NULL && List->IsFlag(DepPkg,pkgOrderList::Loop) == false)
+      *Changed = true;
+   return true;
+}
+                                                                       /*}}}*/
 // PM::SmartConfigure - Perform immediate configuration of the pkg     /*{{{*/
 // ---------------------------------------------------------------------
 /* This function tries to put the system in a state where Pkg can be configured.
-   This involves checking each of Pkg's dependanies and unpacking and 
-   configuring packages where needed. 
-   
-   Note on failure: This method can fail, without causing any problems. 
-   This can happen when using Immediate-Configure-All, SmartUnPack may call
-   SmartConfigure, it may fail because of a complex dependency situation, but
-   a error will only be reported if ConfigureAll fails. This is why some of the
-   messages this function reports on failure (return false;) as just warnings
-   only shown when debuging*/
+   This involves checking each of Pkg's dependencies and unpacking and
+   configuring packages where needed. */
 bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
 {
    // If this is true, only check and correct and dependencies without the Loop flag
@@ -339,9 +362,9 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
    }
 
    VerIterator const instVer = Cache[Pkg].InstVerIter(Cache);
-      
-   /* Because of the ordered list, most dependencies should be unpacked, 
-      however if there is a loop (A depends on B, B depends on A) this will not 
+
+   /* Because of the ordered list, most dependencies should be unpacked,
+      however if there is a loop (A depends on B, B depends on A) this will not
       be the case, so check for dependencies before configuring. */
    bool Bad = false, Changed = false;
    const unsigned int max_loops = _config->FindI("APT::pkgPackageManager::MaxLoopCount", 5000);
@@ -388,24 +411,15 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
                  if (Debug)
                     std::clog << OutputInDepth(Depth) << "Package " << Pkg << " loops in SmartConfigure" << std::endl;
                  Bad = false;
-                 break;
               }
               else
               {
                  if (Debug)
                     clog << OutputInDepth(Depth) << "Unpacking " << DepPkg.FullName() << " to avoid loop " << Cur << endl;
-                 if (PkgLoop == false)
-                    List->Flag(Pkg,pkgOrderList::Loop);
-                 if (SmartUnPack(DepPkg, true, Depth + 1) == false)
+                 if (NonLoopingSmart(UNPACK_IMMEDIATE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false)
                     return false;
-                 Bad = false;
-                 if (List->IsFlag(DepPkg,pkgOrderList::Loop) == false)
-                    Changed = true;
-                 if (PkgLoop == false)
-                    List->RmFlag(Pkg,pkgOrderList::Loop);
-                 if (Bad == false)
-                    break;
               }
+              break;
            }
 
            if (Cur == End || Bad == false)
@@ -460,22 +474,12 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
                    Bad = false;
                    break;
                  }
-                 /* Check for a loop to prevent one forming
-                      If A depends on B and B depends on A, SmartConfigure will
-                      just hop between them if this is not checked. Dont remove the
-                      loop flag after finishing however as loop is already set.
-                      This means that there is another SmartConfigure call for this
-                      package and it will remove the loop flag */
-                 if (PkgLoop == false)
-                    List->Flag(Pkg,pkgOrderList::Loop);
-                 if (SmartConfigure(DepPkg, Depth + 1) == false)
+                 if (Debug)
+                    std::clog << OutputInDepth(Depth) << "Configure already unpacked " << DepPkg << std::endl;
+                 if (NonLoopingSmart(CONFIGURE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false)
                     return false;
-                 Bad = false;
-                 if (List->IsFlag(DepPkg,pkgOrderList::Loop) == false)
-                    Changed = true;
-                 if (PkgLoop == false)
-                   List->RmFlag(Pkg,pkgOrderList::Loop);
                  break;
+
               }
               else if (List->IsFlag(DepPkg,pkgOrderList::Configured))
               {
@@ -494,16 +498,16 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
       if (i++ > max_loops)
          return _error->Error("Internal error: MaxLoopCount reached in SmartUnPack (2) for %s, aborting", Pkg.FullName().c_str());
    } while (Changed == true);
-   
+
    if (Bad == true)
       return _error->Error(_("Could not configure '%s'. "),Pkg.FullName().c_str());
-   
+
    if (PkgLoop) return true;
 
    static std::string const conf = _config->Find("PackageManager::Configure","all");
    static bool const ConfigurePkgs = (conf == "all" || conf == "smart");
 
-   if (List->IsFlag(Pkg,pkgOrderList::Configured)) 
+   if (List->IsFlag(Pkg,pkgOrderList::Configured))
       return _error->Error("Internal configure error on '%s'.", Pkg.FullName().c_str());
 
    if (ConfigurePkgs == true && Configure(Pkg) == false)
@@ -535,29 +539,37 @@ bool pkgPackageManager::SmartConfigure(PkgIterator Pkg, int const Depth)
 // ---------------------------------------------------------------------
 /* This is called to deal with conflicts arising from unpacking */
 bool pkgPackageManager::EarlyRemove(PkgIterator Pkg)
+{
+   return EarlyRemove(Pkg, NULL);
+}
+bool pkgPackageManager::EarlyRemove(PkgIterator Pkg, DepIterator const * const Dep)
 {
    if (List->IsNow(Pkg) == false)
       return true;
-        
+
    // Already removed it
    if (List->IsFlag(Pkg,pkgOrderList::Removed) == true)
       return true;
-   
+
    // Woops, it will not be re-installed!
    if (List->IsFlag(Pkg,pkgOrderList::InList) == false)
       return false;
 
+   // these breaks on M-A:same packages can be dealt with. They 'loop' by design
+   if (Dep != NULL && (*Dep)->Type == pkgCache::Dep::DpkgBreaks && Dep->IsMultiArchImplicit() == true)
+      return true;
+
    // Essential packages get special treatment
    bool IsEssential = false;
    if ((Pkg->Flags & pkgCache::Flag::Essential) != 0 ||
        (Pkg->Flags & pkgCache::Flag::Important) != 0)
       IsEssential = true;
 
-   /* Check for packages that are the dependents of essential packages and 
+   /* Check for packages that are the dependents of essential packages and
       promote them too */
    if (Pkg->CurrentVer != 0)
    {
-      for (DepIterator D = Pkg.RevDependsList(); D.end() == false &&
+      for (pkgCache::DepIterator D = Pkg.RevDependsList(); D.end() == false &&
           IsEssential == false; ++D)
         if (D->Type == pkgCache::Dep::Depends || D->Type == pkgCache::Dep::PreDepends)
            if ((D.ParentPkg()->Flags & pkgCache::Flag::Essential) != 0 ||
@@ -574,11 +586,14 @@ bool pkgPackageManager::EarlyRemove(PkgIterator Pkg)
                                "but if you really want to do it, activate the "
                                "APT::Force-LoopBreak option."),Pkg.FullName().c_str());
    }
-   
+   // dpkg will auto-deconfigure it, no need for the big remove hammer
+   else if (Dep != NULL && (*Dep)->Type == pkgCache::Dep::DpkgBreaks)
+      return true;
+
    bool Res = SmartRemove(Pkg);
    if (Cache[Pkg].Delete() == false)
       List->Flag(Pkg,pkgOrderList::Removed,pkgOrderList::States);
-   
+
    return Res;
 }
                                                                        /*}}}*/
@@ -623,13 +638,14 @@ bool pkgPackageManager::SmartUnPack(PkgIterator Pkg, bool const Immediate, int c
 
    VerIterator const instVer = Cache[Pkg].InstVerIter(Cache);
 
-   /* PreUnpack Checks: This loop checks and attempts to rectify and problems that would prevent the package being unpacked.
+   /* PreUnpack Checks: This loop checks and attempts to rectify any problems that would prevent the package being unpacked.
       It addresses: PreDepends, Conflicts, Obsoletes and Breaks (DpkgBreaks). Any resolutions that do not require it should
       avoid configuration (calling SmartUnpack with Immediate=true), this is because when unpacking some packages with
-      complex dependency structures, trying to configure some packages while breaking the loops can complicate things .
+      complex dependency structures, trying to configure some packages while breaking the loops can complicate things.
       This will be either dealt with if the package is configured as a dependency of Pkg (if and when Pkg is configured),
       or by the ConfigureAll call at the end of the for loop in OrderInstall. */
-   bool Changed = false;
+   bool SomethingBad = false, Changed = false;
+   bool couldBeTemporaryRemoved = Depth != 0 && List->IsFlag(Pkg,pkgOrderList::Removed) == false;
    const unsigned int max_loops = _config->FindI("APT::pkgPackageManager::MaxLoopCount", 5000);
    unsigned int i = 0;
    do 
@@ -677,183 +693,142 @@ bool pkgPackageManager::SmartUnPack(PkgIterator Pkg, bool const Immediate, int c
               for (Version **I = VList; *I != 0; ++I)
               {
                  VerIterator Ver(Cache,*I);
-                 PkgIterator Pkg = Ver.ParentPkg();
+                 PkgIterator DepPkg = Ver.ParentPkg();
 
                  // Not the install version
-                 if (Cache[Pkg].InstallVer != *I ||
-                     (Cache[Pkg].Keep() == true && Pkg.State() == PkgIterator::NeedsNothing))
+                 if (Cache[DepPkg].InstallVer != *I ||
+                     (Cache[DepPkg].Keep() == true && DepPkg.State() == PkgIterator::NeedsNothing))
                     continue;
 
-                 if (List->IsFlag(Pkg,pkgOrderList::Configured))
+                 if (List->IsFlag(DepPkg,pkgOrderList::Configured))
                  {
                     Bad = false;
                     break;
                  }
 
                  // check if it needs unpack or if if configure is enough
-                 if (List->IsFlag(Pkg,pkgOrderList::UnPacked) == false)
+                 if (List->IsFlag(DepPkg,pkgOrderList::UnPacked) == false)
                  {
                     if (Debug)
-                       clog << OutputInDepth(Depth) << "Trying to SmartUnpack " << Pkg.FullName() << endl;
-                    // SmartUnpack with the ImmediateFlag to ensure its really ready
-                    if (SmartUnPack(Pkg, true, Depth + 1) == false)
+                       clog << OutputInDepth(Depth) << "Trying to SmartUnpack " << DepPkg.FullName() << endl;
+                    if (NonLoopingSmart(UNPACK_IMMEDIATE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false)
                        return false;
-                    Bad = false;
-                    if (List->IsFlag(Pkg,pkgOrderList::Loop) == false)
-                       Changed = true;
-                    break;
                  }
                  else
                  {
                     if (Debug)
-                       clog << OutputInDepth(Depth) << "Trying to SmartConfigure " << Pkg.FullName() << endl;
-                    if (SmartConfigure(Pkg, Depth + 1) == false)
+                       clog << OutputInDepth(Depth) << "Trying to SmartConfigure " << DepPkg.FullName() << endl;
+                    if (NonLoopingSmart(CONFIGURE, Pkg, DepPkg, Depth, PkgLoop, &Bad, &Changed) == false)
                        return false;
-                    Bad = false;
-                    if (List->IsFlag(Pkg,pkgOrderList::Loop) == false)
-                       Changed = true;
-                    break;
                  }
+                 break;
               }
            }
 
            if (Bad == true)
-           {
-              if (Start == End)
-                 return _error->Error("Couldn't configure pre-depend %s for %s, "
-                                       "probably a dependency cycle.",
-                                       End.TargetPkg().FullName().c_str(),Pkg.FullName().c_str());
-           }
-           else
-              continue;
+              SomethingBad = true;
         }
         else if (End->Type == pkgCache::Dep::Conflicts ||
-                 End->Type == pkgCache::Dep::Obsoletes)
+                 End->Type == pkgCache::Dep::Obsoletes ||
+                 End->Type == pkgCache::Dep::DpkgBreaks)
         {
-           /* Look for conflicts. Two packages that are both in the install
-              state cannot conflict so we don't check.. */
            SPtrArray<Version *> VList = End.AllTargets();
-           for (Version **I = VList; *I != 0; I++)
+           for (Version **I = VList; *I != 0; ++I)
            {
               VerIterator Ver(Cache,*I);
               PkgIterator ConflictPkg = Ver.ParentPkg();
-              VerIterator InstallVer(Cache,Cache[ConflictPkg].InstallVer);
+              if (ConflictPkg.CurrentVer() != Ver)
+              {
+                 if (Debug)
+                    std::clog << OutputInDepth(Depth) << "Ignore not-installed version " << Ver.VerStr() << " of " << ConflictPkg.FullName() << " for " << End << std::endl;
+                 continue;
+              }
+
+              if (List->IsNow(ConflictPkg) == false)
+              {
+                 if (Debug)
+                    std::clog << OutputInDepth(Depth) << "Ignore already dealt-with version " << Ver.VerStr() << " of " << ConflictPkg.FullName() << " for " << End << std::endl;
+                 continue;
+              }
 
-              // See if the current version is conflicting
-              if (ConflictPkg.CurrentVer() == Ver && List->IsNow(ConflictPkg))
+              if (List->IsFlag(ConflictPkg,pkgOrderList::Removed) == true)
               {
                  if (Debug)
-                    clog << OutputInDepth(Depth) << Pkg.FullName() << " conflicts with " << ConflictPkg.FullName() << endl;
-                 /* If a loop is not present or has not yet been detected, attempt to unpack packages
-                    to resolve this conflict. If there is a loop present, remove packages to resolve this conflict */
-                 if (List->IsFlag(ConflictPkg,pkgOrderList::Loop) == false)
+                    clog << OutputInDepth(Depth) << "Ignoring " << End << " as " << ConflictPkg.FullName() << "was temporarily removed" << endl;
+                 continue;
+              }
+
+              if (List->IsFlag(ConflictPkg,pkgOrderList::Loop) && PkgLoop)
+              {
+                 if (End->Type == pkgCache::Dep::DpkgBreaks && End.IsMultiArchImplicit() == true)
                  {
-                    if (Cache[ConflictPkg].Keep() == 0 && Cache[ConflictPkg].InstallVer != 0)
-                    {
-                       if (Debug)
-                          clog << OutputInDepth(Depth) << "Unpacking " << ConflictPkg.FullName() << " to prevent conflict" << endl;
-                       List->Flag(Pkg,pkgOrderList::Loop);
-                       if (SmartUnPack(ConflictPkg,false, Depth + 1) == false)
-                          return false;
-                       if (List->IsFlag(ConflictPkg,pkgOrderList::Loop) == false)
-                          Changed = true;
-                       // Remove loop to allow it to be used later if needed
-                       List->RmFlag(Pkg,pkgOrderList::Loop);
-                    }
-                    else if (EarlyRemove(ConflictPkg) == false)
-                       return _error->Error("Internal Error, Could not early remove %s (1)",ConflictPkg.FullName().c_str());
+                    if (Debug)
+                       clog << OutputInDepth(Depth) << "Because dependency is MultiArchImplicit we ignored looping on: " << ConflictPkg << endl;
+                    continue;
                  }
-                 else if (List->IsFlag(ConflictPkg,pkgOrderList::Removed) == false)
+                 if (Debug)
                  {
-                    if (Debug)
+                    if (End->Type == pkgCache::Dep::DpkgBreaks)
+                       clog << OutputInDepth(Depth) << "Because of breaks knot, deconfigure " << ConflictPkg.FullName() << " temporarily" << endl;
+                    else
                        clog << OutputInDepth(Depth) << "Because of conflict knot, removing " << ConflictPkg.FullName() << " temporarily" << endl;
-                    if (EarlyRemove(ConflictPkg) == false)
-                       return _error->Error("Internal Error, Could not early remove %s (2)",ConflictPkg.FullName().c_str());
                  }
-              }
-           }
-        }
-        else if (End->Type == pkgCache::Dep::DpkgBreaks)
-        {
-           SPtrArray<Version *> VList = End.AllTargets();
-           for (Version **I = VList; *I != 0; ++I)
-           {
-              VerIterator Ver(Cache,*I);
-              PkgIterator BrokenPkg = Ver.ParentPkg();
-              if (BrokenPkg.CurrentVer() != Ver)
-              {
-                 if (Debug)
-                    std::clog << OutputInDepth(Depth) << "  Ignore not-installed version " << Ver.VerStr() << " of " << Pkg.FullName() << " for " << End << std::endl;
+                 if (EarlyRemove(ConflictPkg, &End) == false)
+                    return _error->Error("Internal Error, Could not early remove %s (2)",ConflictPkg.FullName().c_str());
+                 SomethingBad = true;
                  continue;
               }
 
-              // Check if it needs to be unpacked
-              if (List->IsFlag(BrokenPkg,pkgOrderList::InList) && Cache[BrokenPkg].Delete() == false &&
-                  List->IsNow(BrokenPkg))
+              if (Cache[ConflictPkg].Delete() == false)
               {
-                 if (List->IsFlag(BrokenPkg,pkgOrderList::Loop) && PkgLoop)
+                 if (Debug)
                  {
-                    // This dependency has already been dealt with by another SmartUnPack on Pkg
-                    break;
+                    clog << OutputInDepth(Depth) << "Unpacking " << ConflictPkg.FullName() << " to avoid " << End;
+                    if (PkgLoop == true)
+                       clog << " (Looping)";
+                    clog << std::endl;
                  }
-                 else
+                 // we would like to avoid temporary removals and all that at best via a simple unpack
+                 _error->PushToStack();
+                 if (NonLoopingSmart(UNPACK, Pkg, ConflictPkg, Depth, PkgLoop, NULL, &Changed) == false)
                  {
-                    // Found a break, so see if we can unpack the package to avoid it
-                    // but do not set loop if another SmartUnPack already deals with it
-                    // Also, avoid it if the package we would unpack pre-depends on this one
-                    VerIterator InstallVer(Cache,Cache[BrokenPkg].InstallVer);
-                    bool circle = false;
-                    for (pkgCache::DepIterator D = InstallVer.DependsList(); D.end() == false; ++D)
+                    // but if it fails ignore this failure and look for alternative ways of solving
+                    if (Debug)
                     {
-                       if (D->Type != pkgCache::Dep::PreDepends)
-                          continue;
-                       SPtrArray<Version *> VL = D.AllTargets();
-                       for (Version **I = VL; *I != 0; ++I)
-                       {
-                          VerIterator V(Cache,*I);
-                          PkgIterator P = V.ParentPkg();
-                          // we are checking for installation as an easy 'protection' against or-groups and (unchosen) providers
-                          if (P != Pkg || (P.CurrentVer() != V && Cache[P].InstallVer != V))
-                             continue;
-                          circle = true;
-                          break;
-                       }
-                       if (circle == true)
-                          break;
+                       clog << OutputInDepth(Depth) << "Avoidance unpack of " << ConflictPkg.FullName() << " failed for " << End << std::endl;
+                       _error->DumpErrors(std::clog);
+                    }
+                    _error->RevertToStack();
+                    // ignorance can only happen if a) one of the offenders is already gone
+                    if (List->IsFlag(ConflictPkg,pkgOrderList::Removed) == true)
+                    {
+                       if (Debug)
+                          clog << OutputInDepth(Depth) << "But " << ConflictPkg.FullName() << " was temporarily removed in the meantime to satisfy " << End << endl;
                     }
-                    if (circle == true)
+                    else if (List->IsFlag(Pkg,pkgOrderList::Removed) == true)
                     {
                        if (Debug)
-                          clog << OutputInDepth(Depth) << "  Avoiding " << End << " avoided as " << BrokenPkg.FullName() << " has a pre-depends on " << Pkg.FullName() << std::endl;
-                       continue;
+                          clog << OutputInDepth(Depth) << "But " << Pkg.FullName() << " was temporarily removed in the meantime to satisfy " << End << endl;
                     }
+                    // or b) we can make one go (removal or dpkg auto-deconfigure)
                     else
                     {
                        if (Debug)
-                       {
-                          clog << OutputInDepth(Depth) << "  Unpacking " << BrokenPkg.FullName() << " to avoid " << End;
-                          if (PkgLoop == true)
-                             clog << " (Looping)";
-                          clog << std::endl;
-                       }
-                       if (PkgLoop == false)
-                          List->Flag(Pkg,pkgOrderList::Loop);
-                       if (SmartUnPack(BrokenPkg, false, Depth + 1) == false)
-                          return false;
-                       if (List->IsFlag(BrokenPkg,pkgOrderList::Loop) == false)
-                          Changed = true;
-                       if (PkgLoop == false)
-                          List->RmFlag(Pkg,pkgOrderList::Loop);
+                          clog << OutputInDepth(Depth) << "So temprorary remove/deconfigure " << ConflictPkg.FullName() << " to satisfy " << End << endl;
+                       if (EarlyRemove(ConflictPkg, &End) == false)
+                          return _error->Error("Internal Error, Could not early remove %s (2)",ConflictPkg.FullName().c_str());
                     }
                  }
+                 else
+                    _error->MergeWithStack();
               }
-              // Check if a package needs to be removed
-              else if (Cache[BrokenPkg].Delete() == true && List->IsFlag(BrokenPkg,pkgOrderList::Configured) == false)
+              else
               {
                  if (Debug)
-                    clog << OutputInDepth(Depth) << "  Removing " << BrokenPkg.FullName() << " to avoid " << End << endl;
-                 if (SmartRemove(BrokenPkg) == false)
-                    return false;
+                    clog << OutputInDepth(Depth) << "Removing " << ConflictPkg.FullName() << " now to avoid " << End << endl;
+                 // no earlyremove() here as user has already agreed to the permanent removal
+                 if (SmartRemove(Pkg) == false)
+                    return _error->Error("Internal Error, Could not early remove %s (1)",ConflictPkg.FullName().c_str());
               }
            }
         }
@@ -861,7 +836,17 @@ bool pkgPackageManager::SmartUnPack(PkgIterator Pkg, bool const Immediate, int c
       if (i++ > max_loops)
          return _error->Error("Internal error: APT::pkgPackageManager::MaxLoopCount reached in SmartConfigure for %s, aborting", Pkg.FullName().c_str());
    } while (Changed == true);
-   
+
+   if (SomethingBad == true)
+      return _error->Error("Couldn't configure %s, probably a dependency cycle.", Pkg.FullName().c_str());
+
+   if (couldBeTemporaryRemoved == true && List->IsFlag(Pkg,pkgOrderList::Removed) == true)
+   {
+      if (Debug)
+        std::clog << OutputInDepth(Depth) << "Prevent unpack as " << Pkg << " is currently temporarily removed" << std::endl;
+      return true;
+   }
+
    // Check for reverse conflicts.
    if (CheckRConflicts(Pkg,Pkg.RevDependsList(),
                   instVer.VerStr()) == false)
@@ -922,7 +907,7 @@ bool pkgPackageManager::SmartUnPack(PkgIterator Pkg, bool const Immediate, int c
    if (Immediate == true) {
       // Perform immedate configuration of the package. 
          if (SmartConfigure(Pkg, Depth + 1) == false)
-            _error->Warning(_("Could not perform immediate configuration on '%s'. "
+            _error->Error(_("Could not perform immediate configuration on '%s'. "
                "Please see man 5 apt.conf under APT::Immediate-Configure for details. (%d)"),Pkg.FullName().c_str(),2);
    }
    
index d690644..d72790b 100644 (file)
@@ -84,8 +84,9 @@ class pkgPackageManager : protected pkgCache::Namespace
    bool SmartUnPack(PkgIterator Pkg) APT_MUSTCHECK;
    bool SmartUnPack(PkgIterator Pkg, bool const Immediate, int const Depth) APT_MUSTCHECK;
    bool SmartRemove(PkgIterator Pkg) APT_MUSTCHECK;
-   bool EarlyRemove(PkgIterator Pkg) APT_MUSTCHECK;
-   
+   bool EarlyRemove(PkgIterator Pkg, DepIterator const * const Dep) APT_MUSTCHECK;
+   APT_DEPRECATED bool EarlyRemove(PkgIterator Pkg) APT_MUSTCHECK;
+
    // The Actual installation implementation
    virtual bool Install(PkgIterator /*Pkg*/,std::string /*File*/) {return false;};
    virtual bool Configure(PkgIterator /*Pkg*/) {return false;};
@@ -139,6 +140,12 @@ class pkgPackageManager : protected pkgCache::Namespace
 
    pkgPackageManager(pkgDepCache *Cache);
    virtual ~pkgPackageManager();
+
+   private:
+   enum APT_HIDDEN SmartAction { UNPACK_IMMEDIATE, UNPACK, CONFIGURE };
+   APT_HIDDEN bool NonLoopingSmart(SmartAction const action, pkgCache::PkgIterator &Pkg,
+      pkgCache::PkgIterator DepPkg, int const Depth, bool const PkgLoop,
+      bool * const Bad, bool * const Changed) APT_MUSTCHECK;
 };
 
 #endif
index e0305b6..536124c 100755 (executable)
@@ -16,22 +16,23 @@ buildsimplenativepackage 'apt' 'i386' '2' 'unstable' 'Depends: libsame (= 2)' ''
 buildsimplenativepackage 'apt2' 'amd64' '2' 'unstable' 'Depends: libsame (= 2)' '' 'required'
 
 setupaptarchive
-aptget dist-upgrade -s >output.apt 2>&1
+testsuccess aptget dist-upgrade -s -o Debug::pkgPackageManager=1
 
 # order in switch libsame:{amd64,i386} are unpacked is irrelevant, as both are installed - but we need to do it together
-LS_U_AMD="$(grep -o -n '^Inst libsame ' output.apt | cut -d: -f1)"
-LS_U_INT="$(grep -o -n '^Inst libsame:i386 ' output.apt | cut -d: -f1)"
-LS_C_AMD="$(grep -o -n '^Conf libsame ' output.apt | cut -d: -f1)"
-LS_C_INT="$(grep -o -n '^Conf libsame:i386 ' output.apt | cut -d: -f1)"
+OUTPUT=rootdir/tmp/testsuccess.output
+LS_U_AMD="$(grep -o -n '^Inst libsame ' $OUTPUT | cut -d: -f1)"
+LS_U_INT="$(grep -o -n '^Inst libsame:i386 ' $OUTPUT | cut -d: -f1)"
+LS_C_AMD="$(grep -o -n '^Conf libsame ' $OUTPUT | cut -d: -f1)"
+LS_C_INT="$(grep -o -n '^Conf libsame:i386 ' $OUTPUT | cut -d: -f1)"
 
-msgtest 'Test if libsame:amd64 unpack before configure'
+msgtest 'Test if' 'libsame:amd64 unpack before configure'
 test "$LS_U_AMD" -lt "$LS_C_AMD" && msgpass || msgfail
 
-msgtest 'Test if libsame:i386 unpack before configure'
+msgtest 'Test if' 'libsame:i386 unpack before configure'
 test "$LS_U_INT" -lt "$LS_C_INT" && msgpass || msgfail
 
-msgtest 'Test if libsame:amd64 unpack is before libsame:i386 configure'
+msgtest 'Test if' 'libsame:amd64 unpack is before libsame:i386 configure'
 test "$LS_U_AMD" -lt "$LS_C_INT" && msgpass || msgfail
 
-msgtest 'Test if libsame:i386 unpack is before libsame:amd64 configure'
+msgtest 'Test if' 'libsame:i386 unpack is before libsame:amd64 configure'
 test "$LS_U_INT" -lt "$LS_C_AMD" && msgpass || msgfail
index f6a90b2..21bd5e0 100755 (executable)
@@ -6,18 +6,32 @@ TESTDIR=$(readlink -f $(dirname $0))
 setupenvironment
 configarchitecture 'native'
 
-buildsimplenativepackage 'basic' 'native' '1' 'stable'
+buildsimplenativepackage 'advanced' 'native' '1' 'stable'
+buildsimplenativepackage 'advanced' 'native' '2' 'unstable' 'Pre-Depends: basic'
 buildsimplenativepackage 'basic' 'native' '2' 'unstable' 'Pre-Depends: common'
-buildsimplenativepackage 'common' 'native' '2' 'unstable' 'Breaks: basic (<= 1)'
+
+buildsimplenativepackage 'common' 'native' '2~conflict' 'unstable-conflict' 'Conflicts: advanced (<= 1)'
+buildsimplenativepackage 'common' 'native' '2~break' 'unstable-break' 'Conflicts: advanced (<= 1)'
 
 setupaptarchive
 
 # we check with 'real' packages here as the simulation reports a 'Conf broken'
 # which is technical correct for the simulation, but testing errormsg is ugly
 
-testsuccess aptget install basic=1 -y
-testdpkginstalled basic
-testdpkgnotinstalled common
+cp -a rootdir/var/lib/dpkg/status dpkg.status.backup
+
+testloopbreak() {
+       cp -a dpkg.status.backup rootdir/var/lib/dpkg/status
+       rm -f rootdir/var/lib/apt/extended_states
+
+
+       testsuccess aptget install advanced=1 -y -t "$1" -o Debug::pkgPackageManager=1
+       testdpkginstalled advanced
+       testdpkgnotinstalled basic common
+
+       testsuccess aptget dist-upgrade -y -t "$1" -o Debug::pkgPackageManager=1
+       testdpkginstalled advanced basic common
+}
 
-testsuccess aptget dist-upgrade -y
-testdpkginstalled basic common
+testloopbreak 'unstable-break'
+testloopbreak 'unstable-conflict'
index 4978fe1..a2c411a 100755 (executable)
@@ -20,11 +20,13 @@ Building dependency tree...
 The following packages will be upgraded:
   openjdk-6-jre openjdk-6-jre-headless openjdk-6-jre-lib
 3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
-Remv openjdk-6-jre [6b16-1.8-0ubuntu1]
+Remv openjdk-6-jre-headless [6b16-1.8-0ubuntu1]
 Remv openjdk-6-jre-lib [6b16-1.8-0ubuntu1]
-Inst openjdk-6-jre-headless [6b16-1.8-0ubuntu1] (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
 Inst openjdk-6-jre [6b16-1.8-0ubuntu1] (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
 Inst openjdk-6-jre-lib [6b16-1.8-0ubuntu1] (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
 Conf openjdk-6-jre-lib (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
 Conf openjdk-6-jre (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
+Inst openjdk-6-jre-headless [6b16-1.8-0ubuntu1] (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])
 Conf openjdk-6-jre-headless (6b20-1.9.8-0ubuntu1~10.04.1 unstable [i386])' aptget dist-upgrade -s -o APT::Immediate-Configure-All=true
+
+testsuccess aptget dist-upgrade -s -o Debug::pkgPackageManager=1
diff --git a/test/integration/test-essential-force-loopbreak b/test/integration/test-essential-force-loopbreak
new file mode 100755 (executable)
index 0000000..842dce6
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+set -e
+
+TESTDIR=$(readlink -f $(dirname $0))
+. $TESTDIR/framework
+
+setupenvironment
+configarchitecture 'amd64'
+
+insertinstalledpackage 'sysvinit' 'amd64' '1' 'Essential: yes'
+
+buildsimplenativepackage 'sysvinit' 'amd64' '2' 'sid' 'Pre-Depends: sysvinit-core | systemd-sysv
+Essential: yes'
+buildsimplenativepackage 'sysvinit-core' 'amd64' '2' 'sid'
+
+buildsimplenativepackage 'systemd-sysv' 'amd64' '2~conflict' 'sid-conflict' 'Conflicts: sysvinit (<< 2)
+Breaks: sysvinit-core'
+
+buildsimplenativepackage 'systemd-sysv' 'amd64' '2~break' 'sid-break' 'Breaks: sysvinit (<< 2), sysvinit-core'
+
+setupaptarchive
+
+cp -a rootdir/var/lib/dpkg/status dpkg.status.backup
+
+testforcebreak() {
+       cp -a dpkg.status.backup rootdir/var/lib/dpkg/status
+       rm -f rootdir/var/lib/apt/extended_states
+       testequal 'Reading package lists...
+Building dependency tree...
+The following extra packages will be installed:
+  sysvinit
+The following NEW packages will be installed:
+  systemd-sysv
+The following packages will be upgraded:
+  sysvinit
+1 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
+E: This installation run will require temporarily removing the essential package sysvinit:amd64 due to a Conflicts/Pre-Depends loop. This is often bad, but if you really want to do it, activate the APT::Force-LoopBreak option.
+E: Internal Error, Could not early remove sysvinit:amd64 (2)' aptget install systemd-sysv -t "$1" -s
+       # ensure that really nothing happens
+       testfailure aptget install systemd-sysv -y -t "$1" -o Debug::pkgPackageManager=1
+       testdpkginstalled 'sysvinit'
+       testdpkgnotinstalled 'systemd-sysv'
+
+       # with enough force however …
+       cp -a dpkg.status.backup rootdir/var/lib/dpkg/status
+       testsuccess aptget install systemd-sysv -y -t "$1" -o Debug::pkgPackageManager=1 -o APT::Force-LoopBreak=1
+       testdpkginstalled 'sysvinit' 'systemd-sysv'
+}
+
+testforcebreak 'sid-conflict'
+testforcebreak 'sid-break'