mirror of
https://github.com/webmin/webmin.git
synced 2026-06-05 12:50:23 +01:00
217 lines
7.2 KiB
Perl
Executable File
217 lines
7.2 KiB
Perl
Executable File
#!/usr/local/bin/perl
|
|
# Save common GRUB 2 defaults.
|
|
|
|
use strict;
|
|
use warnings;
|
|
require './grub2-lib.pl'; ## no critic
|
|
|
|
our (%in, %text);
|
|
|
|
&ReadParse();
|
|
&error_setup($text{'defaults_err'});
|
|
my %access = &get_module_acl();
|
|
&error("$text{'eacl_np'} $text{'eacl_pedit'}") if (!$access{'edit'});
|
|
|
|
# Capture both the current defaults file and generated entries for validation.
|
|
my $current = &read_grub_defaults();
|
|
my $current_values = $current->{'values'};
|
|
my @entries = &grub2_boot_entries();
|
|
my %updates;
|
|
# Default entries must come from the selector, except for the preserved value.
|
|
$in{'default'} = '' if (!defined($in{'default'}));
|
|
$in{'default'} =~ /[\r\n\0]/ && &error($text{'defaults_edefault'});
|
|
&error($text{'defaults_edefault_choice'})
|
|
if (!&valid_default_entry_value(
|
|
$in{'default'}, $current_values->{'GRUB_DEFAULT'}, \@entries));
|
|
$updates{'GRUB_DEFAULT'} = $in{'default'} if ($in{'default'} ne '');
|
|
|
|
my %styles = map { $_ => 1 } ('', qw(menu hidden countdown));
|
|
&error($text{'defaults_etimeout_style'})
|
|
if (!defined($in{'timeout_style'}) || !$styles{$in{'timeout_style'}});
|
|
$updates{'GRUB_TIMEOUT_STYLE'} =
|
|
$in{'timeout_style'} eq '' ? undef : $in{'timeout_style'};
|
|
|
|
# Empty timeout removes the local override; otherwise GRUB accepts -1 or more.
|
|
if (defined($in{'timeout'}) && $in{'timeout'} ne '') {
|
|
$in{'timeout'} =~ /^-?\d+\z/ && $in{'timeout'} >= -1
|
|
|| &error($text{'defaults_etimeout'});
|
|
$updates{'GRUB_TIMEOUT'} = $in{'timeout'};
|
|
}
|
|
else {
|
|
$updates{'GRUB_TIMEOUT'} = undef;
|
|
}
|
|
|
|
# Kernel command-line values are single-line shell assignment values.
|
|
foreach my $field (
|
|
[ 'cmdline_default', 'GRUB_CMDLINE_LINUX_DEFAULT',
|
|
$text{'defaults_cmdline_default'} ],
|
|
[ 'cmdline', 'GRUB_CMDLINE_LINUX', $text{'defaults_cmdline'} ],
|
|
)
|
|
{
|
|
my ($input, $key, $label) = @$field;
|
|
$in{$input} = '' if (!defined($in{$input}));
|
|
$in{$input} =~ /[\r\n\0]/ && &error(&text('defaults_ecmdline', $label));
|
|
$updates{$key} = $in{$input} eq '' ? undef : $in{$input};
|
|
}
|
|
|
|
# Boolean GRUB defaults keep their tri-state UI: inherit, true, or false.
|
|
foreach my $field (
|
|
[ 'disable_recovery', 'GRUB_DISABLE_RECOVERY',
|
|
$text{'defaults_disable_recovery'} ],
|
|
[ 'disable_os_prober', 'GRUB_DISABLE_OS_PROBER',
|
|
$text{'defaults_disable_os_prober'} ],
|
|
)
|
|
{
|
|
my ($input, $key, $label) = @$field;
|
|
$in{$input} = '' if (!defined($in{$input}));
|
|
if ($in{$input} eq '') {
|
|
$updates{$key} = undef;
|
|
}
|
|
elsif ($in{$input} =~ /^(true|false)\z/) {
|
|
$updates{$key} = $in{$input};
|
|
}
|
|
else {
|
|
&error(&text('defaults_ebool', $label));
|
|
}
|
|
}
|
|
|
|
my $err = &save_grub_defaults_values(\%updates);
|
|
&error(&text('manual_evalidate', $err)) if ($err);
|
|
|
|
# BLS rescue entries are real files, so restore them before changing BLS args.
|
|
my $disable_bls_rescue =
|
|
(($updates{'GRUB_DISABLE_RECOVERY'} || '') eq 'true') ? 1 : 0;
|
|
my $can_handle_bls_rescue =
|
|
&grub2_has_bls_rescue_entries(\@entries) ||
|
|
&grub2_disabled_bls_rescue_files();
|
|
my $bls_rescue_err;
|
|
if (!$disable_bls_rescue) {
|
|
$bls_rescue_err = &grub2_set_bls_rescue_disabled(0);
|
|
# Refresh entry data after restores so grubby sees current BLS files.
|
|
@entries = &grub2_boot_entries() if (!$bls_rescue_err);
|
|
}
|
|
|
|
# On BLS systems, grubby applies kernel arg deltas to existing boot entries.
|
|
my %bls_args_updated;
|
|
my $bls_err;
|
|
if (&grub2_bls_update_available(\@entries)) {
|
|
my @locked_bls_files = &lock_bls_update_files(
|
|
$current_values, \%updates, \@entries);
|
|
($bls_err, %bls_args_updated) = &update_bls_kernel_args(
|
|
$current_values, \%updates, \@entries);
|
|
&unlock_bls_update_files(@locked_bls_files);
|
|
}
|
|
if (!$bls_err && !$bls_rescue_err && $disable_bls_rescue) {
|
|
# Hide rescue files after grubby has had a chance to update normal entries.
|
|
$bls_rescue_err = &grub2_set_bls_rescue_disabled(1, \@entries);
|
|
}
|
|
$bls_args_updated{'GRUB_DISABLE_RECOVERY'} = 1
|
|
if (!$bls_rescue_err && $can_handle_bls_rescue &&
|
|
!&grub2_has_non_bls_recovery_entries(\@entries));
|
|
&grub2_mark_regenerate_needed()
|
|
if (&grub2_defaults_updates_need_generate(
|
|
$current_values, \%updates, \%bls_args_updated));
|
|
&webmin_log("defaults");
|
|
&error($bls_err) if ($bls_err);
|
|
&error($bls_rescue_err) if ($bls_rescue_err);
|
|
&redirect("index.cgi");
|
|
|
|
# valid_default_entry_value(value, current-value, &entries)
|
|
# Returns true if a posted default entry came from the detected selector.
|
|
sub valid_default_entry_value
|
|
{
|
|
my ($value, $current, $entries) = @_;
|
|
return 1 if (!defined($value) || $value eq '');
|
|
$current = '0' if (!defined($current) || $current eq '');
|
|
return 1 if ($value eq 'saved');
|
|
return 1 if ($value eq $current);
|
|
foreach my $entry (@$entries) {
|
|
my $selector = &grub2_entry_selector($entry);
|
|
return 1 if (defined($selector) && $selector eq $value);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
# update_bls_kernel_args(&old-values, &updates, &entries)
|
|
# Applies changed kernel option defaults to existing BLS entries.
|
|
sub update_bls_kernel_args
|
|
{
|
|
my ($old_values, $updates, $entries) = @_;
|
|
my %updated;
|
|
foreach my $field (
|
|
[ 'GRUB_CMDLINE_LINUX', undef ],
|
|
[ 'GRUB_CMDLINE_LINUX_DEFAULT',
|
|
[ &grub2_bls_kernel_arg_targets($entries, 0) ] ],
|
|
)
|
|
{
|
|
my ($key, $targets) = @$field;
|
|
my ($remove, $add) =
|
|
&grub2_kernel_args_delta($old_values->{$key}, $updates->{$key});
|
|
# No delta means this defaults field can be ignored for BLS updates.
|
|
next if (!@$remove && !@$add);
|
|
next if (defined($targets) && !@$targets);
|
|
my $err = &grub2_update_bls_kernel_args(
|
|
$old_values->{$key}, $updates->{$key}, $targets);
|
|
if ($err) {
|
|
# A partial grubby failure falls back to regeneration when needed.
|
|
&grub2_mark_regenerate_needed()
|
|
if (&grub2_defaults_updates_need_generate(
|
|
$old_values, $updates, \%updated));
|
|
return ($err, %updated);
|
|
}
|
|
$updated{$key} = 1;
|
|
}
|
|
return (undef, %updated);
|
|
}
|
|
|
|
# lock_bls_update_files(&old-values, &updates, &entries)
|
|
# Locks files that grubby may change so Webmin can diff them.
|
|
sub lock_bls_update_files
|
|
{
|
|
my ($old_values, $updates, $entries) = @_;
|
|
my %lock_all = &kernel_args_changed(
|
|
$old_values->{'GRUB_CMDLINE_LINUX'}, $updates->{'GRUB_CMDLINE_LINUX'}) ?
|
|
( all => 1 ) : ();
|
|
my %lock_default = &kernel_args_changed(
|
|
$old_values->{'GRUB_CMDLINE_LINUX_DEFAULT'},
|
|
$updates->{'GRUB_CMDLINE_LINUX_DEFAULT'}) ? ( default => 1 ) : ();
|
|
return () if (!%lock_all && !%lock_default);
|
|
my (@locked, %seen);
|
|
# kernelopts-based entries may be updated through grubenv instead of .conf files.
|
|
if (grep { &grub2_entry_uses_kernelopts($_) } @$entries) {
|
|
my $file = &grub2_config_value('grubenv_file') || '';
|
|
if ($file ne '' && -e $file && !$seen{$file}++) {
|
|
&lock_file($file);
|
|
push(@locked, $file);
|
|
}
|
|
}
|
|
foreach my $entry (@$entries) {
|
|
next if (($entry->{'source'} || '') ne 'bls');
|
|
# Default-only changes skip rescue entries, matching the edit form wording.
|
|
next if (!%lock_all && &grub2_entry_is_bls_rescue($entry));
|
|
my $file = $entry->{'file'};
|
|
next if (!defined($file) || $file eq '' || $seen{$file}++);
|
|
&lock_file($file);
|
|
push(@locked, $file);
|
|
}
|
|
return @locked;
|
|
}
|
|
|
|
# unlock_bls_update_files(files...)
|
|
# Unlocks files after grubby has run.
|
|
sub unlock_bls_update_files
|
|
{
|
|
foreach my $file (reverse @_) {
|
|
&unlock_file($file);
|
|
}
|
|
}
|
|
|
|
# kernel_args_changed(old-args, new-args)
|
|
# Returns true when a kernel-args delta would be applied.
|
|
sub kernel_args_changed
|
|
{
|
|
my ($old_args, $new_args) = @_;
|
|
my ($remove, $add) = &grub2_kernel_args_delta($old_args, $new_args);
|
|
return @$remove || @$add;
|
|
}
|