#!/usr/local/cpanel/3rdparty/bin/perl
# Copyright 2024 WebPros International, LLC
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited.
package scripts::find_and_fix_rpm_issues;
use cPstrict;
use parent qw( Cpanel::HelpfulScript );
use Cpanel::Usage;
use Cpanel::Binaries::Rpm ();
use Cpanel::OS ();
use Cpanel::Pkgr ();
use Cpanel::SafeRun::Simple ();
use Cpanel::Update::Logger ();
our $RPM_DB_DIR = '/var/lib/rpm';
exit( __PACKAGE__->new(@ARGV)->run() // 0 ) unless caller;
=encoding utf-8
=head1 NAME
find_and_fix_rpm_issues
=head1 USAGE
scripts/find_and_fix_rpm_issues [--findonly] [--rebuildonly] [--help]
=head1 DESCRIPTION
Detects problems with the rpm database and will rebuild the database
if it detects problems. Exits 0 if RPM is working properly, or if
we were able to fix it by rebuilding its database.
This script also detects duplicate cPanel RPMs, removes, and then
re-installs them if necessary.
--findonly - Detect and report problems. Do not make any changes.
--rebuildonly - Unconditionally rebuild the RPM database.
=cut
sub _OPTIONS {
return qw( findonly rebuildonly );
}
# NOTE: Return logic throughout the script is reversed so that $? is 0 for
# success or 1 for failure.
sub run {
my ($self) = @_;
my $logger = Cpanel::Update::Logger->new( { 'stdout' => 1, 'log_level' => 'debug', 'timestamp' => 0 } );
# Bail on non-rpm based s
if ( !Cpanel::OS::is_rpm_based() ) {
$logger->warn( "find_and_fix_rpm_issues: Cannot be used on a non rpm based distro. Current distro is " . Cpanel::OS::display_name() . "\n" );
return;
}
my $findonly = $self->getopt('findonly');
my $rebuildonly = $self->getopt('rebuildonly');
my $rpm_db_is_good = 1;
if ( !$rebuildonly ) {
my $status;
( $rpm_db_is_good, $status ) = Cpanel::Pkgr::verify_package_manager_can_install_packages($logger);
if ($rpm_db_is_good) {
my $rpm_db = _dump_rpm_db();
fix_duplicate_cpanel_rpms( $logger, $rpm_db );
$rpm_db_is_good = verify_no_duplicate_rpms( $logger, $rpm_db );
}
$logger->info("find_and_fix_rpm_issues: rpm issues have been found") if !$rpm_db_is_good;
}
$rpm_db_is_good = 0 if $rebuildonly;
if ( !$findonly && !$rpm_db_is_good ) {
$logger->info("find_and_fix_rpm_issues: Performing rpm rebuild");
# A non-zero return from rebuild_rpm_database indicates failure. It just returns $?.
rebuild_rpm_database($logger) && return 1;
}
remove_cpanel_obsoleted_rpms($logger);
return 0;
}
sub rebuild_rpm_database {
my ($logger) = @_;
if ( opendir my $dh, $RPM_DB_DIR ) {
while ( my $file = readdir $dh ) {
next unless $file =~ m{^__db\.[0-9]+$} && -f "$RPM_DB_DIR/$file";
unlink "$RPM_DB_DIR/$file" or do {
$logger->info("find_and_fix_rpm_issues: Could not unlink $RPM_DB_DIR/$file: $!");
return 1;
};
}
closedir $dh;
}
my $rpm = Cpanel::Binaries::Rpm->new;
my $result = $rpm->cmd( '-vvv', '--rebuilddb' );
my $exit_code = $result->{'status'} >> 8;
if ($exit_code) {
$logger->info("find_and_fix_rpm_issues: Rebuilding the rpm database failed with exit code $exit_code:");
$logger->debug( $result->{'output'} );
return 1;
}
else {
return 0;
}
}
sub _dump_rpm_db {
my $rpm = Cpanel::Binaries::Rpm->new;
my $result = $rpm->cmd( qw { -qa --nodigest --nosignature --queryformat }, '%{INSTALLTIME}\t%{NAME}\t%{VERSION}\t%{RELEASE}\t%{ARCH}\t\n' );
return [ split "\n", $result->{'output'} ];
}
sub fix_duplicate_cpanel_rpms {
my ( $logger, $rpmdb_ar ) = @_;
my %rpms;
my %rpm_erase;
foreach my $line (@$rpmdb_ar) {
next if index( $line, '.cp' ) == -1;
my ( $installtime, $name, $version, $release, $arch ) = split( m/\t/, $line );
# Only fix cp11## rpms.
next if ( $release !~ m/cp\d{4}$/ );
if ( $rpms{$name} ) {
$rpm_erase{ sprintf( "%s-%s-%s.%s", $name, $rpms{$name}[0], $rpms{$name}[1], $rpms{$name}[2] ) } = 1;
$rpm_erase{ sprintf( "%s-%s-%s.%s", $name, $version, $release, $arch ) } = 1;
}
else {
# No duplicate found.
$rpms{$name} = [ $version, $release, $arch ];
}
}
return 0 if !%rpm_erase;
$logger->info("Duplicate RPMs found.");
my $rpm = Cpanel::Binaries::Rpm->new;
my @cmd_args = ( qw{-e --nodeps --justdb}, sort { $a cmp $b } keys %rpm_erase );
$logger->info( "\$> rpm " . join( " ", @cmd_args ) . "\n" );
my $result = $rpm->cmd(@cmd_args);
$logger->info( $result->{'output'} );
$logger->info("\$> /usr/local/cpanel/scripts/check_cpanel_pkgs --fix\n");
$logger->info( Cpanel::SafeRun::Simple::saferunallerrors(qw{/usr/local/cpanel/scripts/check_cpanel_pkgs --fix --no-digest}) );
return 0;
}
# if check_cpanel_pkgs or one of its child processes are killed during an rpm transaction, this can put the rpm
# database in an unstable state as far as what rpms should be installed. This cleans that mistake up after the fact.
#
# NEVER kill -9 an rpm command. Bad things can happen!
sub remove_cpanel_obsoleted_rpms ($logger) {
my $obsoletes = Cpanel::Pkgr::installed_cpanel_obsoletes();
return unless ref $obsoletes && @$obsoletes; # Nothing is obsolete!
$logger->info( "Removing obsoleted package(s): " . join( ", ", @$obsoletes ) );
$logger->info( Cpanel::Pkgr::remove_packages_nodeps(@$obsoletes) );
$logger->info("Attempting to fix the local install by running scripts/check_cpanel_pkgs --fix --no-digest");
$logger->info( Cpanel::SafeRun::Simple::saferunallerrors(qw{/usr/local/cpanel/scripts/check_cpanel_pkgs --fix --no-digest}) );
}
# NOTE: The logic here may not be obvious.
# If the system has duplicate RPMs, this function will return 0, indicating a problem.
# Otherwise, it will return 1, indicating that it did not detect a problem.
#
# (That doesn't mean there isn't a problem; it just means we didn't find one.)
sub verify_no_duplicate_rpms {
my ( $logger, $rpmdb_ar ) = @_;
my %rpm_hash;
$rpm_hash{ substr( $_, index( $_, "\t" ) + 1 ) }++ for @$rpmdb_ar;
# Multiple kernel packages are ok
delete @rpm_hash{ grep { index( $_, "kernel" ) == 0 } keys %rpm_hash };
if ( grep { $_ > 1 } values %rpm_hash ) {
foreach my $line ( grep { $rpm_hash{$_} > 1 } keys %rpm_hash ) {
my ( $name, $version, $release, $arch ) = split( m/\t/, $line );
my $dupe_count = $rpm_hash{$line} - 1;
$logger->info( "The “$name” package has “$dupe_count” duplicate package" . ( $dupe_count > 1 ? 's' : '' ) . " installed." );
}
return 0;
}
return 1;
}
1;
Mr. DellatioNx196 GaLers xh3LL Backd00r 1.0, Coded By Mr. DellatioNx196 - Bogor BlackHat