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