Skip to content

Commit

Permalink
Merge pull request #479 from sloanebernstein/RE-514
Browse files Browse the repository at this point in the history
Check whether leapp can control the boot process
  • Loading branch information
toddr authored Jul 30, 2024
2 parents e3666d4 + 40111e0 commit 82e7996
Show file tree
Hide file tree
Showing 7 changed files with 480 additions and 107 deletions.
215 changes: 163 additions & 52 deletions elevate-cpanel
Original file line number Diff line number Diff line change
Expand Up @@ -3537,6 +3537,7 @@ EOS
use Cpanel::JSON ();
use Cpanel::Pkgr ();
use Cpanel::SafeRun::Simple ();
use Cpanel::SafeRun::Object ();

# use Elevate::Components::Base();
our @ISA;
Expand All @@ -3547,6 +3548,118 @@ EOS
use constant GRUB_EDITENV => '/usr/bin/grub2-editenv';
use constant GRUB_ENV_FILE => '/boot/grub2/grubenv';

use constant GRUBBY_PATH => '/usr/sbin/grubby';
use constant CMDLINE_PATH => '/proc/cmdline';

use constant EX_UNAVAILABLE => 69;

sub _call_grubby ( $self, @args ) {

my %opts = (
should_capture_output => 0,
should_hide_output => 0,
die_on_error => 0,
);

if ( ref $args[0] eq 'HASH' ) {
my %opt_args = %{ shift @args };
foreach my $key ( keys %opt_args ) {
$opts{$key} = $opt_args{$key};
}
}

unshift @args, GRUBBY_PATH;

return $opts{die_on_error} ? $self->ssystem_and_die(@args) : $self->ssystem( \%opts, @args );
}

sub _default_kernel ($self) {
return $self->_call_grubby( { should_capture_output => 1, should_hide_output => 1 }, '--default-kernel' )->{'stdout'}->[0] // '';
}

sub _persistent_id {
my $id = Elevate::StageFile::read_stage_file( 'bootloader_random_tag', '' );
return $id if $id;

$id = int( rand(100000) );
Elevate::StageFile::update_stage_file( { 'bootloader_random_tag', $id } );
return $id;
}

sub mark_cmdline ($self) {
my $arg = "elevate-" . _persistent_id;
INFO("Marking default boot entry with additional parameter \"$arg\".");

my $kernel_path = $self->_default_kernel;
$self->_call_grubby( { die_on_error => 1 }, "--update-kernel=$kernel_path", "--args=$arg" );

return;
}

sub _remove_but_dont_stop_service ($self) {

$self->cpev->service->disable();
$self->ssystem( '/usr/bin/systemctl', 'daemon-reload' );

return;
}

sub verify_cmdline ($self) {
if ( $self->cpev->should_run_leapp() ) {
my $arg = "elevate-" . _persistent_id;
INFO("Checking for \"$arg\" in booted kernel's command line...");

my $kernel_cmdline = eval { File::Slurper::read_binary(CMDLINE_PATH) } // '';
DEBUG( CMDLINE_PATH . " contains: $kernel_cmdline" );

my $detected = scalar grep { $_ eq $arg } split( ' ', $kernel_cmdline );
if ($detected) {
INFO("Parameter detected; restoring entry to original state.");
}
else {
ERROR("Parameter not detected. Attempt to upgrade is being aborted.");
}

my $kernel_path = $self->_default_kernel;
my $result = $self->_call_grubby( "--update-kernel=$kernel_path", "--remove-args=$arg" );
WARN("Unable to restore original command line. This should not cause problems but is unusual.") if $result != 0;

if ( !$detected ) {

my $stage = Elevate::Stages::get_stage();
my $pretty_distro_name = $self->cpev->upgrade_to_pretty_name();
my $msg = <<"EOS";
The elevation process failed during stage $stage.
Specifically, the script could not prove that the system has control over its
own boot process using the utilities the operating system provides.
For this reason, the elevation process has terminated before making any
irreversible changes.
You can check the error log by running:
$0
Before you can run the elevation process, you must provide for the ability for
the system to manipulate its own boot process. Then you can start the elevation
process anew:
$0 --start
EOS
Elevate::Notify::send_notification( qq[Failed to update to $pretty_distro_name] => $msg );

$self->cpev->do_cleanup(1);
$self->_remove_but_dont_stop_service();

exit EX_UNAVAILABLE; ## no critic(Cpanel::NoExitsFromSubroutines)
}
}

return;
}

sub pre_leapp ($self) {

$self->run_once('_update_grub2_workaround_if_needed'); # required part
Expand Down Expand Up @@ -7074,9 +7187,9 @@ EOS
keep_env => $opts{keep_env} // 0,
read_timeout => 0,
);
INFO(); # Buffer so they can more easily read the output.
INFO() unless $opts{should_hide_output}; # Buffer so they can more easily read the output.

$? = $sr->CHILD_ERROR; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- emulate return behavior of system()
$? = $sr->CHILD_ERROR; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- emulate return behavior of system()

if ( $opts{should_capture_output} ) {
$capture_output->{status} = $?;
Expand Down Expand Up @@ -8227,6 +8340,7 @@ use constant NOC_RECOMMENDATIONS_TOUCH_FILE => q[/var/cpanel/elevate-noc-recomme

use constant ACTION_REBOOT_NEEDED => 4242; # just one unique id
use constant ACTION_PAUSE_REQUESTED => 4243;
use constant ACTION_CONTINUE => 4244;

use constant PAUSE_ELEVATE_TOUCHFILE => q[/waiting_for_distro_upgrade];

Expand Down Expand Up @@ -8410,6 +8524,9 @@ sub monitor_upgrade ($self) {
}

sub start ($self) {

return 1 unless _sanity_check();

my $stage = Elevate::Stages::get_stage();
if ( $stage != 0 ) {
my $header;
Expand Down Expand Up @@ -8437,8 +8554,6 @@ sub start ($self) {

system touch => Elevate::Constants::LOG_FILE;

Elevate::Stages::bump_stage(); # init stage number to 1

# store the manual reboots flag
if ( $self->getopt('manual-reboots') ) {
WARN('Manual Reboot would be required between each stages');
Expand All @@ -8460,8 +8575,8 @@ sub start ($self) {
Elevate::StageFile::update_stage_file( { no_leapp => 1 } );
}

# prefer over running step1: so status and notifications are enabled
return $self->run_service_and_notify();
# This starts the service for us:
return $self->service->install();
}

sub _capture_env_variables ($self) {
Expand Down Expand Up @@ -8548,11 +8663,6 @@ sub continue_elevation ($self) {
Elevate::Logger::ERROR_nolog("Looks like no elevate process was started. Please consider running: /scripts/elevate-cpanel --start");
return;
}
elsif ( $stage == 1 ) {
Elevate::Logger::INFO_nolog("Continuing elevate process from stage 1 (service not setup yet)");
$self->run_stage_1();
return;
}

$self->_capture_env_variables(); # capture env before restart

Expand All @@ -8570,11 +8680,13 @@ sub continue_elevation ($self) {
}
}

sub do_cleanup ($self) {
sub do_cleanup ( $self, $handle_service_manually = 0 ) {

$self->service->remove;
if ( !$handle_service_manually ) {
$self->service->remove;
$self->ssystem( '/usr/bin/systemctl', 'daemon-reload' );
}

$self->ssystem( '/usr/bin/systemctl', 'daemon-reload' );
Elevate::StageFile::remove_stage_file();
unlink Elevate::Constants::PID_FILE;

Expand All @@ -8598,37 +8710,40 @@ sub run_service_and_notify ($self) { # FIXME: move to Elevate::Service - need
}

my $action_todo;
my $ok = eval {
$action_todo = $self->_run_service();
1;
};
do {
my $ok = eval {
$action_todo = $self->_run_service();
1;
};

$action_todo //= 0;
$action_todo //= 0;

if ($ok) {
if ($ok) {

# only notify a success when reaching the last stage
if ( $stage == VALID_STAGES ) {
Elevate::StageFile::update_stage_file( { status => q[success] } );
$self->_notify_success();
Elevate::StageFile::create_success_file();
}
# only notify a success when reaching the last stage
if ( $stage == VALID_STAGES ) {
Elevate::StageFile::update_stage_file( { status => q[success] } );
$self->_notify_success();
Elevate::StageFile::create_success_file();
}

if ( $action_todo == ACTION_PAUSE_REQUESTED ) {
Elevate::StageFile::update_stage_file( { status => q[paused] } );
}
elsif ( $action_todo == ACTION_REBOOT_NEEDED ) {
$self->reboot();
}

if ( $action_todo == ACTION_PAUSE_REQUESTED ) {
Elevate::StageFile::update_stage_file( { status => q[paused] } );
}
elsif ( $action_todo == ACTION_REBOOT_NEEDED ) {
$self->reboot();
}
}
else {
my $error = $@ // q[Unknown error];
else {
my $error = $@ // q[Unknown error];

Elevate::StageFile::update_stage_file( { status => q[failed] } );
$self->_notify_error($error);
Elevate::StageFile::update_stage_file( { status => q[failed] } );
$self->_notify_error($error);

LOGDIE($error);
}
LOGDIE($error);
}
} while ( $action_todo == ACTION_CONTINUE );

return $action_todo;
}
Expand Down Expand Up @@ -8731,7 +8846,7 @@ sub _run_service ($self) {

my $stage = Elevate::Stages::get_stage();

print_box( "Starting stage $stage of " . VALID_STAGES ) if $stage > 1;
print_box( "Starting stage $stage of " . VALID_STAGES );

$self->_wait_on_pause(); # handle pause when requested

Expand Down Expand Up @@ -8827,20 +8942,18 @@ responsible of the multiple reboots.

sub run_stage_1 ($self) {

# do not leave cruft behing when aborting
my $abort = sub {
$self->do_cleanup();
exit 42; ## no critic(Cpanel::NoExitsFromSubroutines) catching signals
};
Elevate::Marker::startup();

local $SIG{'INT'} = $abort;
local $SIG{'HUP'} = $abort;
Elevate::Motd->setup();

return 1 unless _sanity_check();
if ( !$self->should_run_leapp() ) {
Elevate::Stages::bump_stage();
return ACTION_CONTINUE;
}

print_box( "Starting stage 1 of " . VALID_STAGES . ": Installing " . $self->service->name . " service" );
$self->run_component_once( 'Grub2' => 'mark_cmdline' );

return $self->service->install();
return ACTION_REBOOT_NEEDED;
}

=head2 run_stage_2
Expand All @@ -8851,9 +8964,7 @@ Update the current distro packages then reboot.

sub run_stage_2 ($self) {

Elevate::Marker::startup();

Elevate::Motd->setup();
$self->run_component_once( 'Grub2' => 'verify_cmdline' );

$self->ssystem(qw{/usr/bin/yum clean all});
$self->ssystem_and_die(qw{/scripts/update-packages});
Expand Down
Loading

0 comments on commit 82e7996

Please sign in to comment.