Handle hostnames with upper-case letters
[webmin.git] / raid / raid-lib.pl
1 # raid-lib.pl
2 # Functions for managing RAID
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 &foreign_require("fdisk");
8
9 open(MODE, "$module_config_directory/mode");
10 chop($raid_mode = <MODE>);
11 close(MODE);
12
13 %container = ( 'raiddev', 1,
14                'device', 1 );
15
16 # get_raid_levels()
17 # Returns a list of allowed RAID levels
18 sub get_raid_levels
19 {
20 if ($raid_mode eq "mdadm") {
21         return ( 0, 1, 4, 5, 6, 10 );
22         }
23 else {
24         return ( 0, 1, 4, 5 );
25         }
26 }
27
28 # get_mdstat()
29 # Read information about active RAID devices. Returns a hash indexed by
30 # device name (like /dev/md0), with each value being an array reference
31 # containing  status  level  disks  blocks  resync  disk-info
32 sub get_mdstat
33 {
34 # Read the mdstat file
35 local %mdstat;
36 local $lastdev;
37 open(MDSTAT, $config{'mdstat'});
38 while(<MDSTAT>) {
39         if (/^(md\d+)\s*:\s+(\S+)\s+(\S+)\s+(.*)\s+(\d+)\s+blocks\s*(.*)resync=(\d+)/) {
40                 $mdstat{$lastdev = "/dev/$1"} = [ $2, $3, $4, $5, $7, $6 ];
41                 }
42         elsif (/^(md\d+)\s*:\s+(\S+)\s+(\S+)\s+(.*)\s+(\d+)\s+blocks\s*(.*)/) {
43                 $mdstat{$lastdev = "/dev/$1"} = [ $2, $3, $4, $5, undef, $6 ];
44                 }
45         elsif (/^(md\d+)\s*:\s+(\S+)\s+(\S+)\s+(.*)/) {
46                 $mdstat{$lastdev = "/dev/$1"} = [ $2, $3, $4 ];
47                 $_ = <MDSTAT>;
48                 if (/\s+(\d+)\s+blocks\s*(.*)resync=(\d+)/) {
49                         $mdstat{$lastdev}->[3] = $1;
50                         $mdstat{$lastdev}->[4] = $3;
51                         $mdstat{$lastdev}->[5] = $2;
52                         }
53                 elsif (/\s+(\d+)\s+blocks\s*(.*)/) {
54                         $mdstat{$lastdev}->[3] = $1;
55                         $mdstat{$lastdev}->[5] = $2;
56                         }
57                 }
58         }
59 close(MDSTAT);
60 return %mdstat;
61 }
62
63 # get_raidtab()
64 # Parse the raid config file into a list of devices
65 sub get_raidtab
66 {
67 local ($raiddev, $device, %mdstat);
68 return \@get_raidtab_cache if (scalar(@get_raidtab_cache));
69 %mdstat = &get_mdstat();
70
71 if ($raid_mode eq "raidtools") {
72         # Read the raidtab file
73         local $lnum = 0;
74         open(RAID, $config{'raidtab'});
75         while(<RAID>) {
76                 s/\r|\n//g;
77                 s/#.*$//;
78                 if (/^\s*(\S+)\s+(\S+)/) {
79                         local $dir = { 'name' => lc($1),
80                                        'value' => $2,
81                                        'line' => $lnum,
82                                        'eline' => $lnum };
83                         if ($dir->{'name'} =~ /^(raid|spare|parity|failed)-disk$/) {
84                                 push(@{$device->{'members'}}, $dir);
85                                 $device->{'eline'} = $lnum;
86                                 $raiddev->{'eline'} = $lnum;
87                                 }
88                         elsif ($dir->{'name'} eq 'raiddev') {
89                                 $dir->{'index'} = scalar(@get_raidtab_cache);
90                                 push(@get_raidtab_cache, $dir);
91                                 }
92                         else {
93                                 push(@{$raiddev->{'members'}}, $dir);
94                                 $raiddev->{'eline'} = $lnum;
95                                 }
96                         if ($dir->{'name'} eq 'device') {
97                                 $device = $dir;
98                                 }
99                         elsif ($dir->{'name'} eq 'raiddev') {
100                                 $raiddev = $dir;
101                                 local $m = $mdstat{$dir->{'value'}};
102                                 $dir->{'active'} = $m->[0] =~ /^active/;
103                                 $dir->{'level'} = $m->[1] =~ /raid(\d+)/ ? $1 : $m->[1];
104                                 $dir->{'devices'} = [
105                                         map { /(\S+)\[\d+\](\((.)\))?/;
106                                               $3 eq 'F' ? () : ("/dev/$1") }
107                                             split(/\s+/, $m->[2]) ];
108                                 $dir->{'size'} = $m->[3];
109                                 $dir->{'resync'} = $m->[4];
110                                 $dir->{'errors'} = &disk_errors($m->[5]);
111                                 }
112                         }
113                 $lnum++;
114                 }
115         close(RAID);
116         }
117 else {
118         # Fake up the same format from mdadm output
119         local $m;
120         foreach $m (sort { $a cmp $b } keys %mdstat) {
121                 local $md = { 'value' => $m,
122                               'members' => [ ],
123                               'index' => scalar(@get_raidtab_cache) };
124                 local $mdstat = $mdstat{$md->{'value'}};
125                 $md->{'active'} = $mdstat->[0] =~ /^active/;
126                 $md->{'level'} = $mdstat->[1] =~ /raid(\d+)/ ? $1 : $mdstat->[1];
127                 $md->{'devices'} = [
128                         map { /(\S+)\[\d+\](\((.)\))?/;
129                               $3 eq 'F' ? () : (&convert_to_hd("/dev/$1")) }
130                             split(/\s+/, $mdstat->[2]) ];
131                 $md->{'size'} = $mdstat->[3];
132                 $md->{'resync'} = $mdstat->[4];
133                 $md->{'errors'} = &disk_errors($mdstat->[5]);
134                 open(MDSTAT, "mdadm --detail $m |");
135                 while(<MDSTAT>) {
136                         if (/^\s*Raid\s+Level\s*:\s*(\S+)/) {
137                                 local $lvl = $1;
138                                 $lvl =~ s/^raid//;
139                                 push(@{$md->{'members'}}, { 'name' => 'raid-level',
140                                                             'value' => $lvl });
141                                 }
142                         elsif (/^\s*Persistence\s*:\s*(.*)/) {
143                                 push(@{$md->{'members'}},
144                                         { 'name' => 'persistent-superblock',
145                                           'value' => $1 =~ /is\s+persistent/ });
146                                 }
147                         elsif (/^\s*State\s*:\s*(.*)/) {
148                                 $md->{'state'} = $1;
149                                 }
150                         elsif ((/^\s*Rebuild\s+Status\s*:\s*(\d+)\s*\%/) || (/^\s*Reshape\s+Status\s*:\s*(\d+)\s*\%/)) {
151                                 $md->{'rebuild'} = $1;
152                                 }
153                         elsif (/^\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+|\-)\s+(.*\S)\s+(\/\S+)/) {
154                                 # A device line
155                                 local $device = { 'name' => 'device',
156                                                   'value' => $6,
157                                                   'members' => [ ] };
158                                 push(@{$device->{'members'}},
159                                      { 'name' => $5 eq 'spare' ? 'spare-disk'
160                                                                : 'raid-disk',
161                                        'value' => $3 });
162                                 push(@{$md->{'members'}}, $device);
163                                 }
164                         elsif (/^\s+(Chunk\s+Size|Rounding)\s+:\s+(\d+)/i) {
165                                 push(@{$md->{'members'}},
166                                         { 'name' => 'chunk-size',
167                                           'value' => $2 });
168                                 }
169                         }
170                 close(MDSTAT);
171                 open(MDSTAT, $config{'mdstat'});
172                 while(<MDSTAT>){
173                         if (/^.*finish=(\S+)min/){
174                                 $md->{'remain'} = $1;
175                                 }
176                         if (/^.*speed=(\S+)K/){
177                                 $md->{'speed'} = $1;
178                                 }
179                         }
180                 close(MDSTAT);
181                 push(@get_raidtab_cache, $md);
182                 }
183
184         # Merge in info from mdadm.conf
185         local $lref = &read_file_lines($config{'mdadm'});
186         foreach my $l (@$lref) {
187                 if ($l =~ /^ARRAY\s+(\S+)\s*(.*)/) {
188                         local $dev = $1;
189                         local %opts = map { split(/=/, $_, 2) }
190                                           split(/\s+/, $2);
191                         local ($md) = grep { $_->{'value'} eq $dev }
192                                            @get_raidtab_cache;
193                         if ($md) {
194                                 push(@{$md->{'members'}},
195                                         { 'name' => 'spare-group',
196                                           'value' => $opts{'spare-group'} });
197                                 }
198                         }
199                 }
200         }
201 return \@get_raidtab_cache;
202 }
203
204 # disk_errors(string)
205 # Converts an mdstat errors string into an array of disk statuses
206 sub disk_errors
207 {
208 if ($_[0] =~ /\[([0-9\/]+)\].*\[([A-Z_]+)\]/i) {
209         local ($idxs, $errs) = ($1, $2);
210         local @idxs = split(/\//, $idxs);
211         local @errs = split(//, $errs);
212         #if (@idxs == @errs) {
213         #       return [ map { $errs[$_-1] } @idxs ];
214         #       }
215         return \@errs;
216         }
217 return undef;
218 }
219
220 sub lock_raid_files
221 {
222 &lock_file($raid_mode eq "raidtools" ? $config{'raidtab'} : $config{'mdadm'});
223 }
224
225 sub unlock_raid_files
226 {
227 &unlock_file($raid_mode eq "raidtools" ? $config{'raidtab'} : $config{'mdadm'});
228 }
229
230 # create_raid(&raid)
231 # Create a new raid set in the configuration file
232 sub create_raid
233 {
234 if ($raid_mode eq "raidtools") {
235         # Add to /etc/raidtab
236         local $lref = &read_file_lines($config{'raidtab'});
237         $_[0]->{'line'} = @$lref;
238         push(@$lref, &directive_lines($_[0]));
239         $_[0]->{'eline'} = @$lref - 1;
240         &flush_file_lines();
241         }
242 else {
243         # Add to /etc/mdadm.conf
244         local ($d, @devices);
245         foreach $d (&find("device", $_[0]->{'members'})) {
246                 push(@devices, $d->{'value'});
247                 }
248         local $sg = &find_value("spare-group", $_[0]->{'members'});
249         local $lref = &read_file_lines($config{'mdadm'});
250         local $lvl = &find_value('raid-level', $_[0]->{'members'});
251         $lvl = $lvl =~ /^\d+$/ ? "raid$lvl" : $lvl;
252         push(@$lref, "DEVICE ".
253                      join(" ", map { &device_to_volid($_) } @devices));
254         push(@$lref, "ARRAY $_[0]->{'value'} level=$lvl devices=".
255                      join(",", @devices).
256                      ($sg ? " spare-group=$sg" : ""));
257         &flush_file_lines();
258         &update_initramfs();
259         }
260 }
261
262 # delete_raid(&raid)
263 # Delete a raid set from the config file
264 sub delete_raid
265 {
266 if ($raid_mode eq "raidtools") {
267         # Remove from /etc/raidtab
268         local $lref = &read_file_lines($config{'raidtab'});
269         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1);
270         &flush_file_lines($config{'raidtab'});
271         }
272 else {
273         # Zero out the RAID
274         &system_logged("mdadm --zero-superblock ".
275                        "$_[0]->{'value'} >/dev/null 2>&1");
276
277         # Zero out component superblocks
278         my @devs = &find('device', $_[0]->{'members'});
279         foreach $d (@devs) {
280                 if (&find('raid-disk', $d->{'members'}) ||
281                     &find('parity-disk', $d->{'members'}) ||
282                     &find('spare-disk', $d->{'members'})) {
283                         &system_logged("mdadm --zero-superblock ".
284                                        "$d->{'value'} >/dev/null 2>&1");
285                         }
286                 }
287
288         # Remove from /etc/mdadm.conf
289         local ($d, %devices);
290         foreach $d (&find("device", $_[0]->{'members'})) {
291                 $devices{$d->{'value'}} = 1;
292                 }
293         local $lref = &read_file_lines($config{'mdadm'});
294         local $i;
295         for($i=0; $i<@$lref; $i++) {
296                 if ($lref->[$i] =~ /^ARRAY\s+(\S+)/ && $1 eq $_[0]->{'value'}) {
297                         splice(@$lref, $i--, 1);
298                         }
299                 elsif ($lref->[$i] =~ /^DEVICE\s+(.*)/) {
300                         local @olddevices = split(/\s+/, $1);
301                         local @newdevices = grep { !$devices{$_} } @olddevices;
302                         if (@newdevices) {
303                                 $lref->[$i] = "DEVICE ".join(" ", @newdevices);
304                                 }
305                         else {
306                                 splice(@$lref, $i--, 1);
307                                 }
308                         }
309                 }
310         &flush_file_lines($config{'mdadm'});
311         &update_initramfs();
312         }
313 }
314
315 # device_to_volid(device)
316 # Given a device name like /dev/sda1, convert it to a volume ID if possible.
317 # Otherwise return the device name.
318 sub device_to_volid
319 {
320 local ($dev) = @_;
321 return $dev;
322 #return &fdisk::get_volid($dev) || $dev;
323 }
324
325 # make_raid(&raid, force, [missing], [assume-clean])
326 # Call mkraid or mdadm to make a raid set for real
327 sub make_raid
328 {
329 if (!-r $_[0]->{'value'} && $_[0]->{'value'} =~ /\/md(\d+)$/) {
330         # Device file is missing - create it now
331         &system_logged("mknod $_[0]->{'value'} b 9 $1");
332         }
333 if ($raid_mode eq "raidtools") {
334         # Call the raidtools mkraid command
335         local $f = $_[1] ? "--really-force" : "";
336         local $out = &backquote_logged("mkraid $f $_[0]->{'value'} ".
337                                        "2>&1 </dev/null");
338         return $? ? &text($out =~ /force/i ? 'eforce' : 'emkraid',
339                           "<pre>$out</pre>")
340                   : undef;
341         }
342 else {
343         # Call the complete mdadm command
344         local $lvl = &find_value("raid-level", $_[0]->{'members'});
345         $lvl =~ s/^raid//;
346         local $chunk = &find_value("chunk-size", $_[0]->{'members'});
347         local $mode = &find_value("persistent-superblock", $_[0]->{'members'}) ? "create" : "build";
348         local $layout = &find_value("parity-algorithm", $_[0]->{'members'});
349         local ($d, @devices, @spares, @parities);
350         foreach $d (&find("device", $_[0]->{'members'})) {
351                 if (&find("raid-disk", $d->{'members'})) {
352                         push(@devices, $d->{'value'});
353                         }
354                 elsif (&find("spare-disk", $d->{'members'})) {
355                         push(@spares, $d->{'value'});
356                         }
357                 elsif (&find("parity-disk", $d->{'members'})) {
358                         push(@parities, $d->{'value'});
359                         }
360                 }
361         local $cmd = "mdadm --$mode --level $lvl --chunk $chunk";
362         if ($_[2]) {
363                 push(@devices, "missing");
364                 }
365         $cmd .= " --layout $layout" if ($layout);
366         $cmd .= " --raid-devices ".scalar(@devices);
367         $cmd .= " --spare-devices ".scalar(@spares) if (@spares);
368         $cmd .= " --force" if ($_[1]);
369         $cmd .= " --assume-clean" if ($_[3]);
370         $cmd .= " --run";
371         $cmd .= " $_[0]->{'value'}";
372         foreach $d (@devices, @parities, @spares) {
373                 $cmd .= " $d";
374                 }
375         local $out = &backquote_logged("$cmd 2>&1 </dev/null");
376         
377         return $? ? &text('emdadmcreate', "<pre>$out</pre>") : undef;
378         }
379 }
380
381 # readwrite_raid(&raid)
382 # Set RAID mode to read/write.
383 sub readwrite_raid
384 {
385         local $cmd = "mdadm --readwrite $_[0]->{'value'}";
386         local $out = &backquote_logged("$cmd 2>&1 </dev/null");
387         return;
388 }
389
390 # unmake_raid(&raid)
391 # Shut down a RAID set permanently
392 sub unmake_raid
393 {
394 if ($raid_mode eq "raidtools") {
395         &deactivate_raid($_[0]) if ($_[0]->{'active'});
396         }
397 else {
398         local $out = &backquote_logged("mdadm --stop $_[0]->{'value'} 2>&1");
399         &error(&text('emdadmstop', "<tt>$out</tt>")) if ($?);
400         }
401 }
402
403 # activate_raid(&raid)
404 # Activate a raid set, which has previously been deactivated
405 sub activate_raid
406 {
407 if ($raid_mode eq "raidtools") {
408         local $out = &backquote_logged("raidstart $_[0]->{'value'} 2>&1");
409         &error(&text('eraidstart', "<tt>$out</tt>")) if ($?);
410         }
411 }
412
413 # deactivate_raid(&raid)
414 # Deactivate a raid set, without actually deleting it
415 sub deactivate_raid
416 {
417 if ($raid_mode eq "raidtools") {
418         # Just stop the raid set
419         local $out = &backquote_logged("raidstop $_[0]->{'value'} 2>&1");
420         &error(&text('eraidstop', "<tt>$out</tt>")) if ($?);
421         }
422 }
423
424 # add_partition(&raid, device)
425 # Adds a device to some RAID set, both in the config file and for real
426 sub add_partition
427 {
428 if ($raid_mode eq "mdadm") {
429         # Call mdadm command to add
430         local $out = &backquote_logged(
431                 "mdadm --manage $_[0]->{'value'} --add $_[1] 2>&1");
432         &error(&text('emdadmadd', "<tt>$out</tt>")) if ($?);
433
434         # Add device to mdadm.conf
435         local $lref = &read_file_lines($config{'mdadm'});
436         local ($i, $done_device);
437         for($i=0; $i<@$lref; $i++) {
438                 if ($lref->[$i] =~ /^DEVICE\s+/ && !$done_device) {
439                         $lref->[$i] .= " $_[1]";
440                         $done_device++;
441                         }
442                 elsif ($lref->[$i] =~ /^ARRAY\s+(\S+)/ &&
443                        $1 eq $_[0]->{'value'}) {
444                         $lref->[$i] =~ s/(\s)devices=(\S+)/${1}devices=${2},$_[1]/;
445                         }
446                 }
447         &flush_file_lines();
448         &update_initramfs();
449         }
450 }
451
452 # grow(&raid, totaldisks)
453 # Grows a RAID set to contain totaldisks active partitions
454 sub grow
455 {
456 if ($raid_mode eq "mdadm") {
457         # Call mdadm command to add
458         $cmd="mdadm --grow $_[0]->{'value'} -n $_[1] 2>&1";
459         local $out = &backquote_logged(
460                 $cmd);
461         &error(&text('emdadmgrow', "<tt>'$cmd' -> $out</tt>")) if ($?);
462         }
463 }
464
465 # convert_raid(&raid, oldcount, newcount, level)
466 # Converts a RAID set to a defferent level RAID set
467 sub convert_raid
468 {
469 if ($raid_mode eq "mdadm") {
470         if ($_[2]) {
471                 # Call mdadm command to convert
472                 $cmd="mdadm --grow $_[0]->{'value'} --level $_[3]";
473                 $grow_by = $_[2] - $_[1];
474                 if ($grow_by == 1) {
475                         $raid_device_short = $_[0]->{'value'};
476                         $raid_device_short =~ s/\/dev\///;
477                         $date = `date \+\%Y\%m\%d-\%H\%M`;
478                         chomp($date);
479                         $cmd .= " --backup-file /tmp/convert-$raid_device_short-$date";
480                 }
481                 $cmd .= " -n $_[2]  2>&1";
482         
483                 local $out = &backquote_logged(
484                         $cmd);
485                 &error(&text('emdadmgrow', "<tt>'$cmd' -> $out</tt>")) if ($?);
486                 }
487         else {
488                 $newcount = $_[1] - 1;
489                 $cmd="mdadm --grow $_[0]->{'value'} --level $_[3] -n $newcount";
490                 $raid_device_short = $_[0]->{'value'};
491                 $raid_device_short =~ s/\/dev\///;
492                 $date = `date \+\%Y\%m\%d-\%H\%M`;
493                 chomp($date);
494                 $cmd .= " --backup-file /tmp/convert-$raid_device_short-$date";
495                 local $out = &backquote_logged(
496                         $cmd);
497                 &error(&text('emdadmgrow', "<tt>'$cmd' -> $out</tt>")) if ($?);
498                 }
499         }
500 }
501
502 # remove_partition(&raid, device)
503 # Removes a device from some RAID set, both in the config file and for real
504 sub remove_partition
505 {
506 if ($raid_mode eq "mdadm") {
507         # Call mdadm commands to fail and remove
508         local $out = &backquote_logged(
509                 "mdadm --manage $_[0]->{'value'} --fail $_[1] 2>&1");
510         &error(&text('emdadfail', "<tt>$out</tt>")) if ($?);
511         local $out = &backquote_logged(
512                 "mdadm --manage $_[0]->{'value'} --remove $_[1] 2>&1");
513         &error(&text('emdadremove', "<tt>$out</tt>")) if ($?);
514
515         # Remove device from mdadm.conf
516         local $lref = &read_file_lines($config{'mdadm'});
517         local ($i, $done_device);
518         for($i=0; $i<@$lref; $i++) {
519                 if ($lref->[$i] =~ /^DEVICE\s+(.*)/) {
520                         local @olddevices = split(/\s+/, $1);
521                         local @newdevices = grep { $_ ne $_[1] } @olddevices;
522                         if (@newdevices) {
523                                 $lref->[$i] = "DEVICE ".join(" ", @newdevices);
524                                 }
525                         else {
526                                 splice(@$lref, $i--, 1);
527                                 }
528                         }
529                 elsif ($lref->[$i] =~ /^ARRAY\s+(\S+)/ &&
530                        $1 eq $_[0]->{'value'}) {
531                         $lref->[$i] =~ s/((=)|,)\Q$_[1]\E/$2/;
532                         }
533                 }
534         &flush_file_lines();
535         &update_initramfs();
536         }
537 }
538
539 # remove_detached(&raid)
540 # Removes detached device(s) from some RAID set
541 sub remove_detached
542 {
543 if ($raid_mode eq "mdadm") {
544         # Call mdadm commands to remove
545         local $out = &backquote_logged(
546                 "mdadm --manage $_[0]->{'value'} --remove detached 2>&1");
547         &error(&text('emdadremove', "<tt>$out</tt>")) if ($?);
548         }
549 }
550
551 # directive_lines(&directive, indent)
552 sub directive_lines
553 {
554 local @rv = ( "$_[1]$_[0]->{'name'}\t$_[0]->{'value'}" );
555 foreach $m (@{$_[0]->{'members'}}) {
556         push(@rv, &directive_lines($m, $_[1]."\t"));
557         }
558 return @rv;
559 }
560
561 # find(name, &array)
562 sub find
563 {
564 local($c, @rv);
565 foreach $c (@{$_[1]}) {
566         if ($c->{'name'} eq $_[0]) {
567                 push(@rv, $c);
568                 }
569         }
570 return @rv ? wantarray ? @rv : $rv[0]
571            : wantarray ? () : undef;
572 }
573
574 # find_value(name, &array)
575 sub find_value
576 {
577 local(@v);
578 @v = &find($_[0], $_[1]);
579 if (!@v) { return undef; }
580 elsif (wantarray) { return map { $_->{'value'} } @v; }
581 else { return $v[0]->{'value'}; }
582 }
583
584 # device_status(device)
585 # Returns an array of  directory, type, mounted
586 sub device_status
587 {
588 @mounted = &mount::list_mounted() if (!@mounted);
589 @mounts = &mount::list_mounts() if (!@mounts);
590 local $label = &fdisk::get_label($_[0]);
591 local $volid = &fdisk::get_volid($_[0]);
592
593 local ($mounted) = grep { &same_file($_->[1], $_[0]) ||
594                           $_->[1] eq "LABEL=$label" ||
595                           $_->[1] eq "UUID=$volid" } @mounted;
596 local ($mount) = grep { &same_file($_->[1], $_[0]) ||
597                         $_->[1] eq "LABEL=$label" ||
598                         $_->[1] eq "UUID=$volid" } @mounts;
599 if ($mounted) { return ($mounted->[0], $mounted->[2], 1,
600                         &indexof($mount, @mounts),
601                         &indexof($mounted, @mounted)); }
602 elsif ($mount) { return ($mount->[0], $mount->[2], 0,
603                          &indexof($mount, @mounts)); }
604 if (!scalar(@physical_volumes)) {
605         @physical_volumes = ();
606         foreach $vg (&lvm::list_volume_groups()) {
607                 push(@physical_volumes,
608                         &lvm::list_physical_volumes($vg->{'name'}));
609                 }
610         }
611 foreach $pv (@physical_volumes) {
612         return ( $pv->{'vg'}, "lvm", 1)
613                 if ($pv->{'device'} eq $_[0]);
614         }
615 return ();
616 }
617
618 # find_free_partitions(&skip, showtype, showsize)
619 # Returns a list of options, suitable for ui_select
620 sub find_free_partitions
621 {
622 &foreign_require("fdisk");
623 &foreign_require("mount");
624 &foreign_require("lvm");
625 local %skip = map { $_, 1 } @{$_[0]};
626 local %used;
627 local $c;
628 local $conf = &get_raidtab();
629 foreach $c (@$conf) {
630         foreach $d (&find_value('device', $c->{'members'})) {
631                 $used{$d}++;
632                 }
633         }
634 local @disks;
635 local $d;
636 foreach $d (&fdisk::list_disks_partitions()) {
637         foreach $p (@{$d->{'parts'}}) {
638                 next if ($used{$p->{'device'}} || $p->{'extended'} ||
639                          $skip{$p->{'device'}});
640                 local @st = &device_status($p->{'device'});
641                 next if (@st);
642                 $tag = &fdisk::tag_name($p->{'type'});
643                 $p->{'blocks'} =~ s/\+$//;
644                 push(@disks, [ $p->{'device'},
645                                $p->{'desc'}.
646                                ($tag && $_[1] ? " ($tag)" : "").
647                                (!$_[2] ? "" :
648                                 $d->{'cylsize'} ? " (".&nice_size($d->{'cylsize'}*($p->{'end'} - $p->{'start'} + 1)).")" :
649                                 " ($p->{'blocks'} $text{'blocks'})") ]);
650                 }
651         if (!@{$d->{'parts'}} &&
652             !$used{$d->{'device'}} && !$skip{$d->{'device'}}) {
653                 # Raw disk has no partitions - add it as an option
654                 push(@disks, [ $d->{'device'},
655                                $d->{'desc'}.
656                                ($d->{'cylsize'} ? " (".&nice_size($d->{'cylsize'}*$d->{'cylinders'}).")" : "") ]);
657                 }
658         }
659 foreach $c (@$conf) {
660         next if (!$c->{'active'} || $used{$c->{'value'}});
661         local @st = &device_status($c->{'value'});
662         next if (@st || $skip{$c->{'value'}});
663         push(@disks, [ $c->{'value'},
664                        &text('create_rdev',
665                          $c->{'value'} =~ /md(\d+)$/ ? "$1" : $c->{'value'}) ]);
666         }
667 local $vg;
668 foreach $vg (&lvm::list_volume_groups()) {
669         local $lv;
670         foreach $lv (&lvm::list_logical_volumes($vg->{'name'})) {
671                 next if ($lv->{'perm'} ne 'rw' || $used{$lv->{'device'}} ||
672                          $skip->{$lv->{'device'}});
673                 local @st = &device_status($lv->{'device'});
674                 next if (@st);
675                 push(@disks, [ $lv->{'device'},
676                               &text('create_lvm', $lv->{'vg'}, $lv->{'name'}) ]);
677                 }
678         }
679 return sort { $a->[0] cmp $b->[0] } @disks;
680 }
681
682 # convert_to_hd(device)
683 # Converts a device file like /dev/ide/host0/bus0/target1/lun0/part1 to
684 # /dev/hdb1, if it doesn't actually exist.
685 sub convert_to_hd
686 {
687 local ($dev) = @_;
688 return $dev if (-r $dev);
689 if ($dev =~ /ide\/host(\d+)\/bus(\d+)\/target(\d+)\/lun(\d+)\/part(\d+)/) {
690         local ($host, $bus, $target, $lun, $part) = ($1, $2, $3, $4, $5);
691         return "/dev/".&fdisk::hbt_to_device($host, $bus, $target).$part;
692         }
693 else {
694         return $dev;
695         }
696 }
697
698 %mdadm_notification_opts = map { $_, 1 } ( 'MAILADDR', 'MAILFROM', 'PROGRAM' );
699
700 # get_mdadm_notifications()
701 # Returns a hash from mdadm.conf notification-related settings to values
702 sub get_mdadm_notifications
703 {
704 local $lref = &read_file_lines($config{'mdadm'});
705 local %rv;
706 foreach my $l (@$lref) {
707         $l =~ s/#.*$//;
708         if ($l =~ /^(\S+)\s+(\S.*)/ && $mdadm_notification_opts{$1}) {
709                 $rv{$1} = $2;
710                 }
711         }
712 return \%rv;
713 }
714
715 # save_mdadm_notifications(&notifications)
716 # Updates mdadm.conf with settings from the given hash. Those set to undef
717 # are removed from the file.
718 sub save_mdadm_notifications
719 {
720 local ($notif) = @_;
721 local $lref = &read_file_lines($config{'mdadm'});
722 local %done;
723 for(my $i=0; $i<@$lref; $i++) {
724         my $l = $lref->[$i];
725         $l =~ s/#.*$//;
726         local ($k, $v) = split(/\s+/, $l, 2);
727         if (exists($notif->{$k})) {
728                 if (defined($notif->{$k})) {
729                         $lref->[$i] = "$k $notif->{$k}";
730                         }
731                 else {
732                         splice(@$lref, $i--, 1);
733                         }
734                 $done{$k}++;
735                 }
736         }
737 foreach my $k (grep { !$done{$_} && defined($notif->{$_}) } keys %$notif) {
738         push(@$lref, "$k $notif->{$k}");
739         }
740 &flush_file_lines($config{'mdadm'});
741 }
742
743 # get_mdadm_action()
744 # Returns the name of an init module action for mdadm monitoring, or undef if
745 # not supported.
746 sub get_mdadm_action
747 {
748 if (&foreign_installed("init")) {
749         &foreign_require("init");
750         foreach my $a ("mdmonitor", "mdadm", "mdadmd") {
751                 local $st = &init::action_status($a);
752                 return $a if ($st);
753                 }
754         }
755 return undef;
756 }
757
758 # get_mdadm_monitoring()
759 # Returns 1 if mdadm monitoring is enabled, 0 if not
760 sub get_mdadm_monitoring
761 {
762 local $act = &get_mdadm_action();
763 if ($act) {
764         &foreign_require("init");
765         local $st = &init::action_status($act);
766         return $st == 2;
767         }
768 return 0;
769 }
770
771 # save_mdadm_monitoring(enabled)
772 # Tries to enable or disable mdadm monitoring. Returns an error mesage
773 # if something goes wrong, undef on success
774 sub save_mdadm_monitoring
775 {
776 local ($enabled) = @_;
777 local $act = &get_mdadm_action();
778 if ($act) {
779         &foreign_require("init");
780         if ($enabled) {
781                 &init::enable_at_boot($act);
782                 &init::stop_action($act);
783                 sleep(2);
784                 local ($ok, $err) = &init::start_action($act);
785                 return $err if (!$ok);
786                 }
787         else {
788                 &init::disable_at_boot($act);
789                 &init::stop_action($act);
790                 }
791         }
792 return undef;
793 }
794
795 # update_initramfs()
796 # If the update-initramfs command is installed, run it to update mdadm.conf
797 # in the ramdisk
798 sub update_initramfs
799 {
800 if (&has_command("update-initramfs")) {
801         &system_logged("update-initramfs -u >/dev/null 2>&1 </dev/null");
802         }
803 }
804
805 # get_mdadm_version()
806 # Returns the mdadm version number
807 sub get_mdadm_version
808 {
809 local $out = `mdadm --version 2>&1`;
810 local $ver = $out =~ /\s+v([0-9\.]+)/ ? $1 : undef;
811 return wantarray ? ( $ver, $out ) : $ver;
812 }
813
814 1;
815