1 |
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 |
2 |
--- e-smith-base-5.8.0.old/root/sbin/e-smith/add_drive_to_raid 2008-03-26 16:49:00.000000000 +0000 |
3 |
+++ e-smith-base-5.8.0/root/sbin/e-smith/add_drive_to_raid 2020-12-23 17:45:39.455641041 +0000 |
4 |
@@ -31,133 +31,96 @@ |
5 |
GetOptions(\%options, 'f', 'force'); |
6 |
|
7 |
my $force = $options{f} || $options{force}; |
8 |
- |
9 |
my $newdev = $ARGV[0] || die "usage:\n\n\tadd_drive_to_raid [-f] dev\n\n"; |
10 |
- |
11 |
+my $target_drive = "/dev/$newdev"; |
12 |
my $raid = require "/sbin/e-smith/console-menu-items/manageRAID.pl"; |
13 |
|
14 |
+# Log STDOUT from this point on and return STDERR back to the console |
15 |
+my $pid = open(STDOUT, "|-"); |
16 |
+die gettext("Can't fork"), ": $!\n" unless defined $pid; |
17 |
+ |
18 |
+unless ($pid) |
19 |
+{ |
20 |
+ exec qw(/usr/bin/logger -p local1.info -t add_drive_to_raid); |
21 |
+} |
22 |
+ |
23 |
+# Get dictionary of active md devices and sort by size |
24 |
my %devices = $raid->get_raid_details(); |
25 |
my @devices = sort { $devices{$a}{DeviceSize} <=> $devices{$b}{DeviceSize} } keys %devices; |
26 |
|
27 |
die "There are no RAID devices configured\n" unless $#devices >= 0; |
28 |
|
29 |
+# Get dictionary of all partitions from /proc/partitions |
30 |
my %partitions = $raid->get_partitions(); |
31 |
my @partitions; |
32 |
|
33 |
-my $minsize = 0; |
34 |
- |
35 |
-die "/dev/$newdev is not a block special device\n" unless -b "/dev/$newdev"; |
36 |
+die "$target_drive is not a block special device\n" unless -b $target_drive; |
37 |
|
38 |
+# Calculate min size of new disk to accomodate active md devices |
39 |
+my $minsize = 0; |
40 |
for my $dev (@devices) |
41 |
{ |
42 |
- die "/dev/$newdev is already in use\n" if grep m#^$newdev$#, @{$devices{$dev}{UsedDisks}}; |
43 |
+ die "$target_drive is already in use\n" if grep m#^$newdev$#, @{$devices{$dev}{UsedDisks}}; |
44 |
$minsize += $devices{$dev}{DeviceSize} + 65; |
45 |
} |
46 |
|
47 |
-die "/dev/$newdev is not large enough\n" unless $partitions{$newdev}{blocks} >= $minsize; |
48 |
- |
49 |
-die "/dev/$newdev already contains partitions\n" unless $force or ! grep m#^$newdev.+$#, keys %partitions; |
50 |
+die "$target_drive is not large enough\n" unless $partitions{$newdev}{blocks} >= $minsize; |
51 |
+die "$target_drive already contains partitions\n" unless $force or ! grep m#^$newdev.+$#, keys %partitions; |
52 |
|
53 |
-my $pid = open(STDERR, "|-"); |
54 |
-die gettext("Can't fork"), ": $!\n" unless defined $pid; |
55 |
- |
56 |
-unless ($pid) |
57 |
-{ |
58 |
- exec qw(/usr/bin/logger -p local1.info -t add_drive_to_raid); |
59 |
+# Find a healthy drive hosting our /boot partition to use as our template |
60 |
+my @srcdrives = qx(df /boot --output=source | grep /dev/ | xargs -r lsblk -lnsp | grep disk); |
61 |
+die "Unable to identify existing boot device - manual intervention required\n" unless (scalar @srcdrives) >= 1; |
62 |
+my ($source_drive) = $srcdrives[0] =~ /(\S+)/; |
63 |
+print "Using $source_drive as source partition template.\n"; |
64 |
+ |
65 |
+# Check if it's MBR or GPT |
66 |
+my $pttype = qx(blkid -o value -s PTTYPE $source_drive); |
67 |
+chomp $pttype; |
68 |
+die "Unable to identify source partition table type for $source_drive\n" unless $pttype; |
69 |
+print "$source_drive partition table type is $pttype\n"; |
70 |
+ |
71 |
+# Clear disk in preparation |
72 |
+print "Wiping $target_drive...\n"; |
73 |
+system("wipefs", "-a", $target_drive) == 0 |
74 |
+ or die "Error clearing existing partition table on $target_drive\n"; |
75 |
+ |
76 |
+# Copy new partition layout |
77 |
+print "Copying partition table from $source_drive to $target_drive...\n"; |
78 |
+if ($pttype eq 'dos') { |
79 |
+ system("sfdisk -d $source_drive | sfdisk -qf --no-reread $target_drive") == 0 |
80 |
+ or die "Error copying MBR partition table to $target_drive\n"; |
81 |
+} elsif ($pttype eq 'gpt') { |
82 |
+ system("sgdisk", "-R", $target_drive, $source_drive) == 0 |
83 |
+ or die "Error copying GPT partition table to $target_drive\n"; |
84 |
+ system("sgdisk", "-G", $target_drive) == 0 |
85 |
+ or die "Error randomising GUID on $target_drive\n"; |
86 |
+} else { |
87 |
+ die "Couldn't interpret partition table type '$pttype' on $source_drive\n"; |
88 |
} |
89 |
|
90 |
-unless (open(OUTPUT, "-|")) |
91 |
-{ |
92 |
- my $boot = "*"; |
93 |
- my $pid = open(SFDISK, "|-"); |
94 |
- if ($pid) |
95 |
- { |
96 |
- # parent |
97 |
- for my $dev (@devices) |
98 |
- { |
99 |
- unless ($dev eq $devices[$#devices]) |
100 |
- { |
101 |
- print SFDISK ",", $devices{$dev}{DeviceSize} + 65, ",fd,$boot\n"; |
102 |
- } |
103 |
- else |
104 |
- { |
105 |
- print SFDISK ",,fd,$boot\n"; |
106 |
- } |
107 |
- $boot = "-"; |
108 |
- } |
109 |
- print SFDISK ",0\n" for (1..4); |
110 |
- close(SFDISK) || die "SFDISK kid exited $?\n"; |
111 |
- } |
112 |
- else |
113 |
- { |
114 |
- # child |
115 |
- exec("/sbin/sfdisk", "-uB", "--no-reread", "--force", "/dev/$newdev") |
116 |
- or die "can't exec program: $!\n"; |
117 |
- # NOTREACHED |
118 |
- } |
119 |
- |
120 |
- print "\nChecking partitions on /dev/$newdev...\n"; |
121 |
- sleep(3); |
122 |
- my $good; |
123 |
- my $cnt = 0; |
124 |
- do { |
125 |
- $cnt++; |
126 |
- $good = 1; |
127 |
- sleep(1); |
128 |
- |
129 |
- %partitions = $raid->get_partitions(); |
130 |
- @partitions = sort grep m#^$newdev.+$#, keys %partitions; |
131 |
- if ( $#devices == $#partitions ) { |
132 |
- foreach my $part ( 0..($#devices-1) ) { |
133 |
- $good &= $partitions{$partitions[$part]}{blocks} >= $devices{$devices[$part]}{DeviceSize} + 64; |
134 |
- $good &= $partitions{$partitions[$part]}{blocks} <= $devices{$devices[$part]}{DeviceSize} + 68; |
135 |
- $good &= -b "/dev/$partitions[$part]" || 0; |
136 |
- } |
137 |
- $good &= $partitions{$partitions[$#devices]}{blocks} >= $devices{$devices[$#devices]}{DeviceSize} + 64; |
138 |
- $good &= -b "/dev/$partitions[$#devices]" || 0; |
139 |
- } else { |
140 |
- $good = 0; |
141 |
- } |
142 |
- } until ( $good || $cnt > 60 ); |
143 |
- print "\n"; |
144 |
- |
145 |
- die "\nPartitions on /dev/$newdev aren't correct. Reboot may be necessary.\n" unless $good; |
146 |
- |
147 |
- sleep(3); |
148 |
- foreach my $part (0..$#devices) |
149 |
- { |
150 |
- print "Going to add /dev/$partitions[$part] to $devices[$part]\n"; |
151 |
- system("/sbin/mdadm", $devices[$part], "--add", "/dev/$partitions[$part]"); |
152 |
- sleep(2); |
153 |
- } |
154 |
- |
155 |
- print "\nWaiting for boot partition to sync before installing grub...\n"; |
156 |
- sleep(15); |
157 |
- |
158 |
- $pid = open(GRUB, "|-"); |
159 |
- if ($pid) |
160 |
- { |
161 |
- # parent |
162 |
- print GRUB "device (hd0) /dev/$newdev\n"; |
163 |
- print GRUB "root (hd0,0)\n"; |
164 |
- print GRUB "setup (hd0)\n"; |
165 |
- print GRUB "quit\n"; |
166 |
- close(GRUB) || die "GRUB kid exited $?\n"; |
167 |
- exit(0); |
168 |
- } |
169 |
- else |
170 |
- { |
171 |
- # child |
172 |
- exec("/sbin/grub", "--no-floppy", "--batch") |
173 |
- or die "can't exec program: $!\n"; |
174 |
- # NOTREACHED |
175 |
- } |
176 |
-} |
177 |
-while(<OUTPUT>) |
178 |
+# Pause to sync |
179 |
+sleep(3); |
180 |
+ |
181 |
+# Install GRUB |
182 |
+print "Installing GRUB on $target_drive...\n"; |
183 |
+system("grub2-install", "--recheck", $target_drive) == 0 |
184 |
+ or warn "Warning - error installing GRUB to $target_drive\n"; |
185 |
+ |
186 |
+# Loop through RAID devices and add the corresponding new partitions |
187 |
+my @srcparts; |
188 |
+my $srcpart; |
189 |
+my $tgtpart; |
190 |
+foreach my $part (0..$#devices) |
191 |
{ |
192 |
- print "$_"; |
193 |
- next unless $_; |
194 |
- warn "$_"; |
195 |
+ # Find the matching source drive partition and substitute the name |
196 |
+ @srcparts = qx(mdadm -v --detail --scan $devices[$part]); |
197 |
+ foreach my $s (@srcparts) {($srcpart) = $s =~ /devices=(\Q$source_drive\E\d+)/}; |
198 |
+ $tgtpart = $srcpart =~ s/\Q$source_drive/$target_drive/r; |
199 |
+ |
200 |
+ print "Adding $tgtpart to $devices[$part]\n"; |
201 |
+ system("/sbin/mdadm", $devices[$part], "--add", $tgtpart) == 0 |
202 |
+ or die "Error adding $tgtpart to $devices[$part]"; |
203 |
} |
204 |
-close(OUTPUT) or die "Closing stdin pipe reported: $!\n"; |
205 |
|
206 |
+# Finished |
207 |
+print "Successfully added $target_drive to RAID!\n"; |
208 |
\ No newline at end of file |
209 |
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 |
210 |
--- e-smith-base-5.8.0.old/root/sbin/e-smith/console-menu-items/manageRAID.pl 2020-12-23 17:42:19.991247057 +0000 |
211 |
+++ e-smith-base-5.8.0/root/sbin/e-smith/console-menu-items/manageRAID.pl 2020-12-23 17:46:12.587310812 +0000 |
212 |
@@ -3,10 +3,11 @@ |
213 |
use warnings; |
214 |
use esmith::console; |
215 |
use Locale::gettext; |
216 |
+use Taint::Util; |
217 |
|
218 |
use Data::Dumper; |
219 |
|
220 |
-use constant DEBUG_MANAGE_RAID => 1; |
221 |
+use constant DEBUG_MANAGE_RAID => 0; |
222 |
|
223 |
sub new |
224 |
{ |
225 |
@@ -35,11 +36,12 @@ |
226 |
my ($rc, $choice); |
227 |
|
228 |
use POSIX qw(strftime); |
229 |
+ |
230 |
+ SCAN: |
231 |
my $today = strftime "%A %B %e, %Y %H:%M:%S", localtime; |
232 |
my $title = gettext("Disk redundancy status as of") . " " . $today, |
233 |
my $text = gettext("Current RAID status:") . "\n\n" . |
234 |
join("", get_raid_status()) . "\n\n"; |
235 |
- |
236 |
my %devices = get_raid_details(); |
237 |
|
238 |
warn $text if DEBUG_MANAGE_RAID; |
239 |
@@ -52,31 +54,40 @@ |
240 |
return; |
241 |
} |
242 |
|
243 |
+ # Determine the status of each array |
244 |
my @unclean = (); |
245 |
my @recovering = (); |
246 |
+ my @failed = (); |
247 |
my %used_disks = (); |
248 |
|
249 |
for my $dev (keys %devices) |
250 |
{ |
251 |
- $used_disks{$_}++ for (@{$devices{$dev}{UsedDisks}}); |
252 |
+ $used_disks{$_}++ for (@{$devices{$dev}{UsedDisks}}); |
253 |
|
254 |
- if ($devices{$dev}{State} =~ /recovering|resync/) |
255 |
- { |
256 |
+ if ($devices{$dev}{FailedDevices} > 0) { |
257 |
+ push @failed, "$dev => " . $devices{$dev}{FailedDevices}; |
258 |
+ } |
259 |
+ |
260 |
+ if ($devices{$dev}{State} =~ /recovering|resync/) { |
261 |
push @recovering, "$dev => " . $devices{$dev}{State}; |
262 |
next; |
263 |
} |
264 |
|
265 |
next if ($devices{$dev}{State} =~ /^(clean|active)\s*$/); |
266 |
- |
267 |
push @unclean, "$dev => " . $devices{$dev}{State}; |
268 |
} |
269 |
|
270 |
- warn "used_disks: " . Dumper(\%used_disks) . "\n" if DEBUG_MANAGE_RAID; |
271 |
- |
272 |
warn "unclean: @unclean\n" if DEBUG_MANAGE_RAID; |
273 |
- |
274 |
warn "recovering: @recovering\n" if DEBUG_MANAGE_RAID; |
275 |
+ warn "failed: @failed\n" if DEBUG_MANAGE_RAID; |
276 |
+ warn "used_disks: " . Dumper(\%used_disks) . "\n" if DEBUG_MANAGE_RAID; |
277 |
|
278 |
+ # Check for any spare disks we could add |
279 |
+ my %free_disks = map {$_ => 1} get_disks(); |
280 |
+ delete $free_disks{$_} for keys %used_disks; |
281 |
+ warn "free_disks: " . Dumper(\%free_disks) . "\n" if DEBUG_MANAGE_RAID; |
282 |
+ |
283 |
+ # Report status and return if recovering |
284 |
if (scalar @recovering) |
285 |
{ |
286 |
$text .= gettext("A RAID resynchronization is in progress."); |
287 |
@@ -84,29 +95,33 @@ |
288 |
return; |
289 |
} |
290 |
|
291 |
- unless (scalar @unclean) |
292 |
+ # Report status and return if arrays are inconsistent |
293 |
+ if ((scalar @unclean && scalar @unclean != scalar keys %devices) || (scalar @failed && scalar @failed != scalar keys %devices)) |
294 |
{ |
295 |
- $text .= gettext("All RAID devices are in clean state"); |
296 |
+ $text .= gettext("Only some of the RAID devices are unclean or contain failed disks.") . |
297 |
+ "\n\n" . |
298 |
+ gettext("Manual intervention may be required.") . "\n\n"; |
299 |
+ |
300 |
($rc, $choice) = $console->message_page(title => $title, text => $text); |
301 |
return; |
302 |
} |
303 |
|
304 |
- unless (scalar @unclean == scalar keys %devices) |
305 |
+ # Report status if arrays are clean and continue if a spare disk is available or there's only one disk in the system |
306 |
+ unless (scalar @unclean || scalar @failed) |
307 |
{ |
308 |
- $text .= gettext("Only some of the RAID devices are unclean.") . |
309 |
- "\n\n" . |
310 |
- gettext("Manual intervention may be required.") . "\n\n"; |
311 |
- |
312 |
+ $text .= gettext("All RAID devices are in a clean state."); |
313 |
+ ($rc, $choice) = $console->message_page(title => $title, text => $text); |
314 |
+ return unless scalar keys %free_disks > 0 || scalar keys %used_disks == 1; |
315 |
+ } |
316 |
+ |
317 |
+ # Report status if all arrays are dirty and continue |
318 |
+ if ((scalar @unclean && scalar @unclean == scalar keys %devices) || (scalar @failed && scalar @failed == scalar keys %devices)) |
319 |
+ { |
320 |
+ $text .= gettext("All RAID devices are in an unclean state or contain failed disks."); |
321 |
($rc, $choice) = $console->message_page(title => $title, text => $text); |
322 |
- return; |
323 |
} |
324 |
|
325 |
- my %free_disks = map {$_ => 1} get_disks(); |
326 |
- |
327 |
- delete $free_disks{$_} for keys %used_disks; |
328 |
- |
329 |
- warn "free_disks: " . Dumper(\%free_disks) . "\n" if DEBUG_MANAGE_RAID; |
330 |
- |
331 |
+ # Summarise disk assignments |
332 |
my $disk_status = gettext("Current disk status:") . "\n\n"; |
333 |
$disk_status .= gettext("Installed disks") . ": " . |
334 |
join(" ", get_disks()) . "\n"; |
335 |
@@ -115,50 +130,64 @@ |
336 |
$disk_status .= gettext("Free disks") . ": " . |
337 |
join(" ", keys %free_disks) . "\n"; |
338 |
|
339 |
- if (scalar keys %used_disks == 1 and scalar keys %free_disks == 0) |
340 |
+ # Spare disk scenarios |
341 |
+ # Scenario 1 - single disk or degraded array with no spare - warn |
342 |
+ if ((scalar @unclean || scalar @failed || scalar keys %used_disks == 1) && scalar keys %free_disks == 0) |
343 |
{ |
344 |
- $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."); |
345 |
+ $text = $disk_status . |
346 |
+ "\n\n" . |
347 |
+ gettext("To ensure continued redundancy, please shut down, install another drive of the same capacity and then return to this screen."); |
348 |
|
349 |
($rc, $choice) = $console->message_page(title => $title, text => $text); |
350 |
return; |
351 |
} |
352 |
|
353 |
- unless (scalar keys %used_disks == 1 and |
354 |
- scalar keys %free_disks == scalar keys %used_disks) |
355 |
+ # Scenario 2 - no spares and not degraded so something has gone wrong |
356 |
+ if (scalar keys %free_disks == 0) |
357 |
{ |
358 |
- $text .= gettext("The free and used disk count must equal one.") . |
359 |
- "\n\n" . |
360 |
- gettext("Manual intervention may be required.") . "\n\n" . |
361 |
- $disk_status; |
362 |
+ $text = $disk_status . |
363 |
+ "\n\n" . |
364 |
+ 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."); |
365 |
|
366 |
($rc, $choice) = $console->message_page(title => $title, text => $text); |
367 |
return; |
368 |
} |
369 |
|
370 |
- my @cmd = ("/sbin/e-smith/add_drive_to_raid", "-f", join("", keys %free_disks)); |
371 |
+ # Scenario 3 - multiple spares |
372 |
+ if (scalar keys %free_disks > 1) |
373 |
+ { |
374 |
+ $text = $disk_status . |
375 |
+ "\n\n" . |
376 |
+ 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."); |
377 |
|
378 |
+ ($rc, $choice) = $console->message_page(title => $title, text => $text); |
379 |
+ return; |
380 |
+ } |
381 |
+ |
382 |
+ # Scenario 4 - single spare ready to add |
383 |
$text = $disk_status . |
384 |
"\n\n" . |
385 |
gettext("There is an unused disk drive in your system. Do you want to add it to the existing RAID array(s)?") . |
386 |
- "\n\n" . |
387 |
- gettext("WARNING: ALL DATA ON THE NEW DISK WILL BE DESTROYED!") . |
388 |
- "\n" |
389 |
- ; |
390 |
+ "\n\n" . |
391 |
+ gettext("WARNING: ALL DATA ON THE NEW DISK WILL BE DESTROYED!") . |
392 |
+ "\n"; |
393 |
|
394 |
($rc, $choice) = $console->yesno_page(title => $title, text => $text, defaultno => 1); |
395 |
return unless ($rc == 0); |
396 |
|
397 |
+ my @cmd = ("/sbin/e-smith/add_drive_to_raid", "-f", join("", keys %free_disks)); |
398 |
my $cmd_out = qx( @cmd 2>&1 ); |
399 |
- unless ($? == 0) |
400 |
- { |
401 |
- $text = gettext("The command failed:") . " @cmd" . |
402 |
- "\n\n" . $cmd_out . "\n\n" . |
403 |
- gettext("This configuration is not yet fully supported in these screens."); |
404 |
+ untaint $cmd_out; |
405 |
|
406 |
- ($rc, $choice) = $console->message_page(title => $title, text => $text); |
407 |
- return; |
408 |
+ if ($? == 0) { |
409 |
+ $text = "\nSuccessfully added /dev/" . join("", keys %free_disks) . " to RAID!"; |
410 |
+ } else { |
411 |
+ $text = gettext("The command failed:") . " @cmd" . |
412 |
+ "\n\n" . $cmd_out . "\n\n"; |
413 |
} |
414 |
- |
415 |
+ |
416 |
+ ($rc, $choice) = $console->message_page(title => $title, text => $text); |
417 |
+ goto SCAN; |
418 |
} |
419 |
|
420 |
sub get_raid_status |
421 |
@@ -185,7 +214,7 @@ |
422 |
|
423 |
while (<MDADM>) |
424 |
{ |
425 |
- push @devices, $1 if ( m:ARRAY (/dev/md/\d+): ) |
426 |
+ push @devices, $1 if ( m:ARRAY (/dev/md/\w+): ) |
427 |
} |
428 |
close MDADM; |
429 |
|
430 |
@@ -203,7 +232,7 @@ |
431 |
my ($key, $value) = ($1, $2); |
432 |
$key =~ s/\s//g; |
433 |
|
434 |
- # Allow for different mdadm output formats for DeviceSize |
435 |
+ # Allow for different mdadm output formats for DeviceSize |
436 |
$key =~ s/UsedDevSize/DeviceSize/; |
437 |
|
438 |
$devices{$dev}{$key} = $value; |