Handle hostnames with upper-case letters
[webmin.git] / lvm / lvm-lib.pl
1 # lvm-lib.pl
2 # Common functions for managing VGs, PVs and LVs
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 &foreign_require("mount");
8 if (&foreign_check("raid")) {
9         &foreign_require("raid");
10         $has_raid++;
11         }
12 &foreign_require("fdisk");
13
14 $lvm_proc = "/proc/lvm";
15 $lvm_tab = "/etc/lvmtab";
16
17 # list_physical_volumes(vg)
18 # Returns a list of all physical volumes for some volume group
19 sub list_physical_volumes
20 {
21 local @rv;
22 if (-d $lvm_proc) {
23         # Get list from /proc/lvm
24         opendir(DIR, "$lvm_proc/VGs/$_[0]/PVs");
25         foreach $f (readdir(DIR)) {
26                 next if ($f eq '.' || $f eq '..');
27                 local $pv = { 'name' => $f,
28                               'vg' => $_[0] };
29                 local %p = &parse_colon_file("$lvm_proc/VGs/$_[0]/PVs/$f");
30                 $pv->{'device'} = $p{'name'};
31                 $pv->{'number'} = $p{'number'};
32                 $pv->{'size'} = $p{'size'}/2;
33                 $pv->{'status'} = $p{'status'};
34                 $pv->{'number'} = $p{'number'};
35                 $pv->{'pe_size'} = $p{'PE size'};
36                 $pv->{'pe_total'} = $p{'PE total'};
37                 $pv->{'pe_alloc'} = $p{'PE allocated'};
38                 $pv->{'alloc'} = $p{'allocatable'} == 2 ? 'y' : 'n';
39                 push(@rv, $pv);
40                 }
41         closedir(DIR);
42         }
43 else {
44         # Use pvdisplay command
45         local $pv;
46         local $_;
47         open(DISPLAY, "pvdisplay 2>/dev/null |");
48         while(<DISPLAY>) {
49                 s/\r|\n//g;
50                 if (/PV\s+Name\s+(.*)/i) {
51                         $pv = { 'name' => $1,
52                                 'device' => $1,
53                                 'number' => scalar(@rv) };
54                         $pv->{'name'} =~ s/^\/dev\///;
55                         push(@rv, $pv);
56                         }
57                 elsif (/VG\s+Name\s+(.*)/i) {
58                         $pv->{'vg'} = $1;
59                         $pv->{'vg'} =~ s/\s+\(.*\)//;
60                         }
61                 elsif (/PV\s+Size\s+(\S+)\s+(\S+)/i) {
62                         $pv->{'size'} = &mult_units($1, $2);
63                         }
64                 elsif (/PE\s+Size\s+\(\S+\)\s+(\S+)/i) {
65                         $pv->{'pe_size'} = $1;
66                         }
67                 elsif (/PE\s+Size\s+(\S+)\s+(\S+)/i) {
68                         $pv->{'pe_size'} = &mult_units($1, $2);
69                         }
70                 elsif (/Total\s+PE\s+(\S+)/i) {
71                         $pv->{'pe_total'} = $1;
72                         }
73                 elsif (/Allocated\s+PE\s+(\S+)/i) {
74                         $pv->{'pe_alloc'} = $1;
75                         }
76                 elsif (/Allocatable\s+(\S+)/i) {
77                         $pv->{'alloc'} = lc($1) eq 'yes' ? 'y' : 'n';
78                         }
79                 }
80         close(DISPLAY);
81         @rv = grep { $_->{'vg'} eq $_[0] } @rv;
82         }
83 return @rv;
84 }
85
86 # get_physical_volume_usage(&lv)
87 # Returns a list of LVs and blocks used on this physical volume
88 sub get_physical_volume_usage
89 {
90 local @rv;
91 open(DISPLAY, "pvdisplay -m ".quotemeta($_[0]->{'device'})." 2>/dev/null |");
92 local $lastlen;
93 while(<DISPLAY>) {
94         if (/Physical\s+extent\s+(\d+)\s+to\s+(\d+)/) {
95                 $lastlen = $2 - $1 + 1;
96                 }
97         elsif (/Logical\s+volume\s+\/dev\/(\S+)\/(\S+)/) {
98                 push(@rv, [ $2, $lastlen ]);
99                 }
100         }
101 close(DISPLAY);
102 return @rv;
103 }
104
105 # create_physical_volume(&pv, [force])
106 # Add a new physical volume to a volume group
107 sub create_physical_volume
108 {
109 local $cmd = "pvcreate -y ".($_[1] ? "-ff " : "-f ");
110 $cmd .= quotemeta($_[0]->{'device'});
111 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
112 return $out if ($?);
113 $cmd = "vgextend ".quotemeta($_[0]->{'vg'})." ".quotemeta($_[0]->{'device'});
114 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
115 return $? ? $out : undef;
116 }
117
118 # change_physical_volume(&pv)
119 # Change the allocation flag for a physical volume
120 sub change_physical_volume
121 {
122 local $cmd = "pvchange -x ".quotemeta($_[0]->{'alloc'}).
123              " ".quotemeta($_[0]->{'device'});
124 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
125 return $? ? $out : undef;
126 }
127
128 # delete_physical_volume(&pv)
129 # Remove a physical volume from a volume group
130 sub delete_physical_volume
131 {
132 if ($_[0]->{'pe_alloc'}) {
133         local $cmd;
134         if (&get_lvm_version() >= 2) {
135                 $cmd = "yes | pvmove ".quotemeta($_[0]->{'device'});
136                 }
137         else {
138                 $cmd = "pvmove -f ".quotemeta($_[0]->{'device'});
139                 }
140         local $out = &backquote_logged("$cmd 2>&1");
141         return $out if ($? && $out !~ /\-\-\s+f/);
142         }
143 local $cmd = "vgreduce ".quotemeta($_[0]->{'vg'})." ".
144              quotemeta($_[0]->{'device'});
145 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
146 return $? ? $out : undef;
147 }
148
149 # resize_physical_volume(&pv)
150 # Set the size of a physical volume to match the underlying device
151 sub resize_physical_volume
152 {
153 local $cmd = "pvresize ".quotemeta($_[0]->{'device'});
154 local $out = &backquote_logged("$cmd 2>&1");
155 return $? ? $out : undef;
156 }
157
158 # list_volume_groups()
159 # Returns a list of all volume groups
160 sub list_volume_groups
161 {
162 local (@rv, $f);
163 if (-d $lvm_proc) {
164         # Can scan /proc/lvm
165         opendir(DIR, "$lvm_proc/VGs");
166         foreach $f (readdir(DIR)) {
167                 next if ($f eq '.' || $f eq '..');
168                 local $vg = { 'name' => $f };
169                 local %g = &parse_colon_file("$lvm_proc/VGs/$f/group");
170                 $vg->{'number'} = $g{'number'};
171                 $vg->{'size'} = $g{'size'};
172                 $vg->{'pe_size'} = $g{'PE size'};
173                 $vg->{'pe_total'} = $g{'PE total'};
174                 $vg->{'pe_alloc'} = $g{'PE allocated'};
175                 push(@rv, $vg);
176                 }
177         closedir(DIR);
178         }
179 else {
180         # Parse output of vgdisplay
181         local $vg;
182         open(DISPLAY, "vgdisplay 2>/dev/null |");
183         while(<DISPLAY>) {
184                 s/\r|\n//g;
185                 if (/VG\s+Name\s+(.*)/i) {
186                         $vg = { 'name' => $1 };
187                         push(@rv, $vg);
188                         }
189                 elsif (/VG\s+Size\s+(\S+)\s+(\S+)/i) {
190                         $vg->{'size'} = &mult_units($1, $2);
191                         }
192                 elsif (/PE\s+Size\s+(\S+)\s+(\S+)/i) {
193                         $vg->{'pe_size'} = &mult_units($1, $2);
194                         }
195                 elsif (/Total\s+PE\s+(\d+)/i) {
196                         $vg->{'pe_total'} = $1;
197                         }
198                 elsif (/Alloc\s+PE\s+\/\s+Size\s+(\d+)/i) {
199                         $vg->{'pe_alloc'} = $1;
200                         }
201                 }
202         close(DISPLAY);
203         }
204 return @rv;
205 }
206
207 sub mult_units
208 {
209 local ($n, $u) = @_;
210 return $n*(uc($u) eq "KB" || uc($u) eq "KIB" ? 1 :
211            uc($u) eq "MB" || uc($u) eq "MIB" ? 1024 :
212            uc($u) eq "GB" || uc($u) eq "GIB" ? 1024*1024 :
213            uc($u) eq "TB" || uc($u) eq "TIB" ? 1024*1024*1024 :
214            uc($u) eq "PB" || uc($u) eq "PIB" ? 1024*1024*1024 : 1);
215 }
216
217 # delete_volume_group(&vg)
218 sub delete_volume_group
219 {
220 local $cmd = "vgchange -a n ".quotemeta($_[0]->{'name'});
221 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
222 return $out if ($?);
223 $cmd = "vgremove ".quotemeta($_[0]->{'name'});
224 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
225 return $? ? $out : undef;
226 }
227
228 # create_volume_group(&vg, device)
229 sub create_volume_group
230 {
231 &system_logged("vgscan >/dev/null 2>&1 </dev/null");
232 local $cmd = "pvcreate -f -y ".quotemeta($_[1]);
233 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
234 return $out if ($?);
235 $cmd = "vgcreate";
236 $cmd .= " -s ".quotemeta($_[0]->{'pe_size'})."k" if ($_[0]->{'pe_size'});
237 $cmd .= " ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]);
238 $out = &backquote_logged("$cmd 2>&1 </dev/null");
239 return $? ? $out : undef;
240 }
241
242 # rename_volume_group(&vg, name)
243 sub rename_volume_group
244 {
245 local $cmd = "vgrename ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]);
246 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
247 return $out if ($?);
248 $cmd = "vgchange -a n ".quotemeta($_[1]);
249 $out = &backquote_logged("$cmd 2>&1 </dev/null");
250 return $out if ($?);
251 $cmd = "vgchange -a y ".quotemeta($_[1]);
252 $out = &backquote_logged("$cmd 2>&1 </dev/null");
253 return $? ? $out : undef;
254 }
255
256
257 # list_logical_volumes(vg)
258 sub list_logical_volumes
259 {
260 local @rv;
261 if (-d $lvm_proc) {
262         # Get LVs from /proc/lvm
263         opendir(DIR, "$lvm_proc/VGs/$_[0]/LVs");
264         foreach $f (readdir(DIR)) {
265                 next if ($f eq '.' || $f eq '..');
266                 local $lv = { 'name' => $f,
267                               'vg' => $_[0] };
268                 local %p = &parse_colon_file("$lvm_proc/VGs/$_[0]/LVs/$f");
269                 $lv->{'device'} = $p{'name'};
270                 $lv->{'number'} = $p{'number'};
271                 $lv->{'size'} = $p{'size'}/2;
272                 $lv->{'perm'} = $p{'access'} == 3 ? 'rw' : 'r';
273                 $lv->{'alloc'} = $p{'allocation'} == 2 ? 'y' : 'n';
274                 $lv->{'has_snap'} = $p{'access'} == 11;
275                 $lv->{'is_snap'} = $p{'access'} == 5;
276                 $lv->{'stripes'} = $p{'stripes'};
277                 push(@rv, $lv);
278
279                 # For snapshots, use the lvdisplay command to get usage
280                 if ($lv->{'is_snap'}) {
281                         local $out = &backquote_command(
282                                 "lvdisplay ".quotemeta($lv->{'device'}).
283                                 " 2>/dev/null");
284                         if ($out =~/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) {
285                                 $lv->{'snapusage'} = $1;
286                                 }
287                         }
288                 }
289         closedir(DIR);
290         }
291 else {
292         # Use the lvdisplay command
293         local $lv;
294         local $_;
295         local ($vg) = grep { $_->{'name'} eq $_[0] } &list_volume_groups();
296         open(DISPLAY, "lvdisplay -m 2>/dev/null |");
297         while(<DISPLAY>) {
298                 s/\r|\n//g;
299                 if (/LV\s+Name\s+(.*\/(\S+))/i) {
300                         $lv = { 'name' => $2,
301                                 'device' => $1,
302                                 'number' => scalar(@rv) };
303                         push(@rv, $lv);
304                         }
305                 elsif (/VG\s+Name\s+(.*)/) {
306                         $lv->{'vg'} = $1;
307                         }
308                 elsif (/LV\s+Size\s+(\S+)\s+(\S+)/i) {
309                         $lv->{'size'} = &mult_units($1, $2);
310                         }
311                 elsif (/Current\s+LE\s+(\d+)/ && $vg) {
312                         $lv->{'size'} = $1 * $vg->{'pe_size'};
313                         }
314                 elsif (/LV\s+Write\s+Access\s+(\S+)/i) {
315                         $lv->{'perm'} = $1 eq 'read/write' ? 'rw' : 'r';
316                         }
317                 elsif (/Allocation\s+(.*)/i) {
318                         $lv->{'alloc'} = $1 eq 'contiguous' ? 'y' : 'n';
319                         }
320                 elsif (/LV\s+snapshot\s+status\s+(.*)/i) {
321                         if ($1 =~ /source/) {
322                                 $lv->{'has_snap'} = 1;
323                                 }
324                         else {
325                                 $lv->{'is_snap'} = 1;
326                                 }
327                         if (/destination\s+for\s+\/dev\/[^\/]+\/(\S+)/) {
328                                 $lv->{'snap_of'} = $1;
329                                 }
330                         }
331                  elsif (/Read ahead sectors\s+(\d+)/) {
332                         $lv->{'readahead'} = $1;
333                         }
334                 elsif (/Stripes\s+(\d+)/) {
335                         $lv->{'stripes'} = $1;
336                         }
337                 elsif (/Stripe\s+size\s+(\S+)\s+(\S+)/) {
338                         $lv->{'stripesize'} = &mult_units($1, $2);
339                         }
340                 elsif (/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) {
341                         $lv->{'snapusage'} = $1;
342                         }
343                 }
344         close(DISPLAY);
345         @rv = grep { $_->{'vg'} eq $_[0] } @rv;
346         }
347 return @rv;
348 }
349
350 # get_logical_volume_usage(&lv)
351 # Returns a list of PVs and blocks used by this logical volume. Each is an
352 # array ref of : device physical-blocks reads writes
353 sub get_logical_volume_usage
354 {
355 local @rv;
356 if (&get_lvm_version() >= 2) {
357         # LVdisplay has new format in version 2
358         open(DISPLAY, "lvdisplay -m ".quotemeta($_[0]->{'device'})." 2>/dev/null |");
359         while(<DISPLAY>) {
360                 if (/\s+Physical\s+volume\s+\/dev\/(\S+)/) {
361                         push(@rv, [ $1, undef ]);
362                         }
363                 elsif (/\s+Physical\s+extents\s+(\d+)\s+to\s+(\d+)/ && @rv) {
364                         $rv[$#rv]->[1] = $2-$1+1;
365                         }
366                 }
367         close(DISPLAY);
368         }
369 else {
370         # Old version 1 format
371         open(DISPLAY, "lvdisplay -v ".quotemeta($_[0]->{'device'})." 2>/dev/null |");
372         local $started;
373         while(<DISPLAY>) {
374                 if (/^\s*PV\s+Name/i) {
375                         $started = 1;
376                         }
377                 elsif ($started && /^\s*\/dev\/(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/) {
378                         push(@rv, [ $1, $2, $3, $4 ]);
379                         }
380                 elsif ($started) {
381                         last;
382                         }
383                 }
384         close(DISPLAY);
385         }
386 return @rv;
387 }
388
389 # create_logical_volume(&lv)
390 sub create_logical_volume
391 {
392 local $cmd = "lvcreate -n".quotemeta($_[0]->{'name'})." ";
393 local $suffix;
394 if ($_[0]->{'size_of'} eq 'VG' || $_[0]->{'size_of'} eq 'FREE') {
395         $cmd .= "-l ".quotemeta("$_[0]->{'size'}%$_[0]->{'size_of'}");
396         }
397 elsif ($_[0]->{'size_of'}) {
398         $cmd .= "-l $_[0]->{'size'}%PVS";
399         $suffix = " ".quotemeta("/dev/".$_[0]->{'size_of'});
400         }
401 else {
402         $cmd .= "-L$_[0]->{'size'}k";
403         }
404 if ($_[0]->{'is_snap'}) {
405         $cmd .= " -s ".quotemeta("/dev/$_[0]->{'vg'}/$_[0]->{'snapof'}");
406         }
407 else {
408         $cmd .= " -p ".quotemeta($_[0]->{'perm'});
409         $cmd .= " -C ".quotemeta($_[0]->{'alloc'});
410         $cmd .= " -r ".quotemeta($_[0]->{'readahead'})
411                 if ($_[0]->{'readahead'});
412         $cmd .= " -i ".quotemeta($_[0]->{'stripe'})
413                 if ($_[0]->{'stripe'});
414         $cmd .= " -I ".quotemeta($_[0]->{'stripesize'})
415                 if ($_[0]->{'stripesize'} && $_[0]->{'stripe'});
416         $cmd .= " ".quotemeta($_[0]->{'vg'});
417         }
418 $cmd .= $suffix;
419 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
420 return $? ? $out : undef;
421 }
422
423 # delete_logical_volume(&lv)
424 sub delete_logical_volume
425 {
426 local $cmd = "lvremove -f ".quotemeta($_[0]->{'device'});
427 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
428 return $? ? $out : undef;
429 }
430
431 # resize_logical_volume(&lv, size)
432 sub resize_logical_volume
433 {
434 local $cmd = $_[1] > $_[0]->{'size'} ? "lvextend" : "lvreduce -f";
435 $cmd .= " -L".quotemeta($_[1])."k";
436 $cmd .= " ".quotemeta($_[0]->{'device'});
437 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
438 return $? ? $out : undef;
439 }
440
441 # change_logical_volume(&lv, [&old-lv])
442 sub change_logical_volume
443 {
444 local $cmd = "lvchange ";
445 $cmd .= " -p ".quotemeta($_[0]->{'perm'})
446         if (!$_[1] || $_[0]->{'perm'} ne $_[1]->{'perm'});
447 $cmd .= " -r ".quotemeta($_[0]->{'readahead'})
448         if (!$_[1] || $_[0]->{'readahead'} ne $_[1]->{'readahead'});
449 $cmd .= " -C ".quotemeta($_[0]->{'alloc'})
450         if (!$_[1] || $_[0]->{'alloc'} ne $_[1]->{'alloc'});
451 $cmd .= " ".quotemeta($_[0]->{'device'});
452 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
453 return $? ? $out : undef;
454 }
455
456 # rename_logical_volume(&lv, name)
457 sub rename_logical_volume
458 {
459 local $cmd = "lvrename ".quotemeta($_[0]->{'device'})." ".
460              quotemeta("/dev/$_[0]->{'vg'}/$_[1]");
461 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
462 return $? ? $out : undef;
463 }
464
465 # can_resize_filesystem(type)
466 # 0 = no, 1 = enlarge only, 2 = enlarge or shrink
467 sub can_resize_filesystem
468 {
469 if ($_[0] =~ /^ext\d+$/) {
470         if (&has_command("e2fsadm")) {
471                 return 2;       # Can extend and reduce
472                 }
473         elsif (&has_command("resize2fs")) {
474                 # Only new versions can reduce a FS
475                 local $out = &backquote_command("resize2fs 2>&1");
476                 return $out =~ /resize2fs\s+([0-9\.]+)/i && $1 >= 1.4 ? 2 : 1;
477                 }
478         else {
479                 return 0;
480                 }
481         }
482 elsif ($_[0] eq "xfs") {
483         return &has_command("xfs_growfs") ? 1 : 0;
484         }
485 elsif ($_[0] eq "reiserfs") {
486         return &has_command("resize_reiserfs") ? 2 : 0;
487         }
488 elsif ($_[0] eq "jfs") {
489         return 1;
490         }
491 else {
492         return 0;
493         }
494 }
495
496 # can_resize_lv_stat(dir, type, mounted)
497 # Returns 1 if some LV can be enlarged, 2 if enlarged or shrunk, or 0
498 # if neither, based on the details provided by device_status
499 sub can_resize_lv_stat
500 {
501 local ($dir, $type, $mounted) = @_;
502 if (!$type) {
503         # No FS known, assume can resize safely
504         return 2;
505         }
506 else {
507         my $can = &can_resize_filesystem($type);
508         if ($can && $mounted) {
509                 # If currently mounted, check if resizing is possible
510                 if ($dir eq "/") {
511                         # Cannot resize root
512                         $can = 0;
513                         }
514                 elsif ($type =~ /^ext[3-9]$/ || $type eq "xfs" ||
515                        $type eq "reiserfs" || $type eq "jfs") {
516                         # ext*, xfs, jfs and reiserfs can be resized up
517                         $can = 1;
518                         }
519                 else {
520                         # Nothing else can
521                         $can = 0;
522                         }
523                 }
524         return $can;
525         }
526 }
527
528 # resize_filesystem(&lv, type, size)
529 sub resize_filesystem
530 {
531 if ($_[1] =~ /^ext\d+$/) {
532         &foreign_require("proc");
533         if (&has_command("e2fsadm")) {
534                 # The e2fsadm command can re-size an LVM and filesystem together
535                 local $cmd = "e2fsadm -v -L ".quotemeta($_[2])."k ".
536                              quotemeta($_[0]->{'device'});
537                 local ($fh, $fpid) = &proc::pty_process_exec($cmd);
538                 print $fh "yes\n";
539                 local $out;
540                 while(<$fh>) {
541                         $out .= $_;
542                         }
543                 close($fh);
544                 waitpid($fpid, 0);
545                 &additional_log("exec", undef, $cmd);
546                 return $? ? $out : undef;
547                 }
548         else {
549                 if ($_[2] > $_[0]->{'size'}) {
550                         # Need to enlarge LV first, then filesystem
551                         local $err = &resize_logical_volume($_[0], $_[2]);
552                         return $err if ($err);
553
554                         local $cmd = "resize2fs -f ".
555                                      quotemeta($_[0]->{'device'});
556                         local $out = &backquote_logged("$cmd 2>&1");
557                         return $? ? $out : undef;
558                         }
559                 else {
560                         # Need to shrink filesystem first, then LV
561                         local $cmd = "resize2fs -f ".
562                                      quotemeta($_[0]->{'device'})." ".
563                                      quotemeta($_[2])."k";
564                         local $out = &backquote_logged("$cmd 2>&1");
565                         return $out if ($?);
566
567                         local $err = &resize_logical_volume($_[0], $_[2]);
568                         return $err;
569                         }
570                 }
571         }
572 elsif ($_[1] eq "xfs") {
573         # Resize the logical volume first
574         local $err = &resize_logical_volume($_[0], $_[2]);
575         return $err if ($err);
576
577         # Resize the filesystem .. which must be mounted!
578         local @stat = &device_status($_[0]->{'device'});
579         local ($m, $mount);
580         foreach $m (&mount::list_mounts()) {
581                 if ($m->[1] eq $_[0]->{'device'}) {
582                         $mount = $m;
583                         }
584                 }
585         if (!$stat[2]) {
586                 $mount || return "Mount not found";
587                 &mount::mount_dir(@$mount);
588                 }
589         local $cmd = "xfs_growfs ".quotemeta($stat[0] || $mount->[0]);
590         local $out = &backquote_logged("$cmd 2>&1");
591         local $q = $?;
592         if (!$stat[2]) {
593                 &mount::unmount_dir(@$mount);
594                 }
595         return $q ? $out : undef;
596         }
597 elsif ($_[1] eq "reiserfs") {
598         if ($_[2] > $_[0]->{'size'}) {
599                 # Enlarge the logical volume first
600                 local $err = &resize_logical_volume($_[0], $_[2]);
601                 return $err if ($err);
602
603                 # Now enlarge the reiserfs filesystem
604                 local $cmd = "resize_reiserfs ".quotemeta($_[0]->{'device'});
605                 local $out = &backquote_logged("$cmd 2>&1");
606                 return $? ? $out : undef;
607                 }
608         else {
609                 # Try to shrink the filesystem
610                 local $cmd = "yes | resize_reiserfs -s ".
611                              quotemeta($_[2])."K ".quotemeta($_[0]->{'device'});
612                 local $out = &backquote_logged("$cmd 2>&1");
613                 return $out if ($?);
614
615                 # Now shrink the logical volume
616                 local $err = &resize_logical_volume($_[0], $_[2]);
617                 return $err ? $err : undef;
618                 }
619         }
620 elsif ($_[1] eq "jfs") {
621         # Enlarge the logical volume first
622         local $err = &resize_logical_volume($_[0], $_[2]);
623         return $err if ($err);
624
625         # Now enlarge the jfs filesystem with a remount - must be mounted first
626         local @stat = &device_status($_[0]->{'device'});
627         local ($m, $mount);
628         foreach $m (&mount::list_mounts()) {
629                 if ($m->[1] eq $_[0]->{'device'}) {
630                         $mount = $m;
631                         }
632                 }
633         if (!$stat[2]) {
634                 $mount || return "Mount not found";
635                 &mount::mount_dir(@$mount);
636                 }
637         local $ropts = $mount->[3];
638         $ropts = $ropts eq "-" ? "resize,remount" : "$ropts,resize,remount";
639         local $err = &mount::mount_dir($mount->[0], $mount->[1],
640                                        $mount->[2], $ropts);
641         if (!$stat[2]) {
642                 &mount::unmount_dir(@$mount);
643                 }
644         return $err ? $err : undef;
645         }
646 else {
647         return "???";
648         }
649 }
650
651
652 # parse_colon_file(file)
653 sub parse_colon_file
654 {
655 local %rv;
656 open(FILE, $_[0]);
657 while(<FILE>) {
658         if (/^([^:]+):\s*(.*)/) {
659                 $rv{$1} = $2;
660                 }
661         }
662 close(FILE);
663 return %rv;
664 }
665
666 # device_status(device)
667 # Returns an array of  directory, type, mounted
668 sub device_status
669 {
670 @mounted = &mount::list_mounted() if (!@mounted);
671 @mounts = &mount::list_mounts() if (!@mounts);
672 my $label = &fdisk::get_label($_[0]);
673
674 my ($mounted) = grep { &same_file($_->[1], $_[0]) ||
675                           $_->[1] eq "LABEL=$label" } @mounted;
676 my ($mount) = grep { &same_file($_->[1], $_[0]) ||
677                         $_->[1] eq "LABEL=$label" } @mounts;
678 if ($mounted) { return ($mounted->[0], $mounted->[2], 1,
679                         &indexof($mount, @mounts),
680                         &indexof($mounted, @mounted)); }
681 elsif ($mount) { return ($mount->[0], $mount->[2], 0,
682                          &indexof($mount, @mounts)); }
683 elsif ($has_raid) {
684         $raidconf = &raid::get_raidtab() if (!$raidconf);
685         foreach my $c (@$raidconf) {
686                 foreach my $d (&raid::find_value('device', $c->{'members'})) {
687                         return ( $c->{'value'}, "raid", 1 ) if ($d eq $_[0]);
688                         }
689                 }
690         }
691 if (&foreign_check("server-manager")) {
692         # Look for Cloudmin systems using the disk, hosted on this system
693         if (!@server_manager_systems) {
694                 &foreign_require("server-manager");
695                 @server_manager_systems =
696                         grep { my $p = &server_manager::get_parent_server($_);
697                                $p && $p->{'id'} eq '0' }
698                              &server_manager::list_managed_servers();
699                 }
700         foreach my $s (@server_manager_systems) {
701                 if ($s->{$s->{'manager'}.'_filesystem'} eq $_[0]) {
702                         return ( $s->{'host'}, 'cloudmin', 
703                                  $s->{'status'} ne 'down' );
704                         }
705                 my $ffunc = "type_".$s->{'manager'}."_list_disk_devices";
706                 if (&foreign_defined("server-manager", $ffunc)) {
707                         my @disks = &foreign_call("server-manager", $ffunc, $s);
708                         if (&indexof($_[0], @disks) >= 0) {
709                                 return ( $s->{'host'}, 'cloudmin', 
710                                          $s->{'status'} ne 'down' );
711                                 }
712                         }
713                 }
714         }
715 return ();
716 }
717
718 # device_message(stat)
719 # Returns a text string about the status of an LV
720 sub device_message
721 {
722 my $msg;
723 if ($_[1] eq 'cloudmin') {
724         # Used by Cloudmin system
725         $msg = $_[2] ? 'lv_mountcm' : 'lv_umountcm';
726         return &text($msg, "<tt>$_[0]</tt>");
727         }
728 else {
729         # Used by filesystem or RAID
730         $msg = $_[2] ? 'lv_mount' : 'lv_umount';
731         $msg .= 'vm' if ($_[1] eq 'swap');
732         $msg .= 'raid' if ($_[1] eq 'raid');
733         return &text($msg, "<tt>$_[0]</tt>", "<tt>$_[1]</tt>");
734         }
735 }
736
737 # list_lvmtab()
738 sub list_lvmtab
739 {
740 local @rv;
741 open(TAB, $lvm_tab);
742 local $/ = "\0";
743 while(<TAB>) {
744         chop;
745         push(@rv, $_) if ($_);
746         }
747 close(TAB);
748 return @rv;
749 }
750
751 # device_input()
752 # Returns a selector for a free device
753 sub device_input
754 {
755 local (%used, $vg, $pv, $d, $p);
756
757 # Find partitions that are part of an LVM
758 foreach $vg (&list_volume_groups()) {
759         foreach $pv (&list_physical_volumes($vg->{'name'})) {
760                 $used{$pv->{'device'}}++;
761                 }
762         }
763
764 # Show available partitions
765 local @opts;
766 foreach $d (&fdisk::list_disks_partitions()) {
767         foreach $p (@{$d->{'parts'}}) {
768                 next if ($used{$p->{'device'}} || $p->{'extended'});
769                 local @ds = &device_status($p->{'device'});
770                 next if (@ds);
771                 if ($p->{'type'} eq '83' || $p->{'type'} eq 'ext2') {
772                         local $label = &fdisk::get_label($p->{'device'});
773                         next if ($used{"LABEL=$label"});
774                         }
775                 local $tag = &fdisk::tag_name($p->{'type'});
776                 push(@opts, [ $p->{'device'},
777                         $p->{'desc'}.
778                         ($tag ? " ($tag)" : "").
779                         ($d->{'cylsize'} ? " (".&nice_size($d->{'cylsize'}*($p->{'end'} - $p->{'start'} + 1)).")" :
780                         " ($p->{'blocks'} $text{'blocks'})") ]);
781                 }
782         }
783
784 # Show available RAID devices
785 local $conf = &raid::get_raidtab();
786 foreach $c (@$conf) {
787         next if ($used{$c->{'value'}});
788         local @ds = &device_status($c->{'value'});
789         next if (@ds);
790         push(@opts, [ $c->{'value'}, &text('pv_raid', $c->{'value'} =~ /md(\d+)$/ ? "$1" : $c->{'value'}) ]);
791         }
792
793 push(@opts, [ '', $text{'pv_other'} ]);
794 return &ui_select("device", $opts[0]->[0], \@opts)." ".
795        &ui_textbox("other", undef, 30)." ".&file_chooser_button("other").
796        "<br>\n<b>$text{'pv_warn'}</b>";
797 }
798
799 # get_lvm_version()
800 # Returns the lvm version number and optionally output from the vgdisplay
801 # command used to get it.
802 sub get_lvm_version
803 {
804 local $out = `vgdisplay --version 2>&1`;
805 local $ver = $out =~ /\s+([0-9\.]+)/ ? $1 : undef;
806 return wantarray ? ( $ver, $out ) : $ver;
807 }
808
809 # nice_round(number)
810 # Round some number to TB, GB, MB or kB, depending on size
811 sub nice_round
812 {
813 local ($bytes) = @_;
814 my $units;
815 if ($bytes >= 10*1024*1024*1024*1024) {
816         $units = 1024*1024*1024*1024;
817         }
818 elsif ($bytes >= 10*1024*1024*1024) {
819         $units = 1024*1024*1024;
820         }
821 elsif ($bytes >= 10*1024*1024) {
822         $units = 1024*1024;
823         }
824 elsif ($bytes >= 10*1024) {
825         $units = 1024;
826         }
827 else {
828         $units = 1;
829         }
830 return int($bytes / $units) * $units;
831 }
832
833 1;
834