#!/usr/local/cpanel/3rdparty/bin/perl
# cpanel - autofixer2/auto_migration_ea3_to_ea4    Copyright 2020 cPanel, L.L.C.
#                                                           All rights Reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

package autofixer2::auto_migrate_ea3_to_ea4;

use strict;
use warnings;
our $VERSION = 12;

use Cpanel::AcctUtils::DomainOwner ();
use Cpanel::Config::Users          ();
use Cpanel::DomainLookup           ();
use Cpanel::FileUtils::Attr        ();
use Cpanel::HttpUtils              ();
use Cpanel::Logger                 ();
use Cpanel::PHPINI                 ();
use Cpanel::Version::Tiny          ();
use Cpanel::SafeDir::Read          ();
use Cpanel::Sys::OS                ();
use Cpanel::Proc::PID              ();
use Cpanel::SafeRun::Object        ();
use Cpanel::Update::Config         ();
use Cpanel::YAML                   ();
use Digest::MD5                    ();
use IO::Callback                   ();
use Capture::Tiny                  ();

use HTTP::Tiny;
use Path::Tiny qw(path);
use Path::Iter;
use Config::INI::Reader;
use Config::INI::Writer;

our $blocker_file  = '/var/cpanel/update_blocks.config';
our $ea3_serverxml = "/usr/local/easy/share/easy-tomcat7/conf/server.xml";
our $ea3_phpinid   = '/usr/local/lib/php.ini.d';                             # /usr/local/lib/php.ini.d can come from Cpanel/Easy/PHP5/SafePHPCGI.pm
our $default_extdir;
our $ea3_phpini          = '/usr/local/lib/php.ini';
our $marked_as_ran       = "/var/cpanel/auto_migrate_ea3_to_ea4.v$VERSION";
our $floodgate_url       = 'https://tsunami.cpanel.net/the-great-ea3-migration';
our $disabled_yum_repos  = undef;
our $find_and_fix_rpms   = '/usr/local/cpanel/scripts/find_and_fix_rpm_issues';
our $yum_conf            = '/etc/yum.conf';
our $ub_php              = '/usr/bin/php';
our $ulb_php             = '/usr/local/bin/php';
our $ea3_profile_fname   = '/var/cpanel/easy/apache/profile/_main.yaml';
our $ea4_enabled         = '/etc/cpanel/ea4/is_ea4';
our $magic_bak_extension = '.ea3-auto-migration.bak';
our $usr_local_phpini    = '/usr/local/lib/php.ini';

our $x_correlation_id = '_NOT_DETERMINED_YET_';
our $yum_repos_d      = '/etc/yum.repos.d';

our $log_version_str = "autofixer version v$VERSION";

our $httpd_conf = "/usr/local/apache/conf/httpd.conf";

our $logger;

our %yum_repos_to_disable = (
    'City-Fan'           => { url_qr => qr/city-fan\.org/i },
    'cpanel-letsencrypt' => { url_qr => qr/r\.cpanel\.fleetssl\.com/i, section_qr => qr/^cpanel-plugins$/, },
    'DAG'                => { url_qr => qr/apt\.sw\.be/i, mirrorlist_qr => qr/(?:rpmforge|repoforge)/ },
);

our %known_php_extensions = (    # filename w/ ext since they do not all end in .so
    "memcache.so"                                                  => 1,
    "memcached.so"                                                 => 1,
    "ixed.5.6.lin"                                                 => 1,
    "ixed.5.5.lin"                                                 => 1,
    "ixed.5.4.lin"                                                 => 1,
    "/usr/local/IonCube/ioncube_loader_lin_5.6.so"                 => 1,
    "/usr/local/IonCube/ioncube_loader_lin_5.5.so"                 => 1,
    "/usr/local/IonCube/ioncube_loader_lin_5.4.so"                 => 1,
    "/usr/local/Zend/lib/Guard-7.0.0/php-5.6.x/ZendGuardLoader.so" => 1,
    "/usr/local/Zend/lib/Guard-7.0.0/php-5.5.x/ZendGuardLoader.so" => 1,
    "/usr/local/Zend/lib/Guard-6.0.0/php-5.4.x/ZendGuardLoader.so" => 1,
    "pdo_mysql.so"                                                 => 1,
    "pdo.so"                                                       => 1,
    "pdo_sqlite.so"                                                => 1,
    "suhosin.so"                                                   => 1,
    "opcache.so"                                                   => 1,
);

our %allowed_custom_opt_mods = (
    'Cpanel::Easy::Speed'                                => 1,
    'Cpanel::Easy::ModGeoIP'                             => 1,
    'Cpanel::Easy::ModHTScanner'                         => 1,
    'Cpanel::Easy::Apache::Bucketeer'                    => 1,
    'Cpanel::Easy::Apache::Echo'                         => 1,
    'Cpanel::Easy::Apache::FpOptMod'                     => 1,
    'Cpanel::Easy::Apache::Ident'                        => 1,
    'Cpanel::Easy::Apache::OptionalFnExport'             => 1,
    'Cpanel::Easy::Apache::OptionalFnImport'             => 1,
    'Cpanel::Easy::Apache::OptionalHookExport'           => 1,
    'Cpanel::Easy::Apache::OptionalHookImport'           => 1,
    'Cpanel::Easy::ModBandwidthPatched'                  => 1,
    'Cpanel::Easy::PHP::_'                               => 1,
    'Cpanel::Easy::PHP5::Dbase'                          => 1,
    'Cpanel::Easy::PHP5::DiscardPath'                    => 1,
    'Cpanel::Easy::PHP5::DomXslt'                        => 1,
    'Cpanel::Easy::PHP5::MemcachePHP'                    => 1,
    'Cpanel::Easy::PHP5::PHPExecDirPatch'                => 1,
    'Cpanel::Easy::PHP5::Versioning'                     => 1,
    'Cpanel::Easy::Apache::Access'                       => 1,
    'Cpanel::Easy::Apache::Auth'                         => 1,
    'Cpanel::Easy::Apache::AuthBasic'                    => 1,
    'Cpanel::Easy::Apache::AuthnAlias'                   => 1,
    'Cpanel::Easy::Apache::AuthzDefault'                 => 1,
    'Cpanel::Easy::Apache::AuthzGroupfile'               => 1,
    'Cpanel::Easy::Apache::AuthzUser'                    => 1,
    'Cpanel::Easy::Apache::CaseFilter'                   => 1,
    'Cpanel::Easy::Apache::CaseFilterIn'                 => 1,
    'Cpanel::Easy::Apache::CGI'                          => 1,
    'Cpanel::Easy::Apache::CGId'                         => 1,
    'Cpanel::Easy::Apache::Fileprotect'                  => 1,
    'Cpanel::Easy::Apache::Frontpage'                    => 1,
    'Cpanel::Easy::Apache::Info'                         => 1,
    'Cpanel::Easy::Apache::RaiseFDSetsize'               => 1,
    'Cpanel::Easy::Apache::RaiseHardServerLimit'         => 1,
    'Cpanel::Easy::Apache::SlowRestartPatch'             => 1,
    'Cpanel::Easy::Apache::SymlinkProtection'            => 1,
    'Cpanel::Easy::Apache::SymLinkPatch'                 => 1,
    'Cpanel::Easy::Apache::FollowSymLinks_to_OwnerMatch' => 1,
    'Cpanel::Easy::ModCloudflare'                        => 1,
    'Cpanel::Easy::ModFastInclude'                       => 1,
    'Cpanel::Easy::ModReqtimeout'                        => 1,
    'Cpanel::Easy::ModSubstitute'                        => 1,
    'Cpanel::Easy::ModGzip'                              => 1,
    'Cpanel::Easy::ModJk'                                => 1,
    'Cpanel::Easy::ModJk5'                               => 1,
    'Cpanel::Easy::PHP5::Bcmath'                         => 1,
    'Cpanel::Easy::PHP5::cPPHPOpts'                      => 1,
    'Cpanel::Easy::PHP5::Curlwrappers'                   => 1,
    'Cpanel::Easy::PHP5::Expat'                          => 1,
    'Cpanel::Easy::PHP5::Fastcgi'                        => 1,
    'Cpanel::Easy::PHP5::ForceCGIRedirect'               => 1,
    'Cpanel::Easy::PHP5::MagicQuotes'                    => 1,
    'Cpanel::Easy::PHP5::MailHeaders'                    => 1,
    'Cpanel::Easy::PHP5::MemoryLimit'                    => 1,
    'Cpanel::Easy::PHP5::Mhash'                          => 1,
    'Cpanel::Easy::PHP5::MimeMagic'                      => 1,
    'Cpanel::Easy::PHP5::Ming'                           => 1,
    'Cpanel::Easy::PHP5::PathInfoCheck'                  => 1,
    'Cpanel::Easy::PHP5::PDFLib'                         => 1,
    'Cpanel::Easy::PHP5::PDOPgsql'                       => 1,
    'Cpanel::Easy::PHP5::SafeMode'                       => 1,
    'Cpanel::Easy::PHP5::SafePHPCGI'                     => 1,
    'Cpanel::Easy::PHP5::SilenceDeprecatedPatch'         => 1,
    'Cpanel::Easy::PHP5::SysTimezone'                    => 1,
    'Cpanel::Easy::PHP5::XsltSablot'                     => 1,
    'Cpanel::Easy::PHP5::ZendMultibyte'                  => 1,
);

exit( run(@ARGV) ) if !caller();

sub run {    ## no critic qw(Subroutines::ProhibitExcessComplexity)
    my ($debug) = @_;
    $debug //= 0;

    # we will have some criteria in the autorepair script, but I will repeat
    # them for safety, sort of a belt and suspenders thing

    # IF WE RAN AND FAILED BEFORE
    #   STOP, allow for future autofixers to clear these stop MARKS

    print "auto_migrate_ea3_to_ea4: 001\n" if ($debug);

    return 0 if ( -e $marked_as_ran );
    return 0 if ( -e $ea4_enabled );     # nothing to do in this case

    print "auto_migrate_ea3_to_ea4: 002\n" if ($debug);

    # MUST be 76
    my $current_version = $Cpanel::Version::Tiny::major_version;
    return 0 if ( $current_version && $current_version != 76 );

    print "auto_migrate_ea3_to_ea4: 003\n" if ($debug);

    # MUST be blocked by EA3 ONLY
    if ( open my $fh, '<', $blocker_file ) {
        my $line;                        # buffer
        my $blocker_count = 0;
        while ( $line = <$fh> ) {
            chomp($line);
            next if $line =~ m/^\s*$/;
            $blocker_count++;
            if ( $line !~ m/You must migrate from EA3 to EA4 before upgrading/ ) {
                print "Found blocker “$line”\n" if ($debug);

                # there is some other blocker, we will not force the migration.
                close $fh;
                return 0;
            }
        }

        close $fh;

        if ( !$blocker_count ) {
            print "No blockers (“$blocker_file” exists but contains no blockers).\n" if $debug;
            return 0;
        }
    }
    else {
        if ( $! != 2 ) {    # 2 is ENOENT
            print "Failed to open “$blocker_file”: $!\n" if $debug;
        }
        else {
            print "No blockers (“$blocker_file” does not exist).\n" if $debug;
        }

        return 0;
    }

    print "auto_migrate_ea3_to_ea4: 004\n" if ($debug);

    # MUST Only be CentOS, do not convert Amazon, CloudLinux or RHEL
    return 0 if ( Cpanel::Sys::OS::getos() ne 'centos' );

    print "auto_migrate_ea3_to_ea4: 005\n" if ($debug);

    # IF running PHP5.3 or less bail
    my $php_version = _determine_ea3_php5_version();
    return 0 if $php_version && $php_version < 54;

    print "auto_migrate_ea3_to_ea4: 006\n" if ($debug);

    return 0 unless _cpupdate_runs_daily();

    print "auto_migrate_ea3_to_ea4: cpupdate.conf is set to daily\n" if ($debug);

    # CHECK for disk space minimums
    # TODO: are these valid locations to check, and the minimums?
    return 0 if _is_disk_of_path_full( '/', 600_000_000 );
    print "auto_migrate_ea3_to_ea4: 007\n" if ($debug);

    return 0 if _is_disk_of_path_full( '/var', 600_000_000 );
    print "auto_migrate_ea3_to_ea4: 008\n" if ($debug);

    return 0 if _is_disk_of_path_full( '/usr/local/cpanel', 600_000_000 );
    print "auto_migrate_ea3_to_ea4: 009\n" if ($debug);

    # Bail if there is less than 256 megs of mem free
    return 0 if !_memory_check_ok(256);
    print "auto_migrate_ea3_to_ea4: 010.1\n" if $debug;

    # ASK FOR PERMISSION FROM tsunami/floodgate
    my $company_id;

    # Cpanel::License::CompanyID has been confirmed to be in v76
    require Cpanel::License::CompanyID;
    eval { $company_id = Cpanel::License::CompanyID::get_company_id(); };

    # if we cannot figure out the company id, bail
    return 0 if ( !defined $company_id );
    $company_id = Digest::MD5::md5_hex($company_id);

    print "auto_migrate_ea3_to_ea4: 011\n" if ($debug);

    my $url = "$floodgate_url/$company_id";

    my $http     = HTTP::Tiny->new;
    my $response = $http->get($url);
    return 0 if ( $response->{status} ne '200' );
    if ( exists $response->{headers}{"x-correlation-id"} ) {
        $x_correlation_id = $response->{headers}{"x-correlation-id"};
    }
    else {
        $x_correlation_id = "Null_X-Correlation-ID";
        warn "X-Correlation-ID was not sent\n";
    }

    path($marked_as_ran)->spew("$x_correlation_id = pre migration started");
    print "auto_migrate_ea3_to_ea4: 012\n" if ($debug);

    my @skip;
    push @skip, "has tomcat domains configured via EA3 tools" if _should_skip_because_of_tomcat();

    print "auto_migrate_ea3_to_ea4: 012.1\n" if $debug;

    my $ret;

    # We found in our research that rpm and yum issues were the cause of
    # the inability to migrate in a large number of cases. So we are going
    # to fix any of those problems and do a yum update.

    # DISABLE certain yum repos if necessary
    $disabled_yum_repos = _disable_yum_repos( \%yum_repos_to_disable, $debug );
    print "auto_migrate_ea3_to_ea4: 013\n" if ($debug);

    # RUN rpm database fixer in maintenance scripts
    if ( -e $find_and_fix_rpms ) {

        # very race-conditiony but at least we can catch extreme cases
        my $fib;    # buffer
        my $final_fib;
        for $fib ( 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 ) {
            $final_fib = $fib;
            last if !_yum_is_running("/var/run/yum.pid");
            if ($fib) {
                print "Yum seems to be running, waiting $fib second(s) to check again …\n";
                sleep $fib;
            }
        }

        if ( $final_fib == 233 && _yum_is_running("/var/run/yum.pid") ) {
            print "Yum has been running for over 10 minutes, aborting migration …\n";
            push @skip, "Yum has been running for over 10 minutes, aborting migration …\n";
        }

        my $combined_output = '';
        my $output_handler  = IO::Callback->new( ">", sub { $combined_output .= shift } );
        my $run             = Cpanel::SafeRun::Object->new(
            program => $find_and_fix_rpms,
            args    => [],
            stdout  => $output_handler,
            stderr  => $output_handler,
        );

        if ( $run->CHILD_ERROR() ) {

            # push stdout/stderr to error log
            _append_log_lines(
                'auto_migrate_ea3_to_ea4',
                "BEGIN: find_and_fix_rpms stdout/stderr",
                split( /\n/, $combined_output ),    # if this is refactored to build @lines as we go: KIM that IO::Callback will send multiple \n in $_[0]
                "END: find_and_fix_rpms",
            );

            push @skip, "find_and_fix_rpms failed";
        }
    }
    print "auto_migrate_ea3_to_ea4: 014\n" if ($debug);

    my $log_ar = [];
    push @skip, "has custom opt mod", @{$log_ar} if _has_custom_opt_mods( $http => $url, $log_ar );
    print "auto_migrate_ea3_to_ea4: 015\n" if $debug;

    $log_ar = [];
    push @skip, "has custom conf", @{$log_ar} if !_config_check_ok( $http => $url, $log_ar );
    print "auto_migrate_ea3_to_ea4: 015.1\n" if $debug;

    $log_ar = [];
    push @skip, "yum clean all failed", @{$log_ar} if !_run_command( $http => $url, $log_ar, qw(yum clean all) );
    print "auto_migrate_ea3_to_ea4: 016\n" if ($debug);

    $log_ar = [];
    push @skip, "yum makecache failed", @{$log_ar} if !_run_command( $http => $url, $log_ar, qw(yum makecache) );
    print "auto_migrate_ea3_to_ea4: 017\n" if ($debug);

    $log_ar = [];
    push @skip, "yum -y --exclude=kernel-* upgrade failed", @{$log_ar} if !_run_command( $http => $url, $log_ar, qw(yum -y --exclude=kernel-* upgrade) );
    print "auto_migrate_ea3_to_ea4: 018\n" if ($debug);

    # IF /usr/bin/php DOES NOT EXIST symlink it to /usr/local/bin/php
    if ( !-e $ub_php && -e $ulb_php ) {
        $ret = symlink $ulb_php, $ub_php;
        if ( !$ret ) {
            push @skip, "unable to symlink /usr/local/bin/php";
        }
    }
    elsif ( !-e $ub_php && !-e $ulb_php ) {

        # a pathological case, for sure
        if ( open my $fh, '>', $ub_php ) {
            print $fh q{#!/bin/bash
echo 'PHP 5.4.0 (faked)'
};
            close $fh;
            chmod 0755, $ub_php;
        }
    }
    print "auto_migrate_ea3_to_ea4: 019\n" if ($debug);

    if (@skip) {
        _ship_error_logs( undef, undef, "post tsunami pre migration skipped", \@skip );
        _post_status_of_zero( $http, $url );
        _put_system_back_together();
        return 0;
    }

    my @additional_logs;
    my @post_migrate_items = _get_post_migrate_items( \@additional_logs );

    # RUN /scripts/migrate_ea3_to_ea4 --yes --run --skip-blockers
    # A record of this process may be found at : /usr/local/cpanel/logs/packman/migrate_ea3_to_ea4.Fri_Nov_1_14:37:40_2019.log

    path($marked_as_ran)->spew("$x_correlation_id = migration started");

    my $migration_output         = "";
    my $migration_output_handler = IO::Callback->new( ">", sub { $migration_output .= shift } );
    my $run                      = Cpanel::SafeRun::Object->new(
        program => '/usr/local/cpanel/scripts/migrate_ea3_to_ea4',
        args    => [ "--yes", "--run", "--skip-blockers" ],
        stdout  => $migration_output_handler,
        stderr  => $migration_output_handler,
    );
    print "auto_migrate_ea3_to_ea4: 020\n" if ($debug);

    my $log_file;
    my $revert_log;

    my @output_array = split( /\n/, $migration_output );
    foreach my $line (@output_array) {
        if ( $line =~ m/^A record of this process may be found at : ([^\s]+)\s*$/ ) {
            $log_file = $1;
        }
    }
    print "auto_migrate_ea3_to_ea4: found log_file :$log_file:\n" if ( $debug && $log_file );
    print "auto_migrate_ea3_to_ea4: 021\n" if ($debug);

    my $status = 1;
    if ( $run->CHILD_ERROR() && ( !Cpanel::Config::Httpd::EA4::is_ea4() || !Cpanel::HttpUtils::httpd_is_running() ) ) {
        $status = 0;

        # IF FAIL
        #   RUN /scripts/migrate_ea3_to_ea4 --revert --run
        #   MARK that we failed, so we do not run again

        print "A error occurred during migration\n";

        # now revert
        my $revert_output         = "";
        my $revert_output_handler = IO::Callback->new( ">", sub { $revert_output .= shift } );
        $run = Cpanel::SafeRun::Object->new(
            program => '/usr/local/cpanel/scripts/migrate_ea3_to_ea4',
            args    => [ "--yes", "--run", "--revert" ],
            stdout  => $revert_output_handler,
            stderr  => $revert_output_handler,
        );

        my @output_array = split( /\n/, $revert_output );
        foreach my $line (@output_array) {
            if ( $line =~ m/^A record of this process may be found at : ([^\s]+)\s*$/ ) {
                $revert_log = $1;
            }
        }

        $log_file   //= \$migration_output;
        $revert_log //= \$revert_output;
        _ship_error_logs( $log_file, $revert_log, "migrate failed" );

        print "Revert completed\n";
    }
    else {
        if (@post_migrate_items) {
            my @pkgs;
            for my $item (@post_migrate_items) {
                if ( ref($item) eq 'CODE' ) {
                    $item->();
                }
                else {
                    push @pkgs, $item;
                }
            }

            if (@pkgs) {
                push @additional_logs, _run_and_get_logs( 'Additional Packages' => [ qw(yum install -y), @pkgs ] );
            }
        }
    }

    path($marked_as_ran)->spew("$x_correlation_id = $status");
    $http->post( "$url/$x_correlation_id/$status", { headers => { 'X-Correlation-ID' => $x_correlation_id } } );
    if ($status) {
        _append_log_lines(
            'auto_migrate_ea3_to_ea4',
            "Successfully Migrated" . ( $log_file ? ", log available at $log_file" : "" ),
            "X-Correlation-ID: $x_correlation_id",
            $log_version_str,
            ( $log_file ? () : ( "migration output:", split( /\n/, $migration_output ) ) ),
            @additional_logs,
        );
    }

    _put_system_back_together();

    # This autofixer will only run if there is exactly one blocker
    # and that is they are stuck on EA3.  Therefore it is
    # reasonable to delete the blocker file.

    if ($status) {
        my $ret = eval {
            require Cpanel::Config::Httpd::EA4;

            # this is a belt and suspenders check
            # if we are finally EA4 remove the blocker
            # file.

            Cpanel::Config::Httpd::EA4::reset_cache();    ## no critic qw(Subroutines::ProhibitCallsToUnexportedSubs), critic is mistaken
            if ( Cpanel::Config::Httpd::EA4::is_ea4() ) { ## no critic qw(Subroutines::ProhibitCallsToUnexportedSubs), critic is mistaken
                unlink $blocker_file;
            }
        };
    }

    print "Finished!\n";

    return 0;
}

###############
#### helpers ##
###############

sub _get_post_migrate_items {
    my ($additional_logs) = @_;

    my @additional_pkgs;
    my $profiles    = get_ea3_and_ea4_conf_hr();
    my $php_version = _determine_ea3_php5_version();

    # Add packages to @additional_pkgs based on $profiles
    # !!!!
    # !!!! Anything added here needs to be added to %allowed_custom_opt_mods or otherwise be allowed in _has_custom_opt_mods() !!
    # !!!!
    push @additional_pkgs, "ea-apache24-mod_remoteip"   if $profiles->{ea3}{'Cpanel::Easy::ModCloudflare'};    # ZC-6177
    push @additional_pkgs, "ea-apache24-mod_info"       if $profiles->{ea3}{Apache}{optmods}{Info};            # ZC-6303
    push @additional_pkgs, "ea-apache24-mod_deflate"    if $profiles->{ea3}{'Cpanel::Easy::ModGzip'};          # ZC-6304
    push @additional_pkgs, "ea-apache24-mod_reqtimeout" if $profiles->{ea3}{'Cpanel::Easy::ModReqtimeout'};    # ZC-6361
    push @additional_pkgs, "ea-apache24-mod_substitute" if $profiles->{ea3}{'Cpanel::Easy::ModSubstitute'};    # ZC-6425

    if ( $profiles->{ea3}{'Cpanel::Easy::Speed'} ) {                                                           # ZC-6200
        push @additional_pkgs, "ea-apache24-mod_pagespeed";

        if ( $profiles->{ea3}{'Cpanel::Easy::ModRuid2'} ) {
            push @additional_pkgs, sub {
                require Cpanel::PackMan;
                my $ruid2 = Cpanel::PackMan->instance->pkg_hr("ea-apache24-mod_ruid2");
                if ( $ruid2 && $ruid2->{version_installed} ) {
                    push @{$additional_logs}, _run_and_get_logs( 'Remove mod_ruid2 since it conflicts w/ mod_pagespeed' => [ qw(yum erase -y), "ea-apache24-mod_ruid2" ] );
                }
            };
        }
    }

    if ( $profiles->{ea3}{Apache}{optmods}{SymlinkProtection} ) {    # ZC-6172
        push @additional_pkgs, sub {
            require Cpanel::AdvConfig::apache;
            Cpanel::AdvConfig::apache::save_main_item( symlink_protect => "On" );
            system("/usr/local/cpanel/scripts/rebuildhttpdconf");
            system("/usr/local/cpanel/scripts/restartsrv_httpd");
        };
    }

    if ($php_version) {
        push @additional_pkgs, ( "ea-php$php_version-php-memcache", "ea-php$php_version-php-memcached", "memcached" ) if $profiles->{ea3}{'Cpanel::Easy::PHP5::MemcachePHP'};    # ZC-6513
        push @additional_pkgs, "ea-php$php_version-php-bcmath" if $profiles->{ea3}{'Cpanel::Easy::PHP5::Bcmath'};                                                                   # ZC-6218
        push( @additional_pkgs, ( "ea-php$php_version-php-pdo", "ea-php$php_version-php-pgsql" ) ) if $profiles->{ea3}{'Cpanel::Easy::PHP5::PDOPgsql'};                             # ZC-6181
    }

    return @additional_pkgs;
}

sub _run_and_get_logs {
    my ( $label, $cmd ) = @_;

    my @logs;
    push @logs, "↓$label↓";

    my ( $out, $ret ) = Capture::Tiny::tee_merged( sub { return system( @{$cmd} ) } );
    if ($ret) {
        push @logs, "This command did not exit cleanly:",;
    }

    push @logs, "\t`" . join( " ", @{$cmd} ) . "`";
    push @logs, $out;
    push @logs, "↑$label↑";

    return @logs;
}

sub _memory_check_ok {
    my ($min_mb) = @_;

    require cPanel::MemTest;
    my $allocate_mb = cPanel::MemTest::testallocate($min_mb);
    return 1 if $allocate_mb == $min_mb;

    _ship_error_logs( undef, undef, "not enough memory available ($min_mb MB required, only $allocate_mb MB available)" );

    return;
}

sub _config_check_ok {
    my ( $http, $url, $log_ar ) = @_;

    my $go = 1;
    my @log_lines;

    # Useful PHP INI info:
    #    https://www.php.net/manual/en/ini.core.php
    #    https://www.php.net/manual/en/configuration.changes.modes.php

    #### check global stuff before docroot ##
    my $hc_attr = Cpanel::FileUtils::Attr::get_file_or_fh_attributes("/usr/local/apache/conf/httpd.conf") || {};
    if ( $hc_attr->{IMMUTABLE} ) {
        $go = 0;
        push @log_lines, "/usr/local/apache/conf/httpd.conf is immutable";
    }
    elsif ( my @logme = _has_enabled_php_extension($ea3_phpini) ) {
        $go = 0;
        push @log_lines, "“$ea3_phpini” has unknown php extensions", @logme;
    }
    elsif ( _has_unknown_extension_so( \@log_lines ) ) {
        $go = 0;
    }
    elsif ( _has_unknown_includes( \@log_lines ) ) {
        $go = 0;
    }
    elsif ( _has_geoip_enabled_non_htaccess( \@log_lines ) ) {
        $go = 0;
    }

    if ($go) {

        # TODO/YAGNI? extension_dir can be set in httpd.conf/includes

        #### now check docroots, only processing a given path once ##
        my %searched;
        my %docroots;

      USER:
        for my $user ( Cpanel::Config::Users::getcpusers() ) {
          DOCROOT:
            for my $docroot ( keys %{ Cpanel::DomainLookup::getdocrootlist($user) } ) {
                my $cur_path = $docroot;
                $docroots{$docroot}++;
              PATH:
                while ($cur_path) {
                    if ( !exists $searched{$cur_path} ) {
                        $searched{$cur_path} = 1;
                        if ( _path_has_skip_condition( $cur_path, \@log_lines ) ) {
                            $go = 0;
                            last USER;
                        }
                    }

                    last if $cur_path eq "/";
                    $cur_path = path($cur_path)->parent->stringify;
                }
            }
        }

        if ($go) {
          SUBDIR:
            for my $docroot ( keys %docroots ) {
                my $fetch = Path::Iter::get_iterator($docroot);
                while ( my $current_subdir = $fetch->() ) {
                    next if $current_subdir eq $docroot;
                    next if exists $docroots{$current_subdir} || exists $searched{$current_subdir};
                    next if !-d $current_subdir;                                                      # symlink to dirs should be traversed even if it ends up doing the same target more than once, because resolving targets it fragile

                    $searched{$current_subdir} = 1;
                    if ( _path_has_skip_condition( $current_subdir, \@log_lines ) ) {
                        $go = 0;
                        last SUBDIR;
                    }
                }
            }
        }
    }

    if ( !$go ) {
        push(
            @{$log_ar},
            "BEGIN: _config_check_ok issues",
            @log_lines,
            "END: _config_check_ok issues",
        );
        return;
    }

    return 1;
}

sub _line_has_geoip_enabled {
    my ( $line, $file, $log_ar ) = @_;
    if ( $line =~ m/^\s*GeoIPEnable(?:UTF8)?\s+(\S+)/ ) {
        my $val = $1;
        if ( $val =~ m/on/i || $val eq "1" ) {
            push @{$log_ar}, "“$file” has geoip enabled";
            return 1;
        }
    }
    return;
}

sub _has_geoip_enabled_non_htaccess {
    my ($log_ar) = @_;

    my @files = ( $httpd_conf, map { $_ =~ m/^\s*\(\d+\)\s+(.*)\n/ ? "$1" : () } `/usr/local/apache/bin/httpd -t -D DUMP_INCLUDES` );
    for my $file (@files) {
        for my $line ( path($file)->lines ) {
            return 1 if _line_has_geoip_enabled( $line, $file, $log_ar );
        }
    }

    return;
}

sub _has_unknown_includes {
    my ($log_ar) = @_;

    my @suspect;

    # Only want ' (N) ' lines.
    # Do not want (*) line as that is the main httpd.conf
    # Other lines are labels and warnings which we also do not want
    my @includes = map { $_ =~ m/^\s*\(\d+\)\s+(.*)\n/ ? "$1" : () } `/usr/local/apache/bin/httpd -t -D DUMP_INCLUDES`;

    for my $include (@includes) {
        if ( substr( $include, -5, 5 ) ne ".conf" ) {
            push @suspect, "“$include” does not end w/ .conf";
            next;
        }

        if ( substr( $include, 0, 22 ) ne "/usr/local/apache/conf" ) {
            push @suspect, "“$include” is not inside /usr/local/apache/conf";
            next;
        }

        # these are migrated individually because they are “special”
        next if $include eq "/usr/local/apache/conf/php.conf";
        next if $include eq "/usr/local/apache/conf/modsec2.conf";
        next if $include eq "/usr/local/apache/conf/modsec2.user.conf";
        next if $include eq "/usr/local/apache/conf/modsec2.cpanel.conf";
        next if $include eq "/usr/local/apache/conf/mod_gzip.conf";
        next if $include eq "/usr/local/apache/conf/mod_ruid2.conf";
        next if $include eq "/usr/local/apache/conf/mod_bandwidth.conf";

        my $parent = $include;
        $parent =~ s{/[^/]+$}{};

        # migrate_global_includes() either migrates these or they are recreated/dropped (%includes_skip)
        next if $parent eq "/usr/local/apache/conf/includes";

        # migrate_userdata_includes() does these sans %includes_skip which is ok
        next if $parent eq "/usr/local/apache/conf/userdata";
        next if $parent eq "/usr/local/apache/conf/userdata/std";
        next if $parent eq "/usr/local/apache/conf/userdata/ssl";
        next if $parent =~ m{^/usr/local/apache/conf/userdata/(?:std|ssl)/(?:2|2_2|2_4)$};
        next if $parent =~ m{^/usr/local/apache/conf/userdata/(?:std|ssl)/(?:2|2_2|2_4)/[^/]+$};
        next if $parent =~ m{^/usr/local/apache/conf/userdata/(?:std|ssl)/(?:2|2_2|2_4)/[^/]+/[^/]+$};
        next if $include =~ m{^/usr/local/apache/conf/userdata/(?:std|ssl)/2/[^/]+/[^/]+/cp_bw_all_limit\.conf$};    # always migrated
        next if $include =~ m{^/usr/local/apache/conf/userdata/(?:std|ssl)/2_4/[^/]+/[^/]+/cp_jkmount\.conf$};       # always added to %includes_skip

        push @suspect, "“$include” is in an unmigrated path";
    }

    if (@suspect) {
        push @{$log_ar}, @suspect;
        return 1;
    }

    return;
}

sub _has_unknown_extension_so {
    my ($log_ar) = @_;
    $default_extdir ||= Cpanel::PHPINI::get_default_extension_dir("/usr/local");

    my $has = 0;
    for my $dir ( $ea3_phpinid, $default_extdir ) {
        for my $ext ( Cpanel::SafeDir::Read::read_dir($dir) ) {
            if ( !exists $known_php_extensions{$ext} && $ext =~ m/\.so$/ ) {
                push @{$log_ar}, "“$dir” has at least one unknown extension ($ext)";
                $has++;
            }
        }
    }

    return $has;
    return;
}

sub _path_has_skip_condition {
    my ( $path, $log_ar ) = @_;

    ## htaccess
    my @lines = eval { path("$path/.htaccess")->lines };
    for my $line (@lines) {
        return 1 if _line_has_geoip_enabled( $line, "$path/.htaccess", $log_ar );

        # PHP handling HTML files
        if ( $line =~ m/^\s*AddHandler.*-php.*\s+.*\.htm/ ) {
            push @{$log_ar}, "“$path/.htaccess” has PHP handling HTML files";
            return 1;
        }

        # non-EA3 PHPs that are too old
        if ( $line =~ m/^\s*AddHandler.*-php([1-9][0-9]?).*\s+.*\.php/ ) {
            my $v = "$1";
            $v .= 0 if length($v) == 1;
            if ( $v < 54 ) {
                push @{$log_ar}, "“$path/.htaccess” has custom PHP that is older than 5.4";
                return 1;
            }
        }

        # There is some evidence (e.g. old stackoverflow posts) that .htaccess
        # can be used to load extensions, however that is not what the documentation says.
    }

    ## php.ini
    @lines = eval { path("$path/php.ini")->lines };
    if ( my @logme = _has_enabled_php_extension( \@lines ) ) {
        push @{$log_ar}, "“$path/php.ini” has unknown php extensions", @logme;
        return 1;
    }

    ## .user.ini
    # @lines = eval { path("$path/.user.ini")->lines };
    # .user.ini cannot load extensions

    return;
}

sub _has_enabled_php_extension {
    my ($path) = @_;

    $default_extdir ||= Cpanel::PHPINI::get_default_extension_dir("/usr/local");

    my $lines = ref($path) eq 'ARRAY' ? $path : [ eval { path($path)->lines } ];

    my @return;

    # TODO/YAGNI: use an object to parse the file instead of dumb regex (See EA-5696 for why that is not trivial (git grep EA-5696, in ULC))
    for my $line ( @{$lines} ) {

        # dl() removed in 5.3.0 so no need to check for and act upon `enable_dl`
        if ( $line =~ m/^\s*(?:zend_)?extension(?:_ts)?\s*=\s*(.*)/ ) {
            my $ext = $1;
            $ext =~ s/(?:\A["']|["']\z)//g;
            $ext =~ s{^\Q$default_extdir\E/*}{};
            $ext =~ s{^\Q$ea3_phpinid\E/*}{};
            push @return, $ext if !exists $known_php_extensions{$ext};
        }
        elsif ( $line =~ m/^\s*extension_dir\s*=\s*(.*)/ ) {
            my $extdir = $1;
            chomp($extdir);
            $extdir =~ s/^['"]//;
            $extdir =~ s/['"]$//;
            $extdir =~ s{/$}{}g;
            if ( $extdir ne $ea3_phpinid && $extdir ne $default_extdir ) {
                my @files = glob("$extdir/*.so");
                my $num   = @files;
                push @return, "extension_dir=$extdir/*.so:", @files if $num;
            }
        }
    }

    return @return;
}

sub _add_conditionally_allowed_optmods {
    my ($ok) = @_;

    my $profiles = eval { get_ea3_and_ea4_conf_hr() };

    # Cpanel::Easy::EAccelerator
    my @lines = eval { path($usr_local_phpini)->lines };

    if ( !$@ ) {
        $ok->{'Cpanel::Easy::EAccelerator'} = 1;
        for my $line (@lines) {
            if ( $line =~ m/^\s*(?:zend_)?extension(?:_ts)?\s*=\s*["']?\S*eaccelerator\.so["']?/ ) {
                delete $ok->{'Cpanel::Easy::EAccelerator'};
                last;
            }
        }
    }

    if ( !-e '/opt/mm/lib' ) {
        $ok->{'Cpanel::Easy::PHP5::MM'} = 1;
    }

    # non-2.4
    if ( $profiles && $profiles->{ea3} && $profiles->{ea3}{Apache} && $profiles->{ea3}{Apache}{version} eq "2_4" ) {
        $ok->{'Cpanel::Easy::Apache::MemCache'} = 1;    # presumably mod_mem_cache which is not available on 2.4, ZC-6513
        $ok->{'Cpanel::Easy::ModXSendfile'}     = 1;
        $ok->{'Cpanel::Easy::ModMono'}          = 1;
        $ok->{'Cpanel::Easy::ModMono2'}         = 1;
        $ok->{'Cpanel::Easy::ModPerl'}          = 1;
    }

    return 1;
}

sub _has_custom_opt_mods {
    my ( $http, $url, $log_ar ) = @_;
    my @custom_opt_mods;
    _add_conditionally_allowed_optmods( \%allowed_custom_opt_mods );

    my $profiles = eval { get_ea3_and_ea4_conf_hr() };
    push @custom_opt_mods, $@ if $@;

    warn "ModHTScanner is enabled, anything configured via it needs moved to the user’s php.ini\n" if $profiles->{ea3}{'Cpanel::Easy::ModHTScanner'};

    foreach my $warn ( @{ $profiles->{ea4}{warnings} } ) {
        if ( $warn =~ m/“(.*)” ignored since it does not have an RPM/ ) {
            my $ns = $1;
            push( @custom_opt_mods, $warn ) if !exists $allowed_custom_opt_mods{$ns} && $ns !~ m/::PHP4/;
        }
    }

    push( @custom_opt_mods, "Has PHP4 (/usr/local/php4/bin/php)" ) if -x '/usr/local/php4/bin/php';

    if (@custom_opt_mods) {
        push(
            @{$log_ar},
            "BEGIN: _has_custom_opt_mods issues",
            @custom_opt_mods,
            "END: _has_custom_opt_mods issues",
        );

        return 1;
    }

    return;
}

our $_profiles;

sub get_ea3_and_ea4_conf_hr {
    return $_profiles if $_profiles;

    eval {
        $_profiles->{ea3} = Cpanel::YAML::LoadFile($ea3_profile_fname);
        $_profiles->{ea4} = Cpanel::EA4::EA3Map::convert_ea3_config_to_ea4( $_profiles->{ea3} );
    };
    die if $@;

    return $_profiles;
}

sub _run_command {
    my ( $http, $url, $log_ar, @cmd ) = @_;

    my ( $out, $ret ) = Capture::Tiny::tee_merged(
        sub {
            return system(@cmd);
        }
    );

    if ($ret) {
        my $cmd = join( ' ', @cmd );

        push(
            @{$log_ar},
            "BEGIN: $cmd issues (via _run_command)",
            split( /\n/, $out ),
            "END: $cmd issues (via _run_command)",
        );
        return 0;
    }

    return 1;
}

# this is a duplicate of the code in the migration script,
# but we want to "pre-screen" this out, so as to not waste
# a permission request if we have this situation
sub _get_ea3_tomcat_doms {
    return if !-f $ea3_serverxml;

    my @ea3_tomcat_doms;

    eval {
        require XML::LibXML;
        my $dom = XML::LibXML->load_xml( location => $ea3_serverxml );

        for my $hostnode ( $dom->findnodes("//Server/Service/Engine/Host") ) {    # ea3 uses the same xpath as ea4
            my $fqdn = $hostnode->getAttribute("name");
            next if $fqdn eq "localhost";
            push @ea3_tomcat_doms, $fqdn;
        }
    };

    return @ea3_tomcat_doms;
}

sub _should_skip_because_of_tomcat {
    my @ea3_tomcat_doms = _get_ea3_tomcat_doms();
    return 0 if ( !@ea3_tomcat_doms );

    my %ea4_setup;
    for my $dom (@ea3_tomcat_doms) {
        my $user = Cpanel::AcctUtils::DomainOwner::getdomainowner( $dom, { default => "" } );
        if ( !$user ) {
            next;
        }
        push @{ $ea4_setup{$user} }, $dom;
    }
    my $count = keys %ea4_setup;    # want this incase we want to re-introduce a threshold
    return 1 if $count;

    return 0;
}

# if we can find a php5 config, we will return a 2 digit integer
# 53 for php5.3, 54 for php5.4 etc.
# otherwise it returns 0

our $phpversion;

sub _determine_ea3_php5_version {
    return $phpversion if $phpversion;

    require Cpanel::EA4::EA3Map;    # is NOT available in versions past v76

    my $ea3_hr;

    unless ( eval { $ea3_hr = Cpanel::YAML::LoadFile($ea3_profile_fname); } ) {
        _ship_error_logs( undef, undef, "‘$ea3_profile_fname’ either does not exist or is invalid YAML" );
        return 0;
    }

    my $norm_hr = Cpanel::EA4::EA3Map::normalize_ea3_profile($ea3_hr);

    my $php5_major_version;

    foreach my $key ( keys %{$norm_hr} ) {
        my $val = $norm_hr->{$key};
        if ( $key =~ m/Cpanel::Easy::PHP5::(\d+)_\d+/ && $val == 1 ) {
            $php5_major_version = $1;
        }
    }

    return 0 if !$php5_major_version;
    $phpversion = 50 + $php5_major_version;
    return $phpversion;

}

# stole and modified from v86 Cpanel::Update::Blocker::Always
# _max, _zip, _statfs (albeit modified)

sub _max {
    my ( $a, $b ) = @_;

    return $a > $b ? $a : $b;
}

sub _zip {
    my ( $a, $b ) = @_;

    my @ret;

    my $count = _max( scalar @{$a}, scalar @{$b} );

    for ( my $i = 0; $i < $count; $i++ ) {
        push @ret, defined $a->[$i] ? $a->[$i] : undef;
        push @ret, defined $b->[$i] ? $b->[$i] : undef;
    }

    return \@ret;
}

sub _statfs {
    my ($path) = @_;

    # both confirmed to be in v76,
    # note, Cpanel::Pack was missing a routine called malloc, and I compensated for

    require Cpanel::Pack;
    require Cpanel::Sys::Uname;

    my @MEMBERS = qw(
      f_type f_bsize f_blocks f_bfree f_bavail f_files
      f_ffree f_fsid f_namelen f_frsize f_flags f_spare
    );

    my %TEMPLATES = ( 'x86_64' => [qw(Q Q Q Q Q Q Q Q Q Q Q QQQQ)] );

    # note we DO NOT SUPPORT i386 anymore
    my %SYSCALL_IDS = ( 'x86_64' => 137 );

    my @uname = Cpanel::Sys::Uname::get_uname_cached();

    unless ( defined $SYSCALL_IDS{ $uname[4] } ) {
        die "Unsupported platform '$uname[4]'";
    }

    my $struct = Cpanel::Pack->new( _zip( \@MEMBERS, $TEMPLATES{ $uname[4] } ) );

    my $buf = pack( $struct->{'template_str'} );

    return syscall( $SYSCALL_IDS{ $uname[4] }, $path, $buf ) < 0
      ? ()
      : $struct->unpack_to_hashref($buf);
}

# stolen and modified from Cpanel::Update::Blocker::Always on v86
sub _is_disk_of_path_full {
    my ( $path, $minimum ) = @_;

    my $statfs = _statfs($path);
    my $space  = $statfs->{'f_bsize'} * $statfs->{'f_bfree'};
    my $inodes = $statfs->{'f_ffree'};

    my @ignore_free_inode_types = (
        0x9123683E,    # BTRFS_SUPER_MAGIC from include/linux/magic.h -- UNSUPPORTED, always reports 0 free, OK to ignore because inodes are effe
    );
    my $ignore_free_inodes = ( grep { $statfs->{'f_type'} == $_ } @ignore_free_inode_types ) ? 1 : 0;

    if ( $space < $minimum ) {
        return 1;
    }

    if ( !$ignore_free_inodes && $inodes < 360000 ) {
        return 1;
    }

    return 0;
}

# returns an arrayref containing a list of repo files that
# have been disabled.  That repo section is disabled
# and the original file is copied to a .$magic_bak_extension
# version of the file.

sub _disable_yum_repos {
    my ( $conf, $debug ) = @_;
    $debug //= 0;

    my @changed_files;

    my @repo_files = glob( $yum_repos_d . '/*.repo' );
    foreach my $repo (@repo_files) {
        my $data = path($repo)->slurp();
        $data =~ s/#.*\n/\n/g;
        my $repo_hash;

        my $ret = eval { $repo_hash = Config::INI::Reader->read_string($data); };

        if ( !defined $ret ) {
            print "Skipping “$repo” because of syntax errors\n" if $debug;
            next;
        }

        my $changed_file = 0;

      SECTION:
        foreach my $key ( keys %{$repo_hash} ) {

            # first look for a mirrorlist or baseurl
            my $url;
            if ( exists $repo_hash->{$key}{mirrorlist} ) {
                $url = $repo_hash->{$key}{mirrorlist};
            }
            elsif ( exists $repo_hash->{$key}{baseurl} ) {
                $url = $repo_hash->{$key}{baseurl};
            }
            else {
                next;
            }

          YUMREPO:
            for my $name ( sort keys %{$conf} ) {
                if ( _skip_yum_repo( $conf->{$name}, $repo_hash->{$key}, $key => $url ) ) {
                    my $enabled = 1;
                    if ( exists $repo_hash->{$key}{enabled} ) {
                        $enabled = $repo_hash->{$key}{enabled};
                    }

                    if ($enabled) {
                        $repo_hash->{$key}{enabled} = 0;
                        push( @changed_files, $repo ) if !$changed_file++;
                        print "Disabling $name Repo ($repo)\n" if $debug;
                    }
                }
            }
        }

        if ($changed_file) {
            path($repo)->copy( $repo . $magic_bak_extension );
            eval { Config::INI::Writer->write_file( $repo_hash, $repo ); };
            if ($@) {
                print "Failed to write $repo with items disabled: $@\n" if $debug;
            }
        }
    }

    return \@changed_files if @changed_files;
    return;    # found nothing
}

sub _skip_yum_repo {
    my ( $bad_hr, $section_hr, $section, $url ) = @_;

    return 1 if exists $bad_hr->{section_qr} && $section =~ $bad_hr->{section_qr};
    return 1 if exists $bad_hr->{url_qr}     && $url     =~ $bad_hr->{url_qr};
    return 1 if exists $bad_hr->{mirrorlist_qr} && exists $section_hr->{mirrorlist} && $section_hr->{mirrorlist} =~ $bad_hr->{mirrorlist_qr};

    return;
}

# reenable, will take the original lines back to the yum repo file
# to make sure it is back to it's pristine state
sub _reenable_yum_repos {
    my ($changed_files) = @_;

    foreach my $file_name ( @{$changed_files} ) {
        my $repo_path = path( $file_name . $magic_bak_extension );
        if ( $repo_path->exists ) {
            $repo_path->move($file_name);
        }
    }

    return;
}

sub _append_log_lines {
    my ( $service, @lines ) = @_;

    $logger ||= Cpanel::Logger->new();

    chomp(@lines);
    foreach my $line (@lines) {
        $logger->logger(
            {
                message   => $line,
                level     => 'info',
                service   => $service,
                output    => 0,
                backtrace => 0,
            }
        );
    }

    return;
}

sub _ship_error_logs {
    my ( $migrate, $revert, $optional_msg, $additional_lines ) = @_;

    my @lines;
    if ( $migrate && ( ref($migrate) || -f $migrate ) ) {
        push(
            @lines,
            '[Migration Log]: BEGIN',
            ( -f $migrate ? ( "Path: $migrate", Path::Tiny::path($migrate)->lines() ) : split( /\n/, ${$migrate} ) ),
            '[Migration Log]: END'
        );
    }

    if ( $revert && ( ref($migrate) || -f $revert ) ) {
        push(
            @lines,
            '[Revert Log]: BEGIN',
            ( -f $revert ? ( "Path: $revert", Path::Tiny::path($revert)->lines() ) : split( /\n/, ${$revert} ) ),
            '[Revert Log]: END'
        );
    }

    if ( defined $additional_lines && ref($additional_lines) eq 'ARRAY' ) {
        unshift @lines, @{$additional_lines};
    }

    if ($optional_msg) {
        unshift @lines, "Message: $optional_msg";
    }

    _append_log_lines(
        'auto_migrate_ea3_to_ea4',
        "BEGIN: EA3 to EA4 Migration Log",
        "X-Correlation-ID: $x_correlation_id",
        $log_version_str,
        @lines,
        "END: EA3 to EA4 Migration Log"
    );

    return;
}

sub _post_status_of_zero {
    my ( $http, $url ) = @_;

    $http->post( "$url/$x_correlation_id/0", { headers => { 'X-Correlation-ID' => $x_correlation_id } } );

    return;
}

sub _put_system_back_together {
    if ($disabled_yum_repos) {
        _reenable_yum_repos($disabled_yum_repos);
    }

    return;
}

sub _yum_is_running {
    my ($pidfile) = @_;

    my $pid = eval { path($pidfile)->slurp() };
    return if $@;

    my $cmdline = eval { Cpanel::Proc::PID->new($pid)->cmdline() };
    return 1 if $cmdline && $cmdline->[1] =~ m{/yum$};

    unlink $pidfile;    # get rid of stale pid file
    return;
}

sub _cpupdate_runs_daily {
    my $cpupdate_conf = Cpanel::Update::Config::load();
    return 1 if ( $cpupdate_conf->{UPDATES} eq 'daily' );
    return 0;
}

1;
