]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/Drive.pm
ovmf efi disk: ignore `efitype` parameter for ARM VMs
[qemu-server.git] / PVE / QemuServer / Drive.pm
CommitLineData
e0fd2b2f
FE
1package PVE::QemuServer::Drive;
2
3use strict;
4use warnings;
5
c1accf9d
FE
6use Storable qw(dclone);
7
e0fd2b2f
FE
8use PVE::Storage;
9use PVE::JSONSchema qw(get_standard_option);
10
11use base qw(Exporter);
12
13our @EXPORT_OK = qw(
14is_valid_drivename
15drive_is_cloudinit
16drive_is_cdrom
75748d44 17drive_is_read_only
e0fd2b2f
FE
18parse_drive
19print_drive
e0fd2b2f
FE
20);
21
22our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
23
24PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
25 type => 'string',
26 enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
27 description => "The drive's backing file's data format.",
28 optional => 1,
29});
30
31my $MAX_IDE_DISKS = 4;
32my $MAX_SCSI_DISKS = 31;
33my $MAX_VIRTIO_DISKS = 16;
34our $MAX_SATA_DISKS = 6;
35our $MAX_UNUSED_DISKS = 256;
36
37our $drivedesc_hash;
c1accf9d
FE
38# Schema when disk allocation is possible.
39our $drivedesc_hash_with_alloc = {};
e0fd2b2f
FE
40
41my %drivedesc_base = (
42 volume => { alias => 'file' },
43 file => {
44 type => 'string',
45 format => 'pve-volume-id-or-qm-path',
46 default_key => 1,
47 format_description => 'volume',
48 description => "The drive's backing volume.",
49 },
50 media => {
51 type => 'string',
52 enum => [qw(cdrom disk)],
53 description => "The drive's media type.",
54 default => 'disk',
55 optional => 1
56 },
57 cyls => {
58 type => 'integer',
59 description => "Force the drive's physical geometry to have a specific cylinder count.",
60 optional => 1
61 },
62 heads => {
63 type => 'integer',
64 description => "Force the drive's physical geometry to have a specific head count.",
65 optional => 1
66 },
67 secs => {
68 type => 'integer',
69 description => "Force the drive's physical geometry to have a specific sector count.",
70 optional => 1
71 },
72 trans => {
73 type => 'string',
74 enum => [qw(none lba auto)],
75 description => "Force disk geometry bios translation mode.",
76 optional => 1,
77 },
78 snapshot => {
79 type => 'boolean',
80 description => "Controls qemu's snapshot mode feature."
81 . " If activated, changes made to the disk are temporary and will"
82 . " be discarded when the VM is shutdown.",
83 optional => 1,
84 },
85 cache => {
86 type => 'string',
87 enum => [qw(none writethrough writeback unsafe directsync)],
88 description => "The drive's cache mode",
89 optional => 1,
90 },
91 format => get_standard_option('pve-qm-image-format'),
92 size => {
93 type => 'string',
94 format => 'disk-size',
95 format_description => 'DiskSize',
96 description => "Disk size. This is purely informational and has no effect.",
97 optional => 1,
98 },
99 backup => {
100 type => 'boolean',
101 description => "Whether the drive should be included when making backups.",
102 optional => 1,
103 },
104 replicate => {
105 type => 'boolean',
106 description => 'Whether the drive should considered for replication jobs.',
107 optional => 1,
108 default => 1,
109 },
110 rerror => {
111 type => 'string',
112 enum => [qw(ignore report stop)],
113 description => 'Read error action.',
114 optional => 1,
115 },
116 werror => {
117 type => 'string',
118 enum => [qw(enospc ignore report stop)],
119 description => 'Write error action.',
120 optional => 1,
121 },
122 aio => {
123 type => 'string',
59e59342 124 enum => [qw(native threads io_uring)],
e0fd2b2f
FE
125 description => 'AIO type to use.',
126 optional => 1,
127 },
128 discard => {
129 type => 'string',
130 enum => [qw(ignore on)],
131 description => 'Controls whether to pass discard/trim requests to the underlying storage.',
132 optional => 1,
133 },
134 detect_zeroes => {
135 type => 'boolean',
136 description => 'Controls whether to detect and try to optimize writes of zeroes.',
137 optional => 1,
138 },
139 serial => {
140 type => 'string',
141 format => 'urlencoded',
142 format_description => 'serial',
143 maxLength => 20*3, # *3 since it's %xx url enoded
144 description => "The drive's reported serial number, url-encoded, up to 20 bytes long.",
145 optional => 1,
146 },
147 shared => {
148 type => 'boolean',
149 description => 'Mark this locally-managed volume as available on all nodes',
150 verbose_description => "Mark this locally-managed volume as available on all nodes.\n\nWARNING: This option does not share the volume automatically, it assumes it is shared already!",
151 optional => 1,
152 default => 0,
153 }
154);
155
156my %iothread_fmt = ( iothread => {
157 type => 'boolean',
158 description => "Whether to use iothreads for this drive",
159 optional => 1,
160});
161
162my %model_fmt = (
163 model => {
164 type => 'string',
165 format => 'urlencoded',
166 format_description => 'model',
167 maxLength => 40*3, # *3 since it's %xx url enoded
168 description => "The drive's reported model name, url-encoded, up to 40 bytes long.",
169 optional => 1,
170 },
171);
172
173my %queues_fmt = (
174 queues => {
175 type => 'integer',
176 description => "Number of queues.",
177 minimum => 2,
178 optional => 1
179 }
180);
181
12e1d472
DC
182my %readonly_fmt = (
183 ro => {
184 type => 'boolean',
185 description => "Whether the drive is read-only.",
186 optional => 1,
187 },
188);
189
e0fd2b2f
FE
190my %scsiblock_fmt = (
191 scsiblock => {
192 type => 'boolean',
193 description => "whether to use scsi-block for full passthrough of host block device\n\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host",
194 optional => 1,
195 default => 0,
196 },
197);
198
199my %ssd_fmt = (
200 ssd => {
201 type => 'boolean',
202 description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
203 optional => 1,
204 },
205);
206
207my %wwn_fmt = (
208 wwn => {
209 type => 'string',
210 pattern => qr/^(0x)[0-9a-fA-F]{16}/,
211 format_description => 'wwn',
212 description => "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
213 optional => 1,
214 },
215);
216
217my $add_throttle_desc = sub {
218 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
219 my $d = {
220 type => $type,
221 format_description => $unit,
222 description => "Maximum $what in $longunit.",
223 optional => 1,
224 };
225 $d->{minimum} = $minimum if defined($minimum);
226 $drivedesc_base{$key} = $d;
227};
228# throughput: (leaky bucket)
229$add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');
230$add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');
231$add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');
232$add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');
233$add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');
234$add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');
235$add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');
236$add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');
237$add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');
238
239# pools: (pool of IO before throttling starts taking effect)
240$add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');
241$add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes per second');
242$add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes per second');
243$add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations per second');
244$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations per second');
245$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
246
247# burst lengths
248$add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
249$add_throttle_desc->('bps_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
250$add_throttle_desc->('bps_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
251$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
252$add_throttle_desc->('iops_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
253$add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
254
255# legacy support
256$drivedesc_base{'bps_rd_length'} = { alias => 'bps_rd_max_length' };
257$drivedesc_base{'bps_wr_length'} = { alias => 'bps_wr_max_length' };
258$drivedesc_base{'iops_rd_length'} = { alias => 'iops_rd_max_length' };
259$drivedesc_base{'iops_wr_length'} = { alias => 'iops_wr_max_length' };
260
261my $ide_fmt = {
262 %drivedesc_base,
263 %model_fmt,
264 %ssd_fmt,
265 %wwn_fmt,
266};
267PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
268
269my $idedesc = {
270 optional => 1,
271 type => 'string', format => $ide_fmt,
c1accf9d 272 description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
e0fd2b2f
FE
273};
274PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
275
276my $scsi_fmt = {
277 %drivedesc_base,
278 %iothread_fmt,
279 %queues_fmt,
12e1d472 280 %readonly_fmt,
e0fd2b2f
FE
281 %scsiblock_fmt,
282 %ssd_fmt,
283 %wwn_fmt,
284};
285my $scsidesc = {
286 optional => 1,
287 type => 'string', format => $scsi_fmt,
c1accf9d 288 description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
e0fd2b2f
FE
289};
290PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
291
292my $sata_fmt = {
293 %drivedesc_base,
294 %ssd_fmt,
295 %wwn_fmt,
296};
297my $satadesc = {
298 optional => 1,
299 type => 'string', format => $sata_fmt,
c1accf9d 300 description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
e0fd2b2f
FE
301};
302PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
303
304my $virtio_fmt = {
305 %drivedesc_base,
306 %iothread_fmt,
12e1d472 307 %readonly_fmt,
e0fd2b2f
FE
308};
309my $virtiodesc = {
310 optional => 1,
311 type => 'string', format => $virtio_fmt,
c1accf9d 312 description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
e0fd2b2f
FE
313};
314PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
315
b5099b4f
SR
316my %efitype_fmt = (
317 efitype => {
318 type => 'string',
319 enum => [qw(2m 4m)],
320 description => "Size and type of the OVMF EFI vars. '4m' is newer and recommended,"
321 . " and required for Secure Boot. For backwards compatibility, '2m' is used"
1183c8f1 322 . " if not otherwise specified. Ignored for VMs with arch=aarc64 (ARM).",
b5099b4f
SR
323 optional => 1,
324 default => '2m',
325 },
a064e511 326 'pre-enrolled-keys' => {
b5099b4f 327 type => 'boolean',
a064e511
TL
328 description => "Use am EFI vars template with distribution-specific and Microsoft Standard"
329 ." keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by"
330 ." default, though it can still be turned off from within the VM.",
b5099b4f
SR
331 optional => 1,
332 default => 0,
333 },
334);
335
e0fd2b2f
FE
336my $efidisk_fmt = {
337 volume => { alias => 'file' },
338 file => {
339 type => 'string',
340 format => 'pve-volume-id-or-qm-path',
341 default_key => 1,
342 format_description => 'volume',
343 description => "The drive's backing volume.",
344 },
345 format => get_standard_option('pve-qm-image-format'),
346 size => {
347 type => 'string',
348 format => 'disk-size',
349 format_description => 'DiskSize',
350 description => "Disk size. This is purely informational and has no effect.",
351 optional => 1,
352 },
b5099b4f 353 %efitype_fmt,
e0fd2b2f
FE
354};
355
356my $efidisk_desc = {
357 optional => 1,
358 type => 'string', format => $efidisk_fmt,
1183c8f1 359 description => "Configure a disk for storing EFI vars.",
e0fd2b2f
FE
360};
361
362PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
363
f9dde219
SR
364my %tpmversion_fmt = (
365 version => {
366 type => 'string',
367 enum => [qw(v1.2 v2.0)],
2b9ee944
TL
368 description => "The TPM interface version. v2.0 is newer and should be preferred."
369 ." Note that this cannot be changed later on.",
f9dde219
SR
370 optional => 1,
371 default => 'v2.0',
372 },
373);
374my $tpmstate_fmt = {
375 volume => { alias => 'file' },
376 file => {
377 type => 'string',
378 format => 'pve-volume-id-or-qm-path',
379 default_key => 1,
380 format_description => 'volume',
381 description => "The drive's backing volume.",
382 },
383 size => {
384 type => 'string',
385 format => 'disk-size',
386 format_description => 'DiskSize',
387 description => "Disk size. This is purely informational and has no effect.",
388 optional => 1,
389 },
390 %tpmversion_fmt,
391};
392my $tpmstate_desc = {
393 optional => 1,
394 type => 'string', format => $tpmstate_fmt,
c1accf9d 395 description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
f9dde219
SR
396};
397use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
398
399my $alldrive_fmt = {
400 %drivedesc_base,
401 %iothread_fmt,
402 %model_fmt,
403 %queues_fmt,
12e1d472 404 %readonly_fmt,
f9dde219
SR
405 %scsiblock_fmt,
406 %ssd_fmt,
407 %wwn_fmt,
408 %tpmversion_fmt,
b5099b4f 409 %efitype_fmt,
f9dde219
SR
410};
411
e6ac9fed
DJ
412my %import_from_fmt = (
413 'import-from' => {
414 type => 'string',
415 format => 'pve-volume-id-or-absolute-path',
416 format_description => 'source volume',
417 description => "Create a new disk, importing from this source (volume ID or absolute ".
418 "path). When an absolute path is specified, it's up to you to ensure that the source ".
419 "is not actively used by another process during the import!",
420 optional => 1,
421 },
422);
423
c1accf9d
FE
424my $alldrive_fmt_with_alloc = {
425 %$alldrive_fmt,
e6ac9fed 426 %import_from_fmt,
c1accf9d
FE
427};
428
43c4c7b6
FE
429my $unused_fmt = {
430 volume => { alias => 'file' },
431 file => {
432 type => 'string',
433 format => 'pve-volume-id',
434 default_key => 1,
435 format_description => 'volume',
436 description => "The drive's backing volume.",
437 },
438};
439
440my $unuseddesc = {
441 optional => 1,
442 type => 'string', format => $unused_fmt,
443 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
444};
445
c1accf9d
FE
446my $with_alloc_desc_cache = {
447 unused => $unuseddesc, # Allocation for unused is not supported currently.
448};
449my $desc_with_alloc = sub {
450 my ($type, $desc) = @_;
451
452 return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
453
454 my $new_desc = dclone($desc);
455
e6ac9fed
DJ
456 $new_desc->{format}->{'import-from'} = $import_from_fmt{'import-from'};
457
c1accf9d
FE
458 my $extra_note = '';
459 if ($type eq 'efidisk') {
460 $extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
461 "copied to the volume instead.";
462 } elsif ($type eq 'tpmstate') {
463 $extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
464 }
465
466 $new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
e6ac9fed
DJ
467 "volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an ".
468 "existing volume.";
c1accf9d
FE
469
470 $with_alloc_desc_cache->{$type} = $new_desc;
471
472 return $new_desc;
473};
474
e0fd2b2f
FE
475for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
476 $drivedesc_hash->{"ide$i"} = $idedesc;
c1accf9d 477 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
e0fd2b2f
FE
478}
479
480for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
481 $drivedesc_hash->{"sata$i"} = $satadesc;
c1accf9d 482 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
e0fd2b2f
FE
483}
484
485for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
486 $drivedesc_hash->{"scsi$i"} = $scsidesc;
c1accf9d 487 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
e0fd2b2f
FE
488}
489
490for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
491 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
c1accf9d 492 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
e0fd2b2f
FE
493}
494
495$drivedesc_hash->{efidisk0} = $efidisk_desc;
c1accf9d
FE
496$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
497
f9dde219 498$drivedesc_hash->{tpmstate0} = $tpmstate_desc;
c1accf9d 499$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
e0fd2b2f 500
43c4c7b6
FE
501for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
502 $drivedesc_hash->{"unused$i"} = $unuseddesc;
c1accf9d 503 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
43c4c7b6 504}
e0fd2b2f 505
1319908f
DC
506sub valid_drive_names_for_boot {
507 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();
508}
509
e0fd2b2f
FE
510sub valid_drive_names {
511 # order is important - used to autoselect boot disk
512 return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
513 (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
514 (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
515 (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
f9dde219
SR
516 'efidisk0',
517 'tpmstate0');
e0fd2b2f
FE
518}
519
10713730
AL
520sub valid_drive_names_with_unused {
521 return (valid_drive_names(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
522}
523
e0fd2b2f
FE
524sub is_valid_drivename {
525 my $dev = shift;
526
43c4c7b6 527 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
e0fd2b2f
FE
528}
529
530PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
531sub verify_bootdisk {
532 my ($value, $noerr) = @_;
533
534 return $value if is_valid_drivename($value);
535
d1c1af4b 536 return if $noerr;
e0fd2b2f
FE
537
538 die "invalid boot disk '$value'\n";
539}
540
541sub drive_is_cloudinit {
542 my ($drive) = @_;
f5a88e98 543 return $drive->{file} =~ m@[:/](?:vm-\d+-)?cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
e0fd2b2f
FE
544}
545
546sub drive_is_cdrom {
547 my ($drive, $exclude_cloudinit) = @_;
548
549 return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
550
551 return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
552}
553
75748d44
FG
554sub drive_is_read_only {
555 my ($conf, $drive) = @_;
556
557 return 0 if !PVE::QemuConfig->is_template($conf);
558
559 # don't support being marked read-only
560 return $drive->{interface} ne 'sata' && $drive->{interface} ne 'ide';
561}
562
e0fd2b2f
FE
563# ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
564# [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
565# [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
566# [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
567# [,iothread=on][,serial=serial][,model=model]
568
569sub parse_drive {
c1accf9d 570 my ($key, $data, $with_alloc) = @_;
e0fd2b2f
FE
571
572 my ($interface, $index);
573
574 if ($key =~ m/^([^\d]+)(\d+)$/) {
575 $interface = $1;
576 $index = $2;
577 } else {
d1c1af4b 578 return;
e0fd2b2f
FE
579 }
580
c1accf9d
FE
581 my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
582
583 if (!defined($desc_hash->{$key})) {
e0fd2b2f 584 warn "invalid drive key: $key\n";
d1c1af4b 585 return;
e0fd2b2f 586 }
43c4c7b6 587
c1accf9d 588 my $desc = $desc_hash->{$key}->{format};
e0fd2b2f 589 my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
d1c1af4b 590 return if !$res;
e0fd2b2f
FE
591 $res->{interface} = $interface;
592 $res->{index} = $index;
593
594 my $error = 0;
595 foreach my $opt (qw(bps bps_rd bps_wr)) {
596 if (my $bps = defined(delete $res->{$opt})) {
597 if (defined($res->{"m$opt"})) {
598 warn "both $opt and m$opt specified\n";
599 ++$error;
600 next;
601 }
602 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
603 }
604 }
605
606 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
607 for my $requirement (
608 [mbps_max => 'mbps'],
609 [mbps_rd_max => 'mbps_rd'],
610 [mbps_wr_max => 'mbps_wr'],
611 [miops_max => 'miops'],
612 [miops_rd_max => 'miops_rd'],
613 [miops_wr_max => 'miops_wr'],
614 [bps_max_length => 'mbps_max'],
615 [bps_rd_max_length => 'mbps_rd_max'],
616 [bps_wr_max_length => 'mbps_wr_max'],
617 [iops_max_length => 'iops_max'],
618 [iops_rd_max_length => 'iops_rd_max'],
619 [iops_wr_max_length => 'iops_wr_max']) {
620 my ($option, $requires) = @$requirement;
621 if ($res->{$option} && !$res->{$requires}) {
622 warn "$option requires $requires\n";
623 ++$error;
624 }
625 }
626
d1c1af4b 627 return if $error;
e0fd2b2f 628
d1c1af4b
TL
629 return if $res->{mbps_rd} && $res->{mbps};
630 return if $res->{mbps_wr} && $res->{mbps};
631 return if $res->{iops_rd} && $res->{iops};
632 return if $res->{iops_wr} && $res->{iops};
e0fd2b2f
FE
633
634 if ($res->{media} && ($res->{media} eq 'cdrom')) {
d1c1af4b
TL
635 return if $res->{snapshot} || $res->{trans} || $res->{format};
636 return if $res->{heads} || $res->{secs} || $res->{cyls};
637 return if $res->{interface} eq 'virtio';
e0fd2b2f
FE
638 }
639
640 if (my $size = $res->{size}) {
d1c1af4b 641 return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));
e0fd2b2f
FE
642 }
643
644 return $res;
645}
646
647sub print_drive {
c1accf9d 648 my ($drive, $with_alloc) = @_;
e0fd2b2f 649 my $skip = [ 'index', 'interface' ];
c1accf9d
FE
650 my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
651 return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
e0fd2b2f
FE
652}
653
2141a802
SR
654sub get_bootdisks {
655 my ($conf) = @_;
656
f7d1505b
TL
657 my $bootcfg;
658 $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot}) if $conf->{boot};
2141a802
SR
659
660 if (!defined($bootcfg) || $bootcfg->{legacy}) {
661 return [$conf->{bootdisk}] if $conf->{bootdisk};
662 return [];
663 }
664
665 my @list = PVE::Tools::split_list($bootcfg->{order});
666 @list = grep {is_valid_drivename($_)} @list;
667 return \@list;
668}
669
776c5f50 670sub bootdisk_size {
e0fd2b2f
FE
671 my ($storecfg, $conf) = @_;
672
2141a802 673 my $bootdisks = get_bootdisks($conf);
d1c1af4b 674 return if !@$bootdisks;
30664f14
DC
675 for my $bootdisk (@$bootdisks) {
676 next if !is_valid_drivename($bootdisk);
677 next if !$conf->{$bootdisk};
678 my $drive = parse_drive($bootdisk, $conf->{$bootdisk});
679 next if !defined($drive);
680 next if drive_is_cdrom($drive);
681 my $volid = $drive->{file};
682 next if !$volid;
683 return $drive->{size};
684 }
e0fd2b2f 685
30664f14 686 return;
e0fd2b2f
FE
687}
688
689sub update_disksize {
9b29cbd0 690 my ($drive, $newsize) = @_;
e0fd2b2f 691
d1c1af4b 692 return if !defined($newsize);
e0fd2b2f 693
63e313f3 694 my $oldsize = $drive->{size} // 0;
e0fd2b2f 695
63e313f3 696 if ($newsize != $oldsize) {
e0fd2b2f
FE
697 $drive->{size} = $newsize;
698
699 my $old_fmt = PVE::JSONSchema::format_size($oldsize);
700 my $new_fmt = PVE::JSONSchema::format_size($newsize);
701
9b29cbd0
FE
702 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
703
704 return ($drive, $msg);
e0fd2b2f
FE
705 }
706
d1c1af4b 707 return;
e0fd2b2f
FE
708}
709
710sub is_volume_in_use {
711 my ($storecfg, $conf, $skip_drive, $volid) = @_;
712
713 my $path = PVE::Storage::path($storecfg, $volid);
714
715 my $scan_config = sub {
ab5b97d8 716 my ($cref) = @_;
e0fd2b2f
FE
717
718 foreach my $key (keys %$cref) {
719 my $value = $cref->{$key};
720 if (is_valid_drivename($key)) {
721 next if $skip_drive && $key eq $skip_drive;
722 my $drive = parse_drive($key, $value);
723 next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
724 return 1 if $volid eq $drive->{file};
725 if ($drive->{file} =~ m!^/!) {
726 return 1 if $drive->{file} eq $path;
727 } else {
728 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
729 next if !$storeid;
730 my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
731 next if !$scfg;
a0d8d859 732 return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file});
e0fd2b2f
FE
733 }
734 }
735 }
736
737 return 0;
738 };
739
740 return 1 if &$scan_config($conf);
741
742 undef $skip_drive;
743
ab5b97d8
FE
744 for my $snap (values %{$conf->{snapshots}}) {
745 return 1 if $scan_config->($snap);
e0fd2b2f
FE
746 }
747
748 return 0;
749}
750
751sub resolve_first_disk {
5cfa9f5f 752 my ($conf, $cdrom) = @_;
1319908f 753 my @disks = valid_drive_names_for_boot();
5cfa9f5f 754 foreach my $ds (@disks) {
e0fd2b2f
FE
755 next if !$conf->{$ds};
756 my $disk = parse_drive($ds, $conf->{$ds});
5cfa9f5f
SR
757 next if drive_is_cdrom($disk) xor $cdrom;
758 return $ds;
e0fd2b2f 759 }
d1c1af4b 760 return;
e0fd2b2f
FE
761}
762
7631;