]> git.proxmox.com Git - qemu-server.git/blame_incremental - PVE/QemuServer/Drive.pm
api: support VM disk import
[qemu-server.git] / PVE / QemuServer / Drive.pm
... / ...
CommitLineData
1package PVE::QemuServer::Drive;
2
3use strict;
4use warnings;
5
6use Storable qw(dclone);
7
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
17drive_is_read_only
18parse_drive
19print_drive
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;
38# Schema when disk allocation is possible.
39our $drivedesc_hash_with_alloc = {};
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',
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
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
182my %readonly_fmt = (
183 ro => {
184 type => 'boolean',
185 description => "Whether the drive is read-only.",
186 optional => 1,
187 },
188);
189
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,
272 description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
273};
274PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
275
276my $scsi_fmt = {
277 %drivedesc_base,
278 %iothread_fmt,
279 %queues_fmt,
280 %readonly_fmt,
281 %scsiblock_fmt,
282 %ssd_fmt,
283 %wwn_fmt,
284};
285my $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};
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,
300 description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
301};
302PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
303
304my $virtio_fmt = {
305 %drivedesc_base,
306 %iothread_fmt,
307 %readonly_fmt,
308};
309my $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};
314PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
315
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"
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
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 },
353 %efitype_fmt,
354};
355
356my $efidisk_desc = {
357 optional => 1,
358 type => 'string', format => $efidisk_fmt,
359 description => "Configure a Disk for storing EFI vars.",
360};
361
362PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
363
364my %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);
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,
395 description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
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,
404 %readonly_fmt,
405 %scsiblock_fmt,
406 %ssd_fmt,
407 %wwn_fmt,
408 %tpmversion_fmt,
409 %efitype_fmt,
410};
411
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
424my $alldrive_fmt_with_alloc = {
425 %$alldrive_fmt,
426 %import_from_fmt,
427};
428
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
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
456 $new_desc->{format}->{'import-from'} = $import_from_fmt{'import-from'};
457
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 ".
467 "volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an ".
468 "existing volume.";
469
470 $with_alloc_desc_cache->{$type} = $new_desc;
471
472 return $new_desc;
473};
474
475for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
476 $drivedesc_hash->{"ide$i"} = $idedesc;
477 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
478}
479
480for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
481 $drivedesc_hash->{"sata$i"} = $satadesc;
482 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
483}
484
485for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
486 $drivedesc_hash->{"scsi$i"} = $scsidesc;
487 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
488}
489
490for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
491 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
492 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
493}
494
495$drivedesc_hash->{efidisk0} = $efidisk_desc;
496$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
497
498$drivedesc_hash->{tpmstate0} = $tpmstate_desc;
499$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
500
501for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
502 $drivedesc_hash->{"unused$i"} = $unuseddesc;
503 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
504}
505
506sub valid_drive_names_for_boot {
507 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();
508}
509
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))),
516 'efidisk0',
517 'tpmstate0');
518}
519
520sub valid_drive_names_with_unused {
521 return (valid_drive_names(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
522}
523
524sub is_valid_drivename {
525 my $dev = shift;
526
527 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
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
536 return if $noerr;
537
538 die "invalid boot disk '$value'\n";
539}
540
541sub drive_is_cloudinit {
542 my ($drive) = @_;
543 return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
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
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
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 {
570 my ($key, $data, $with_alloc) = @_;
571
572 my ($interface, $index);
573
574 if ($key =~ m/^([^\d]+)(\d+)$/) {
575 $interface = $1;
576 $index = $2;
577 } else {
578 return;
579 }
580
581 my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
582
583 if (!defined($desc_hash->{$key})) {
584 warn "invalid drive key: $key\n";
585 return;
586 }
587
588 my $desc = $desc_hash->{$key}->{format};
589 my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
590 return if !$res;
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
627 return if $error;
628
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};
633
634 if ($res->{media} && ($res->{media} eq 'cdrom')) {
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';
638 }
639
640 if (my $size = $res->{size}) {
641 return if !defined($res->{size} = PVE::JSONSchema::parse_size($size));
642 }
643
644 return $res;
645}
646
647sub print_drive {
648 my ($drive, $with_alloc) = @_;
649 my $skip = [ 'index', 'interface' ];
650 my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
651 return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
652}
653
654sub get_bootdisks {
655 my ($conf) = @_;
656
657 my $bootcfg;
658 $bootcfg = PVE::JSONSchema::parse_property_string('pve-qm-boot', $conf->{boot}) if $conf->{boot};
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
670sub bootdisk_size {
671 my ($storecfg, $conf) = @_;
672
673 my $bootdisks = get_bootdisks($conf);
674 return if !@$bootdisks;
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 }
685
686 return;
687}
688
689sub update_disksize {
690 my ($drive, $newsize) = @_;
691
692 return if !defined($newsize);
693
694 my $oldsize = $drive->{size} // 0;
695
696 if ($newsize != $oldsize) {
697 $drive->{size} = $newsize;
698
699 my $old_fmt = PVE::JSONSchema::format_size($oldsize);
700 my $new_fmt = PVE::JSONSchema::format_size($newsize);
701
702 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
703
704 return ($drive, $msg);
705 }
706
707 return;
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 {
716 my ($cref) = @_;
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;
732 return 1 if $path eq PVE::Storage::path($storecfg, $drive->{file});
733 }
734 }
735 }
736
737 return 0;
738 };
739
740 return 1 if &$scan_config($conf);
741
742 undef $skip_drive;
743
744 for my $snap (values %{$conf->{snapshots}}) {
745 return 1 if $scan_config->($snap);
746 }
747
748 return 0;
749}
750
751sub resolve_first_disk {
752 my ($conf, $cdrom) = @_;
753 my @disks = valid_drive_names_for_boot();
754 foreach my $ds (@disks) {
755 next if !$conf->{$ds};
756 my $disk = parse_drive($ds, $conf->{$ds});
757 next if drive_is_cdrom($disk) xor $cdrom;
758 return $ds;
759 }
760 return;
761}
762
7631;