]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
snapshot_delete: reorder parent "connection" after prepare
[qemu-server.git] / PVE / QemuServer.pm
index 591a863225363a8105940fb30f72b8e0f715acf2..ed46df125403579b6794de71d687f6e2ab732d05 100644 (file)
@@ -22,7 +22,7 @@ use PVE::SafeSyslog;
 use Storable qw(dclone);
 use PVE::Exception qw(raise raise_param_exc);
 use PVE::Storage;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline);
+use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach);
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::INotify;
@@ -300,6 +300,13 @@ EODESC
        minimum => 1,
        default => 1,
     },
+    maxcpus => {
+       optional => 1,
+       type => 'integer',
+       description => "Maximum cpus for hotplug.",
+       minimum => 1,
+       default => 1,
+    },
     acpi => {
        optional => 1,
        type => 'boolean',
@@ -430,6 +437,13 @@ EODESCR
        maxLength => 40,
        optional => 1,
     },
+    smbios1 => {
+       description => "Specify SMBIOS type 1 fields.",
+       type => 'string', format => 'pve-qm-smbios1',
+       typetext => "[manufacturer=str][,product=str][,version=str][,serial=str] [,uuid=uuid][,sku=str][,family=str]",
+       maxLength => 256,
+       optional => 1,
+    },
 };
 
 # what about other qemu settings ?
@@ -471,7 +485,7 @@ my $nic_model_list_txt = join(' ', sort @$nic_model_list);
 my $netdesc = {
     optional => 1,
     type => 'string', format => 'pve-qm-net',
-    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,rate=<mbps>][,tag=<vlanid>]",
+    typetext => "MODEL=XX:XX:XX:XX:XX:XX [,bridge=<dev>][,queues=<nbqueues>][,rate=<mbps>][,tag=<vlanid>][,firewall=0|1]",
     description => <<EODESCR,
 Specify network devices.
 
@@ -558,7 +572,7 @@ PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
 my $hostpcidesc = {
         optional => 1,
         type => 'string', format => 'pve-qm-hostpci',
-        typetext => "[host=]HOSTPCIDEVICE [,driver=kvm|vfio] [,rombar=on|off]",
+        typetext => "[host=]HOSTPCIDEVICE [,driver=kvm|vfio] [,rombar=on|off] [,pcie=0|1] [,x-vga=on|off]",
         description => <<EODESCR,
 Map host pci devices. HOSTPCIDEVICE syntax is:
 
@@ -568,8 +582,6 @@ You can us the 'lspci' command to list existing pci devices.
 
 The 'rombar' option determines whether or not the device's ROM will be visible in the guest's memory map (default is 'on').
 
-The 'driver' option is currently ignored.
-
 Note: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
 
 Experimental: user reported problems with this option.
@@ -1052,6 +1064,23 @@ sub path_is_scsi {
     return $res;
 }
 
+sub machine_type_is_q35 {
+    my ($conf) = @_;
+    return $conf->{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0;
+}
+
+sub print_tabletdevice_full {
+    my ($conf) = @_;
+    my $q35 = machine_type_is_q35($conf);
+
+    # we use uhci for old VMs because tablet driver was buggy in older qemu
+    my $usbbus = $q35 ? "ehci" : "uhci";
+    
+    return "usb-tablet,id=tablet,bus=$usbbus.0,port=1";
+}
+
 sub print_drivedevice_full {
     my ($storecfg, $conf, $vmid, $drive, $bridges) = @_;
 
@@ -1118,6 +1147,20 @@ sub print_drivedevice_full {
     return $device;
 }
 
+sub get_initiator_name {
+    my $initiator;
+
+    my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return undef;
+    while (defined(my $line = <$fh>)) {
+       next if $line !~ m/^\s*InitiatorName\s*=\s*([\.\-:\w]+)/;
+       $initiator = $1;
+       last;
+    }
+    $fh->close();
+
+    return $initiator;
+}
+
 sub print_drive_full {
     my ($storecfg, $vmid, $drive) = @_;
 
@@ -1169,6 +1212,11 @@ sub print_netdevice_full {
     my $extra = ($bootorder !~ m/n/) ? "romfile=," : '';
     my $pciaddr = print_pci_addr("$netid", $bridges);
     my $tmpstr = "$device,${extra}mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
+    if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio'){
+       #Consider we have N queues, the number of vectors needed is 2*N + 2 (plus one config interrupt and control vq)
+       my $vectors = $net->{queues} * 2 + 2;
+       $tmpstr .= ",vectors=$vectors,mq=on";
+    }
     $tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
     return $tmpstr;
 }
@@ -1194,11 +1242,17 @@ sub print_netdev_full {
 
     my $vmname = $conf->{name} || "vm$vmid";
 
+    my $netdev = "";
+
     if ($net->{bridge}) {
-        return "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge$vhostparam";
+        $netdev = "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/pve-bridge,downscript=/var/lib/qemu-server/pve-bridgedown$vhostparam";
     } else {
-        return "type=user,id=$netid,hostname=$vmname";
+        $netdev = "type=user,id=$netid,hostname=$vmname";
     }
+
+    $netdev .= ",queues=$net->{queues}" if ($net->{queues} && $net->{model} eq 'virtio');
+
+    return $netdev;
 }
 
 sub drive_is_cdrom {
@@ -1220,13 +1274,23 @@ sub parse_hostpci {
     my $res = {};
     foreach my $kv (@list) {
 
-       if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2}\.[a-f0-9])$/) {
+       if ($kv =~ m/^(host=)?([a-f0-9]{2}:[a-f0-9]{2})(\.([a-f0-9]))?$/) {
            $found = 1;
-           $res->{pciid} = $2;
+           if(defined($4)){
+               push @{$res->{pciid}}, { id => $2 , function => $4};
+
+           }else{
+               my $pcidevices = lspci($2);
+               $res->{pciid} = $pcidevices->{$2};
+           }
        } elsif ($kv =~ m/^driver=(kvm|vfio)$/) {
            $res->{driver} = $1;
        } elsif ($kv =~ m/^rombar=(on|off)$/) {
            $res->{rombar} = $1;
+       } elsif ($kv =~ m/^x-vga=(on|off)$/) {
+           $res->{'x-vga'} = $1;
+       } elsif ($kv =~ m/^pcie=(\d+)$/) {
+           $res->{pcie} = 1 if $1 == 1;
        } else {
            warn "unknown hostpci setting '$kv'\n";
        }
@@ -1252,10 +1316,14 @@ sub parse_net {
            $res->{macaddr} = $mac;
        } elsif ($kvp =~ m/^bridge=(\S+)$/) {
            $res->{bridge} = $1;
+       } elsif ($kvp =~ m/^queues=(\d+)$/) {
+           $res->{queues} = $1;
        } elsif ($kvp =~ m/^rate=(\d+(\.\d+)?)$/) {
            $res->{rate} = $1;
         } elsif ($kvp =~ m/^tag=(\d+)$/) {
             $res->{tag} = $1;
+        } elsif ($kvp =~ m/^firewall=(\d+)$/) {
+           $res->{firewall} = $1;
        } else {
            return undef;
        }
@@ -1275,6 +1343,7 @@ sub print_net {
     $res .= ",bridge=$net->{bridge}" if $net->{bridge};
     $res .= ",rate=$net->{rate}" if $net->{rate};
     $res .= ",tag=$net->{tag}" if $net->{tag};
+    $res .= ",firewall=$net->{firewall}" if $net->{firewall};
 
     return $res;
 }
@@ -1310,6 +1379,45 @@ sub add_unused_volume {
     return $key;
 }
 
+my $valid_smbios1_options = {
+    manufacturer => '\S+',
+    product => '\S+',
+    version => '\S+',
+    serial => '\S+',
+    uuid => '[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}',
+    sku => '\S+',
+    family => '\S+',
+};
+
+# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str]
+sub parse_smbios1 {
+    my ($data) = @_;
+
+    my $res = {};
+
+    foreach my $kvp (split(/,/, $data)) {
+       return undef if $kvp !~ m/^(\S+)=(.+)$/;
+       my ($k, $v) = split(/=/, $kvp);
+       return undef if !defined($k) || !defined($v);
+       return undef if !$valid_smbios1_options->{$k};
+       return undef if $v !~ m/^$valid_smbios1_options->{$k}$/;
+       $res->{$k} = $v;
+    }
+
+    return $res;
+}
+
+PVE::JSONSchema::register_format('pve-qm-smbios1', \&verify_smbios1);
+sub verify_smbios1 {
+    my ($value, $noerr) = @_;
+
+    return $value if parse_smbios1($value);
+
+    return undef if $noerr;
+
+    die "unable to parse smbios (type 1) options\n";
+}
+
 PVE::JSONSchema::register_format('pve-qm-bootdisk', \&verify_bootdisk);
 sub verify_bootdisk {
     my ($value, $noerr) = @_;
@@ -1741,6 +1849,10 @@ sub write_vm_config {
        delete $conf->{smp};
     }
 
+    if ($conf->{maxcpus} && $conf->{sockets}) {
+       delete $conf->{sockets};
+    }
+
     my $used_volids = {};
 
     my $cleanup_config = sub {
@@ -2290,6 +2402,8 @@ sub config_to_command {
 
     my $have_ovz = -f '/proc/vz/vestat';
 
+    my $q35 = machine_type_is_q35($conf);
+
     push @$cmd, '/usr/bin/kvm';
 
     push @$cmd, '-id', $vmid;
@@ -2307,16 +2421,26 @@ sub config_to_command {
 
     push @$cmd, '-daemonize';
 
-    $pciaddr = print_pci_addr("piix3", $bridges);
-    push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2";
+    if ($conf->{smbios1}) {
+       push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
+    }
 
-    my $use_usb2 = 0;
-    for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
-       next if !$conf->{"usb$i"};
-       $use_usb2 = 1;
+    if ($q35) {
+       # the q35 chipset support native usb2, so we enable usb controller 
+       # by default for this machine type
+        push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
+    } else {
+        $pciaddr = print_pci_addr("piix3", $bridges);
+        push @$devices, '-device', "piix3-usb-uhci,id=uhci$pciaddr.0x2";
+
+        my $use_usb2 = 0;
+       for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
+           next if !$conf->{"usb$i"};
+           $use_usb2 = 1;
+       }
+       # include usb device config
+       push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
     }
-    # include usb device config
-    push @$devices, '-readconfig', '/usr/share/qemu-server/pve-usb.cfg' if $use_usb2;
 
     my $vga = $conf->{vga};
 
@@ -2324,8 +2448,8 @@ sub config_to_command {
     $vga = 'qxl' if $qxlnum;
 
     if (!$vga) {
-       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' || 
-                               $conf->{ostype} eq 'win7' || 
+       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
+                               $conf->{ostype} eq 'win7' ||
                                $conf->{ostype} eq 'w2k8')) {
            $vga = 'std';
        } else {
@@ -2343,15 +2467,45 @@ sub config_to_command {
        $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
     }
 
-    push @$devices, '-device', 'usb-tablet,id=tablet,bus=uhci.0,port=1' if $tablet;
+    push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
 
     # host pci devices
     for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
-          my $d = parse_hostpci($conf->{"hostpci$i"});
-          next if !$d;
-         $pciaddr = print_pci_addr("hostpci$i", $bridges);
-         my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : ""; 
-          push @$devices, '-device', "pci-assign,host=$d->{pciid},id=hostpci$i$pciaddr$rombar";
+       my $d = parse_hostpci($conf->{"hostpci$i"});
+       next if !$d;
+
+       my $pcie = $d->{pcie};
+       if($pcie){
+           die "q35 machine model is not enabled" if !$q35;
+           $pciaddr = print_pcie_addr("hostpci$i");
+       }else{
+           $pciaddr = print_pci_addr("hostpci$i", $bridges);
+       }
+
+       my $rombar = $d->{rombar} && $d->{rombar} eq 'off' ? ",rombar=0" : "";
+       my $driver = $d->{driver} && $d->{driver} eq 'vfio' ? "vfio-pci" : "pci-assign";
+       my $xvga = $d->{'x-vga'} && $d->{'x-vga'} eq 'on' ? ",x-vga=on" : "";
+       $driver = "vfio-pci" if $xvga ne '';
+       my $pcidevices = $d->{pciid};
+       my $multifunction = 1 if @$pcidevices > 1;
+
+       my $j=0;
+        foreach my $pcidevice (@$pcidevices) {
+
+           my $id = "hostpci$i";
+           $id .= ".$j" if $multifunction;
+           my $addr = $pciaddr;
+           $addr .= ".$j" if $multifunction;
+           my $devicestr = "$driver,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr";
+
+           if($j == 0){
+               $devicestr .= "$rombar$xvga";
+               $devicestr .= ",multifunction=on" if $multifunction;
+           }
+
+           push @$devices, '-device', $devicestr;
+           $j++;
+       }
     }
 
     # usb devices
@@ -2403,7 +2557,13 @@ sub config_to_command {
     $sockets = $conf->{sockets} if  $conf->{sockets};
 
     my $cores = $conf->{cores} || 1;
-    push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
+    my $maxcpus = $conf->{maxcpus} if $conf->{maxcpus};
+
+    if ($maxcpus) {
+       push @$cmd, '-smp', "cpus=$cores,maxcpus=$maxcpus";
+    } else {
+       push @$cmd, '-smp', "sockets=$sockets,cores=$cores";
+    }
 
     push @$cmd, '-nodefaults';
 
@@ -2487,6 +2647,8 @@ sub config_to_command {
 
     $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
 
+    # Note: enforce needs kernel 3.10, so we do not use it for now
+    # push @$cmd, '-cpu', "$cpu,enforce";
     push @$cmd, '-cpu', $cpu;
 
     push @$cmd, '-S' if $conf->{freeze};
@@ -2554,6 +2716,11 @@ sub config_to_command {
     my $ahcicontroller = {};
     my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : $defaults->{scsihw};
 
+    # Add iscsi initiator name if available
+    if (my $initiator = get_initiator_name()) {
+       push @$devices, '-iscsi', "initiator-name=$initiator";
+    }
+
     foreach_drive($conf, sub {
        my ($ds, $drive) = @_;
 
@@ -2591,8 +2758,9 @@ sub config_to_command {
            $ahcicontroller->{$controller}=1;
         }
 
-       push @$devices, '-drive',print_drive_full($storecfg, $vmid, $drive);
-       push @$devices, '-device',print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
+       my $drive_cmd = print_drive_full($storecfg, $vmid, $drive);
+       push @$devices, '-drive',$drive_cmd;
+       push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
     });
 
     push @$cmd, '-m', $conf->{memory} || $defaults->{memory};
@@ -2616,13 +2784,14 @@ sub config_to_command {
          push @$devices, '-device', $netdevicefull;
     }
 
-    #bridges
-    while (my ($k, $v) = each %$bridges) {
-       $pciaddr = print_pci_addr("pci.$k");
-       unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0;
+    if (!$q35) {
+       # add pci bridges
+       while (my ($k, $v) = each %$bridges) {
+           $pciaddr = print_pci_addr("pci.$k");
+           unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0;
+       }
     }
 
-
     # hack: virtio with fairsched is unreliable, so we do not use fairsched
     # when the VM uses virtio devices.
     if (!$use_virtio && $have_ovz) {
@@ -2702,9 +2871,10 @@ sub vm_deviceplug {
 
     return 1 if !check_running($vmid);
 
+    my $q35 = machine_type_is_q35($conf);
+
     if ($deviceid eq 'tablet') {
-       my $devicefull = "usb-tablet,id=tablet,bus=uhci.0,port=1";
-       qemu_deviceadd($vmid, $devicefull);
+       qemu_deviceadd($vmid, print_tabletdevice_full($conf));
        return 1;
     }
 
@@ -2754,7 +2924,8 @@ sub vm_deviceplug {
         }
     }
 
-    if ($deviceid =~ m/^(pci\.)(\d+)$/) {
+    
+    if (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
        my $bridgeid = $2;
        my $pciaddr = print_pci_addr($deviceid);
        my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
@@ -2927,6 +3098,29 @@ sub qemu_netdevdel {
     return 1;
 }
 
+sub qemu_cpu_hotplug {
+    my ($vmid, $conf, $cores) = @_;
+
+    die "new cores config is not defined" if !$cores;
+    die "you can't add more cores than maxcpus"
+       if $conf->{maxcpus} && ($cores > $conf->{maxcpus});
+    return if !check_running($vmid);
+
+    my $currentcores = $conf->{cores} if $conf->{cores};
+    die "current cores is not defined" if !$currentcores;
+    die "maxcpus is not defined" if !$conf->{maxcpus};
+    raise_param_exc({ 'cores' => "online cpu unplug is not yet possible" })
+       if($cores < $currentcores);
+
+    my $currentrunningcores = vm_mon_cmd($vmid, "query-cpus");
+    raise_param_exc({ 'cores' => "cores number if running vm is different than configuration" })
+       if scalar (@{$currentrunningcores}) != $currentcores;
+
+    for(my $i = $currentcores; $i < $cores; $i++) {
+       vm_mon_cmd($vmid, "cpu-add", id => int($i));
+    }
+}
+
 sub qemu_block_set_io_throttle {
     my ($vmid, $deviceid, $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr) = @_;
 
@@ -3094,31 +3288,29 @@ sub qga_unfreezefs {
     #need to impplement call to qemu-ga
 }
 
-sub PVE::QemuServer::set_migration_caps { 
-    my ($vmid) = @_; 
+sub set_migration_caps {
+    my ($vmid) = @_;
 
-    my @capabilities = ();
-    my $cap_ref = \@capabilities;
+    my $cap_ref = [];
 
     my $enabled_cap = {
-       "auto-converge" => 1,
-       "xbzrle" => 0,
-       "x-rdma-pin-all" => 0,
-       "zero-blocks" => 0,
+       "auto-converge" => 1,
+       "xbzrle" => 0,
+       "x-rdma-pin-all" => 0,
+       "zero-blocks" => 0,
     };
 
-    my $supported_capabilities = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities");
+    my $supported_capabilities = vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities");
 
-    for my $supported_capability (@$supported_capabilities){
-       if($enabled_cap->{$supported_capability->{capability}} eq 1){
-           my $capability->{capability} = $supported_capability->{capability};
-           $capability->{state} = JSON::true;
-           push(@$cap_ref,$capability);
-        }
+    for my $supported_capability (@$supported_capabilities) {
+       push @$cap_ref, {
+           capability => $supported_capability->{capability},
+           state => $enabled_cap->{$supported_capability->{capability}} ? JSON::true : JSON::false,
+       };
     }
 
-    PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
-} 
+    vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
+}
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused, $forcemachine, $spice_ticket) = @_;
@@ -3164,11 +3356,22 @@ sub vm_start {
         for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
           my $d = parse_hostpci($conf->{"hostpci$i"});
           next if !$d;
-          my $info = pci_device_info("0000:$d->{pciid}");
-          die "IOMMU not present\n" if !check_iommu_support();
-          die "no pci device info for device '$d->{pciid}'\n" if !$info;
-          die "can't unbind pci device '$d->{pciid}'\n" if !pci_dev_bind_to_stub($info);
-          die "can't reset pci device '$d->{pciid}'\n" if !pci_dev_reset($info);
+         my $pcidevices = $d->{pciid};
+         foreach my $pcidevice (@$pcidevices) {
+               my $pciid = $pcidevice->{id}.".".$pcidevice->{function};
+
+               my $info = pci_device_info("0000:$pciid");
+               die "IOMMU not present\n" if !check_iommu_support();
+               die "no pci device info for device '$pciid'\n" if !$info;
+
+               if ($d->{driver} && $d->{driver} eq "vfio") {
+                   die "can't unbind/bind pci group to vfio '$pciid'\n" if !pci_dev_group_bind_to_vfio($pciid);
+               } else {
+                   die "can't unbind/bind to stub pci device '$pciid'\n" if !pci_dev_bind_to_stub($info);
+               }
+
+               die "can't reset pci device '$pciid'\n" if !pci_dev_reset($info);
+         }
         }
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
@@ -3562,6 +3765,61 @@ sub pci_dev_bind_to_stub {
     return -d $testdir;
 }
 
+sub pci_dev_bind_to_vfio {
+    my ($dev) = @_;
+
+    my $name = $dev->{name};
+
+    my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
+
+    if (!-d $vfio_basedir) {
+       system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
+    }
+    die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
+
+    my $testdir = "$vfio_basedir/$name";
+    return 1 if -d $testdir;
+
+    my $data = "$dev->{vendor} $dev->{product}";
+    return undef if !file_write("$vfio_basedir/new_id", $data);
+
+    my $fn = "$pcisysfs/devices/$name/driver/unbind";
+    if (!file_write($fn, $name)) {
+       return undef if -f $fn;
+    }
+
+    $fn = "$vfio_basedir/bind";
+    if (! -d $testdir) {
+       return undef if !file_write($fn, $name);
+    }
+
+    return -d $testdir;
+}
+
+sub pci_dev_group_bind_to_vfio {
+    my ($pciid) = @_;
+
+    my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
+
+    if (!-d $vfio_basedir) {
+       system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
+    }
+    die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
+
+    # get IOMMU group devices
+    opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n";
+      my @devs = grep /^0000:/, readdir($D);
+    closedir($D);
+
+    foreach my $pciid (@devs) {
+       $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n";
+       my $info = pci_device_info($1);
+       pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n";
+    }
+
+    return 1;
+}
+
 sub print_pci_addr {
     my ($id, $bridges) = @_;
 
@@ -3644,6 +3902,26 @@ sub print_pci_addr {
 
 }
 
+sub print_pcie_addr {
+    my ($id) = @_;
+
+    my $res = '';
+    my $devices = {
+       hostpci0 => { bus => "ich9-pcie-port-1", addr => 0 },
+       hostpci1 => { bus => "ich9-pcie-port-2", addr => 0 },
+       hostpci2 => { bus => "ich9-pcie-port-3", addr => 0 },
+       hostpci3 => { bus => "ich9-pcie-port-4", addr => 0 },
+    };
+
+    if (defined($devices->{$id}->{bus}) && defined($devices->{$id}->{addr})) {
+          my $addr = sprintf("0x%x", $devices->{$id}->{addr});
+          my $bus = $devices->{$id}->{bus};
+          $res = ",bus=$bus,addr=$addr";
+    }
+    return $res;
+
+}
+
 # vzdump restore implementaion
 
 sub tar_archive_read_firstfile {
@@ -4117,6 +4395,10 @@ sub restore_vma_archive {
                my ($dev_id, $size, $devname) = ($1, $2, $3);
                $devinfo->{$devname} = { size => $size, dev_id => $dev_id };
            } elsif ($line =~ m/^CTIME: /) {
+               # we correctly received the vma config, so we can disable
+               # the timeout now for disk allocation (set to 10 minutes, so
+               # that we always timeout if something goes wrong)
+               alarm(600);
                &$print_devmap();
                print $fifofh "done\n";
                my $tmp = $oldtimeout || 0;
@@ -4653,10 +4935,12 @@ sub snapshot_delete {
        die "snapshot '$snapname' does not exist\n" if !defined($snap);
 
        # remove parent refs
-       &$unlink_parent($conf, $snap->{parent});
-       foreach my $sn (keys %{$conf->{snapshots}}) {
-           next if $sn eq $snapname;
-           &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
+       if (!$prepare) {
+           &$unlink_parent($conf, $snap->{parent});
+           foreach my $sn (keys %{$conf->{snapshots}}) {
+               next if $sn eq $snapname;
+               &$unlink_parent($conf->{snapshots}->{$sn}, $snap->{parent});
+           }
        }
 
        if ($remove_drive) {
@@ -4908,7 +5192,7 @@ sub clone_disk {
 
     if (!$full) {
        print "create linked clone of drive $drivename ($drive->{file})\n";
-       $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newvmid);
+       $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newvmid, $snapname);
        push @$newvollist, $newvolid;
     } else {
        my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
@@ -4963,4 +5247,17 @@ sub get_current_qemu_machine {
     return $current || $default || 'pc';
 }
 
+sub lspci {
+
+    my $devices = {};
+
+    dir_glob_foreach("$pcisysfs/devices", '[a-f0-9]{4}:([a-f0-9]{2}:[a-f0-9]{2})\.([0-9])', sub {
+            my (undef, $id, $function) = @_;
+           my $res = { id => $id, function => $function};
+           push @{$devices->{$id}}, $res;
+    });
+
+    return $devices;
+}
+
 1;