#!/usr/local/cpanel/3rdparty/bin/perl # cpanel - scripts/check_mysql Copyright 2022 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 scripts::check_mysql; use strict; use warnings; use Cpanel::Services::Enabled (); use Cpanel::ConfigFiles (); use Cpanel::Daemonizer::Tiny (); use Cpanel::FileUtils::Open (); use Cpanel::FindBin (); use Cpanel::DB::Reserved (); use Cpanel::MysqlUtils::MyCnf::Basic (); use Cpanel::MysqlUtils::Dir (); use Cpanel::Alarm (); use Cpanel::Unix::PID::Tiny (); use Cpanel::Imports; exit script(@ARGV) unless caller; sub report_errors { my ($errors) = @_; require Cpanel::Notify; Cpanel::Notify::notification_class( 'class' => 'Check::MySQL', 'application' => 'Check::MySQL', 'constructor_args' => [ 'origin' => 'check_mysql', 'report' => $errors, ] ); return; } sub run_mysqlcheck { my ( $mysqlcheck, $db ) = @_; my @output = `LANG=C $mysqlcheck -u root -m --silent --databases $db 2>&1`; return @output; } sub daemon_process { my ($uxpd) = @_; $uxpd->pid_file('/var/run/check_mysql.pid'); # update our pid file to new sporked PID # These are the databases we analyze my @databases = grep { !m/^pg_/ && !m/^postgres$/ && !m/^test$/ } Cpanel::DB::Reserved::get_reserved_database_names(); my $timeout_seconds = 64800; # 64800 seconds == 18 hours my $mysqlcheck = Cpanel::FindBin::findbin("mysqlcheck"); my $mysqldir = Cpanel::MysqlUtils::Dir::getmysqldir() || '/var/lib/mysql'; my %errors; my %processed; my $ret = eval { # must go out of scope to stop and clean up, ick on a stick my $timeout = Cpanel::Alarm->new( $timeout_seconds, sub { die "time out\n" } ); for my $db (@databases) { if ( $db ne 'mysql' && !-d "$mysqldir/$db" ) { # if mysql is missing then @output contains an error $processed{$db} = 1; next; } logger->info("starting mysqlcheck on $db (PID $$)"); my @output = run_mysqlcheck( $mysqlcheck, $db ); if ( grep { /error: 2002: Can't connect/ } @output ) { logger->warn("mysqlcheck can't connect to MySQL"); return 0; } logger->info("mysqlcheck of $db is finished (PID $$)"); my $oops = purge_warnings( \@output ); if ($oops) { logger->warn("mysqlcheck found errors on $db: $oops"); $errors{$db} = $oops; } else { logger->info("mysqlcheck found no errors on $db"); } $processed{$db} = 2; } return 1; }; return if defined $ret && $ret == 0; for my $db (@databases) { next if exists $processed{$db}; next if $db ne 'mysql' && !-d "$mysqldir/$db"; # in case the timeout happened right before $processed{$db} = 1; we just won a race! my $timeout_hours = $timeout_seconds / 60 / 60; $errors{$db} = "not checked because mysqlcheck has been running for $timeout_hours hours (large data? $mysqldir partition full?)"; } if ( keys %errors ) { report_errors( \%errors ); } return; } sub script { local $ENV{'HOME'} = '/root'; die "/usr/local/cpanel/scripts/check_mysql is currently disabled.\n" if -e '/etc/check_mysql_disable'; die "MySQL is disabled.\n" unless Cpanel::Services::Enabled::is_enabled('mysql'); # This script is not written in a way so as to support remote mysql servers die "Remote MySQL database servers are not supported.\n" if Cpanel::MysqlUtils::MyCnf::Basic::is_remote_mysql(); # this can be an expensive and thus time consuming thing so only allow one run at a time and give it a large timeout (via $timeout_seconds) my $uxpd = Cpanel::Unix::PID::Tiny->new(); die "/usr/local/cpanel/scripts/check_mysql is currently running.\n" if !$uxpd->pid_file('/var/run/check_mysql.pid'); my $pid = Cpanel::Daemonizer::Tiny::run_as_daemon( sub { Cpanel::FileUtils::Open::sysopen_with_real_perms( \*STDERR, $Cpanel::ConfigFiles::CPANEL_ROOT . '/logs/error_log', 'O_WRONLY|O_APPEND|O_CREAT', 0600 ); open( STDOUT, '>&', \*STDERR ) || warn "Failed to redirect STDOUT to STDERR"; daemon_process($uxpd); } ); print locale->maketext( "“[_1]” will complete in the background (process ID [_2]).", 'check_mysql', $pid ) . "\n"; return 0; } sub purge_warnings { my ($output_ar) = @_; my $full_output = ''; # Loop through each line get rid of warnings my $previous_line = ''; foreach my $line (@$output_ar) { chomp $line; # Skip the warnings and a non-Error-error: http://bugs.mysql.com/bug.php?id=30487 if ( $line =~ /^([iI]nfo|[nN]ote|[wW]arning)/ || $line =~ m/Error\s*:\s*You can't use locks with log tables\./ ) { $previous_line = ''; next; } # We only allow a line if it is not followed by a warning $full_output .= $previous_line . "\n" if $previous_line; $previous_line = $line; } $full_output .= $previous_line . "\n" if $previous_line; return $full_output; }