mirror of
https://git.proxmox.com/git/pve-installer
synced 2025-06-11 07:12:42 +00:00

.. and introduce some helpers to finding/getting cached disks. The hd_list method, which scans for physical disks, can be made private with this change. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
283 lines
7.1 KiB
Perl
283 lines
7.1 KiB
Perl
package Proxmox::Sys::Block;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use File::Basename;
|
|
use IO::File;
|
|
use List::Util qw(first);
|
|
|
|
use Proxmox::Install::Env;
|
|
use Proxmox::Sys::Command qw(syscmd);
|
|
use Proxmox::Sys::File qw(file_read_firstline);
|
|
|
|
use base qw(Exporter);
|
|
our @EXPORT_OK = qw(get_cached_disks);
|
|
|
|
my sub is_same_file {
|
|
my ($a, $b) = @_;
|
|
|
|
my ($dev_a ,$ino_a) = stat($a);
|
|
my ($dev_b, $ino_b) = stat($b);
|
|
|
|
return 0 if !($dev_a && $dev_b && $ino_a && $ino_b);
|
|
|
|
return $ino_a == $ino_b && $dev_a == $dev_b;
|
|
}
|
|
|
|
my sub find_stable_path {
|
|
my ($stabledir, $bdev) = @_;
|
|
|
|
foreach my $path (<$stabledir/*>) {
|
|
if (is_same_file($path, $bdev)) {
|
|
return $path;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub get_disk_by_id_path {
|
|
my ($dev) = @_;
|
|
return find_stable_path('/dev/disk/by-id', $dev);
|
|
}
|
|
|
|
sub get_dev_uuid {
|
|
my $bdev = shift;
|
|
|
|
my $by_uuid_path = find_stable_path("/dev/disk/by-uuid", $bdev);
|
|
|
|
return basename($by_uuid_path);
|
|
}
|
|
|
|
my sub hd_list {
|
|
|
|
if (is_test_mode()) {
|
|
my $disks = Proxmox::Install::Env::get_test_images();
|
|
|
|
return [
|
|
map { [ -1, $_, int((-s $_)/512), "TESTDISK", 512] } $disks->@*
|
|
];
|
|
}
|
|
|
|
my $res = [];
|
|
my $count = 0;
|
|
foreach my $bd (</sys/block/*>) {
|
|
next if $bd =~ m|^/sys/block/ram\d+$|;
|
|
next if $bd =~ m|^/sys/block/loop\d+$|;
|
|
next if $bd =~ m|^/sys/block/md\d+$|;
|
|
next if $bd =~ m|^/sys/block/dm-.*$|;
|
|
next if $bd =~ m|^/sys/block/fd\d+$|;
|
|
next if $bd =~ m|^/sys/block/sr\d+$|;
|
|
|
|
my $dev = file_read_firstline("$bd/dev");
|
|
chomp $dev;
|
|
|
|
next if !$dev;
|
|
|
|
my $info = `udevadm info --path $bd --query all`;
|
|
next if !$info;
|
|
|
|
next if $info !~ m/^E: DEVTYPE=disk$/m;
|
|
|
|
next if $info =~ m/^E: ID_CDROM/m;
|
|
next if $info =~ m/^E: ID_FS_TYPE=iso9660/m;
|
|
|
|
my ($name) = $info =~ m/^N: (\S+)$/m;
|
|
|
|
if ($name) {
|
|
my $real_name = "/dev/$name";
|
|
|
|
my $size = file_read_firstline("$bd/size");
|
|
chomp $size;
|
|
$size = undef if !($size && $size =~ m/^\d+$/);
|
|
|
|
my $model = file_read_firstline("$bd/device/model") || '';
|
|
$model =~ s/^\s+//;
|
|
$model =~ s/\s+$//;
|
|
if (length ($model) > 30) {
|
|
$model = substr ($model, 0, 30);
|
|
}
|
|
|
|
my $logical_bsize = file_read_firstline("$bd/queue/logical_block_size") // '';
|
|
chomp $logical_bsize;
|
|
$logical_bsize = undef if !($logical_bsize && $logical_bsize =~ m/^\d+$/);
|
|
|
|
push @$res, [$count++, $real_name, $size, $model, $logical_bsize] if $size;
|
|
} else {
|
|
print STDERR "ERROR: unable to map device $dev ($bd)\n";
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
my $cached_disks;
|
|
sub cache_disks {
|
|
$cached_disks = hd_list();
|
|
}
|
|
sub get_cached_disks {
|
|
cache_disks() if !defined($cached_disks);
|
|
return $cached_disks;
|
|
}
|
|
|
|
sub find_cached_disk_by_devname {
|
|
my ($dev, $noerr) = @_;
|
|
|
|
my $disks = get_cached_disks();
|
|
my $record = first { $_->[1] eq $dev } $disks->@*; # ($disk, $devname, $size, $model, $lbsize)
|
|
die "no such disk device '$dev'\n" if !defined($record) && !$noerr;
|
|
|
|
return $record;
|
|
}
|
|
|
|
sub hd_size {
|
|
my ($dev) = @_;
|
|
|
|
my ($_disk, $_name, $size) = find_cached_disk_by_devname($dev)->@*;
|
|
|
|
return int($size / 2);
|
|
}
|
|
|
|
sub logical_blocksize {
|
|
my ($dev) = @_;
|
|
|
|
my ($_disk, $_dev, $_size, $_model, $logical_bsize) = find_cached_disk_by_devname($dev)->@*;
|
|
|
|
return $logical_bsize;
|
|
}
|
|
|
|
sub get_partition_dev {
|
|
my ($dev, $partnum) = @_;
|
|
|
|
if ($dev =~ m|^/dev/sd([a-h]?[a-z]\|i[a-v])$|) {
|
|
return "${dev}$partnum";
|
|
} elsif ($dev =~ m|^/dev/xvd[a-z]$|) {
|
|
# Citrix Hypervisor blockdev
|
|
return "${dev}$partnum";
|
|
} elsif ($dev =~ m|^/dev/[hxev]d[a-z]$|) {
|
|
return "${dev}$partnum";
|
|
} elsif ($dev =~ m|^/dev/[^/]+/c\d+d\d+$|) {
|
|
return "${dev}p$partnum";
|
|
} elsif ($dev =~ m|^/dev/[^/]+/d\d+$|) {
|
|
return "${dev}p$partnum";
|
|
} elsif ($dev =~ m|^/dev/[^/]+/hd[a-z]$|) {
|
|
return "${dev}$partnum";
|
|
} elsif ($dev =~ m|^/dev/nvme\d+n\d+$|) {
|
|
return "${dev}p$partnum";
|
|
} else {
|
|
die "unable to get device for partition $partnum on device $dev\n";
|
|
}
|
|
}
|
|
|
|
sub udevadm_trigger_block {
|
|
my ($nowait) = @_;
|
|
|
|
sleep(1) if !$nowait; # give kernel time to reread part table
|
|
|
|
# trigger udev to create /dev/disk/by-uuid
|
|
syscmd("udevadm trigger --subsystem-match block");
|
|
syscmd("udevadm settle --timeout 10");
|
|
};
|
|
|
|
sub wipe_disk {
|
|
my ($disk) = @_;
|
|
|
|
# sort longest first as we need to cleanup depth-first
|
|
my @partitions = sort { length($b) <=> length($a) }
|
|
split("\n", `lsblk --output kname --noheadings --path --list $disk`);
|
|
|
|
for my $part (@partitions) {
|
|
next if $part eq $disk;
|
|
next if $part !~ /^\Q$disk\E/;
|
|
eval { syscmd("pvremove -ff -y $part"); };
|
|
eval { syscmd("zpool labelclear -f $part"); };
|
|
eval { syscmd("dd if=/dev/zero of=$part bs=1M count=16"); };
|
|
}
|
|
eval { syscmd(['wipefs', '-a', @partitions]) };
|
|
warn "$@" if $@;
|
|
};
|
|
|
|
sub partition_bootable_disk {
|
|
my ($target_dev, $maxhdsizegb, $ptype) = @_;
|
|
|
|
die "too dangerous" if is_test_mode();
|
|
|
|
die "unknown partition type '$ptype'"
|
|
if !($ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01');
|
|
|
|
my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
|
|
|
|
# For bigger disks default to generous ESP size to allow users having multiple kernels/UKI's
|
|
my $esp_size = $hdsize > 100 * 1024 * 1024 ? 1024 : 512; # MB
|
|
my $esp_end = $esp_size + 1;
|
|
|
|
my $restricted_hdsize_mb = 0; # 0 ==> end of partition
|
|
if ($maxhdsizegb) {
|
|
my $maxhdsize = $maxhdsizegb * 1024 * 1024;
|
|
if ($maxhdsize < $hdsize) {
|
|
$hdsize = $maxhdsize;
|
|
$restricted_hdsize_mb = int($hdsize/1024) . 'M';
|
|
}
|
|
}
|
|
|
|
my $hdgb = int($hdsize/(1024*1024));
|
|
|
|
my ($hard_limit, $soft_limit) = (2, 8);
|
|
|
|
die "root disk '$target_dev' too small (${hdgb} GB < $hard_limit GB)\n" if $hdgb < $hard_limit;
|
|
if ($hdgb < $soft_limit) {
|
|
my $response = display_prompt(
|
|
"Root disk space ${hdgb} GB is below recommended minimum space of $soft_limit GB,"
|
|
." installation might not be successful! Continue?"
|
|
);
|
|
die "root disk '$target_dev' too small (${hdgb} GB < $soft_limit GB), and warning not accepted.\n"
|
|
if $response ne 'ok';
|
|
}
|
|
|
|
syscmd("sgdisk -Z ${target_dev}");
|
|
|
|
# 1 - BIOS boot partition (Grub Stage2): first free 1 MB
|
|
# 2 - EFI ESP: next free 512 or 1024 MB
|
|
# 3 - OS/Data partition: rest, up to $maxhdsize in MB
|
|
|
|
my $grubbootdev = get_partition_dev($target_dev, 1);
|
|
my $efibootdev = get_partition_dev($target_dev, 2);
|
|
my $osdev = get_partition_dev ($target_dev, 3);
|
|
|
|
my $pcmd = ['sgdisk'];
|
|
|
|
my $pnum = 2;
|
|
push @$pcmd, "-n${pnum}:1M:+${esp_size}M", "-t$pnum:EF00";
|
|
|
|
$pnum = 3;
|
|
push @$pcmd, "-n${pnum}:${esp_end}M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
|
|
|
|
push @$pcmd, $target_dev;
|
|
|
|
my $os_size = $hdsize - $esp_end * 1024; # efi + 1M bios_boot + 1M alignment
|
|
|
|
syscmd($pcmd) == 0 ||
|
|
die "unable to partition harddisk '${target_dev}'\n";
|
|
|
|
my $blocksize = logical_blocksize($target_dev);
|
|
|
|
if ($blocksize != 4096) {
|
|
$pnum = 1;
|
|
$pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
|
|
|
|
syscmd($pcmd) == 0 ||
|
|
die "unable to create bios_boot partition '${target_dev}'\n";
|
|
}
|
|
|
|
udevadm_trigger_block();
|
|
|
|
foreach my $part ($efibootdev, $osdev) {
|
|
syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
|
|
}
|
|
|
|
return ($os_size, $osdev, $efibootdev);
|
|
}
|
|
|
|
|
|
1;
|