summaryrefslogtreecommitdiffstats
path: root/perl-install/lvm.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl-install/lvm.pm')
-rw-r--r--perl-install/lvm.pm255
1 files changed, 206 insertions, 49 deletions
diff --git a/perl-install/lvm.pm b/perl-install/lvm.pm
index 01e8e55a6..bf212bd6f 100644
--- a/perl-install/lvm.pm
+++ b/perl-install/lvm.pm
@@ -1,4 +1,4 @@
-package lvm; # $Id$
+package lvm;
use diagnostics;
use strict;
@@ -8,127 +8,284 @@ use strict;
#-######################################################################################
use common;
use modules;
-use fsedit;
use devices;
+use fs::type;
use run_program;
+=head1 SYNOPSYS
+
+Manage LVM (PV, VG, LV)
+
+=head1 Functions
+
+=over 4
+
+=cut
+
#- for partition_table_xxx emulation
+sub new {
+ my ($class, $name) = @_;
+ $name =~ s/[^\w-]/_/g;
+ $name = substr($name, 0, 63); # max length must be < NAME_LEN / 2 where NAME_LEN is 128
+ bless { disks => [], VG_name => $name, device => $name }, $class;
+}
+sub use_pt_type { 0 }
sub hasExtended { 0 }
sub adjustStart {}
sub adjustEnd {}
sub write {}
sub cylinder_size {
my ($hd) = @_;
- $hd->{PE_size};
+ $hd->{extent_size};
+}
+
+=item detect_durting_install()
+
+Explicitly scan VGs.
+
+=cut
+
+sub detect_during_install() {
+ run_program::run('lvm2', 'vgscan');
+ run_program::run('lvm2', 'vgchange', '-a', 'y');
}
-init();
+=item init()
+
+Loads LVM modules and scan VGs (if in installer, not in standalone tool).
-sub init {
- eval { modules::load('lvm-mod') };
- run_program::run('vgscan') if !-e '/etc/lvmtab';
- run_program::run('vgchange', '-a', 'y');
+=cut
+
+sub init() {
+ devices::init_device_mapper();
+ detect_during_install() if $::isInstall || $::isLiveInstall;
+ 1;
}
-sub run {
- if (my $r = run_program::run(@_)) {
+init() or log::l("lvm::init failed");
+
+=item lvm_cmd(...)
+
+Run a LVM command, then rescan VG.
+See run_program::run() for arguments.
+
+=cut
+
+sub lvm_cmd {
+ if (my $r = run_program::run('lvm2', @_)) {
$r;
} else {
$? >> 8 == 98 or return;
#- sometimes, it needs running vgscan again, doing so:
- run_program::run('vgscan');
- run_program::run(@_);
+ log::l("forcing rescan because of prior failure");
+ run_program::run('lvm2', 'vgscan');
+ run_program::run('lvm2', @_);
}
}
-sub run_or_die {
+
+=item lvm_cmd_or_die($prog, @para)
+
+Like lvm_cmd() but die if there's an error.
+
+=cut
+
+sub lvm_cmd_or_die {
my ($prog, @para) = @_;
- run($prog, @para) or die "$prog failed\n";
+ my @err;
+ lvm_cmd("2>", \@err, $prog, @para) or do {
+ my $err = $err[-1]; # prevent "Modification of non-creatable array value attempted"
+ chomp($err);
+ die "$prog failed: $err\n";
+ };
}
sub check {
- my ($in) = @_;
-
- my $f = '/sbin/pvcreate';
- -e $f or $in->do_pkgs->install('lvm');
- -e $f or $in->ask_warn('', "Mandatory package lvm is missing"), return;
+ my ($do_pkgs) = @_;
+ local $::prefix = ''; # We want lvm2 on current system
+ $do_pkgs->ensure_binary_is_installed('lvm2', 'lvm2') or return;
init();
1;
}
-sub get_vg {
- my ($part) = @_;
- my $dev = expand_symlinks(devices::make($part->{device}));
- (split(':', run_program::get_stdout('pvdisplay', '-c', $dev)))[1];
+sub get_pv_field {
+ my ($pv, $field) = @_;
+ my $dev = expand_symlinks(devices::make($pv->{device}));
+ run_program::get_stdout('lvm2', 'pvs', '--noheadings', '--nosuffix', '-o', $field, $dev);
+}
+sub pv_physical_extents {
+ my ($pv) = @_;
+ split(' ', lvm::get_pv_field($pv, 'pv_pe_alloc_count,pv_pe_count'));
+}
+sub pv_to_vg {
+ my ($pv) = @_;
+ get_pv_field($pv, 'vg_name') =~ /(\S+)/ && $1;
+}
+
+sub pv_move {
+ my ($pv) = @_;
+ my $dev = expand_symlinks(devices::make($pv->{device}));
+ lvm_cmd('pvmove', '-v', $dev) or die N("Moving used physical extents to other physical volumes failed");
}
sub update_size {
my ($lvm) = @_;
- my @l = split(':', run_program::get_stdout('vgdisplay', '-c', '-D', $lvm->{VG_name}));
- $lvm->{totalsectors} = ($lvm->{PE_size} = $l[12]) * $l[13];
+ $lvm->{extent_size} = to_int(run_program::get_stdout('lvm2', 'vgs', '--noheadings', '--nosuffix', '--units', 's', '-o', 'vg_extent_size', $lvm->{VG_name}));
+ $lvm->{totalsectors} = to_int(run_program::get_stdout('lvm2', 'vgs', '--noheadings', '--nosuffix', '--units', 's', '-o', 'vg_size', $lvm->{VG_name}));
+}
+
+sub get_lv_size {
+ my ($lvm_device) = @_;
+ to_int(run_program::get_stdout('lvm2', 'lvs', '--noheadings', '--nosuffix', '--units', 's', '-o', 'lv_size', "/dev/$lvm_device"));
}
+sub lv_to_pvs {
+ my ($lv) = @_;
+ map { m!(\S+)\(! } run_program::get_stdout('lvm2', 'lvs', '--noheadings', '-o', 'devices', "/dev/$lv->{device}");
+}
+sub lv_nb_pvs {
+ my ($lv) = @_;
+ listlength(lv_to_pvs($lv));
+}
+
+=item get_lvs($lvm)
+
+Return list of LVs.
+
+=cut
+
sub get_lvs {
my ($lvm) = @_;
- my @l = run_program::get_stdout('vgdisplay', '-v', '-D', $lvm->{VG_name});
+ my @l = run_program::get_stdout('lvm2', 'lvs', '--noheadings', '--nosuffix', '--units', 's', '-o', 'lv_name', $lvm->{VG_name}) =~ /(\S+)/g;
$lvm->{primary}{normal} =
[
map {
- my $type = -e "/dev/$_" && fsedit::typeOfPart("/dev/$_");
- my $info = run_program::get_stdout('lvdisplay', '-D', '-c', "/dev/$_");
-
- { device => $_,
- type => $type || 0x83,
- size => (split(':', $info))[6] }
- } map { if_(m|^LV Name\s+/dev/(\S+)|, $1) } @l
+ my $device = "$lvm->{VG_name}/$_";
+ my $p = fs::wild_device::to_subpart("/dev/$device");
+ my $part = {
+ device => $device,
+ lv_name => $_,
+ rootDevice => $lvm->{VG_name},
+ minor => $p->{minor},
+ major => $p->{major},
+ size => get_lv_size($device) };
+ if (my $type = -e "/dev/$device" && fs::type::type_subpart_from_magic($part)) {
+ put_in_hash($part, $type);
+ } else {
+ $part->{fs_type} = defaultFS();
+ }
+ $part;
+ } @l
];
}
sub vg_add {
my ($part) = @_;
my $dev = expand_symlinks(devices::make($part->{device}));
- run_or_die('pvcreate', '-y', '-ff', $dev);
- my $prog = run('vgdisplay', $part->{lvm}) ? 'vgextend' : 'vgcreate';
- run_or_die($prog, $part->{lvm}, $dev);
+ output($dev, '\0' x 512); #- help pvcreate
+ lvm_cmd_or_die('pvcreate', '-y', '-ff', $dev);
+ my $prog = lvm_cmd('vgs', $part->{lvm}) ? 'vgextend' : 'vgcreate';
+ lvm_cmd_or_die($prog, $part->{lvm}, $dev);
+}
+
+sub vg_reduce {
+ my ($lvm_vg, $part_pv) = @_;
+
+ lvm_cmd('vgreduce', $lvm_vg->{VG_name}, devices::make($part_pv->{device})) or die N("Physical volume %s is still in use", $part_pv->{device});
+ @{$lvm_vg->{disks}} = difference2($lvm_vg->{disks}, [ $part_pv ]);
+ update_size($lvm_vg);
+ delete $part_pv->{lvm};
+ set_isFormatted($part_pv, 0);
}
sub vg_destroy {
my ($lvm) = @_;
- is_empty_array_ref($lvm->{primary}{normal}) or die _("Remove the logical volumes first\n");
- run('vgchange', '-a', 'n', $lvm->{VG_name});
- run_or_die('vgremove', $lvm->{VG_name});
+ is_empty_array_ref($lvm->{primary}{normal}) or die N("Remove the logical volumes first\n");
+ lvm_cmd('vgchange', '-a', 'n', $lvm->{VG_name});
+ lvm_cmd_or_die('vgremove', $lvm->{VG_name});
foreach (@{$lvm->{disks}}) {
+ lvm_cmd_or_die('pvremove', devices::make($_->{device}));
delete $_->{lvm};
- $_->{isFormatted} = 0;
- $_->{notFormatted} = 1;
+ set_isFormatted($_, 0);
}
}
sub lv_delete {
my ($lvm, $lv) = @_;
- run_or_die('lvremove', '-f', "/dev/$lv->{device}");
+ lvm_cmd_or_die('lvremove', '-f', "/dev/$lv->{device}");
my $list = $lvm->{primary}{normal};
@$list = grep { $_ != $lv } @$list;
}
+sub suggest_lv_name_from_mnt_point {
+ my ($lv) = @_;
+ my $str = $lv->{mntpoint};
+ $str = "root" if $str eq '/';
+ $str =~ s!^/!!;
+ $str =~ s!/!_!g;
+ $str =~ s! !_!g;
+ 'lv_' . $str;
+}
+
+sub suggest_lv_name {
+ my ($lvm, $lv) = @_;
+ my $list = $lvm->{primary}{normal} ||= [];
+ $lv->{lv_name} ||= suggest_lv_name_from_mnt_point($lv);
+ $lv->{lv_name} ||= "lv_" . (1 + max(map { if_($_->{device} =~ /(\d+)$/, $1) } @$list));
+}
+
sub lv_create {
my ($lvm, $lv) = @_;
+ suggest_lv_name($lvm, $lv);
+ $lv->{device} = "$lvm->{VG_name}/$lv->{lv_name}";
+ lvm_cmd_or_die('lvcreate', '--size', int($lv->{size} / 2) . 'k', '-n', $lv->{lv_name}, $lvm->{VG_name});
+
+ if ($lv->{mntpoint} eq '/boot' && lv_nb_pvs($lv) > 1) {
+ lvm_cmd_or_die('lvremove', '-f', "/dev/$lv->{device}");
+ die N("The bootloader can't handle /boot on multiple physical volumes");
+ }
+
+ $lv->{size} = get_lv_size($lv->{device}); #- the created size is smaller than asked size
+ set_isFormatted($lv, 0);
my $list = $lvm->{primary}{normal} ||= [];
- my $nb = 1 + max(map { basename($_->{device}) } @$list);
- $lv->{device} = "$lvm->{VG_name}/$nb";
- run_or_die('lvcreate', '--size', int($lv->{size} / 2) . 'k', '-n', $nb, $lvm->{VG_name});
- $lv->{notFormatted} = 1;
- $lv->{isFormatted} = 0;
push @$list, $lv;
}
sub lv_resize {
my ($lv, $oldsize) = @_;
- run_or_die($oldsize > $lv->{size} ? ('lvreduce', '-f') : 'lvextend',
- '--size', int($lv->{size} / 2) . 'k', "/dev/$lv->{device}");
+ lvm_cmd_or_die($oldsize > $lv->{size} ? ('lvreduce', '-f') : 'lvextend',
+ '--size', int($lv->{size} / 2) . 'k', "/dev/$lv->{device}");
+ $lv->{size} = get_lv_size($lv->{device}); #- the resized partition may not be the exact asked size
+}
+
+sub add_to_VG {
+ my ($part, $lvm) = @_;
+
+ $part->{lvm} = $lvm->{VG_name};
+ push @{$lvm->{disks}}, $part;
+ delete $part->{mntpoint};
+
+ vg_add($part);
+ update_size($lvm);
}
+sub create_singleton_vg {
+ my ($lvms, $part) = @_;
+
+ my %existing = map { $_->{VG_name} => 1 } @$lvms;
+ my $VG_name = find { !$existing{$_} } map { "VG$_" } 1 .. 100 or internal_error();
+
+ my $lvm = new lvm($VG_name);
+ push @$lvms, $lvm;
+
+ add_to_VG($part, $lvm);
+}
+
+=back
+
+=cut
+
1;