The OVF parser only looks at disk drives, so the CD drives of a guest are dropped on import. Appliances shipped as OVA sometimes come with an ISO image in a CD drive, for example to install the guest on first boot. Such a guest then does not boot after the import, and the user is not told about the image at all. Add the CD drives of the manifest as empty drives on their controller, after the disks in the boot order, like the ESXi import does. As CD images cannot be imported, report a referenced image with the existing 'cdrom-image-ignored' warning, so that the import wizard tells the user to configure the drive in the 'Advanced' tab. As the address only takes the position on the controller into account, a CD drive can end up with the address of a disk, for example with a disk on the first IDE controller and the CD drive on the second one. Such a CD drive gets the next free address on its bus. Signed-off-by: Michal Fox <me@dualfroz.com>
523 lines
19 KiB
Perl
523 lines
19 KiB
Perl
# Open Virtualization Format import routines
|
|
# https://www.dmtf.org/standards/ovf
|
|
package PVE::GuestImport::OVF;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use XML::LibXML;
|
|
use File::Spec;
|
|
use File::Basename;
|
|
use Cwd 'realpath';
|
|
|
|
use PVE::Tools;
|
|
use PVE::Storage;
|
|
use PVE::Seccomp qw(stmt /^BPF_/);
|
|
|
|
# map OVF resources types to descriptive strings
|
|
# this will allow us to explore the xml tree without using magic numbers
|
|
# http://schemas.dmtf.org/wbem/cim-html/2/CIM_ResourceAllocationSettingData.html
|
|
my @resources = (
|
|
{ id => 1, dtmf_name => 'Other' },
|
|
{ id => 2, dtmf_name => 'Computer System' },
|
|
{ id => 3, dtmf_name => 'Processor' },
|
|
{ id => 4, dtmf_name => 'Memory' },
|
|
{ id => 5, dtmf_name => 'IDE Controller', pve_type => 'ide' },
|
|
{ id => 6, dtmf_name => 'Parallel SCSI HBA', pve_type => 'scsi' },
|
|
{ id => 7, dtmf_name => 'FC HBA' },
|
|
{ id => 8, dtmf_name => 'iSCSI HBA' },
|
|
{ id => 9, dtmf_name => 'IB HCA' },
|
|
{ id => 10, dtmf_name => 'Ethernet Adapter' },
|
|
{ id => 11, dtmf_name => 'Other Network Adapter' },
|
|
{ id => 12, dtmf_name => 'I/O Slot' },
|
|
{ id => 13, dtmf_name => 'I/O Device' },
|
|
{ id => 14, dtmf_name => 'Floppy Drive' },
|
|
{ id => 15, dtmf_name => 'CD Drive' },
|
|
{ id => 16, dtmf_name => 'DVD drive' },
|
|
{ id => 17, dtmf_name => 'Disk Drive' },
|
|
{ id => 18, dtmf_name => 'Tape Drive' },
|
|
{ id => 19, dtmf_name => 'Storage Extent' },
|
|
{ id => 20, dtmf_name => 'Other storage device', pve_type => 'sata' },
|
|
{ id => 21, dtmf_name => 'Serial port' },
|
|
{ id => 22, dtmf_name => 'Parallel port' },
|
|
{ id => 23, dtmf_name => 'USB Controller' },
|
|
{ id => 24, dtmf_name => 'Graphics controller' },
|
|
{ id => 25, dtmf_name => 'IEEE 1394 Controller' },
|
|
{ id => 26, dtmf_name => 'Partitionable Unit' },
|
|
{ id => 27, dtmf_name => 'Base Partitionable Unit' },
|
|
{ id => 28, dtmf_name => 'Power' },
|
|
{ id => 29, dtmf_name => 'Cooling Capacity' },
|
|
{ id => 30, dtmf_name => 'Ethernet Switch Port' },
|
|
{ id => 31, dtmf_name => 'Logical Disk' },
|
|
{ id => 32, dtmf_name => 'Storage Volume' },
|
|
{ id => 33, dtmf_name => 'Ethernet Connection' },
|
|
{ id => 34, dtmf_name => 'DMTF reserved' },
|
|
{ id => 35, dtmf_name => 'Vendor Reserved' },
|
|
);
|
|
|
|
# see https://schemas.dmtf.org/wbem/cim-html/2.55.0+/CIM_OperatingSystem.html
|
|
my $ostype_ids = {
|
|
18 => 'winxp', # 'WINNT',
|
|
29 => 'solaris', # 'Solaris',
|
|
36 => 'l26', # 'LINUX',
|
|
58 => 'w2k', # 'Windows 2000',
|
|
67 => 'wxp', #'Windows XP',
|
|
69 => 'w2k3', # 'Microsoft Windows Server 2003',
|
|
70 => 'w2k3', # 'Microsoft Windows Server 2003 64-Bit',
|
|
71 => 'wxp', # 'Windows XP 64-Bit',
|
|
72 => 'wxp', # 'Windows XP Embedded',
|
|
73 => 'wvista', # 'Windows Vista',
|
|
74 => 'wvista', # 'Windows Vista 64-Bit',
|
|
75 => 'wxp', # 'Windows Embedded for Point of Service', ??
|
|
76 => 'w2k8', # 'Microsoft Windows Server 2008',
|
|
77 => 'w2k8', # 'Microsoft Windows Server 2008 64-Bit',
|
|
79 => 'l26', # 'RedHat Enterprise Linux',
|
|
80 => 'l26', # 'RedHat Enterprise Linux 64-Bit',
|
|
81 => 'solaris', #'Solaris 64-Bit',
|
|
82 => 'l26', # 'SUSE',
|
|
83 => 'l26', # 'SUSE 64-Bit',
|
|
84 => 'l26', # 'SLES',
|
|
85 => 'l26', # 'SLES 64-Bit',
|
|
87 => 'l26', # 'Novell Linux Desktop',
|
|
89 => 'l26', # 'Mandriva',
|
|
90 => 'l26', # 'Mandriva 64-Bit',
|
|
91 => 'l26', # 'TurboLinux',
|
|
92 => 'l26', # 'TurboLinux 64-Bit',
|
|
93 => 'l26', # 'Ubuntu',
|
|
94 => 'l26', # 'Ubuntu 64-Bit',
|
|
95 => 'l26', # 'Debian',
|
|
96 => 'l26', # 'Debian 64-Bit',
|
|
97 => 'l24', # 'Linux 2.4.x',
|
|
98 => 'l24', # 'Linux 2.4.x 64-Bit',
|
|
99 => 'l26', # 'Linux 2.6.x',
|
|
100 => 'l26', # 'Linux 2.6.x 64-Bit',
|
|
101 => 'l26', # 'Linux 64-Bit',
|
|
103 => 'win7', # 'Microsoft Windows Server 2008 R2',
|
|
105 => 'win7', # 'Microsoft Windows 7',
|
|
106 => 'l26', # 'CentOS 32-bit',
|
|
107 => 'l26', # 'CentOS 64-bit',
|
|
108 => 'l26', # 'Oracle Linux 32-bit',
|
|
109 => 'l26', # 'Oracle Linux 64-bit',
|
|
111 => 'win8', # 'Microsoft Windows Server 2011', ??
|
|
112 => 'win8', # 'Microsoft Windows Server 2012',
|
|
113 => 'win8', # 'Microsoft Windows 8',
|
|
114 => 'win8', # 'Microsoft Windows 8 64-bit',
|
|
115 => 'win8', # 'Microsoft Windows Server 2012 R2',
|
|
116 => 'win10', # 'Microsoft Windows Server 2016',
|
|
117 => 'win8', # 'Microsoft Windows 8.1',
|
|
118 => 'win8', # 'Microsoft Windows 8.1 64-bit',
|
|
119 => 'win10', # 'Microsoft Windows 10',
|
|
120 => 'win10', # 'Microsoft Windows 10 64-bit',
|
|
121 => 'win10', # 'Microsoft Windows Server 2019',
|
|
122 => 'win11', # 'Microsoft Windows 11 64-bit',
|
|
123 => 'win11', # 'Microsoft Windows Server 2022',
|
|
# others => 'other',
|
|
};
|
|
|
|
sub get_ostype {
|
|
my ($id) = @_;
|
|
|
|
return $ostype_ids->{$id} // 'other';
|
|
}
|
|
|
|
my $allowed_nic_models = [
|
|
'e1000', 'e1000e', 'vmxnet3',
|
|
];
|
|
|
|
sub find_by {
|
|
my ($key, $param) = @_;
|
|
foreach my $resource (@resources) {
|
|
if ($resource->{$key} eq $param) {
|
|
return ($resource);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub dtmf_name_to_id {
|
|
my ($dtmf_name) = @_;
|
|
my $found = find_by('dtmf_name', $dtmf_name);
|
|
if ($found) {
|
|
return $found->{id};
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
sub id_to_pve {
|
|
my ($id) = @_;
|
|
my $resource = find_by('id', $id);
|
|
if ($resource) {
|
|
return $resource->{pve_type};
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
# technically defined in DSP0004 (https://www.dmtf.org/dsp/DSP0004) as an ABNF
|
|
# but realistically this always takes the form of 'byte * base^exponent'
|
|
sub try_parse_capacity_unit {
|
|
my ($unit_text) = @_;
|
|
|
|
if ($unit_text =~ m/^\s*byte\s*\*\s*([0-9]+)\s*\^\s*([0-9]+)\s*$/) {
|
|
my $base = $1;
|
|
my $exp = $2;
|
|
return $base**$exp;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
my sub read_ovf_file {
|
|
my ($ovf, $isOva) = @_;
|
|
|
|
return PVE::Tools::file_get_contents($ovf) if !$isOva;
|
|
|
|
my $raw = "";
|
|
PVE::Tools::run_command(
|
|
['tar', '-xO', '--wildcards', '--occurrence=1', '-f', $ovf, '--', '*.ovf'],
|
|
outfunc => sub {
|
|
my $line = shift;
|
|
$raw .= $line;
|
|
},
|
|
);
|
|
return $raw;
|
|
}
|
|
|
|
# returns two references, $qm which holds qm.conf style key/values, and \@disks
|
|
my sub parse_ovf_do {
|
|
my ($ovf, $ovf_data, $isOva, $debug) = @_;
|
|
|
|
# we have to ignore missing disk images for ova
|
|
my $dom = XML::LibXML->load_xml(string => $ovf_data, no_blanks => 1);
|
|
|
|
# register the xml namespaces in a xpath context object
|
|
# 'ovf' is the default namespace so it will prepended to each xml element
|
|
my $xpc = XML::LibXML::XPathContext->new($dom);
|
|
$xpc->registerNs('ovf', 'http://schemas.dmtf.org/ovf/envelope/1');
|
|
$xpc->registerNs('vmw', 'http://www.vmware.com/schema/ovf');
|
|
$xpc->registerNs(
|
|
'rasd',
|
|
'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData',
|
|
);
|
|
$xpc->registerNs(
|
|
'vssd',
|
|
'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData',
|
|
);
|
|
|
|
# hash to save qm.conf parameters
|
|
my $qm;
|
|
|
|
#array to save a disk list
|
|
my @disks;
|
|
|
|
# easy xpath
|
|
# walk down the dom until we find the matching XML element
|
|
my $ovf_name = $xpc->findvalue("/ovf:Envelope/ovf:VirtualSystem/ovf:Name");
|
|
if (!$ovf_name) {
|
|
# this is a bit of a hack, but best-effort and can only win here
|
|
my @nodes = $xpc->findnodes("/ovf:Envelope/ovf:VirtualSystem");
|
|
if (my $virtual_system_node = shift @nodes) {
|
|
for my $attr ($virtual_system_node->attributes()) {
|
|
if ($attr->nodeName() eq 'ovf:id') {
|
|
$ovf_name = $attr->getValue();
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($ovf_name) {
|
|
# PVE::QemuServer::confdesc requires a valid DNS name
|
|
$ovf_name =~ s/\s+/-/g;
|
|
($qm->{name} = $ovf_name) =~ s/[^a-zA-Z0-9\-\.]//g;
|
|
} else {
|
|
warn
|
|
"warning: unable to parse the VM name in this OVF manifest, generating a default value\n";
|
|
}
|
|
|
|
# middle level xpath
|
|
# element[child] search the elements which have this [child]
|
|
my $processor_id = dtmf_name_to_id('Processor');
|
|
my $xpath_find_vcpu_count =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${processor_id}]/rasd:VirtualQuantity";
|
|
$qm->{'cores'} = $xpc->findvalue($xpath_find_vcpu_count);
|
|
|
|
my $memory_id = dtmf_name_to_id('Memory');
|
|
my $xpath_find_memory = (
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${memory_id}]/rasd:VirtualQuantity"
|
|
);
|
|
$qm->{'memory'} = $xpc->findvalue($xpath_find_memory);
|
|
|
|
# middle level xpath
|
|
# here we expect multiple results, so we do not read the element value with
|
|
# findvalue() but store multiple elements with findnodes()
|
|
my $disk_id = dtmf_name_to_id('Disk Drive');
|
|
my $xpath_find_disks =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${disk_id}]";
|
|
my @disk_items = $xpc->findnodes($xpath_find_disks);
|
|
|
|
my $xpath_find_ostype_id =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:OperatingSystemSection/\@ovf:id";
|
|
my $ostype_id = $xpc->findvalue($xpath_find_ostype_id);
|
|
$qm->{ostype} = get_ostype($ostype_id);
|
|
|
|
# vmware specific firmware config, seems to not be standardized in ovf ?
|
|
my $xpath_find_firmware =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/vmw:Config[\@vmw:key=\"firmware\"]/\@vmw:value";
|
|
my $firmware = $xpc->findvalue($xpath_find_firmware) || 'seabios';
|
|
$qm->{bios} = 'ovmf' if $firmware eq 'efi';
|
|
|
|
# disks metadata is split in four different xml elements:
|
|
# * as an Item node of type DiskDrive in the VirtualHardwareSection
|
|
# * as an Disk node in the DiskSection
|
|
# * as a File node in the References section
|
|
# * each Item node also holds a reference to its owning controller
|
|
#
|
|
# we iterate over the list of Item nodes of type disk drive, and for each item,
|
|
# find the corresponding Disk node, and File node and owning controller
|
|
# when all the nodes has been found out, we copy the relevant information to
|
|
# a $pve_disk hash ref, which we push to @disks;
|
|
|
|
for my $item_node (@disk_items) {
|
|
my ($disk_node, $file_node, $controller_node, $pve_disk);
|
|
|
|
print "disk item:\n", $item_node->toString(1), "\n" if $debug;
|
|
|
|
# from Item, find corresponding Disk node
|
|
# here the dot means the search should start from the current element in dom
|
|
my $host_resource = $xpc->findvalue('rasd:HostResource', $item_node);
|
|
my $disk_section_path;
|
|
my $disk_id;
|
|
|
|
# RFC 3986 "2.3. Unreserved Characters"
|
|
my $valid_uripath_chars = qr/[[:alnum:]]|[\-\._~]/;
|
|
|
|
if ($host_resource =~ m|^(?:ovf:)?/(${valid_uripath_chars}+)/(${valid_uripath_chars}+)$|) {
|
|
$disk_section_path = $1;
|
|
$disk_id = $2;
|
|
} else {
|
|
warn "invalid host resource $host_resource, skipping\n";
|
|
next;
|
|
}
|
|
printf "disk section path: $disk_section_path and disk id: $disk_id\n" if $debug;
|
|
|
|
# tricky xpath
|
|
# @ means we filter the result query based on a the value of an item attribute ( @ = attribute)
|
|
# @ needs to be escaped to prevent Perl double quote interpolation
|
|
my $xpath_find_fileref = sprintf(
|
|
"/ovf:Envelope/ovf:DiskSection/\
|
|
ovf:Disk[\@ovf:diskId='%s']/\@ovf:fileRef", $disk_id,
|
|
);
|
|
my $xpath_find_capacity = sprintf(
|
|
"/ovf:Envelope/ovf:DiskSection/\
|
|
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacity", $disk_id,
|
|
);
|
|
my $xpath_find_capacity_unit = sprintf(
|
|
"/ovf:Envelope/ovf:DiskSection/\
|
|
ovf:Disk[\@ovf:diskId='%s']/\@ovf:capacityAllocationUnits", $disk_id,
|
|
);
|
|
my $fileref = $xpc->findvalue($xpath_find_fileref);
|
|
my $capacity = $xpc->findvalue($xpath_find_capacity);
|
|
my $capacity_unit = $xpc->findvalue($xpath_find_capacity_unit);
|
|
my $virtual_size;
|
|
if (my $factor = try_parse_capacity_unit($capacity_unit)) {
|
|
$virtual_size = $capacity * $factor;
|
|
}
|
|
|
|
my $valid_url_chars = qr@${valid_uripath_chars}|/@;
|
|
if (!$fileref || $fileref !~ m/^${valid_url_chars}+$/) {
|
|
warn "invalid host resource $host_resource, skipping\n";
|
|
next;
|
|
}
|
|
|
|
# from Item, find owning Controller type
|
|
my $controller_id = $xpc->findvalue('rasd:Parent', $item_node);
|
|
my $xpath_find_parent_type = sprintf(
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
|
|
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id,
|
|
);
|
|
my $controller_type = $xpc->findvalue($xpath_find_parent_type);
|
|
if (!$controller_type) {
|
|
warn "invalid or missing controller: $controller_type, skipping\n";
|
|
next;
|
|
}
|
|
print "owning controller type: $controller_type\n" if $debug;
|
|
|
|
# extract corresponding Controller node details
|
|
my $adress_on_controller = $xpc->findvalue('rasd:AddressOnParent', $item_node);
|
|
my $pve_disk_address = id_to_pve($controller_type) . $adress_on_controller;
|
|
|
|
# from Disk Node, find corresponding filepath
|
|
my $xpath_find_filepath =
|
|
sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $fileref);
|
|
my $filepath = $xpc->findvalue($xpath_find_filepath);
|
|
if (!$filepath) {
|
|
warn "invalid file reference $fileref, skipping\n";
|
|
next;
|
|
}
|
|
print "file path: $filepath\n" if $debug;
|
|
my $original_filepath = $filepath;
|
|
($filepath) = $filepath =~ m|^(${PVE::Storage::SAFE_CHAR_WITH_WHITESPACE_CLASS_RE}+)$|; # untaint & check no sub/parent dirs
|
|
die "referenced path '$original_filepath' is invalid\n"
|
|
if !$filepath || $filepath eq "." || $filepath eq "..";
|
|
|
|
push @disks,
|
|
{
|
|
filepath => $filepath,
|
|
virtual_size => $virtual_size,
|
|
disk_address => $pve_disk_address,
|
|
};
|
|
}
|
|
|
|
my $nic_id = dtmf_name_to_id('Ethernet Adapter');
|
|
my $xpath_find_nics =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${nic_id}]";
|
|
my @nic_items = $xpc->findnodes($xpath_find_nics);
|
|
|
|
my $net = {};
|
|
|
|
my $net_count = 0;
|
|
for my $item_node (@nic_items) {
|
|
my $model = $xpc->findvalue('rasd:ResourceSubType', $item_node);
|
|
$model = lc($model);
|
|
$model = 'e1000' if !grep { $_ eq $model } @$allowed_nic_models;
|
|
$net->{"net${net_count}"} = { model => $model };
|
|
$net_count++;
|
|
}
|
|
|
|
# CD drives are added empty, as images cannot be imported, but the referenced image is kept
|
|
# so the user can be told about it
|
|
my $cdrom_id = dtmf_name_to_id('CD Drive');
|
|
my $xpath_find_cdroms =
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${cdrom_id}]";
|
|
my @cdrom_items = $xpc->findnodes($xpath_find_cdroms);
|
|
|
|
my $addresses_in_use = { map { $_->{disk_address} => 1 } @disks };
|
|
my @cdroms;
|
|
for my $item_node (@cdrom_items) {
|
|
my $controller_id = $xpc->findvalue('rasd:Parent', $item_node);
|
|
my $xpath_find_parent_type = sprintf(
|
|
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/\
|
|
ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id,
|
|
);
|
|
my $bus = id_to_pve($xpc->findvalue($xpath_find_parent_type));
|
|
if (!$bus) {
|
|
warn "invalid or missing controller for CD drive, skipping\n";
|
|
next;
|
|
}
|
|
|
|
my $index = $xpc->findvalue('rasd:AddressOnParent', $item_node);
|
|
$index = 0 if $index !~ m/^\d+$/;
|
|
$index++ while $addresses_in_use->{"$bus$index"};
|
|
my $address = "$bus$index";
|
|
$addresses_in_use->{$address} = 1;
|
|
|
|
my $image;
|
|
my $host_resource = $xpc->findvalue('rasd:HostResource', $item_node);
|
|
if ($host_resource =~ m|^(?:ovf:)?/file/([[:alnum:]\-\._~]+)$|) {
|
|
my $xpath_find_image =
|
|
sprintf("/ovf:Envelope/ovf:References/ovf:File[\@ovf:id='%s']/\@ovf:href", $1);
|
|
$image = $xpc->findvalue($xpath_find_image) || undef;
|
|
}
|
|
|
|
$qm->{$address} = 'none,media=cdrom';
|
|
push @cdroms, { disk_address => $address, image => $image };
|
|
}
|
|
|
|
return { qm => $qm, disks => \@disks, net => $net, cdroms => \@cdroms };
|
|
}
|
|
|
|
my sub resolve_disks {
|
|
my ($ovf, $qm, $isOva) = @_;
|
|
|
|
my $boot_order = [];
|
|
|
|
my $parsed_disks = delete($qm->{disks});
|
|
|
|
my @disks;
|
|
for my $disk (@$parsed_disks) {
|
|
my $filepath = $disk->{filepath};
|
|
my $virtual_size = $disk->{virtual_size};
|
|
my $pve_disk_address = $disk->{disk_address};
|
|
|
|
# resolve symlinks and relative path components
|
|
# and die if the diskimage is not somewhere under the $ovf path
|
|
my $ovf_dir = realpath(dirname(File::Spec->rel2abs($ovf)))
|
|
or die "could not get absolute path of $ovf: $!\n";
|
|
my $backing_file_path = realpath(join('/', $ovf_dir, $filepath))
|
|
or die "could not get absolute path of $filepath: $!\n";
|
|
if ($backing_file_path !~ /^\Q${ovf_dir}\E/) {
|
|
die "error parsing $filepath, are you using a symlink ?\n";
|
|
}
|
|
|
|
($backing_file_path) = $backing_file_path =~ m|^(/.*)|; # untaint
|
|
|
|
if (!-e $backing_file_path && !$isOva) {
|
|
die "error parsing $filepath, file seems not to exist at $backing_file_path\n";
|
|
}
|
|
|
|
if (!$isOva) {
|
|
my $size = PVE::Storage::file_size_info($backing_file_path, undef, 'auto-detect');
|
|
die "error parsing $backing_file_path, cannot determine file size\n"
|
|
if !$size;
|
|
|
|
$virtual_size = $size;
|
|
}
|
|
|
|
my $pve_disk = {
|
|
disk_address => $pve_disk_address,
|
|
backing_file => $backing_file_path,
|
|
virtual_size => $virtual_size,
|
|
relative_path => $filepath,
|
|
};
|
|
$pve_disk->{virtual_size} = $virtual_size if defined($virtual_size);
|
|
push @disks, $pve_disk;
|
|
push @$boot_order, $pve_disk_address;
|
|
}
|
|
|
|
push @$boot_order, map { $_->{disk_address} } $qm->{cdroms}->@*;
|
|
|
|
$qm->{qm}->{boot} = "order=" . join(';', @$boot_order) if scalar(@$boot_order) > 0;
|
|
|
|
$qm->{disks} = \@disks;
|
|
return;
|
|
}
|
|
|
|
my sub bpf_prog {
|
|
return [
|
|
stmt(BPF_LD | BPF_W | BPF_ABS, 0, 0, 0),
|
|
stmt(BPF_JMP | BPF_JEQ | BPF_K, 4, 0, PVE::Syscall::write),
|
|
stmt(BPF_JMP | BPF_JEQ | BPF_K, 3, 0, PVE::Syscall::exit),
|
|
stmt(BPF_JMP | BPF_JEQ | BPF_K, 2, 0, PVE::Syscall::exit_group),
|
|
stmt(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, PVE::Syscall::brk),
|
|
stmt(BPF_RET | BPF_K, 0, 0, BPF_RET_KILL), # default: no syscall matched, kill the process
|
|
stmt(BPF_RET | BPF_K, 0, 0, BPF_RET_ALLOW), # a syscall above matched, allow it
|
|
];
|
|
}
|
|
|
|
sub parse_ovf {
|
|
my ($ovf, $isOva, $debug) = @_;
|
|
|
|
my $qm = PVE::Tools::run_fork_with_timeout(
|
|
3,
|
|
sub {
|
|
my $ovf_data = read_ovf_file($ovf, $isOva);
|
|
|
|
PVE::Seccomp::set_no_new_privs();
|
|
PVE::Seccomp::set_filter(bpf_prog());
|
|
|
|
return parse_ovf_do($ovf, $ovf_data, $isOva, $debug);
|
|
},
|
|
);
|
|
|
|
if (!$qm) {
|
|
die "OVF parser terminated unexpectedly trying to parse $ovf\n";
|
|
}
|
|
|
|
resolve_disks($ovf, $qm, $isOva);
|
|
|
|
return $qm;
|
|
}
|
|
|
|
1;
|