]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer/Drive.pm
ovmf efi disk: ignore `efitype` parameter for ARM VMs
[qemu-server.git] / PVE / QemuServer / Drive.pm
index 6389dbb2967df7c5079b9b40242de9c7eeca5655..b0e0a961a402b4a54c3376ede6bd22226e2b0e1f 100644 (file)
@@ -3,6 +3,8 @@ package PVE::QemuServer::Drive;
 use strict;
 use warnings;
 
+use Storable qw(dclone);
+
 use PVE::Storage;
 use PVE::JSONSchema qw(get_standard_option);
 
@@ -33,6 +35,8 @@ our $MAX_SATA_DISKS = 6;
 our $MAX_UNUSED_DISKS = 256;
 
 our $drivedesc_hash;
+# Schema when disk allocation is possible.
+our $drivedesc_hash_with_alloc = {};
 
 my %drivedesc_base = (
     volume => { alias => 'file' },
@@ -175,6 +179,14 @@ my %queues_fmt = (
     }
 );
 
+my %readonly_fmt = (
+    ro => {
+       type => 'boolean',
+       description => "Whether the drive is read-only.",
+       optional => 1,
+    },
+);
+
 my %scsiblock_fmt = (
     scsiblock => {
        type => 'boolean',
@@ -254,14 +266,10 @@ my $ide_fmt = {
 };
 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
-my $ALLOCATION_SYNTAX_DESC =
-    "Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.";
-
 my $idedesc = {
     optional => 1,
     type => 'string', format => $ide_fmt,
-    description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS -1) . "). " .
-       $ALLOCATION_SYNTAX_DESC,
+    description => "Use volume as IDE hard disk or CD-ROM (n is 0 to " .($MAX_IDE_DISKS - 1) . ").",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-ide", $idedesc);
 
@@ -269,6 +277,7 @@ my $scsi_fmt = {
     %drivedesc_base,
     %iothread_fmt,
     %queues_fmt,
+    %readonly_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
     %wwn_fmt,
@@ -276,8 +285,7 @@ my $scsi_fmt = {
 my $scsidesc = {
     optional => 1,
     type => 'string', format => $scsi_fmt,
-    description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . "). " .
-       $ALLOCATION_SYNTAX_DESC,
+    description => "Use volume as SCSI hard disk or CD-ROM (n is 0 to " . ($MAX_SCSI_DISKS - 1) . ").",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
 
@@ -289,23 +297,42 @@ my $sata_fmt = {
 my $satadesc = {
     optional => 1,
     type => 'string', format => $sata_fmt,
-    description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). "). " .
-       $ALLOCATION_SYNTAX_DESC,
+    description => "Use volume as SATA hard disk or CD-ROM (n is 0 to " . ($MAX_SATA_DISKS - 1). ").",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-sata", $satadesc);
 
 my $virtio_fmt = {
     %drivedesc_base,
     %iothread_fmt,
+    %readonly_fmt,
 };
 my $virtiodesc = {
     optional => 1,
     type => 'string', format => $virtio_fmt,
-    description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . "). " .
-       $ALLOCATION_SYNTAX_DESC,
+    description => "Use volume as VIRTIO hard disk (n is 0 to " . ($MAX_VIRTIO_DISKS - 1) . ").",
 };
 PVE::JSONSchema::register_standard_option("pve-qm-virtio", $virtiodesc);
 
+my %efitype_fmt = (
+    efitype => {
+       type => 'string',
+       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. Ignored for VMs with arch=aarc64 (ARM).",
+       optional => 1,
+       default => '2m',
+    },
+    'pre-enrolled-keys' => {
+       type => 'boolean',
+       description => "Use am EFI vars template with distribution-specific and Microsoft Standard"
+           ." keys enrolled, if used with 'efitype=4m'. Note that this will enable Secure Boot by"
+           ." default, though it can still be turned off from within the VM.",
+       optional => 1,
+       default => 0,
+    },
+);
+
 my $efidisk_fmt = {
     volume => { alias => 'file' },
     file => {
@@ -323,14 +350,13 @@ my $efidisk_fmt = {
        description => "Disk size. This is purely informational and has no effect.",
        optional => 1,
     },
+    %efitype_fmt,
 };
 
 my $efidisk_desc = {
     optional => 1,
     type => 'string', format => $efidisk_fmt,
-    description => "Configure a Disk for storing EFI vars. " .
-       $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
-       "and that the default EFI vars are copied to the volume instead.",
+    description => "Configure a disk for storing EFI vars.",
 };
 
 PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
@@ -366,10 +392,7 @@ my $tpmstate_fmt = {
 my $tpmstate_desc = {
     optional => 1,
     type => 'string', format => $tpmstate_fmt,
-    description => "Configure a Disk for storing TPM state. " .
-       $ALLOCATION_SYNTAX_DESC . " Note that SIZE_IN_GiB is ignored here " .
-       "and that the default size of 4 MiB will always be used instead. The " .
-       "format is also fixed to 'raw'.",
+    description => "Configure a Disk for storing TPM state. The format is fixed to 'raw'.",
 };
 use constant TPMSTATE_DISK_SIZE => 4 * 1024 * 1024;
 
@@ -378,10 +401,29 @@ my $alldrive_fmt = {
     %iothread_fmt,
     %model_fmt,
     %queues_fmt,
+    %readonly_fmt,
     %scsiblock_fmt,
     %ssd_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 = {
@@ -401,27 +443,68 @@ my $unuseddesc = {
     description => "Reference to unused volumes. This is used internally, and should not be modified manually.",
 };
 
+my $with_alloc_desc_cache = {
+    unused => $unuseddesc, # Allocation for unused is not supported currently.
+};
+my $desc_with_alloc = sub {
+    my ($type, $desc) = @_;
+
+    return $with_alloc_desc_cache->{$type} if $with_alloc_desc_cache->{$type};
+
+    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 ".
+           "copied to the volume instead.";
+    } elsif ($type eq 'tpmstate') {
+       $extra_note = " Note that SIZE_IN_GiB is ignored here and 4 MiB will be used instead.";
+    }
+
+    $new_desc->{description} .= " Use the special syntax STORAGE_ID:SIZE_IN_GiB to allocate a new ".
+       "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;
+
+    return $new_desc;
+};
+
 for (my $i = 0; $i < $MAX_IDE_DISKS; $i++)  {
     $drivedesc_hash->{"ide$i"} = $idedesc;
+    $drivedesc_hash_with_alloc->{"ide$i"} = $desc_with_alloc->('ide', $idedesc);
 }
 
 for (my $i = 0; $i < $MAX_SATA_DISKS; $i++)  {
     $drivedesc_hash->{"sata$i"} = $satadesc;
+    $drivedesc_hash_with_alloc->{"sata$i"} = $desc_with_alloc->('sata', $satadesc);
 }
 
 for (my $i = 0; $i < $MAX_SCSI_DISKS; $i++)  {
     $drivedesc_hash->{"scsi$i"} = $scsidesc;
+    $drivedesc_hash_with_alloc->{"scsi$i"} = $desc_with_alloc->('scsi', $scsidesc);
 }
 
 for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++)  {
     $drivedesc_hash->{"virtio$i"} = $virtiodesc;
+    $drivedesc_hash_with_alloc->{"virtio$i"} = $desc_with_alloc->('virtio', $virtiodesc);
 }
 
 $drivedesc_hash->{efidisk0} = $efidisk_desc;
+$drivedesc_hash_with_alloc->{efidisk0} = $desc_with_alloc->('efidisk', $efidisk_desc);
+
 $drivedesc_hash->{tpmstate0} = $tpmstate_desc;
+$drivedesc_hash_with_alloc->{tpmstate0} = $desc_with_alloc->('tpmstate', $tpmstate_desc);
 
 for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) {
     $drivedesc_hash->{"unused$i"} = $unuseddesc;
+    $drivedesc_hash_with_alloc->{"unused$i"} = $desc_with_alloc->('unused', $unuseddesc);
+}
+
+sub valid_drive_names_for_boot {
+    return grep { $_ ne 'efidisk0' && $_ ne 'tpmstate0' } valid_drive_names();
 }
 
 sub valid_drive_names {
@@ -434,6 +517,10 @@ sub valid_drive_names {
             'tpmstate0');
 }
 
+sub valid_drive_names_with_unused {
+    return (valid_drive_names(), map {"unused$_"} (0 .. ($MAX_UNUSED_DISKS - 1)));
+}
+
 sub is_valid_drivename {
     my $dev = shift;
 
@@ -453,7 +540,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 {
@@ -480,7 +567,7 @@ sub drive_is_read_only {
 #        [,iothread=on][,serial=serial][,model=model]
 
 sub parse_drive {
-    my ($key, $data) = @_;
+    my ($key, $data, $with_alloc) = @_;
 
     my ($interface, $index);
 
@@ -491,12 +578,14 @@ sub parse_drive {
        return;
     }
 
-    if (!defined($drivedesc_hash->{$key})) {
+    my $desc_hash = $with_alloc ? $drivedesc_hash_with_alloc : $drivedesc_hash;
+
+    if (!defined($desc_hash->{$key})) {
        warn "invalid drive key: $key\n";
        return;
     }
 
-    my $desc = $drivedesc_hash->{$key}->{format};
+    my $desc = $desc_hash->{$key}->{format};
     my $res = eval { PVE::JSONSchema::parse_property_string($desc, $data) };
     return if !$res;
     $res->{interface} = $interface;
@@ -556,9 +645,10 @@ sub parse_drive {
 }
 
 sub print_drive {
-    my ($drive) = @_;
+    my ($drive, $with_alloc) = @_;
     my $skip = [ 'index', 'interface' ];
-    return PVE::JSONSchema::print_property_string($drive, $alldrive_fmt, $skip);
+    my $fmt = $with_alloc ? $alldrive_fmt_with_alloc : $alldrive_fmt;
+    return PVE::JSONSchema::print_property_string($drive, $fmt, $skip);
 }
 
 sub get_bootdisks {
@@ -660,7 +750,7 @@ sub is_volume_in_use {
 
 sub resolve_first_disk {
     my ($conf, $cdrom) = @_;
-    my @disks = valid_drive_names();
+    my @disks = valid_drive_names_for_boot();
     foreach my $ds (@disks) {
        next if !$conf->{$ds};
        my $disk = parse_drive($ds, $conf->{$ds});