1 package PVE
::QemuServer
::Drive
;
6 use Storable
qw(dclone);
11 use PVE
::JSONSchema
qw(get_standard_option);
13 use base
qw(Exporter);
25 our $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
27 PVE
::JSONSchema
::register_standard_option
('pve-qm-image-format', {
29 enum
=> [qw(raw cow qcow qed qcow2 vmdk cloop)],
30 description
=> "The drive's backing file's data format.",
34 my $MAX_IDE_DISKS = 4;
35 my $MAX_SCSI_DISKS = 31;
36 my $MAX_VIRTIO_DISKS = 16;
37 our $MAX_SATA_DISKS = 6;
38 our $MAX_UNUSED_DISKS = 256;
41 # Schema when disk allocation is possible.
42 our $drivedesc_hash_with_alloc = {};
44 my %drivedesc_base = (
45 volume
=> { alias
=> 'file' },
48 format
=> 'pve-volume-id-or-qm-path',
50 format_description
=> 'volume',
51 description
=> "The drive's backing volume.",
55 enum
=> [qw(cdrom disk)],
56 description
=> "The drive's media type.",
62 description
=> "Force the drive's physical geometry to have a specific cylinder count.",
67 description
=> "Force the drive's physical geometry to have a specific head count.",
72 description
=> "Force the drive's physical geometry to have a specific sector count.",
77 enum
=> [qw(none lba auto)],
78 description
=> "Force disk geometry bios translation mode.",
83 description
=> "Controls qemu's snapshot mode feature."
84 . " If activated, changes made to the disk are temporary and will"
85 . " be discarded when the VM is shutdown.",
90 enum
=> [qw(none writethrough writeback unsafe directsync)],
91 description
=> "The drive's cache mode",
94 format
=> get_standard_option
('pve-qm-image-format'),
97 format
=> 'disk-size',
98 format_description
=> 'DiskSize',
99 description
=> "Disk size. This is purely informational and has no effect.",
104 description
=> "Whether the drive should be included when making backups.",
109 description
=> 'Whether the drive should considered for replication jobs.',
115 enum
=> [qw(ignore report stop)],
116 description
=> 'Read error action.',
121 enum
=> [qw(enospc ignore report stop)],
122 description
=> 'Write error action.',
127 enum
=> [qw(native threads io_uring)],
128 description
=> 'AIO type to use.',
133 enum
=> [qw(ignore on)],
134 description
=> 'Controls whether to pass discard/trim requests to the underlying storage.',
139 description
=> 'Controls whether to detect and try to optimize writes of zeroes.',
144 format
=> 'urlencoded',
145 format_description
=> 'serial',
146 maxLength
=> 20*3, # *3 since it's %xx url enoded
147 description
=> "The drive's reported serial number, url-encoded, up to 20 bytes long.",
152 description
=> 'Mark this locally-managed volume as available on all nodes',
153 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!",
159 my %iothread_fmt = ( iothread
=> {
161 description
=> "Whether to use iothreads for this drive",
168 format
=> 'urlencoded',
169 format_description
=> 'model',
170 maxLength
=> 40*3, # *3 since it's %xx url enoded
171 description
=> "The drive's reported model name, url-encoded, up to 40 bytes long.",
179 description
=> "Number of queues.",
188 description
=> "Whether the drive is read-only.",
193 my %scsiblock_fmt = (
196 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",
205 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
213 pattern
=> qr/^(0x)[0-9a-fA-F]{16}/,
214 format_description
=> 'wwn',
215 description
=> "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
220 my $add_throttle_desc = sub {
221 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
224 format_description
=> $unit,
225 description
=> "Maximum $what in $longunit.",
228 $d->{minimum
} = $minimum if defined($minimum);
229 $drivedesc_base{$key} = $d;
231 # throughput: (leaky bucket)
232 $add_throttle_desc->('bps', 'integer', 'r/w speed', 'bps', 'bytes per second');
233 $add_throttle_desc->('bps_rd', 'integer', 'read speed', 'bps', 'bytes per second');
234 $add_throttle_desc->('bps_wr', 'integer', 'write speed', 'bps', 'bytes per second');
235 $add_throttle_desc->('mbps', 'number', 'r/w speed', 'mbps', 'megabytes per second');
236 $add_throttle_desc->('mbps_rd', 'number', 'read speed', 'mbps', 'megabytes per second');
237 $add_throttle_desc->('mbps_wr', 'number', 'write speed', 'mbps', 'megabytes per second');
238 $add_throttle_desc->('iops', 'integer', 'r/w I/O', 'iops', 'operations per second');
239 $add_throttle_desc->('iops_rd', 'integer', 'read I/O', 'iops', 'operations per second');
240 $add_throttle_desc->('iops_wr', 'integer', 'write I/O', 'iops', 'operations per second');
242 # pools: (pool of IO before throttling starts taking effect)
243 $add_throttle_desc->('mbps_max', 'number', 'unthrottled r/w pool', 'mbps', 'megabytes per second');
244 $add_throttle_desc->('mbps_rd_max', 'number', 'unthrottled read pool', 'mbps', 'megabytes per second');
245 $add_throttle_desc->('mbps_wr_max', 'number', 'unthrottled write pool', 'mbps', 'megabytes per second');
246 $add_throttle_desc->('iops_max', 'integer', 'unthrottled r/w I/O pool', 'iops', 'operations per second');
247 $add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool', 'iops', 'operations per second');
248 $add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
251 $add_throttle_desc->('bps_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
252 $add_throttle_desc->('bps_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
253 $add_throttle_desc->('bps_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
254 $add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts', 'seconds', 'seconds', 1);
255 $add_throttle_desc->('iops_rd_max_length', 'integer', 'length of read I/O bursts', 'seconds', 'seconds', 1);
256 $add_throttle_desc->('iops_wr_max_length', 'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
259 $drivedesc_base{'bps_rd_length'} = { alias
=> 'bps_rd_max_length' };
260 $drivedesc_base{'bps_wr_length'} = { alias
=> 'bps_wr_max_length' };
261 $drivedesc_base{'iops_rd_length'} = { alias
=> 'iops_rd_max_length' };
262 $drivedesc_base{'iops_wr_length'} = { alias
=> 'iops_wr_max_length' };
270 PVE
::JSONSchema
::register_format
("pve-qm-ide", $ide_fmt);
274 type
=> 'string', format
=> $ide_fmt,
275 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
277 PVE
::JSONSchema
::register_standard_option
("pve-qm-ide", $idedesc);
290 type
=> 'string', format
=> $scsi_fmt,
291 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
293 PVE
::JSONSchema
::register_standard_option
("pve-qm-scsi", $scsidesc);
302 type
=> 'string', format
=> $sata_fmt,
303 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
305 PVE
::JSONSchema
::register_standard_option
("pve-qm-sata", $satadesc);
314 type
=> 'string', format
=> $virtio_fmt,
315 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
317 PVE
::JSONSchema
::register_standard_option
("pve-qm-virtio", $virtiodesc);
323 description
=> "Size and type of the OVMF EFI vars. '4m' is newer and recommended,"
324 . " and required for Secure Boot. For backwards compatibility, '2m' is used"
325 . " if not otherwise specified. Ignored for VMs with arch=aarch64 (ARM).",
329 'pre-enrolled-keys' => {
331 description
=> "Use am EFI vars template with distribution-specific and Microsoft Standard"
332 ." keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by"
333 ." default, though it can still be turned off from within the VM.",
340 volume
=> { alias
=> 'file' },
343 format
=> 'pve-volume-id-or-qm-path',
345 format_description
=> 'volume',
346 description
=> "The drive's backing volume.",
348 format
=> get_standard_option
('pve-qm-image-format'),
351 format
=> 'disk-size',
352 format_description
=> 'DiskSize',
353 description
=> "Disk size. This is purely informational and has no effect.",
361 type
=> 'string', format
=> $efidisk_fmt,
362 description
=> "Configure a disk for storing EFI vars.",
365 PVE
::JSONSchema
::register_standard_option
("pve-qm-efidisk", $efidisk_desc);
367 my %tpmversion_fmt = (
370 enum
=> [qw(v1.2 v2.0)],
371 description
=> "The TPM interface version. v2.0 is newer and should be preferred."
372 ." Note that this cannot be changed later on.",
378 volume
=> { alias
=> 'file' },
381 format
=> 'pve-volume-id-or-qm-path',
383 format_description
=> 'volume',
384 description
=> "The drive's backing volume.",
388 format
=> 'disk-size',
389 format_description
=> 'DiskSize',
390 description
=> "Disk size. This is purely informational and has no effect.",
395 my $tpmstate_desc = {
397 type
=> 'string', format
=> $tpmstate_fmt,
398 description
=> "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
400 use constant TPMSTATE_DISK_SIZE
=> 4 * 1024 * 1024;
415 my %import_from_fmt = (
418 format
=> 'pve-volume-id-or-absolute-path',
419 format_description
=> 'source volume',
420 description
=> "Create a new disk, importing from this source (volume ID or absolute ".
421 "path). When an absolute path is specified, it's up to you to ensure that the source ".
422 "is not actively used by another process during the import!",
427 my $alldrive_fmt_with_alloc = {
433 volume
=> { alias
=> 'file' },
436 format
=> 'pve-volume-id',
438 format_description
=> 'volume',
439 description
=> "The drive's backing volume.",
445 type
=> 'string', format
=> $unused_fmt,
446 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
449 my $with_alloc_desc_cache = {
450 unused
=> $unuseddesc, # Allocation for unused is not supported currently.
452 my $desc_with_alloc = sub {
453 my ($type, $desc) = @_;
455 return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
457 my $new_desc = dclone
($desc);
459 $new_desc->{format
}->{'import-from'} = $import_from_fmt{'import-from'};
462 if ($type eq 'efidisk') {
463 $extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
464 "copied to the volume instead.";
465 } elsif ($type eq 'tpmstate') {
466 $extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
469 $new_desc->{description
} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
470 "volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an ".
473 $with_alloc_desc_cache->{$type} = $new_desc;
478 for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
479 $drivedesc_hash->{"ide$i"} = $idedesc;
480 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
483 for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
484 $drivedesc_hash->{"sata$i"} = $satadesc;
485 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
488 for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
489 $drivedesc_hash->{"scsi$i"} = $scsidesc;
490 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
493 for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
494 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
495 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
498 $drivedesc_hash->{efidisk0
} = $efidisk_desc;
499 $drivedesc_hash_with_alloc->{efidisk0
} = $desc_with_alloc->('efidisk', $efidisk_desc);
501 $drivedesc_hash->{tpmstate0
} = $tpmstate_desc;
502 $drivedesc_hash_with_alloc->{tpmstate0
} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
504 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
505 $drivedesc_hash->{"unused$i"} = $unuseddesc;
506 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
509 sub valid_drive_names_for_boot
{
510 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names
();
513 sub valid_drive_names
{
514 # order is important - used to autoselect boot disk
515 return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
516 (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
517 (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
518 (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
523 sub valid_drive_names_with_unused
{
524 return (valid_drive_names
(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
527 sub is_valid_drivename
{
530 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
533 PVE
::JSONSchema
::register_format
('pve-qm-bootdisk', \
&verify_bootdisk
);
534 sub verify_bootdisk
{
535 my ($value, $noerr) = @_;
537 return $value if is_valid_drivename
($value);
541 die "invalid boot disk '$value'\n";
544 sub drive_is_cloudinit
{
546 return $drive->{file
} =~ m
@[:/](?
:vm-\d
+-)?cloudinit
(?
:\
.$QEMU_FORMAT_RE)?
$@;
550 my ($drive, $exclude_cloudinit) = @_;
552 return 0 if $exclude_cloudinit && drive_is_cloudinit
($drive);
554 return $drive && $drive->{media
} && ($drive->{media
} eq 'cdrom');
557 sub drive_is_read_only
{
558 my ($conf, $drive) = @_;
560 return 0 if !PVE
::QemuConfig-
>is_template($conf);
562 # don't support being marked read-only
563 return $drive->{interface
} ne 'sata' && $drive->{interface
} ne 'ide';
566 # ideX = [volume=]volume-id[,media=d][,cyls=c,heads=h,secs=s[,trans=t]]
567 # [,snapshot=on|off][,cache=on|off][,format=f][,backup=yes|no]
568 # [,rerror=ignore|report|stop][,werror=enospc|ignore|report|stop]
569 # [,aio=native|threads][,discard=ignore|on][,detect_zeroes=on|off]
570 # [,iothread=on][,serial=serial][,model=model]
573 my ($key, $data, $with_alloc) = @_;
575 my ($interface, $index);
577 if ($key =~ m/^([^\d]+)(\d+)$/) {
584 my $desc_hash = $with_alloc ?
$drivedesc_hash_with_alloc : $drivedesc_hash;
586 if (!defined($desc_hash->{$key})) {
587 warn "invalid drive key: $key\n";
591 my $desc = $desc_hash->{$key}->{format
};
592 my $res = eval { PVE
::JSONSchema
::parse_property_string
($desc, $data) };
594 $res->{interface
} = $interface;
595 $res->{index} = $index;
598 foreach my $opt (qw(bps bps_rd bps_wr)) {
599 if (my $bps = defined(delete $res->{$opt})) {
600 if (defined($res->{"m$opt"})) {
601 warn "both $opt and m$opt specified\n";
605 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
609 # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
610 for my $requirement (
611 [mbps_max
=> 'mbps'],
612 [mbps_rd_max
=> 'mbps_rd'],
613 [mbps_wr_max
=> 'mbps_wr'],
614 [miops_max
=> 'miops'],
615 [miops_rd_max
=> 'miops_rd'],
616 [miops_wr_max
=> 'miops_wr'],
617 [bps_max_length
=> 'mbps_max'],
618 [bps_rd_max_length
=> 'mbps_rd_max'],
619 [bps_wr_max_length
=> 'mbps_wr_max'],
620 [iops_max_length
=> 'iops_max'],
621 [iops_rd_max_length
=> 'iops_rd_max'],
622 [iops_wr_max_length
=> 'iops_wr_max']) {
623 my ($option, $requires) = @$requirement;
624 if ($res->{$option} && !$res->{$requires}) {
625 warn "$option requires $requires\n";
632 return if $res->{mbps_rd
} && $res->{mbps
};
633 return if $res->{mbps_wr
} && $res->{mbps
};
634 return if $res->{iops_rd
} && $res->{iops
};
635 return if $res->{iops_wr
} && $res->{iops
};
637 if ($res->{media
} && ($res->{media
} eq 'cdrom')) {
638 return if $res->{snapshot
} || $res->{trans
} || $res->{format
};
639 return if $res->{heads
} || $res->{secs
} || $res->{cyls
};
640 return if $res->{interface
} eq 'virtio';
643 if (my $size = $res->{size
}) {
644 return if !defined($res->{size
} = PVE
::JSONSchema
::parse_size
($size));
651 my ($drive, $with_alloc) = @_;
652 my $skip = [ 'index', 'interface' ];
653 my $fmt = $with_alloc ?
$alldrive_fmt_with_alloc : $alldrive_fmt;
654 return PVE
::JSONSchema
::print_property_string
($drive, $fmt, $skip);
661 $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $conf->{boot
}) if $conf->{boot
};
663 if (!defined($bootcfg) || $bootcfg->{legacy
}) {
664 return [$conf->{bootdisk
}] if $conf->{bootdisk
};
668 my @list = PVE
::Tools
::split_list
($bootcfg->{order
});
669 @list = grep {is_valid_drivename
($_)} @list;
674 my ($storecfg, $conf) = @_;
676 my $bootdisks = get_bootdisks
($conf);
677 return if !@$bootdisks;
678 for my $bootdisk (@$bootdisks) {
679 next if !is_valid_drivename
($bootdisk);
680 next if !$conf->{$bootdisk};
681 my $drive = parse_drive
($bootdisk, $conf->{$bootdisk});
682 next if !defined($drive);
683 next if drive_is_cdrom
($drive);
684 my $volid = $drive->{file
};
686 return $drive->{size
};
692 sub update_disksize
{
693 my ($drive, $newsize) = @_;
695 return if !defined($newsize);
697 my $oldsize = $drive->{size
} // 0;
699 if ($newsize != $oldsize) {
700 $drive->{size
} = $newsize;
702 my $old_fmt = PVE
::JSONSchema
::format_size
($oldsize);
703 my $new_fmt = PVE
::JSONSchema
::format_size
($newsize);
705 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
707 return ($drive, $msg);
713 sub is_volume_in_use
{
714 my ($storecfg, $conf, $skip_drive, $volid) = @_;
716 my $path = PVE
::Storage
::path
($storecfg, $volid);
718 my $scan_config = sub {
721 foreach my $key (keys %$cref) {
722 my $value = $cref->{$key};
723 if (is_valid_drivename
($key)) {
724 next if $skip_drive && $key eq $skip_drive;
725 my $drive = parse_drive
($key, $value);
726 next if !$drive || !$drive->{file
} || drive_is_cdrom
($drive);
727 return 1 if $volid eq $drive->{file
};
728 if ($drive->{file
} =~ m!^/!) {
729 return 1 if $drive->{file
} eq $path;
731 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
}, 1);
733 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid, 1);
735 return 1 if $path eq PVE
::Storage
::path
($storecfg, $drive->{file
});
743 return 1 if &$scan_config($conf);
747 for my $snap (values %{$conf->{snapshots
}}) {
748 return 1 if $scan_config->($snap);
754 sub resolve_first_disk
{
755 my ($conf, $cdrom) = @_;
756 my @disks = valid_drive_names_for_boot
();
757 foreach my $ds (@disks) {
758 next if !$conf->{$ds};
759 my $disk = parse_drive
($ds, $conf->{$ds});
760 next if drive_is_cdrom
($disk) xor $cdrom;
767 my($fh, $noerr) = @_;
770 my $SG_GET_VERSION_NUM = 0x2282;
772 my $versionbuf = "\x00" x
8;
773 my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
775 die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
778 my $version = unpack("I", $versionbuf);
779 if ($version < 30000) {
780 die "scsi generic interface too old\n" if !$noerr;
784 my $buf = "\x00" x
36;
785 my $sensebuf = "\x00" x
8;
786 my $cmd = pack("C x3 C x1", 0x12, 36);
788 # see /usr/include/scsi/sg.h
789 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";
792 $sg_io_hdr_t, ord('S'), -3, length($cmd), length($sensebuf), 0, length($buf), $buf, $cmd, $sensebuf, 6000
795 $ret = ioctl($fh, $SG_IO, $packet);
797 die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
801 my @res = unpack($sg_io_hdr_t, $packet);
802 if ($res[17] || $res[18]) {
803 die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
808 $res->@{qw(type removable vendor product revision)} = unpack("C C x6 A8 A16 A4", $buf);
810 $res->{removable
} = $res->{removable
} & 128 ?
1 : 0;
811 $res->{type
} &= 0x1F;
819 my $fh = IO
::File-
>new("+<$path") || return;
820 my $res = scsi_inquiry
($fh, 1);