]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer/Drive.pm
schema: fix description of migrate_downtime parameter
[qemu-server.git] / PVE / QemuServer / Drive.pm
index cebf1730ad6d7ad8b2a00bd6a59a5078891ff7d4..6a4fafd95cf07c17c06b5d94e83ec8a30d59ab4c 100644 (file)
@@ -5,6 +5,8 @@ use warnings;
 
 use Storable qw(dclone);
 
+use IO::File;
+
 use PVE::Storage;
 use PVE::JSONSchema qw(get_standard_option);
 
@@ -15,6 +17,7 @@ is_valid_drivename
 drive_is_cloudinit
 drive_is_cdrom
 drive_is_read_only
+get_scsi_devicetype
 parse_drive
 print_drive
 );
@@ -33,6 +36,7 @@ my $MAX_SCSI_DISKS = 31;
 my $MAX_VIRTIO_DISKS = 16;
 our $MAX_SATA_DISKS = 6;
 our $MAX_UNUSED_DISKS = 256;
+our $NEW_DISK_RE = qr!^(([^/:\s]+):)?(\d+(\.\d+)?)$!;
 
 our $drivedesc_hash;
 # Schema when disk allocation is possible.
@@ -159,6 +163,26 @@ my %iothread_fmt = ( iothread => {
        optional => 1,
 });
 
+my %product_fmt = (
+    product => {
+       type => 'string',
+       pattern => '[A-Za-z0-9\-_\s]{,16}', # QEMU (8.1) will quietly only use 16 bytes
+       format_description => 'product',
+       description => "The drive's product name, up to 16 bytes long.",
+       optional => 1,
+    },
+);
+
+my %vendor_fmt = (
+    vendor => {
+       type => 'string',
+       pattern => '[A-Za-z0-9\-_\s]{,8}', # QEMU (8.1) will quietly only use 8 bytes
+       format_description => 'vendor',
+       description => "The drive's vendor name, up to 8 bytes long.",
+       optional => 1,
+    },
+);
+
 my %model_fmt = (
     model => {
        type => 'string',
@@ -276,10 +300,12 @@ PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
 my $scsi_fmt = {
     %drivedesc_base,
     %iothread_fmt,
+    %product_fmt,
     %queues_fmt,
     %readonly_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %vendor_fmt,
     %wwn_fmt,
 };
 my $scsidesc = {
@@ -319,7 +345,7 @@ my %efitype_fmt = (
        enum => [qw(2m 4m)],
        description => "Size and type of the OVMF EFI vars. '4m' is newer and recommended,"
            . " and required for Secure Boot. For backwards compatibility, '2m' is used"
-           . " if not otherwise specified.",
+           . " if not otherwise specified. Ignored for VMs with arch=aarch64 (ARM).",
        optional => 1,
        default => '2m',
     },
@@ -356,7 +382,7 @@ my $efidisk_fmt = {
 my $efidisk_desc = {
     optional => 1,
     type => 'string', format => $efidisk_fmt,
-    description => "Configure a Disk for storing EFI vars.",
+    description => "Configure a disk for storing EFI vars.",
 };
 
 PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
@@ -400,17 +426,32 @@ my $alldrive_fmt = {
     %drivedesc_base,
     %iothread_fmt,
     %model_fmt,
+    %product_fmt,
     %queues_fmt,
     %readonly_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %vendor_fmt,
     %wwn_fmt,
     %tpmversion_fmt,
     %efitype_fmt,
 };
 
+my %import_from_fmt = (
+    'import-from' => {
+       type => 'string',
+       format => 'pve-volume-id-or-absolute-path',
+       format_description => 'source volume',
+       description => "Create a new disk, importing from this source (volume ID or absolute ".
+           "path). When an absolute path is specified, it's up to you to ensure that the source ".
+           "is not actively used by another process during the import!",
+       optional => 1,
+    },
+);
+
 my $alldrive_fmt_with_alloc = {
     %$alldrive_fmt,
+    %import_from_fmt,
 };
 
 my $unused_fmt = {
@@ -440,6 +481,8 @@ my $desc_with_alloc = sub {
 
     my $new_desc = dclone($desc);
 
+    $new_desc->{format}->{'import-from'} = $import_from_fmt{'import-from'};
+
     my $extra_note = '';
     if ($type eq 'efidisk') {
        $extra_note = " Note that SIZE_IN_GiB is ignored here and that the default EFI vars are ".
@@ -449,7 +492,8 @@ my $desc_with_alloc = sub {
     }
 
     $new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
-       "volume.${extra_note}";
+       "volume.${extra_note} Use STORAGE_ID:0 and the 'import-from' parameter to import from an ".
+       "existing volume.";
 
     $with_alloc_desc_cache->{$type} = $new_desc;
 
@@ -524,7 +568,7 @@ sub verify_bootdisk {
 
 sub drive_is_cloudinit {
     my ($drive) = @_;
-    return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
+    return $drive->{file} =~ m@[:/](?:vm-\d+-)?cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
 }
 
 sub drive_is_cdrom {
@@ -744,4 +788,97 @@ sub resolve_first_disk {
     return;
 }
 
+sub scsi_inquiry {
+    my($fh, $noerr) = @_;
+
+    my $SG_IO = 0x2285;
+    my $SG_GET_VERSION_NUM = 0x2282;
+
+    my $versionbuf = "\x00" x 8;
+    my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
+    if (!$ret) {
+       die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
+       return;
+    }
+    my $version = unpack("I", $versionbuf);
+    if ($version < 30000) {
+       die "scsi generic interface too old\n"  if !$noerr;
+       return;
+    }
+
+    my $buf = "\x00" x 36;
+    my $sensebuf = "\x00" x 8;
+    my $cmd = pack("C x3 C x1", 0x12, 36);
+
+    # see /usr/include/scsi/sg.h
+    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";
+
+    my $packet = pack(
+       $sg_io_hdr_t, ord('S'), -3, length($cmd), length($sensebuf), 0, length($buf), $buf, $cmd, $sensebuf, 6000
+    );
+
+    $ret = ioctl($fh, $SG_IO, $packet);
+    if (!$ret) {
+       die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
+       return;
+    }
+
+    my @res = unpack($sg_io_hdr_t, $packet);
+    if ($res[17] || $res[18]) {
+       die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
+       return;
+    }
+
+    my $res = {};
+    $res->@{qw(type removable vendor product revision)} = unpack("C C x6 A8 A16 A4", $buf);
+
+    $res->{removable} = $res->{removable} & 128 ? 1 : 0;
+    $res->{type} &= 0x1F;
+
+    return $res;
+}
+
+sub path_is_scsi {
+    my ($path) = @_;
+
+    my $fh = IO::File->new("+<$path") || return;
+    my $res = scsi_inquiry($fh, 1);
+    close($fh);
+
+    return $res;
+}
+
+sub get_scsi_device_type {
+    my ($drive, $storecfg, $machine_version) = @_;
+
+    my $devicetype = 'hd';
+    my $path = '';
+    if (drive_is_cdrom($drive) || drive_is_cloudinit($drive)) {
+       $devicetype = 'cd';
+    } else {
+       if ($drive->{file} =~ m|^/|) {
+           $path = $drive->{file};
+           if (my $info = path_is_scsi($path)) {
+               if ($info->{type} == 0 && $drive->{scsiblock}) {
+                   $devicetype = 'block';
+               } elsif ($info->{type} == 1) { # tape
+                   $devicetype = 'generic';
+               }
+           }
+       } elsif ($drive->{file} =~ $NEW_DISK_RE){
+           # special syntax cannot be parsed to path
+           return $devicetype;
+       } else {
+           $path = PVE::Storage::path($storecfg, $drive->{file});
+       }
+
+       # for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
+       if ($path =~ m/^iscsi\:\/\// &&
+           !PVE::QemuServer::Helpers::min_version($machine_version, 4, 1)) {
+           $devicetype = 'generic';
+       }
+    }
+
+    return $devicetype;
+}
 1;