13#include <zypp/base/String.h>
14#include <zypp/base/Logger.h>
15#include <zypp/base/Regex.h>
16#include <zypp/base/Iterator.h>
20#include <zypp/Resolver.h>
27#include <unordered_map>
28#include <sys/utsname.h>
33#undef ZYPP_BASE_LOGGER_LOGGROUP
34#define ZYPP_BASE_LOGGER_LOGGROUP "PurgeKernels"
57 using GroupMap = std::unordered_map<std::string, GroupInfo>;
62 struct utsname unameData;
63 if ( uname( &unameData) == 0 ) {
68 setUnameR( std::string( unameData.release ) );
74 MIL <<
"Edition variant: " << edVar <<
"\n";
78 MIL <<
"Failed to detect running kernel: " << errno << std::endl;
86 MIL <<
"Set uname " << uname << std::endl;
96 auto makeRcVariant = [ &release ]( std::string myVersion,
const std::string &replace ){
101 return Edition( myVersion, release );
110 MIL <<
"Parsed info from uname: " << std::endl;
112 MIL <<
"Kernel Edition Variants: \n";
114 MIL <<
" " << var <<
"\n";
121 void fillKeepList(
const GroupMap &installedKernels, std::set<sat::Solvable> &keepList , std::set<sat::Solvable> &removeList )
const;
153 if ( !pool.resolver().resolvePool() ) {
154 MIL <<
"Pool failed to resolve, not doing anything" << std::endl;
158 MIL <<
"Request to remove package: " << pi << std::endl;
161 const str::regex validRemovals(
"(kernel-syms(-.*)?|kgraft-patch(-.*)?|kernel-(.*)-livepatch(-.*)?|kernel-livepatch(-.*)?|.*-kmp(-.*)?)");
164 MIL <<
"Package " << pi <<
" is locked by the user, not removing." << std::endl;
169 std::set<sat::Solvable> currentSetOfRemovals;
170 for (
const PoolItem & p : pool.byStatus( toBeUninstalledFilter ) ) {
171 currentSetOfRemovals.insert( p.satSolvable() );
176 if ( !pool.resolver().resolvePool() ) {
177 MIL <<
"Failed to resolve pool, skipping " << pi << std::endl;
178 pool.resolver().problems();
184 std::set<sat::Solvable> removedInThisRun;
185 removedInThisRun.insert( slv );
187 for (
const PoolItem & p : pool.byStatus( toBeUninstalledFilter ) ) {
190 if ( p.status().isByUser()
191 || (currentSetOfRemovals.find( p.satSolvable() ) != currentSetOfRemovals.end())
196 removedInThisRun.insert( p.satSolvable() );
198 MIL <<
"Package " << p <<
" was marked by the solver for removal." << std::endl;
201 if ( removeList.find( p.satSolvable() ) != removeList.end() )
204 if ( keepList.find( p.satSolvable() ) != keepList.end() ) {
205 MIL <<
"Package " << p <<
" is in keep spec, skipping" << pi << std::endl;
220 bool mostLikelyKmod =
false;
223 for (
const auto &prov : p.provides() ) {
224 if ( matchMod.
doMatch( prov.detail().name().c_str()) || matchSym.
doMatch( prov.detail().name().c_str() ) ) {
225 mostLikelyKmod =
true;
230 if ( mostLikelyKmod ) {
231 MIL <<
"Package " << p <<
" is most likely a kmod " << std::endl;
235 MIL <<
"Package " << p <<
" should not be removed, skipping " << pi << std::endl;
242 MIL <<
"Successfully marked package: " << pi <<
" for removal."<<std::endl;
245 MIL <<
"Trying to remove debuginfo for: " << pi <<
"."<<std::endl;
248 if ( solvable.arch() == Arch_noarch ||
252 for (
const char * suffix : {
"-debugsource",
"-debuginfo" } ) {
261 if ( debugPackage.arch() != solvable.arch() )
264 MIL <<
"Found debug package for " << solvable <<
" : " << debugPackage << std::endl;
270 MIL <<
"Finished removing debuginfo for: " << pi <<
"."<<std::endl;
286 std::string versionStr =
b.asString();
288 if ( buildCntRegex.
matches( versionStr.data(), matches ) ) {
289 if ( matches.
size() >= 2 ) {
290 versionStr.replace( matches.
begin(0), (matches.
end(0) - matches.
begin(0))+1, matches[1] );
305 const unsigned tokenGrp = 1;
306 const unsigned modifierGrp = 2;
309 MIL <<
"Parsing keep spec: " << _keepSpec << std::endl;
311 std::vector<std::string> words;
313 if ( words.empty() ) {
314 WAR <<
"Invalid keep spec: " << _keepSpec <<
" using default latest,running." << std::endl;
318 _keepRunning =
false;
319 _keepLatestOffsets.clear();
320 _keepOldestOffsets.clear();
322 for (
const std::string &word : words ) {
323 if ( word ==
"running" ) {
330 std::string_view edition(word);
331 const auto firstDash = word.find_first_of (
'-');
332 if ( firstDash != std::string::npos ) {
333 const auto secondDash = word.find_first_of (
'-', firstDash+1 );
334 if ( secondDash != std::string::npos ) {
335 WAR <<
"Ignoring possible flavor postfix:'"<< word.substr (secondDash) <<
"' in keep spec: " << word << std::endl;
336 edition = std::string_view( word.c_str (), secondDash );
343 auto addKeepOff = [](
const auto &off,
auto &set,
const auto &constraint ){
344 const off_t num = off.empty() ? 0 : str::strtonum<off_t>( off );
345 if ( !constraint(num) )
return false;
346 set.insert(
static_cast<size_t>(std::abs(num)) );
350 if ( what[tokenGrp] ==
"oldest" ) {
351 addKeepOff( what[modifierGrp], _keepOldestOffsets, [ &word ]( off_t num ) {
353 WAR <<
"Ignoring invalid modifier in keep spec: " << word <<
", oldest supports only positive modifiers." << std::endl;
359 addKeepOff( what[modifierGrp], _keepLatestOffsets, [ &word ]( off_t num ) {
361 WAR <<
"Ignoring invalid modifier in keep spec: " << word <<
", latest supports only negative modifiers." << std::endl;
383 const auto markAsKeep = [ &keepList, &removeList ](
sat::Solvable pck ) {
384 MIL <<
"Marking package " << pck <<
" as to keep." << std::endl;
385 keepList.insert( pck ) ;
386 removeList.erase( pck );
389 const auto versionPredicate = [](
const auto &editionVariants ){
390 return [ &editionVariants ](
const auto &elem ) {
391 const auto &f = std::bind( versionMatch, _1, elem.first );
392 return std::any_of( editionVariants.begin(), editionVariants.end(), f );
396 for (
const auto &groupInfo : installedKernels ) {
398 MIL <<
"Starting with group " << groupInfo.first << std::endl;
400 for (
const auto &archMap : groupInfo.second.archToEdMap ) {
402 MIL <<
"Starting with arch " << archMap.first << std::endl;
405 size_t currROff = archMap.second.size() - 1;
411 && ( ( archMap.first == _kernelArch && groupInfo.second.groupFlavour == _runningKernelFlavour )
414 MIL <<
"Matching packages against running kernel "<< _runningKernelFlavour <<
"-" <<_kernelArch <<
"\nVariants:\n";
415 for (
const auto &var : _runningKernelEditionVariants )
419 const auto &editionPredicate = versionPredicate( _runningKernelEditionVariants );
420 auto it = std::find_if( map.begin(), map.end(), editionPredicate );
421 if ( it == map.end() ) {
425 MIL <<
"Running kernel " << _runningKernelFlavour <<
"-" <<_kernelArch <<
"\n";
426 for (
const auto &var : _runningKernelEditionVariants )
427 MIL <<
" Possible Variant:" << var <<
"\n";
428 MIL <<
"Not installed! \n";
429 MIL <<
"NOT removing any packages for flavor "<<_runningKernelFlavour<<
"-"<<_kernelArch<<
" ."<<std::endl;
431 for (
const auto &kernelMap : map ) {
440 MIL <<
"Found possible running candidate edition: " << it->first << std::endl;
442 for ( nit++ ; nit != map.end() && editionPredicate( *nit ) ; nit++ ) {
443 MIL <<
"Found possible more recent running candidate edition: " << nit->first << std::endl;
449 if ( it != map.end() ) {
456 for (
const auto &kernelMap : map ) {
458 if ( _keepOldestOffsets.find( currOff ) != _keepOldestOffsets.end() || _keepLatestOffsets.find( currROff ) != _keepLatestOffsets.end() ) {
459 std::for_each( kernelMap.second.begin(), kernelMap.second.end(), markAsKeep );
467 std::for_each( kernelMap.second.begin(), kernelMap.second.end(), [ & ](
sat::Solvable solv ){
468 for ( Capability prov : solv.provides() ) {
469 if ( prov.detail().name() == solv.name() && _keepSpecificEditions.count( prov.detail().ed() ) ) {
481 : _pimpl( new
Impl() )
488 MIL << std::endl <<
"--------------------- Starting to mark obsolete kernels ---------------------"<<std::endl;
490 if (
_pimpl->_keepSpec.empty() ) {
491 WAR <<
"Keep spec is empty, removing nothing." << std::endl;
497 if (
_pimpl->_keepRunning && !
_pimpl->_detectedRunning ) {
498 WAR <<
"Unable to detect running kernel, but keeping the running kernel was requested. Not removing any packages." << std::endl;
503 pool.resolver().setForceResolve(
true );
508 const str::regex kernelFlavourRegex(
"^kernel-(.*)$");
516 std::set<sat::Solvable> packagesToRemove;
518 const auto addPackageToMap = [&installedKrnlPackages, &packagesToRemove] (
const GroupInfo::GroupType type,
const std::string &ident,
const std::string &flavour,
const sat::Solvable &installedKrnlPck ) {
520 if ( !installedKrnlPackages.count( ident ) )
521 installedKrnlPackages.insert( std::make_pair( ident,
GroupInfo(type, flavour) ) );
523 auto &groupInfo = installedKrnlPackages[ ident ];
524 if ( groupInfo.groupType != type || groupInfo.groupFlavour != flavour ) {
525 ERR <<
"Got inconsistent type and flavour for ident this is a BUG: " << ident << std::endl
526 <<
"Original Flavour-Type: "<<groupInfo.groupFlavour<<
"-"<<groupInfo.groupType << std::endl
527 <<
"Competing Flavour-Type: "<< flavour <<
"-" << type << std::endl;
530 const auto currArch = installedKrnlPck.arch();
531 if ( !groupInfo.archToEdMap.count( currArch ) )
534 auto &editionToSolvableMap = groupInfo.archToEdMap[ currArch ];
540 auto currCount = INT_MAX;
542 for (
Capability prov : installedKrnlPck.provides() ) {
543 if ( prov.detail().name() == installedKrnlPck.name() ) {
545 edToUse = installedKrnlPck.edition();
546 const auto &relStr = edToUse.
release();
547 currCount = std::count( relStr.begin(), relStr.end(),
'.');
549 const auto &pckEd = prov.detail().ed();
550 const auto &relStr = pckEd.release();
551 if (
const auto pntCnt = std::count( relStr.begin(), relStr.end(),
'.'); pntCnt < currCount ) {
559 if ( !editionToSolvableMap.count( edToUse ) )
560 editionToSolvableMap.insert( std::make_pair( edToUse,
SolvableList{} ) );
562 editionToSolvableMap[edToUse].push_back( installedKrnlPck );
565 packagesToRemove.insert( installedKrnlPck );
569 std::set<sat::Solvable> packagesToKeep;
578 MIL <<
"Searching for obsolete multiversion kernel packages." << std::endl;
582 MIL <<
"Found installed multiversion kernel package " << installedKrnlPck << std::endl;
584 if ( installedKrnlPck.provides().matches(
Capability(
"kernel-uname-r")) ) {
585 MIL <<
"Identified as a kernel package " << std::endl;
590 if ( what[1].empty() ) {
591 WAR <<
"Could not detect flavour for: " << installedKrnlPck <<
" ...skipping" << std::endl;
595 std::string flavour = what[1];
598 const auto dash = flavour.find_first_of(
'-');
599 if ( dash != std::string::npos ) {
600 flavour = flavour.substr( 0, dash );
609 const str::regex explicitlyHandled(
"kernel-syms(-.*)?|kernel(-.*)?-devel");
611 MIL <<
"Not a kernel package, inspecting more closely " << std::endl;
614 if ( installedKrnlPck.arch() == Arch_noarch ) {
616 MIL <<
"Handling package explicitly due to architecture (noarch)."<< std::endl;
628 if ( match.size() > 1 && match[1].size() )
629 flav = match[1].substr(1);
631 else if ( match.size() > 2 && match[2].size() )
632 flav = match[2].substr(1);
633 else if ( installedKrnlPck.name() ==
"kernel-syms" )
636 MIL <<
"Handling package explicitly due to name match."<< std::endl;
639 MIL <<
"Package not explicitly handled" << std::endl;
645 MIL <<
"Grouped packages: " << std::endl;
646 std::for_each( installedKrnlPackages.begin(), installedKrnlPackages.end(),[](
const auto &ident ){
647 MIL <<
"\tGroup ident: "<<ident.first<<std::endl;
648 MIL <<
"\t Group type: "<<ident.second.groupType<<std::endl;
649 MIL <<
"\t Group flav: "<<ident.second.groupFlavour<<std::endl;
650 std::for_each( ident.second.archToEdMap.begin(), ident.second.archToEdMap.end(), []( const auto &arch) {
651 MIL <<
"\t\tArch: "<<arch.first<<std::endl;
652 std::for_each( arch.second.begin(), arch.second.end(), []( const auto &edition) {
653 MIL <<
"\t\t\tEdition: "<<edition.first<<std::endl;
654 std::for_each( edition.second.begin(), edition.second.end(), []( const auto &packageId) {
655 MIL <<
"\t\t\t\t "<<sat::Solvable(packageId)<<std::endl;
661 _pimpl->fillKeepList( installedKrnlPackages, packagesToKeep, packagesToRemove );
664 _pimpl->removePackageAndCheck( slv, packagesToKeep, packagesToRemove );
679 _pimpl->_kernelArch = arch;
684 return _pimpl->_kernelArch;
Edition represents [epoch:]version[-release]
std::string release() const
Release.
static const Edition noedition
Value representing noedition ("") This is in fact a valid Edition.
Access to the sat-pools string space.
Combining sat::Solvable and ResStatus.
ResStatus & status() const
Returns the current status.
ResStatus & statusReset() const
Resets status to the default state (KEEP_STATE bySOLVER; clears any lock!).
void setMatchExact()
Set to match exact string instead of substring.
void addKind(const ResKind &kind)
Filter by selectable kind.
void addAttribute(const sat::SolvAttr &attr, const std::string &value="")
Filter by the value of the specified attr attribute.
void setInstalledOnly()
Return only @System repo packages.
void addDependency(const sat::SolvAttr &attr, const std::string &name, const Rel &op, const Edition &edition)
Query "name|global op edition".
std::string unameR() const
void setKeepSpec(const std::string &val)
std::string keepSpec() const
void setUnameR(const std::string &val)
RW_pointer< Impl > _pimpl
void setKernelArch(const zypp::Arch &arch)
void markObsoleteKernels()
static const ResKind package
static ResPool instance()
Singleton ctor.
bool setToBeUninstalled(TransactByValue causer)
bool isToBeUninstalled() const
String matching (STRING|SUBSTRING|GLOB|REGEX).
bool doMatch(const char *string_r) const
Return whether string matches.
static ZConfig & instance()
Singleton ctor.
std::string multiversionKernels() const
Filter solvables according to their status.
static const SolvAttr provides
A Solvable object within the sat Pool.
@ match_extended
Use POSIX Extended Regular Expression syntax when interpreting regex.
bool matches(const char *s, str::smatch &matches, int flags=none) const
Regular expression match result.
std::string::size_type end(unsigned i) const
End index of subexpression i in match_str (or std::string::npos)
std::string::size_type begin(unsigned i) const
Begin index of subexpression i in match_str (or std::string::npos)
std::string regex_substitute(const std::string &s, const regex ®ex, const std::string &replacement, bool global=true)
Replaces the matched regex with the string passed in replacement.
bool regex_match(const std::string &s, smatch &matches, const regex ®ex)
\relates regex \ingroup ZYPP_STR_REGEX \relates regex \ingroup ZYPP_STR_REGEX
unsigned split(const C_Str &line_r, TOutputIterator result_r, const C_Str &sepchars_r=" \t", const Trim trim_r=NO_TRIM)
Split line_r into words.
Easy-to use interface to the ZYPP dependency resolver.
std::list< sat::Solvable > SolvableList
std::map< Edition, SolvableList > EditionToSolvableMap
std::unordered_map< std::string, GroupInfo > GroupMap
const Arch Arch_empty(IdString::Empty)
std::map< Arch, EditionToSolvableMap > ArchToEditionMap
enum zypp::GroupInfo::GroupType groupType
ArchToEditionMap archToEdMap
GroupInfo(const GroupType type=None, std::string flav="")
static bool versionMatch(const Edition &a, const Edition &b)
std::set< Edition > _keepSpecificEditions
bool removePackageAndCheck(const sat::Solvable slv, const std::set< sat::Solvable > &keepList, const std::set< sat::Solvable > &removeList) const
std::set< Edition > _runningKernelEditionVariants
std::set< size_t > _keepLatestOffsets
void setUnameR(const std::string &uname)
std::set< size_t > _keepOldestOffsets
void fillKeepList(const GroupMap &installedKernels, std::set< sat::Solvable > &keepList, std::set< sat::Solvable > &removeList) const
Flavour _runningKernelFlavour