<> or do {
die "Worker type names always begin with a capital! (given: “$worker_type”)";
};
return "WORKER_NODE-$worker_type";
}
sub _parse {
my ($str) = @_;
return $str ? [ split m<:>, $str, 2 ] : undef;
}
1;
} # --- END Cpanel/LinkedNode/Worker/Storage.pm
{ # --- BEGIN Cpanel/SafeFile/Replace.pm
package Cpanel::SafeFile::Replace;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::FileUtils::Open (); # perlpkg line 211
use File::Basename ();
use constant {
WRONLY_CREAT_EXCL => $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL,
_EEXIST => 17
};
sub safe_replace_content {
my ( $fh, $safelock, @content ) = @_;
return locked_atomic_replace_contents(
$fh,
$safelock,
sub {
local $!;
@content = @{ $content[0] } if scalar @content == 1 && ref $content[0] eq 'ARRAY';
print { $_[0] } @content;
if ($!) {
my $length = 0;
$length += length for @content;
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::WriteError', [ length => $length, error => $err ] );
}
return 1;
}
);
}
my $_lock_ex_nb;
sub locked_atomic_replace_contents {
my ( $fh, $safelock, $coderef ) = @_;
$_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB;
if ( !flock $fh, $_lock_ex_nb ) {
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create_raw( 'IOError', "locked_atomic_replace_contents could not lock the file handle because of an error: $err" );
}
if ( !ref $safelock ) {
local $@;
if ( !eval { $safelock->isa('Cpanel::SafeFileLock') } ) {
die "locked_atomic_replace_contents requires a Cpanel::SafeFileLock object";
}
}
my $locked_path = $safelock->get_path_to_file_being_locked();
die "locked_path must be valid" if !length $locked_path;
my ( $temp_file, $temp_fh, $created_temp_file, $attempts );
my $current_perms = ( stat($fh) )[2] & 07777;
while ( !$created_temp_file && ++$attempts < 100 ) {
$temp_file = sprintf(
'%s-%x-%x-%x',
$locked_path,
substr( rand, 2 ),
scalar( reverse time ),
scalar( reverse $$ ),
);
my ( $basename, $dirname );
$basename = File::Basename::basename($temp_file);
if ( length $basename >= 255 ) {
$basename = substr( $basename, 255 );
$dirname = File::Basename::dirname($temp_file);
$temp_file = "$dirname/$basename";
}
$created_temp_file = Cpanel::FileUtils::Open::sysopen_with_real_perms( $temp_fh, $temp_file, WRONLY_CREAT_EXCL, $current_perms ) or do {
last if $! != _EEXIST;
};
}
if ( !$created_temp_file ) {
my $lasterr = $!;
die Cpanel::Exception::create( 'TempFileCreateError', [ path => $temp_file, error => $lasterr ] );
}
if ( !flock $temp_fh, $Cpanel::Fcntl::Constants::LOCK_EX ) {
my $err = $!;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FlockError', [ path => $temp_file, error => $err, operation => $Cpanel::Fcntl::Constants::LOCK_EX ] );
}
select( ( select($temp_fh), $| = 1 )[0] ); ##no critic qw(ProhibitOneArgSelect Variables::RequireLocalizedPunctuationVars) #aka $fd->autoflush(1);
if ( $coderef->( $temp_fh, $temp_file, $current_perms ) ) {
rename( $temp_file, $locked_path );
return $temp_fh;
}
local $!;
close $temp_fh;
unlink $temp_file;
die "locked_atomic_replace_contents coderef returns false";
}
1;
} # --- END Cpanel/SafeFile/Replace.pm
{ # --- BEGIN Cpanel/Config/CpUserGuard.pm
package Cpanel::Config::CpUserGuard;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Destruct (); # perlpkg line 211
# use Cpanel::Config::CpUser (); # perlpkg line 211
# use Cpanel::Config::CpUser::Write (); # perlpkg line 211
# use Cpanel::Config::LoadCpUserFile (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
sub new {
my ( $class, $user ) = @_;
my ( $data, $file, $lock, $is_locked ) = ( undef, undef, undef, 0 );
my $cpuser = Cpanel::Config::LoadCpUserFile::_load_locked($user);
if ( $cpuser && ref $cpuser eq 'HASH' ) {
$data = $cpuser->{'data'};
$file = $cpuser->{'file'};
$lock = $cpuser->{'lock'};
$is_locked = defined $lock;
}
else {
Cpanel::Debug::log_warn("Failed to load user file for '$user': $!");
return;
}
my $path = "$Cpanel::Config::CpUser::cpuser_dir/$user";
return bless {
user => $user,
data => $data,
path => $path,
_file => $file,
_lock => $lock,
_pid => $$,
is_locked => $is_locked,
};
}
sub data {
my ($self) = @_;
return $self->{'data'};
}
sub set_worker_node {
my ( $self, $worker_type, $worker_alias, $token ) = @_;
require Cpanel::LinkedNode::Worker::Storage;
Cpanel::LinkedNode::Worker::Storage::set( $self->{'data'}, $worker_type, $worker_alias, $token );
return $self;
}
sub unset_worker_node {
my ( $self, $worker_type ) = @_;
require Cpanel::LinkedNode::Worker::Storage;
return Cpanel::LinkedNode::Worker::Storage::unset( $self->{'data'}, $worker_type );
}
sub save {
my ($self) = @_;
my $user = $self->{'user'};
my $data = $self->{'data'};
if ( $self->{'_pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot save');
return;
}
if ( !UNIVERSAL::isa( $data, 'HASH' ) ) {
Cpanel::Debug::log_die( __PACKAGE__ . ': hash reference required' );
return;
}
my $clean_data = Cpanel::Config::CpUser::clean_cpuser_hash( $self->{'data'}, $user );
if ( !$clean_data ) {
Cpanel::Debug::log_warn("Data for user '$user' was not saved.");
return;
}
if ( !$self->{'_file'} || !$self->{'_lock'} ) {
Cpanel::Debug::log_warn("Unable to save user file for '$user': file not open and locked for writing");
return;
}
require Cpanel::SafeFile::Replace;
require Cpanel::Autodie;
my $newfh = Cpanel::SafeFile::Replace::locked_atomic_replace_contents(
$self->{'_file'}, $self->{'_lock'},
sub {
my ($fh) = @_;
chmod( 0640, $fh ) or do {
warn sprintf( "Failed to set permissions on “%s” to 0%o: %s", $self->{'path'}, 0640, $! );
};
return Cpanel::Autodie::syswrite_sigguard(
$fh,
Cpanel::Config::CpUser::Write::serialize($clean_data),
);
}
)
or do {
Cpanel::Debug::log_warn("Failed to save user file for “$user”: $!");
};
$self->{'_file'} = $newfh;
my $cpgid = Cpanel::Config::CpUser::get_cpgid($user);
if ($cpgid) {
chown 0, $cpgid, $self->{'path'} or do {
Cpanel::Debug::log_warn("Failed to chown( 0, $cpgid, $self->{'path'}): $!");
};
}
if ( $INC{'Cpanel/Locale/Utils/User.pm'} ) {
Cpanel::Locale::Utils::User::clear_user_cache($user);
}
Cpanel::Config::CpUser::recache( $data, $user, $cpgid );
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} ) or do {
Cpanel::Debug::log_warn("Failed to safeclose $self->{'path'}: $!");
};
$self->{'_file'} = $self->{'_lock'} = undef;
$self->{'is_locked'} = 0;
return 1;
}
sub abort {
my ($self) = @_;
my $user = $self->{'user'};
my $data = $self->{'data'};
if ( $self->{'_pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot save');
return;
}
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} );
$self->{'_file'} = $self->{'_lock'} = undef;
$self->{'is_locked'} = 0;
return 1;
}
sub DESTROY {
my ($self) = @_;
return unless $self->{'is_locked'};
return if Cpanel::Destruct::in_dangerous_global_destruction();
return unless $self->{'_pid'} == $$;
Cpanel::SafeFile::safeclose( $self->{'_file'}, $self->{'_lock'} );
$self->{'is_locked'} = 0;
return;
}
1;
} # --- END Cpanel/Config/CpUserGuard.pm
{ # --- BEGIN Cpanel/Locale/Utils/User/Modify.pm
package Cpanel::Locale::Utils::User::Modify;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::PwCache (); # perlpkg line 211
sub save_user_locale {
my ( $locale, undef, $user ) = @_;
$locale ||= 'en';
$user ||= $Cpanel::user || $ENV{'REMOTE_USER'} || ( $> == 0 ? 'root' : ( Cpanel::PwCache::getpwuid_noshadow($>) )[0] );
if ( $user eq 'root' ) {
require Cpanel::LoadModule;
Cpanel::LoadModule::load_perl_module('Cpanel::DataStore');
my $root_conf_yaml = Cpanel::PwCache::gethomedir('root') . '/.cpanel_config';
my $hr = Cpanel::DataStore::fetch_ref($root_conf_yaml);
return 2 if exists $hr->{'locale'} && $hr->{'locale'} eq $locale;
$hr->{'locale'} = $locale;
return 1 if Cpanel::DataStore::store_ref( $root_conf_yaml, $hr );
return;
}
elsif ( $> == 0 ) {
require Cpanel::Config::CpUserGuard;
my $cpuser_guard = Cpanel::Config::CpUserGuard->new($user) or return;
$cpuser_guard->{'data'}->{'LOCALE'} = $locale;
delete $cpuser_guard->{'data'}->{'LANG'};
delete $cpuser_guard->{'data'}{'__LOCALE_MISSING'};
return $cpuser_guard->save();
}
else {
require Cpanel::LoadModule;
Cpanel::LoadModule::load_perl_module('Cpanel::AdminBin');
if ( $ENV{'TEAM_USER'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::AdminBin::Call');
return Cpanel::AdminBin::Call::call( 'Cpanel', 'teamconfig', 'SET_LOCALE', $ENV{'TEAM_USER'}, $locale );
}
return Cpanel::AdminBin::run_adminbin_with_status( 'lang', 'SAVEUSERSETTINGS', $locale, 0, $user )->{'status'};
}
return 1;
}
1;
} # --- END Cpanel/Locale/Utils/User/Modify.pm
{ # --- BEGIN Cpanel/Locale.pm
package Cpanel::Locale;
use strict;
BEGIN {
$ENV{'IGNORE_WIN32_LOCALE'} = 1;
}
# use parent Cpanel::CPAN::Locale::Maketext::Utils (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::CPAN::Locale::Maketext::Utils); }
# use Cpanel::Locale::Utils (); # Individual Locale modules depend on this being brought in here, if it is removed they will all need updated. Same for cpanel.pl # perlpkg line 211
# use Cpanel::Locale::Utils::Paths (); # perlpkg line 211
# use Cpanel::CPAN::Locale::Maketext (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
use constant _ENOENT => 2;
BEGIN {
local $^H = 0; # cheap no warnings without importing it
local $^W = 0;
*Cpanel::CPAN::Locale::Maketext::Utils::remove_key_from_lexicons = sub { }; # PPI NO PARSE - loaded above - disabled
}
our $SERVER_LOCALE_FILE = '/var/cpanel/server_locale';
our $LTR = 1;
our $RTL = 2;
our %known_locales_character_orientation = (
ar => $RTL,
bn => $LTR,
bg => $LTR,
cs => $LTR,
da => $LTR,
de => $LTR,
el => $LTR,
en => $LTR,
en_US => $LTR,
en_GB => $LTR,
es_419 => $LTR,
es => $LTR,
es_es => $LTR,
fi => $LTR,
fil => $LTR,
fr => $LTR,
he => $RTL,
hi => $LTR,
hu => $LTR,
i_cpanel_snowmen => $LTR,
i_cp_qa => $LTR,
id => $LTR,
it => $LTR,
ja => $LTR,
ko => $LTR,
ms => $LTR,
nb => $LTR,
nl => $LTR,
no => $LTR,
pl => $LTR,
pt_br => $LTR,
pt => $LTR,
ro => $LTR,
ru => $LTR,
sl => $LTR,
sv => $LTR,
th => $LTR,
tr => $LTR,
uk => $LTR,
vi => $LTR,
zh => $LTR,
zh_tw => $LTR,
zh_cn => $LTR,
);
my $logger;
sub _logger {
require Cpanel::Logger;
return ( $logger ||= Cpanel::Logger->new() );
}
*get_lookup_hash_of_mutli_epoch_datetime = *get_lookup_hash_of_multi_epoch_datetime;
sub preinit {
if ( exists $INC{'Cpanel.pm'} && !$Cpanel::CPDATA{'LOCALE'} ) {
require Cpanel::Locale::Utils::User if !exists $INC{'Cpanel/Locale/Utils/User.pm'};
Cpanel::Locale::Utils::User::init_cpdata_keys();
}
if ( $ENV{'HTTP_COOKIE'} ) {
require Cpanel::Cookies unless $INC{'Cpanel/Cookies.pm'};
if ( !keys %Cpanel::Cookies ) {
%Cpanel::Cookies = %{ Cpanel::Cookies::get_cookie_hashref() };
}
}
%Cpanel::Grapheme = %{ Cpanel::Locale->get_grapheme_helper_hashref() };
return 1;
}
sub makevar {
return $_[0]->maketext( ref $_[1] ? @{ $_[1] } : @_[ 1 .. $#_ ] ); ## no extract maketext
}
*maketext = *Cpanel::CPAN::Locale::Maketext::maketext; ## no extract maketext
my %singleton_stash = ();
BEGIN {
no warnings; ## no critic(ProhibitNoWarnings)
CHECK {
if ( ( $INC{'O.pm'} || $INC{'Cpanel/BinCheck.pm'} || $INC{'Cpanel/BinCheck/Lite.pm'} ) && %singleton_stash ) {
die("If you use a locale at begin time, you are responsible for deleting it too. Try calling _reset_singleton_stash\n");
}
}
}
sub _reset_singleton_stash {
foreach my $class ( keys %singleton_stash ) {
foreach my $args_sig ( keys %{ $singleton_stash{$class} } ) {
$singleton_stash{$class}{$args_sig}->cpanel_detach_lexicon();
}
}
%singleton_stash = ();
return 1;
}
sub get_handle {
preinit();
no warnings 'redefine';
*get_handle = *_real_get_handle;
goto &_real_get_handle;
}
sub _map_any_old_style_to_new_style {
my (@locales) = @_;
if ( grep { !$known_locales_character_orientation{$_} && index( $_, 'i_' ) != 0 } @locales ) {
require Cpanel::Locale::Utils::Legacy;
goto \&Cpanel::Locale::Utils::Legacy::map_any_old_style_to_new_style;
}
return @locales;
}
our $IN_REAL_GET_HANDLE = 0;
sub _setup_for_real_get_handle { ## no critic qw(RequireArgUnpacking)
if ($IN_REAL_GET_HANDLE) {
_load_carp();
if ( $IN_REAL_GET_HANDLE > 1 ) {
die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to call _setup_for_real_get_handle from _setup_for_real_get_handle");
}
warn 'Cpanel::Carp'->can('safe_longmess')->("Attempted to call _setup_for_real_get_handle from _setup_for_real_get_handle");
if ($Cpanel::Exception::IN_EXCEPTION_CREATION) { # PPI NO PARSE - Only care about this check if the module is loaded
$Cpanel::Exception::LOCALIZE_STRINGS = 0; # PPI NO PARSE - Only care about this check if the module is loaded
}
}
local $IN_REAL_GET_HANDLE = $IN_REAL_GET_HANDLE + 1;
if ( defined $Cpanel::App::appname && defined $ENV{'REMOTE_USER'} ) { # PPI NO PARSE - Only care about this check if the module is loaded
if (
$Cpanel::App::appname eq 'whostmgr' # PPI NO PARSE - Only care about this check if the module is loaded
&& $ENV{'REMOTE_USER'} ne 'root'
) {
require Cpanel::Config::HasCpUserFile;
if ( Cpanel::Config::HasCpUserFile::has_readable_cpuser_file( $ENV{'REMOTE_USER'} ) ) {
require Cpanel::Config::LoadCpUserFile::CurrentUser;
my $cpdata_ref = Cpanel::Config::LoadCpUserFile::CurrentUser::load( $ENV{'REMOTE_USER'} );
if ( scalar keys %{$cpdata_ref} ) {
*Cpanel::CPDATA = $cpdata_ref;
}
}
}
}
my ( $class, @langtags ) = (
$_[0],
(
defined $_[1] ? _map_any_old_style_to_new_style( (@_)[ 1 .. $#_ ] )
: exists $Cpanel::Cookies{'session_locale'} && $Cpanel::Cookies{'session_locale'} ? _map_any_old_style_to_new_style( $Cpanel::Cookies{'session_locale'} )
: ( exists $Cpanel::CPDATA{'LOCALE'} && $Cpanel::CPDATA{'LOCALE'} ) ? ( $Cpanel::CPDATA{'LOCALE'} )
: ( exists $Cpanel::CPDATA{'LANG'} && $Cpanel::CPDATA{'LANG'} ) ? ( _map_any_old_style_to_new_style( $Cpanel::CPDATA{'LANG'} ) )
: ( get_server_locale() )
)
);
if ( !$Cpanel::Locale::CDB_File_Path ) {
$Cpanel::Locale::CDB_File_Path = Cpanel::Locale::Utils::init_lexicon( 'en', \%Cpanel::Locale::Lexicon, \$Cpanel::Locale::VERSION, \$Cpanel::Locale::Encoding );
}
_make_alias_if_needed( @langtags ? @langtags : 'en_us' );
return @langtags;
}
my %_made_aliases;
sub _make_alias_if_needed {
foreach my $tag ( grep { ( $_ eq 'en' || $_ eq 'i_default' || $_ eq 'en_us' ) && !$_made_aliases{$_} } ( 'en', @_ ) ) {
Cpanel::Locale->make_alias( [$tag], 1 );
$_made_aliases{$tag} = 1;
}
return 0;
}
sub _real_get_handle {
my ( $class, @arg_langtags ) = @_;
my @langtags = _setup_for_real_get_handle( $class, @arg_langtags );
@langtags = map { my $l = $_; $l = 'en' if ( $l eq 'en_us' || $l eq 'i_default' ); $l } grep { $class->cpanel_is_valid_locale($_) } @langtags;
@langtags = ('en') unless scalar @langtags;
my $args_sig = join( ',', @langtags ) || 'no_args';
return (
( defined $singleton_stash{$class}{$args_sig} && ++$singleton_stash{$class}{$args_sig}->{'_singleton_reused'} )
? $singleton_stash{$class}{$args_sig}
: ( $singleton_stash{$class}{$args_sig} = Cpanel::CPAN::Locale::Maketext::get_handle( $class, @langtags ) )
);
}
sub get_non_singleton_handle {
my ( $class, @arg_langtags ) = @_;
my @langtags = _setup_for_real_get_handle( $class, @arg_langtags );
return Cpanel::CPAN::Locale::Maketext::get_handle( $class, @langtags );
}
sub init {
my ($lh) = @_;
$lh->SUPER::init();
$lh->_initialize_unknown_phrase_logging();
$lh->_initialize_bracket_notation_whitelist();
return $lh;
}
sub _initialize_unknown_phrase_logging {
my $lh = shift;
if ( defined $Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT ) { # PPI NO PARSE - Only needed if loaded
my $setter_cr = $lh->can("set_context_${Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT}") or do { # PPI NO PARSE - Only needed if loaded
die "Invalid \$Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT: “$Cpanel::Locale::Context::DEFAULT_OUTPUT_CONTEXT”!"; # PPI NO PARSE - Only needed if loaded
};
$setter_cr->($lh);
}
elsif ( defined $Cpanel::Carp::OUTPUT_FORMAT ) { # issafe
if ( $Cpanel::Carp::OUTPUT_FORMAT eq 'xml' ) { # issafe
$lh->set_context_plain(); # no HTML markup or ANSI escape sequences
}
elsif ( $Cpanel::Carp::OUTPUT_FORMAT eq 'html' ) { # issafe
$lh->set_context_html(); # HTML
}
}
$lh->{'use_external_lex_cache'} = 1;
if ( exists $Cpanel::CPDATA{'LOCALE_LOG_MISSING'} && $Cpanel::CPDATA{'LOCALE_LOG_MISSING'} ) {
$lh->{'_log_phantom_key'} = sub {
my ( $lh, $key ) = @_;
my $chain = '';
my $base_class = $lh->get_base_class();
foreach my $class ( $lh->get_language_class, $base_class ) {
my $lex_path = $lh->get_cdb_file_path( $class eq $base_class ? 1 : 0 );
next if !$lex_path;
$chain .= "\tLOCALE: $class\n\tPATH: $lex_path\n";
last if $class eq 'Cpanel::Locale::en' || $class eq 'Cpanel::Locale::en_us' || $class eq 'Cpanel::Locale::i_default';
}
my $pkg = $lh->get_language_tag();
_logger->info( ( $Cpanel::Parser::Vars::file ? "$Cpanel::Parser::Vars::file ::" : '' ) . qq{ Could not find key via '$pkg' locale:\n\tKEY: '$key'\n$chain} ); # PPI NO PARSE -- module will already be there is we care about it
};
}
return $lh;
}
our @DEFAULT_WHITELIST = qw(quant asis output current_year list_and list_or comment boolean datetime local_datetime format_bytes get_locale_name get_user_locale_name is_defined is_future join list_and_quoted list_or_quoted numerate numf);
sub _initialize_bracket_notation_whitelist {
my $lh = shift;
my @whitelist = @DEFAULT_WHITELIST;
my $custom_whitelist_file = Cpanel::Locale::Utils::Paths::get_custom_whitelist_path();
if ( open( my $fh, '<', $custom_whitelist_file ) ) {
while ( my $ln = readline($fh) ) {
chomp $ln;
push @whitelist, $ln if length($ln);
}
close $fh;
}
$lh->whitelist(@whitelist);
return $lh;
}
sub output_cpanel_error {
my ( $lh, $position ) = @_;
if ( $lh->context_is_ansi() ) {
return "\e[1;31m" if $position eq 'begin';
return "\e[0m" if $position eq 'end';
return '';
}
elsif ( $lh->context_is_html() ) {
return qq{} if $position eq 'begin';
return '
' if $position eq 'end';
return '';
}
else {
return ''; # e.g. $lh->context_is_plain()
}
}
sub cpanel_get_3rdparty_lang {
my ( $lh, $_3rdparty ) = @_;
require Cpanel::Locale::Utils::3rdparty;
return Cpanel::Locale::Utils::3rdparty::get_app_setting( $lh, $_3rdparty ) || Cpanel::Locale::Utils::3rdparty::get_3rdparty_lang( $lh, $_3rdparty ) || $lh->get_language_tag() || 'en';
}
sub cpanel_is_valid_locale {
my ( $lh, $locale ) = @_;
my %valid_locales = map { $_ => 1 } ( qw(en en_us i_default), $lh->list_available_locales );
return $valid_locales{$locale} ? 1 : 0;
}
sub cpanel_get_3rdparty_list {
my ($lh) = @_;
require Cpanel::Locale::Utils::3rdparty;
return Cpanel::Locale::Utils::3rdparty::get_3rdparty_list($lh);
}
sub cpanel_get_lex_path {
my ( $lh, $path, $rv ) = @_;
return if !defined $path || $path eq '' || substr( $path, -3 ) ne '.js';
require Cpanel::JS::Variations;
my $query = $path;
$query = Cpanel::JS::Variations::get_base_file( $query, '-%s.js' );
if ( defined $rv && index( $rv, '%s' ) == -1 ) {
substr( $rv, -3, 3, '-%s.js' );
}
my $asset_path = $lh->get_asset_file( $query, $rv );
return $asset_path if $asset_path && substr( $asset_path, -3 ) eq '.js' && index( $asset_path, '-' ) > -1; # Only return a value if there is a localized js file here
return;
}
sub tag_is_default_locale {
my $tag = $_[1] || $_[0]->get_language_tag();
return 1 if $tag eq 'en' || $tag eq 'en_us' || $tag eq 'i_default';
return;
}
sub get_cdb_file_path {
my ( $lh, $core ) = @_;
my $class = $core ? $lh->get_base_class() : $lh->get_language_class();
no strict 'refs';
return
$class eq 'Cpanel::Locale::en'
|| $class eq 'Cpanel::Locale::en_us'
|| $class eq 'Cpanel::Locale::i_default' ? $Cpanel::Locale::CDB_File_Path : ${ $class . '::CDB_File_Path' };
}
sub _slurp_small_file_if_exists_no_exception {
my ($path) = @_;
local ( $!, $^E );
open my $rfh, '<', $path or do {
if ( $! != _ENOENT() ) {
warn "open($path): $!";
}
return undef;
};
read $rfh, my $buf, 8192 or do {
warn "read($path): $!";
};
return $buf;
}
my $_server_locale_file_contents;
sub get_server_locale {
if ( exists $ENV{'CPANEL_SERVER_LOCALE'} ) {
return $ENV{'CPANEL_SERVER_LOCALE'} if $ENV{'CPANEL_SERVER_LOCALE'} !~ tr{A-Za-z0-9_-}{}c;
return undef;
}
if (%main::CPCONF) {
return $main::CPCONF{'server_locale'} if exists $main::CPCONF{'server_locale'};
}
return ( $_server_locale_file_contents //= ( _slurp_small_file_if_exists_no_exception($SERVER_LOCALE_FILE) || '' ) );
}
sub _clear_cache {
$_server_locale_file_contents = undef;
return;
}
sub get_locale_for_user_cpanel {
if (%main::CPCONF) {
return $main::CPCONF{'cpanel_locale'} if exists $main::CPCONF{'cpanel_locale'};
return $main::CPCONF{'server_locale'} if exists $main::CPCONF{'server_locale'};
}
require Cpanel::Config::LoadCpConf;
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf_not_copy(); # safe since we do not modify cpconf
return $cpconf->{'cpanel_locale'} if $cpconf->{'cpanel_locale'}; # will not be autovivified, 0 and "" are invalid, if the value is invalid they will get 'en'
return $cpconf->{'server_locale'} if $cpconf->{'server_locale'}; # will not be autovivified, 0 and "" are invalid, if the value is invalid they will get 'en'
return;
}
sub cpanel_reinit_lexicon {
my ($lh) = @_;
$lh->cpanel_detach_lexicon();
$lh->cpanel_attach_lexicon();
}
my $detach_locale_lex;
sub cpanel_detach_lexicon {
my ($lh) = @_;
my $locale = $lh->get_language_tag();
no strict 'refs';
undef $Cpanel::Locale::CDB_File_Path;
if ( $locale ne 'en' && $locale ne 'en_us' && $locale ne 'i_default' ) {
$detach_locale_lex = ${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' };
undef ${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' };
}
untie( %{ 'Cpanel::Locale::' . $locale . '::Lexicon' } );
untie %Cpanel::Locale::Lexicon;
}
sub cpanel_attach_lexicon {
my ($lh) = @_;
my $locale = $lh->get_language_tag();
$Cpanel::Locale::CDB_File_Path = Cpanel::Locale::Utils::init_lexicon( 'en', \%Cpanel::Locale::Lexicon, \$Cpanel::Locale::VERSION, \$Cpanel::Locale::Encoding );
_make_alias_if_needed($locale);
no strict 'refs';
if ( defined $detach_locale_lex ) {
${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' } = $detach_locale_lex;
}
else {
${ 'Cpanel::Locale::' . $locale . '::CDB_File_Path' } = $Cpanel::Locale::CDB_File_Path;
}
my $file_path = $lh->get_cdb_file_path();
return if !$file_path;
return Cpanel::Locale::Utils::get_readonly_tie( $lh->get_cdb_file_path(), \%{ 'Cpanel::Locale::' . $locale . '::Lexicon' } );
}
sub is_rtl {
my ($lh) = @_;
return 'right-to-left' eq $lh->get_language_tag_character_orientation() ? 1 : 0;
}
sub get_language_tag_character_orientation {
if ( my $direction = $known_locales_character_orientation{ $_[1] || $_[0]->{'fallback_locale'} || $_[0]->get_language_tag() } ) {
return 'right-to-left' if $direction == $RTL;
return 'left-to-right';
}
$_[0]->SUPER::get_language_tag_character_orientation( @_[ 1 .. $#_ ] );
}
my $menu_ar;
sub get_locale_menu_arrayref {
return $menu_ar if $menu_ar;
require Cpanel::Locale::Utils::Display;
$menu_ar = [ Cpanel::Locale::Utils::Display::get_locale_menu_hashref(@_) ]; # always array context to get all structs, properly uses other args besides object
return $menu_ar;
}
my $non_existent;
sub get_non_existent_locale_menu_arrayref {
return $non_existent if $non_existent;
require Cpanel::Locale::Utils::Display;
$non_existent = [ Cpanel::Locale::Utils::Display::get_non_existent_locale_menu_hashref(@_) ]; # always array context to get all structs, properly uses other args besides object
return $non_existent;
}
sub _api1_maketext {
require Cpanel::Locale::Utils::Api1;
goto \&Cpanel::Locale::Utils::Api1::_api1_maketext; ## no extract maketext
}
our $api1 = {
'maketext' => { ## no extract maketext
'function' => \&_api1_maketext, ## no extract maketext
'internal' => 1,
'legacy_function' => 2,
'modify' => 'inherit',
},
};
sub current_year {
return (localtime)[5] + 1900; # we override datetime() so we can't use the internal current_year()
}
sub local_datetime {
my ( $lh, $epoch, $format ) = @_;
my $timezone = $ENV{'TZ'} // do {
require Cpanel::Timezones;
Cpanel::Timezones::calculate_TZ_env();
};
return $lh->datetime( $epoch, $format, $timezone );
}
sub datetime {
my ( $lh, $epoch, $format, $timezone ) = @_;
require Cpanel::Locale::Utils::DateTime;
if ( $epoch && $epoch =~ tr<0-9><>c ) {
require # do not include it in updatenow.static
Cpanel::Validate::Time;
Cpanel::Validate::Time::iso_or_die($epoch);
require Cpanel::Time::ISO;
$epoch = Cpanel::Time::ISO::iso2unix($epoch);
}
return Cpanel::Locale::Utils::DateTime::datetime( $lh, $epoch, $format, $timezone );
}
sub get_lookup_hash_of_multi_epoch_datetime {
my ( $lh, $epochs_ar, $format, $timezone ) = @_;
require Cpanel::Locale::Utils::DateTime;
return Cpanel::Locale::Utils::DateTime::get_lookup_hash_of_multi_epoch_datetime( $lh, $epochs_ar, $format, $timezone );
}
sub get_locale_name_or_nothing {
my ( $locale, $name, $in_locale_tongue ) = @_;
$name ||= $locale->get_language_tag();
if ( index( $name, 'i_' ) == 0 ) {
require Cpanel::DataStore;
my $i_locales_path = Cpanel::Locale::Utils::Paths::get_i_locales_config_path();
my $i_conf = Cpanel::DataStore::fetch_ref("$i_locales_path/$name.yaml");
return $i_conf->{'display_name'} if $i_conf->{'display_name'};
}
else {
my $real = $locale->get_language_tag_name( $name, $in_locale_tongue );
return $real if $real;
}
return;
}
sub get_locale_name_or_tag {
return $_[0]->get_locale_name_or_nothing( $_[1], $_[2] ) || $_[1] || $_[0]->get_language_tag();
}
*get_locale_name = *get_locale_name_or_tag; # for shorter BN
sub get_user_locale {
return $Cpanel::CPDATA{'LOCALE'} if $Cpanel::CPDATA{'LOCALE'};
require Cpanel::Locale::Utils::User; # probably a no-op but just in case since its loading is conditional
return Cpanel::Locale::Utils::User::get_user_locale();
}
sub get_user_locale_name {
require Cpanel::Locale::Utils::User; # probably a no-op but just in case since its loading is conditional
return $_[0]->get_locale_name_or_tag( Cpanel::Locale::Utils::User::get_user_locale( $_[1] ) );
}
sub set_user_locale {
my ( $locale, $country_code ) = @_;
if ($country_code) {
my $language_name = $locale->lang_names_hashref();
if ( exists $language_name->{$country_code} ) {
require Cpanel::Locale::Utils::Legacy;
require Cpanel::Locale::Utils::User::Modify;
my $language = Cpanel::Locale::Utils::Legacy::get_best_guess_of_legacy_from_locale($country_code);
if ( Cpanel::Locale::Utils::User::Modify::save_user_locale( $country_code, $language, $Cpanel::user ) ) {
return 1;
}
}
}
die Cpanel::Exception::create_raw( "Empty", $locale->maketext("Unable to set locale, please specify a valid country code.") );
}
sub get_locales {
my $locale = shift;
my @listing;
my ( $names, $local_names ) = $locale->lang_names_hashref();
foreach ( keys %{$names} ) {
push @listing, {
locale => $_,
name => $names->{$_},
local_name => $local_names->{$_},
direction => ( !defined $known_locales_character_orientation{$_} || $known_locales_character_orientation{$_} == $LTR ) ? 'ltr' : 'rtl'
};
}
return \@listing;
}
my $api2_lh;
sub api2_get_user_locale {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'locale' => $api2_lh->get_user_locale() } );
}
sub api2_get_user_locale_name {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'name' => $api2_lh->get_user_locale_name() } );
}
sub api2_get_locale_name {
$api2_lh ||= Cpanel::Locale->get_handle();
my $tag = ( scalar @_ > 2 ) ? {@_}->{'locale'} : $_[1];
return ( { 'name' => $api2_lh->get_locale_name_or_tag($tag) } );
}
sub api2_get_encoding {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'encoding' => $api2_lh->encoding() } );
}
sub api2_numf {
my %args = @_;
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'numf' => $api2_lh->numf( $args{number}, $args{max_decimal_places} ) } );
}
sub api2_get_html_dir_attr {
$api2_lh ||= Cpanel::Locale->get_handle();
return ( { 'dir' => $api2_lh->get_html_dir_attr() } );
}
my $allow_demo = { allow_demo => 1 };
our %API = (
get_locale_name => $allow_demo,
get_encoding => $allow_demo,
get_html_dir_attr => $allow_demo,
get_user_locale => $allow_demo,
get_user_locale_name => $allow_demo,
numf => $allow_demo,
);
sub api2 {
my ($func) = @_;
return { %{ $API{$func} } } if $API{$func};
return;
}
my $global_lh;
sub lh {
return ( $global_lh ||= Cpanel::Locale->get_handle() );
}
sub import {
my ( $package, @args ) = @_;
my ($namespace) = caller;
if ( @args == 1 && $args[0] eq 'lh' ) {
no strict 'refs'; ## no critic(ProhibitNoStrict)
my $exported_name = "${namespace}::lh";
*$exported_name = \*lh;
}
}
sub _load_carp {
if ( !$INC{'Cpanel/Carp.pm'} ) {
local $@;
eval 'require Cpanel::Carp; 1;' or die $@; # hide from perlcc
}
return;
}
1;
} # --- END Cpanel/Locale.pm
{ # --- BEGIN Cpanel/Sys/Uname.pm
package Cpanel::Sys::Uname;
use strict;
our $SYS_UNAME = 63;
our $UNAME_ELEMENTS = 6;
our $_UTSNAME_LENGTH = 65;
my $UNAME_PACK_TEMPLATE = ( 'c' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my $UNAME_UNPACK_TEMPLATE = ( 'Z' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my @uname_cache;
sub get_uname_cached {
return ( @uname_cache ? @uname_cache : ( @uname_cache = syscall_uname() ) );
}
sub clearcache {
@uname_cache = ();
return;
}
sub syscall_uname {
my $uname;
if ( syscall( $SYS_UNAME, $uname = pack( $UNAME_PACK_TEMPLATE, () ) ) == 0 ) {
return unpack( $UNAME_UNPACK_TEMPLATE, $uname );
}
else {
die "The uname() system call failed because of an error: $!";
}
return;
}
1;
} # --- END Cpanel/Sys/Uname.pm
{ # --- BEGIN Cpanel/Sys/Hostname/Fallback.pm
package Cpanel::Sys::Hostname::Fallback;
use strict;
use warnings;
no warnings 'once';
use Socket ();
# use Cpanel::Sys::Uname (); # perlpkg line 211
sub get_canonical_hostname {
my @uname = Cpanel::Sys::Uname::get_uname_cached();
my ( $err, @results ) = Socket::getaddrinfo( $uname[1], 0, { flags => Socket::AI_CANONNAME() } );
if ( @results && $results[0]->{'canonname'} ) {
return $results[0]->{'canonname'};
}
return undef;
}
1;
} # --- END Cpanel/Sys/Hostname/Fallback.pm
{ # --- BEGIN Cpanel/Sys/Hostname.pm
package Cpanel::Sys::Hostname;
use strict;
use warnings;
no warnings 'once';
our $VERSION = 2.0;
# use Cpanel::Sys::Uname (); # perlpkg line 211
our $cachedhostname = '';
sub gethostname {
my $nocache = shift || 0;
if ( !$nocache && length $cachedhostname ) { return $cachedhostname }
my $hostname = _gethostname($nocache);
if ( length $hostname ) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$cachedhostname = $hostname;
}
return $hostname;
}
sub _gethostname {
my $nocache = shift || 0;
my $hostname;
Cpanel::Sys::Uname::clearcache() if $nocache;
my @uname = Cpanel::Sys::Uname::get_uname_cached();
if ( $uname[1] && index( $uname[1], '.' ) > -1 ) {
$hostname = $uname[1];
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
eval {
require Cpanel::Sys::Hostname::Fallback;
$hostname = Cpanel::Sys::Hostname::Fallback::get_canonical_hostname();
};
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
require Cpanel::LoadFile;
chomp( $hostname = Cpanel::LoadFile::loadfile( '/proc/sys/kernel/hostname', { 'skip_exists_check' => 1 } ) );
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$hostname =~ tr{\r\n}{}d; # chomp is not enough (not sure if this is required, however we cannot test all kernels so its safer to leave it in)
return $hostname;
}
require Cpanel::Debug;
Cpanel::Debug::log_warn('Unable to determine correct hostname');
return;
}
sub shorthostname {
my $hostname = gethostname();
return $hostname if index( $hostname, '.' ) == -1; # Hostname is not a FQDN (this should never happen)
return substr( $hostname, 0, index( $hostname, '.' ) );
}
1;
} # --- END Cpanel/Sys/Hostname.pm
{ # --- BEGIN Cpanel/Hostname.pm
package Cpanel::Hostname;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Sys::Hostname (); # perlpkg line 211
our $VERSION = 2.0;
{
no warnings 'once';
*gethostname = *Cpanel::Sys::Hostname::gethostname;
*shorthostname = *Cpanel::Sys::Hostname::shorthostname;
}
1;
} # --- END Cpanel/Hostname.pm
{ # --- BEGIN Cpanel/Config/CpConfGuard/CORE.pm
package Cpanel::Config::CpConfGuard::CORE;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::ConfigFiles (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::FileUtils::Write::JSON::Lazy (); # perlpkg line 211
# use Cpanel::LoadModule (); # perlpkg line 211
# use Cpanel::Config::CpConfGuard (); # perlpkg line 211
our $SENDING_MISSING_FILE_NOTICE = 0;
my $FILESYS_PERMS = 0644;
sub find_missing_keys {
my ($self) = @_;
_verify_called_as_object_method($self);
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Default');
my $default = 'Cpanel::Config::CpConfGuard::Default'->new(
current_config => $self->{data},
current_changes => $self->{changes},
);
if ( $self->{'is_missing'} ) {
if ( UNIVERSAL::isa( $self->{'cache'}, 'HASH' ) && %{ $self->{'cache'} } ) {
$self->{'data'} = {};
%{ $self->{'data'} } = %{ $self->{'cache'} };
my $config = $self->{'data'};
foreach my $key ( $default->get_keys() ) {
next if exists $config->{$key};
$config->{$key} = $default->get_default_for($key);
}
}
else {
$self->{'data'} = $default->get_all_defaults();
}
$self->{'modified'} = 1; # Mark as save needed.
return;
}
my $cache = $self->{'cache'};
undef( $self->{'cache'} ); # we do not need the cache after the first pass
my $config = $self->{'data'};
my $changes = $self->{'changes'}; # used for notifications
$config->{'tweak_unset_vars'} ||= '';
foreach my $key ( $default->get_keys() ) {
next if exists $config->{$key};
$self->{'modified'} = 1; # Mark as save needed.
if ( exists $cache->{$key} ) {
$config->{$key} = $cache->{$key};
$changes->{'from_cache'} ||= [];
push @{ $changes->{'from_cache'} }, $key;
$changes->{'changed_keys'} ||= {};
$changes->{'changed_keys'}{$key} = 'from_cache';
next;
}
my $changes_type = $default->is_dynamic($key) ? 'from_dynamic' : 'from_default';
$changes->{'changed_keys'} ||= {};
$changes->{'changed_keys'}{$key} = $changes_type;
$changes->{$changes_type} ||= [];
push @{ $changes->{$changes_type} }, $key;
$config->{$key} = $default->get_default_for($key);
}
foreach my $key ( @{ $default->dead_variables() } ) {
next unless exists $config->{$key};
$self->{'modified'} = 1; # Mark as save needed.
delete( $config->{$key} );
$changes->{'dead_variable'} ||= [];
push @{ $changes->{'dead_variable'} }, $key;
}
return;
}
sub validate_keys {
my ($self) = @_;
_verify_called_as_object_method($self);
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Validate');
my $invalid = 'Cpanel::Config::CpConfGuard::Validate'->can('patch_cfg')->( $self->{'data'} );
if (%$invalid) {
$self->{modified} = 1;
$self->{'changes'}->{'invalid'} = $invalid;
}
return;
}
sub notify_and_save_if_changed {
my ($self) = @_;
_verify_called_as_object_method($self);
return if !$self->{'use_lock'};
return if !$self->{'modified'};
my $config = $self->{'data'};
if ( $ENV{'CPANEL_BASE_INSTALL'} ) {
; # Do nothing for notification.
}
elsif ( $self->{'is_missing'} ) {
$config->{'tweak_unset_vars'} = '';
Cpanel::Debug::log_warn("Missing cpanel.config regenerating …");
$self->notify_missing_file;
}
elsif ( %{ $self->{'changes'} } ) {
my $changes = $self->{'changes'};
my %uniq = map { $_ => 1 } @{ $changes->{'from_default'} || [] }, @{ $changes->{'from_dynamic'} || [] }, split( /\s*,\s*/, $config->{'tweak_unset_vars'} );
$config->{'tweak_unset_vars'} = join ",", sort keys %uniq;
$self->log_missing_values();
}
return $self->save( keep_lock => 1 );
}
sub _server_locale {
my ($self) = @_;
_verify_called_as_object_method($self);
my $locale_name = $self->{'data'}->{'server_locale'} || 'en';
require Cpanel::Locale;
return Cpanel::Locale->_real_get_handle($locale_name);
}
sub _longest {
my @array = @_;
return length( ( sort { length $b <=> length $a } @array )[0] );
}
sub _stringify_undef {
my $value = shift;
return defined $value ? $value : '';
}
sub log_missing_values {
my ($self) = @_;
require Cpanel::Hostname;
my $changes = $self->{'changes'};
my $locale = $self->_server_locale();
my $hostname = Cpanel::Hostname::gethostname();
my $prev = $locale->set_context_plain();
my $message = '';
$message .= $locale->maketext( 'One or more key settings for “[_1]” were either not found in [asis,cPanel amp() WHM]’s server configuration file ([_2]), or were present but did not pass validation.', $hostname, $self->{'path'} ) . "\n";
if ( $changes->{'from_dynamic'} ) {
$message .= $locale->maketext('The following settings were absent and have been selected based on the current state of your installation.');
$message .= "\n";
my @keys = @{ $changes->{'from_dynamic'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
$message .= "\n";
}
if ( $changes->{'from_cache'} ) {
$message .= $locale->maketext('The following settings were absent, but were restored from your [asis,cpanel.config.cache] file:');
$message .= "\n";
my @keys = @{ $changes->{'from_cache'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
$message .= "\n";
}
if ( $changes->{'from_default'} or $changes->{'invalid'} ) {
$message .= $locale->maketext('The following settings were absent or invalid. Your server has copied the defaults for them from the configuration defaults file ([asis,/usr/local/cpanel/etc/cpanel.config]).');
$message .= "\n";
if ( $changes->{'from_default'} ) {
my @keys = @{ $changes->{'from_default'} };
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s\n", $key, _stringify_undef( $self->{'data'}->{$key} ) );
}
}
if ( $changes->{'invalid'} ) {
my $invalid = $changes->{'invalid'};
my @keys = keys %$invalid;
my $max_len = _longest(@keys) + 2;
foreach my $key (@keys) {
$message .= sprintf( " %-${max_len}s= %s (Previously set to '%s')\n", $key, _stringify_undef( $invalid->{$key}->{'to'} ), _stringify_undef( $invalid->{$key}->{'from'} ) );
}
}
$message .= "\n";
}
if ( $changes->{'dead_variable'} ) {
$message .= $locale->maketext('The following settings are obsolete and have been removed from the server configuration file:');
$message .= "\n";
$message .= ' ' . join( ', ', @{ $changes->{'dead_variable'} } );
$message .= "\n\n";
}
$message .= $locale->maketext( 'Read the [asis,cpanel.config] file [output,url,_1,documentation] for important information about this file.', 'https://go.cpanel.net/cpconfig' );
$message .= "\n\n";
Cpanel::Debug::logger(); # initialize the logger
local $Cpanel::Logger::ENABLE_BACKTRACE = 0;
foreach my $chunk ( split( /\n+/, $message ) ) {
Cpanel::Debug::log_warn($chunk);
}
$locale->set_context($prev);
return;
}
sub notify_missing_file {
my ($self) = @_;
if ($SENDING_MISSING_FILE_NOTICE) {
return; #Already sending notification, don't double up
}
require Cpanel::Hostname;
local $SENDING_MISSING_FILE_NOTICE = 1;
my $locale = $self->_server_locale();
my $prev = $locale->set_context_plain();
my @to_log;
my %critical_values;
my $hostname = Cpanel::Hostname::gethostname();
push @to_log, $locale->maketext('Your server has copied the defaults from your cache and the configuration defaults file ([asis,/usr/local/cpanel/etc/cpanel.config]) to [asis,/var/cpanel/cpanel.config], and it has generated the following critical values:');
Cpanel::LoadModule::load_perl_module('Cpanel::Config::CpConfGuard::Default');
my $critical = Cpanel::Config::CpConfGuard::Default::critical_values();
my $max_len = _longest(@$critical) + 2;
my $critical_value;
foreach my $key ( sort @$critical ) {
$critical_value = _stringify_undef( $self->{'data'}->{$key} );
$critical_values{$key} = $critical_value;
push @to_log, sprintf( " %-${max_len}s= %s\n", $key, $critical_value );
}
push @to_log, $locale->maketext( 'Read the [asis,cpanel.config] file [output,url,_1,documentation] for more information about this file.', 'https://go.cpanel.net/cpconfig' ) . ' ';
Cpanel::Debug::logger(); # initialize the logger
local $Cpanel::Logger::ENABLE_BACKTRACE = 0;
foreach my $chunk (@to_log) {
chomp $chunk;
Cpanel::Debug::log_warn($chunk);
}
_icontact( \%critical_values );
$locale->set_context($prev);
return;
}
sub _icontact {
my $critical_values = shift;
Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::Config::CpConfGuard");
Cpanel::LoadModule::load_perl_module('Cpanel::Notify');
'Cpanel::Notify'->can('notification_class')->(
'class' => 'Config::CpConfGuard',
'application' => 'Config::CpConfGuard',
'constructor_args' => [
'origin' => 'cpanel.config',
'critical_values' => $critical_values,
]
);
return;
}
sub save {
my ( $self, %opts ) = @_;
_verify_called_as_object_method($self);
return unless ( $self->{'use_lock'} );
return 1 if $Cpanel::Config::CpConfGuard::memory_only;
if ( !$self->{'rw'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::SafeFile');
$self->{'fh'} = 'Cpanel::SafeFile'->can('safereopen')->( $self->{'fh'}, '+>', $Cpanel::ConfigFiles::cpanel_config_file );
return $self->abort('Cannot reopen file for rw') unless $self->{'fh'};
$self->{'rw'} = 1;
}
return $self->abort('Locked in parent, cannot save') if $self->{'pid'} != $$;
return $self->abort('hash reference required') if !UNIVERSAL::isa( $self->{'data'}, 'HASH' );
Cpanel::LoadModule::load_perl_module('Cpanel::Config::FlushConfig');
Cpanel::LoadModule::load_perl_module('Cpanel::Config::SaveCpConf');
'Cpanel::Config::FlushConfig'->can('flushConfig')->(
$self->{'fh'},
$self->{'data'},
'=',
'Cpanel::Config::SaveCpConf'->can('header_message')->(),
{
sort => 1,
perms => $FILESYS_PERMS,
},
);
%{$Cpanel::Config::CpConfGuard::MEM_CACHE} = %{ $self->{'data'} };
return 1 if $opts{keep_lock};
$self->release_lock;
return 1;
}
sub _update_cache {
my ($self) = @_;
_verify_called_as_object_method($self);
return 0 if Cpanel::Config::CpConfGuard::_cache_is_valid() && $self->{'cache_is_valid'}; # Don't re-write the file if it looks correct.
$Cpanel::Config::CpConfGuard::MEM_CACHE_CPANEL_CONFIG_MTIME = ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
return unless $self->{'use_lock'}; # never update the cache when not root
local $@;
my $ok = eval { Cpanel::FileUtils::Write::JSON::Lazy::write_file( $Cpanel::ConfigFiles::cpanel_config_cache_file, $Cpanel::Config::CpConfGuard::MEM_CACHE, $FILESYS_PERMS ) || 0 };
if ( !$ok ) {
if ( !defined $ok ) {
Cpanel::Debug::log_warn("Cannot update cache file: $Cpanel::ConfigFiles::cpanel_config_cache_file $@");
unlink $Cpanel::ConfigFiles::cpanel_config_cache_file;
return -1;
}
return;
}
my $past = ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] - 1;
return _adjust_timestamp_for( $Cpanel::ConfigFiles::cpanel_config_file => $past );
}
sub _adjust_timestamp_for {
my ( $f, $time ) = @_;
return unless defined $f && defined $time;
return 1 if utime( $time, $time, $f );
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime($time);
my $stamp = sprintf( "%04d%02d%02d%02d%02d.%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );
unless ( _touch( $f => $stamp ) ) {
Cpanel::Debug::log_warn("Cannot update mtime on $f: $@");
return;
}
return 1;
}
sub _touch { # mainly created to easily mock that part during the tests
my ( $f, $stamp ) = @_;
return system( 'touch', '-t', $stamp, $f ) == 0 ? 1 : 0;
}
sub _verify_called_as_object_method {
if ( ref( $_[0] ) ne "Cpanel::Config::CpConfGuard" ) {
die '' . ( caller(0) )[3] . " was not called as an object method [" . ref( $_[0] ) . "]\n";
}
return;
}
sub abort {
my ( $self, $msg ) = @_;
_verify_called_as_object_method($self);
if ( $self->{'pid'} != $$ ) {
Cpanel::Debug::log_die('Locked in parent, cannot release lock');
return;
}
$self->release_lock();
Cpanel::Debug::log_die($msg) if $msg;
return 1;
}
sub set {
my ( $self, $k, $v ) = @_;
_verify_called_as_object_method($self);
return unless defined $k;
my $config = $self->{'data'};
$config->{$k} = $v;
if ( $config->{'tweak_unset_vars'} && index( $config->{'tweak_unset_vars'}, $k ) > -1 ) {
my %unset = map { ( $_ => 1 ) } split( /\s*,\s*/, $config->{'tweak_unset_vars'} );
delete( $unset{$k} );
$config->{'tweak_unset_vars'} = join( ',', sort keys %unset );
}
return 1;
}
1;
} # --- END Cpanel/Config/CpConfGuard/CORE.pm
{ # --- BEGIN Cpanel/Config/CpConfGuard.pm
package Cpanel::Config::CpConfGuard;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::JSON::FailOK (); # perlpkg line 211
# use Cpanel::ConfigFiles (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::Destruct (); # perlpkg line 211
use constant {
_ENOENT => 2,
};
our $IN_LOAD = 0;
our $SENDING_MISSING_FILE_NOTICE = 0;
my $FILESYS_PERMS = 0644;
my $is_daemon;
BEGIN {
$is_daemon = 0; # initialize the value in the begin block
if ( index( $0, 'updatenow' ) > -1
|| index( $0, 'cpsrvd' ) > -1
|| index( $0, 'cpdavd' ) > -1
|| index( $0, 'queueprocd' ) > -1
|| index( $0, 'tailwatchd' ) > -1
|| index( $0, 'cpanellogd' ) > -1
|| ( length $0 > 7 && substr( $0, -7 ) eq '.static' ) ) {
$is_daemon = 1;
}
}
my $module_file;
our ( $MEM_CACHE_CPANEL_CONFIG_MTIME, $MEM_CACHE ) = ( 0, undef );
our $memory_only;
sub _is_daemon { $is_daemon }; # for testing
sub clearcache {
$MEM_CACHE_CPANEL_CONFIG_MTIME = 0;
$MEM_CACHE = undef;
return;
}
sub new {
my ( $class, %opts ) = @_;
Cpanel::JSON::FailOK::LoadJSONModule() if !$is_daemon && !$INC{'Cpanel/JSON.pm'};
my $self = bless {
%opts, # to be improved
'path' => $Cpanel::ConfigFiles::cpanel_config_file,
'pid' => $$,
'modified' => 0,
'changes' => {},
}, $class;
$self->{'use_lock'} //= ( $> == 0 ) ? 1 : 0;
if ($memory_only) {
$self->{'data'} = ref($memory_only) eq 'HASH' ? $memory_only : {};
return $self;
}
( $self->{'cache'}, $self->{'cache_is_valid'} ) = get_cache();
return $self if $self->{'loadcpconf'} && $self->{'cache_is_valid'};
$self->load_cpconf_file();
return $self if $is_daemon || $opts{'no_validate'} || !$self->{'use_lock'};
$self->find_missing_keys();
$self->validate_keys();
$self->notify_and_save_if_changed();
return $self;
}
sub set {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::set;
}
sub config_copy {
my ($self) = @_;
_verify_called_as_object_method($self);
my $config = $self->{'data'} || $self->{'cache'} || {};
return {%$config};
}
sub find_missing_keys {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::find_missing_keys;
}
sub validate_keys {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::validate_keys;
}
sub notify_and_save_if_changed {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::notify_and_save_if_changed;
}
sub log_missing_values {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::log_missing_values;
}
sub notify_missing_file {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::notify_missing_file;
}
sub save {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::save;
}
sub release_lock {
my ($self) = @_;
_verify_called_as_object_method($self);
return unless $self->{'use_lock'} && defined $self->{'pid'} && $self->{'pid'} eq $$ && $self->{'lock'};
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $self->{'fh'}, $self->{'lock'}, sub { return $self->_update_cache() } );
$self->{'fh'} = $self->{'lock'} = undef;
$self->{'is_locked'} = 0;
return;
}
sub abort {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::abort;
}
sub _update_cache {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::_update_cache;
}
sub _server_locale {
require Cpanel::Config::CpConfGuard::CORE;
goto \&Cpanel::Config::CpConfGuard::CORE::_server_locale;
}
sub get_cache {
my $cpanel_config_mtime = ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
my $verbose = ( defined $Cpanel::Debug::level ? $Cpanel::Debug::level : 0 ) >= 5;
if ( $MEM_CACHE && ref($MEM_CACHE) eq 'HASH' && $cpanel_config_mtime && $cpanel_config_mtime == $MEM_CACHE_CPANEL_CONFIG_MTIME ) {
Cpanel::Debug::log_info("loadcpconf memory cache hit") if $verbose;
return ( $MEM_CACHE, 1 );
}
clearcache(); # Invalidate the memory cache.
Cpanel::Debug::log_info("loadcpconf memory cache miss") if $verbose;
my $mtime_before_read;
if ( !$INC{'Cpanel/JSON.pm'} ) {
Cpanel::Debug::log_info("Cpanel::JSON not loaded. Skipping cache load.") if $verbose;
return ( undef, 0 );
}
elsif ( -e $Cpanel::ConfigFiles::cpanel_config_cache_file ) { # No need to do -r (costs 5 additional syscalls) since we write this 0644
$mtime_before_read = ( stat _ )[9] || 0;
}
else {
Cpanel::Debug::log_info("The cache file “$Cpanel::ConfigFiles::cpanel_config_cache_file” could not be read. Skipping cache load.") if $verbose;
return ( undef, 0 );
}
my ( $mtime_after_read, $cpconf_ref ) = (0);
my $loop_count = 0;
while ( $mtime_after_read != $mtime_before_read && $loop_count++ < 10 ) {
sleep 1 if ( $mtime_after_read == time ); # If it was just written to, give it a second in case it's being written to.
Cpanel::Debug::log_info( "loadcpconf cache_filesys_mtime = $mtime_before_read , filesys_mtime: $cpanel_config_mtime , memory_mtime: $MEM_CACHE_CPANEL_CONFIG_MTIME , now: " . time ) if $verbose;
$cpconf_ref = Cpanel::JSON::FailOK::LoadFile($Cpanel::ConfigFiles::cpanel_config_cache_file);
$mtime_after_read = ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] || 0;
sleep 1 if ( $mtime_after_read != $mtime_before_read );
}
if ( $cpconf_ref && scalar keys %{$cpconf_ref} ) {
if ( _cache_is_valid( $cpanel_config_mtime, $mtime_after_read ) ) {
Cpanel::Debug::log_info("loadcpconf file system cache hit") if $verbose;
( $MEM_CACHE, $MEM_CACHE_CPANEL_CONFIG_MTIME ) = ( $cpconf_ref, $cpanel_config_mtime );
return ( $cpconf_ref, 1 );
}
Cpanel::Debug::log_info("loadcpconf cpanel.config.cache miss.") if $verbose;
return ( $cpconf_ref, 0 );
}
Cpanel::Debug::log_info("loadcpconf cpanel.config.cache miss.") if $verbose;
return ( undef, 0 );
}
sub _cache_is_valid {
my ( $config_mtime, $cache_mtime ) = @_;
$cache_mtime ||= ( stat($Cpanel::ConfigFiles::cpanel_config_cache_file) )[9] || 0;
return 0 unless $cache_mtime;
$config_mtime ||= ( stat($Cpanel::ConfigFiles::cpanel_config_file) )[9] || 0;
return 0 unless $config_mtime;
return ( $config_mtime + 1 == $cache_mtime ) ? 1 : 0;
}
sub load_cpconf_file {
my ($self) = @_;
if ($IN_LOAD) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Load loop detected");
}
local $IN_LOAD = 1;
_verify_called_as_object_method($self);
my $config = {};
my $config_file = $Cpanel::ConfigFiles::cpanel_config_file;
$self->{'is_missing'} = ( -e $config_file ) ? 0 : 1;
return if ( !$self->{'use_lock'} && $self->{'is_missing'} ); # We can't do anything if the file is missing and we're not root. ABORT!
if ( $self->{'use_lock'} && $self->{'is_missing'} ) {
if ( open( my $touch_fh, '>>', $config_file ) ) {
print {$touch_fh} '';
close $touch_fh;
chown 0, 0, $config_file; # avoid pulling in Cpanel::PwCache for memory reasons
chmod 0644, $config_file;
}
}
$self->{'rw'} = 0;
$self->{'rw'} = 1 if ( $self->{'use_lock'} && !$self->{'cache_is_valid'} );
require Cpanel::Config::LoadConfig;
my ( $ref, $fh, $conflock, $err ) = Cpanel::Config::LoadConfig::loadConfig(
$Cpanel::ConfigFiles::cpanel_config_file,
$config,
(undef) x 4,
{
'keep_locked_open' => !!$self->{'use_lock'},
'nocache' => 1,
'rw' => $self->{'rw'},
'allow_undef_values' => 1,
},
);
if ( !$ref && !$fh && $! != _ENOENT() ) {
$err ||= '(unknown error)';
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Can’t read “$Cpanel::ConfigFiles::cpanel_config_file” ($err)");
}
$self->{'fh'} = $fh;
$self->{'lock'} = $conflock;
$self->{'data'} = $config;
if ( $self->{'use_lock'} ) {
Cpanel::Debug::log_warn("Failed to establish lock on $Cpanel::ConfigFiles::cpanel_config_file") unless $self->{'lock'};
Cpanel::Debug::log_warn("Failed to get file handle for $Cpanel::ConfigFiles::cpanel_config_file") unless $self->{'fh'};
}
$self->{'is_locked'} = defined $self->{'lock'} ? 1 : 0; # alias for external usage
if ( !$MEM_CACHE ) {
$MEM_CACHE = {};
%$MEM_CACHE = %$config;
}
return;
}
sub _verify_called_as_object_method {
if ( ref( $_[0] ) ne __PACKAGE__ ) {
die '' . ( caller(0) )[3] . " was not called as an object method [" . ref( $_[0] ) . "]\n";
}
return;
}
sub DESTROY { ## no critic(RequireArgUnpacking)
return 1 if ( $> || $memory_only ); # Special modes we don't or won't write to cpanel.config files.
return 2 if ( !$_[0] || !keys %{ $_[0] } ); # Nothing to cleanup if we're just a blessed empty hash.
return if !$_[0]->{'lock'};
return if Cpanel::Destruct::in_dangerous_global_destruction();
$_[0]->release_lock(); # Close the file so we can update the cache properly.
return;
}
1;
} # --- END Cpanel/Config/CpConfGuard.pm
{ # --- BEGIN Cpanel/Config/LoadCpConf.pm
package Cpanel::Config::LoadCpConf;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Config::CpConfGuard (); # perlpkg line 211
sub loadcpconf {
my $cpconf = Cpanel::Config::CpConfGuard->new( 'loadcpconf' => 1 )->config_copy;
return wantarray ? %$cpconf : $cpconf;
}
sub loadcpconf_not_copy {
if ( !defined $Cpanel::Config::CpConfGuard::memory_only && $Cpanel::Config::CpConfGuard::MEM_CACHE_CPANEL_CONFIG_MTIME ) {
my ( $cache, $cache_is_valid ) = Cpanel::Config::CpConfGuard::get_cache();
if ($cache_is_valid) {
return wantarray ? %$cache : $cache;
}
}
my $cpconf_obj = Cpanel::Config::CpConfGuard->new( 'loadcpconf' => 1 );
my $cpconf = $cpconf_obj->{'data'} || $cpconf_obj->{'cache'} || {};
return wantarray ? %$cpconf : $cpconf;
}
sub clearcache;
*clearcache = *Cpanel::Config::CpConfGuard::clearcache;
1;
} # --- END Cpanel/Config/LoadCpConf.pm
{ # --- BEGIN Cpanel/Maxmem.pm
package Cpanel::Maxmem;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Config::LoadUserDomains::Count (); # perlpkg line 211
use constant _INITIAL_DEFAULT => 4096;
sub _count_domains {
return eval { Cpanel::Config::LoadUserDomains::Count::countuserdomains() } // 1;
}
sub minimum {
return _INITIAL_DEFAULT() * ( 1 + int( _count_domains() / 10_000 ) );
}
*default = *minimum;
1;
} # --- END Cpanel/Maxmem.pm
{ # --- BEGIN Cpanel/OSSys/Bits.pm
package Cpanel::OSSys::Bits;
use strict;
use warnings;
no warnings 'once';
our $MAX_32_BIT_SIGNED;
our $MAX_32_BIT_UNSIGNED;
our $MAX_64_BIT_SIGNED;
our $MAX_64_BIT_UNSIGNED;
our $MAX_NATIVE_SIGNED;
our $MAX_NATIVE_UNSIGNED;
sub getbits {
return length( pack( 'l!', 1000 ) ) * 8;
}
BEGIN {
$MAX_32_BIT_UNSIGNED = ( 1 << 32 ) - 1;
$MAX_32_BIT_SIGNED = ( 1 << 31 ) - 1;
$MAX_64_BIT_UNSIGNED = ~0; #true on both 32- and 64-bit systems
$MAX_64_BIT_SIGNED = -1 >> 1; #true on both 32- and 64-bit systems
if ( getbits() == 32 ) {
$MAX_NATIVE_SIGNED = $MAX_32_BIT_SIGNED;
$MAX_NATIVE_UNSIGNED = $MAX_32_BIT_UNSIGNED;
}
else {
$MAX_NATIVE_SIGNED = $MAX_64_BIT_SIGNED;
$MAX_NATIVE_UNSIGNED = $MAX_64_BIT_UNSIGNED;
}
}
1;
} # --- END Cpanel/OSSys/Bits.pm
{ # --- BEGIN Cpanel/Sys/Rlimit.pm
package Cpanel::Sys::Rlimit;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::OSSys::Bits (); # perlpkg line 211
# use Cpanel::Pack (); # perlpkg line 211
# use Cpanel::Syscall (); # perlpkg line 211
my $SYS_getrlimit;
my $SYS_setrlimit;
our $RLIM_INFINITY; # denotes no limit on a resource
our %RLIMITS = (
'CPU' => 0, # CPU time limit in seconds.
'DATA' => 2, # The maximum size of the process's data segment
'CORE' => 4, # Maximum size of a core file
'RSS' => 5, # Specifies the limit (in pages) of the process's resident set
'NPROC' => 6, # The maximum number of processes
'NOFILE' => 7, # The maximum number of file descriptors
'AS' => 9, # The maximum size of the process's virtual memory
'FSIZE' => 1,
'STACK' => 3,
'MEMLOCK' => 8,
'LOCKS' => 10,
'SIGPENDING' => 11,
'MSGQUEUE' => 12,
'NICE' => 13,
'RTPRIO' => 14,
'RTTIME' => 15,
);
BEGIN {
$RLIM_INFINITY = $Cpanel::OSSys::Bits::MAX_NATIVE_UNSIGNED;
}
our $PACK_TEMPLATE = 'L!L!';
our @TEMPLATE = (
rlim_cur => 'L!', # unsigned long
rlim_max => 'L!', # unsigned long
);
sub getrlimit {
my ($rlimit) = @_;
local $!;
die "getrlimit requires an rlimit constant" if !defined $rlimit;
my $buffer = pack( $PACK_TEMPLATE, 0 );
my $rlimit_num = _rlimit_to_num($rlimit);
Cpanel::Syscall::syscall( 'getrlimit', $rlimit_num, $buffer );
my $getrlimit_hr = Cpanel::Pack->new( \@TEMPLATE )->unpack_to_hashref($buffer);
return ( $getrlimit_hr->{'rlim_cur'}, $getrlimit_hr->{'rlim_max'} );
}
sub setrlimit {
my ( $rlimit, $soft, $hard ) = @_;
local $!;
die "setrlimit requires an rlimit constant" if !defined $rlimit;
die "setrlimit requires a soft limit" if !defined $soft;
die "setrlimit requires a hard limit" if !defined $hard;
my $buffer = pack( $PACK_TEMPLATE, $soft, $hard );
my $rlimit_num = _rlimit_to_num($rlimit);
Cpanel::Syscall::syscall( 'setrlimit', $rlimit_num, $buffer );
return 1;
}
sub _rlimit_to_num {
my ($rlimit) = @_;
if ( length($rlimit) && $rlimit !~ tr<0-9><>c ) {
return $rlimit;
}
elsif ( exists $RLIMITS{$rlimit} ) {
return $RLIMITS{$rlimit};
}
die "Unknown RLIMIT: $rlimit";
}
1;
} # --- END Cpanel/Sys/Rlimit.pm
{ # --- BEGIN Cpanel/Rlimit.pm
package Cpanel::Rlimit;
use strict;
# use Cpanel::Config::LoadCpConf (); # perlpkg line 211
# use Cpanel::Maxmem (); # perlpkg line 211
# use Cpanel::Sys::Rlimit (); # perlpkg line 211
sub set_rlimit {
my ( $limit, $limit_names ) = @_;
my ( $default_rlimit, $coredump_are_enabled ) = _get_server_setting_or_default();
$limit ||= $default_rlimit || $Cpanel::Sys::Rlimit::RLIM_INFINITY;
$limit_names ||= [qw/RSS AS/];
my $core_limit = $coredump_are_enabled ? $limit : 0;
if ( $limit > $Cpanel::Sys::Rlimit::RLIM_INFINITY ) {
require Cpanel::Logger;
Cpanel::Logger->new->warn("set_rlimit adjusted the requested limit of “$limit” to infinity because it exceeded the maximum allowed value.");
$limit = $Cpanel::Sys::Rlimit::RLIM_INFINITY;
}
my $error = '';
foreach my $lim (@$limit_names) {
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $lim, $limit, $limit ) } or do {
my $limit_human_value = ( $limit == $Cpanel::Sys::Rlimit::RLIM_INFINITY ? 'INFINITY' : $limit );
$error .= "$$: Unable to set RLIMIT_$lim to $limit_human_value: $@\n";
}
}
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( 'CORE', $core_limit, $core_limit ) }
or $error .= "$$: Unable to set RLIMIT_CORE to $core_limit: $@\n";
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub set_min_rlimit {
my ($min) = @_;
my $error = '';
foreach my $lim (qw(RSS AS)) {
my ( $current_soft, $current_hard ) = Cpanel::Sys::Rlimit::getrlimit($lim);
if ( $current_soft < $min || $current_hard < $min ) {
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $lim, $min, $min ) } or $error .= "$$: Unable to set RLIMIT_$lim to $min: $@\n";
}
}
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub get_current_rlimits {
return { map { $_ => [ Cpanel::Sys::Rlimit::getrlimit($_) ] } (qw(RSS AS CORE)) };
}
sub restore_rlimits {
my $limit_hr = shift;
my $error = '';
if ( ref $limit_hr eq 'HASH' ) {
foreach my $resource_name ( keys %{$limit_hr} ) {
my $values = $limit_hr->{$resource_name};
if ( ref $values ne 'ARRAY' || scalar @{$values} != 2 ) {
$error .= "Invalid limit arguments, could not restore resource limit for $resource_name.\n";
next;
}
local $@;
eval { Cpanel::Sys::Rlimit::setrlimit( $resource_name, $values->[0], $values->[1] ) }
or $error .= "$$: Unable to set $resource_name to $values->[0] and $values->[1]: $@\n";
}
}
else {
$error .= "Invalid arguments, could not restore resource limits.\n";
}
if ($error) {
$error =~ s/\n$//;
require Cpanel::Logger;
Cpanel::Logger->new->warn($error);
return 0;
}
return 1;
}
sub set_rlimit_to_infinity {
return set_rlimit($Cpanel::Sys::Rlimit::RLIM_INFINITY);
}
sub set_open_files_to_maximum {
my $limit = 1048576;
if ( open( my $fh, '<', '/proc/sys/fs/nr_open' ) ) {
$limit = <$fh>;
chomp($limit);
close($fh);
}
return set_rlimit( $limit, [qw/NOFILE/] );
}
sub _get_server_setting_or_default {
my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf_not_copy();
my $default_maxmem = Cpanel::Maxmem::default();
my $core_dumps_enabled = $cpconf->{'coredump'};
my $configured_maxmem = exists $cpconf->{'maxmem'} ? int( $cpconf->{'maxmem'} || 0 ) : $default_maxmem;
if ( $configured_maxmem && $configured_maxmem < $default_maxmem ) {
return ( _mebibytes_to_bytes($default_maxmem), $core_dumps_enabled );
}
elsif ( $configured_maxmem == 0 ) {
return ( $Cpanel::Sys::Rlimit::RLIM_INFINITY, $core_dumps_enabled );
}
else {
return ( _mebibytes_to_bytes($configured_maxmem), $core_dumps_enabled );
}
}
sub _mebibytes_to_bytes {
my $mebibytes = shift;
return ( $mebibytes * 1024**2 );
}
1;
} # --- END Cpanel/Rlimit.pm
{ # --- BEGIN Cpanel/LocaleString.pm
package Cpanel::LocaleString;
use strict;
use warnings;
no warnings 'once';
sub DESTROY { }
sub new {
if ( !length $_[1] ) {
die 'Must include at least a string!';
}
return bless \@_, shift;
}
sub set_json_to_freeze {
no warnings 'redefine';
*TO_JSON = \&_to_list_ref;
return ( __PACKAGE__ . '::_JSON_MODE' )->new();
}
sub thaw {
if ( ref( $_[1] ) ne 'ARRAY' ) {
die "Call thaw() on an ARRAY reference, not “$_[1]”!";
}
return $_[0]->new( @{ $_[1] }[ 1 .. $#{ $_[1] } ] );
}
sub is_frozen {
{
last if ref( $_[1] ) ne 'ARRAY';
last if !$_[1][0]->isa( $_[0] );
last if @{ $_[1] } < 2;
return 1;
}
return 0;
}
sub to_string {
return _locale()->makevar( @{ $_[0] } );
}
sub to_en_string {
return _locale()->makethis_base( @{ $_[0] } );
}
sub clone_with_args {
return ( ref $_[0] )->new(
$_[0][0], #the phrase, currently stored in the object
@_[ 1 .. $#_ ], #the new args, supplied by the caller
);
}
sub to_list {
if ( !wantarray ) {
require Cpanel::Context;
Cpanel::Context::must_be_list();
}
return @{ $_[0] };
}
*TO_JSON = \&to_string;
my $_locale;
sub _locale {
return $_locale if $_locale;
local $@;
eval 'require Cpanel::Locale;' or do { ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
warn "Failed to load Cpanel::Locale; falling back to substitute. Error was: $@";
};
eval { $_locale = Cpanel::Locale->get_handle() };
return $_locale || bless {}, 'Cpanel::LocaleString::_Cpanel_Locale_unavailable';
}
sub _put_back {
no warnings 'redefine';
*TO_JSON = \&to_string;
return;
}
sub _to_list_ref {
return [ ref( $_[0] ), @{ $_[0] } ];
}
package Cpanel::LocaleString::_JSON_MODE;
sub new {
require Cpanel::Finally; # PPI USE OK - loaded only when needed
return $_[0]->SUPER::new( \&Cpanel::LocaleString::_put_back );
}
package Cpanel::LocaleString::_Cpanel_Locale_unavailable;
BEGIN {
*Cpanel::LocaleString::_Cpanel_Locale_unavailable::makethis_base = *Cpanel::LocaleString::_Cpanel_Locale_unavailable::makevar;
}
sub makevar {
my ( $self, $str, @maketext_opts ) = @_;
local ( $@, $! );
require Cpanel::Locale::Utils::Fallback;
return Cpanel::Locale::Utils::Fallback::interpolate_variables( $str, @maketext_opts );
}
1;
} # --- END Cpanel/LocaleString.pm
{ # --- BEGIN Cpanel/Errno.pm
package Cpanel::Errno;
use strict;
my %_err_name_cache;
sub get_name_for_errno_number {
my ($number) = @_;
if ( !$INC{'Errno.pm'} ) {
local ( $@, $! );
require Errno;
}
die 'need number!' if !length $number;
if ( !%_err_name_cache ) {
my $s = scalar keys %Errno::; # init iterator
foreach my $k ( sort keys %Errno:: ) {
if ( Errno->EXISTS($k) ) {
my $v = 'Errno'->can($k)->();
$_err_name_cache{$v} = $k;
}
}
}
return $_err_name_cache{$number};
}
1;
} # --- END Cpanel/Errno.pm
{ # --- BEGIN Cpanel/Config/Constants/Perl.pm
package Cpanel::Config::Constants::Perl;
use strict;
our $ABRT = 6;
our $ALRM = 14;
our $BUS = 7;
our $CHLD = 17;
our $CLD = 17;
our $CONT = 18;
our $FPE = 8;
our $HUP = 1;
our $ILL = 4;
our $INT = 2;
our $IO = 29;
our $IOT = 6;
our $KILL = 9;
our $NUM32 = 32;
our $NUM33 = 33;
our $NUM35 = 35;
our $NUM36 = 36;
our $NUM37 = 37;
our $NUM38 = 38;
our $NUM39 = 39;
our $NUM40 = 40;
our $NUM41 = 41;
our $NUM42 = 42;
our $NUM43 = 43;
our $NUM44 = 44;
our $NUM45 = 45;
our $NUM46 = 46;
our $NUM47 = 47;
our $NUM48 = 48;
our $NUM49 = 49;
our $NUM50 = 50;
our $NUM51 = 51;
our $NUM52 = 52;
our $NUM53 = 53;
our $NUM54 = 54;
our $NUM55 = 55;
our $NUM56 = 56;
our $NUM57 = 57;
our $NUM58 = 58;
our $NUM59 = 59;
our $NUM60 = 60;
our $NUM61 = 61;
our $NUM62 = 62;
our $NUM63 = 63;
our $PIPE = 13;
our $POLL = 29;
our $PROF = 27;
our $PWR = 30;
our $QUIT = 3;
our $RTMAX = 64;
our $RTMIN = 34;
our $SEGV = 11;
our $STKFLT = 16;
our $STOP = 19;
our $SYS = 31;
our $TERM = 15;
our $TRAP = 5;
our $TSTP = 20;
our $TTIN = 21;
our $TTOU = 22;
our $UNUSED = 31;
our $URG = 23;
our $USR1 = 10;
our $USR2 = 12;
our $VTALRM = 26;
our $WINCH = 28;
our $XCPU = 24;
our $XFSZ = 25;
our $ZERO = 0;
our %SIGNAL_NAME = qw(
0 ZERO
1 HUP
10 USR1
11 SEGV
12 USR2
13 PIPE
14 ALRM
15 TERM
16 STKFLT
17 CHLD
18 CONT
19 STOP
2 INT
20 TSTP
21 TTIN
22 TTOU
23 URG
24 XCPU
25 XFSZ
26 VTALRM
27 PROF
28 WINCH
29 IO
3 QUIT
30 PWR
31 SYS
32 NUM32
33 NUM33
34 RTMIN
35 NUM35
36 NUM36
37 NUM37
38 NUM38
39 NUM39
4 ILL
40 NUM40
41 NUM41
42 NUM42
43 NUM43
44 NUM44
45 NUM45
46 NUM46
47 NUM47
48 NUM48
49 NUM49
5 TRAP
50 NUM50
51 NUM51
52 NUM52
53 NUM53
54 NUM54
55 NUM55
56 NUM56
57 NUM57
58 NUM58
59 NUM59
6 ABRT
60 NUM60
61 NUM61
62 NUM62
63 NUM63
64 RTMAX
7 BUS
8 FPE
9 KILL
);
1;
} # --- END Cpanel/Config/Constants/Perl.pm
{ # --- BEGIN Cpanel/ChildErrorStringifier.pm
package Cpanel::ChildErrorStringifier;
use strict;
# use Cpanel::LocaleString (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
sub new {
my ( $class, $CHILD_ERROR, $PROGRAM_NAME ) = @_;
return bless { _CHILD_ERROR => $CHILD_ERROR, _PROGRAM_NAME => $PROGRAM_NAME }, $class;
}
sub CHILD_ERROR {
my ($self) = @_;
return $self->{'_CHILD_ERROR'};
}
sub error_code {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() >> 8;
}
sub error_name {
my ($self) = @_;
my $error_number = $self->error_code();
return '' if ( !defined $error_number ); # Can't index a hash with undef
require Cpanel::Errno;
return Cpanel::Errno::get_name_for_errno_number($error_number) || q<>;
}
sub dumped_core {
my ($self) = @_;
return $self->CHILD_ERROR() && ( $self->CHILD_ERROR() & 128 ) ? 1 : 0;
}
sub signal_code {
my ($self) = @_;
return if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() & 127;
}
sub signal_name {
my ($self) = @_;
require Cpanel::Config::Constants::Perl;
return $Cpanel::Config::Constants::Perl::SIGNAL_NAME{ $self->signal_code() };
}
sub exec_failed {
return $_[0]->{'_exec_failed'} ? 1 : 0;
}
sub program {
my ($self) = @_;
return $self->{'_PROGRAM_NAME'} || undef;
}
sub set_program {
my ( $self, $program ) = @_;
return ( $self->{'_PROGRAM_NAME'} = $program );
}
sub autopsy {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
my @localized_strings = (
$self->error_code() ? $self->_ERROR_PHRASE() : $self->_SIGNAL_PHRASE(),
$self->_core_dump_for_phrase_if_needed(),
$self->_additional_phrases_for_autopsy(),
);
return join ' ', map { $_->to_string() } @localized_strings;
}
sub terse_autopsy {
my ($self) = @_;
my $str;
if ( $self->signal_code() ) {
$str .= 'SIG' . $self->signal_name() . " (#" . $self->signal_code() . ")";
}
elsif ( my $code = $self->error_code() ) {
$str .= "exit $code";
}
else {
$str = 'OK';
}
if ( $self->dumped_core() ) {
$str .= ' (+core)';
}
return $str;
}
sub die_if_error {
my ($self) = @_;
my $err = $self->to_exception();
die $err if $err;
return $self;
}
sub to_exception {
my ($self) = @_;
if ( $self->signal_code() ) {
return Cpanel::Exception::create(
'ProcessFailed::Signal',
[
process_name => $self->program(),
signal_code => $self->signal_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
if ( $self->error_code() ) {
return Cpanel::Exception::create(
'ProcessFailed::Error',
[
process_name => $self->program(),
error_code => $self->error_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return undef;
}
sub _extra_error_args_for_die_if_error { }
sub _additional_phrases_for_autopsy { }
sub _core_dump_for_phrase_if_needed {
my ($self) = @_;
if ( $self->dumped_core() ) {
return Cpanel::LocaleString->new('The process dumped a core file.');
}
return;
}
sub _ERROR_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” reported error number [numf,_2] when it ended.', $self->program(), $self->error_code() );
}
return Cpanel::LocaleString->new( 'The subprocess reported error number [numf,_1] when it ended.', $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” ended prematurely because it received the “[_2]” ([_3]) signal.', $self->program(), $self->signal_name(), $self->signal_code() );
}
return Cpanel::LocaleString->new( 'The subprocess ended prematurely because it received the “[_1]” ([_2]) signal.', $self->signal_name(), $self->signal_code() );
}
1;
} # --- END Cpanel/ChildErrorStringifier.pm
{ # --- BEGIN Cpanel/FHUtils/OS.pm
package Cpanel::FHUtils::OS;
use strict;
use warnings;
no warnings 'once';
my $fileno;
sub is_os_filehandle {
local $@;
$fileno = eval { fileno $_[0] };
return ( defined $fileno ) && ( $fileno != -1 );
}
1;
} # --- END Cpanel/FHUtils/OS.pm
{ # --- BEGIN Cpanel/FHUtils/Blocking.pm
package Cpanel::FHUtils::Blocking;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::Autodie qw(fcntl); # perlpkg line 248
INIT { Cpanel::Autodie->import(qw{fcntl}); }
sub set_non_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) | $Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub set_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) & ~$Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub is_set_to_block {
return !( _get_fl_flags( $_[0] ) & $Cpanel::Fcntl::Constants::O_NONBLOCK ) ? 1 : 0;
}
sub _get_fl_flags {
return int Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_GETFL, 0 );
}
1;
} # --- END Cpanel/FHUtils/Blocking.pm
{ # --- BEGIN Cpanel/IO/Flush.pm
package Cpanel::IO::Flush;
use strict;
use warnings;
no warnings 'once';
use constant {
_EAGAIN => 11,
_EINTR => 4,
};
# use Cpanel::Exception (); # perlpkg line 211
use IO::SigGuard ();
sub write_all { ##no critic qw( RequireArgUnpacking )
my ( $fh, $timeout ) = @_; # $_[2] = payload
local ( $!, $^E );
my $offset = 0;
{
my $this_time = IO::SigGuard::syswrite( $fh, $_[2], length( $_[2] ), $offset );
if ($this_time) {
$offset += $this_time;
}
elsif ( $! == _EAGAIN() ) {
_wait_until_ready( $fh, $timeout );
}
else {
die Cpanel::Exception::create( 'IO::WriteError', [ error => $!, length => length( $_[2] ) - $offset ] );
}
redo if $offset < length( $_[2] );
}
return;
}
sub _wait_until_ready {
my ( $fh, $timeout ) = @_;
my $win;
vec( $win, fileno($fh), 1 ) = 1;
my $ready = select( undef, my $wout = $win, undef, $timeout );
if ( $ready == -1 ) {
redo if $! == _EINTR();
die Cpanel::Exception::create( 'IO::SelectError', [ error => $! ] );
}
elsif ( !$ready ) {
die Cpanel::Exception::create_raw( 'Timeout', 'write timeout!' );
}
return;
}
1;
} # --- END Cpanel/IO/Flush.pm
{ # --- BEGIN Cpanel/ReadMultipleFH.pm
package Cpanel::ReadMultipleFH;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::FHUtils::Blocking (); # perlpkg line 211
# use Cpanel::FHUtils::OS (); # perlpkg line 211
# use Cpanel::IO::Flush (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 600; #10 minutes
my $DEFAULT_READ_TIMEOUT = 0;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %opts ) = @_;
my %fh_buffer;
my %output;
my @fhs = @{ $opts{'filehandles'} };
my $read_input = '';
my $read_output = '';
my %fhmap;
my %is_os_filehandle;
for my $fh_buf_ar (@fhs) {
if ( UNIVERSAL::isa( $fh_buf_ar, 'GLOB' ) ) {
$fh_buf_ar = [$fh_buf_ar];
}
elsif ( !UNIVERSAL::isa( $fh_buf_ar, 'ARRAY' ) ) {
die 'items in “filehandles” must be either a filehandle or ARRAY';
}
my $fh = $fh_buf_ar->[0];
Cpanel::FHUtils::Blocking::set_non_blocking($fh);
$fhmap{ fileno($fh) } = $fh;
vec( $read_input, fileno($fh), 1 ) = 1;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'SCALAR' ) ) {
$fh_buffer{$fh} = $fh_buf_ar->[1];
}
else {
my $buf = q{};
$fh_buffer{$fh} = \$buf;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'GLOB' ) ) {
$output{$fh} = $fh_buf_ar->[1];
$is_os_filehandle{$fh} = Cpanel::FHUtils::OS::is_os_filehandle( $fh_buf_ar->[1] );
}
elsif ( defined $fh_buf_ar->[1] ) {
die '2nd value in “filehandles” array member must be undef, SCALAR, or GLOB!';
}
}
}
my $finished;
my $self = {
_fh_buffer => \%fh_buffer,
_finished => 0,
};
bless $self, $class;
my ( $nfound, $select_time_left, $select_timeout );
my $overall_timeout = defined $opts{'timeout'} ? $opts{'timeout'} : $DEFAULT_TIMEOUT;
my $read_timeout = defined $opts{'read_timeout'} ? $opts{'read_timeout'} : $DEFAULT_READ_TIMEOUT;
my $has_overall_timeout = $overall_timeout ? 1 : 0;
my $overall_time_left = $overall_timeout || undef;
READ_LOOP:
while (
!$finished && # has not finished
( !$has_overall_timeout || $overall_time_left > 0 ) # has not reached overall timeout
) {
$select_timeout = _get_shortest_timeout( $overall_time_left, $read_timeout );
( $nfound, $select_time_left ) = select( $read_output = $read_input, undef, undef, $select_timeout );
if ( !$nfound ) {
$self->{'_timed_out'} = ( $select_timeout == $read_timeout ) ? $read_timeout : $overall_timeout;
last;
}
elsif ( $nfound != -1 ) { # case 47309: If we get -1 it probably means we got interrupted by a signal
for my $fileno ( grep { vec( $read_output, $_, 1 ) } keys %fhmap ) {
my $fh = $fhmap{$fileno};
Cpanel::LoadFile::ReadFast::read_fast( $fh, ${ $fh_buffer{$fh} }, $CHUNK_SIZE, length ${ $fh_buffer{$fh} } ) or do {
delete $fhmap{$fileno};
$finished = !( scalar keys %fhmap );
last READ_LOOP if $finished;
vec( $read_input, $fileno, 1 ) = 0;
next;
};
if ( $output{$fh} ) {
my $payload_sr = \substr( ${ $fh_buffer{$fh} }, 0, length ${ $fh_buffer{$fh} }, q<> );
if ( $is_os_filehandle{$fh} ) {
Cpanel::IO::Flush::write_all( $output{$fh}, $read_timeout, $$payload_sr );
}
else {
print { $output{$fh} } $$payload_sr;
}
}
}
}
$overall_time_left -= ( $select_timeout - $select_time_left ) if $has_overall_timeout;
}
delete $fh_buffer{$_} for keys %output;
%fhmap = ();
$self->{'_finished'} = $finished;
if ( !$finished && defined $overall_time_left && $overall_time_left <= 0 ) {
$self->{'_timed_out'} = $overall_timeout;
}
return $self;
}
sub _get_shortest_timeout {
my ( $overall_time_left, $read_timeout ) = @_;
return undef if ( !$overall_time_left && !$read_timeout );
return $read_timeout if !defined $overall_time_left;
return ( !$read_timeout || $overall_time_left <= $read_timeout )
?
$overall_time_left
:
$read_timeout;
}
sub get_buffer {
return $_[0]->{'_fh_buffer'}{ $_[1] };
}
sub did_finish {
return $_[0]->{'_finished'} ? 1 : 0;
}
sub timed_out {
return defined $_[0]->{'_timed_out'} ? $_[0]->{'_timed_out'} : 0;
}
1;
} # --- END Cpanel/ReadMultipleFH.pm
{ # --- BEGIN Cpanel/ForkAsync.pm
package Cpanel::ForkAsync;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
my $DEFAULT_ERROR_CODE = 127; #EKEYEXPIRED
our $quiet = 0;
our $no_warn = 0;
sub do_in_child {
my ( $code, @args ) = @_;
local ( $!, $^E );
my $pid = fork();
die Cpanel::Exception::create( 'IO::ForkError', [ error => $! ] ) if !defined $pid;
if ( !$pid ) {
local $@;
if ( !eval { $code->(@args); 1 } ) {
my $err = $@;
my $io_err = 0 + $!;
_print($err) unless $quiet;
exit( $io_err || $DEFAULT_ERROR_CODE );
}
exit 0;
}
return $pid;
}
sub do_in_child_quiet {
my ( $code, @args ) = @_;
local $quiet = 1;
return do_in_child( $code, @args );
}
sub _print {
my ($msg) = @_;
warn $msg unless $no_warn;
print STDERR $msg;
return;
}
1;
} # --- END Cpanel/ForkAsync.pm
{ # --- BEGIN Cpanel/SafeRun/Object.pm
package Cpanel::SafeRun::Object;
use cPstrict;
no warnings 'once';
# use parent Cpanel::ChildErrorStringifier (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ChildErrorStringifier); }
BEGIN {
eval { require Proc::FastSpawn; };
}
use IO::SigGuard ();
# use Cpanel::Env (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::FHUtils::Autoflush (); # perlpkg line 211
# use Cpanel::FHUtils::OS (); # perlpkg line 211
# use Cpanel::ReadMultipleFH (); # perlpkg line 211
# use Cpanel::LoadModule (); # perlpkg line 211
# use Cpanel::LocaleString (); # perlpkg line 211
use constant _ENOENT => 2;
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 3600; # 1 hour
my $DEFAULT_READ_TIMEOUT = 0;
our $SAFEKILL_TIMEOUT = 1;
my @_allowed_env_vars_cache;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %OPTS ) = @_;
die "No “program”!" if !length $OPTS{'program'};
if ( !defined $OPTS{'timeout'} ) {
$OPTS{'timeout'} = $DEFAULT_TIMEOUT;
}
if ( !defined $OPTS{'read_timeout'} ) {
$OPTS{'read_timeout'} = $DEFAULT_READ_TIMEOUT;
}
if ( $OPTS{'program'} =~ tr{><*?[]`$()|;$\\\r\n\t }{} && !-e $OPTS{'program'} ) {
die Cpanel::Exception::create( 'InvalidParameter', 'A value of “[_1]” is invalid for “[_2]” as it does not permit the following characters: “[_3]”', [ $OPTS{'program'}, 'program', '><*?[]`$()|;$\\\\\r\\n\\t' ] );
}
my $args_ar = $OPTS{'args'} || [];
die "“args” must be an arrayref" if defined $args_ar && ref $args_ar ne 'ARRAY';
die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” argument is invalid.', ['logger'] ) if $OPTS{'logger'};
die "Undefined value given as argument! (@$args_ar)" if grep { !defined } @$args_ar;
my $pump_stdin_filehandle_into_child;
my ( %parent_read_fh, %child_write_fh );
my $merge_output_yn = $OPTS{'stdout'} && $OPTS{'stderr'} && ( $OPTS{'stdout'} eq $OPTS{'stderr'} );
local $!;
for my $handle_name (qw(stdout stderr)) {
my $custom_fh = $OPTS{$handle_name} && UNIVERSAL::isa( $OPTS{$handle_name}, 'GLOB' ) && $OPTS{$handle_name};
my $dupe_filehandle_will_work = $custom_fh && !tied(*$custom_fh) && ( fileno($custom_fh) > -1 );
if ( !$custom_fh && $OPTS{$handle_name} ) {
die "“$handle_name” must be a filehandle or undef, not $OPTS{$handle_name}";
}
if ($dupe_filehandle_will_work) {
if ( fileno($custom_fh) < 3 ) {
open my $copy, '>&', $custom_fh or die "dup($handle_name): $!";
$child_write_fh{$handle_name} = $copy;
}
else {
$child_write_fh{$handle_name} = $custom_fh;
}
}
elsif ( $merge_output_yn && $handle_name eq 'stderr' ) {
$parent_read_fh{'stderr'} = $parent_read_fh{'stdout'};
$child_write_fh{'stderr'} = $child_write_fh{'stdout'};
}
else {
pipe $parent_read_fh{$handle_name}, $child_write_fh{$handle_name} #
or die "pipe() failed: $!";
}
}
my ( $child_reads, $parent_writes );
my $close_child_reads = 0;
if ( !defined $OPTS{'stdin'} || !length $OPTS{'stdin'} ) {
open $child_reads, '<', '/dev/null' or die "open(<, /dev/null) failed: $!";
$close_child_reads = 1;
}
elsif ( UNIVERSAL::isa( $OPTS{'stdin'}, 'GLOB' ) ) {
my $fileno = fileno $OPTS{'stdin'};
if ( !defined $fileno || $fileno == -1 ) {
$pump_stdin_filehandle_into_child = 1;
}
else {
$child_reads = $OPTS{'stdin'};
}
}
if ( !$child_reads ) {
$close_child_reads = 1;
pipe( $child_reads, $parent_writes ) or die "pipe() failed: $!";
}
my $self = bless {
_program => $OPTS{'program'},
_args => $OPTS{'args'} || [],
}, $class;
local $SIG{'CHLD'} = 'DEFAULT';
my $exec_failed_message = "exec($OPTS{'program'}) failed:";
my $used_fastspawn = 0;
if (
$INC{'Proc/FastSpawn.pm'} # may not be available yet due to upcp.static or updatenow.static
&& !$OPTS{'before_exec'}
&& !$Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED # PPI NO PARSE - We not ever be set if its not loaded
) {
$used_fastspawn = 1;
my @env;
if ( !$OPTS{'keep_env'} ) {
if ( !@_allowed_env_vars_cache ) {
@_allowed_env_vars_cache = ( split( m{ }, Cpanel::Env::get_safe_env_vars() ) );
}
@env = map { exists $ENV{$_} ? ( $_ . '=' . ( $ENV{$_} // '' ) ) : () } @_allowed_env_vars_cache;
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
my ( $pw_user, $pw_homedir ) = ( getpwuid $> )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
push @env, "HOME=$homedir", "USER=$user"; # need to always be set since we start clean and don't have before_exec
push @env, "TMP=$homedir/tmp", "TEMP=$homedir/tmp" if !defined $ENV{'TMP'};
$self->{'_child_pid'} = Proc::FastSpawn::spawn_open3(
fileno($child_reads), # stdin
defined $child_write_fh{'stdout'} ? fileno( $child_write_fh{'stdout'} ) : -1, # stdout
defined $child_write_fh{'stderr'} ? fileno( $child_write_fh{'stderr'} ) : -1, # stderr
$OPTS{'program'}, # program
[ $OPTS{'program'}, @$args_ar ], # args
$OPTS{'keep_env'} ? () : \@env # env
);
if ( !$self->{_child_pid} ) {
$self->{'_CHILD_ERROR'} = $! << 8;
$self->{'_exec_failed'} = 1;
${ $self->{'_stdout'} } = '';
${ $self->{'_stderr'} } .= "$exec_failed_message $!";
}
}
else {
require Cpanel::ForkAsync;
$self->{'_child_pid'} = Cpanel::ForkAsync::do_in_child(
sub {
$SIG{'__DIE__'} = 'DEFAULT'; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- will never be unset
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or die "child close parent stdout failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or die "child close parent stderr failed: $!";
}
if ($parent_writes) {
close $parent_writes or die "close() failed: $!";
}
open( *STDIN, '<&=' . fileno $child_reads ) or die "open(STDIN) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
my $fileno_stdout = fileno \*STDOUT;
if ( $fileno_stdout != fileno( $child_write_fh{'stdout'} ) ) {
if ( $fileno_stdout != 1 ) {
close *STDOUT or die "close(STDOUT) failed: $!";
open( *STDOUT, '>>&=1' ) or die "open(STDOUT, '>>&=1') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( *STDOUT, '>>&=' . fileno $child_write_fh{'stdout'} ) or die "open(STDOUT) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
my $fileno_stderr = fileno \*STDERR;
if ( $fileno_stderr != fileno( $child_write_fh{'stderr'} ) ) {
if ( $fileno_stderr != 2 ) {
close *STDERR or die "close(STDOUT) failed: $!";
open( *STDERR, '>>&=2' ) or die "open(STDERR, '>>&=2') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( *STDERR, '>>&=' . fileno $child_write_fh{'stderr'} ) or die "open(STDERR) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
if ( !$OPTS{'keep_env'} ) {
Cpanel::Env::clean_env();
}
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
my $target_euid = "$>";
my $target_egid = ( split( m{ }, "$)" ) )[0];
Cpanel::AccessIds::ReducedPrivileges::_restore_privileges( 0, 0 ); # PPI NO PARSE -- we will never get here if ReducedPrivileges wasn't loaded
Cpanel::LoadModule::load_perl_module('Cpanel::Sys::Setsid::Fast') if !$INC{'Cpanel/Sys/Setsid/Fast.pm'};
Cpanel::Sys::Setsid::Fast::fast_setsid();
Cpanel::LoadModule::load_perl_module('Cpanel::AccessIds::SetUids') if !$INC{'Cpanel/AccessIds/SetUids.pm'};
Cpanel::AccessIds::SetUids::setuids( $target_euid, $target_egid );
}
if ( $OPTS{'before_exec'} ) {
$OPTS{'before_exec'}->();
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
Cpanel::LoadModule::load_perl_module('Cpanel::PwCache') if !$INC{'Cpanel/PwCache.pm'};
my ( $pw_user, $pw_homedir ) = ( Cpanel::PwCache::getpwuid_noshadow($>) )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
$ENV{'HOME'} = $homedir if !defined $ENV{'HOME'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'USER'} = $user if !defined $ENV{'USER'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'TMP'} = "$homedir/tmp" if !defined $ENV{'TMP'};
$ENV{'TEMP'} = "$homedir/tmp" if !defined $ENV{'TEMP'};
exec( $OPTS{'program'}, @$args_ar ) or die "$exec_failed_message $!";
}
);
}
if ( $OPTS{'after_fork'} ) {
$OPTS{'after_fork'}->( $self->{'_child_pid'} );
}
if ($close_child_reads) { #only close it if we opened it
close $child_reads or die "close() failed: $!";
}
if ( $parent_read_fh{'stdout'} ) {
close $child_write_fh{'stdout'} or die "close() failed: $!";
}
if ( !$merge_output_yn && $parent_read_fh{'stderr'} ) {
close $child_write_fh{'stderr'} or die "close() failed: $!";
}
if ($parent_writes) {
if ( ref $OPTS{'stdin'} eq 'CODE' ) {
$OPTS{'stdin'}->($parent_writes);
}
else {
local $SIG{'PIPE'} = 'IGNORE';
Cpanel::FHUtils::Autoflush::enable($parent_writes);
if ($pump_stdin_filehandle_into_child) {
my $buffer;
my $is_os_stdin = Cpanel::FHUtils::OS::is_os_filehandle( $OPTS{'stdin'} );
local $!;
if ($is_os_stdin) {
while ( IO::SigGuard::sysread( $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) ) {
$self->_write_buffer_to_fh( $buffer, $parent_writes );
}
}
else {
while ( read $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) {
$self->_write_buffer_to_fh( $buffer, $parent_writes );
}
}
if ($!) {
die Cpanel::Exception::create( 'IO::ReadError', 'The system failed to read up to [format_bytes,_1] from the filehandle that contains standard input for the process that is running the command “[_2]”. This failure happened because of an error: [_3]', [ $CHUNK_SIZE, "$OPTS{'program'} @$args_ar", "$!" ] );
}
}
else {
my $to_print_r = ( ref $OPTS{'stdin'} eq 'SCALAR' ) ? $OPTS{'stdin'} : \$OPTS{'stdin'};
if ( length $$to_print_r ) {
$self->_write_buffer_to_fh( $$to_print_r, $parent_writes );
}
}
}
close $parent_writes or warn "close() failed: $!";
}
my $reader;
my $err_obj;
my @filehandles = map { $parent_read_fh{$_} ? [ $parent_read_fh{$_}, $OPTS{$_} ] : () } qw( stdout stderr );
if (@filehandles) {
local $@;
eval {
$reader = Cpanel::ReadMultipleFH->new(
filehandles => \@filehandles,
timeout => $OPTS{'timeout'},
read_timeout => $OPTS{'read_timeout'},
);
};
$err_obj = $@;
}
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or warn "parent close(stdout) failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or warn "parent close(stderr) failed: $!";
}
if ($err_obj) {
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
die $err_obj;
}
elsif ($reader) {
if ( !$reader->did_finish() ) {
$self->{'_timed_out_after'} = $reader->timed_out();
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
}
$self->{"_stdout"} = $parent_read_fh{stdout} && $reader->get_buffer( $parent_read_fh{stdout} );
if ( !$self->{"_stderr"} ) {
$self->{"_stderr"} = $parent_read_fh{stderr} && $reader->get_buffer( $parent_read_fh{stderr} );
}
}
if ( !defined $self->{'_CHILD_ERROR'} ) {
local $?;
waitpid( $self->{'_child_pid'}, 0 ) if defined $self->{'_child_pid'};
$self->{'_CHILD_ERROR'} = $?;
if ( $self->{'_CHILD_ERROR'} ) {
$self->{'_exec_failed'} = 1;
}
}
if ( $used_fastspawn && $self->{'_CHILD_ERROR'} == 32512 ) {
$self->{'_CHILD_ERROR'} = _ENOENT() << 8;
$self->{'_exec_failed'} = 1;
${ $self->{'_stderr'} } .= "$exec_failed_message $!";
}
elsif ( !$used_fastspawn && $self->{'_stderr'} && $self->{'_CHILD_ERROR'} && ( $self->{'_CHILD_ERROR'} >> 8 ) == 2 && index( ${ $self->{'_stderr'} }, $exec_failed_message ) > -1 ) {
$self->{'_exec_failed'} = 1;
}
return $self;
}
sub _write_buffer_to_fh ( $self, $buffer, $fh ) {
while ( length $buffer ) {
my $wrote = IO::SigGuard::syswrite( $fh, $buffer ) or die $self->_write_error( \$buffer, $! );
substr( $buffer, 0, $wrote, q<> );
}
return;
}
sub new_or_die {
my ( $class, @args ) = @_;
return $class->new(@args)->die_if_error();
}
sub to_exception {
my ($self) = @_;
if ( $self->timed_out() ) {
return Cpanel::Exception::create(
'ProcessFailed::Timeout',
[
process_name => $self->program(),
( $self->child_pid() ? ( pid => $self->child_pid() ) : () ),
timeout => $self->timed_out(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return $self->SUPER::to_exception();
}
sub _extra_error_args_for_die_if_error {
my ($self) = @_;
return (
stdout => $self->{'_stdout'} ? $self->stdout() : '',
stderr => $self->{'_stderr'} ? $self->stderr() : '',
);
}
sub _safe_kill_child {
my ($self) = @_;
Cpanel::LoadModule::load_perl_module('Cpanel::Kill::Single');
return 'Cpanel::Kill::Single'->can('safekill_single_pid')->( $self->{'_child_pid'}, $SAFEKILL_TIMEOUT ); # One second to die
}
sub stdout_r {
if ( !$_[0]->{'_stdout'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDOUT output went to filehandle!");
}
return $_[0]->{'_stdout'};
}
sub _additional_phrases_for_autopsy {
if ( $_[0]->timed_out() ) {
return Cpanel::LocaleString->new( 'The system aborted the subprocess because it reached the timeout of [quant,_1,second,seconds].', $_[0]->timed_out() );
}
return;
}
sub stdout {
return ${ $_[0]->stdout_r() };
}
sub stderr_r {
if ( !$_[0]->{'_stderr'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDERR output went to filehandle!");
}
return $_[0]->{'_stderr'};
}
sub stderr {
return ${ $_[0]->stderr_r() };
}
sub child_pid {
return $_[0]->{'_child_pid'};
}
sub timed_out {
return $_[0]->{'_timed_out_after'};
}
sub program {
return $_[0]->{'_program'};
}
sub _program_with_args_str {
my $args_ar = $_[0]->{'_args'};
return $_[0]->{'_program'} . ( ( $args_ar && ref $args_ar && scalar @$args_ar ) ? " @$args_ar" : '' );
}
sub _ERROR_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) reported error number [_3] when it ended.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) ended prematurely because it received the “[_3]” ([_4]) signal.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->signal_name(), $self->signal_code() );
}
sub _write_error {
my ( $self, $buffer_sr, $OS_ERROR ) = @_;
my @cmd = ( $self->{'_program'}, @{ $self->{'_args'} } );
return Cpanel::Exception::create( 'IO::WriteError', 'The system failed to send [format_bytes,_1] to the process that is running the command “[_2]” because of an error: [_3]', [ length($$buffer_sr), "@cmd", $OS_ERROR ], { length => length($$buffer_sr), error => $OS_ERROR } );
}
1;
} # --- END Cpanel/SafeRun/Object.pm
package main;
# Copyright 2025 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::upcp;
BEGIN {
unshift @INC, q{/usr/local/cpanel};
# if we are being called with a compile check flag ( perl -c ), skip the begin block
# so we don't actually call upcp.static when just checking syntax and such is OK
return if $^C;
# static never gets --use-checked and should pass all the begin block checks
return if $0 =~ /\.static$/;
# let the '--use-check' instance compiled
if ( grep { $_ eq '--use-check' } @ARGV ) {
no warnings;
# dynamic definition of the INIT block
eval "INIT { exit(0); }";
return;
}
system("$0 --use-check >/dev/null 2>&1");
# compilation is ok with '--use-check', we will continue the non static version
return if $? == 0;
my $static = $0 . ".static";
if ( -f $static ) {
print STDERR "We determined that $0 had compilation issues..\n";
print STDERR "Trying to exec $static " . join( ' ', @ARGV ) . "\n";
exec( $^X, $static, @ARGV );
}
}
use cPstrict;
no warnings; ## no critic qw(ProhibitNoWarnings)
use Try::Tiny;
# use Cpanel::OS::All (); # PPI USE OK -- make sure Cpanel::OS is embedded # perlpkg line 289
# use Cpanel::HiRes ( preload => 'perl' ); # perlpkg line 289
# use Cpanel::Env (); # perlpkg line 289
# use Cpanel::Update::IsCron (); # perlpkg line 289
# use Cpanel::Update::Logger (); # perlpkg line 289
# use Cpanel::FileUtils::TouchFile (); # perlpkg line 289
# use Cpanel::LoadFile (); # perlpkg line 289
# use Cpanel::LoadModule (); # perlpkg line 289
# use Cpanel::Usage (); # perlpkg line 289
# use Cpanel::UPID (); # perlpkg line 289
use IO::Handle ();
use POSIX ();
# use Cpanel::Unix::PID::Tiny (); # perlpkg line 289
my $pidfile = '/var/run/upcp.pid';
my $lastlog = '/var/cpanel/updatelogs/last';
my $upcp_disallowed_path = '/root/.upcp_controlc_disallowed';
my $version_upgrade_file = '/usr/local/cpanel/upgrade_in_progress.txt';
our $logger; # Global for logger object.
our $logfile_path;
my $now;
my $forced = 0;
my $fromself = 0;
my $sync_requested = 0;
my $bg = 0;
my $rlimit_max = 1024;
my $from_version;
my $pbar_starting_point;
exit( upcp() || 0 ) unless caller();
sub usage {
print <&STDOUT" ) or die $!;
local $| = 1;
umask(0022);
$now = time();
setupenv();
unset_rlimits();
#############################################################################
# Record the arguments used when started, check for certain flags
my $update_is_available_exit_code = 42;
my @retain_argv = @ARGV;
foreach my $arg (@ARGV) {
if ( $arg =~ m/^--log=(.*)/ ) {
$logfile_path = $1;
}
elsif ( $arg eq '--fromself' ) {
$fromself = 1;
}
elsif ( $arg eq '--force' ) {
$forced = 1;
$ENV{'FORCEDCPUPDATE'} = 1;
}
elsif ( $arg eq '--sync' ) {
$sync_requested = 1;
}
elsif ( $arg eq '--bg' ) {
$bg = 1;
}
}
if ( $sync_requested && $forced ) {
print "FATAL: --force and --sync are mutually exclusive commands.\n";
print " Force is designed to update your installed version, regardless of whether it's already up to date.\n";
print " Sync is designed to update the version already installed, regardless of what is available.\n";
return 1;
}
if ( $> != 0 ) {
die "upcp must be run as root";
}
#############################################################################
# Make sure easyapache isn't already running
my $upid = Cpanel::Unix::PID::Tiny->new();
if ( $upid->is_pidfile_running('/var/run/easyapache.pid') ) {
print "EasyApache is currently running. Please wait for EasyApache to complete before running cPanel Update (upcp).\n";
return 1;
}
#############################################################################
# Make sure we aren't already running && make sure everyone knows we are running
my $curpid = $upid->get_pid_from_pidfile($pidfile) || 0;
if ( $curpid && $curpid != $$ && !$fromself && -e '/var/cpanel/upcpcheck' ) {
my $pidfile_mtime = ( stat($pidfile) )[9];
my $pidfile_age = ( time - $pidfile_mtime );
if ( $pidfile_age > 21600 ) { # Running for > 6 hours
_logger()->warning("previous PID ($curpid) has been running more than 6 hours. Killing processes.");
kill_upcp($curpid); # the pid_file_no_cleanup() will exit if it is still stuck after this
sleep 1; # Give the process group time to die.
}
elsif ( my $logpath = _determine_logfile_path_if_running($curpid) ) {
print _message_about_already_running( $curpid, $logpath ) . "\n";
return 1;
}
}
if ( $curpid && $curpid != $$ && !$upid->is_pidfile_running($pidfile) ) {
print "Stale PID file '$pidfile' (pid=$curpid)\n";
}
if ( !$fromself && !$upid->pid_file_no_cleanup($pidfile) ) {
print "process is already running\n";
return 1;
}
# to indicate re-entry into upcp
$pbar_starting_point = $fromself ? 17 : 0;
# record current version
$from_version = fetch_cpanel_version();
#############################################################################
# Set up the upcp log directory and files
setup_updatelogs();
#############################################################################
# Fork a child to the background. The child does all the heavy lifting and
# logs to a file; the parent just watches, reads, and parses the log file,
# displaying what it gets.
#
# Note that the parent reads the log in proper line-oriented -- and buffered!
# -- fashion. An earlier version of this script did raw sysread() calls here,
# and had to deal with all the mess that that entailed. The current approach
# reaps all the benefits of Perl's and Linux's significant file read
# optimizations without needing to re-invent any of them. The parent loop
# below becomes lean, mean, and even elegant.
#
# Note in particular that we do not need to explicitly deal with an
# end-of-file condition (other than avoiding using undefined data). For
# exiting the read loop we merely need to test that the child has expired,
# which in any case is the only situation that can cause an eof condition for
# us on the file the child is writing.
#
# Note, too, that the open() needs to be inside this loop, in case the child
# has not yet created the file.
if ( !$fromself ) {
# we need to be sure that log an pid are the current one when giving back the end
unlink $lastlog if $bg;
if ( my $updatepid = fork() ) {
$logfile_path ||= _determine_logfile_path_if_running($updatepid);
if ($logger) { # Close if logged about killing stale process.
$logger->{'brief'} = 1; # Don't be chatty about closing
$logger->close_log;
}
if ($bg) {
print _message_about_newly_started( $updatepid, $logfile_path ) . "\n";
my $progress;
select undef, undef, undef, .10;
while ( !-e $lastlog ) {
print '.';
select undef, undef, undef, .25;
$progress = 1;
}
print "\n" if $progress;
}
else {
monitor_upcp($updatepid);
}
return;
}
else {
$logfile_path ||= _determine_logfile_path_if_running($$);
}
}
local $0 = 'cPanel Update (upcp) - Slave';
open( my $RNULL, '<', '/dev/null' ) or die "Cannot open /dev/null: $!";
chdir '/';
_logger(); # Open the log file.
#############################################################################
# Set CPANEL_IS_CRON env var based on detection algorithm
my $cron_reason = set_cron_env();
$logger->info("Detected cron=$ENV{'CPANEL_IS_CRON'} ($cron_reason)");
my $set_cron_method = $ENV{'CPANEL_IS_CRON'} ? 'set_on' : 'set_off';
Cpanel::Update::IsCron->$set_cron_method();
my $openmax = POSIX::sysconf( POSIX::_SC_OPEN_MAX() );
if ( !$openmax ) { $openmax = 64; }
foreach my $i ( 0 .. $openmax ) { POSIX::close($i) unless $i == fileno( $logger->{'fh'} ); }
POSIX::setsid();
open( STDOUT, '>', '/dev/null' ) or warn $!;
open( STDERR, '>', '/dev/null' ) or warn $!;
$logger->update_pbar($pbar_starting_point);
##############################################################################
# Symlink /var/cpanel/updatelogs/last to the current log file
unlink $lastlog;
symlink( $logfile_path, $lastlog ) or $logger->error("Could not symlink $lastlog: $!");
#############################################################################
# now that we have sporked: update our pidfile and ensure it is removed
unlink $pidfile; # so that pid_file() won't see it as running.
if ( !$upid->pid_file($pidfile) ) { # re-verifies (i.e. upcp was not also started after the unlink() and here) and sets up cleanup of $pidfile for sporked proc
$logger->error("Could not update pidfile “$pidfile” with BG process: $!\n");
return 1;
}
# Assuming we didn't get re-executed from a upcp change after updatenow (!$fromself).
# If the file is still there from a failed run, remove it.
unlink($upcp_disallowed_path) if !$fromself && -f $upcp_disallowed_path;
# make sure that the pid file is going to be removed when killed by a signal
$SIG{INT} = $SIG{HUP} = $SIG{TERM} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
unlink $pidfile;
if ($logger) {
$logger->close_log;
$logger->open_log;
$logger->error("User hit ^C or killed the process ( pid file '$pidfile' removed ).");
$logger->close_log;
}
return;
};
#############################################################################
# Get variables needed for update
my $gotSigALRM = 0;
my $connecttimeout = 30;
my $liveconnect = 0;
my $connectedhost = q{};
my @HOST_IPs = ();
## Case 46528: license checks moved to updatenow and Cpanel::Update::Blocker
$logger->debug("Done getting update config variables..");
$logger->increment_pbar;
#############################################################################
# Run the preupcp hook
if ( -x '/usr/local/cpanel/scripts/preupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/preupcp");
system '/usr/local/cpanel/scripts/preupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=pre';
}
$logger->increment_pbar();
#############################################################################
# Check mtime on ourselves before sync
# This is the target for a goto in the case that the remote TIERS file is
# changed sometime during the execution of this upcp run. It prevents the
# need for a new script argument and re-exec.
STARTOVER:
my $updatenow_exit_code;
my $mtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
$logger->info( "mtime on upcp is $mtime (" . scalar( localtime($mtime) ) . ")" );
# * If no fromself arg is passed, it's either the first run from crontab or called manually.
# * --force is passed to updatenow, has no bearing on upcp itself.
# * Even if upcp is changed 3 times in a row during an update (fastest builds ever?), we
# would never actually update more than once unless the new upcp script changed the logic below
if ( !$fromself ) {
# run updatenow to sync everything
# updatenow expects --upcp to be passed or will error out
my @updatenow_args = ( '/usr/local/cpanel/scripts/updatenow', '--upcp', "--log=$logfile_path" );
# if --forced was received, pass it on to updatenow
if ($forced) { push( @updatenow_args, '--force' ); }
# if --sync was received, pass it on to updatenow. --force makes --sync meaningless.
if ( !$forced && $sync_requested ) { push( @updatenow_args, '--sync' ); }
# This is the point of no return, we are upgrading
# and its no longer abortable.
# set flag to disallow ^C during updatenow
Cpanel::FileUtils::TouchFile::touchfile($upcp_disallowed_path) or $logger->warn("Failed to create: $upcp_disallowed_path: $!");
# call updatenow, if we get a non-zero status, die.
my $exit_code = system(@updatenow_args);
$logger->increment_pbar(15);
if ( $exit_code != 0 ) {
$updatenow_exit_code = $exit_code;
my $signal = $exit_code % 256;
$exit_code = $exit_code >> 8;
analyze_and_report_error(
#success_msg => undef,
error_msg => "Running `@updatenow_args` failed, exited with code $exit_code (signal = $signal)",
type => 'upcp::UpdateNowFailed',
exit_status => $exit_code,
extra => [
'signal' => $signal,
'updatenow_args' => \@updatenow_args,
],
);
}
# get the new mtime and compare it, if upcp changed, let's run ourselves again.
# this should be a fairly rare occasion.
my $newmtime = ( stat('/usr/local/cpanel/scripts/upcp') )[9];
if ( $newmtime ne $mtime ) {
#----> Run our new self (and never come back).
$logger->info("New upcp detected, restarting ourself");
$logger->close_log();
exec '/usr/local/cpanel/scripts/upcp', @retain_argv, '--fromself', "--log=$logfile_path";
}
}
#############################################################################
# Run the maintenance script
my $last_logfile_position;
my $save_last_logfile_position = sub {
$last_logfile_position = int( qx{wc -l $logfile_path 2>/dev/null} || 0 );
};
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance script
my $exit_status;
my $version_change_happened = -e $version_upgrade_file;
if ($version_change_happened) {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--pre', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=30' );
}
else {
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--log=' . $logfile_path, '--pbar-start=20', '--pbar-stop=95' );
}
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Pre Maintenance completed successfully",
error_msg => "Pre Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Run post-sync cleanup only if updatenow did a sync
# Formerly run after layer2 did a sync.
if ($version_change_happened) {
# post_sync pbar range: 30%-55%
$logger->close_log(); # Yield the log to post_sync_cleanup
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the post_sync_cleanup script
my $post_exit_status = system( '/usr/local/cpanel/scripts/post_sync_cleanup', '--log=' . $logfile_path, '--pbar-start=30', '--pbar-stop=55' );
$logger->open_log; # reopen the log to continue writing messages
analyze_and_report_error(
success_msg => "Post-sync cleanup completed successfully",
error_msg => "Post-sync cleanup has ended, however it did not exit cleanly. Please check the logs for an indication of what happened",
type => 'upcp::PostSyncCleanupFailed',
exit_status => $post_exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
unlink $version_upgrade_file;
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
# Maintenance pbar range: 55-95%
$logger->close_log(); # Allow maintenance to write to the log
$save_last_logfile_position->(); # remember how many lines has the logfile before starting the maintenance --post
$exit_status = system( '/usr/local/cpanel/scripts/maintenance', '--post', '--log=' . $logfile_path, '--pbar-start=55', '--pbar-stop=95' );
$logger->open_log(); # Re-open the log now maintenance is done.
analyze_and_report_error(
success_msg => "Post Maintenance completed successfully",
error_msg => "Post Maintenance ended, however it did not exit cleanly ($exit_status). Please check the logs for an indication of what happened",
type => 'upcp::MaintenanceFailed',
exit_status => $exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Check for new version... used when updating to next LTS version
$logger->info("Polling updatenow to see if a newer version is available for upgrade");
$logger->close_log(); # Yield the log to updatenow
my $update_available = system( '/usr/local/cpanel/scripts/updatenow', "--log=$logfile_path", '--checkremoteversion' );
$logger->open_log; # reopen the log to continue writing messages
if ( !$sync_requested && $update_available && ( $update_available >> 8 ) == $update_is_available_exit_code ) {
$logger->info("\n\n/!\\ - Next LTS version available, restarting upcp and updating system. /!\\\n\n");
$fromself = 0;
goto STARTOVER;
}
}
else {
# Run sanity check scripts when no version change happened
# This allows certain install scripts marked as "sanity" to run during regular upcp runs
$logger->info("Running sanity check scripts");
$logger->close_log(); # Yield the log to bin/taskrun --sanity
$save_last_logfile_position->(); # remember how many lines has the logfile before starting sanity scripts
my $sanity_exit_status = run_sanity_install_scripts($logfile_path);
$logger->open_log; # reopen the log to continue writing messages
analyze_and_report_error(
success_msg => "Sanity check scripts completed successfully",
error_msg => "Sanity check scripts ended, however some did not exit cleanly. Please check the logs for an indication of what happened",
type => 'upcp::SanityInstallScriptsFailed',
exit_status => $sanity_exit_status,
logfile => $logfile_path,
last_logfile_position => $last_logfile_position,
);
# Perform the return we used to do if updatenow failed:
return ( $updatenow_exit_code >> 8 ) if defined $updatenow_exit_code && $updatenow_exit_code;
unlink($upcp_disallowed_path) if -f ($upcp_disallowed_path);
}
#############################################################################
# Run the post upcp hook
$logger->update_pbar(95);
if ( -x '/usr/local/cpanel/scripts/postupcp' ) {
$logger->info("Running /usr/local/cpanel/scripts/postupcp");
system '/usr/local/cpanel/scripts/postupcp';
}
if ( -x '/usr/local/cpanel/scripts/hook' ) {
$logger->info("Running Standardized hooks");
system '/usr/local/cpanel/scripts/hook', '--category=System', '--event=upcp', '--stage=post';
}
close($RNULL);
#############################################################################
# All done.
#############################################################################
$logger->update_pbar(100);
$logger->info( "\n\n\tcPanel update completed\n\n", 1 );
$logger->info("A log of this update is available at $logfile_path\n\n");
# this happens on exit so it shouldn't be necessary
$logger->info("Removing upcp pidfile");
unlink $pidfile if -f $pidfile || $logger->warn("Could not delete pidfile $pidfile : $!");
my $update_blocks_fname = '/var/cpanel/update_blocks.config';
if ( -s $update_blocks_fname ) {
$logger->warning("NOTE: A system upgrade was not possible due to the following blockers:\n");
if ( open( my $blocks_fh, '<', $update_blocks_fname ) ) {
while ( my $line = readline $blocks_fh ) {
my ( $level, $message ) = split /,/, $line, 2;
# Not using the level in the log, cause the logger can emit additional messages
# on some of the levels used (fatal emits an 'email message', etc)
# Remove URL from log output. Make sure message is defined.
if ($message) {
$message =~ s///ig;
$message =~ s{}{}ig;
}
$logger->warning( uc("[$level]") . " - $message" );
}
}
else {
$logger->warning("Unable to open blocks file! Please review '/var/cpanel/update_blocks.config' manually.");
}
}
else {
$logger->info("\n\nCompleted all updates\n\n");
}
$logger->close_log();
return 0;
}
#############################################################################
######[ Subroutines ]########################################################
#############################################################################
sub analyze_and_report_error {
my %info = @_;
my $type = $info{type} or die;
my $exit_status = $info{exit_status};
if ( $exit_status == 0 ) {
if ( defined $info{success_msg} ) {
$logger->info( $info{success_msg} );
}
return;
}
my $msg = $info{error_msg} or die;
my @extra;
if ( ref $info{extra} ) {
@extra = @{ $info{extra} };
}
my $logfile_content = Cpanel::LoadFile::loadfile_r($logfile_path);
# add events to the end of the error log
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::Logs::ErrorEvents") } ) ) {
my ($events) = Cpanel::Logs::ErrorEvents::extract_events_from_log( log => $logfile_content, after_line => $info{last_logfile_position} );
if ( $events && ref $events && scalar @$events ) {
my $events_str = join ', ', map { qq["$_"] } @$events;
$events_str = qq[The following events were logged: ${events_str}.];
$msg =~ s{(Please check)}{${events_str} $1} or $msg .= ' ' . $events_str;
}
}
$logger->error( $msg, 1 );
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::$type") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => $type,
'application' => $type,
'constructor_args' => [
'exit_code' => $exit_status,
'events_after_line' => $info{last_logfile_position},
@extra,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => $logfile_content, 'number_of_preview_lines' => 25 } ]
]
);
}
elsif (
!try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => 'cPanel & WHM update failure (upcp)',
'message' => $msg,
);
}
)
) {
$logger->error('Failed to send contact message');
}
return 1;
}
#############################################################################
sub kill_upcp {
my $pid = shift or die;
my $status = shift || 'hanging';
my $msg = shift || "/usr/local/cpanel/scripts/upcp was running as pid '$pid' for longer than 6 hours. cPanel will kill this process and run a new upcp in its place.";
# Attempt to notify admin of the kill.
if ( try( sub { Cpanel::LoadModule::load_perl_module("Cpanel::iContact::Class::upcp::Killed") } ) ) {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => 'upcp::Killed',
'application' => 'upcp::Killed',
'constructor_args' => [
'upcp_path' => '/usr/local/cpanel/scripts/upcp',
'pid' => $pid,
'status' => $status,
'attach_files' => [ { 'name' => 'update_log.txt', 'content' => Cpanel::LoadFile::loadfile_r($logfile_path), 'number_of_preview_lines' => 25 } ]
]
);
}
else {
try(
sub {
Cpanel::LoadModule::load_perl_module("Cpanel::iContact");
Cpanel::iContact::icontact(
'application' => 'upcp',
'subject' => "cPanel update $status",
'message' => $msg,
);
}
);
}
print "Sending kill signal to process group for $pid\n";
kill -1, $pid; # Kill the process group
for ( 1 .. 60 ) {
print "Waiting for processes to die\n";
waitpid( $pid, POSIX::WNOHANG() );
last if ( !kill( 0, $pid ) );
sleep 1;
}
if ( kill( 0, $pid ) ) {
print "Could not kill upcp nicely. Doing kill -9 $pid\n";
kill 9, $pid;
}
else {
print "Done!\n";
}
return;
}
#############################################################################
sub setupenv {
Cpanel::Env::clean_env();
delete $ENV{'DOCUMENT_ROOT'};
delete $ENV{'SERVER_SOFTWARE'};
if ( $ENV{'WHM50'} ) {
$ENV{'GATEWAY_INTERFACE'} = 'CGI/1.1';
}
( $ENV{'USER'}, $ENV{'HOME'} ) = ( getpwuid($>) )[ 0, 7 ];
$ENV{'PATH'} .= ':/sbin:/usr/sbin:/usr/bin:/bin:/usr/local/bin';
$ENV{'LANG'} = 'C';
$ENV{'LC_ALL'} = 'C';
}
sub unset_rlimits {
# This is required if upcp started running from a pre-1132
eval {
local $SIG{__DIE__};
require Cpanel::Rlimit;
Cpanel::Rlimit::set_rlimit_to_infinity();
};
# CPANEL-43452: Ensure consistent rlimit value between UI and CLI
eval {
local $SIG{__DIE__};
Cpanel::Rlimit::set_rlimit( $rlimit_max, [qw/NOFILE/] );
};
}
#############################################################################
sub setup_updatelogs {
return if ( -d '/var/cpanel/updatelogs' );
unlink('/var/cpanel/updatelogs');
mkdir( '/var/cpanel/updatelogs', 0700 );
}
sub set_cron_env {
# Do not override the env var if set.
return 'env var CPANEL_IS_CRON was present before this process started.' if ( defined $ENV{'CPANEL_IS_CRON'} );
if ( grep { $_ eq '--cron' } @ARGV ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'cron mode set from command line';
}
if ( $ARGV[0] eq 'manual' ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'manual flag passed on command line';
}
if ($forced) {
$ENV{'CPANEL_IS_CRON'} = 0;
return '--force passed on command line';
}
if ( -t STDOUT ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'Terminal detected';
}
if ( $ENV{'SSH_CLIENT'} ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'SSH connection detected';
}
# cron sets TERM=dumb
if ( $ENV{'TERM'} eq 'dumb' ) {
$ENV{'CPANEL_IS_CRON'} = 1;
return 'TERM detected as set to dumb';
}
# Check if parent is whostmgr
if ( readlink( '/proc/' . getppid() . '/exe' ) =~ m/whostmgrd/ ) {
$ENV{'CPANEL_IS_CRON'} = 0;
return 'parent process is whostmgrd';
}
# Default to cron enabled.
$ENV{'CPANEL_IS_CRON'} = 1;
return 'default';
}
#############################################################################
sub fetch_cpanel_version {
my $version;
my $version_file = '/usr/local/cpanel/version';
return if !-f $version_file;
my $fh;
local $/ = undef;
return if !open $fh, '<', $version_file;
$version = <$fh>;
close $fh;
$version =~ s/^\s+|\s+$//gs;
return $version;
}
#############################################################################
sub monitor_upcp {
my $updatepid = shift or die;
$0 = 'cPanel Update (upcp) - Master';
$SIG{INT} = $SIG{TERM} = sub {
print "User hit ^C\n";
if ( -f $upcp_disallowed_path ) {
print "Not allowing upcp slave to be killed during updatenow, just killing monitoring process.\n";
exit;
}
print "killing upcp\n";
kill_upcp( $updatepid, "aborted", "/usr/local/cpanel/scripts/upcp was aborted by the user hitting Ctrl-C." );
exit;
};
$SIG{HUP} = sub {
print "SIGHUP detected; closing monitoring process.\n";
print "The upcp slave has not been affected\n";
exit;
};
# Wait till the file shows up.
until ( -e $logfile_path ) {
select undef, undef, undef, .25; # sleep just a bit
}
# Wait till we're allowed to open it.
my $fh;
until ( defined $fh && fileno $fh ) {
$fh = IO::Handle->new();
if ( !open $fh, '<', $logfile_path ) {
undef $fh;
select undef, undef, undef, .25; # sleep just a bit
next;
}
}
# Read the file until the pid dies.
my $child_done = 0;
while (1) {
# Read all the available lines.
while (1) {
my $line = <$fh>;
last if ( !defined $line || $line eq '' );
print $line;
}
# Once the child is history, we need to do yet one more final read,
# on the off chance (however remote) that she has written one last
# hurrah after we last checked. Hence the following.
last if $child_done; # from prev. pass
$child_done = 1 if -1 == waitpid( $updatepid, 1 ); # and loop back for one more read
select undef, undef, undef, .25; # Yield idle time to the cpu
}
close $fh if $fh;
exit;
}
sub _logger {
return $logger if $logger;
$logger = Cpanel::Update::Logger->new( { 'logfile' => $logfile_path, 'stdout' => 1, 'log_level' => 'info' } );
# do not set the pbar in the constructor to do not display the 0 % in bg mode
$logger->{pbar} = $pbar_starting_point;
return $logger;
}
sub _determine_logfile_path_if_running ($pid) {
my $upid = Cpanel::UPID::get($pid);
return $upid ? "/var/cpanel/updatelogs/update.$upid.log" : undef;
}
sub run_sanity_install_scripts {
my ($logfile_path) = @_;
# Use bin/taskrun --sanity to execute sanity check install tasks
require Cpanel::SafeRun::Object;
print "Running sanity check scripts via taskrun\n";
my $saferun = Cpanel::SafeRun::Object->new_or_die(
program => '/usr/local/cpanel/bin/taskrun',
args => ['--sanity'],
);
my $stdout = $saferun->stdout() // '';
my $stderr = $saferun->stderr() // '';
my $exit_status = $saferun->CHILD_ERROR() >> 8;
# Print any output from taskrun
print $stdout if $stdout;
print STDERR $stderr if $stderr;
if ( $exit_status == 0 ) {
print "Sanity check scripts completed successfully\n";
}
else {
print "Sanity check scripts exited with status: $exit_status\n";
}
return $exit_status;
}
#----------------------------------------------------------------------
# HANDLE WITH CARE!! This string is parsed
# in at least one place. (cf. Cpanel::Update::Start)
sub _message_about_newly_started ( $updatepid, $logfile_path ) {
return "upcp is going into background mode (PID $updatepid). You can follow “$logfile_path” to watch its progress.";
}
#----------------------------------------------------------------------
# HANDLE WITH CARE!! This string is parsed
# in at least one place. (cf. Cpanel::Update::Start)
sub _message_about_already_running ( $curpid, $logpath ) {
return "cPanel Update (upcp) is already running. Please wait for the previous upcp (PID $curpid, log file “$logpath”) to complete, then try again. You can use the command 'ps --pid $curpid' to check if the process is running. You may wish to use '--force'.";
}
1;