1# lvm-lib.pl
2# Common functions for managing VGs, PVs and LVs
3
4BEGIN { push(@INC, ".."); };
5use WebminCore;
6&init_config();
7&foreign_require("mount");
8if (&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
19sub list_physical_volumes
20{
21local @rv;
22if (-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	}
43else {
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	@rv = grep { $_->{'name'} ne 'unknown device' } @rv;
83	}
84return @rv;
85}
86
87# get_physical_volume_usage(&lv)
88# Returns a list of LVs and blocks used on this physical volume
89sub get_physical_volume_usage
90{
91local @rv;
92open(DISPLAY, "pvdisplay -m ".quotemeta($_[0]->{'device'})." 2>/dev/null |");
93local $lastlen;
94while(<DISPLAY>) {
95	if (/Physical\s+extent\s+(\d+)\s+to\s+(\d+)/) {
96		$lastlen = $2 - $1 + 1;
97		}
98	elsif (/Logical\s+volume\s+\/dev\/(\S+)\/(\S+)/) {
99		push(@rv, [ $2, $lastlen ]);
100		}
101	}
102close(DISPLAY);
103return @rv;
104}
105
106# create_physical_volume(&pv, [force])
107# Add a new physical volume to a volume group
108sub create_physical_volume
109{
110local $cmd = "pvcreate -y ".($_[1] ? "-ff " : "-f ");
111$cmd .= quotemeta($_[0]->{'device'});
112local $out = &backquote_logged("$cmd 2>&1 </dev/null");
113return $out if ($?);
114$cmd = "vgextend ".quotemeta($_[0]->{'vg'})." ".quotemeta($_[0]->{'device'});
115local $out = &backquote_logged("$cmd 2>&1 </dev/null");
116return $? ? $out : undef;
117}
118
119# change_physical_volume(&pv)
120# Change the allocation flag for a physical volume
121sub change_physical_volume
122{
123local $cmd = "pvchange -x ".quotemeta($_[0]->{'alloc'}).
124	     " ".quotemeta($_[0]->{'device'});
125local $out = &backquote_logged("$cmd 2>&1 </dev/null");
126return $? ? $out : undef;
127}
128
129# delete_physical_volume(&pv)
130# Remove a physical volume from a volume group
131sub delete_physical_volume
132{
133if ($_[0]->{'pe_alloc'}) {
134	local $cmd;
135	if (&get_lvm_version() >= 2) {
136		$cmd = "yes | pvmove ".quotemeta($_[0]->{'device'});
137		}
138	else {
139		$cmd = "pvmove -f ".quotemeta($_[0]->{'device'});
140		}
141	local $out = &backquote_logged("$cmd 2>&1");
142	return $out if ($? && $out !~ /\-\-\s+f/);
143	}
144local $cmd = "vgreduce ".quotemeta($_[0]->{'vg'})." ".
145	     quotemeta($_[0]->{'device'});
146local $out = &backquote_logged("$cmd 2>&1 </dev/null");
147return $? ? $out : undef;
148}
149
150# resize_physical_volume(&pv)
151# Set the size of a physical volume to match the underlying device
152sub resize_physical_volume
153{
154local $cmd = "pvresize ".quotemeta($_[0]->{'device'});
155local $out = &backquote_logged("$cmd 2>&1");
156return $? ? $out : undef;
157}
158
159# list_volume_groups()
160# Returns a list of all volume groups
161sub list_volume_groups
162{
163local (@rv, $f);
164if (-d $lvm_proc) {
165	# Can scan /proc/lvm
166	opendir(DIR, "$lvm_proc/VGs");
167	foreach $f (readdir(DIR)) {
168		next if ($f eq '.' || $f eq '..');
169		local $vg = { 'name' => $f };
170		local %g = &parse_colon_file("$lvm_proc/VGs/$f/group");
171		$vg->{'number'} = $g{'number'};
172		$vg->{'size'} = $g{'size'};
173		$vg->{'pe_size'} = $g{'PE size'};
174		$vg->{'pe_total'} = $g{'PE total'};
175		$vg->{'pe_alloc'} = $g{'PE allocated'};
176		push(@rv, $vg);
177		}
178	closedir(DIR);
179	}
180else {
181	# Parse output of vgdisplay
182	local $vg;
183	open(DISPLAY, "vgdisplay 2>/dev/null |");
184	while(<DISPLAY>) {
185		s/\r|\n//g;
186		if (/VG\s+Name\s+(.*)/i) {
187			$vg = { 'name' => $1 };
188			push(@rv, $vg);
189			}
190		elsif (/VG\s+Size\s+<?(\S+)\s+(\S+)/i) {
191			$vg->{'size'} = &mult_units($1, $2);
192			}
193		elsif (/PE\s+Size\s+(\S+)\s+(\S+)/i) {
194			$vg->{'pe_size'} = &mult_units($1, $2);
195			}
196		elsif (/Total\s+PE\s+(\d+)/i) {
197			$vg->{'pe_total'} = $1;
198			}
199		elsif (/Alloc\s+PE\s+\/\s+Size\s+(\d+)/i) {
200			$vg->{'pe_alloc'} = $1;
201			}
202		}
203	close(DISPLAY);
204	}
205return @rv;
206}
207
208sub mult_units
209{
210local ($n, $u) = @_;
211return $n*(uc($u) eq "KB" || uc($u) eq "KIB" ? 1 :
212	   uc($u) eq "MB" || uc($u) eq "MIB" ? 1024 :
213	   uc($u) eq "GB" || uc($u) eq "GIB" ? 1024*1024 :
214	   uc($u) eq "TB" || uc($u) eq "TIB" ? 1024*1024*1024 :
215	   uc($u) eq "PB" || uc($u) eq "PIB" ? 1024*1024*1024 : 1);
216}
217
218# delete_volume_group(&vg)
219sub delete_volume_group
220{
221local $cmd = "vgchange -a n ".quotemeta($_[0]->{'name'});
222local $out = &backquote_logged("$cmd 2>&1 </dev/null");
223return $out if ($?);
224$cmd = "vgremove ".quotemeta($_[0]->{'name'});
225local $out = &backquote_logged("$cmd 2>&1 </dev/null");
226return $? ? $out : undef;
227}
228
229# create_volume_group(&vg, device)
230sub create_volume_group
231{
232&system_logged("vgscan >/dev/null 2>&1 </dev/null");
233local $cmd = "pvcreate -f -y ".quotemeta($_[1]);
234local $out = &backquote_logged("$cmd 2>&1 </dev/null");
235return $out if ($?);
236$cmd = "vgcreate";
237$cmd .= " -s ".quotemeta($_[0]->{'pe_size'})."k" if ($_[0]->{'pe_size'});
238$cmd .= " ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]);
239$out = &backquote_logged("$cmd 2>&1 </dev/null");
240return $? ? $out : undef;
241}
242
243# rename_volume_group(&vg, name)
244sub rename_volume_group
245{
246local $cmd = "vgrename ".quotemeta($_[0]->{'name'})." ".quotemeta($_[1]);
247local $out = &backquote_logged("$cmd 2>&1 </dev/null");
248return $out if ($?);
249$cmd = "vgchange -a n ".quotemeta($_[1]);
250$out = &backquote_logged("$cmd 2>&1 </dev/null");
251return $out if ($?);
252$cmd = "vgchange -a y ".quotemeta($_[1]);
253$out = &backquote_logged("$cmd 2>&1 </dev/null");
254return $? ? $out : undef;
255}
256
257
258# list_logical_volumes(vg)
259sub list_logical_volumes
260{
261local @rv;
262if (-d $lvm_proc) {
263	# Get LVs from /proc/lvm
264	opendir(DIR, "$lvm_proc/VGs/$_[0]/LVs");
265	foreach $f (readdir(DIR)) {
266		next if ($f eq '.' || $f eq '..');
267		local $lv = { 'name' => $f,
268			      'vg' => $_[0] };
269		local %p = &parse_colon_file("$lvm_proc/VGs/$_[0]/LVs/$f");
270		$lv->{'device'} = $p{'name'};
271		$lv->{'number'} = $p{'number'};
272		$lv->{'size'} = $p{'size'}/2;
273		$lv->{'perm'} = $p{'access'} == 3 ? 'rw' : 'r';
274		$lv->{'alloc'} = $p{'allocation'} == 2 ? 'y' : 'n';
275		$lv->{'has_snap'} = $p{'access'} == 11;
276		$lv->{'is_snap'} = $p{'access'} == 5;
277		$lv->{'stripes'} = $p{'stripes'};
278		push(@rv, $lv);
279
280		# For snapshots, use the lvdisplay command to get usage
281		if ($lv->{'is_snap'}) {
282			local $out = &backquote_command(
283				"lvdisplay ".quotemeta($lv->{'device'}).
284				" 2>/dev/null");
285			if ($out =~/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) {
286				$lv->{'snapusage'} = $1;
287				}
288			}
289		}
290	closedir(DIR);
291	}
292else {
293	# Use the lvdisplay command
294	local $lv;
295	local $_;
296	local ($vg) = grep { $_->{'name'} eq $_[0] } &list_volume_groups();
297	open(DISPLAY, "lvdisplay -m 2>/dev/null |");
298	while(<DISPLAY>) {
299		s/\r|\n//g;
300		if (/LV\s+(Name|Path)\s+(.*\/(\S+))/i) {
301			$lv = { 'name' => $3,
302				'device' => $2,
303				'number' => scalar(@rv) };
304			push(@rv, $lv);
305			}
306		elsif (/LV\s+Name\s+([^\/ ]+)/) {
307			# Ignore this, as we got the name from LV Path line.
308			# UNLESS it doesn't match the current LV, in which
309			# case it is the start of a new thinpool LV.
310			if (!@rv || $rv[$#rv]->{'name'} ne $1) {
311				$lv = { 'name' => $1,
312					'device' => $1,
313					'thin' => $1,
314					'number' => scalar(@rv) };
315				push(@rv, $lv);
316				}
317			}
318		elsif (/VG\s+Name\s+(.*)/) {
319			$lv->{'vg'} = $1;
320			}
321		elsif (/LV\s+Size\s+<?(\S+)\s+(\S+)/i) {
322			$lv->{'size'} = &mult_units($1, $2);
323			}
324		elsif (/Current\s+LE\s+(\d+)/ && $vg) {
325			$lv->{'size'} = $1 * $vg->{'pe_size'};
326			}
327		elsif (/COW-table\s+LE\s+(\d+)/ && $vg) {
328			$lv->{'cow_size'} = $1 * $vg->{'pe_size'};
329			}
330		elsif (/LV\s+Write\s+Access\s+(\S+)/i) {
331			$lv->{'perm'} = $1 eq 'read/write' ? 'rw' : 'r';
332			}
333		elsif (/Allocation\s+(.*)/i) {
334			$lv->{'alloc'} = $1 eq 'contiguous' ? 'y' : 'n';
335			}
336		elsif (/LV\s+snapshot\s+status\s+(.*)/i) {
337			if ($1 =~ /source/) {
338				$lv->{'has_snap'} = 1;
339				}
340			else {
341				$lv->{'is_snap'} = 1;
342				}
343			if (/destination\s+for\s+\/dev\/[^\/]+\/(\S+)/) {
344				$lv->{'snap_of'} = $1;
345				}
346			if (/active\s+destination/i) {
347				$lv->{'snap_active'} = 1;
348				}
349			elsif (/INACTIVE\s+destination/i) {
350				$lv->{'snap_active'} = 0;
351				}
352			}
353		elsif (/LV\s+Thin\s+origin\s+name\s+(\S+)/) {
354			# Snapshot in a thin pool
355			$lv->{'is_snap'} = 1;
356			$lv->{'snap_of'} = $1;
357			}
358		elsif (/Read ahead sectors\s+(\d+|auto)/) {
359                        $lv->{'readahead'} = $1;
360                        }
361		elsif (/Stripes\s+(\d+)/) {
362			$lv->{'stripes'} = $1;
363			}
364		elsif (/Stripe\s+size\s+(\S+)\s+(\S+)/) {
365			$lv->{'stripesize'} = &mult_units($1, $2);
366			}
367		elsif (/Allocated\s+to\s+snapshot\s+([0-9\.]+)%/i) {
368			$lv->{'snapusage'} = $1;
369			}
370		elsif (/LV\s+Pool\s+name\s+(\S+)/) {
371			$lv->{'thin_in'} = $1;
372			}
373		elsif (/Allocated\s+pool\s+data\s+(\S+)%/) {
374			$lv->{'thin_percent'} = $1;
375			$lv->{'thin_used'} = $1 / 100.0 * $lv->{'size'};
376			}
377		}
378	close(DISPLAY);
379	@rv = grep { $_->{'vg'} eq $_[0] } @rv;
380	}
381return @rv;
382}
383
384# get_logical_volume_usage(&lv)
385# Returns a list of PVs and blocks used by this logical volume. Each is an
386# array ref of : device physical-blocks reads writes
387sub get_logical_volume_usage
388{
389local ($lv) = @_;
390local @rv;
391if (&get_lvm_version() >= 2) {
392	# LVdisplay has new format in version 2
393	open(DISPLAY, "lvdisplay -m ".quotemeta($lv->{'device'})." 2>/dev/null |");
394	while(<DISPLAY>) {
395		if (/\s+Physical\s+volume\s+\/dev\/(\S+)/) {
396			push(@rv, [ $1, undef ]);
397			}
398		elsif (/\s+Physical\s+extents\s+(\d+)\s+to\s+(\d+)/ && @rv) {
399			$rv[$#rv]->[1] = $2-$1+1;
400			}
401		}
402	close(DISPLAY);
403	}
404else {
405	# Old version 1 format
406	open(DISPLAY, "lvdisplay -v ".quotemeta($lv->{'device'})." 2>/dev/null |");
407	local $started;
408	while(<DISPLAY>) {
409		if (/^\s*PV\s+Name/i) {
410			$started = 1;
411			}
412		elsif ($started && /^\s*\/dev\/(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/) {
413			push(@rv, [ $1, $2, $3, $4 ]);
414			}
415		elsif ($started) {
416			last;
417			}
418		}
419	close(DISPLAY);
420	}
421return @rv;
422}
423
424# create_logical_volume(&lv)
425# Creates a new LV, and returns undef on success or an error message on failure
426sub create_logical_volume
427{
428local ($lv) = @_;
429local $cmd = "lvcreate -y -n".quotemeta($lv->{'name'})." ";
430local $suffix;
431if ($lv->{'size_of'} eq 'VG' || $lv->{'size_of'} eq 'FREE' ||
432    $lv->{'size_of'} eq 'ORIGIN') {
433	$cmd .= "-l ".quotemeta("$lv->{'size'}%$lv->{'size_of'}");
434	}
435elsif ($lv->{'size_of'}) {
436	$cmd .= "-l $lv->{'size'}%PVS";
437	$suffix = " ".quotemeta("/dev/".$lv->{'size_of'});
438	}
439elsif ($lv->{'thin_in'} && $lv->{'is_snap'}) {
440	# For a snapshot inside a thin pool, no size is needed
441	}
442else {
443	$cmd .= ($lv->{'thin_in'} ? "-V" : "-L").$lv->{'size'}."k";
444	}
445if ($lv->{'is_snap'}) {
446	$cmd .= " -s ".quotemeta("/dev/$lv->{'vg'}/$lv->{'snapof'}");
447	}
448else {
449	$cmd .= " -p ".quotemeta($lv->{'perm'});
450	if (!$lv->{'thin_in'}) {
451		$cmd .= " -C ".quotemeta($lv->{'alloc'});
452		}
453	$cmd .= " -r ".quotemeta($lv->{'readahead'})
454		if ($lv->{'readahead'} && $lv->{'readahead'} ne "auto");
455	$cmd .= " -i ".quotemeta($lv->{'stripe'})
456		if ($lv->{'stripe'});
457	$cmd .= " -I ".quotemeta($lv->{'stripesize'})
458		if ($lv->{'stripesize'} && $lv->{'stripe'});
459	if ($lv->{'thin_in'}) {
460		$cmd .= " --thinpool ".quotemeta($lv->{'vg'})."/".
461				       quotemeta($lv->{'thin_in'});
462		}
463	else {
464		$cmd .= " ".quotemeta($lv->{'vg'});
465		}
466	}
467$cmd .= $suffix;
468local $out = &backquote_logged("$cmd 2>&1 </dev/null");
469return $? ? $out : undef;
470}
471
472# delete_logical_volume(&lv)
473# Deletes an existing LV, and returns undef on success or an error message
474sub delete_logical_volume
475{
476local ($lv) = @_;
477
478# First delete any /dev/mapper entries for the LV
479my $mapper = $lv->{'vg'}."-".$lv->{'lv'};
480my $dashvg = $lv->{'vg'};
481$dashvg =~ s/\-/\-\-/g;
482my $dashmapper = $dashvg."-".$lv->{'lv'};
483opendir(MAPPER, "/dev/mapper");
484my @files = readdir(MAPPER);
485closedir(MAPPER);
486my @delmaps;
487foreach my $f (@files) {
488	if ($f =~ /^\Q$mapper\Ep?\d+$/ ||
489	    $f =~ /^\Q$dashmapper\Ep?\d+$/) {
490		push(@delmaps, $f);
491		}
492	}
493foreach my $f (@files) {
494	if ($f eq $mapper || $f eq $dashmapper) {
495		push(@delmaps, $f);
496		}
497	}
498foreach my $f (@delmaps) {
499	&system_logged("dmsetup remove /dev/mapper/$f >/dev/null 2>&1");
500	}
501
502# Finally remove the LV
503local $cmd = "lvremove -f ".quotemeta($lv->{'device'});
504local $out = &backquote_logged("$cmd 2>&1 </dev/null");
505return $? ? $out : undef;
506}
507
508# resize_logical_volume(&lv, size)
509# Grows or shrinks a regular LV to the new size in KB
510sub resize_logical_volume
511{
512local ($lv, $size) = @_;
513local $cmd = $size > $lv->{'size'} ? "lvextend" : "lvreduce -f";
514$cmd .= " -L".quotemeta($size)."k";
515$cmd .= " ".quotemeta($lv->{'device'});
516local $out = &backquote_logged("$cmd 2>&1 </dev/null");
517return $? ? $out : undef;
518}
519
520# resize_snapshot_volume(&lv, size)
521# Grows or shrinks a snapshot LV to the new size in KB
522sub resize_snapshot_volume
523{
524local ($lv, $size) = @_;
525local $cmd = $size > $lv->{'cow_size'} ? "lvextend" : "lvreduce -f";
526$cmd .= " -L".quotemeta($size)."k";
527$cmd .= " ".quotemeta($lv->{'device'});
528local $out = &backquote_logged("$cmd 2>&1 </dev/null");
529return $? ? $out : undef;
530}
531
532# change_logical_volume(&lv, [&old-lv])
533# Changes various parameters of an LV< like the permissions and readahead
534sub change_logical_volume
535{
536local ($lv, $oldlv) = @_;
537local $cmd = "lvchange ";
538$cmd .= " -p ".quotemeta($lv->{'perm'})
539	if (!$oldlv || $lv->{'perm'} ne $oldlv->{'perm'});
540$cmd .= " -r ".quotemeta($lv->{'readahead'})
541	if (!$oldlv || $lv->{'readahead'} ne $oldlv->{'readahead'});
542$cmd .= " -C ".quotemeta($lv->{'alloc'})
543	if (!$oldlv || $lv->{'alloc'} ne $oldlv->{'alloc'});
544$cmd .= " ".quotemeta($lv->{'device'});
545local $out = &backquote_logged("$cmd 2>&1 </dev/null");
546return $? ? $out : undef;
547}
548
549# rename_logical_volume(&lv, name)
550# Renames an existing LV
551sub rename_logical_volume
552{
553local ($lv, $name) = @_;
554local $cmd = "lvrename ".quotemeta($lv->{'device'})." ".
555	     quotemeta("/dev/$lv->{'vg'}/$name");
556local $out = &backquote_logged("$cmd 2>&1 </dev/null");
557return $? ? $out : undef;
558}
559
560# rollback_snapshot(&lv)
561# Returns the underlying LV to the state in the given snapshot
562sub rollback_snapshot
563{
564local ($lv) = @_;
565local $cmd = "lvconvert --merge ".quotemeta($lv->{'device'});
566local $out = &backquote_logged("$cmd 2>&1 </dev/null");
567return $? ? $out : undef;
568}
569
570# can_resize_filesystem(type)
571# 0 = no, 1 = enlarge only, 2 = enlarge or shrink
572sub can_resize_filesystem
573{
574local ($type) = @_;
575if ($type =~ /^ext\d+$/) {
576	if (&has_command("e2fsadm")) {
577		return 2;	# Can extend and reduce
578		}
579	elsif (&has_command("resize2fs")) {
580		# Only new versions can reduce a FS
581		local $out = &backquote_command("resize2fs 2>&1");
582		return $out =~ /resize2fs\s+([0-9\.]+)/i && $1 >= 1.4 ? 2 : 1;
583		}
584	else {
585		return 0;
586		}
587	}
588elsif ($type eq "xfs") {
589	return &has_command("xfs_growfs") ? 1 : 0;
590	}
591elsif ($type eq "reiserfs") {
592	return &has_command("resize_reiserfs") ? 2 : 0;
593	}
594elsif ($type eq "jfs") {
595	return 1;
596	}
597else {
598	return 0;
599	}
600}
601
602# can_resize_lv_stat(dir, type, mounted)
603# Returns 1 if some LV can be enlarged, 2 if enlarged or shrunk, or 0
604# if neither, based on the details provided by device_status
605sub can_resize_lv_stat
606{
607local ($dir, $type, $mounted) = @_;
608if (!$type) {
609	# No FS known, assume can resize safely
610	return 2;
611	}
612else {
613	my $can = &can_resize_filesystem($type);
614	if ($can && $mounted) {
615		# If currently mounted, check if resizing is possible
616		if ($type =~ /^ext[3-9]$/ || $type eq "xfs" ||
617		    $type eq "reiserfs" || $type eq "jfs") {
618			# ext*, xfs, jfs and reiserfs can be resized up
619			$can = 1;
620			}
621		else {
622			# Nothing else can
623			$can = 0;
624			}
625		}
626	return $can;
627	}
628}
629
630# resize_filesystem(&lv, type, size)
631sub resize_filesystem
632{
633if ($_[1] =~ /^ext\d+$/) {
634	&foreign_require("proc");
635	if (&has_command("e2fsadm")) {
636		# The e2fsadm command can re-size an LVM and filesystem together
637		local $cmd = "e2fsadm -v -L ".quotemeta($_[2])."k ".
638			     quotemeta($_[0]->{'device'});
639		local ($fh, $fpid) = &proc::pty_process_exec($cmd);
640		print $fh "yes\n";
641		local $out;
642		while(<$fh>) {
643			$out .= $_;
644			}
645		close($fh);
646		waitpid($fpid, 0);
647		&additional_log("exec", undef, $cmd);
648		return $? ? $out : undef;
649		}
650	else {
651		if ($_[2] > $_[0]->{'size'}) {
652			# Need to enlarge LV first, then filesystem
653			local $err = &resize_logical_volume($_[0], $_[2]);
654			return $err if ($err);
655
656			local $cmd = "resize2fs -f ".
657				     quotemeta($_[0]->{'device'});
658			local $out = &backquote_logged("$cmd 2>&1");
659			return $? ? $out : undef;
660			}
661		else {
662			# Need to shrink filesystem first, then LV
663			local $cmd = "resize2fs -f ".
664				     quotemeta($_[0]->{'device'})." ".
665				     quotemeta($_[2])."k";
666			local $out = &backquote_logged("$cmd 2>&1");
667			return $out if ($?);
668
669			local $err = &resize_logical_volume($_[0], $_[2]);
670			return $err;
671			}
672		}
673	}
674elsif ($_[1] eq "xfs") {
675	# Resize the logical volume first
676	local $err = &resize_logical_volume($_[0], $_[2]);
677	return $err if ($err);
678
679	# Resize the filesystem .. which must be mounted!
680	local @stat = &device_status($_[0]->{'device'});
681	local ($m, $mount);
682	foreach $m (&mount::list_mounts()) {
683		if ($m->[1] eq $_[0]->{'device'}) {
684			$mount = $m;
685			}
686		}
687	if (!$stat[2]) {
688		$mount || return "Mount not found";
689		&mount::mount_dir(@$mount);
690		}
691	local $cmd = "xfs_growfs ".quotemeta($stat[0] || $mount->[0]);
692	local $out = &backquote_logged("$cmd 2>&1");
693	local $q = $?;
694	if (!$stat[2]) {
695		&mount::unmount_dir(@$mount);
696		}
697	return $q ? $out : undef;
698	}
699elsif ($_[1] eq "reiserfs") {
700	if ($_[2] > $_[0]->{'size'}) {
701		# Enlarge the logical volume first
702		local $err = &resize_logical_volume($_[0], $_[2]);
703		return $err if ($err);
704
705		# Now enlarge the reiserfs filesystem
706		local $cmd = "resize_reiserfs ".quotemeta($_[0]->{'device'});
707		local $out = &backquote_logged("$cmd 2>&1");
708		return $? ? $out : undef;
709		}
710	else {
711		# Try to shrink the filesystem
712		local $cmd = "yes | resize_reiserfs -s ".
713			     quotemeta($_[2])."K ".quotemeta($_[0]->{'device'});
714		local $out = &backquote_logged("$cmd 2>&1");
715		return $out if ($?);
716
717		# Now shrink the logical volume
718		local $err = &resize_logical_volume($_[0], $_[2]);
719		return $err ? $err : undef;
720		}
721	}
722elsif ($_[1] eq "jfs") {
723	# Enlarge the logical volume first
724	local $err = &resize_logical_volume($_[0], $_[2]);
725	return $err if ($err);
726
727	# Now enlarge the jfs filesystem with a remount - must be mounted first
728	local @stat = &device_status($_[0]->{'device'});
729	local ($m, $mount);
730	foreach $m (&mount::list_mounts()) {
731		if ($m->[1] eq $_[0]->{'device'}) {
732			$mount = $m;
733			}
734		}
735	if (!$stat[2]) {
736		$mount || return "Mount not found";
737		&mount::mount_dir(@$mount);
738		}
739	local $ropts = $mount->[3];
740	$ropts = $ropts eq "-" ? "resize,remount" : "$ropts,resize,remount";
741	local $err = &mount::mount_dir($mount->[0], $mount->[1],
742				       $mount->[2], $ropts);
743	if (!$stat[2]) {
744		&mount::unmount_dir(@$mount);
745		}
746	return $err ? $err : undef;
747	}
748else {
749	return "???";
750	}
751}
752
753
754# parse_colon_file(file)
755sub parse_colon_file
756{
757local %rv;
758open(FILE, "<".$_[0]);
759while(<FILE>) {
760	if (/^([^:]+):\s*(.*)/) {
761		$rv{$1} = $2;
762		}
763	}
764close(FILE);
765return %rv;
766}
767
768# device_status(device)
769# Returns an array of  directory, type, mounted
770sub device_status
771{
772local ($dev) = @_;
773return ( ) if ($dev !~ /^\//);
774local @st = &fdisk::device_status($dev);
775return @st if (@st);
776if (&foreign_check("server-manager")) {
777	# Look for Cloudmin systems using the disk, hosted on this system
778	if (!@server_manager_systems) {
779		&foreign_require("server-manager");
780		@server_manager_systems =
781			grep { my $p = &server_manager::get_parent_server($_);
782			       $p && $p->{'id'} eq '0' }
783			     &server_manager::list_managed_servers();
784		}
785	foreach my $s (@server_manager_systems) {
786		if ($s->{$s->{'manager'}.'_filesystem'} eq $_[0]) {
787			return ( $s->{'host'}, 'cloudmin',
788			         $s->{'status'} ne 'down' );
789			}
790		my $ffunc = "type_".$s->{'manager'}."_list_disk_devices";
791		if (&foreign_defined("server-manager", $ffunc)) {
792			my @disks = &foreign_call("server-manager", $ffunc, $s);
793			if (&indexof($_[0], @disks) >= 0) {
794				return ( $s->{'host'}, 'cloudmin',
795					 $s->{'status'} ne 'down' );
796				}
797			}
798		}
799	}
800return ();
801}
802
803# device_message(stat)
804# Returns a text string about the status of an LV
805sub device_message
806{
807my $msg;
808if ($_[1] eq 'cloudmin') {
809	# Used by Cloudmin system
810	$msg = $_[2] ? 'lv_mountcm' : 'lv_umountcm';
811	return &text($msg, "<tt>$_[0]</tt>");
812	}
813else {
814	# Used by filesystem or RAID or iSCSI
815	$msg = $_[2] ? 'lv_mount' : 'lv_umount';
816	$msg .= 'vm' if ($_[1] eq 'swap');
817	$msg .= 'raid' if ($_[1] eq 'raid');
818	$msg .= 'iscsi' if ($_[1] eq 'iscsi');
819	return &text($msg, "<tt>$_[0]</tt>", "<tt>$_[1]</tt>");
820	}
821}
822
823# list_lvmtab()
824sub list_lvmtab
825{
826local @rv;
827open(TAB, "<".$lvm_tab);
828local $/ = "\0";
829while(<TAB>) {
830	chop;
831	push(@rv, $_) if ($_);
832	}
833close(TAB);
834return @rv;
835}
836
837# device_input()
838# Returns a selector for a free device
839sub device_input
840{
841local (%used, $vg, $pv, $d, $p);
842
843# Find partitions that are part of an LVM
844foreach $vg (&list_volume_groups()) {
845	foreach $pv (&list_physical_volumes($vg->{'name'})) {
846		$used{$pv->{'device'}}++;
847		}
848	}
849
850# Show available partitions
851local @opts;
852foreach $d (&fdisk::list_disks_partitions()) {
853	foreach $p (@{$d->{'parts'}}) {
854		next if ($used{$p->{'device'}} || $p->{'extended'});
855		local @ds = &device_status($p->{'device'});
856		next if (@ds);
857		if ($p->{'type'} eq '83' || $p->{'type'} eq 'ext2') {
858			local $label = &fdisk::get_label($p->{'device'});
859			next if ($used{"LABEL=$label"});
860			}
861		local $tag = &fdisk::tag_name($p->{'type'});
862		push(@opts, [ $p->{'device'},
863			$p->{'desc'}.
864			($tag ? " ($tag)" : "").
865			($d->{'cylsize'} ? " (".&nice_size($d->{'cylsize'}*($p->{'end'} - $p->{'start'} + 1)).")" :
866			" ($p->{'blocks'} $text{'blocks'})") ]);
867		}
868	}
869
870# Show available RAID devices
871local $conf = &raid::get_raidtab();
872foreach $c (@$conf) {
873	next if ($used{$c->{'value'}});
874	local @ds = &device_status($c->{'value'});
875	next if (@ds);
876	push(@opts, [ $c->{'value'}, &text('pv_raid', $c->{'value'} =~ /md(\d+)$/ ? "$1" : $c->{'value'}) ]);
877	}
878
879push(@opts, [ '', $text{'pv_other'} ]);
880return &ui_select("device", $opts[0]->[0], \@opts)." ".
881       &ui_textbox("other", undef, 30)." ".&file_chooser_button("other").
882       "<br>\n<b>$text{'pv_warn'}</b>";
883}
884
885# get_lvm_version()
886# Returns the lvm version number and optionally output from the vgdisplay
887# command used to get it.
888sub get_lvm_version
889{
890local $out = `vgdisplay --version 2>&1`;
891local $ver = $out =~ /\s+([0-9\.]+)/ ? $1 : undef;
892return wantarray ? ( $ver, $out ) : $ver;
893}
894
895# nice_round(number)
896# Round some number to TB, GB, MB or kB, depending on size
897sub nice_round
898{
899local ($bytes) = @_;
900my $units;
901if ($bytes >= 10*1024*1024*1024*1024) {
902        $units = 1024*1024*1024*1024;
903        }
904elsif ($bytes >= 10*1024*1024*1024) {
905        $units = 1024*1024*1024;
906        }
907elsif ($bytes >= 10*1024*1024) {
908        $units = 1024*1024;
909        }
910elsif ($bytes >= 10*1024) {
911        $units = 1024;
912        }
913else {
914	$units = 1;
915	}
916return int($bytes / $units) * $units;
917}
918
919# move_logical_volume(&lv, from, to, [print])
920# Moves blocks on an LV from one PV to another. Returns an error message on
921# failure, or undef on success
922sub move_logical_volume
923{
924local ($lv, $from, $to, $print) = @_;
925local $cmd = "pvmove -n $lv->{'name'} /dev/$from /dev/$to";
926if ($print) {
927	open(OUT, "$cmd 2>&1 </dev/null |");
928	my $old = select(OUT);
929	$| = 1;
930	select($old);
931	while(<OUT>) {
932		print &html_escape($_);
933		}
934	my $ex = close(OUT);
935	return $? ? "Failed" : undef;
936	}
937else {
938	local $out = &backquote_logged("$cmd 2>&1 </dev/null");
939	return $? ? $out : undef;
940	}
941}
942
943# supports_snapshot_rollback()
944# Only newer kernels safely support this (2.6.35 and above)
945sub supports_snapshot_rollback
946{
947my $out = &backquote_command("uname -r 2>/dev/null </dev/null");
948return $out =~ /^(\d+)\./ && $1 >= 3 ||
949       $out =~ /^(\d+)\.(\d+)/ && $1 == 2 && $2 >= 7 ||
950       $out =~ /^(\d+)\.(\d+)\.(\d+)/ && $1 == 2 && $2 == 6 && $3 >= 35;
951}
952
953# create_thin_pool(&data-lv, &metadata-lv)
954# Convert two LVs into a thin pool
955sub create_thin_pool
956{
957local ($datalv, $metadatalv) = @_;
958local $cmd = "lvconvert -y --type thin-pool --poolmetadata ".
959	     quotemeta($metadatalv->{'device'})." ".
960	     quotemeta($datalv->{'device'});
961local $out = &backquote_logged("$cmd 2>&1 </dev/null");
962return $? ? $out : undef;
963}
964
9651;
966
967