diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6072d5680..e9f25c6ab 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,7 @@ FILE( GLOB ALLCC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc" ) STRING( REPLACE ".cc" ";" APLLPROG ${ALLCC} ) # make sure not to statically linked installed tools -SET( LINKALLSYM CalculateReusableBlocks DownloadFiles ) +SET( LINKALLSYM CalculateReusableBlocks DownloadFiles zypp-runpurge) FOREACH( loop_var ${APLLPROG} ) ADD_EXECUTABLE( ${loop_var} diff --git a/tools/zypp-runpurge.cc b/tools/zypp-runpurge.cc index 546e54f53..ec960de31 100644 --- a/tools/zypp-runpurge.cc +++ b/tools/zypp-runpurge.cc @@ -1,3 +1,5 @@ +#include +#include #include #include "argparse.h" @@ -22,6 +24,186 @@ int usage( const argparse::Options & options_r, int return_r = 0 ) return return_r; } + +// Helper for version sorted containers +struct NVR : public sat::Solvable { + NVR( const sat::Solvable & slv ) + : sat::Solvable { slv } + {} +}; + +inline std::ostream & operator<<( std::ostream & str, const NVR & obj ) +{ return str << obj.ident() << " " << obj.edition() << (obj.isSystem()?" (i)":""); } + +inline bool operator<( const NVR & lhs, const NVR & rhs ) +{ return compareByNVRA( lhs, rhs ) < 0; } + + +// Cluster KMPs by kernel and fitting KMP versions (check either installed or available versions) +// (see also bsc#1232399 etc.) +void KMPcheck( const bool checkSystem = true ) +{ + using SolvableSet =std::unordered_set; + + // Whether slv is on container + auto contains = []( const SolvableSet & container, const sat::Solvable & slv ) -> bool { + return container.find( slv ) != container.end(); + }; + + // Remove items from fitting which do not occur in matching + auto intersect = [&]( SolvableSet & fitting, const SolvableSet & matching ) { + for ( auto it = fitting.begin(); it != fitting.end(); ) { + if ( contains( matching, *it ) ) + ++it; + else + it = fitting.erase( it ); + } + }; + + SolvableSet kernels; // multiversion kernels + SolvableSet kmps; // multiversion kmps + SolvableSet others; // multiversion others + { + const Capability idxcap { "kernel-uname-r" }; + sat::WhatProvides idx { idxcap }; + for ( const auto & i : idx ) { + if ( i.isSystem() != checkSystem ) + continue; + kernels.insert( i ); + } + } + { + const Capability idxcap { "multiversion(kernel)" }; + sat::WhatProvides idx { idxcap }; + + StrMatcher matchMod { "kmod(*)", Match::GLOB }; + auto isKmp = [&matchMod]( const sat::Solvable & slv ) -> bool { + for ( const auto & prov : slv.provides() ) { + if ( matchMod.doMatch( prov.detail().name().c_str() ) ) + return true; + } + return false; + }; + + for ( const auto & i : idx ) { + if ( i.isSystem() != checkSystem ) + continue; + if ( not contains( kernels, i ) ) { + ( isKmp( i ) ? kmps : others ).insert( i ); + } + } + } + cout << "K " << kernels.size() << ", M " << kmps.size() << ", O " << others.size() << endl; + + // Caching which KMP requirement is provided by what kernels. + std::unordered_map whatKernelProvidesCache; + auto whatKernelProvides = [&]( const Capability & req ) -> const SolvableSet & { + auto iter = whatKernelProvidesCache.find( req ); + if ( iter != whatKernelProvidesCache.end() ) + return iter->second; // hit + // miss: + SolvableSet & providingKernels = whatKernelProvidesCache[req]; + sat::WhatProvides idx { req }; + for ( const auto & i : idx ) { + if ( contains( kernels, i ) ) + providingKernels.insert( i ); + } + return providingKernels; + }; + +#if 0 + using Bucket = SolvableSet; // fitting KMP versions + using KmpBuckets = std::unordered_map; // kernel : fitting KMP versions + using KernelKmps = std::unordered_map; // KMP name : kernel : fitting KMP versions +#else + // sorted containers: + using Bucket = std::set; // fitting KMP versions + using KmpBuckets = std::map; // kernel : fitting KMP versions + using KernelKmps = std::map; // KMP name : kernel : fitting KMP versions +#endif + KernelKmps kernelKmps; + + // Cluster the KMPs.... + for ( const auto & kmp : kmps ) { + //cout << endl << "==== " << kmp << endl; + std::optional fittingKernels; // kernel satisfying all kernel related requirements + for ( const auto & req : kmp.requires() ) { + const SolvableSet & providingKernels { whatKernelProvides( req ) }; + if ( providingKernels.size() == 0 ) + continue; // a not kernel related requirement + if ( not fittingKernels ) { + fittingKernels = providingKernels; // initial set + } else { + intersect( *fittingKernels, providingKernels ); + } + //cout << "?? " << req << " provided by " << providingKernels.size() << endl; + //cout << " - fit " << fittingKernels->size() << endl; + } + if ( fittingKernels ) { + for ( const auto & kernel : *fittingKernels ) { + kernelKmps[kmp.ident()][kernel].insert( kmp ); + } + } + else { + cout << endl << "==== " << kmp << endl; + cout << " No fitting kernels!" << endl; + } + } + + // Print the clusters... + if ( checkSystem ) { + + // For the installed/prunned system cluster per kernel is IMO easier to check + // ==== kernel-default 5.14.21-150400.24.136.1 (i) + // drbd-kmp-default fit 3 versions(s) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.22-150400.1.75 (i) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.11-150400.3.2.9 (i) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.46-150400.3.4.1 (i) + std::map> reordered; + for ( const auto & [kmp,bucket] : kernelKmps ) { + for ( const auto & [kernel,versions] : bucket ) { + reordered[kernel][kmp] = versions; + } + } + for ( const auto & [kernel,bucket] : reordered ) { + cout << endl << "==== " << kernel << endl; + for ( const auto & [kmp,versions] : bucket ) { + cout << " " << kmp << " fit " << versions.size() << " versions(s)" << endl; + for ( const auto & version : versions ) + cout << " - " << version << endl; + } + } + + } else { + + // ==== drbd-kmp-default + // ABI kernel-default 5.14.21-150400.24.136.1 (i) + // fit 3 versions(s) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.22-150400.1.75 (i) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.11-150400.3.2.9 (i) + // - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.46-150400.3.4.1 (i) + for ( const auto & [kmp,bucket] : kernelKmps ) { + cout << endl << "==== " << kmp << endl; + const Bucket * lastBucket = nullptr; + for ( const auto & [kernel,versions] : bucket ) { + if ( lastBucket && *lastBucket != versions ) { + cout << " fit " << lastBucket->size() << " versions(s)" << endl; + for ( const auto & version : *lastBucket ) + cout << " - " << version << endl; + } + cout << " ABI " << kernel << endl; + lastBucket = &versions; + } + if ( lastBucket ) { + cout << " fit " << lastBucket->size() << " versions(s)" << endl; + for ( const auto & version : *lastBucket ) + cout << " - " << version << endl; + } + } + + } +} + int main ( int argc, char *argv[] ) { @@ -102,6 +284,9 @@ int main ( int argc, char *argv[] ) std::cout << "Removing " << it->asString() + (it->status().isByUser() ? " (by user)" : " (autoremoved)") << std::endl; } + std::cout << "KMPcheck: " << std::endl; + KMPcheck(); + return 0; }