0
 õ    #!/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)