Imported Upstream version 17.25.4
[platform/upstream/libzypp.git] / zypp / PurgeKernels.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/PurgeKernels.cc
10  *
11 */
12
13 #include <zypp/base/String.h>
14 #include <zypp/base/Logger.h>
15 #include <zypp/base/Regex.h>
16 #include <zypp/base/Iterator.h>
17 #include <zypp/PurgeKernels.h>
18 #include <zypp/PoolQuery.h>
19 #include <zypp/ResPool.h>
20 #include <zypp/Resolver.h>
21 #include <zypp/Filter.h>
22 #include <zypp/ZConfig.h>
23
24 #include <iostream>
25 #include <fstream>
26 #include <map>
27 #include <unordered_map>
28 #include <sys/utsname.h>
29 #include <functional>
30 #include <array>
31
32 #undef ZYPP_BASE_LOGGER_LOGGROUP
33 #define ZYPP_BASE_LOGGER_LOGGROUP "PurgeKernels"
34
35 namespace zypp {
36
37   using Flavour                = std::string;
38   using SolvableList           = std::list<sat::Solvable>;
39   using EditionToSolvableMap   = std::map<Edition, SolvableList >;
40   using ArchToEditionMap       = std::map<Arch, EditionToSolvableMap >;
41
42   struct GroupInfo {
43
44     enum GroupType {
45       None,             //<< Just here to support default construction
46       Kernels,          //<< Map contains kernel packages, so need to receive special handling and flavour matching
47       RelatedBinaries,  //<< Map contains related binary packages, so need to receive special handling and flavour matching
48       Sources           //<< Map contains source packages, so when matching those against running we ignore the flavour
49     } groupType;
50
51     GroupInfo( const GroupType type = None, std::string flav = "") : groupType(type), groupFlavour( std::move(flav) ) { }
52
53     ArchToEditionMap archToEdMap;   //<< Map of actual packages
54     std::string groupFlavour;       //<< This would contain a specific flavour if there is one calculated
55   };
56   using GroupMap = std::unordered_map<std::string, GroupInfo>;
57
58   struct PurgeKernels::Impl  {
59
60     Impl() {
61       struct utsname unameData;
62       if ( uname( &unameData) == 0 ) {
63
64         const auto archStr = str::regex_substitute( unameData.machine, str::regex( "^i.86$", str::regex::match_extended ), "i586" );
65
66         _kernelArch = Arch( archStr );
67         setUnameR( std::string( unameData.release ) );
68
69         _detectedRunning = true;
70
71         MIL << "Detected running kernel: " << _runningKernelEdition << " " << _runningKernelFlavour << " " << _kernelArch << std::endl;
72
73       } else {
74         MIL << "Failed to detect running kernel: " << errno << std::endl;
75       }
76     }
77
78     void setUnameR ( const std::string &uname ) {
79
80       _uname_r = uname;
81
82       MIL << "Set uname " << uname << std::endl;
83
84       const std::string flavour = str::regex_substitute( _uname_r, str::regex( ".*-", str::regex::match_extended ), "", true );
85       std::string version = str::regex_substitute( _uname_r, str::regex( "-[^-]*$", str::regex::match_extended | str::regex::newline ), "", true );
86
87       const std::string release = str::regex_substitute( version, str::regex( ".*-", str::regex::match_extended ), "", true );
88
89       version = str::regex_substitute( version, str::regex( "-[^-]*$", str::regex::match_extended | str::regex::newline ), "", true );
90
91       // from purge-kernels script, was copied from kernel-source/rpm/mkspec
92       version = str::regex_substitute( version, str::regex( "\\.0-rc", str::regex::match_extended ), ".rc", true );
93       version = str::regex_substitute( version, str::regex( "-rc\\d+", str::regex::match_extended ), "", true );
94       version = str::regex_substitute( version, str::regex( "-", str::regex::match_extended ), ".", true );
95
96       _runningKernelEdition = Edition( version, release );
97       _runningKernelFlavour = flavour;
98
99       MIL << "Parsed info from uname: " << std::endl;
100       MIL << "Kernel Flavour: " << _runningKernelFlavour << std::endl;
101       MIL << "Kernel Edition: " << _runningKernelEdition << std::endl;
102     }
103
104     bool removePackageAndCheck( const sat::Solvable slv, const std::set<sat::Solvable> &keepList , const std::set<sat::Solvable> &removeList ) const;
105     static bool versionMatch ( const Edition &a, const Edition &b );
106     void parseKeepSpec();
107     void fillKeepList(const GroupMap &installedKernels, std::set<sat::Solvable> &keepList , std::set<sat::Solvable> &removeList ) const;
108
109     std::set<size_t>  _keepLatestOffsets = { 0 };
110     std::set<size_t>  _keepOldestOffsets;
111     std::set<Edition> _keepSpecificEditions;
112     std::string       _uname_r;
113     Edition           _runningKernelEdition;
114     Flavour           _runningKernelFlavour;
115     Arch              _kernelArch;
116     std::string       _keepSpec = ZConfig::instance().multiversionKernels();
117     bool              _keepRunning     = true;
118     bool              _detectedRunning = false;
119   };
120
121   /*!
122    * tries to remove a the \ref PoolItem \a pi from the pool, solves and checks if no unexpected packages are removed due to the \a validRemovals regex.
123    * If the constraint fails the changes are reverted and \a false is returned.
124    */
125   bool PurgeKernels::Impl::removePackageAndCheck( const sat::Solvable slv, const std::set<sat::Solvable> &keepList , const std::set<sat::Solvable> &removeList ) const
126   {
127     const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
128
129     PoolItem pi ( slv );
130
131     auto pool = ResPool::instance();
132
133     // make sure the pool is clean
134     if ( !pool.resolver().resolvePool() ) {
135       MIL << "Pool failed to resolve, not doing anything" << std::endl;
136       return false;
137     }
138
139     MIL << "Request to remove package: " << pi << std::endl;
140
141     //list of packages that are allowed to be removed automatically.
142     const str::regex validRemovals("(kernel-syms(-.*)?|kgraft-patch(-.*)?|kernel-livepatch(-.*)?|.*-kmp(-.*)?)");
143
144     if ( pi.status().isLocked() ) {
145       MIL << "Package " << pi << " is locked by the user, not removing." << std::endl;
146       return false;
147     }
148
149     //remember which packages are already marked for removal, we do not need to check them again
150     std::set<sat::Solvable> currentSetOfRemovals;
151     for ( const PoolItem & p : pool.byStatus( toBeUninstalledFilter ) ) {
152       currentSetOfRemovals.insert( p.satSolvable() );
153     }
154
155     pi.status().setToBeUninstalled( ResStatus::USER );
156
157     if ( !pool.resolver().resolvePool() ) {
158       MIL << "Failed to resolve pool, skipping " << pi << std::endl;
159       pool.resolver().problems();
160       pi.statusReset();
161
162       return false;
163     }
164
165     std::set<sat::Solvable> removedInThisRun;
166     removedInThisRun.insert( slv );
167
168     for ( const PoolItem & p : pool.byStatus( toBeUninstalledFilter ) ) {
169
170       //check if that package is removeable
171       if ( p.status().isByUser()      //this was set by us, ignore it
172            || (currentSetOfRemovals.find( p.satSolvable() ) != currentSetOfRemovals.end()) //this was marked by a previous removal, ignore them
173         )
174         continue;
175
176       // remember for later we need remove the debugsource and debuginfo packages as well
177       removedInThisRun.insert( p.satSolvable() );
178
179       MIL << "Package " << p << " was marked by the solver for removal." << std::endl;
180
181       // if we do not plan to remove that package anyway, we need to check if its allowed to be removed ( package in removelist can never be in keep list )
182       if ( removeList.find( p.satSolvable() ) != removeList.end() )
183         continue;
184
185       if ( keepList.find( p.satSolvable() ) != keepList.end() ) {
186         MIL << "Package " << p << " is in keep spec, skipping" << pi << std::endl;
187         pi.statusReset();
188         return false;
189       }
190
191       str::smatch what;
192       if ( !str::regex_match( p.name(), what, validRemovals) ) {
193         MIL << "Package " << p << " should not be removed, skipping " << pi << std::endl;
194         pi.statusReset();
195         return false;
196       }
197     }
198
199     MIL << "Successfully marked package: " << pi << " for removal."<<std::endl;
200
201     //now check and mark the -debugsource and -debuginfo packages for this package and all the packages that were removed. Maybe collect it before and just remove here
202     MIL << "Trying to remove debuginfo for: " << pi <<"."<<std::endl;
203     for ( sat::Solvable solvable : removedInThisRun ) {
204
205       if ( solvable.arch() == Arch_noarch ||
206            solvable.arch() == Arch_empty )
207         continue;
208
209       for ( const char * suffix : { "-debugsource", "-debuginfo" } ) {
210         PoolQuery q;
211         q.addKind( zypp::ResKind::package );
212         q.addDependency( sat::SolvAttr::provides, Capability( solvable.name()+suffix, Rel::EQ, solvable.edition() ) );
213         q.setInstalledOnly();
214         q.setMatchExact();
215
216         for ( sat::Solvable debugPackage : q ) {
217
218           if ( debugPackage.arch() != solvable.arch() )
219             continue;
220
221           MIL << "Found debug package for " << solvable << " : " << debugPackage << std::endl;
222           //if removing the package fails it will not stop us from going on , so no need to check
223           removePackageAndCheck( debugPackage, keepList, removeList );
224         }
225       }
226     }
227     MIL << "Finished removing debuginfo for: " << pi <<"."<<std::endl;
228
229     return true;
230   }
231
232   /*!
233    * Return true if \a a == \a b or \a a == (\a b minus rebuild counter)
234    */
235   bool PurgeKernels::Impl::versionMatch( const Edition &a, const Edition &b )
236   {
237     if ( a == b )
238       return true;
239
240     // the build counter should not be considered here, so if there is one we cut it off
241     const str::regex buildCntRegex( "\\.[0-9]+($|\\.g[0-9a-f]{7}$)", str::regex::match_extended );
242
243     std::string versionStr = b.asString();
244     str::smatch matches;
245     if ( buildCntRegex.matches( versionStr.data(), matches ) ) {
246       if ( matches.size() >= 2 ) {
247         versionStr.replace( matches.begin(0), (matches.end(0) - matches.begin(0))+1, matches[1] );
248         return a == Edition(versionStr);
249       }
250     }
251     return false;
252   }
253
254   /*!
255    * Parse the config line keep spec that tells us which kernels should be kept
256    */
257   void PurgeKernels::Impl::parseKeepSpec( )
258   {
259     //keep spec parse regex, make sure to edit the group offsets if changing this regex
260     const str::regex specRegex( "^(latest|oldest)([+-][0-9]+)?$", str::regex::match_extended );
261
262     const unsigned tokenGrp = 1; //index of the group matching the token
263     const unsigned modifierGrp = 2; //index of the group matching the offset modifier
264
265
266     MIL << "Parsing keep spec: " << _keepSpec << std::endl;
267
268     std::vector<std::string> words;
269     str::split( _keepSpec, std::back_inserter(words), ",", str::TRIM );
270     if ( words.empty() ) {
271       WAR << "Invalid keep spec: " << _keepSpec << " using default latest,running." << std::endl;
272       return;
273     }
274
275     _keepRunning = false;
276     _keepLatestOffsets.clear();
277     _keepOldestOffsets.clear();
278
279     for ( const std::string &word : words ) {
280       if ( word == "running" ) {
281         _keepRunning = true;
282       } else {
283         str::smatch what;
284         if ( !str::regex_match( word, what, specRegex ) ) {
285           _keepSpecificEditions.insert( Edition(word) );
286           continue;
287         }
288
289         auto addKeepOff = []( const auto &off, auto &set, const auto &constraint ){
290           const off_t num = off.empty() ? 0 : str::strtonum<off_t>( off );
291           if ( !constraint(num) ) return false;
292           set.insert( static_cast<size_t>(std::abs(num)) );
293           return true;
294         };
295
296         if ( what[tokenGrp] == "oldest" ) {
297           addKeepOff( what[modifierGrp], _keepOldestOffsets, [ &word ]( off_t num ) {
298             if ( num < 0 ) {
299               WAR << "Ignoring invalid modifier in keep spec: " << word << ", oldest supports only positive modifiers." << std::endl;
300               return false;
301             }
302             return true;
303           });
304         } else {
305           addKeepOff( what[modifierGrp], _keepLatestOffsets, [ &word ]( off_t num ) {
306             if ( num > 0 ) {
307               WAR << "Ignoring invalid modifier in keep spec: " << word << ", latest supports only negative modifiers." << std::endl;
308               return false;
309             }
310             return true;
311           });
312         }
313       }
314     }
315   }
316
317   /*!
318    * Go over the list of available Editions for each flavour/arch combinations, apply the keep spec and mark the
319    * packages that belong to a matching category as to keep
320    *
321    * All packages with Arch_noarch will only be matched against the version but NOT the flavour, reasoning for that is
322    * simply that source flavours not necessarily match the binary flavours. Without a translation table that would not be
323    * doable. This is also what the perl script did.
324    *
325    */
326   void PurgeKernels::Impl::fillKeepList( const GroupMap &installedKernels, std::set<sat::Solvable> &keepList, std::set<sat::Solvable> &removeList ) const
327   {
328
329     const auto markAsKeep = [ &keepList, &removeList ]( sat::Solvable pck ) {
330       MIL << "Marking package " << pck << " as to keep." << std::endl;
331       keepList.insert( pck ) ;
332       removeList.erase( pck );
333     };
334
335     const auto versionPredicate = []( const auto &edition ){
336       return [ &edition ]( const auto &elem ) {
337         return versionMatch( edition, elem.first );
338       };
339     };
340
341     for ( const auto &groupInfo : installedKernels ) {
342
343       MIL << "Starting with group " << groupInfo.first << std::endl;
344
345       for ( const auto &archMap : groupInfo.second.archToEdMap ) {
346
347         MIL << "Starting with arch " << archMap.first << std::endl;
348
349         size_t currOff = 0; //the current "oldest" offset ( runs from map start to end )
350         size_t currROff = archMap.second.size() - 1; // the current "latest" offset ( runs from map end to start )
351
352
353         const EditionToSolvableMap &map = archMap.second;
354
355         if ( _keepRunning
356              && ( ( archMap.first == _kernelArch && groupInfo.second.groupFlavour == _runningKernelFlavour )
357                   || groupInfo.second.groupType == GroupInfo::Sources ) ) {
358
359           MIL << "Matching packages against running kernel "<< _runningKernelEdition << "-" << _runningKernelFlavour << "-" <<_kernelArch << std::endl;
360
361           auto it = std::find_if( map.begin(), map.end(), versionPredicate( _runningKernelEdition ) );
362           if ( it == map.end() ) {
363
364             // If we look at Sources we cannot match the flavour but we still want to keep on checking the rest of the keep spec
365             if ( groupInfo.second.groupType != GroupInfo::Sources  ) {
366               MIL << "Running kernel "<< _runningKernelEdition << "-" << _runningKernelFlavour << "-" <<_kernelArch << " not installed."<<std::endl;
367               MIL << "NOT removing any packages for flavor "<<_runningKernelFlavour<<"-"<<_kernelArch<<" ."<<std::endl;
368
369               for ( const auto &kernelMap : map ) {
370                 for( sat::Solvable pck : kernelMap.second )
371                   markAsKeep(pck);
372               }
373               continue;
374             }
375
376           } else {
377             // there could be multiple matches here because of rebuild counter, lets try to find the last one
378             MIL << "Found possible running candidate edition: " << it->first << std::endl;
379             auto nit = it;
380             for ( nit++ ; nit != map.end() && versionMatch( _runningKernelEdition, nit->first ) ; nit++ ) {
381               MIL << "Found possible more recent running candidate edition: " << nit->first << std::endl;
382               it = nit;
383             }
384           }
385
386           // mark all packages of the running version as keep
387           if ( it != map.end() ) {
388             for( sat::Solvable pck : it->second ) {
389               markAsKeep(pck);
390             }
391           }
392         }
393
394         for ( const auto &kernelMap : map ) {
395           //if we find one of the running offsets in the keepspec, we add the kernel id the the list of packages to keep
396           if (  _keepOldestOffsets.find( currOff ) != _keepOldestOffsets.end() || _keepLatestOffsets.find( currROff ) != _keepLatestOffsets.end() ) {
397             std::for_each( kernelMap.second.begin(), kernelMap.second.end(), markAsKeep );
398           }
399           currOff++;
400           currROff--;
401
402           // a kernel package might be explicitely locked by version
403           // We need to go over all package name provides ( provides is named like the package ) and match
404           // them against the specified version to know which ones to keep. (bsc#1176740  bsc#1176192)
405           std::for_each( kernelMap.second.begin(), kernelMap.second.end(), [ & ]( sat::Solvable solv ){
406             for ( Capability prov : solv.provides() ) {
407               if ( prov.detail().name() == solv.name() && _keepSpecificEditions.count( prov.detail().ed() ) ) {
408                 markAsKeep( solv );
409               }
410             }
411           });
412         }
413       }
414     }
415   }
416
417   PurgeKernels::PurgeKernels()
418     : _pimpl( new Impl() )
419   {
420
421   }
422
423   void PurgeKernels::markObsoleteKernels()
424   {
425     MIL << std::endl << "--------------------- Starting to mark obsolete kernels ---------------------"<<std::endl;
426
427     if ( _pimpl->_keepSpec.empty() ) {
428       WAR << "Keep spec is empty, removing nothing." << std::endl;
429       return;
430     }
431
432     _pimpl->parseKeepSpec();
433
434     if ( _pimpl->_keepRunning && !_pimpl->_detectedRunning ) {
435       WAR << "Unable to detect running kernel, but keeping the running kernel was requested. Not removing any packages." << std::endl;
436       return;
437     }
438
439     auto pool = ResPool::instance();
440     pool.resolver().setForceResolve( true ); // set allow uninstall flag
441
442     const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
443
444     // kernel flavour regex
445     const str::regex kernelFlavourRegex("^kernel-(.*)$");
446
447     // the map of all installed kernel packages, grouped by Flavour -> Arch -> Version -> (List of all packages in that category)
448     // devel and source packages are grouped together
449     GroupMap installedKrnlPackages;
450
451
452     // packages that we plan to remove
453     std::set<sat::Solvable> packagesToRemove;
454
455     const auto addPackageToMap = [&installedKrnlPackages, &packagesToRemove] ( const GroupInfo::GroupType type, const std::string &ident, const std::string &flavour, const sat::Solvable &installedKrnlPck ) {
456
457       if ( !installedKrnlPackages.count( ident ) )
458         installedKrnlPackages.insert( std::make_pair( ident, GroupInfo(type, flavour) ) );
459
460       auto &groupInfo = installedKrnlPackages[ ident ];
461       if ( groupInfo.groupType != type || groupInfo.groupFlavour != flavour ) {
462         ERR << "Got inconsistent type and flavour for ident this is a BUG: " <<  ident << std::endl
463             << "Original Flavour-Type:  "<<groupInfo.groupFlavour<<"-"<<groupInfo.groupType << std::endl
464             << "Competing Flavour-Type: "<< flavour << "-" << type << std::endl;
465       }
466
467       const auto currArch = installedKrnlPck.arch();
468       if ( !groupInfo.archToEdMap.count( currArch ) )
469         groupInfo.archToEdMap.insert( std::make_pair( currArch , EditionToSolvableMap {} ) );
470
471       auto &editionToSolvableMap = groupInfo.archToEdMap[ currArch ];
472
473       // calculate the "shortest" or most generic edition of all the package name provides
474       // ( the key of the provides is the package name ). This generic edition is used to
475       // group the packages together. This should get us around the issue that uname -r does
476       // not represent the actual rpm package version anymore. ( bsc#1176740 )
477       auto currCount = INT_MAX;
478       Edition edToUse;
479       for ( Capability prov : installedKrnlPck.provides() ) {
480         if ( prov.detail().name() == installedKrnlPck.name() ) {
481           if ( edToUse == Edition::noedition ) {
482             edToUse = installedKrnlPck.edition();
483             const auto &relStr = edToUse.release();
484             currCount = std::count( relStr.begin(), relStr.end(), '.');
485           } else {
486             const auto &pckEd = prov.detail().ed();
487             const auto &relStr = pckEd.release();
488             if ( const auto pntCnt = std::count( relStr.begin(), relStr.end(), '.'); pntCnt < currCount ) {
489               currCount = pntCnt;
490               edToUse = pckEd;
491             }
492           }
493         }
494       }
495
496       if ( !editionToSolvableMap.count( edToUse ) )
497         editionToSolvableMap.insert( std::make_pair( edToUse, SolvableList{} ) );
498
499       editionToSolvableMap[edToUse].push_back( installedKrnlPck );
500
501       //in the first step we collect all packages in this list, then later we will remove the packages we want to explicitely keep
502       packagesToRemove.insert( installedKrnlPck );
503     };
504
505     // the set of satSolvables that have to be kept always
506     std::set<sat::Solvable> packagesToKeep;
507
508     //collect the list of installed kernel packages
509     PoolQuery q;
510     q.addKind( zypp::ResKind::package );
511     q.addAttribute( sat::SolvAttr::provides, "multiversion(kernel)" );
512     q.setInstalledOnly();
513     q.setMatchExact();
514
515     MIL << "Searching for obsolete multiversion kernel packages." << std::endl;
516
517     for ( sat::Solvable installedKrnlPck : q ) {
518
519       MIL << "Found installed multiversion kernel package " << installedKrnlPck << std::endl;
520
521       if ( installedKrnlPck.provides().matches(Capability("kernel-uname-r")) ) {
522         MIL << "Identified as a kernel package " << std::endl;
523
524         // we group kernel packages by flavour
525         str::smatch what;
526         str::regex_match( installedKrnlPck.name(), what, kernelFlavourRegex );
527         if ( what[1].empty() ) {
528           WAR << "Could not detect flavour for: " << installedKrnlPck << " ...skipping" << std::endl;
529           continue;
530         }
531
532         std::string flavour = what[1];
533
534         // XXX: No dashes in flavor names
535         const auto dash = flavour.find_first_of('-');
536         if ( dash != std::string::npos ) {
537           flavour = flavour.substr( 0, dash );
538         }
539
540         // the ident for kernels is the flavour, to also handle cases like kernel-base and kernel which should be in the same group handled together
541         addPackageToMap( GroupInfo::Kernels, flavour, flavour, installedKrnlPck );
542
543       } else {
544
545         // if adapting the groups do not forget to explicitely handle the group when querying the matches
546         const str::regex explicitelyHandled("kernel-syms(-.*)?|kernel(-.*)?-devel");
547
548         MIL << "Not a kernel package, inspecting more closely " << std::endl;
549
550         // we directly handle all noarch packages that export multiversion(kernel)
551         if ( installedKrnlPck.arch() == Arch_noarch ) {
552
553           MIL << "Handling package explicitely due to architecture (noarch)."<< std::endl;
554           addPackageToMap( GroupInfo::Sources, installedKrnlPck.name(), "", installedKrnlPck );
555
556         } else if ( str::smatch match; str::regex_match( installedKrnlPck.name(), match, explicitelyHandled ) ) {
557
558           // try to get the flavour from the name
559           // if we have a kernel-syms getting no flavour means we have the "default" one, otherwise we use the flavour
560           // getting no flavour for a kernel(-*)?-devel means we have the kernel-devel package otherwise the flavour specific one
561           // ...yes this is horrible
562           std::string flav;
563
564           // first group match is a kernel-syms
565           if ( match.size() > 1 && match[1].size() )
566             flav = match[1].substr(1);
567           // second group match is a kernel-flavour-devel
568           else if ( match.size() > 2 &&  match[2].size() )
569             flav = match[2].substr(1);
570           else if ( installedKrnlPck.name() == "kernel-syms" )
571             flav = "default";
572
573           MIL << "Handling package explicitely due to name match."<< std::endl;
574           addPackageToMap ( GroupInfo::RelatedBinaries, installedKrnlPck.name(), flav, installedKrnlPck );
575         } else {
576           MIL << "Package not explicitely handled" << std::endl;
577         }
578       }
579
580     }
581
582     MIL << "Grouped packages: " << std::endl;
583     std::for_each( installedKrnlPackages.begin(), installedKrnlPackages.end(),[]( const auto &ident ){
584       MIL << "\tGroup ident: "<<ident.first<<std::endl;
585       MIL << "\t Group type: "<<ident.second.groupType<<std::endl;
586       MIL << "\t Group flav: "<<ident.second.groupFlavour<<std::endl;
587       std::for_each( ident.second.archToEdMap.begin(), ident.second.archToEdMap.end(), []( const auto &arch) {
588         MIL << "\t\tArch: "<<arch.first<<std::endl;
589         std::for_each( arch.second.begin(), arch.second.end(), []( const auto &edition) {
590           MIL << "\t\t\tEdition: "<<edition.first<<std::endl;
591           std::for_each( edition.second.begin(), edition.second.end(), []( const auto &packageId) {
592             MIL << "\t\t\t\t "<<sat::Solvable(packageId)<<std::endl;
593           });
594         });
595       });
596     });
597
598     _pimpl->fillKeepList( installedKrnlPackages, packagesToKeep, packagesToRemove );
599
600     for ( sat::Solvable slv : packagesToRemove )
601       _pimpl->removePackageAndCheck( slv, packagesToKeep, packagesToRemove );
602   }
603
604   void PurgeKernels::setUnameR( const std::string &val )
605   {
606     _pimpl->setUnameR( val );
607   }
608
609   std::string PurgeKernels::unameR() const
610   {
611     return _pimpl->_uname_r;
612   }
613
614   void PurgeKernels::setKernelArch(const Arch &arch)
615   {
616     _pimpl->_kernelArch = arch;
617   }
618
619   Arch PurgeKernels::kernelArch() const
620   {
621     return _pimpl->_kernelArch;
622   }
623
624   void PurgeKernels::setKeepSpec( const std::string &val )
625   {
626     _pimpl->_keepSpec = val;
627   }
628
629   std::string PurgeKernels::keepSpec() const
630   {
631     return _pimpl->_keepSpec;
632   }
633
634 }