1 package Proxmox
::Sys
::Block
;
8 use List
::Util
qw(first);
10 use Proxmox
::Install
::Env
;
11 use Proxmox
::Sys
::Command
qw(syscmd);
12 use Proxmox
::Sys
::File
qw(file_read_firstline);
14 use base
qw(Exporter);
15 our @EXPORT_OK = qw(get_cached_disks wipe_disk partition_bootable_disk);
20 my ($dev_a ,$ino_a) = stat($a);
21 my ($dev_b, $ino_b) = stat($b);
23 return 0 if !($dev_a && $dev_b && $ino_a && $ino_b);
25 return $ino_a == $ino_b && $dev_a == $dev_b;
28 my sub find_stable_path
{
29 my ($stabledir, $bdev) = @_;
31 foreach my $path (<$stabledir/*>) {
32 if (is_same_file
($path, $bdev)) {
39 sub get_disk_by_id_path
{
41 return find_stable_path
('/dev/disk/by-id', $dev);
47 my $by_uuid_path = find_stable_path
("/dev/disk/by-uuid", $bdev);
49 return basename
($by_uuid_path);
54 my $disks = Proxmox
::Install
::Env
::get_test_images
();
57 map { [ -1, $_, int((-s
$_)/512), "TESTDISK", 512] } $disks->@*
63 foreach my $bd (</sys/block
/*>) {
64 next if $bd =~ m
|^/sys/block
/ram\d
+$|;
65 next if $bd =~ m
|^/sys/block
/loop\d
+$|;
66 next if $bd =~ m
|^/sys/block
/md\d
+$|;
67 next if $bd =~ m
|^/sys/block
/dm-
.*$|;
68 next if $bd =~ m
|^/sys/block
/fd\d
+$|;
69 next if $bd =~ m
|^/sys/block
/sr\d
+$|;
71 my $dev = file_read_firstline
("$bd/dev");
76 my $info = `udevadm info --path $bd --query all`;
79 next if $info !~ m/^E: DEVTYPE=disk$/m;
81 next if $info =~ m/^E: ID_CDROM/m;
82 next if $info =~ m/^E: ID_FS_TYPE=iso9660/m;
84 my ($name) = $info =~ m/^N: (\S+)$/m;
87 my $real_name = "/dev/$name";
89 my $size = file_read_firstline
("$bd/size");
91 $size = undef if !($size && $size =~ m/^\d+$/);
93 my $model = file_read_firstline
("$bd/device/model") || '';
96 if (length ($model) > 30) {
97 $model = substr ($model, 0, 30);
100 my $logical_bsize = file_read_firstline
("$bd/queue/logical_block_size") // '';
101 chomp $logical_bsize;
102 $logical_bsize = undef if !($logical_bsize && $logical_bsize =~ m/^\d+$/);
104 push @$res, [$count++, $real_name, $size, $model, $logical_bsize] if $size;
106 print STDERR
"ERROR: unable to map device $dev ($bd)\n";
115 $cached_disks = hd_list
();
117 sub get_cached_disks
{
118 cache_disks
() if !defined($cached_disks);
119 return $cached_disks;
122 sub find_cached_disk_by_devname
{
123 my ($dev, $noerr) = @_;
125 my $disks = get_cached_disks
();
126 my $record = first
{ $_->[1] eq $dev } $disks->@*; # ($disk, $devname, $size, $model, $lbsize)
127 die "no such disk device '$dev'\n" if !defined($record) && !$noerr;
135 my ($_disk, $_name, $size) = find_cached_disk_by_devname
($dev)->@*;
137 return int($size / 2);
140 sub logical_blocksize
{
143 my ($_disk, $_dev, $_size, $_model, $logical_bsize) = find_cached_disk_by_devname
($dev)->@*;
145 return $logical_bsize;
148 sub get_partition_dev
{
149 my ($dev, $partnum) = @_;
151 if ($dev =~ m
|^/dev/sd([a-h
]?
[a-z
]\
|i
[a-v
])$|) {
152 return "${dev}$partnum";
153 } elsif ($dev =~ m
|^/dev/xvd
[a-z
]$|) {
154 # Citrix Hypervisor blockdev
155 return "${dev}$partnum";
156 } elsif ($dev =~ m
|^/dev/[hxev
]d
[a-z
]$|) {
157 return "${dev}$partnum";
158 } elsif ($dev =~ m
|^/dev/[^/]+/c\d
+d\d
+$|) {
159 return "${dev}p$partnum";
160 } elsif ($dev =~ m
|^/dev/[^/]+/d\d
+$|) {
161 return "${dev}p$partnum";
162 } elsif ($dev =~ m
|^/dev/[^/]+/hd
[a-z
]$|) {
163 return "${dev}$partnum";
164 } elsif ($dev =~ m
|^/dev/nvme\d
+n\d
+$|) {
165 return "${dev}p$partnum";
167 die "unable to get device for partition $partnum on device $dev\n";
171 sub udevadm_trigger_block
{
174 sleep(1) if !$nowait; # give kernel time to reread part table
176 # trigger udev to create /dev/disk/by-uuid
177 syscmd
("udevadm trigger --subsystem-match block");
178 syscmd
("udevadm settle --timeout 10");
184 # sort longest first as we need to cleanup depth-first
185 my @partitions = sort { length($b) <=> length($a) }
186 split("\n", `lsblk --output kname --noheadings --path --list $disk`);
188 for my $part (@partitions) {
189 next if $part eq $disk;
190 next if $part !~ /^\Q$disk\E/;
191 eval { syscmd("pvremove -ff -y $part"); };
192 eval { syscmd("zpool labelclear -f $part"); };
193 eval { syscmd("dd if=/dev/zero of=$part bs=1M count=16"); };
195 eval { syscmd(['wipefs', '-a', @partitions]) };
199 sub partition_bootable_disk {
200 my ($target_dev, $maxhdsizegb, $ptype) = @_;
202 die "too dangerous" if is_test_mode();
204 die "unknown partition type '$ptype'"
205 if !($ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01');
207 my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
209 # For bigger disks default to generous ESP size to allow users having multiple kernels/UKI's
210 my $esp_size = $hdsize > 100 * 1024 * 1024 ? 1024 : 512; # MB
211 my $esp_end = $esp_size + 1;
213 my $restricted_hdsize_mb = 0; # 0 ==> end of partition
215 my $maxhdsize = $maxhdsizegb * 1024 * 1024;
216 if ($maxhdsize < $hdsize) {
217 $hdsize = $maxhdsize;
218 $restricted_hdsize_mb = int($hdsize/1024) . 'M';
222 my $hdgb = int($hdsize/(1024*1024));
224 my ($hard_limit, $soft_limit) = (2, 8);
226 die "root disk '$target_dev' too small (${hdgb} GB < $hard_limit GB)\n" if $hdgb < $hard_limit;
227 if ($hdgb < $soft_limit) {
228 my $response = display_prompt(
229 "Root disk space ${hdgb} GB is below recommended minimum space of $soft_limit GB,"
230 ." installation might not be successful! Continue?"
232 die "root disk '$target_dev' too small (${hdgb} GB < $soft_limit GB), and warning not accepted.\n"
233 if $response ne 'ok';
236 syscmd("sgdisk -Z ${target_dev}");
238 # 1 - BIOS boot partition (Grub Stage2): first free 1 MB
239 # 2 - EFI ESP: next free 512 or 1024 MB
240 # 3 - OS/Data partition: rest, up to $maxhdsize in MB
242 my $grubbootdev = get_partition_dev($target_dev, 1);
243 my $efibootdev = get_partition_dev($target_dev, 2);
244 my $osdev = get_partition_dev ($target_dev, 3);
246 my $pcmd = ['sgdisk'];
249 push @$pcmd, "-n${pnum}:1M:+${esp_size}M", "-t$pnum:EF00";
252 push @$pcmd, "-n${pnum}:${esp_end}M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
254 push @$pcmd, $target_dev;
256 my $os_size = $hdsize - $esp_end * 1024; # efi + 1M bios_boot + 1M alignment
258 syscmd($pcmd) == 0 ||
259 die "unable to partition harddisk '${target_dev}'\n";
261 my $blocksize = logical_blocksize($target_dev);
263 if ($blocksize != 4096) {
265 $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
267 syscmd($pcmd) == 0 ||
268 die "unable to create bios_boot partition '${target_dev}'\n";
271 udevadm_trigger_block();
273 foreach my $part ($efibootdev, $osdev) {
274 syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
277 return ($os_size, $osdev, $efibootdev);