diff -urN e-smith-base-5.8.0.old/root/sbin/e-smith/add_drive_to_raid e-smith-base-5.8.0/root/sbin/e-smith/add_drive_to_raid --- e-smith-base-5.8.0.old/root/sbin/e-smith/add_drive_to_raid 2008-03-26 16:49:00.000000000 +0000 +++ e-smith-base-5.8.0/root/sbin/e-smith/add_drive_to_raid 2020-12-23 17:45:39.455641041 +0000 @@ -31,133 +31,96 @@ GetOptions(\%options, 'f', 'force'); my $force = $options{f} || $options{force}; - my $newdev = $ARGV[0] || die "usage:\n\n\tadd_drive_to_raid [-f] dev\n\n"; - +my $target_drive = "/dev/$newdev"; my $raid = require "/sbin/e-smith/console-menu-items/manageRAID.pl"; +# Log STDOUT from this point on and return STDERR back to the console +my $pid = open(STDOUT, "|-"); +die gettext("Can't fork"), ": $!\n" unless defined $pid; + +unless ($pid) +{ + exec qw(/usr/bin/logger -p local1.info -t add_drive_to_raid); +} + +# Get dictionary of active md devices and sort by size my %devices = $raid->get_raid_details(); my @devices = sort { $devices{$a}{DeviceSize} <=> $devices{$b}{DeviceSize} } keys %devices; die "There are no RAID devices configured\n" unless $#devices >= 0; +# Get dictionary of all partitions from /proc/partitions my %partitions = $raid->get_partitions(); my @partitions; -my $minsize = 0; - -die "/dev/$newdev is not a block special device\n" unless -b "/dev/$newdev"; +die "$target_drive is not a block special device\n" unless -b $target_drive; +# Calculate min size of new disk to accomodate active md devices +my $minsize = 0; for my $dev (@devices) { - die "/dev/$newdev is already in use\n" if grep m#^$newdev$#, @{$devices{$dev}{UsedDisks}}; + die "$target_drive is already in use\n" if grep m#^$newdev$#, @{$devices{$dev}{UsedDisks}}; $minsize += $devices{$dev}{DeviceSize} + 65; } -die "/dev/$newdev is not large enough\n" unless $partitions{$newdev}{blocks} >= $minsize; - -die "/dev/$newdev already contains partitions\n" unless $force or ! grep m#^$newdev.+$#, keys %partitions; +die "$target_drive is not large enough\n" unless $partitions{$newdev}{blocks} >= $minsize; +die "$target_drive already contains partitions\n" unless $force or ! grep m#^$newdev.+$#, keys %partitions; -my $pid = open(STDERR, "|-"); -die gettext("Can't fork"), ": $!\n" unless defined $pid; - -unless ($pid) -{ - exec qw(/usr/bin/logger -p local1.info -t add_drive_to_raid); +# Find a healthy drive hosting our /boot partition to use as our template +my @srcdrives = qx(df /boot --output=source | grep /dev/ | xargs -r lsblk -lnsp | grep disk); +die "Unable to identify existing boot device - manual intervention required\n" unless (scalar @srcdrives) >= 1; +my ($source_drive) = $srcdrives[0] =~ /(\S+)/; +print "Using $source_drive as source partition template.\n"; + +# Check if it's MBR or GPT +my $pttype = qx(blkid -o value -s PTTYPE $source_drive); +chomp $pttype; +die "Unable to identify source partition table type for $source_drive\n" unless $pttype; +print "$source_drive partition table type is $pttype\n"; + +# Clear disk in preparation +print "Wiping $target_drive...\n"; +system("wipefs", "-a", $target_drive) == 0 + or die "Error clearing existing partition table on $target_drive\n"; + +# Copy new partition layout +print "Copying partition table from $source_drive to $target_drive...\n"; +if ($pttype eq 'dos') { + system("sfdisk -d $source_drive | sfdisk -qf --no-reread $target_drive") == 0 + or die "Error copying MBR partition table to $target_drive\n"; +} elsif ($pttype eq 'gpt') { + system("sgdisk", "-R", $target_drive, $source_drive) == 0 + or die "Error copying GPT partition table to $target_drive\n"; + system("sgdisk", "-G", $target_drive) == 0 + or die "Error randomising GUID on $target_drive\n"; +} else { + die "Couldn't interpret partition table type '$pttype' on $source_drive\n"; } -unless (open(OUTPUT, "-|")) -{ - my $boot = "*"; - my $pid = open(SFDISK, "|-"); - if ($pid) - { - # parent - for my $dev (@devices) - { - unless ($dev eq $devices[$#devices]) - { - print SFDISK ",", $devices{$dev}{DeviceSize} + 65, ",fd,$boot\n"; - } - else - { - print SFDISK ",,fd,$boot\n"; - } - $boot = "-"; - } - print SFDISK ",0\n" for (1..4); - close(SFDISK) || die "SFDISK kid exited $?\n"; - } - else - { - # child - exec("/sbin/sfdisk", "-uB", "--no-reread", "--force", "/dev/$newdev") - or die "can't exec program: $!\n"; - # NOTREACHED - } - - print "\nChecking partitions on /dev/$newdev...\n"; - sleep(3); - my $good; - my $cnt = 0; - do { - $cnt++; - $good = 1; - sleep(1); - - %partitions = $raid->get_partitions(); - @partitions = sort grep m#^$newdev.+$#, keys %partitions; - if ( $#devices == $#partitions ) { - foreach my $part ( 0..($#devices-1) ) { - $good &= $partitions{$partitions[$part]}{blocks} >= $devices{$devices[$part]}{DeviceSize} + 64; - $good &= $partitions{$partitions[$part]}{blocks} <= $devices{$devices[$part]}{DeviceSize} + 68; - $good &= -b "/dev/$partitions[$part]" || 0; - } - $good &= $partitions{$partitions[$#devices]}{blocks} >= $devices{$devices[$#devices]}{DeviceSize} + 64; - $good &= -b "/dev/$partitions[$#devices]" || 0; - } else { - $good = 0; - } - } until ( $good || $cnt > 60 ); - print "\n"; - - die "\nPartitions on /dev/$newdev aren't correct. Reboot may be necessary.\n" unless $good; - - sleep(3); - foreach my $part (0..$#devices) - { - print "Going to add /dev/$partitions[$part] to $devices[$part]\n"; - system("/sbin/mdadm", $devices[$part], "--add", "/dev/$partitions[$part]"); - sleep(2); - } - - print "\nWaiting for boot partition to sync before installing grub...\n"; - sleep(15); - - $pid = open(GRUB, "|-"); - if ($pid) - { - # parent - print GRUB "device (hd0) /dev/$newdev\n"; - print GRUB "root (hd0,0)\n"; - print GRUB "setup (hd0)\n"; - print GRUB "quit\n"; - close(GRUB) || die "GRUB kid exited $?\n"; - exit(0); - } - else - { - # child - exec("/sbin/grub", "--no-floppy", "--batch") - or die "can't exec program: $!\n"; - # NOTREACHED - } -} -while() +# Pause to sync +sleep(3); + +# Install GRUB +print "Installing GRUB on $target_drive...\n"; +system("grub2-install", "--recheck", $target_drive) == 0 + or warn "Warning - error installing GRUB to $target_drive\n"; + +# Loop through RAID devices and add the corresponding new partitions +my @srcparts; +my $srcpart; +my $tgtpart; +foreach my $part (0..$#devices) { - print "$_"; - next unless $_; - warn "$_"; + # Find the matching source drive partition and substitute the name + @srcparts = qx(mdadm -v --detail --scan $devices[$part]); + foreach my $s (@srcparts) {($srcpart) = $s =~ /devices=(\Q$source_drive\E\d+)/}; + $tgtpart = $srcpart =~ s/\Q$source_drive/$target_drive/r; + + print "Adding $tgtpart to $devices[$part]\n"; + system("/sbin/mdadm", $devices[$part], "--add", $tgtpart) == 0 + or die "Error adding $tgtpart to $devices[$part]"; } -close(OUTPUT) or die "Closing stdin pipe reported: $!\n"; +# Finished +print "Successfully added $target_drive to RAID!\n"; \ No newline at end of file diff -urN e-smith-base-5.8.0.old/root/sbin/e-smith/console-menu-items/manageRAID.pl e-smith-base-5.8.0/root/sbin/e-smith/console-menu-items/manageRAID.pl --- e-smith-base-5.8.0.old/root/sbin/e-smith/console-menu-items/manageRAID.pl 2020-12-23 17:42:19.991247057 +0000 +++ e-smith-base-5.8.0/root/sbin/e-smith/console-menu-items/manageRAID.pl 2020-12-23 17:46:12.587310812 +0000 @@ -3,10 +3,11 @@ use warnings; use esmith::console; use Locale::gettext; +use Taint::Util; use Data::Dumper; -use constant DEBUG_MANAGE_RAID => 1; +use constant DEBUG_MANAGE_RAID => 0; sub new { @@ -35,11 +36,12 @@ my ($rc, $choice); use POSIX qw(strftime); + + SCAN: my $today = strftime "%A %B %e, %Y %H:%M:%S", localtime; my $title = gettext("Disk redundancy status as of") . " " . $today, my $text = gettext("Current RAID status:") . "\n\n" . join("", get_raid_status()) . "\n\n"; - my %devices = get_raid_details(); warn $text if DEBUG_MANAGE_RAID; @@ -52,31 +54,40 @@ return; } + # Determine the status of each array my @unclean = (); my @recovering = (); + my @failed = (); my %used_disks = (); for my $dev (keys %devices) { - $used_disks{$_}++ for (@{$devices{$dev}{UsedDisks}}); + $used_disks{$_}++ for (@{$devices{$dev}{UsedDisks}}); - if ($devices{$dev}{State} =~ /recovering|resync/) - { + if ($devices{$dev}{FailedDevices} > 0) { + push @failed, "$dev => " . $devices{$dev}{FailedDevices}; + } + + if ($devices{$dev}{State} =~ /recovering|resync/) { push @recovering, "$dev => " . $devices{$dev}{State}; next; } next if ($devices{$dev}{State} =~ /^(clean|active)\s*$/); - push @unclean, "$dev => " . $devices{$dev}{State}; } - warn "used_disks: " . Dumper(\%used_disks) . "\n" if DEBUG_MANAGE_RAID; - warn "unclean: @unclean\n" if DEBUG_MANAGE_RAID; - warn "recovering: @recovering\n" if DEBUG_MANAGE_RAID; + warn "failed: @failed\n" if DEBUG_MANAGE_RAID; + warn "used_disks: " . Dumper(\%used_disks) . "\n" if DEBUG_MANAGE_RAID; + # Check for any spare disks we could add + my %free_disks = map {$_ => 1} get_disks(); + delete $free_disks{$_} for keys %used_disks; + warn "free_disks: " . Dumper(\%free_disks) . "\n" if DEBUG_MANAGE_RAID; + + # Report status and return if recovering if (scalar @recovering) { $text .= gettext("A RAID resynchronization is in progress."); @@ -84,29 +95,33 @@ return; } - unless (scalar @unclean) + # Report status and return if arrays are inconsistent + if ((scalar @unclean && scalar @unclean != scalar keys %devices) || (scalar @failed && scalar @failed != scalar keys %devices)) { - $text .= gettext("All RAID devices are in clean state"); + $text .= gettext("Only some of the RAID devices are unclean or contain failed disks.") . + "\n\n" . + gettext("Manual intervention may be required.") . "\n\n"; + ($rc, $choice) = $console->message_page(title => $title, text => $text); return; } - unless (scalar @unclean == scalar keys %devices) + # Report status if arrays are clean and continue if a spare disk is available or there's only one disk in the system + unless (scalar @unclean || scalar @failed) { - $text .= gettext("Only some of the RAID devices are unclean.") . - "\n\n" . - gettext("Manual intervention may be required.") . "\n\n"; - + $text .= gettext("All RAID devices are in a clean state."); + ($rc, $choice) = $console->message_page(title => $title, text => $text); + return unless scalar keys %free_disks > 0 || scalar keys %used_disks == 1; + } + + # Report status if all arrays are dirty and continue + if ((scalar @unclean && scalar @unclean == scalar keys %devices) || (scalar @failed && scalar @failed == scalar keys %devices)) + { + $text .= gettext("All RAID devices are in an unclean state or contain failed disks."); ($rc, $choice) = $console->message_page(title => $title, text => $text); - return; } - my %free_disks = map {$_ => 1} get_disks(); - - delete $free_disks{$_} for keys %used_disks; - - warn "free_disks: " . Dumper(\%free_disks) . "\n" if DEBUG_MANAGE_RAID; - + # Summarise disk assignments my $disk_status = gettext("Current disk status:") . "\n\n"; $disk_status .= gettext("Installed disks") . ": " . join(" ", get_disks()) . "\n"; @@ -115,50 +130,64 @@ $disk_status .= gettext("Free disks") . ": " . join(" ", keys %free_disks) . "\n"; - if (scalar keys %used_disks == 1 and scalar keys %free_disks == 0) + # Spare disk scenarios + # Scenario 1 - single disk or degraded array with no spare - warn + if ((scalar @unclean || scalar @failed || scalar keys %used_disks == 1) && scalar keys %free_disks == 0) { - $text .= gettext("Your system only has a single disk drive installed or is using hardware mirroring. If you would like to enable software mirroring, please shut down, install a second disk drive (of the same capacity) and then return to this screen."); + $text = $disk_status . + "\n\n" . + gettext("To ensure continued redundancy, please shut down, install another drive of the same capacity and then return to this screen."); ($rc, $choice) = $console->message_page(title => $title, text => $text); return; } - unless (scalar keys %used_disks == 1 and - scalar keys %free_disks == scalar keys %used_disks) + # Scenario 2 - no spares and not degraded so something has gone wrong + if (scalar keys %free_disks == 0) { - $text .= gettext("The free and used disk count must equal one.") . - "\n\n" . - gettext("Manual intervention may be required.") . "\n\n" . - $disk_status; + $text = $disk_status . + "\n\n" . + gettext("Your RAID devices are in an inconsistent state, and no spare drives were detected. You may need to manually remove a failed drive from your arrays using mdadm."); ($rc, $choice) = $console->message_page(title => $title, text => $text); return; } - my @cmd = ("/sbin/e-smith/add_drive_to_raid", "-f", join("", keys %free_disks)); + # Scenario 3 - multiple spares + if (scalar keys %free_disks > 1) + { + $text = $disk_status . + "\n\n" . + gettext("Multiple spare drives have been detected. This utility can only add one drive at a time. Please either shut down and remove all but one of your spare drives, or configure your array manually."); + ($rc, $choice) = $console->message_page(title => $title, text => $text); + return; + } + + # Scenario 4 - single spare ready to add $text = $disk_status . "\n\n" . gettext("There is an unused disk drive in your system. Do you want to add it to the existing RAID array(s)?") . - "\n\n" . - gettext("WARNING: ALL DATA ON THE NEW DISK WILL BE DESTROYED!") . - "\n" - ; + "\n\n" . + gettext("WARNING: ALL DATA ON THE NEW DISK WILL BE DESTROYED!") . + "\n"; ($rc, $choice) = $console->yesno_page(title => $title, text => $text, defaultno => 1); return unless ($rc == 0); + my @cmd = ("/sbin/e-smith/add_drive_to_raid", "-f", join("", keys %free_disks)); my $cmd_out = qx( @cmd 2>&1 ); - unless ($? == 0) - { - $text = gettext("The command failed:") . " @cmd" . - "\n\n" . $cmd_out . "\n\n" . - gettext("This configuration is not yet fully supported in these screens."); + untaint $cmd_out; - ($rc, $choice) = $console->message_page(title => $title, text => $text); - return; + if ($? == 0) { + $text = "\nSuccessfully added /dev/" . join("", keys %free_disks) . " to RAID!"; + } else { + $text = gettext("The command failed:") . " @cmd" . + "\n\n" . $cmd_out . "\n\n"; } - + + ($rc, $choice) = $console->message_page(title => $title, text => $text); + goto SCAN; } sub get_raid_status @@ -185,7 +214,7 @@ while () { - push @devices, $1 if ( m:ARRAY (/dev/md/\d+): ) + push @devices, $1 if ( m:ARRAY (/dev/md/\w+): ) } close MDADM; @@ -203,7 +232,7 @@ my ($key, $value) = ($1, $2); $key =~ s/\s//g; - # Allow for different mdadm output formats for DeviceSize + # Allow for different mdadm output formats for DeviceSize $key =~ s/UsedDevSize/DeviceSize/; $devices{$dev}{$key} = $value;