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;
39 our $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
42 # Schema when disk allocation is possible.
43 our $drivedesc_hash_with_alloc = {};
45 my %drivedesc_base = (
46 volume
=> { alias
=> 'file' },
49 format
=> 'pve-volume-id-or-qm-path',
51 format_description
=> 'volume',
52 description
=> "The drive's backing volume.",
56 enum
=> [qw(cdrom disk)],
57 description
=> "The drive's media type.",
63 description
=> "Force the drive's physical geometry to have a specific cylinder count.",
68 description
=> "Force the drive's physical geometry to have a specific head count.",
73 description
=> "Force the drive's physical geometry to have a specific sector count.",
78 enum
=> [qw(none lba auto)],
79 description
=> "Force disk geometry bios translation mode.",
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.",
91 enum
=> [qw(none writethrough writeback unsafe directsync)],
92 description
=> "The drive's cache mode",
95 format
=> get_standard_option
('pve-qm-image-format'),
98 format
=> 'disk-size',
99 format_description
=> 'DiskSize',
100 description
=> "Disk size. This is purely informational and has no effect.",
105 description
=> "Whether the drive should be included when making backups.",
110 description
=> 'Whether the drive should considered for replication jobs.',
116 enum
=> [qw(ignore report stop)],
117 description
=> 'Read error action.',
122 enum
=> [qw(enospc ignore report stop)],
123 description
=> 'Write error action.',
128 enum
=> [qw(native threads io_uring)],
129 description
=> 'AIO type to use.',
134 enum
=> [qw(ignore on)],
135 description
=> 'Controls whether to pass discard/trim requests to the underlying storage.',
140 description
=> 'Controls whether to detect and try to optimize writes of zeroes.',
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.",
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!",
160 my %iothread_fmt = ( iothread
=> {
162 description
=> "Whether to use iothreads for this drive",
169 pattern
=> '[A-Za-z0-9\-_\s]{,16}', # QEMU (8.1) will quietly only use 16 bytes
170 format_description
=> 'product',
171 description
=> "The drive's product name, up to 16 bytes long.",
179 pattern
=> '[A-Za-z0-9\-_\s]{,8}', # QEMU (8.1) will quietly only use 8 bytes
180 format_description
=> 'vendor',
181 description
=> "The drive's vendor name, up to 8 bytes long.",
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.",
200 description
=> "Number of queues.",
209 description
=> "Whether the drive is read-only.",
214 my %scsiblock_fmt = (
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",
226 description
=> "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
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'.",
241 my $add_throttle_desc = sub {
242 my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
245 format_description
=> $unit,
246 description
=> "Maximum $what in $longunit.",
249 $d->{minimum
} = $minimum if defined($minimum);
250 $drivedesc_base{$key} = $d;
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');
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');
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);
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' };
291 PVE
::JSONSchema
::register_format
("pve-qm-ide", $ide_fmt);
295 type
=> 'string', format
=> $ide_fmt,
296 description
=> "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
298 PVE
::JSONSchema
::register_standard_option
("pve-qm-ide", $idedesc);
313 type
=> 'string', format
=> $scsi_fmt,
314 description
=> "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
316 PVE
::JSONSchema
::register_standard_option
("pve-qm-scsi", $scsidesc);
325 type
=> 'string', format
=> $sata_fmt,
326 description
=> "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
328 PVE
::JSONSchema
::register_standard_option
("pve-qm-sata", $satadesc);
337 type
=> 'string', format
=> $virtio_fmt,
338 description
=> "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
340 PVE
::JSONSchema
::register_standard_option
("pve-qm-virtio", $virtiodesc);
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"
348 . " if not otherwise specified. Ignored for VMs with arch=aarch64 (ARM).",
352 'pre-enrolled-keys' => {
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.",
363 volume
=> { alias
=> 'file' },
366 format
=> 'pve-volume-id-or-qm-path',
368 format_description
=> 'volume',
369 description
=> "The drive's backing volume.",
371 format
=> get_standard_option
('pve-qm-image-format'),
374 format
=> 'disk-size',
375 format_description
=> 'DiskSize',
376 description
=> "Disk size. This is purely informational and has no effect.",
384 type
=> 'string', format
=> $efidisk_fmt,
385 description
=> "Configure a disk for storing EFI vars.",
388 PVE
::JSONSchema
::register_standard_option
("pve-qm-efidisk", $efidisk_desc);
390 my %tpmversion_fmt = (
393 enum
=> [qw(v1.2 v2.0)],
394 description
=> "The TPM interface version. v2.0 is newer and should be preferred."
395 ." Note that this cannot be changed later on.",
401 volume
=> { alias
=> 'file' },
404 format
=> 'pve-volume-id-or-qm-path',
406 format_description
=> 'volume',
407 description
=> "The drive's backing volume.",
411 format
=> 'disk-size',
412 format_description
=> 'DiskSize',
413 description
=> "Disk size. This is purely informational and has no effect.",
418 my $tpmstate_desc = {
420 type
=> 'string', format
=> $tpmstate_fmt,
421 description
=> "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
423 use constant TPMSTATE_DISK_SIZE
=> 4 * 1024 * 1024;
440 my %import_from_fmt = (
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!",
452 my $alldrive_fmt_with_alloc = {
458 volume
=> { alias
=> 'file' },
461 format
=> 'pve-volume-id',
463 format_description
=> 'volume',
464 description
=> "The drive's backing volume.",
470 type
=> 'string', format
=> $unused_fmt,
471 description
=> "Reference to unused volumes. This is used internally, and should not be modified manually.",
474 my $with_alloc_desc_cache = {
475 unused
=> $unuseddesc, # Allocation for unused is not supported currently.
477 my $desc_with_alloc = sub {
478 my ($type, $desc) = @_;
480 return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
482 my $new_desc = dclone
($desc);
484 $new_desc->{format
}->{'import-from'} = $import_from_fmt{'import-from'};
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.";
494 $new_desc->{description
} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
495 "volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an ".
498 $with_alloc_desc_cache->{$type} = $new_desc;
503 for (my $i = 0; $i < $MAX_IDE_DISKS; $i++) {
504 $drivedesc_hash->{"ide$i"} = $idedesc;
505 $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
508 for (my $i = 0; $i < $MAX_SATA_DISKS; $i++) {
509 $drivedesc_hash->{"sata$i"} = $satadesc;
510 $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
513 for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++) {
514 $drivedesc_hash->{"scsi$i"} = $scsidesc;
515 $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
518 for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++) {
519 $drivedesc_hash->{"virtio$i"} = $virtiodesc;
520 $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
523 $drivedesc_hash->{efidisk0
} = $efidisk_desc;
524 $drivedesc_hash_with_alloc->{efidisk0
} = $desc_with_alloc->('efidisk', $efidisk_desc);
526 $drivedesc_hash->{tpmstate0
} = $tpmstate_desc;
527 $drivedesc_hash_with_alloc->{tpmstate0
} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
529 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
530 $drivedesc_hash->{"unused$i"} = $unuseddesc;
531 $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
534 sub valid_drive_names_for_boot
{
535 return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names
();
538 sub 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))),
548 sub valid_drive_names_with_unused
{
549 return (valid_drive_names
(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
552 sub is_valid_drivename
{
555 return defined($drivedesc_hash->{$dev}) && $dev !~ /^unused\d+$/;
558 PVE
::JSONSchema
::register_format
('pve-qm-bootdisk', \
&verify_bootdisk
);
559 sub verify_bootdisk
{
560 my ($value, $noerr) = @_;
562 return $value if is_valid_drivename
($value);
566 die "invalid boot disk '$value'\n";
569 sub drive_is_cloudinit
{
571 return $drive->{file
} =~ m
@[:/](?
:vm-\d
+-)?cloudinit
(?
:\
.$QEMU_FORMAT_RE)?
$@;
575 my ($drive, $exclude_cloudinit) = @_;
577 return 0 if $exclude_cloudinit && drive_is_cloudinit
($drive);
579 return $drive && $drive->{media
} && ($drive->{media
} eq 'cdrom');
582 sub drive_is_read_only
{
583 my ($conf, $drive) = @_;
585 return 0 if !PVE
::QemuConfig-
>is_template($conf);
587 # don't support being marked read-only
588 return $drive->{interface
} ne 'sata' && $drive->{interface
} ne 'ide';
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]
598 my ($key, $data, $with_alloc) = @_;
600 my ($interface, $index);
602 if ($key =~ m/^([^\d]+)(\d+)$/) {
609 my $desc_hash = $with_alloc ?
$drivedesc_hash_with_alloc : $drivedesc_hash;
611 if (!defined($desc_hash->{$key})) {
612 warn "invalid drive key: $key\n";
616 my $desc = $desc_hash->{$key}->{format
};
617 my $res = eval { PVE
::JSONSchema
::parse_property_string
($desc, $data) };
619 $res->{interface
} = $interface;
620 $res->{index} = $index;
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";
630 $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
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";
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
};
662 if ($res->{media
} && ($res->{media
} eq 'cdrom')) {
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';
668 if (my $size = $res->{size
}) {
669 return if !defined($res->{size
} = PVE
::JSONSchema
::parse_size
($size));
676 my ($drive, $with_alloc) = @_;
677 my $skip = [ 'index', 'interface' ];
678 my $fmt = $with_alloc ?
$alldrive_fmt_with_alloc : $alldrive_fmt;
679 return PVE
::JSONSchema
::print_property_string
($drive, $fmt, $skip);
686 $bootcfg = PVE
::JSONSchema
::parse_property_string
('pve-qm-boot', $conf->{boot
}) if $conf->{boot
};
688 if (!defined($bootcfg) || $bootcfg->{legacy
}) {
689 return [$conf->{bootdisk
}] if $conf->{bootdisk
};
693 my @list = PVE
::Tools
::split_list
($bootcfg->{order
});
694 @list = grep {is_valid_drivename
($_)} @list;
699 my ($storecfg, $conf) = @_;
701 my $bootdisks = get_bootdisks
($conf);
702 return if !@$bootdisks;
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
};
711 return $drive->{size
};
717 sub update_disksize
{
718 my ($drive, $newsize) = @_;
720 return if !defined($newsize);
722 my $oldsize = $drive->{size
} // 0;
724 if ($newsize != $oldsize) {
725 $drive->{size
} = $newsize;
727 my $old_fmt = PVE
::JSONSchema
::format_size
($oldsize);
728 my $new_fmt = PVE
::JSONSchema
::format_size
($newsize);
730 my $msg = "size of disk '$drive->{file}' updated from $old_fmt to $new_fmt";
732 return ($drive, $msg);
738 sub is_volume_in_use
{
739 my ($storecfg, $conf, $skip_drive, $volid) = @_;
741 my $path = PVE
::Storage
::path
($storecfg, $volid);
743 my $scan_config = sub {
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;
756 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($drive->{file
}, 1);
758 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storeid, 1);
760 return 1 if $path eq PVE
::Storage
::path
($storecfg, $drive->{file
});
768 return 1 if &$scan_config($conf);
772 for my $snap (values %{$conf->{snapshots
}}) {
773 return 1 if $scan_config->($snap);
779 sub resolve_first_disk
{
780 my ($conf, $cdrom) = @_;
781 my @disks = valid_drive_names_for_boot
();
782 foreach my $ds (@disks) {
783 next if !$conf->{$ds};
784 my $disk = parse_drive
($ds, $conf->{$ds});
785 next if drive_is_cdrom
($disk) xor $cdrom;
792 my($fh, $noerr) = @_;
795 my $SG_GET_VERSION_NUM = 0x2282;
797 my $versionbuf = "\x00" x
8;
798 my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
800 die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
803 my $version = unpack("I", $versionbuf);
804 if ($version < 30000) {
805 die "scsi generic interface too old\n" if !$noerr;
809 my $buf = "\x00" x
36;
810 my $sensebuf = "\x00" x
8;
811 my $cmd = pack("C x3 C x1", 0x12, 36);
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";
817 $sg_io_hdr_t, ord('S'), -3, length($cmd), length($sensebuf), 0, length($buf), $buf, $cmd, $sensebuf, 6000
820 $ret = ioctl($fh, $SG_IO, $packet);
822 die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
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;
833 $res->@{qw(type removable vendor product revision)} = unpack("C C x6 A8 A16 A4", $buf);
835 $res->{removable
} = $res->{removable
} & 128 ?
1 : 0;
836 $res->{type
} &= 0x1F;
844 my $fh = IO
::File-
>new("+<$path") || return;
845 my $res = scsi_inquiry
($fh, 1);
851 sub get_scsi_device_type
{
852 my ($drive, $storecfg, $machine_version) = @_;
854 my $devicetype = 'hd';
856 if (drive_is_cdrom
($drive) || drive_is_cloudinit
($drive)) {
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';
868 } elsif ($drive->{file
} =~ $NEW_DISK_RE){
869 # special syntax cannot be parsed to path
872 $path = PVE
::Storage
::path
($storecfg, $drive->{file
});
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';