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