libzypp  17.23.5
PurgeKernels.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
13 #include <zypp/base/String.h>
14 #include <zypp/base/Logger.h>
15 #include <zypp/base/Regex.h>
16 #include <zypp/ui/Selectable.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 
30 #undef ZYPP_BASE_LOGGER_LOGGROUP
31 #define ZYPP_BASE_LOGGER_LOGGROUP "PurgeKernels"
32 
33 namespace zypp {
34 
36 
37  Impl() {
38  struct utsname unameData;
39  if ( uname( &unameData) == 0 ) {
40  _kernelArch = Arch( unameData.machine );
41  _uname_r = std::string( unameData.release );
42  }
43  }
44 
45  bool removePackageAndCheck( PoolItem &item, const str::regex &validRemovals ) const;
46  void parseKeepSpec();
47  void fillKeepList( const std::unordered_map< std::string, std::map< Arch, std::map<Edition, sat::Solvable> > > &installedKernels, std::set<sat::Solvable::IdType> &list ) const;
48  void cleanDevelAndSrcPackages ( const str::regex &validRemovals, std::set<Edition> &validEditions, const std::string &flavour = std::string() );
49 
50  static std::string detectRunningKernel() {
51 
52  std::string kernelVersion;
53  std::ifstream procKernel( "/proc/sys/kernel/osrelease" );
54  if ( procKernel ) {
55  std::getline( procKernel, kernelVersion );
56  }
57  return kernelVersion;
58 
59  }
60 
61 
62  std::set<size_t> _keepLatestOffsets = { 0 };
63  std::set<size_t> _keepOldestOffsets;
64  std::set<Edition> _keepSpecificEditions;
65  std::string _uname_r;
68  bool _keepRunning = true;
69  };
70 
75  bool PurgeKernels::Impl::removePackageAndCheck( PoolItem &pi, const str::regex &validRemovals ) const
76  {
77  const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
78  auto pool = ResPool::instance();
79 
80  //remember which packages are already marked for removal, we do not need to check them again
81  std::set< sat::Solvable::IdType> currentSetOfRemovals;
82  for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter ); it++ )
83  currentSetOfRemovals.insert( it->id() );
84 
86 
87  if ( !pool.resolver().resolvePool() ) {
88  MIL << "Failed to resolve pool, skipping " << pi << std::endl;
89  pool.resolver().problems();
90  pi.statusReset();
91 
92  return false;
93  }
94 
95  for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter ); it++ ) {
96 
97  //this was set by us or marked by a previous removal, ignore them
98  if ( it->status().isByUser() || (currentSetOfRemovals.find( it->id() ) != currentSetOfRemovals.end()) )
99  continue;
100 
101  str::smatch what;
102  if ( !str::regex_match( it->name(), what, validRemovals) ) {
103  MIL << "Package " << PoolItem(*it) << " should not be removed, skipping " << pi << std::endl;
104  pi.statusReset();
105  return false;
106  }
107  }
108 
109  MIL << "Removing package: " << pi << std::endl;
110  return true;
111  }
112 
117  {
118  //keep spec parse regex, make sure to edit the group offsets if changing this regex
119  const str::regex specRegex( "^(latest|oldest)([+-][0-9]+)?$", str::regex::match_extended );
120 
121  const unsigned tokenGrp = 1; //index of the group matching the token
122  const unsigned modifierGrp = 2; //index of the group matching the offset modifier
123 
124 
125  MIL << "Parsing keep spec: " << _keepSpec << std::endl;
126 
127  std::vector<std::string> words;
128  str::split( _keepSpec, std::back_inserter(words), ",", str::TRIM );
129  if ( words.empty() ) {
130  WAR << "Invalid keep spec: " << _keepSpec << " using default latest,running." << std::endl;
131  return;
132  }
133 
134  _keepRunning = false;
135  _keepLatestOffsets.clear();
136  _keepOldestOffsets.clear();
137 
138  for ( const std::string &word : words ) {
139  if ( word == "running" ) {
140  _keepRunning = true;
141  } else {
142  str::smatch what;
143  if ( !str::regex_match( word, what, specRegex ) ) {
144  _keepSpecificEditions.insert( Edition(word) );
145  continue;
146  }
147 
148  auto addKeepOff = []( const auto &off, auto &set, const auto &constraint ){
149  const off_t num = off.empty() ? 0 : str::strtonum<off_t>( off );
150  if ( !constraint(num) ) return false;
151  set.insert( static_cast<size_t>(std::abs(num)) );
152  return true;
153  };
154 
155  if ( what[tokenGrp] == "oldest" ) {
156  addKeepOff( what[modifierGrp], _keepOldestOffsets, [ &word ]( off_t num ) {
157  if ( num < 0 ) {
158  WAR << "Ignoring invalid modifier in keep spec: " << word << ", oldest supports only positive modifiers." << std::endl;
159  return false;
160  }
161  return true;
162  });
163  } else {
164  addKeepOff( what[modifierGrp], _keepLatestOffsets, [ &word ]( off_t num ) {
165  if ( num > 0 ) {
166  WAR << "Ignoring invalid modifier in keep spec: " << word << ", latest supports only negative modifiers." << std::endl;
167  return false;
168  }
169  return true;
170  });
171  }
172  }
173  }
174  }
175 
180  void PurgeKernels::Impl::fillKeepList( const std::unordered_map<std::string, std::map<Arch, std::map<Edition, sat::Solvable> > > &installedKernels, std::set<sat::Solvable::IdType> &list ) const
181  {
182  for ( const auto &flavourMap : installedKernels ) {
183  for ( const auto &archMap : flavourMap.second ) {
184  size_t currOff = 0; //the current "oldest" offset ( runs from map start to end )
185  size_t currROff = archMap.second.size() - 1; // the current "latest" offset ( runs from map end to start )
186  for ( const auto &kernelMap : archMap.second ) {
187 
188  //if we find one of the running offsets in the keepspec, we add the kernel id the the list of packages to keep
189  if ( _keepOldestOffsets.find( currOff ) != _keepOldestOffsets.end()
190  || _keepLatestOffsets.find( currROff ) != _keepLatestOffsets.end()
191  // a kernel might be explicitely locked by version
192  || _keepSpecificEditions.find( kernelMap.second.edition() ) != _keepSpecificEditions.end() ) {
193  MIL << "Marking kernel " << kernelMap.second << " as to keep." << std::endl;
194  list.insert( kernelMap.second.id() ) ;
195  }
196 
197  currOff++;
198  currROff--;
199  }
200  }
201  }
202  }
203 
207  void PurgeKernels::Impl::cleanDevelAndSrcPackages(const str::regex &validRemovals, std::set<Edition> &validEditions, const std::string &flavour )
208  {
209  bool isWithFlavour = flavour.size();
210 
211  if ( isWithFlavour )
212  MIL << "Trying to remove source/devel packages for flavour " << flavour << std::endl;
213  else
214  MIL << "Trying to remove global/default source/devel packages "<< std::endl;
215 
216  auto withFlavour = [&isWithFlavour, &flavour]( const std::string &name ) {
217  return isWithFlavour ? name+"-"+flavour : name;
218  };
219 
220  //try to remove the kernel-devel-flavour and kernel-source-flavour packages
221  PoolQuery q;
223 
224  q.addAttribute( sat::SolvAttr::name, withFlavour("kernel-devel") );
225  q.addAttribute( sat::SolvAttr::name, withFlavour("kernel-source") );
226  q.setInstalledOnly();
227  q.setMatchExact();
228 
229  for ( auto installedSrcPck : q ) {
230  // For now print a message that we are removing a source package that has no corresponding kernel installed.
231  // This was changed due to bug #1171224 because orphaned kernel-source/devel packages were kept due to package
232  // rebuilds that did not obsolete the previously installed release, e.g. kernel-source-1-1.1 vs kernel-source-1-1.2
233  if ( validEditions.find( installedSrcPck.edition() ) == validEditions.end() ) {
234  MIL << "Trying to remove source package " << installedSrcPck << " no corresponding kernel with the same version was installed." << std::endl;
235  }
236 
237  //if no package providing kernel-flavour = VERSION is installed , we are free to remove the package
238  PoolQuery instKrnl;
239  instKrnl.addKind( zypp::ResKind::package );
240  instKrnl.setInstalledOnly();
241  instKrnl.setMatchExact();
242  instKrnl.addDependency( sat::SolvAttr::provides, withFlavour("kernel"), Rel::EQ, installedSrcPck.edition() );
243 
244  bool found = std::any_of ( instKrnl.begin(), instKrnl.end(), []( auto it ) { return !PoolItem(it).status().isToBeUninstalled(); } );
245  if ( found ) {
246  MIL << "Skipping source package " << installedSrcPck << " binary packages with the same edition are still installed" << std::endl;
247  continue;
248  }
249 
250  PoolItem pi( installedSrcPck );
251  removePackageAndCheck( pi, validRemovals );
252  }
253  }
254 
256  : _pimpl( new Impl() )
257  {
258 
259  }
260 
262  {
263  if ( _pimpl->_keepSpec.empty() )
264  return;
265 
267 
268  auto pool = ResPool::instance();
269  pool.resolver().setForceResolve( true ); // set allow uninstall flag
270 
271  const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
272 
273  //list of packages that are allowed to be removed automatically.
274  const str::regex validRemovals("(kernel-syms(-.*)?|kgraft-patch(-.*)?|kernel-livepatch(-.*)?|.*-kmp(-.*)?)");
275 
276  //list of packages that are allowed to be removed automatically when uninstalling kernel-devel packages
277  const str::regex validDevelRemovals("(kernel-source(-.*)?|(kernel-syms(-.*)?)|(kernel-devel(-.*)?)|(kernel(-.*)?-devel))");
278 
279  // kernel flavour regex
280  const str::regex kernelFlavourRegex("^kernel-(.*)$");
281 
282  // the map of all installed kernels, grouped by Flavour -> Arch -> Version
283  std::unordered_map< std::string, std::map< Arch, std::map<Edition, sat::Solvable> > > installedKernels;
284 
285  // the set of kernel package IDs that have to be kept always
286  std::set<sat::Solvable::IdType> packagesToKeep;
287 
288  //collect the list of installed kernel packages
289  PoolQuery q;
291  q.addAttribute( sat::SolvAttr::provides, "kernel" );
292  q.setInstalledOnly();
293  q.setMatchExact();
294 
295  MIL << "Searching for obsolete kernels." << std::endl;
296 
297  for ( auto installedKernel : q ) {
298 
299  MIL << "Found installed kernel " << installedKernel << std::endl;
300 
301  //we can not simply skip the running kernel to make sure the keep-spec works correctly
302  if ( _pimpl->_keepRunning
303  && installedKernel.provides().matches( Capability( "kernel-uname-r", Rel::EQ, Edition( _pimpl->_uname_r ) ) )
304  && installedKernel.arch() == _pimpl->_kernelArch ) {
305  MIL << "Marking kernel " << installedKernel << " as to keep." << std::endl;
306  packagesToKeep.insert( installedKernel.id() );
307  }
308 
309  str::smatch what;
310  str::regex_match( installedKernel.name(), what, kernelFlavourRegex );
311  if ( what[1].empty() ) {
312  WAR << "Could not detect kernel flavour for: " << installedKernel << " ...skipping" << std::endl;
313  continue;
314  }
315 
316  const std::string flavour = what[1];
317  if ( !installedKernels.count( flavour ) )
318  installedKernels.insert( std::make_pair( flavour, std::map< Arch, std::map<Edition, sat::Solvable> > {} ) );
319 
320  auto &flavourMap = installedKernels[ flavour ];
321  if ( !flavourMap.count( installedKernel.arch() ) )
322  flavourMap.insert( std::make_pair( installedKernel.arch(), std::map<Edition, sat::Solvable>{} ) );
323 
324  flavourMap[ installedKernel.arch() ].insert( std::make_pair( installedKernel.edition(), installedKernel ) );
325  }
326 
327  _pimpl->fillKeepList( installedKernels, packagesToKeep );
328 
329 
330  MIL << "Starting to remove obsolete kernels." << std::endl;
331 
332 
333  std::set<Edition> removedVersions;
334 
335  /*
336  * If there is a KMP or livepatch depending on the package remove it as well. If
337  * there is another package depending on the kernel keep the kernel. If there is
338  * a package that depends on a KMP keep the KMP and a kernel required to use the
339  * KMP.
340  */
341  for ( const auto &flavourMap : installedKernels ) {
342 
343  // collect all removed versions of this edition
344  std::set<Edition> removedFlavourVersions;
345 
346  for ( const auto &archMap : flavourMap.second ) {
347  for ( const auto &kernelMap : archMap.second ) {
348  auto &installedKernel = kernelMap.second;
349 
350  // if the kernel is locked by the user, its not removed automatically
351  if ( ui::asSelectable()( installedKernel )->hasLocks() )
352  continue;
353 
354  // this package is in the keep spec, do not touch
355  if ( packagesToKeep.count( installedKernel.id() ) )
356  continue;
357 
358  // try to remove the kernel package, check afterwards if only expected packages have been removed
359  PoolItem pi( installedKernel );
360  if ( !_pimpl->removePackageAndCheck( pi, validRemovals ) ) {
361  continue;
362  }
363 
364  removedFlavourVersions.insert( installedKernel.edition() );
365 
366  //lets remove the kernel-flavour-devel package too
367  PoolQuery develPckQ;
368  develPckQ.addKind( zypp::ResKind::package );
369  develPckQ.addDependency( sat::SolvAttr::name, installedKernel.name()+"-devel", Rel::EQ, installedKernel.edition() );
370  develPckQ.addDependency( sat::SolvAttr::name, installedKernel.name()+"-devel-debuginfo", Rel::EQ, installedKernel.edition() );
371  develPckQ.setInstalledOnly();
372  develPckQ.setMatchExact();
373 
374  for ( auto krnlDevPck : develPckQ ) {
375 
376  if ( krnlDevPck.arch() != installedKernel.arch() )
377  continue;
378 
379  PoolItem devPi(krnlDevPck);
380  _pimpl->removePackageAndCheck( devPi, validDevelRemovals );
381  }
382  }
383  }
384  //try to remove the kernel-devel-flavour and kernel-source-flavour packages
385  _pimpl->cleanDevelAndSrcPackages( validDevelRemovals, removedFlavourVersions, flavourMap.first );
386  removedVersions.insert( removedFlavourVersions.begin(), removedFlavourVersions.end() );
387  }
388 
389  // clean the global -devel and -source packages
390  _pimpl->cleanDevelAndSrcPackages( validDevelRemovals, removedVersions );
391  }
392 
393  void PurgeKernels::setUnameR( const std::string &val )
394  {
395  _pimpl->_uname_r = val;
396  }
397 
398  std::string PurgeKernels::unameR() const
399  {
400  return _pimpl->_uname_r;
401  }
402 
404  {
405  _pimpl->_kernelArch = arch;
406  }
407 
409  {
410  return _pimpl->_kernelArch;
411  }
412 
413  void PurgeKernels::setKeepSpec( const std::string &val )
414  {
415  _pimpl->_keepSpec = val;
416  }
417 
418  std::string PurgeKernels::keepSpec() const
419  {
420  return _pimpl->_keepSpec;
421  }
422 
423 }
ResPool.h
zypp::PoolItem
Combining sat::Solvable and ResStatus.
Definition: PoolItem.h:50
PurgeKernels.h
zypp::filter::ByStatus
Filter solvables according to their status.
Definition: Filter.h:141
zypp::PurgeKernels::keepSpec
std::string keepSpec() const
Definition: PurgeKernels.cc:418
zypp::PurgeKernels::unameR
std::string unameR() const
Definition: PurgeKernels.cc:398
zypp::PurgeKernels::Impl::fillKeepList
void fillKeepList(const std::unordered_map< std::string, std::map< Arch, std::map< Edition, sat::Solvable > > > &installedKernels, std::set< sat::Solvable::IdType > &list) const
Definition: PurgeKernels.cc:180
zypp::PoolQuery
Meta-data query API.
Definition: PoolQuery.h:90
zypp::PurgeKernels::markObsoleteKernels
void markObsoleteKernels()
Definition: PurgeKernels.cc:261
zypp::PoolQuery::addAttribute
void addAttribute(const sat::SolvAttr &attr, const std::string &value="")
Filter by the value of the specified attr attribute.
Definition: PoolQuery.cc:873
zypp::PurgeKernels::Impl::Impl
Impl()
Definition: PurgeKernels.cc:37
zypp::PurgeKernels::Impl::parseKeepSpec
void parseKeepSpec()
Definition: PurgeKernels.cc:116
zypp::PurgeKernels::Impl::_keepOldestOffsets
std::set< size_t > _keepOldestOffsets
Definition: PurgeKernels.cc:63
zypp::PoolQuery::addDependency
void addDependency(const sat::SolvAttr &attr, const std::string &name, const Rel &op, const Edition &edition)
Query "name|global op edition".
Definition: PoolQuery.cc:876
ZConfig.h
MIL
#define MIL
Definition: Logger.h:79
zypp::Edition
Edition represents [epoch:]version[-release]
Definition: Edition.h:60
zypp::ResKind::package
static const ResKind package
Definition: ResKind.h:40
zypp::PurgeKernels::Impl::removePackageAndCheck
bool removePackageAndCheck(PoolItem &item, const str::regex &validRemovals) const
Definition: PurgeKernels.cc:75
zypp::ZConfig::multiversionKernels
std::string multiversionKernels() const
Definition: ZConfig.cc:1190
zypp::ResPool::instance
static ResPool instance()
Singleton ctor.
Definition: ResPool.cc:37
zypp::PoolQuery::addKind
void addKind(const ResKind &kind)
Filter by selectable kind.
Definition: PoolQuery.cc:867
zypp::PurgeKernels::Impl::_keepLatestOffsets
std::set< size_t > _keepLatestOffsets
Definition: PurgeKernels.cc:62
zypp::PurgeKernels::Impl::_keepSpec
std::string _keepSpec
Definition: PurgeKernels.cc:67
zypp::Arch
Architecture.
Definition: Arch.h:36
zypp::PurgeKernels::Impl::_keepSpecificEditions
std::set< Edition > _keepSpecificEditions
Definition: PurgeKernels.cc:64
zypp::str::TRIM
Definition: String.h:497
zypp::iostr::getline
std::string getline(std::istream &str)
Read one line from stream.
Definition: IOStream.cc:32
zypp::PurgeKernels::setUnameR
void setUnameR(const std::string &val)
Definition: PurgeKernels.cc:393
Resolver.h
zypp::str::split
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.
Definition: String.h:527
zypp::PurgeKernels::Impl::detectRunningKernel
static std::string detectRunningKernel()
Definition: PurgeKernels.cc:50
Logger.h
WAR
#define WAR
Definition: Logger.h:80
zypp::sat::SolvAttr::name
static const SolvAttr name
Definition: SolvAttr.h:52
zypp::ZConfig::instance
static ZConfig & instance()
Singleton ctor.
Definition: Resolver.cc:123
zypp::PoolQuery::end
const_iterator end() const
An iterator pointing to the end of the query result.
Definition: PoolQuery.h:614
zypp::PoolQuery::setInstalledOnly
void setInstalledOnly()
Return only @System repo packages.
Definition: PoolQuery.cc:963
zypp::PurgeKernels::kernelArch
Arch kernelArch() const
Definition: PurgeKernels.cc:408
zypp
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:1
zypp::PoolQuery::begin
const_iterator begin() const
Query result accessers.
Definition: PoolQuery.cc:1826
PoolQuery.h
Regex.h
zypp::PurgeKernels::Impl::_keepRunning
bool _keepRunning
Definition: PurgeKernels.cc:68
zypp::PurgeKernels::PurgeKernels
PurgeKernels()
Definition: PurgeKernels.cc:255
Selectable.h
zypp::PurgeKernels::_pimpl
RW_pointer< Impl > _pimpl
Definition: PurgeKernels.h:66
zypp::ResStatus::setToBeUninstalled
bool setToBeUninstalled(TransactByValue causer)
Definition: ResStatus.h:545
zypp::str::smatch
Regular expression match result.
Definition: Regex.h:145
zypp::Rel::EQ
static const Rel EQ
Definition: Rel.h:50
zypp::PurgeKernels::setKeepSpec
void setKeepSpec(const std::string &val)
Definition: PurgeKernels.cc:413
zypp::Capability
A sat capability.
Definition: Capability.h:59
zypp::ResStatus::USER
Definition: ResStatus.h:111
zypp::PoolItem::status
ResStatus & status() const
Returns the current status.
Definition: PoolItem.cc:204
zypp::ui::asSelectable
Solvable to Selectable transform functor.
Definition: Selectable.h:565
zypp::ResStatus::isToBeUninstalled
bool isToBeUninstalled() const
Definition: ResStatus.h:261
zypp::str::regex::match_extended
Use POSIX Extended Regular Expression syntax when interpreting regex.
Definition: Regex.h:93
zypp::PurgeKernels::Impl
Definition: PurgeKernels.cc:35
String.h
zypp::sat::SolvAttr::provides
static const SolvAttr provides
Definition: SolvAttr.h:60
zypp::PoolQuery::setMatchExact
void setMatchExact()
Set to match exact string instead of substring.
Definition: PoolQuery.cc:952
zypp::PurgeKernels::setKernelArch
void setKernelArch(const zypp::Arch &arch)
Definition: PurgeKernels.cc:403
zypp::PurgeKernels::Impl::_uname_r
std::string _uname_r
Definition: PurgeKernels.cc:65
zypp::str::regex_match
bool regex_match(const std::string &s, smatch &matches, const regex &regex)
\relates regex \ingroup ZYPP_STR_REGEX \relates regex \ingroup ZYPP_STR_REGEX
Definition: Regex.h:70
zypp::PurgeKernels::Impl::cleanDevelAndSrcPackages
void cleanDevelAndSrcPackages(const str::regex &validRemovals, std::set< Edition > &validEditions, const std::string &flavour=std::string())
Definition: PurgeKernels.cc:207
zypp::str::regex
Regular expression.
Definition: Regex.h:86
zypp::PurgeKernels::Impl::_kernelArch
Arch _kernelArch
Definition: PurgeKernels.cc:66
zypp::PoolItem::statusReset
ResStatus & statusReset() const
Reset status.
Definition: PoolItem.cc:205
Filter.h