]> git.proxmox.com Git - qemu-server.git/blob - PVE/QemuServer/Drive.pm
schema: drive: use separate schema when disk allocation is possible
[qemu-server.git] / PVE / QemuServer / Drive.pm
1 package PVE::QemuServer::Drive;
2
3 use strict;
4 use warnings;
5
6 use Storable qw(dclone);
7
8 use PVE::Storage;
9 use PVE::JSONSchema qw(get_standard_option);
10
11 use base qw(Exporter);
12
13 our @EXPORT_OK = qw(
14 is_valid_drivename
15 drive_is_cloudinit
16 drive_is_cdrom
17 drive_is_read_only
18 parse_drive
19 print_drive
20 );
21
22 our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
23
24 PVE::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
31 my $MAX_IDE_DISKS = 4;
32 my $MAX_SCSI_DISKS = 31;
33 my $MAX_VIRTIO_DISKS = 16;
34 our $MAX_SATA_DISKS = 6;
35 our $MAX_UNUSED_DISKS = 256;
36
37 our $drivedesc_hash;
38 # Schema when disk allocation is possible.
39 our $drivedesc_hash_with_alloc = {};
40
41 my %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',
124 enum => [qw(native threads io_uring)],
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
156 my %iothread_fmt = ( iothread => {
157 type => 'boolean',
158 description => "Whether to use iothreads for this drive",
159 optional => 1,
160 });
161
162 my %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
173 my %queues_fmt = (
174 queues => {
175 type => 'integer',
176 description => "Number of queues.",
177 minimum => 2,
178 optional => 1
179 }
180 );
181
182 my %readonly_fmt = (
183 ro => {
184 type => 'boolean',
185 description => "Whether the drive is read-only.",
186 optional => 1,
187 },
188 );
189
190 my %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
199 my %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
207 my %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
217 my $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
261 my $ide_fmt = {
262 %drivedesc_base,
263 %model_fmt,
264 %ssd_fmt,
265 %wwn_fmt,
266 };
267 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
268
269 my $idedesc = {
270 optional => 1,
271 type => 'string', format => $ide_fmt,
272 description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
273 };
274 PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
275
276 my $scsi_fmt = {
277 %drivedesc_base,
278 %iothread_fmt,
279 %queues_fmt,
280 %readonly_fmt,
281 %scsiblock_fmt,
282 %ssd_fmt,
283 %wwn_fmt,
284 };
285 my $scsidesc = {
286 optional => 1,
287 type => 'string', format => $scsi_fmt,
288 description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
289 };
290 PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
291
292 my $sata_fmt = {
293 %drivedesc_base,
294 %ssd_fmt,
295 %wwn_fmt,
296 };
297 my $satadesc = {
298 optional => 1,
299 type => 'string', format => $sata_fmt,
300 description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
301 };
302 PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
303
304 my $virtio_fmt = {
305 %drivedesc_base,
306 %iothread_fmt,
307 %readonly_fmt,
308 };
309 my $virtiodesc = {
310 optional => 1,
311 type => 'string', format => $virtio_fmt,
312 description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
313 };
314 PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
315
316 my %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"
322 . " if not otherwise specified.",
323 optional => 1,
324 default => '2m',
325 },
326 'pre-enrolled-keys' => {
327 type => 'boolean',
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.",
331 optional => 1,
332 default => 0,
333 },
334 );
335
336 my $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 },
353 %efitype_fmt,
354 };
355
356 my $efidisk_desc = {
357 optional => 1,
358 type => 'string', format => $efidisk_fmt,
359 description => "Configure a Disk for storing EFI vars.",
360 };
361
362 PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
363
364 my %tpmversion_fmt = (
365 version => {
366 type => 'string',
367 enum => [qw(v1.2 v2.0)],
368 description => "The TPM interface version. v2.0 is newer and should be preferred."
369 ." Note that this cannot be changed later on.",
370 optional => 1,
371 default => 'v2.0',
372 },
373 );
374 my $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 };
392 my $tpmstate_desc = {
393 optional => 1,
394 type => 'string', format => $tpmstate_fmt,
395 description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
396 };
397 use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
398
399 my $alldrive_fmt = {
400 %drivedesc_base,
401 %iothread_fmt,
402 %model_fmt,
403 %queues_fmt,
404 %readonly_fmt,
405 %scsiblock_fmt,
406 %ssd_fmt,
407 %wwn_fmt,
408 %tpmversion_fmt,
409 %efitype_fmt,
410 };
411
412 my $alldrive_fmt_with_alloc = {
413 %$alldrive_fmt,
414 };
415
416 my $unused_fmt = {
417 volume => { alias => 'file' },
418 file => {
419 type => 'string',
420 format => 'pve-volume-id',
421 default_key => 1,
422 format_description => 'volume',
423 description => "The drive's backing volume.",
424 },
425 };
426
427 my $unuseddesc = {
428 optional => 1,
429 type => 'string', format => $unused_fmt,
430 description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
431 };
432
433 my $with_alloc_desc_cache = {
434 unused => $unuseddesc, # Allocation for unused is not supported currently.
435 };
436 my $desc_with_alloc = sub {
437 my ($type, $desc) = @_;
438
439 return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
440
441 my $new_desc = dclone($desc);
442
443 my $extra_note = '';
444 if ($type eq 'efidisk') {
445 $extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
446 "copied to the volume instead.";
447 } elsif ($type eq 'tpmstate') {
448 $extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
449 }
450
451 $new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
452 "volume.${extra_note}";
453
454 $with_alloc_desc_cache->{$type} = $new_desc;
455
456 return $new_desc;
457 };
458
459 for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
460 $drivedesc_hash->{"ide$i"} = $idedesc;
461 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
462 }
463
464 for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
465 $drivedesc_hash->{"sata$i"} = $satadesc;
466 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
467 }
468
469 for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
470 $drivedesc_hash->{"scsi$i"} = $scsidesc;
471 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
472 }
473
474 for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
475 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
476 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
477 }
478
479 $drivedesc_hash->{efidisk0} = $efidisk_desc;
480 $drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
481
482 $drivedesc_hash->{tpmstate0} = $tpmstate_desc;
483 $drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
484
485 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
486 $drivedesc_hash->{"unused$i"} = $unuseddesc;
487 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
488 }
489
490 sub valid_drive_names_for_boot {
491 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();
492 }
493
494 sub valid_drive_names {
495 # order is important - used to autoselect boot disk
496 return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
497 (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
498 (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
499 (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
500 'efidisk0',
501 'tpmstate0');
502 }
503
504 sub valid_drive_names_with_unused {
505 return (valid_drive_names(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
506 }
507
508 sub is_valid_drivename {
509 my $dev = shift;
510
511 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
512 }
513
514 PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
515 sub verify_bootdisk {
516 my ($value, $noerr) = @_;
517
518 return $value if is_valid_drivename($value);
519
520 return if $noerr;
521
522 die "invalid boot disk '$value'\n";
523 }
524
525 sub drive_is_cloudinit {
526 my ($drive) = @_;
527 return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
528 }
529
530 sub drive_is_cdrom {
531 my ($drive, $exclude_cloudinit) = @_;
532
533 return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
534
535 return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
536 }
537
538 sub drive_is_read_only {
539 my ($conf, $drive) = @_;
540
541 return 0 if !PVE::QemuConfig->is_template($conf);
542
543 # don't support being marked read-only
544 return $drive->{interface} ne 'sata' && $drive->{interface} ne 'ide';
545 }
546
547 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
548 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
549 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
550 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
551 # [,iothread=on][,serial=serial][,model=model]
552
553 sub parse_drive {
554 my ($key, $data, $with_alloc) = @_;
555
556 my ($interface, $index);
557
558 if ($key =~ m/^([^\d]+)(\d+)$/) {
559 $interface = $1;
560 $index = $2;
561 } else {
562 return;
563 }
564
565 my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
566
567 if (!defined($desc_hash->{$key})) {
568 warn "invalid drive key: $key\n";
569 return;
570 }
571
572 my $desc = $desc_hash->{$key}->{format};
573 my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
574 return if !$res;
575 $res->{interface} = $interface;
576 $res->{index} = $index;
577
578 my $error = 0;
579 foreach my $opt (qw(bps bps_rd bps_wr)) {
580 if (my $bps = defined(delete $res->{$opt})) {
581 if (defined($res->{"m$opt"})) {
582 warn "both $opt and m$opt specified\n";
583 ++$error;
584 next;
585 }
586 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
587 }
588 }
589
590 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
591 for my $requirement (
592 [mbps_max => 'mbps'],
593 [mbps_rd_max => 'mbps_rd'],
594 [mbps_wr_max => 'mbps_wr'],
595 [miops_max => 'miops'],
596 [miops_rd_max => 'miops_rd'],
597 [miops_wr_max => 'miops_wr'],
598 [bps_max_length => 'mbps_max'],
599 [bps_rd_max_length => 'mbps_rd_max'],
600 [bps_wr_max_length => 'mbps_wr_max'],
601 [iops_max_length => 'iops_max'],
602 [iops_rd_max_length => 'iops_rd_max'],
603 [iops_wr_max_length => 'iops_wr_max']) {
604 my ($option, $requires) = @$requirement;
605 if ($res->{$option} && !$res->{$requires}) {
606 warn "$option requires $requires\n";
607 ++$error;
608 }
609 }
610
611 return if $error;
612
613 return if $res->{mbps_rd} && $res->{mbps};
614 return if $res->{mbps_wr} && $res->{mbps};
615 return if $res->{iops_rd} && $res->{iops};
616 return if $res->{iops_wr} && $res->{iops};
617
618 if ($res->{media} && ($res->{media} eq 'cdrom')) {
619 return if $res->{snapshot} || $res->{trans} || $res->{format};
620 return if $res->{heads} || $res->{secs} || $res->{cyls};
621 return if $res->{interface} eq 'virtio';
622 }
623
624 if (my $size = $res->{size}) {
625 return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));
626 }
627
628 return $res;
629 }
630
631 sub print_drive {
632 my ($drive, $with_alloc) = @_;
633 my $skip = [ 'index', 'interface' ];
634 my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
635 return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
636 }
637
638 sub get_bootdisks {
639 my ($conf) = @_;
640
641 my $bootcfg;
642 $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot}) if $conf->{boot};
643
644 if (!defined($bootcfg) || $bootcfg->{legacy}) {
645 return [$conf->{bootdisk}] if $conf->{bootdisk};
646 return [];
647 }
648
649 my @list = PVE::Tools::split_list($bootcfg->{order});
650 @list = grep {is_valid_drivename($_)} @list;
651 return \@list;
652 }
653
654 sub bootdisk_size {
655 my ($storecfg, $conf) = @_;
656
657 my $bootdisks = get_bootdisks($conf);
658 return if !@$bootdisks;
659 for my $bootdisk (@$bootdisks) {
660 next if !is_valid_drivename($bootdisk);
661 next if !$conf->{$bootdisk};
662 my $drive = parse_drive($bootdisk, $conf->{$bootdisk});
663 next if !defined($drive);
664 next if drive_is_cdrom($drive);
665 my $volid = $drive->{file};
666 next if !$volid;
667 return $drive->{size};
668 }
669
670 return;
671 }
672
673 sub update_disksize {
674 my ($drive, $newsize) = @_;
675
676 return if !defined($newsize);
677
678 my $oldsize = $drive->{size} // 0;
679
680 if ($newsize != $oldsize) {
681 $drive->{size} = $newsize;
682
683 my $old_fmt = PVE::JSONSchema::format_size($oldsize);
684 my $new_fmt = PVE::JSONSchema::format_size($newsize);
685
686 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
687
688 return ($drive, $msg);
689 }
690
691 return;
692 }
693
694 sub is_volume_in_use {
695 my ($storecfg, $conf, $skip_drive, $volid) = @_;
696
697 my $path = PVE::Storage::path($storecfg, $volid);
698
699 my $scan_config = sub {
700 my ($cref) = @_;
701
702 foreach my $key (keys %$cref) {
703 my $value = $cref->{$key};
704 if (is_valid_drivename($key)) {
705 next if $skip_drive && $key eq $skip_drive;
706 my $drive = parse_drive($key, $value);
707 next if !$drive || !$drive->{file} || drive_is_cdrom($drive);
708 return 1 if $volid eq $drive->{file};
709 if ($drive->{file} =~ m!^/!) {
710 return 1 if $drive->{file} eq $path;
711 } else {
712 my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1);
713 next if !$storeid;
714 my $scfg = PVE::Storage::storage_config($storecfg, $storeid, 1);
715 next if !$scfg;
716 return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file});
717 }
718 }
719 }
720
721 return 0;
722 };
723
724 return 1 if &$scan_config($conf);
725
726 undef $skip_drive;
727
728 for my $snap (values %{$conf->{snapshots}}) {
729 return 1 if $scan_config->($snap);
730 }
731
732 return 0;
733 }
734
735 sub resolve_first_disk {
736 my ($conf, $cdrom) = @_;
737 my @disks = valid_drive_names_for_boot();
738 foreach my $ds (@disks) {
739 next if !$conf->{$ds};
740 my $disk = parse_drive($ds, $conf->{$ds});
741 next if drive_is_cdrom($disk) xor $cdrom;
742 return $ds;
743 }
744 return;
745 }
746
747 1;