Skip to content

Commit

Permalink
Mark only provides from protected versioned kernel packages
Browse files Browse the repository at this point in the history
An out-of-tree kernel module which doesn't see many new versions can
pile up a considerable amount of packages if it is depended on via
another packages (e.g.: v4l2loopback-utils recommends v4l2loopback-modules)
which in turn can prevent the old kernels from being removed if they
happen to have a dependency on the images.

To prevent this we check if a provider is a versioned kernel package
(like an out-of-tree module) and if so check if that module package is
part of the protected kernel set – if not it is probably good to go.

We only do this if at least one provider is from a protected kernel
though so that the dependency remains satisfied (this can happen e.g. if
the module is currently not buildable against a protected kernel).
  • Loading branch information
DonKult committed Apr 25, 2021
1 parent 408f4e0 commit acc5502
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 26 deletions.
84 changes: 58 additions & 26 deletions apt-pkg/depcache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <apt-pkg/algorithms.h>
#include <apt-pkg/aptconfiguration.h>
#include <apt-pkg/cachefile.h>
#include <apt-pkg/cachefilter.h>
#include <apt-pkg/cacheset.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/depcache.h>
Expand All @@ -28,10 +29,13 @@

#include <algorithm>
#include <iostream>
#include <memory>
#include <sstream>
#include <iterator>
#include <list>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <stdio.h>
Expand Down Expand Up @@ -2295,6 +2299,8 @@ void pkgDepCache::MarkPackage(const pkgCache::PkgIterator &Pkg,
std::clog << "Marking: " << Pkg.FullName() << " " << Ver.VerStr()
<< " (" << reason << ")" << std::endl;

std::unique_ptr<APT::CacheFilter::Matcher> IsAVersionedKernelPackage, IsProtectedKernelPackage;

for (auto D = Ver.DependsList(); D.end() == false; ++D)
{
auto const T = D.TargetPkg();
Expand All @@ -2308,7 +2314,7 @@ void pkgDepCache::MarkPackage(const pkgCache::PkgIterator &Pkg,
continue;

// handle the virtual part first
APT::VersionVector providers;
std::unordered_map<std::string, APT::VersionVector> providers_by_source;
for(auto Prv = T.ProvidesList(); Prv.end() == false; ++Prv)
{
auto PP = Prv.OwnerPkg();
Expand All @@ -2321,36 +2327,62 @@ void pkgDepCache::MarkPackage(const pkgCache::PkgIterator &Pkg,
if (unlikely(PV.end()) || PV != Prv.OwnerVer() || D.IsSatisfied(Prv) == false)
continue;

providers.emplace_back(PV);
providers_by_source[PV.SourcePkgName()].push_back(PV);
}
if (providers.empty() == false)
if (providers_by_source.empty() == false)
{
// sort providers by source version so that only the latest versioned
// binary package of a source package is marked instead of all
std::sort(providers.begin(), providers.end(),
[](pkgCache::VerIterator const &A, pkgCache::VerIterator const &B) {
auto const nameret = strcmp(A.SourcePkgName(), B.SourcePkgName());
if (nameret != 0)
return nameret < 0;
auto const verret = A.Cache()->VS->CmpVersion(A.SourceVerStr(), B.SourceVerStr());
if (verret != 0)
return verret > 0;
return strcmp(A.ParentPkg().Name(), B.ParentPkg().Name()) < 0;
});
auto const prvsize = providers.size();
providers.erase(std::unique(providers.begin(), providers.end(),
[](pkgCache::VerIterator const &A, pkgCache::VerIterator const &B) {
return strcmp(A.SourcePkgName(), B.SourcePkgName()) == 0 &&
strcmp(A.SourceVerStr(), B.SourceVerStr()) != 0;
}), providers.end());
for (auto && PV: providers)
auto const sort_by_version = [](pkgCache::VerIterator const &A, pkgCache::VerIterator const &B) {
auto const verret = A.Cache()->VS->CmpVersion(A.SourceVerStr(), B.SourceVerStr());
if (verret != 0)
return verret > 0;
return A->ID < B->ID;
};
for (auto &providers : providers_by_source)
{
auto const PP = PV.ParentPkg();
if (debug_autoremove)
std::clog << "Following dep: " << APT::PrettyDep(this, D)
<< ", provided by " << PP.FullName() << " " << PV.VerStr()
<< " (" << providers.size() << "/" << prvsize << ")"<< std::endl;
MarkPackage(PP, PV, follow_recommends, follow_suggests, "Provider");
std::sort(providers.second.begin(), providers.second.end(), sort_by_version);
auto const highestSrcVer = (*providers.second.begin()).SourceVerStr();
auto notThisVer = std::find_if_not(providers.second.begin(), providers.second.end(), [&](auto const &V) { return strcmp(highestSrcVer, V.SourceVerStr()) == 0; });
if (notThisVer != providers.second.end())
providers.second.erase(notThisVer, providers.second.end());
// if the provider is a versioned kernel package mark them only for protected kernels
if (providers.second.size() == 1)
continue;
if (not IsAVersionedKernelPackage)
IsAVersionedKernelPackage = [&]() -> std::unique_ptr<APT::CacheFilter::Matcher> {
auto const patterns = _config->FindVector("APT::VersionedKernelPackages");
if (patterns.empty())
return std::make_unique<APT::CacheFilter::FalseMatcher>();
std::ostringstream regex;
regex << '^';
std::copy(patterns.begin(), patterns.end() - 1, std::ostream_iterator<std::string>(regex, "-.*$|^"));
regex << patterns.back() << "-.*$";
return std::make_unique<APT::CacheFilter::PackageNameMatchesRegEx>(regex.str());
}();
if (not std::all_of(providers.second.begin(), providers.second.end(), [&](auto const &Prv) { return (*IsAVersionedKernelPackage)(Prv.ParentPkg()); }))
continue;
// … if there is at least one for protected kernels installed
if (not IsProtectedKernelPackage)
IsProtectedKernelPackage = APT::KernelAutoRemoveHelper::GetProtectedKernelsFilter(Cache);
if (not std::any_of(providers.second.begin(), providers.second.end(), [&](auto const &Prv) { return (*IsProtectedKernelPackage)(Prv.ParentPkg()); }))
continue;
providers.second.erase(std::remove_if(providers.second.begin(), providers.second.end(),
[&](auto const &Prv) { return not((*IsProtectedKernelPackage)(Prv.ParentPkg())); }),
providers.second.end());
}

for (auto const &providers : providers_by_source)
{
for (auto const &PV : providers.second)
{
auto const PP = PV.ParentPkg();
if (debug_autoremove)
std::clog << "Following dep: " << APT::PrettyDep(this, D)
<< ", provided by " << PP.FullName() << " " << PV.VerStr()
<< " (" << providers_by_source.size() << "/" << providers.second.size() << ")\n";
MarkPackage(PP, PV, follow_recommends, follow_suggests, "Provider");
}
}
}

Expand Down
103 changes: 103 additions & 0 deletions test/integration/test-apt-get-autoremove-kernel-module-providers
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/bin/sh
set -e

TESTDIR="$(readlink -f "$(dirname "$0")")"
. "$TESTDIR/framework"
setupenvironment
configarchitecture 'amd64'

insertinstalledpackage 'old-kernel-modules-0.8' 'all' '1' 'Depends: linux-image-0.8
Provides: old-module
Source: kernel-module'
insertinstalledpackage 'old-kernel-modules-0.9' 'all' '1' 'Depends: linux-image-0.9
Provides: old-module
Source: kernel-module'
insertinstalledpackage 'kernel-modules-1.0' 'all' '1' 'Depends: linux-image-1.0
Provides: module
Source: kernel-module'
insertinstalledpackage 'kernel-modules-1.1' 'all' '2' 'Depends: linux-image-1.1
Provides: module
Source: kernel-module'
insertinstalledpackage 'kernel-modules-1.2' 'all' '2' 'Depends: linux-image-1.2
Provides: module
Source: kernel-module'
insertinstalledpackage 'kernel-modules-2.0' 'all' '2' 'Depends: linux-image-2.0
Provides: module
Source: kernel-module'
insertinstalledpackage 'kernel-modules-2.1' 'all' '2' 'Depends: linux-image-2.1
Provides: module
Source: kernel-module'
insertinstalledpackage 'texteditor-gtk' 'all' '2' 'Provides: editor
Source: texteditor'
insertinstalledpackage 'texteditor-kde' 'all' '2' 'Provides: editor
Source: texteditor'

insertinstalledpackage 'linux-image-0.8' 'all' '0.8' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-0.9' 'all' '0.9' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-1.0' 'all' '1.0' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-1.1' 'all' '1.1' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-1.2' 'all' '1.2' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-2.0' 'all' '2.0' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'linux-image-2.1' 'all' '2.1' 'Provides: linux-image
Source: linux-image'
insertinstalledpackage 'has-needs' 'all' '1' 'Depends: editor, module, linux-image'
insertinstalledpackage 'old-needs' 'all' '1' 'Depends: old-module'

testsuccess aptmark auto 'linux-image-*' 'old-kernel-modules-*' 'kernel-modules-*' 'texteditor-*'
testsuccessequal 'kernel-modules-1.0
kernel-modules-1.1
kernel-modules-1.2
kernel-modules-2.0
kernel-modules-2.1
linux-image-0.8
linux-image-0.9
linux-image-1.0
linux-image-1.1
linux-image-1.2
linux-image-2.0
linux-image-2.1
old-kernel-modules-0.8
old-kernel-modules-0.9
texteditor-gtk
texteditor-kde' aptmark showauto

testsuccess aptget check -s
testsuccessequal 'Reading package lists...
Building dependency tree...
Reading state information...
The following packages will be REMOVED:
kernel-modules-1.0 kernel-modules-1.1 kernel-modules-1.2 linux-image-1.0
linux-image-1.1 linux-image-1.2
0 upgraded, 0 newly installed, 6 to remove and 0 not upgraded.
Remv kernel-modules-1.0 [1]
Remv kernel-modules-1.1 [2]
Remv kernel-modules-1.2 [2]
Remv linux-image-1.0 [1.0]
Remv linux-image-1.1 [1.1]
Remv linux-image-1.2 [1.2]' apt autoremove -s

testsuccessequal 'Reading package lists...
Building dependency tree...
Reading state information...
The following packages will be REMOVED:
kernel-modules-1.0 kernel-modules-1.1 kernel-modules-1.2 linux-image-0.8
linux-image-0.9 linux-image-1.0 linux-image-1.1 linux-image-1.2
old-kernel-modules-0.8 old-kernel-modules-0.9 old-needs
0 upgraded, 0 newly installed, 11 to remove and 0 not upgraded.
Remv kernel-modules-1.0 [1]
Remv kernel-modules-1.1 [2]
Remv kernel-modules-1.2 [2]
Remv old-needs [1]
Remv old-kernel-modules-0.8 [1]
Remv linux-image-0.8 [0.8]
Remv old-kernel-modules-0.9 [1]
Remv linux-image-0.9 [0.9]
Remv linux-image-1.0 [1.0]
Remv linux-image-1.1 [1.1]
Remv linux-image-1.2 [1.2]' apt autoremove -s old-needs-

0 comments on commit acc5502

Please sign in to comment.