]> git.proxmox.com Git - pve-installer.git/blob - Proxmox/Sys/Block.pm
visibility fixes for calling into sys/block helpers
[pve-installer.git] / Proxmox / Sys / Block.pm
1 package Proxmox::Sys::Block;
2
3 use strict;
4 use warnings;
5
6 use File::Basename;
7 use IO::File;
8 use List::Util qw(first);
9
10 use Proxmox::Install::Env;
11 use Proxmox::Sys::Command qw(syscmd);
12 use Proxmox::Sys::File qw(file_read_firstline);
13
14 use base qw(Exporter);
15 our @EXPORT_OK = qw(get_cached_disks wipe_disk partition_bootable_disk);
16
17 my sub is_same_file {
18 my ($a, $b) = @_;
19
20 my ($dev_a ,$ino_a) = stat($a);
21 my ($dev_b, $ino_b) = stat($b);
22
23 return 0 if !($dev_a && $dev_b && $ino_a && $ino_b);
24
25 return $ino_a == $ino_b && $dev_a == $dev_b;
26 }
27
28 my sub find_stable_path {
29 my ($stabledir, $bdev) = @_;
30
31 foreach my $path (<$stabledir/*>) {
32 if (is_same_file($path, $bdev)) {
33 return $path;
34 }
35 }
36 return;
37 }
38
39 sub get_disk_by_id_path {
40 my ($dev) = @_;
41 return find_stable_path('/dev/disk/by-id', $dev);
42 }
43
44 sub get_dev_uuid {
45 my $bdev = shift;
46
47 my $by_uuid_path = find_stable_path("/dev/disk/by-uuid", $bdev);
48
49 return basename($by_uuid_path);
50 }
51
52 my sub hd_list {
53 if (is_test_mode()) {
54 my $disks = Proxmox::Install::Env::get_test_images();
55
56 return [
57 map { [ -1, $_, int((-s $_)/512), "TESTDISK", 512] } $disks->@*
58 ];
59 }
60
61 my $res = [];
62 my $count = 0;
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+$|;
70
71 my $dev = file_read_firstline("$bd/dev");
72 chomp $dev;
73
74 next if !$dev;
75
76 my $info = `udevadm info --path $bd --query all`;
77 next if !$info;
78
79 next if $info !~ m/^E: DEVTYPE=disk$/m;
80
81 next if $info =~ m/^E: ID_CDROM/m;
82 next if $info =~ m/^E: ID_FS_TYPE=iso9660/m;
83
84 my ($name) = $info =~ m/^N: (\S+)$/m;
85
86 if ($name) {
87 my $real_name = "/dev/$name";
88
89 my $size = file_read_firstline("$bd/size");
90 chomp $size;
91 $size = undef if !($size && $size =~ m/^\d+$/);
92
93 my $model = file_read_firstline("$bd/device/model") || '';
94 $model =~ s/^\s+//;
95 $model =~ s/\s+$//;
96 if (length ($model) > 30) {
97 $model = substr ($model, 0, 30);
98 }
99
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+$/);
103
104 push @$res, [$count++, $real_name, $size, $model, $logical_bsize] if $size;
105 } else {
106 print STDERR "ERROR: unable to map device $dev ($bd)\n";
107 }
108 }
109
110 return $res;
111 }
112
113 my $cached_disks;
114 sub cache_disks {
115 $cached_disks = hd_list();
116 }
117 sub get_cached_disks {
118 cache_disks() if !defined($cached_disks);
119 return $cached_disks;
120 }
121
122 sub find_cached_disk_by_devname {
123 my ($dev, $noerr) = @_;
124
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;
128
129 return $record;
130 }
131
132 sub hd_size {
133 my ($dev) = @_;
134
135 my ($_disk, $_name, $size) = find_cached_disk_by_devname($dev)->@*;
136
137 return int($size / 2);
138 }
139
140 sub logical_blocksize {
141 my ($dev) = @_;
142
143 my ($_disk, $_dev, $_size, $_model, $logical_bsize) = find_cached_disk_by_devname($dev)->@*;
144
145 return $logical_bsize;
146 }
147
148 sub get_partition_dev {
149 my ($dev, $partnum) = @_;
150
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";
166 } else {
167 die "unable to get device for partition $partnum on device $dev\n";
168 }
169 }
170
171 sub udevadm_trigger_block {
172 my ($nowait) = @_;
173
174 sleep(1) if !$nowait; # give kernel time to reread part table
175
176 # trigger udev to create /dev/disk/by-uuid
177 syscmd("udevadm trigger --subsystem-match block");
178 syscmd("udevadm settle --timeout 10");
179 };
180
181 sub wipe_disk {
182 my ($disk) = @_;
183
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`);
187
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"); };
194 }
195 eval { syscmd(['wipefs', '-a', @partitions]) };
196 warn "$@" if $@;
197 };
198
199 sub partition_bootable_disk {
200 my ($target_dev, $maxhdsizegb, $ptype) = @_;
201
202 die "too dangerous" if is_test_mode();
203
204 die "unknown partition type '$ptype'"
205 if !($ptype eq '8E00' || $ptype eq '8300' || $ptype eq 'BF01');
206
207 my $hdsize = hd_size($target_dev); # size in KB (1024 bytes)
208
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;
212
213 my $restricted_hdsize_mb = 0; # 0 ==> end of partition
214 if ($maxhdsizegb) {
215 my $maxhdsize = $maxhdsizegb * 1024 * 1024;
216 if ($maxhdsize < $hdsize) {
217 $hdsize = $maxhdsize;
218 $restricted_hdsize_mb = int($hdsize/1024) . 'M';
219 }
220 }
221
222 my $hdgb = int($hdsize/(1024*1024));
223
224 my ($hard_limit, $soft_limit) = (2, 8);
225
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?"
231 );
232 die "root disk '$target_dev' too small (${hdgb} GB < $soft_limit GB), and warning not accepted.\n"
233 if $response ne 'ok';
234 }
235
236 syscmd("sgdisk -Z ${target_dev}");
237
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
241
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);
245
246 my $pcmd = ['sgdisk'];
247
248 my $pnum = 2;
249 push @$pcmd, "-n${pnum}:1M:+${esp_size}M", "-t$pnum:EF00";
250
251 $pnum = 3;
252 push @$pcmd, "-n${pnum}:${esp_end}M:${restricted_hdsize_mb}", "-t$pnum:$ptype";
253
254 push @$pcmd, $target_dev;
255
256 my $os_size = $hdsize - $esp_end * 1024; # efi + 1M bios_boot + 1M alignment
257
258 syscmd($pcmd) == 0 ||
259 die "unable to partition harddisk '${target_dev}'\n";
260
261 my $blocksize = logical_blocksize($target_dev);
262
263 if ($blocksize != 4096) {
264 $pnum = 1;
265 $pcmd = ['sgdisk', '-a1', "-n$pnum:34:2047", "-t$pnum:EF02" , $target_dev];
266
267 syscmd($pcmd) == 0 ||
268 die "unable to create bios_boot partition '${target_dev}'\n";
269 }
270
271 udevadm_trigger_block();
272
273 foreach my $part ($efibootdev, $osdev) {
274 syscmd("dd if=/dev/zero of=$part bs=1M count=256") if -b $part;
275 }
276
277 return ($os_size, $osdev, $efibootdev);
278 }
279
280
281 1;