From 04085f46dea9a95dd86123ac00187a63cc4ba2c0 Mon Sep 17 00:00:00 2001 From: Julian Andres Klode Date: Thu, 17 Dec 2020 13:24:56 +0100 Subject: Determine autoremovable kernels at run-time Our kernel autoremoval helper script protects the currently booted kernel, but it only runs whenever we install or remove a kernel, causing it to protect the kernel that was booted at that point in time, which is not necessarily the same kernel as the one that is running right now. Reimplement the logic in C++ such that we can calculate it at run-time: Provide a function to produce a regular expression that matches all kernels that need protecting, and by changing the default root set function in the DepCache to make use of that expression. Note that the code groups the kernels by versions as before, and then marks all kernel packages with the same version. This optimized version inserts a virtual package $kernel into the cache when building it to avoid having to iterate over all packages in the cache to find the installed ones, significantly improving performance at a minor cost when building the cache. LP: #1615381 --- apt-pkg/algorithms.cc | 183 +++++++++++++++++++++++++++++++++++++++++++ apt-pkg/algorithms.h | 14 ++++ apt-pkg/deb/deblistparser.cc | 8 +- apt-pkg/depcache.cc | 30 +++++-- 4 files changed, 228 insertions(+), 7 deletions(-) (limited to 'apt-pkg') diff --git a/apt-pkg/algorithms.cc b/apt-pkg/algorithms.cc index cd09a6944..702030943 100644 --- a/apt-pkg/algorithms.cc +++ b/apt-pkg/algorithms.cc @@ -16,20 +16,32 @@ #include #include +#include #include #include #include #include #include +#include #include #include +#include +#include +#include + #include #include #include +#include +#include +#include +#include #include #include +#include #include +#include #include /*}}}*/ @@ -1442,3 +1454,174 @@ void pkgPrioSortList(pkgCache &Cache,pkgCache::Version **List) std::sort(List,List+Count,PrioComp(Cache)); } /*}}}*/ + +namespace APT +{ + +namespace KernelAutoRemoveHelper +{ + +// \brief Returns the uname from a kernel package name, or "" for non-kernel packages. +std::string getUname(std::string const &packageName) +{ + + static const constexpr char *const prefixes[] = { + "linux-image-", + "kfreebsd-image-", + "gnumach-image-", + }; + + for (auto prefix : prefixes) + { + if (likely(not APT::String::Startswith(packageName, prefix))) + continue; + if (unlikely(APT::String::Endswith(packageName, "-dbgsym"))) + continue; + if (unlikely(APT::String::Endswith(packageName, "-dbg"))) + continue; + + auto aUname = packageName.substr(strlen(prefix)); + + // aUname must start with [0-9]+\. + if (aUname.length() < 2) + continue; + if (strchr("0123456789", aUname[0]) == nullptr) + continue; + auto dot = aUname.find_first_not_of("0123456789"); + if (dot == aUname.npos || aUname[dot] != '.') + continue; + + return aUname; + } + + return ""; +} +std::string GetProtectedKernelsRegex(pkgCache *cache, bool ReturnRemove) +{ + if (_config->FindB("APT::Protect-Kernels", true) == false) + return ""; + + struct CompareKernel + { + pkgCache *cache; + bool operator()(const std::string &a, const std::string &b) const + { + return cache->VS->CmpVersion(a, b) < 0; + } + }; + bool Debug = _config->FindB("Debug::pkgAutoRemove", false); + // kernel version -> list of unames + std::map, CompareKernel> version2unames(CompareKernel{cache}); + // needs to be initialized to 0s, might not be set up. + utsname uts{}; + std::string bootedVersion; + std::string lastInstalledVersion; + + std::string lastInstalledUname = _config->Find("APT::LastInstalledKernel"); + + // Get currently booted version, but only when not on reproducible build. + if (getenv("SOURCE_DATE_EPOCH") == 0) + { + if (uname(&uts) != 0) + abort(); + } + + auto VirtualKernelPkg = cache->FindPkg("$kernel", "any"); + if (VirtualKernelPkg.end()) + return ""; + + for (pkgCache::PrvIterator Prv = VirtualKernelPkg.ProvidesList(); Prv.end() == false; ++Prv) + { + auto Pkg = Prv.OwnerPkg(); + if (likely(Pkg->CurrentVer == 0)) + continue; + + auto pkgUname = APT::KernelAutoRemoveHelper::getUname(Pkg.Name()); + auto pkgVersion = Pkg.CurrentVer().VerStr(); + + if (pkgUname.empty()) + continue; + + if (Debug) + std::clog << "Found kernel " << pkgUname << "(" << pkgVersion << ")" << std::endl; + + version2unames[pkgVersion].push_back(pkgUname); + + if (pkgUname == uts.release) + bootedVersion = pkgVersion; + if (pkgUname == lastInstalledUname) + lastInstalledVersion = pkgVersion; + } + + if (version2unames.size() == 0) + return ""; + + auto latest = version2unames.rbegin(); + auto previous = latest; + ++previous; + + std::set keep; + + if (not bootedVersion.empty()) + { + if (Debug || false) + std::clog << "Keeping booted kernel " << bootedVersion << std::endl; + keep.insert(bootedVersion); + } + if (not lastInstalledVersion.empty()) + { + if (Debug || false) + std::clog << "Keeping installed kernel " << lastInstalledVersion << std::endl; + keep.insert(lastInstalledVersion); + } + if (latest != version2unames.rend()) + { + if (Debug || false) + std::clog << "Keeping latest kernel " << latest->first << std::endl; + keep.insert(latest->first); + } + if (previous != version2unames.rend()) + { + if (Debug) + std::clog << "Keeping previous kernel " << previous->first << std::endl; + keep.insert(previous->first); + } + + std::regex special("([\\.\\+])"); + std::ostringstream ss; + for (auto &pattern : _config->FindVector("APT::VersionedKernelPackages")) + { + // Legacy compatibility: Always protected the booted uname and last installed uname + if (not lastInstalledUname.empty()) + ss << "|^" << pattern << "-" << std::regex_replace(lastInstalledUname, special, "\\$1") << "$"; + if (*uts.release) + ss << "|^" << pattern << "-" << std::regex_replace(uts.release, special, "\\$1") << "$"; + for (auto const &kernel : version2unames) + { + if (ReturnRemove ? keep.find(kernel.first) == keep.end() : keep.find(kernel.first) != keep.end()) + { + for (auto const &uname : kernel.second) + ss << "|^" << pattern << "-" << std::regex_replace(uname, special, "\\$1") << "$"; + } + } + } + + auto re = ss.str().substr(1); + if (Debug) + std::clog << "Kernel protection regex: " << re << "\n"; + + return re; +} + +std::unique_ptr GetProtectedKernelsFilter(pkgCache *cache, bool returnRemove) +{ + auto regex = GetProtectedKernelsRegex(cache, returnRemove); + + if (regex.empty()) + return std::make_unique(); + + return std::make_unique(regex); +} + +} // namespace KernelAutoRemoveHelper +} // namespace APT diff --git a/apt-pkg/algorithms.h b/apt-pkg/algorithms.h index fc578a4ca..12a77d4b8 100644 --- a/apt-pkg/algorithms.h +++ b/apt-pkg/algorithms.h @@ -29,11 +29,13 @@ #ifndef PKGLIB_ALGORITHMS_H #define PKGLIB_ALGORITHMS_H +#include #include #include #include #include +#include #include #include @@ -146,5 +148,17 @@ APT_PUBLIC bool pkgFixBroken(pkgDepCache &Cache); APT_PUBLIC void pkgPrioSortList(pkgCache &Cache,pkgCache::Version **List); +namespace APT +{ +namespace KernelAutoRemoveHelper +{ +// Public for linking to apt-private, but no A{P,B}I guarantee. +APT_PUBLIC std::unique_ptr GetProtectedKernelsFilter(pkgCache *cache, bool returnRemove = false); +std::string GetProtectedKernelsRegex(pkgCache *cache, bool ReturnRemove = false); +std::string getUname(std::string const &packageName); + +} // namespace KernelAutoRemoveHelper + +} // namespace APT #endif diff --git a/apt-pkg/deb/deblistparser.cc b/apt-pkg/deb/deblistparser.cc index 240946529..95f6f6fc8 100644 --- a/apt-pkg/deb/deblistparser.cc +++ b/apt-pkg/deb/deblistparser.cc @@ -11,6 +11,7 @@ // Include Files /*{{{*/ #include +#include #include #include #include @@ -246,7 +247,12 @@ bool debListParser::NewVersion(pkgCache::VerIterator &Ver) if (ParseProvides(Ver) == false) return false; - + if (not APT::KernelAutoRemoveHelper::getUname(Ver.ParentPkg().Name()).empty()) + { + if (not NewProvides(Ver, "$kernel", "any", Ver.VerStr(), pkgCache::Flag::MultiArchImplicit)) + return false; + } + return true; } /*}}}*/ diff --git a/apt-pkg/depcache.cc b/apt-pkg/depcache.cc index 3c9695d56..f2b3dbcdc 100644 --- a/apt-pkg/depcache.cc +++ b/apt-pkg/depcache.cc @@ -9,6 +9,7 @@ // Include Files /*{{{*/ #include +#include #include #include #include @@ -43,6 +44,23 @@ using std::string; +// helper for kernel autoremoval /*{{{*/ + +/** \brief Returns \b true for packages matching a regular + * expression in APT::NeverAutoRemove. + */ +class APT_PUBLIC DefaultRootSetFunc2 : public pkgDepCache::DefaultRootSetFunc +{ + std::unique_ptr Kernels; + + public: + DefaultRootSetFunc2(pkgCache *cache) : Kernels(APT::KernelAutoRemoveHelper::GetProtectedKernelsFilter(cache)){}; + virtual ~DefaultRootSetFunc2(){}; + + bool InRootSet(const pkgCache::PkgIterator &pkg) APT_OVERRIDE { return pkg.end() == false && ((*Kernels)(pkg) || DefaultRootSetFunc::InRootSet(pkg)); }; +}; + + /*}}}*/ // helper for Install-Recommends-Sections and Never-MarkAuto-Sections /*{{{*/ static bool ConfigValueInSubTree(const char* SubTree, const char *needle) @@ -2153,14 +2171,14 @@ APT_PURE signed short pkgDepCache::Policy::GetPriority(pkgCache::PkgFileIterator /*}}}*/ pkgDepCache::InRootSetFunc *pkgDepCache::GetRootSetFunc() /*{{{*/ { - DefaultRootSetFunc *f = new DefaultRootSetFunc; - if(f->wasConstructedSuccessfully()) - return f; - else - { + DefaultRootSetFunc *f = new DefaultRootSetFunc2(&GetCache()); + if (f->wasConstructedSuccessfully()) + return f; + else + { delete f; return NULL; - } + } } pkgDepCache::InRootSetFunc *pkgDepCache::GetCachedRootSetFunc() -- cgit v1.2.3